diff --git a/src/main.asm b/src/main.asm index 59abdd3..6ced0d2 100644 --- a/src/main.asm +++ b/src/main.asm @@ -1,16352 +1,16352 @@ -;SMBDIS.ASM - A COMPREHENSIVE SUPER MARIO BROS. DISASSEMBLY -;by doppelganger (doppelheathen@gmail.com) - -;This file is provided for your own use as-is. It will require the character rom data -;and an iNES file header to get it to work. - -;There are so many people I have to thank for this, and without their help this would -;probably not be possible. So I thank all the peeps in the nesdev scene whose insight into -;the 6502 and the NES helped me learn how it works (you guys know who you are, there's no -;way I could have done this without your help), as well as the authors of x816 and SMB -;Utility, and the reverse-engineers who did the original Super Mario Bros. Hacking Project, -;which I compared notes with but did not copy from. Last but certainly not least, I thank -;Nintendo for creating this game and the NES, without which this disassembly would -;only be theory. - -;Update: removed residual note under ChkStart label. Thanks to ShaneM for pointing out -;the error. - -;Assembles with x816. - -;------------------------------------------------------------------------------------- -;DEFINES - -;NES specific hardware defines - -PPU_CTRL_REG1 = $2000 -PPU_CTRL_REG2 = $2001 -PPU_STATUS = $2002 -PPU_SPR_ADDR = $2003 -PPU_SPR_DATA = $2004 -PPU_SCROLL_REG = $2005 -PPU_ADDRESS = $2006 -PPU_DATA = $2007 - -SND_REGISTER = $4000 -SND_SQUARE1_REG = $4000 -SND_SQUARE2_REG = $4004 -SND_TRIANGLE_REG = $4008 -SND_NOISE_REG = $400c -SND_DELTA_REG = $4010 -SND_MASTERCTRL_REG = $4015 - -SPR_DMA = $4014 -JOYPAD_PORT = $4016 -JOYPAD_PORT1 = $4016 -JOYPAD_PORT2 = $4017 - -; GAME SPECIFIC DEFINES - -ObjectOffset = $08 - -FrameCounter = $09 - -SavedJoypadBits = $06fc -SavedJoypad1Bits = $06fc -SavedJoypad2Bits = $06fd -JoypadBitMask = $074a -JoypadOverride = $0758 - -A_B_Buttons = $0a -PreviousA_B_Buttons = $0d -Up_Down_Buttons = $0b -Left_Right_Buttons = $0c - -GameEngineSubroutine = $0e - -Mirror_PPU_CTRL_REG1 = $0778 -Mirror_PPU_CTRL_REG2 = $0779 - -OperMode = $0770 -OperMode_Task = $0772 -ScreenRoutineTask = $073c - -GamePauseStatus = $0776 -GamePauseTimer = $0777 - -DemoAction = $0717 -DemoActionTimer = $0718 - -TimerControl = $0747 -IntervalTimerControl = $077f - -Timers = $0780 -SelectTimer = $0780 -PlayerAnimTimer = $0781 -JumpSwimTimer = $0782 -RunningTimer = $0783 -BlockBounceTimer = $0784 -SideCollisionTimer = $0785 -JumpspringTimer = $0786 -GameTimerCtrlTimer = $0787 -ClimbSideTimer = $0789 -EnemyFrameTimer = $078a -FrenzyEnemyTimer = $078f -BowserFireBreathTimer = $0790 -StompTimer = $0791 -AirBubbleTimer = $0792 -ScrollIntervalTimer = $0795 -EnemyIntervalTimer = $0796 -BrickCoinTimer = $079d -InjuryTimer = $079e -StarInvincibleTimer = $079f -ScreenTimer = $07a0 -WorldEndTimer = $07a1 -DemoTimer = $07a2 - -Sprite_Data = $0200 - -Sprite_Y_Position = $0200 -Sprite_Tilenumber = $0201 -Sprite_Attributes = $0202 -Sprite_X_Position = $0203 - -ScreenEdge_PageLoc = $071a -ScreenEdge_X_Pos = $071c -ScreenLeft_PageLoc = $071a -ScreenRight_PageLoc = $071b -ScreenLeft_X_Pos = $071c -ScreenRight_X_Pos = $071d - -PlayerFacingDir = $33 -DestinationPageLoc = $34 -VictoryWalkControl = $35 -ScrollFractional = $0768 -PrimaryMsgCounter = $0719 -SecondaryMsgCounter = $0749 - -HorizontalScroll = $073f -VerticalScroll = $0740 -ScrollLock = $0723 -ScrollThirtyTwo = $073d -Player_X_Scroll = $06ff -Player_Pos_ForScroll = $0755 -ScrollAmount = $0775 - -AreaData = $e7 -AreaDataLow = $e7 -AreaDataHigh = $e8 -EnemyData = $e9 -EnemyDataLow = $e9 -EnemyDataHigh = $ea - -AreaParserTaskNum = $071f -ColumnSets = $071e -CurrentPageLoc = $0725 -CurrentColumnPos = $0726 -BackloadingFlag = $0728 -BehindAreaParserFlag = $0729 -AreaObjectPageLoc = $072a -AreaObjectPageSel = $072b -AreaDataOffset = $072c -AreaObjOffsetBuffer = $072d -AreaObjectLength = $0730 -StaircaseControl = $0734 -AreaObjectHeight = $0735 -MushroomLedgeHalfLen = $0736 -EnemyDataOffset = $0739 -EnemyObjectPageLoc = $073a -EnemyObjectPageSel = $073b -MetatileBuffer = $06a1 -BlockBufferColumnPos = $06a0 -CurrentNTAddr_Low = $0721 -CurrentNTAddr_High = $0720 -AttributeBuffer = $03f9 - -LoopCommand = $0745 - -DisplayDigits = $07d7 -TopScoreDisplay = $07d7 -ScoreAndCoinDisplay = $07dd -PlayerScoreDisplay = $07dd -GameTimerDisplay = $07f8 -DigitModifier = $0134 - -VerticalFlipFlag = $0109 -FloateyNum_Control = $0110 -ShellChainCounter = $0125 -FloateyNum_Timer = $012c -FloateyNum_X_Pos = $0117 -FloateyNum_Y_Pos = $011e -FlagpoleFNum_Y_Pos = $010d -FlagpoleFNum_YMFDummy = $010e -FlagpoleScore = $010f -FlagpoleCollisionYPos = $070f -StompChainCounter = $0484 - -VRAM_Buffer1_Offset = $0300 -VRAM_Buffer1 = $0301 -VRAM_Buffer2_Offset = $0340 -VRAM_Buffer2 = $0341 -VRAM_Buffer_AddrCtrl = $0773 -Sprite0HitDetectFlag = $0722 -DisableScreenFlag = $0774 -DisableIntermediate = $0769 -ColorRotateOffset = $06d4 - -TerrainControl = $0727 -AreaStyle = $0733 -ForegroundScenery = $0741 -BackgroundScenery = $0742 -CloudTypeOverride = $0743 -BackgroundColorCtrl = $0744 -AreaType = $074e -AreaAddrsLOffset = $074f -AreaPointer = $0750 - -PlayerEntranceCtrl = $0710 -GameTimerSetting = $0715 -AltEntranceControl = $0752 -EntrancePage = $0751 -NumberOfPlayers = $077a -WarpZoneControl = $06d6 -ChangeAreaTimer = $06de - -MultiLoopCorrectCntr = $06d9 -MultiLoopPassCntr = $06da - -FetchNewGameTimerFlag = $0757 -GameTimerExpiredFlag = $0759 - -PrimaryHardMode = $076a -SecondaryHardMode = $06cc -WorldSelectNumber = $076b -WorldSelectEnableFlag = $07fc -ContinueWorld = $07fd - -CurrentPlayer = $0753 -PlayerSize = $0754 -PlayerStatus = $0756 - -OnscreenPlayerInfo = $075a -NumberofLives = $075a ;used by current player -HalfwayPage = $075b -LevelNumber = $075c ;the actual dash number -Hidden1UpFlag = $075d -CoinTally = $075e -WorldNumber = $075f -AreaNumber = $0760 ;internal number used to find areas - -CoinTallyFor1Ups = $0748 - -OffscreenPlayerInfo = $0761 -OffScr_NumberofLives = $0761 ;used by offscreen player -OffScr_HalfwayPage = $0762 -OffScr_LevelNumber = $0763 -OffScr_Hidden1UpFlag = $0764 -OffScr_CoinTally = $0765 -OffScr_WorldNumber = $0766 -OffScr_AreaNumber = $0767 - -BalPlatformAlignment = $03a0 -Platform_X_Scroll = $03a1 -PlatformCollisionFlag = $03a2 -YPlatformTopYPos = $0401 -YPlatformCenterYPos = $58 - -BrickCoinTimerFlag = $06bc -StarFlagTaskControl = $0746 - -PseudoRandomBitReg = $07a7 -WarmBootValidation = $07ff - -SprShuffleAmtOffset = $06e0 -SprShuffleAmt = $06e1 -SprDataOffset = $06e4 -Player_SprDataOffset = $06e4 -Enemy_SprDataOffset = $06e5 -Block_SprDataOffset = $06ec -Alt_SprDataOffset = $06ec -Bubble_SprDataOffset = $06ee -FBall_SprDataOffset = $06f1 -Misc_SprDataOffset = $06f3 -SprDataOffset_Ctrl = $03ee - -Player_State = $1d -Enemy_State = $1e -Fireball_State = $24 -Block_State = $26 -Misc_State = $2a - -Player_MovingDir = $45 -Enemy_MovingDir = $46 - -SprObject_X_Speed = $57 -Player_X_Speed = $57 -Enemy_X_Speed = $58 -Fireball_X_Speed = $5e -Block_X_Speed = $60 -Misc_X_Speed = $64 - -Jumpspring_FixedYPos = $58 -JumpspringAnimCtrl = $070e -JumpspringForce = $06db - -SprObject_PageLoc = $6d -Player_PageLoc = $6d -Enemy_PageLoc = $6e -Fireball_PageLoc = $74 -Block_PageLoc = $76 -Misc_PageLoc = $7a -Bubble_PageLoc = $83 - -SprObject_X_Position = $86 -Player_X_Position = $86 -Enemy_X_Position = $87 -Fireball_X_Position = $8d -Block_X_Position = $8f -Misc_X_Position = $93 -Bubble_X_Position = $9c - -SprObject_Y_Speed = $9f -Player_Y_Speed = $9f -Enemy_Y_Speed = $a0 -Fireball_Y_Speed = $a6 -Block_Y_Speed = $a8 -Misc_Y_Speed = $ac - -SprObject_Y_HighPos = $b5 -Player_Y_HighPos = $b5 -Enemy_Y_HighPos = $b6 -Fireball_Y_HighPos = $bc -Block_Y_HighPos = $be -Misc_Y_HighPos = $c2 -Bubble_Y_HighPos = $cb - -SprObject_Y_Position = $ce -Player_Y_Position = $ce -Enemy_Y_Position = $cf -Fireball_Y_Position = $d5 -Block_Y_Position = $d7 -Misc_Y_Position = $db -Bubble_Y_Position = $e4 - -SprObject_Rel_XPos = $03ad -Player_Rel_XPos = $03ad -Enemy_Rel_XPos = $03ae -Fireball_Rel_XPos = $03af -Bubble_Rel_XPos = $03b0 -Block_Rel_XPos = $03b1 -Misc_Rel_XPos = $03b3 - -SprObject_Rel_YPos = $03b8 -Player_Rel_YPos = $03b8 -Enemy_Rel_YPos = $03b9 -Fireball_Rel_YPos = $03ba -Bubble_Rel_YPos = $03bb -Block_Rel_YPos = $03bc -Misc_Rel_YPos = $03be - -SprObject_SprAttrib = $03c4 -Player_SprAttrib = $03c4 -Enemy_SprAttrib = $03c5 - -SprObject_X_MoveForce = $0400 -Enemy_X_MoveForce = $0401 - -SprObject_YMF_Dummy = $0416 -Player_YMF_Dummy = $0416 -Enemy_YMF_Dummy = $0417 -Bubble_YMF_Dummy = $042c - -SprObject_Y_MoveForce = $0433 -Player_Y_MoveForce = $0433 -Enemy_Y_MoveForce = $0434 -Block_Y_MoveForce = $043c - -DisableCollisionDet = $0716 -Player_CollisionBits = $0490 -Enemy_CollisionBits = $0491 - -SprObj_BoundBoxCtrl = $0499 -Player_BoundBoxCtrl = $0499 -Enemy_BoundBoxCtrl = $049a -Fireball_BoundBoxCtrl = $04a0 -Misc_BoundBoxCtrl = $04a2 - -EnemyFrenzyBuffer = $06cb -EnemyFrenzyQueue = $06cd -Enemy_Flag = $0f -Enemy_ID = $16 - -PlayerGfxOffset = $06d5 -Player_XSpeedAbsolute = $0700 -FrictionAdderHigh = $0701 -FrictionAdderLow = $0702 -RunningSpeed = $0703 -SwimmingFlag = $0704 -Player_X_MoveForce = $0705 -DiffToHaltJump = $0706 -JumpOrigin_Y_HighPos = $0707 -JumpOrigin_Y_Position = $0708 -VerticalForce = $0709 -VerticalForceDown = $070a -PlayerChangeSizeFlag = $070b -PlayerAnimTimerSet = $070c -PlayerAnimCtrl = $070d -DeathMusicLoaded = $0712 -FlagpoleSoundQueue = $0713 -CrouchingFlag = $0714 -MaximumLeftSpeed = $0450 -MaximumRightSpeed = $0456 - -SprObject_OffscrBits = $03d0 -Player_OffscreenBits = $03d0 -Enemy_OffscreenBits = $03d1 -FBall_OffscreenBits = $03d2 -Bubble_OffscreenBits = $03d3 -Block_OffscreenBits = $03d4 -Misc_OffscreenBits = $03d6 -EnemyOffscrBitsMasked = $03d8 - -Cannon_Offset = $046a -Cannon_PageLoc = $046b -Cannon_X_Position = $0471 -Cannon_Y_Position = $0477 -Cannon_Timer = $047d - -Whirlpool_Offset = $046a -Whirlpool_PageLoc = $046b -Whirlpool_LeftExtent = $0471 -Whirlpool_Length = $0477 -Whirlpool_Flag = $047d - -VineFlagOffset = $0398 -VineHeight = $0399 -VineObjOffset = $039a -VineStart_Y_Position = $039d - -Block_Orig_YPos = $03e4 -Block_BBuf_Low = $03e6 -Block_Metatile = $03e8 -Block_PageLoc2 = $03ea -Block_RepFlag = $03ec -Block_ResidualCounter = $03f0 -Block_Orig_XPos = $03f1 - -BoundingBox_UL_XPos = $04ac -BoundingBox_UL_YPos = $04ad -BoundingBox_DR_XPos = $04ae -BoundingBox_DR_YPos = $04af -BoundingBox_UL_Corner = $04ac -BoundingBox_LR_Corner = $04ae -EnemyBoundingBoxCoord = $04b0 - -PowerUpType = $39 - -FireballBouncingFlag = $3a -FireballCounter = $06ce -FireballThrowingTimer = $0711 - -HammerEnemyOffset = $06ae -JumpCoinMiscOffset = $06b7 - -Block_Buffer_1 = $0500 -Block_Buffer_2 = $05d0 - -HammerThrowingTimer = $03a2 -HammerBroJumpTimer = $3c -Misc_Collision_Flag = $06be - -RedPTroopaOrigXPos = $0401 -RedPTroopaCenterYPos = $58 - -XMovePrimaryCounter = $a0 -XMoveSecondaryCounter = $58 - -CheepCheepMoveMFlag = $58 -CheepCheepOrigYPos = $0434 -BitMFilter = $06dd - -LakituReappearTimer = $06d1 -LakituMoveSpeed = $58 -LakituMoveDirection = $a0 - -FirebarSpinState_Low = $58 -FirebarSpinState_High = $a0 -FirebarSpinSpeed = $0388 -FirebarSpinDirection = $34 - -DuplicateObj_Offset = $06cf -NumberofGroupEnemies = $06d3 - -BlooperMoveCounter = $a0 -BlooperMoveSpeed = $58 - -BowserBodyControls = $0363 -BowserFeetCounter = $0364 -BowserMovementSpeed = $0365 -BowserOrigXPos = $0366 -BowserFlameTimerCtrl = $0367 -BowserFront_Offset = $0368 -BridgeCollapseOffset = $0369 -BowserGfxFlag = $036a -BowserHitPoints = $0483 -MaxRangeFromOrigin = $06dc - -BowserFlamePRandomOfs = $0417 - -PiranhaPlantUpYPos = $0417 -PiranhaPlantDownYPos = $0434 -PiranhaPlant_Y_Speed = $58 -PiranhaPlant_MoveFlag = $a0 - -FireworksCounter = $06d7 -ExplosionGfxCounter = $58 -ExplosionTimerCounter = $a0 - -;sound related defines -Squ2_NoteLenBuffer = $07b3 -Squ2_NoteLenCounter = $07b4 -Squ2_EnvelopeDataCtrl = $07b5 -Squ1_NoteLenCounter = $07b6 -Squ1_EnvelopeDataCtrl = $07b7 -Tri_NoteLenBuffer = $07b8 -Tri_NoteLenCounter = $07b9 -Noise_BeatLenCounter = $07ba -Squ1_SfxLenCounter = $07bb -Squ2_SfxLenCounter = $07bd -Sfx_SecondaryCounter = $07be -Noise_SfxLenCounter = $07bf - -PauseSoundQueue = $fa -Square1SoundQueue = $ff -Square2SoundQueue = $fe -NoiseSoundQueue = $fd -AreaMusicQueue = $fb -EventMusicQueue = $fc - -Square1SoundBuffer = $f1 -Square2SoundBuffer = $f2 -NoiseSoundBuffer = $f3 -AreaMusicBuffer = $f4 -EventMusicBuffer = $07b1 -PauseSoundBuffer = $07b2 - -MusicData = $f5 -MusicDataLow = $f5 -MusicDataHigh = $f6 -MusicOffset_Square2 = $f7 -MusicOffset_Square1 = $f8 -MusicOffset_Triangle = $f9 -MusicOffset_Noise = $07b0 - -NoteLenLookupTblOfs = $f0 -DAC_Counter = $07c0 -NoiseDataLoopbackOfs = $07c1 -NoteLengthTblAdder = $07c4 -AreaMusicBuffer_Alt = $07c5 -PauseModeFlag = $07c6 -GroundMusicHeaderOfs = $07c7 -AltRegContentFlag = $07ca - -;------------------------------------------------------------------------------------- -;CONSTANTS - -;sound effects constants -Sfx_SmallJump = %10000000 -Sfx_Flagpole = %01000000 -Sfx_Fireball = %00100000 -Sfx_PipeDown_Injury = %00010000 -Sfx_EnemySmack = %00001000 -Sfx_EnemyStomp = %00000100 -Sfx_Bump = %00000010 -Sfx_BigJump = %00000001 - -Sfx_BowserFall = %10000000 -Sfx_ExtraLife = %01000000 -Sfx_PowerUpGrab = %00100000 -Sfx_TimerTick = %00010000 -Sfx_Blast = %00001000 -Sfx_GrowVine = %00000100 -Sfx_GrowPowerUp = %00000010 -Sfx_CoinGrab = %00000001 - -Sfx_BowserFlame = %00000010 -Sfx_BrickShatter = %00000001 - -;music constants -Silence = %10000000 - -StarPowerMusic = %01000000 -PipeIntroMusic = %00100000 -CloudMusic = %00010000 -CastleMusic = %00001000 -UndergroundMusic = %00000100 -WaterMusic = %00000010 -GroundMusic = %00000001 - -TimeRunningOutMusic = %01000000 -EndOfLevelMusic = %00100000 -AltGameOverMusic = %00010000 -EndOfCastleMusic = %00001000 -VictoryMusic = %00000100 -GameOverMusic = %00000010 -DeathMusic = %00000001 - -;enemy object constants -GreenKoopa = $00 -BuzzyBeetle = $02 -RedKoopa = $03 -HammerBro = $05 -Goomba = $06 -Bloober = $07 -BulletBill_FrenzyVar = $08 -GreyCheepCheep = $0a -RedCheepCheep = $0b -Podoboo = $0c -PiranhaPlant = $0d -GreenParatroopaJump = $0e -RedParatroopa = $0f -GreenParatroopaFly = $10 -Lakitu = $11 -Spiny = $12 -FlyCheepCheepFrenzy = $14 -FlyingCheepCheep = $14 -BowserFlame = $15 -Fireworks = $16 -BBill_CCheep_Frenzy = $17 -Stop_Frenzy = $18 -Bowser = $2d -PowerUpObject = $2e -VineObject = $2f -FlagpoleFlagObject = $30 -StarFlagObject = $31 -JumpspringObject = $32 -BulletBill_CannonVar = $33 -RetainerObject = $35 -TallEnemy = $09 - -;other constants -World1 = 0 -World2 = 1 -World3 = 2 -World4 = 3 -World5 = 4 -World6 = 5 -World7 = 6 -World8 = 7 -Level1 = 0 -Level2 = 1 -Level3 = 2 -Level4 = 3 - -WarmBootOffset = <$07d6 -ColdBootOffset = <$07fe -TitleScreenDataOffset = $1ec0 -SoundMemory = $07b0 -SwimTileRepOffset = PlayerGraphicsTable + $9e -MusicHeaderOffsetData = MusicHeaderData - 1 -MHD = MusicHeaderData - -A_Button = %10000000 -B_Button = %01000000 -Select_Button = %00100000 -Start_Button = %00010000 -Up_Dir = %00001000 -Down_Dir = %00000100 -Left_Dir = %00000010 -Right_Dir = %00000001 - -TitleScreenModeValue = 0 -GameModeValue = 1 -VictoryModeValue = 2 -GameOverModeValue = 3 - -;------------------------------------------------------------------------------------- -;DIRECTIVES - -; .index 8 -; .mem 8 - - .org $8000 - -;------------------------------------------------------------------------------------- - -Start: - sei ;pretty standard 6502 type init here - cld - lda #%00010000 ;init PPU control register 1 - sta PPU_CTRL_REG1 - ldx #$ff ;reset stack pointer - txs -VBlank1: lda PPU_STATUS ;wait two frames - bpl VBlank1 -VBlank2: lda PPU_STATUS - bpl VBlank2 - ldy #ColdBootOffset ;load default cold boot pointer - ldx #$05 ;this is where we check for a warm boot -WBootCheck: lda TopScoreDisplay,x ;check each score digit in the top score - cmp #10 ;to see if we have a valid digit - bcs ColdBoot ;if not, give up and proceed with cold boot - dex - bpl WBootCheck - lda WarmBootValidation ;second checkpoint, check to see if - cmp #$a5 ;another location has a specific value - bne ColdBoot - ldy #WarmBootOffset ;if passed both, load warm boot pointer -ColdBoot: jsr InitializeMemory ;clear memory using pointer in Y - sta SND_DELTA_REG+1 ;reset delta counter load register - sta OperMode ;reset primary mode of operation - lda #$a5 ;set warm boot flag - sta WarmBootValidation - sta PseudoRandomBitReg ;set seed for pseudorandom register - lda #%00001111 - sta SND_MASTERCTRL_REG ;enable all sound channels except dmc - lda #%00000110 - sta PPU_CTRL_REG2 ;turn off clipping for OAM and background - jsr MoveAllSpritesOffscreen - jsr InitializeNameTables ;initialize both name tables - inc DisableScreenFlag ;set flag to disable screen output - lda Mirror_PPU_CTRL_REG1 - ora #%10000000 ;enable NMIs - jsr WritePPUReg1 -EndlessLoop: jmp EndlessLoop ;endless loop, need I say more? - -;------------------------------------------------------------------------------------- -;$00 - vram buffer address table low, also used for pseudorandom bit -;$01 - vram buffer address table high - -VRAM_AddrTable_Low: - .db VRAM_Buffer1, >WaterPaletteData, >GroundPaletteData - .db >UndergroundPaletteData, >CastlePaletteData, >VRAM_Buffer1_Offset - .db >VRAM_Buffer2, >VRAM_Buffer2, >BowserPaletteData - .db >DaySnowPaletteData, >NightSnowPaletteData, >MushroomPaletteData - .db >MarioThanksMessage, >LuigiThanksMessage, >MushroomRetainerSaved - .db >PrincessSaved1, >PrincessSaved2, >WorldSelectMessage1 - .db >WorldSelectMessage2 - -VRAM_Buffer_Offset: - .db $09 - lda Enemy_State,x - cmp #$02 ;if enemy state defeated or otherwise - bcs FloateyPart ;$02 or greater, branch beyond this part -GetAltOffset: ldx SprDataOffset_Ctrl ;load some kind of control bit - ldy Alt_SprDataOffset,x ;get alternate OAM data offset - ldx ObjectOffset ;get enemy object offset again -FloateyPart: lda FloateyNum_Y_Pos,x ;get vertical coordinate for - cmp #$18 ;floatey number, if coordinate in the - bcc SetupNumSpr ;status bar, branch - sbc #$01 - sta FloateyNum_Y_Pos,x ;otherwise subtract one and store as new -SetupNumSpr: lda FloateyNum_Y_Pos,x ;get vertical coordinate - sbc #$08 ;subtract eight and dump into the - jsr DumpTwoSpr ;left and right sprite's Y coordinates - lda FloateyNum_X_Pos,x ;get horizontal coordinate - sta Sprite_X_Position,y ;store into X coordinate of left sprite - clc - adc #$08 ;add eight pixels and store into X - sta Sprite_X_Position+4,y ;coordinate of right sprite - lda #$02 - sta Sprite_Attributes,y ;set palette control in attribute bytes - sta Sprite_Attributes+4,y ;of left and right sprites - lda FloateyNum_Control,x - asl ;multiply our floatey number control by 2 - tax ;and use as offset for look-up table - lda FloateyNumTileData,x - sta Sprite_Tilenumber,y ;display first half of number of points - lda FloateyNumTileData+1,x - sta Sprite_Tilenumber+4,y ;display the second half - ldx ObjectOffset ;get enemy object offset and leave - rts - -;------------------------------------------------------------------------------------- - -ScreenRoutines: - lda ScreenRoutineTask ;run one of the following subroutines - jsr JumpEngine - - .dw InitScreen - .dw SetupIntermediate - .dw WriteTopStatusLine - .dw WriteBottomStatusLine - .dw DisplayTimeUp - .dw ResetSpritesAndScreenTimer - .dw DisplayIntermediate - .dw ResetSpritesAndScreenTimer - .dw AreaParserTaskControl - .dw GetAreaPalette - .dw GetBackgroundColor - .dw GetAlternatePalette1 - .dw DrawTitleScreen - .dw ClearBuffersDrawIcon - .dw WriteTopScore - -;------------------------------------------------------------------------------------- - -InitScreen: - jsr MoveAllSpritesOffscreen ;initialize all sprites including sprite #0 - jsr InitializeNameTables ;and erase both name and attribute tables - lda OperMode - beq NextSubtask ;if mode still 0, do not load - ldx #$03 ;into buffer pointer - jmp SetVRAMAddr_A - -;------------------------------------------------------------------------------------- - -SetupIntermediate: - lda BackgroundColorCtrl ;save current background color control - pha ;and player status to stack - lda PlayerStatus - pha - lda #$00 ;set background color to black - sta PlayerStatus ;and player status to not fiery - lda #$02 ;this is the ONLY time background color control - sta BackgroundColorCtrl ;is set to less than 4 - jsr GetPlayerColors - pla ;we only execute this routine for - sta PlayerStatus ;the intermediate lives display - pla ;and once we're done, we return bg - sta BackgroundColorCtrl ;color ctrl and player status from stack - jmp IncSubtask ;then move onto the next task - -;------------------------------------------------------------------------------------- - -AreaPalette: - .db $01, $02, $03, $04 - -GetAreaPalette: - ldy AreaType ;select appropriate palette to load - ldx AreaPalette,y ;based on area type -SetVRAMAddr_A: stx VRAM_Buffer_AddrCtrl ;store offset into buffer control -NextSubtask: jmp IncSubtask ;move onto next task - -;------------------------------------------------------------------------------------- -;$00 - used as temp counter in GetPlayerColors - -BGColorCtrl_Addr: - .db $00, $09, $0a, $04 - -BackgroundColors: - .db $22, $22, $0f, $0f ;used by area type if bg color ctrl not set - .db $0f, $22, $0f, $0f ;used by background color control if set - -PlayerColors: - .db $22, $16, $27, $18 ;mario's colors - .db $22, $30, $27, $19 ;luigi's colors - .db $22, $37, $27, $16 ;fiery (used by both) - -GetBackgroundColor: - ldy BackgroundColorCtrl ;check background color control - beq NoBGColor ;if not set, increment task and fetch palette - lda BGColorCtrl_Addr-4,y ;put appropriate palette into vram - sta VRAM_Buffer_AddrCtrl ;note that if set to 5-7, $0301 will not be read -NoBGColor: inc ScreenRoutineTask ;increment to next subtask and plod on through - -GetPlayerColors: - ldx VRAM_Buffer1_Offset ;get current buffer offset - ldy #$00 - lda CurrentPlayer ;check which player is on the screen - beq ChkFiery - ldy #$04 ;load offset for luigi -ChkFiery: lda PlayerStatus ;check player status - cmp #$02 - bne StartClrGet ;if fiery, load alternate offset for fiery player - ldy #$08 -StartClrGet: lda #$03 ;do four colors - sta $00 -ClrGetLoop: lda PlayerColors,y ;fetch player colors and store them - sta VRAM_Buffer1+3,x ;in the buffer - iny - inx - dec $00 - bpl ClrGetLoop - ldx VRAM_Buffer1_Offset ;load original offset from before - ldy BackgroundColorCtrl ;if this value is four or greater, it will be set - bne SetBGColor ;therefore use it as offset to background color - ldy AreaType ;otherwise use area type bits from area offset as offset -SetBGColor: lda BackgroundColors,y ;to background color instead - sta VRAM_Buffer1+3,x - lda #$3f ;set for sprite palette address - sta VRAM_Buffer1,x ;save to buffer - lda #$10 - sta VRAM_Buffer1+1,x - lda #$04 ;write length byte to buffer - sta VRAM_Buffer1+2,x - lda #$00 ;now the null terminator - sta VRAM_Buffer1+7,x - txa ;move the buffer pointer ahead 7 bytes - clc ;in case we want to write anything else later - adc #$07 -SetVRAMOffset: sta VRAM_Buffer1_Offset ;store as new vram buffer offset - rts - -;------------------------------------------------------------------------------------- - -GetAlternatePalette1: - lda AreaStyle ;check for mushroom level style - cmp #$01 - bne NoAltPal - lda #$0b ;if found, load appropriate palette -SetVRAMAddr_B: sta VRAM_Buffer_AddrCtrl -NoAltPal: jmp IncSubtask ;now onto the next task - -;------------------------------------------------------------------------------------- - -WriteTopStatusLine: - lda #$00 ;select main status bar - jsr WriteGameText ;output it - jmp IncSubtask ;onto the next task - -;------------------------------------------------------------------------------------- - -WriteBottomStatusLine: - jsr GetSBNybbles ;write player's score and coin tally to screen - ldx VRAM_Buffer1_Offset - lda #$20 ;write address for world-area number on screen - sta VRAM_Buffer1,x - lda #$73 - sta VRAM_Buffer1+1,x - lda #$03 ;write length for it - sta VRAM_Buffer1+2,x - ldy WorldNumber ;first the world number - iny - tya - sta VRAM_Buffer1+3,x - lda #$28 ;next the dash - sta VRAM_Buffer1+4,x - ldy LevelNumber ;next the level number - iny ;increment for proper number display - tya - sta VRAM_Buffer1+5,x - lda #$00 ;put null terminator on - sta VRAM_Buffer1+6,x - txa ;move the buffer offset up by 6 bytes - clc - adc #$06 - sta VRAM_Buffer1_Offset - jmp IncSubtask - -;------------------------------------------------------------------------------------- - -DisplayTimeUp: - lda GameTimerExpiredFlag ;if game timer not expired, increment task - beq NoTimeUp ;control 2 tasks forward, otherwise, stay here - lda #$00 - sta GameTimerExpiredFlag ;reset timer expiration flag - lda #$02 ;output time-up screen to buffer - jmp OutputInter -NoTimeUp: inc ScreenRoutineTask ;increment control task 2 tasks forward - jmp IncSubtask - -;------------------------------------------------------------------------------------- - -DisplayIntermediate: - lda OperMode ;check primary mode of operation - beq NoInter ;if in title screen mode, skip this - cmp #GameOverModeValue ;are we in game over mode? - beq GameOverInter ;if so, proceed to display game over screen - lda AltEntranceControl ;otherwise check for mode of alternate entry - bne NoInter ;and branch if found - ldy AreaType ;check if we are on castle level - cpy #$03 ;and if so, branch (possibly residual) - beq PlayerInter - lda DisableIntermediate ;if this flag is set, skip intermediate lives display - bne NoInter ;and jump to specific task, otherwise -PlayerInter: jsr DrawPlayer_Intermediate ;put player in appropriate place for - lda #$01 ;lives display, then output lives display to buffer -OutputInter: jsr WriteGameText - jsr ResetScreenTimer - lda #$00 - sta DisableScreenFlag ;reenable screen output - rts -GameOverInter: lda #$12 ;set screen timer - sta ScreenTimer - lda #$03 ;output game over screen to buffer - jsr WriteGameText - jmp IncModeTask_B -NoInter: lda #$08 ;set for specific task and leave - sta ScreenRoutineTask - rts - -;------------------------------------------------------------------------------------- - -AreaParserTaskControl: - inc DisableScreenFlag ;turn off screen -TaskLoop: jsr AreaParserTaskHandler ;render column set of current area - lda AreaParserTaskNum ;check number of tasks - bne TaskLoop ;if tasks still not all done, do another one - dec ColumnSets ;do we need to render more column sets? - bpl OutputCol - inc ScreenRoutineTask ;if not, move on to the next task -OutputCol: lda #$06 ;set vram buffer to output rendered column set - sta VRAM_Buffer_AddrCtrl ;on next NMI - rts - -;------------------------------------------------------------------------------------- - -;$00 - vram buffer address table low -;$01 - vram buffer address table high - -DrawTitleScreen: - lda OperMode ;are we in title screen mode? - bne IncModeTask_B ;if not, exit - lda #>TitleScreenDataOffset ;load address $1ec0 into - sta PPU_ADDRESS ;the vram address register - lda #Palette0_MTiles, >Palette1_MTiles, >Palette2_MTiles, >Palette3_MTiles - -Palette0_MTiles: - .db $24, $24, $24, $24 ;blank - .db $27, $27, $27, $27 ;black metatile - .db $24, $24, $24, $35 ;bush left - .db $36, $25, $37, $25 ;bush middle - .db $24, $38, $24, $24 ;bush right - .db $24, $30, $30, $26 ;mountain left - .db $26, $26, $34, $26 ;mountain left bottom/middle center - .db $24, $31, $24, $32 ;mountain middle top - .db $33, $26, $24, $33 ;mountain right - .db $34, $26, $26, $26 ;mountain right bottom - .db $26, $26, $26, $26 ;mountain middle bottom - .db $24, $c0, $24, $c0 ;bridge guardrail - .db $24, $7f, $7f, $24 ;chain - .db $b8, $ba, $b9, $bb ;tall tree top, top half - .db $b8, $bc, $b9, $bd ;short tree top - .db $ba, $bc, $bb, $bd ;tall tree top, bottom half - .db $60, $64, $61, $65 ;warp pipe end left, points up - .db $62, $66, $63, $67 ;warp pipe end right, points up - .db $60, $64, $61, $65 ;decoration pipe end left, points up - .db $62, $66, $63, $67 ;decoration pipe end right, points up - .db $68, $68, $69, $69 ;pipe shaft left - .db $26, $26, $6a, $6a ;pipe shaft right - .db $4b, $4c, $4d, $4e ;tree ledge left edge - .db $4d, $4f, $4d, $4f ;tree ledge middle - .db $4d, $4e, $50, $51 ;tree ledge right edge - .db $6b, $70, $2c, $2d ;mushroom left edge - .db $6c, $71, $6d, $72 ;mushroom middle - .db $6e, $73, $6f, $74 ;mushroom right edge - .db $86, $8a, $87, $8b ;sideways pipe end top - .db $88, $8c, $88, $8c ;sideways pipe shaft top - .db $89, $8d, $69, $69 ;sideways pipe joint top - .db $8e, $91, $8f, $92 ;sideways pipe end bottom - .db $26, $93, $26, $93 ;sideways pipe shaft bottom - .db $90, $94, $69, $69 ;sideways pipe joint bottom - .db $a4, $e9, $ea, $eb ;seaplant - .db $24, $24, $24, $24 ;blank, used on bricks or blocks that are hit - .db $24, $2f, $24, $3d ;flagpole ball - .db $a2, $a2, $a3, $a3 ;flagpole shaft - .db $24, $24, $24, $24 ;blank, used in conjunction with vines - -Palette1_MTiles: - .db $a2, $a2, $a3, $a3 ;vertical rope - .db $99, $24, $99, $24 ;horizontal rope - .db $24, $a2, $3e, $3f ;left pulley - .db $5b, $5c, $24, $a3 ;right pulley - .db $24, $24, $24, $24 ;blank used for balance rope - .db $9d, $47, $9e, $47 ;castle top - .db $47, $47, $27, $27 ;castle window left - .db $47, $47, $47, $47 ;castle brick wall - .db $27, $27, $47, $47 ;castle window right - .db $a9, $47, $aa, $47 ;castle top w/ brick - .db $9b, $27, $9c, $27 ;entrance top - .db $27, $27, $27, $27 ;entrance bottom - .db $52, $52, $52, $52 ;green ledge stump - .db $80, $a0, $81, $a1 ;fence - .db $be, $be, $bf, $bf ;tree trunk - .db $75, $ba, $76, $bb ;mushroom stump top - .db $ba, $ba, $bb, $bb ;mushroom stump bottom - .db $45, $47, $45, $47 ;breakable brick w/ line - .db $47, $47, $47, $47 ;breakable brick - .db $45, $47, $45, $47 ;breakable brick (not used) - .db $b4, $b6, $b5, $b7 ;cracked rock terrain - .db $45, $47, $45, $47 ;brick with line (power-up) - .db $45, $47, $45, $47 ;brick with line (vine) - .db $45, $47, $45, $47 ;brick with line (star) - .db $45, $47, $45, $47 ;brick with line (coins) - .db $45, $47, $45, $47 ;brick with line (1-up) - .db $47, $47, $47, $47 ;brick (power-up) - .db $47, $47, $47, $47 ;brick (vine) - .db $47, $47, $47, $47 ;brick (star) - .db $47, $47, $47, $47 ;brick (coins) - .db $47, $47, $47, $47 ;brick (1-up) - .db $24, $24, $24, $24 ;hidden block (1 coin) - .db $24, $24, $24, $24 ;hidden block (1-up) - .db $ab, $ac, $ad, $ae ;solid block (3-d block) - .db $5d, $5e, $5d, $5e ;solid block (white wall) - .db $c1, $24, $c1, $24 ;bridge - .db $c6, $c8, $c7, $c9 ;bullet bill cannon barrel - .db $ca, $cc, $cb, $cd ;bullet bill cannon top - .db $2a, $2a, $40, $40 ;bullet bill cannon bottom - .db $24, $24, $24, $24 ;blank used for jumpspring - .db $24, $47, $24, $47 ;half brick used for jumpspring - .db $82, $83, $84, $85 ;solid block (water level, green rock) - .db $24, $47, $24, $47 ;half brick (???) - .db $86, $8a, $87, $8b ;water pipe top - .db $8e, $91, $8f, $92 ;water pipe bottom - .db $24, $2f, $24, $3d ;flag ball (residual object) - -Palette2_MTiles: - .db $24, $24, $24, $35 ;cloud left - .db $36, $25, $37, $25 ;cloud middle - .db $24, $38, $24, $24 ;cloud right - .db $24, $24, $39, $24 ;cloud bottom left - .db $3a, $24, $3b, $24 ;cloud bottom middle - .db $3c, $24, $24, $24 ;cloud bottom right - .db $41, $26, $41, $26 ;water/lava top - .db $26, $26, $26, $26 ;water/lava - .db $b0, $b1, $b2, $b3 ;cloud level terrain - .db $77, $79, $77, $79 ;bowser's bridge - -Palette3_MTiles: - .db $53, $55, $54, $56 ;question block (coin) - .db $53, $55, $54, $56 ;question block (power-up) - .db $a5, $a7, $a6, $a8 ;coin - .db $c2, $c4, $c3, $c5 ;underwater coin - .db $57, $59, $58, $5a ;empty block - .db $7b, $7d, $7c, $7e ;axe - -;------------------------------------------------------------------------------------- -;VRAM BUFFER DATA FOR LOCATIONS IN PRG-ROM - -WaterPaletteData: - .db $3f, $00, $20 - .db $0f, $15, $12, $25 - .db $0f, $3a, $1a, $0f - .db $0f, $30, $12, $0f - .db $0f, $27, $12, $0f - .db $22, $16, $27, $18 - .db $0f, $10, $30, $27 - .db $0f, $16, $30, $27 - .db $0f, $0f, $30, $10 - .db $00 - -GroundPaletteData: - .db $3f, $00, $20 - .db $0f, $29, $1a, $0f - .db $0f, $36, $17, $0f - .db $0f, $30, $21, $0f - .db $0f, $27, $17, $0f - .db $0f, $16, $27, $18 - .db $0f, $1a, $30, $27 - .db $0f, $16, $30, $27 - .db $0f, $0f, $36, $17 - .db $00 - -UndergroundPaletteData: - .db $3f, $00, $20 - .db $0f, $29, $1a, $09 - .db $0f, $3c, $1c, $0f - .db $0f, $30, $21, $1c - .db $0f, $27, $17, $1c - .db $0f, $16, $27, $18 - .db $0f, $1c, $36, $17 - .db $0f, $16, $30, $27 - .db $0f, $0c, $3c, $1c - .db $00 - -CastlePaletteData: - .db $3f, $00, $20 - .db $0f, $30, $10, $00 - .db $0f, $30, $10, $00 - .db $0f, $30, $16, $00 - .db $0f, $27, $17, $00 - .db $0f, $16, $27, $18 - .db $0f, $1c, $36, $17 - .db $0f, $16, $30, $27 - .db $0f, $00, $30, $10 - .db $00 - -DaySnowPaletteData: - .db $3f, $00, $04 - .db $22, $30, $00, $10 - .db $00 - -NightSnowPaletteData: - .db $3f, $00, $04 - .db $0f, $30, $00, $10 - .db $00 - -MushroomPaletteData: - .db $3f, $00, $04 - .db $22, $27, $16, $0f - .db $00 - -BowserPaletteData: - .db $3f, $14, $04 - .db $0f, $1a, $30, $27 - .db $00 - -MarioThanksMessage: -;"THANK YOU MARIO!" - .db $25, $48, $10 - .db $1d, $11, $0a, $17, $14, $24 - .db $22, $18, $1e, $24 - .db $16, $0a, $1b, $12, $18, $2b - .db $00 - -LuigiThanksMessage: -;"THANK YOU LUIGI!" - .db $25, $48, $10 - .db $1d, $11, $0a, $17, $14, $24 - .db $22, $18, $1e, $24 - .db $15, $1e, $12, $10, $12, $2b - .db $00 - -MushroomRetainerSaved: -;"BUT OUR PRINCESS IS IN" - .db $25, $c5, $16 - .db $0b, $1e, $1d, $24, $18, $1e, $1b, $24 - .db $19, $1b, $12, $17, $0c, $0e, $1c, $1c, $24 - .db $12, $1c, $24, $12, $17 -;"ANOTHER CASTLE!" - .db $26, $05, $0f - .db $0a, $17, $18, $1d, $11, $0e, $1b, $24 - .db $0c, $0a, $1c, $1d, $15, $0e, $2b, $00 - -PrincessSaved1: -;"YOUR QUEST IS OVER." - .db $25, $a7, $13 - .db $22, $18, $1e, $1b, $24 - .db $1a, $1e, $0e, $1c, $1d, $24 - .db $12, $1c, $24, $18, $1f, $0e, $1b, $af - .db $00 - -PrincessSaved2: -;"WE PRESENT YOU A NEW QUEST." - .db $25, $e3, $1b - .db $20, $0e, $24 - .db $19, $1b, $0e, $1c, $0e, $17, $1d, $24 - .db $22, $18, $1e, $24, $0a, $24, $17, $0e, $20, $24 - .db $1a, $1e, $0e, $1c, $1d, $af - .db $00 - -WorldSelectMessage1: -;"PUSH BUTTON B" - .db $26, $4a, $0d - .db $19, $1e, $1c, $11, $24 - .db $0b, $1e, $1d, $1d, $18, $17, $24, $0b - .db $00 - -WorldSelectMessage2: -;"TO SELECT A WORLD" - .db $26, $88, $11 - .db $1d, $18, $24, $1c, $0e, $15, $0e, $0c, $1d, $24 - .db $0a, $24, $20, $18, $1b, $15, $0d - .db $00 - -;------------------------------------------------------------------------------------- -;$04 - address low to jump address -;$05 - address high to jump address -;$06 - jump address low -;$07 - jump address high - -JumpEngine: - asl ;shift bit from contents of A - tay - pla ;pull saved return address from stack - sta $04 ;save to indirect - pla - sta $05 - iny - lda ($04),y ;load pointer from indirect - sta $06 ;note that if an RTS is performed in next routine - iny ;it will return to the execution before the sub - lda ($04),y ;that called this routine - sta $07 - jmp ($0006) ;jump to the address we loaded - -;------------------------------------------------------------------------------------- - -InitializeNameTables: - lda PPU_STATUS ;reset flip-flop - lda Mirror_PPU_CTRL_REG1 ;load mirror of ppu reg $2000 - ora #%00010000 ;set sprites for first 4k and background for second 4k - and #%11110000 ;clear rest of lower nybble, leave higher alone - jsr WritePPUReg1 - lda #$24 ;set vram address to start of name table 1 - jsr WriteNTAddr - lda #$20 ;and then set it to name table 0 -WriteNTAddr: sta PPU_ADDRESS - lda #$00 - sta PPU_ADDRESS - ldx #$04 ;clear name table with blank tile #24 - ldy #$c0 - lda #$24 -InitNTLoop: sta PPU_DATA ;count out exactly 768 tiles - dey - bne InitNTLoop - dex - bne InitNTLoop - ldy #64 ;now to clear the attribute table (with zero this time) - txa - sta VRAM_Buffer1_Offset ;init vram buffer 1 offset - sta VRAM_Buffer1 ;init vram buffer 1 -InitATLoop: sta PPU_DATA - dey - bne InitATLoop - sta HorizontalScroll ;reset scroll variables - sta VerticalScroll - jmp InitScroll ;initialize scroll registers to zero - -;------------------------------------------------------------------------------------- -;$00 - temp joypad bit - -ReadJoypads: - lda #$01 ;reset and clear strobe of joypad ports - sta JOYPAD_PORT - lsr - tax ;start with joypad 1's port - sta JOYPAD_PORT - jsr ReadPortBits - inx ;increment for joypad 2's port -ReadPortBits: ldy #$08 -PortLoop: pha ;push previous bit onto stack - lda JOYPAD_PORT,x ;read current bit on joypad port - sta $00 ;check d1 and d0 of port output - lsr ;this is necessary on the old - ora $00 ;famicom systems in japan - lsr - pla ;read bits from stack - rol ;rotate bit from carry flag - dey - bne PortLoop ;count down bits left - sta SavedJoypadBits,x ;save controller status here always - pha - and #%00110000 ;check for select or start - and JoypadBitMask,x ;if neither saved state nor current state - beq Save8Bits ;have any of these two set, branch - pla - and #%11001111 ;otherwise store without select - sta SavedJoypadBits,x ;or start bits and leave - rts -Save8Bits: pla - sta JoypadBitMask,x ;save with all bits in another place and leave - rts - -;------------------------------------------------------------------------------------- -;$00 - vram buffer address table low -;$01 - vram buffer address table high - -WriteBufferToScreen: - sta PPU_ADDRESS ;store high byte of vram address - iny - lda ($00),y ;load next byte (second) - sta PPU_ADDRESS ;store low byte of vram address - iny - lda ($00),y ;load next byte (third) - asl ;shift to left and save in stack - pha - lda Mirror_PPU_CTRL_REG1 ;load mirror of $2000, - ora #%00000100 ;set ppu to increment by 32 by default - bcs SetupWrites ;if d7 of third byte was clear, ppu will - and #%11111011 ;only increment by 1 -SetupWrites: jsr WritePPUReg1 ;write to register - pla ;pull from stack and shift to left again - asl - bcc GetLength ;if d6 of third byte was clear, do not repeat byte - ora #%00000010 ;otherwise set d1 and increment Y - iny -GetLength: lsr ;shift back to the right to get proper length - lsr ;note that d1 will now be in carry - tax -OutputToVRAM: bcs RepeatByte ;if carry set, repeat loading the same byte - iny ;otherwise increment Y to load next byte -RepeatByte: lda ($00),y ;load more data from buffer and write to vram - sta PPU_DATA - dex ;done writing? - bne OutputToVRAM - sec - tya - adc $00 ;add end length plus one to the indirect at $00 - sta $00 ;to allow this routine to read another set of updates - lda #$00 - adc $01 - sta $01 - lda #$3f ;sets vram address to $3f00 - sta PPU_ADDRESS - lda #$00 - sta PPU_ADDRESS - sta PPU_ADDRESS ;then reinitializes it for some reason - sta PPU_ADDRESS -UpdateScreen: ldx PPU_STATUS ;reset flip-flop - ldy #$00 ;load first byte from indirect as a pointer - lda ($00),y - bne WriteBufferToScreen ;if byte is zero we have no further updates to make here -InitScroll: sta PPU_SCROLL_REG ;store contents of A into scroll registers - sta PPU_SCROLL_REG ;and end whatever subroutine led us here - rts - -;------------------------------------------------------------------------------------- - -WritePPUReg1: - sta PPU_CTRL_REG1 ;write contents of A to PPU register 1 - sta Mirror_PPU_CTRL_REG1 ;and its mirror - rts - -;------------------------------------------------------------------------------------- -;$00 - used to store status bar nybbles -;$02 - used as temp vram offset -;$03 - used to store length of status bar number - -;status bar name table offset and length data -StatusBarData: - .db $f0, $06 ; top score display on title screen - .db $62, $06 ; player score - .db $62, $06 - .db $6d, $02 ; coin tally - .db $6d, $02 - .db $7a, $03 ; game timer - -StatusBarOffset: - .db $06, $0c, $12, $18, $1e, $24 - -PrintStatusBarNumbers: - sta $00 ;store player-specific offset - jsr OutputNumbers ;use first nybble to print the coin display - lda $00 ;move high nybble to low - lsr ;and print to score display - lsr - lsr - lsr - -OutputNumbers: - clc ;add 1 to low nybble - adc #$01 - and #%00001111 ;mask out high nybble - cmp #$06 - bcs ExitOutputN - pha ;save incremented value to stack for now and - asl ;shift to left and use as offset - tay - ldx VRAM_Buffer1_Offset ;get current buffer pointer - lda #$20 ;put at top of screen by default - cpy #$00 ;are we writing top score on title screen? - bne SetupNums - lda #$22 ;if so, put further down on the screen -SetupNums: sta VRAM_Buffer1,x - lda StatusBarData,y ;write low vram address and length of thing - sta VRAM_Buffer1+1,x ;we're printing to the buffer - lda StatusBarData+1,y - sta VRAM_Buffer1+2,x - sta $03 ;save length byte in counter - stx $02 ;and buffer pointer elsewhere for now - pla ;pull original incremented value from stack - tax - lda StatusBarOffset,x ;load offset to value we want to write - sec - sbc StatusBarData+1,y ;subtract from length byte we read before - tay ;use value as offset to display digits - ldx $02 -DigitPLoop: lda DisplayDigits,y ;write digits to the buffer - sta VRAM_Buffer1+3,x - inx - iny - dec $03 ;do this until all the digits are written - bne DigitPLoop - lda #$00 ;put null terminator at end - sta VRAM_Buffer1+3,x - inx ;increment buffer pointer by 3 - inx - inx - stx VRAM_Buffer1_Offset ;store it in case we want to use it again -ExitOutputN: rts - -;------------------------------------------------------------------------------------- - -DigitsMathRoutine: - lda OperMode ;check mode of operation - cmp #TitleScreenModeValue - beq EraseDMods ;if in title screen mode, branch to lock score - ldx #$05 -AddModLoop: lda DigitModifier,x ;load digit amount to increment - clc - adc DisplayDigits,y ;add to current digit - bmi BorrowOne ;if result is a negative number, branch to subtract - cmp #10 - bcs CarryOne ;if digit greater than $09, branch to add -StoreNewD: sta DisplayDigits,y ;store as new score or game timer digit - dey ;move onto next digits in score or game timer - dex ;and digit amounts to increment - bpl AddModLoop ;loop back if we're not done yet -EraseDMods: lda #$00 ;store zero here - ldx #$06 ;start with the last digit -EraseMLoop: sta DigitModifier-1,x ;initialize the digit amounts to increment - dex - bpl EraseMLoop ;do this until they're all reset, then leave - rts -BorrowOne: dec DigitModifier-1,x ;decrement the previous digit, then put $09 in - lda #$09 ;the game timer digit we're currently on to "borrow - bne StoreNewD ;the one", then do an unconditional branch back -CarryOne: sec ;subtract ten from our digit to make it a - sbc #10 ;proper BCD number, then increment the digit - inc DigitModifier-1,x ;preceding current digit to "carry the one" properly - jmp StoreNewD ;go back to just after we branched here - -;------------------------------------------------------------------------------------- - -UpdateTopScore: - ldx #$05 ;start with mario's score - jsr TopScoreCheck - ldx #$0b ;now do luigi's score - -TopScoreCheck: - ldy #$05 ;start with the lowest digit - sec -GetScoreDiff: lda PlayerScoreDisplay,x ;subtract each player digit from each high score digit - sbc TopScoreDisplay,y ;from lowest to highest, if any top score digit exceeds - dex ;any player digit, borrow will be set until a subsequent - dey ;subtraction clears it (player digit is higher than top) - bpl GetScoreDiff - bcc NoTopSc ;check to see if borrow is still set, if so, no new high score - inx ;increment X and Y once to the start of the score - iny -CopyScore: lda PlayerScoreDisplay,x ;store player's score digits into high score memory area - sta TopScoreDisplay,y - inx - iny - cpy #$06 ;do this until we have stored them all - bcc CopyScore -NoTopSc: rts - -;------------------------------------------------------------------------------------- - -DefaultSprOffsets: - .db $04, $30, $48, $60, $78, $90, $a8, $c0 - .db $d8, $e8, $24, $f8, $fc, $28, $2c - -Sprite0Data: - .db $18, $ff, $23, $58 - -;------------------------------------------------------------------------------------- - -InitializeGame: - ldy #$6f ;clear all memory as in initialization procedure, - jsr InitializeMemory ;but this time, clear only as far as $076f - ldy #$1f -ClrSndLoop: sta SoundMemory,y ;clear out memory used - dey ;by the sound engines - bpl ClrSndLoop - lda #$18 ;set demo timer - sta DemoTimer - jsr LoadAreaPointer - -InitializeArea: - ldy #$4b ;clear all memory again, only as far as $074b - jsr InitializeMemory ;this is only necessary in game mode - ldx #$21 - lda #$00 -ClrTimersLoop: sta Timers,x ;clear out memory between - dex ;$0780 and $07a1 - bpl ClrTimersLoop - lda HalfwayPage - ldy AltEntranceControl ;if AltEntranceControl not set, use halfway page, if any found - beq StartPage - lda EntrancePage ;otherwise use saved entry page number here -StartPage: sta ScreenLeft_PageLoc ;set as value here - sta CurrentPageLoc ;also set as current page - sta BackloadingFlag ;set flag here if halfway page or saved entry page number found - jsr GetScreenPosition ;get pixel coordinates for screen borders - ldy #$20 ;if on odd numbered page, use $2480 as start of rendering - and #%00000001 ;otherwise use $2080, this address used later as name table - beq SetInitNTHigh ;address for rendering of game area - ldy #$24 -SetInitNTHigh: sty CurrentNTAddr_High ;store name table address - ldy #$80 - sty CurrentNTAddr_Low - asl ;store LSB of page number in high nybble - asl ;of block buffer column position - asl - asl - sta BlockBufferColumnPos - dec AreaObjectLength ;set area object lengths for all empty - dec AreaObjectLength+1 - dec AreaObjectLength+2 - lda #$0b ;set value for renderer to update 12 column sets - sta ColumnSets ;12 column sets = 24 metatile columns = 1 1/2 screens - jsr GetAreaDataAddrs ;get enemy and level addresses and load header - lda PrimaryHardMode ;check to see if primary hard mode has been activated - bne SetSecHard ;if so, activate the secondary no matter where we're at - lda WorldNumber ;otherwise check world number - cmp #World5 ;if less than 5, do not activate secondary - bcc CheckHalfway - bne SetSecHard ;if not equal to, then world > 5, thus activate - lda LevelNumber ;otherwise, world 5, so check level number - cmp #Level3 ;if 1 or 2, do not set secondary hard mode flag - bcc CheckHalfway -SetSecHard: inc SecondaryHardMode ;set secondary hard mode flag for areas 5-3 and beyond -CheckHalfway: lda HalfwayPage - beq DoneInitArea - lda #$02 ;if halfway page set, overwrite start position from header - sta PlayerEntranceCtrl -DoneInitArea: lda #Silence ;silence music - sta AreaMusicQueue - lda #$01 ;disable screen output - sta DisableScreenFlag - inc OperMode_Task ;increment one of the modes - rts - -;------------------------------------------------------------------------------------- - -PrimaryGameSetup: - lda #$01 - sta FetchNewGameTimerFlag ;set flag to load game timer from header - sta PlayerSize ;set player's size to small - lda #$02 - sta NumberofLives ;give each player three lives - sta OffScr_NumberofLives - -SecondaryGameSetup: - lda #$00 - sta DisableScreenFlag ;enable screen output - tay -ClearVRLoop: sta VRAM_Buffer1-1,y ;clear buffer at $0300-$03ff - iny - bne ClearVRLoop - sta GameTimerExpiredFlag ;clear game timer exp flag - sta DisableIntermediate ;clear skip lives display flag - sta BackloadingFlag ;clear value here - lda #$ff - sta BalPlatformAlignment ;initialize balance platform assignment flag - lda ScreenLeft_PageLoc ;get left side page location - lsr Mirror_PPU_CTRL_REG1 ;shift LSB of ppu register #1 mirror out - and #$01 ;mask out all but LSB of page location - ror ;rotate LSB of page location into carry then onto mirror - rol Mirror_PPU_CTRL_REG1 ;this is to set the proper PPU name table - jsr GetAreaMusic ;load proper music into queue - lda #$38 ;load sprite shuffle amounts to be used later - sta SprShuffleAmt+2 - lda #$48 - sta SprShuffleAmt+1 - lda #$58 - sta SprShuffleAmt - ldx #$0e ;load default OAM offsets into $06e4-$06f2 -ShufAmtLoop: lda DefaultSprOffsets,x - sta SprDataOffset,x - dex ;do this until they're all set - bpl ShufAmtLoop - ldy #$03 ;set up sprite #0 -ISpr0Loop: lda Sprite0Data,y - sta Sprite_Data,y - dey - bpl ISpr0Loop - jsr DoNothing2 ;these jsrs doesn't do anything useful - jsr DoNothing1 - inc Sprite0HitDetectFlag ;set sprite #0 check flag - inc OperMode_Task ;increment to next task - rts - -;------------------------------------------------------------------------------------- - -;$06 - RAM address low -;$07 - RAM address high - -InitializeMemory: - ldx #$07 ;set initial high byte to $0700-$07ff - lda #$00 ;set initial low byte to start of page (at $00 of page) - sta $06 -InitPageLoop: stx $07 -InitByteLoop: cpx #$01 ;check to see if we're on the stack ($0100-$01ff) - bne InitByte ;if not, go ahead anyway - cpy #$60 ;otherwise, check to see if we're at $0160-$01ff - bcs SkipByte ;if so, skip write -InitByte: sta ($06),y ;otherwise, initialize byte with current low byte in Y -SkipByte: dey - cpy #$ff ;do this until all bytes in page have been erased - bne InitByteLoop - dex ;go onto the next page - bpl InitPageLoop ;do this until all pages of memory have been erased - rts - -;------------------------------------------------------------------------------------- - -MusicSelectData: - .db WaterMusic, GroundMusic, UndergroundMusic, CastleMusic - .db CloudMusic, PipeIntroMusic - -GetAreaMusic: - lda OperMode ;if in title screen mode, leave - beq ExitGetM - lda AltEntranceControl ;check for specific alternate mode of entry - cmp #$02 ;if found, branch without checking starting position - beq ChkAreaType ;from area object data header - ldy #$05 ;select music for pipe intro scene by default - lda PlayerEntranceCtrl ;check value from level header for certain values - cmp #$06 - beq StoreMusic ;load music for pipe intro scene if header - cmp #$07 ;start position either value $06 or $07 - beq StoreMusic -ChkAreaType: ldy AreaType ;load area type as offset for music bit - lda CloudTypeOverride - beq StoreMusic ;check for cloud type override - ldy #$04 ;select music for cloud type level if found -StoreMusic: lda MusicSelectData,y ;otherwise select appropriate music for level type - sta AreaMusicQueue ;store in queue and leave -ExitGetM: rts - -;------------------------------------------------------------------------------------- - -PlayerStarting_X_Pos: - .db $28, $18 - .db $38, $28 - -AltYPosOffset: - .db $08, $00 - -PlayerStarting_Y_Pos: - .db $00, $20, $b0, $50, $00, $00, $b0, $b0 - .db $f0 - -PlayerBGPriorityData: - .db $00, $20, $00, $00, $00, $00, $00, $00 - -GameTimerData: - .db $20 ;dummy byte, used as part of bg priority data - .db $04, $03, $02 - -Entrance_GameTimerSetup: - lda ScreenLeft_PageLoc ;set current page for area objects - sta Player_PageLoc ;as page location for player - lda #$28 ;store value here - sta VerticalForceDown ;for fractional movement downwards if necessary - lda #$01 ;set high byte of player position and - sta PlayerFacingDir ;set facing direction so that player faces right - sta Player_Y_HighPos - lda #$00 ;set player state to on the ground by default - sta Player_State - dec Player_CollisionBits ;initialize player's collision bits - ldy #$00 ;initialize halfway page - sty HalfwayPage - lda AreaType ;check area type - bne ChkStPos ;if water type, set swimming flag, otherwise do not set - iny -ChkStPos: sty SwimmingFlag - ldx PlayerEntranceCtrl ;get starting position loaded from header - ldy AltEntranceControl ;check alternate mode of entry flag for 0 or 1 - beq SetStPos - cpy #$01 - beq SetStPos - ldx AltYPosOffset-2,y ;if not 0 or 1, override $0710 with new offset in X -SetStPos: lda PlayerStarting_X_Pos,y ;load appropriate horizontal position - sta Player_X_Position ;and vertical positions for the player, using - lda PlayerStarting_Y_Pos,x ;AltEntranceControl as offset for horizontal and either $0710 - sta Player_Y_Position ;or value that overwrote $0710 as offset for vertical - lda PlayerBGPriorityData,x - sta Player_SprAttrib ;set player sprite attributes using offset in X - jsr GetPlayerColors ;get appropriate player palette - ldy GameTimerSetting ;get timer control value from header - beq ChkOverR ;if set to zero, branch (do not use dummy byte for this) - lda FetchNewGameTimerFlag ;do we need to set the game timer? if not, use - beq ChkOverR ;old game timer setting - lda GameTimerData,y ;if game timer is set and game timer flag is also set, - sta GameTimerDisplay ;use value of game timer control for first digit of game timer - lda #$01 - sta GameTimerDisplay+2 ;set last digit of game timer to 1 - lsr - sta GameTimerDisplay+1 ;set second digit of game timer - sta FetchNewGameTimerFlag ;clear flag for game timer reset - sta StarInvincibleTimer ;clear star mario timer -ChkOverR: ldy JoypadOverride ;if controller bits not set, branch to skip this part - beq ChkSwimE - lda #$03 ;set player state to climbing - sta Player_State - ldx #$00 ;set offset for first slot, for block object - jsr InitBlock_XY_Pos - lda #$f0 ;set vertical coordinate for block object - sta Block_Y_Position - ldx #$05 ;set offset in X for last enemy object buffer slot - ldy #$00 ;set offset in Y for object coordinates used earlier - jsr Setup_Vine ;do a sub to grow vine -ChkSwimE: ldy AreaType ;if level not water-type, - bne SetPESub ;skip this subroutine - jsr SetupBubble ;otherwise, execute sub to set up air bubbles -SetPESub: lda #$07 ;set to run player entrance subroutine - sta GameEngineSubroutine ;on the next frame of game engine - rts - -;------------------------------------------------------------------------------------- - -;page numbers are in order from -1 to -4 -HalfwayPageNybbles: - .db $56, $40 - .db $65, $70 - .db $66, $40 - .db $66, $40 - .db $66, $40 - .db $66, $60 - .db $65, $70 - .db $00, $00 - -PlayerLoseLife: - inc DisableScreenFlag ;disable screen and sprite 0 check - lda #$00 - sta Sprite0HitDetectFlag - lda #Silence ;silence music - sta EventMusicQueue - dec NumberofLives ;take one life from player - bpl StillInGame ;if player still has lives, branch - lda #$00 - sta OperMode_Task ;initialize mode task, - lda #GameOverModeValue ;switch to game over mode - sta OperMode ;and leave - rts -StillInGame: lda WorldNumber ;multiply world number by 2 and use - asl ;as offset - tax - lda LevelNumber ;if in area -3 or -4, increment - and #$02 ;offset by one byte, otherwise - beq GetHalfway ;leave offset alone - inx -GetHalfway: ldy HalfwayPageNybbles,x ;get halfway page number with offset - lda LevelNumber ;check area number's LSB - lsr - tya ;if in area -2 or -4, use lower nybble - bcs MaskHPNyb - lsr ;move higher nybble to lower if area - lsr ;number is -1 or -3 - lsr - lsr -MaskHPNyb: and #%00001111 ;mask out all but lower nybble - cmp ScreenLeft_PageLoc - beq SetHalfway ;left side of screen must be at the halfway page, - bcc SetHalfway ;otherwise player must start at the - lda #$00 ;beginning of the level -SetHalfway: sta HalfwayPage ;store as halfway page for player - jsr TransposePlayers ;switch players around if 2-player game - jmp ContinueGame ;continue the game - -;------------------------------------------------------------------------------------- - -GameOverMode: - lda OperMode_Task - jsr JumpEngine - - .dw SetupGameOver - .dw ScreenRoutines - .dw RunGameOver - -;------------------------------------------------------------------------------------- - -SetupGameOver: - lda #$00 ;reset screen routine task control for title screen, game, - sta ScreenRoutineTask ;and game over modes - sta Sprite0HitDetectFlag ;disable sprite 0 check - lda #GameOverMusic - sta EventMusicQueue ;put game over music in secondary queue - inc DisableScreenFlag ;disable screen output - inc OperMode_Task ;set secondary mode to 1 - rts - -;------------------------------------------------------------------------------------- - -RunGameOver: - lda #$00 ;reenable screen - sta DisableScreenFlag - lda SavedJoypad1Bits ;check controller for start pressed - and #Start_Button - bne TerminateGame - lda ScreenTimer ;if not pressed, wait for - bne GameIsOn ;screen timer to expire -TerminateGame: - lda #Silence ;silence music - sta EventMusicQueue - jsr TransposePlayers ;check if other player can keep - bcc ContinueGame ;going, and do so if possible - lda WorldNumber ;otherwise put world number of current - sta ContinueWorld ;player into secret continue function variable - lda #$00 - asl ;residual ASL instruction - sta OperMode_Task ;reset all modes to title screen and - sta ScreenTimer ;leave - sta OperMode - rts - -ContinueGame: - jsr LoadAreaPointer ;update level pointer with - lda #$01 ;actual world and area numbers, then - sta PlayerSize ;reset player's size, status, and - inc FetchNewGameTimerFlag ;set game timer flag to reload - lda #$00 ;game timer from header - sta TimerControl ;also set flag for timers to count again - sta PlayerStatus - sta GameEngineSubroutine ;reset task for game core - sta OperMode_Task ;set modes and leave - lda #$01 ;if in game over mode, switch back to - sta OperMode ;game mode, because game is still on -GameIsOn: rts - -TransposePlayers: - sec ;set carry flag by default to end game - lda NumberOfPlayers ;if only a 1 player game, leave - beq ExTrans - lda OffScr_NumberofLives ;does offscreen player have any lives left? - bmi ExTrans ;branch if not - lda CurrentPlayer ;invert bit to update - eor #%00000001 ;which player is on the screen - sta CurrentPlayer - ldx #$06 -TransLoop: lda OnscreenPlayerInfo,x ;transpose the information - pha ;of the onscreen player - lda OffscreenPlayerInfo,x ;with that of the offscreen player - sta OnscreenPlayerInfo,x - pla - sta OffscreenPlayerInfo,x - dex - bpl TransLoop - clc ;clear carry flag to get game going -ExTrans: rts - -;------------------------------------------------------------------------------------- - -DoNothing1: - lda #$ff ;this is residual code, this value is - sta $06c9 ;not used anywhere in the program -DoNothing2: - rts - -;------------------------------------------------------------------------------------- - -AreaParserTaskHandler: - ldy AreaParserTaskNum ;check number of tasks here - bne DoAPTasks ;if already set, go ahead - ldy #$08 - sty AreaParserTaskNum ;otherwise, set eight by default -DoAPTasks: dey - tya - jsr AreaParserTasks - dec AreaParserTaskNum ;if all tasks not complete do not - bne SkipATRender ;render attribute table yet - jsr RenderAttributeTables -SkipATRender: rts - -AreaParserTasks: - jsr JumpEngine - - .dw IncrementColumnPos - .dw RenderAreaGraphics - .dw RenderAreaGraphics - .dw AreaParserCore - .dw IncrementColumnPos - .dw RenderAreaGraphics - .dw RenderAreaGraphics - .dw AreaParserCore - -;------------------------------------------------------------------------------------- - -IncrementColumnPos: - inc CurrentColumnPos ;increment column where we're at - lda CurrentColumnPos - and #%00001111 ;mask out higher nybble - bne NoColWrap - sta CurrentColumnPos ;if no bits left set, wrap back to zero (0-f) - inc CurrentPageLoc ;and increment page number where we're at -NoColWrap: inc BlockBufferColumnPos ;increment column offset where we're at - lda BlockBufferColumnPos - and #%00011111 ;mask out all but 5 LSB (0-1f) - sta BlockBufferColumnPos ;and save - rts - -;------------------------------------------------------------------------------------- -;$00 - used as counter, store for low nybble for background, ceiling byte for terrain -;$01 - used to store floor byte for terrain -;$07 - used to store terrain metatile -;$06-$07 - used to store block buffer address - -BSceneDataOffsets: - .db $00, $30, $60 - -BackSceneryData: - .db $93, $00, $00, $11, $12, $12, $13, $00 ;clouds - .db $00, $51, $52, $53, $00, $00, $00, $00 - .db $00, $00, $01, $02, $02, $03, $00, $00 - .db $00, $00, $00, $00, $91, $92, $93, $00 - .db $00, $00, $00, $51, $52, $53, $41, $42 - .db $43, $00, $00, $00, $00, $00, $91, $92 - - .db $97, $87, $88, $89, $99, $00, $00, $00 ;mountains and bushes - .db $11, $12, $13, $a4, $a5, $a5, $a5, $a6 - .db $97, $98, $99, $01, $02, $03, $00, $a4 - .db $a5, $a6, $00, $11, $12, $12, $12, $13 - .db $00, $00, $00, $00, $01, $02, $02, $03 - .db $00, $a4, $a5, $a5, $a6, $00, $00, $00 - - .db $11, $12, $12, $13, $00, $00, $00, $00 ;trees and fences - .db $00, $00, $00, $9c, $00, $8b, $aa, $aa - .db $aa, $aa, $11, $12, $13, $8b, $00, $9c - .db $9c, $00, $00, $01, $02, $03, $11, $12 - .db $12, $13, $00, $00, $00, $00, $aa, $aa - .db $9c, $aa, $00, $8b, $00, $01, $02, $03 - -BackSceneryMetatiles: - .db $80, $83, $00 ;cloud left - .db $81, $84, $00 ;cloud middle - .db $82, $85, $00 ;cloud right - .db $02, $00, $00 ;bush left - .db $03, $00, $00 ;bush middle - .db $04, $00, $00 ;bush right - .db $00, $05, $06 ;mountain left - .db $07, $06, $0a ;mountain middle - .db $00, $08, $09 ;mountain right - .db $4d, $00, $00 ;fence - .db $0d, $0f, $4e ;tall tree - .db $0e, $4e, $4e ;short tree - -FSceneDataOffsets: - .db $00, $0d, $1a - -ForeSceneryData: - .db $86, $87, $87, $87, $87, $87, $87 ;in water - .db $87, $87, $87, $87, $69, $69 - - .db $00, $00, $00, $00, $00, $45, $47 ;wall - .db $47, $47, $47, $47, $00, $00 - - .db $00, $00, $00, $00, $00, $00, $00 ;over water - .db $00, $00, $00, $00, $86, $87 - -TerrainMetatiles: - .db $69, $54, $52, $62 - -TerrainRenderBits: - .db %00000000, %00000000 ;no ceiling or floor - .db %00000000, %00011000 ;no ceiling, floor 2 - .db %00000001, %00011000 ;ceiling 1, floor 2 - .db %00000111, %00011000 ;ceiling 3, floor 2 - .db %00001111, %00011000 ;ceiling 4, floor 2 - .db %11111111, %00011000 ;ceiling 8, floor 2 - .db %00000001, %00011111 ;ceiling 1, floor 5 - .db %00000111, %00011111 ;ceiling 3, floor 5 - .db %00001111, %00011111 ;ceiling 4, floor 5 - .db %10000001, %00011111 ;ceiling 1, floor 6 - .db %00000001, %00000000 ;ceiling 1, no floor - .db %10001111, %00011111 ;ceiling 4, floor 6 - .db %11110001, %00011111 ;ceiling 1, floor 9 - .db %11111001, %00011000 ;ceiling 1, middle 5, floor 2 - .db %11110001, %00011000 ;ceiling 1, middle 4, floor 2 - .db %11111111, %00011111 ;completely solid top to bottom - -AreaParserCore: - lda BackloadingFlag ;check to see if we are starting right of start - beq RenderSceneryTerrain ;if not, go ahead and render background, foreground and terrain - jsr ProcessAreaData ;otherwise skip ahead and load level data - -RenderSceneryTerrain: - ldx #$0c - lda #$00 -ClrMTBuf: sta MetatileBuffer,x ;clear out metatile buffer - dex - bpl ClrMTBuf - ldy BackgroundScenery ;do we need to render the background scenery? - beq RendFore ;if not, skip to check the foreground - lda CurrentPageLoc ;otherwise check for every third page -ThirdP: cmp #$03 - bmi RendBack ;if less than three we're there - sec - sbc #$03 ;if 3 or more, subtract 3 and - bpl ThirdP ;do an unconditional branch -RendBack: asl ;move results to higher nybble - asl - asl - asl - adc BSceneDataOffsets-1,y ;add to it offset loaded from here - adc CurrentColumnPos ;add to the result our current column position - tax - lda BackSceneryData,x ;load data from sum of offsets - beq RendFore ;if zero, no scenery for that part - pha - and #$0f ;save to stack and clear high nybble - sec - sbc #$01 ;subtract one (because low nybble is $01-$0c) - sta $00 ;save low nybble - asl ;multiply by three (shift to left and add result to old one) - adc $00 ;note that since d7 was nulled, the carry flag is always clear - tax ;save as offset for background scenery metatile data - pla ;get high nybble from stack, move low - lsr - lsr - lsr - lsr - tay ;use as second offset (used to determine height) - lda #$03 ;use previously saved memory location for counter - sta $00 -SceLoop1: lda BackSceneryMetatiles,x ;load metatile data from offset of (lsb - 1) * 3 - sta MetatileBuffer,y ;store into buffer from offset of (msb / 16) - inx - iny - cpy #$0b ;if at this location, leave loop - beq RendFore - dec $00 ;decrement until counter expires, barring exception - bne SceLoop1 -RendFore: ldx ForegroundScenery ;check for foreground data needed or not - beq RendTerr ;if not, skip this part - ldy FSceneDataOffsets-1,x ;load offset from location offset by header value, then - ldx #$00 ;reinit X -SceLoop2: lda ForeSceneryData,y ;load data until counter expires - beq NoFore ;do not store if zero found - sta MetatileBuffer,x -NoFore: iny - inx - cpx #$0d ;store up to end of metatile buffer - bne SceLoop2 -RendTerr: ldy AreaType ;check world type for water level - bne TerMTile ;if not water level, skip this part - lda WorldNumber ;check world number, if not world number eight - cmp #World8 ;then skip this part - bne TerMTile - lda #$62 ;if set as water level and world number eight, - jmp StoreMT ;use castle wall metatile as terrain type -TerMTile: lda TerrainMetatiles,y ;otherwise get appropriate metatile for area type - ldy CloudTypeOverride ;check for cloud type override - beq StoreMT ;if not set, keep value otherwise - lda #$88 ;use cloud block terrain -StoreMT: sta $07 ;store value here - ldx #$00 ;initialize X, use as metatile buffer offset - lda TerrainControl ;use yet another value from the header - asl ;multiply by 2 and use as yet another offset - tay -TerrLoop: lda TerrainRenderBits,y ;get one of the terrain rendering bit data - sta $00 - iny ;increment Y and use as offset next time around - sty $01 - lda CloudTypeOverride ;skip if value here is zero - beq NoCloud2 - cpx #$00 ;otherwise, check if we're doing the ceiling byte - beq NoCloud2 - lda $00 ;if not, mask out all but d3 - and #%00001000 - sta $00 -NoCloud2: ldy #$00 ;start at beginning of bitmasks -TerrBChk: lda Bitmasks,y ;load bitmask, then perform AND on contents of first byte - bit $00 - beq NextTBit ;if not set, skip this part (do not write terrain to buffer) - lda $07 - sta MetatileBuffer,x ;load terrain type metatile number and store into buffer here -NextTBit: inx ;continue until end of buffer - cpx #$0d - beq RendBBuf ;if we're at the end, break out of this loop - lda AreaType ;check world type for underground area - cmp #$02 - bne EndUChk ;if not underground, skip this part - cpx #$0b - bne EndUChk ;if we're at the bottom of the screen, override - lda #$54 ;old terrain type with ground level terrain type - sta $07 -EndUChk: iny ;increment bitmasks offset in Y - cpy #$08 - bne TerrBChk ;if not all bits checked, loop back - ldy $01 - bne TerrLoop ;unconditional branch, use Y to load next byte -RendBBuf: jsr ProcessAreaData ;do the area data loading routine now - lda BlockBufferColumnPos - jsr GetBlockBufferAddr ;get block buffer address from where we're at - ldx #$00 - ldy #$00 ;init index regs and start at beginning of smaller buffer -ChkMTLow: sty $00 - lda MetatileBuffer,x ;load stored metatile number - and #%11000000 ;mask out all but 2 MSB - asl - rol ;make %xx000000 into %000000xx - rol - tay ;use as offset in Y - lda MetatileBuffer,x ;reload original unmasked value here - cmp BlockBuffLowBounds,y ;check for certain values depending on bits set - bcs StrBlock ;if equal or greater, branch - lda #$00 ;if less, init value before storing -StrBlock: ldy $00 ;get offset for block buffer - sta ($06),y ;store value into block buffer - tya - clc ;add 16 (move down one row) to offset - adc #$10 - tay - inx ;increment column value - cpx #$0d - bcc ChkMTLow ;continue until we pass last row, then leave - rts - -;numbers lower than these with the same attribute bits -;will not be stored in the block buffer -BlockBuffLowBounds: - .db $10, $51, $88, $c0 - -;------------------------------------------------------------------------------------- -;$00 - used to store area object identifier -;$07 - used as adder to find proper area object code - -ProcessAreaData: - ldx #$02 ;start at the end of area object buffer -ProcADLoop: stx ObjectOffset - lda #$00 ;reset flag - sta BehindAreaParserFlag - ldy AreaDataOffset ;get offset of area data pointer - lda (AreaData),y ;get first byte of area object - cmp #$fd ;if end-of-area, skip all this crap - beq RdyDecode - lda AreaObjectLength,x ;check area object buffer flag - bpl RdyDecode ;if buffer not negative, branch, otherwise - iny - lda (AreaData),y ;get second byte of area object - asl ;check for page select bit (d7), branch if not set - bcc Chk1Row13 - lda AreaObjectPageSel ;check page select - bne Chk1Row13 - inc AreaObjectPageSel ;if not already set, set it now - inc AreaObjectPageLoc ;and increment page location -Chk1Row13: dey - lda (AreaData),y ;reread first byte of level object - and #$0f ;mask out high nybble - cmp #$0d ;row 13? - bne Chk1Row14 - iny ;if so, reread second byte of level object - lda (AreaData),y - dey ;decrement to get ready to read first byte - and #%01000000 ;check for d6 set (if not, object is page control) - bne CheckRear - lda AreaObjectPageSel ;if page select is set, do not reread - bne CheckRear - iny ;if d6 not set, reread second byte - lda (AreaData),y - and #%00011111 ;mask out all but 5 LSB and store in page control - sta AreaObjectPageLoc - inc AreaObjectPageSel ;increment page select - jmp NextAObj -Chk1Row14: cmp #$0e ;row 14? - bne CheckRear - lda BackloadingFlag ;check flag for saved page number and branch if set - bne RdyDecode ;to render the object (otherwise bg might not look right) -CheckRear: lda AreaObjectPageLoc ;check to see if current page of level object is - cmp CurrentPageLoc ;behind current page of renderer - bcc SetBehind ;if so branch -RdyDecode: jsr DecodeAreaData ;do sub and do not turn on flag - jmp ChkLength -SetBehind: inc BehindAreaParserFlag ;turn on flag if object is behind renderer -NextAObj: jsr IncAreaObjOffset ;increment buffer offset and move on -ChkLength: ldx ObjectOffset ;get buffer offset - lda AreaObjectLength,x ;check object length for anything stored here - bmi ProcLoopb ;if not, branch to handle loopback - dec AreaObjectLength,x ;otherwise decrement length or get rid of it -ProcLoopb: dex ;decrement buffer offset - bpl ProcADLoop ;and loopback unless exceeded buffer - lda BehindAreaParserFlag ;check for flag set if objects were behind renderer - bne ProcessAreaData ;branch if true to load more level data, otherwise - lda BackloadingFlag ;check for flag set if starting right of page $00 - bne ProcessAreaData ;branch if true to load more level data, otherwise leave -EndAParse: rts - -IncAreaObjOffset: - inc AreaDataOffset ;increment offset of level pointer - inc AreaDataOffset - lda #$00 ;reset page select - sta AreaObjectPageSel - rts - -DecodeAreaData: - lda AreaObjectLength,x ;check current buffer flag - bmi Chk1stB - ldy AreaObjOffsetBuffer,x ;if not, get offset from buffer -Chk1stB: ldx #$10 ;load offset of 16 for special row 15 - lda (AreaData),y ;get first byte of level object again - cmp #$fd - beq EndAParse ;if end of level, leave this routine - and #$0f ;otherwise, mask out low nybble - cmp #$0f ;row 15? - beq ChkRow14 ;if so, keep the offset of 16 - ldx #$08 ;otherwise load offset of 8 for special row 12 - cmp #$0c ;row 12? - beq ChkRow14 ;if so, keep the offset value of 8 - ldx #$00 ;otherwise nullify value by default -ChkRow14: stx $07 ;store whatever value we just loaded here - ldx ObjectOffset ;get object offset again - cmp #$0e ;row 14? - bne ChkRow13 - lda #$00 ;if so, load offset with $00 - sta $07 - lda #$2e ;and load A with another value - bne NormObj ;unconditional branch -ChkRow13: cmp #$0d ;row 13? - bne ChkSRows - lda #$22 ;if so, load offset with 34 - sta $07 - iny ;get next byte - lda (AreaData),y - and #%01000000 ;mask out all but d6 (page control obj bit) - beq LeavePar ;if d6 clear, branch to leave (we handled this earlier) - lda (AreaData),y ;otherwise, get byte again - and #%01111111 ;mask out d7 - cmp #$4b ;check for loop command in low nybble - bne Mask2MSB ;(plus d6 set for object other than page control) - inc LoopCommand ;if loop command, set loop command flag -Mask2MSB: and #%00111111 ;mask out d7 and d6 - jmp NormObj ;and jump -ChkSRows: cmp #$0c ;row 12-15? - bcs SpecObj - iny ;if not, get second byte of level object - lda (AreaData),y - and #%01110000 ;mask out all but d6-d4 - bne LrgObj ;if any bits set, branch to handle large object - lda #$16 - sta $07 ;otherwise set offset of 24 for small object - lda (AreaData),y ;reload second byte of level object - and #%00001111 ;mask out higher nybble and jump - jmp NormObj -LrgObj: sta $00 ;store value here (branch for large objects) - cmp #$70 ;check for vertical pipe object - bne NotWPipe - lda (AreaData),y ;if not, reload second byte - and #%00001000 ;mask out all but d3 (usage control bit) - beq NotWPipe ;if d3 clear, branch to get original value - lda #$00 ;otherwise, nullify value for warp pipe - sta $00 -NotWPipe: lda $00 ;get value and jump ahead - jmp MoveAOId -SpecObj: iny ;branch here for rows 12-15 - lda (AreaData),y - and #%01110000 ;get next byte and mask out all but d6-d4 -MoveAOId: lsr ;move d6-d4 to lower nybble - lsr - lsr - lsr -NormObj: sta $00 ;store value here (branch for small objects and rows 13 and 14) - lda AreaObjectLength,x ;is there something stored here already? - bpl RunAObj ;if so, branch to do its particular sub - lda AreaObjectPageLoc ;otherwise check to see if the object we've loaded is on the - cmp CurrentPageLoc ;same page as the renderer, and if so, branch - beq InitRear - ldy AreaDataOffset ;if not, get old offset of level pointer - lda (AreaData),y ;and reload first byte - and #%00001111 - cmp #$0e ;row 14? - bne LeavePar - lda BackloadingFlag ;if so, check backloading flag - bne StrAObj ;if set, branch to render object, else leave -LeavePar: rts -InitRear: lda BackloadingFlag ;check backloading flag to see if it's been initialized - beq BackColC ;branch to column-wise check - lda #$00 ;if not, initialize both backloading and - sta BackloadingFlag ;behind-renderer flags and leave - sta BehindAreaParserFlag - sta ObjectOffset -LoopCmdE: rts -BackColC: ldy AreaDataOffset ;get first byte again - lda (AreaData),y - and #%11110000 ;mask out low nybble and move high to low - lsr - lsr - lsr - lsr - cmp CurrentColumnPos ;is this where we're at? - bne LeavePar ;if not, branch to leave -StrAObj: lda AreaDataOffset ;if so, load area obj offset and store in buffer - sta AreaObjOffsetBuffer,x - jsr IncAreaObjOffset ;do sub to increment to next object data -RunAObj: lda $00 ;get stored value and add offset to it - clc ;then use the jump engine with current contents of A - adc $07 - jsr JumpEngine - -;large objects (rows $00-$0b or 00-11, d6-d4 set) - .dw VerticalPipe ;used by warp pipes - .dw AreaStyleObject - .dw RowOfBricks - .dw RowOfSolidBlocks - .dw RowOfCoins - .dw ColumnOfBricks - .dw ColumnOfSolidBlocks - .dw VerticalPipe ;used by decoration pipes - -;objects for special row $0c or 12 - .dw Hole_Empty - .dw PulleyRopeObject - .dw Bridge_High - .dw Bridge_Middle - .dw Bridge_Low - .dw Hole_Water - .dw QuestionBlockRow_High - .dw QuestionBlockRow_Low - -;objects for special row $0f or 15 - .dw EndlessRope - .dw BalancePlatRope - .dw CastleObject - .dw StaircaseObject - .dw ExitPipe - .dw FlagBalls_Residual - -;small objects (rows $00-$0b or 00-11, d6-d4 all clear) - .dw QuestionBlock ;power-up - .dw QuestionBlock ;coin - .dw QuestionBlock ;hidden, coin - .dw Hidden1UpBlock ;hidden, 1-up - .dw BrickWithItem ;brick, power-up - .dw BrickWithItem ;brick, vine - .dw BrickWithItem ;brick, star - .dw BrickWithCoins ;brick, coins - .dw BrickWithItem ;brick, 1-up - .dw WaterPipe - .dw EmptyBlock - .dw Jumpspring - -;objects for special row $0d or 13 (d6 set) - .dw IntroPipe - .dw FlagpoleObject - .dw AxeObj - .dw ChainObj - .dw CastleBridgeObj - .dw ScrollLockObject_Warp - .dw ScrollLockObject - .dw ScrollLockObject - .dw AreaFrenzy ;flying cheep-cheeps - .dw AreaFrenzy ;bullet bills or swimming cheep-cheeps - .dw AreaFrenzy ;stop frenzy - .dw LoopCmdE - -;object for special row $0e or 14 - .dw AlterAreaAttributes - -;------------------------------------------------------------------------------------- -;(these apply to all area object subroutines in this section unless otherwise stated) -;$00 - used to store offset used to find object code -;$07 - starts with adder from area parser, used to store row offset - -AlterAreaAttributes: - ldy AreaObjOffsetBuffer,x ;load offset for level object data saved in buffer - iny ;load second byte - lda (AreaData),y - pha ;save in stack for now - and #%01000000 - bne Alter2 ;branch if d6 is set - pla - pha ;pull and push offset to copy to A - and #%00001111 ;mask out high nybble and store as - sta TerrainControl ;new terrain height type bits - pla - and #%00110000 ;pull and mask out all but d5 and d4 - lsr ;move bits to lower nybble and store - lsr ;as new background scenery bits - lsr - lsr - sta BackgroundScenery ;then leave - rts -Alter2: pla - and #%00000111 ;mask out all but 3 LSB - cmp #$04 ;if four or greater, set color control bits - bcc SetFore ;and nullify foreground scenery bits - sta BackgroundColorCtrl - lda #$00 -SetFore: sta ForegroundScenery ;otherwise set new foreground scenery bits - rts - -;-------------------------------- - -ScrollLockObject_Warp: - ldx #$04 ;load value of 4 for game text routine as default - lda WorldNumber ;warp zone (4-3-2), then check world number - beq WarpNum - inx ;if world number > 1, increment for next warp zone (5) - ldy AreaType ;check area type - dey - bne WarpNum ;if ground area type, increment for last warp zone - inx ;(8-7-6) and move on -WarpNum: txa - sta WarpZoneControl ;store number here to be used by warp zone routine - jsr WriteGameText ;print text and warp zone numbers - lda #PiranhaPlant - jsr KillEnemies ;load identifier for piranha plants and do sub - -ScrollLockObject: - lda ScrollLock ;invert scroll lock to turn it on - eor #%00000001 - sta ScrollLock - rts - -;-------------------------------- -;$00 - used to store enemy identifier in KillEnemies - -KillEnemies: - sta $00 ;store identifier here - lda #$00 - ldx #$04 ;check for identifier in enemy object buffer -KillELoop: ldy Enemy_ID,x - cpy $00 ;if not found, branch - bne NoKillE - sta Enemy_Flag,x ;if found, deactivate enemy object flag -NoKillE: dex ;do this until all slots are checked - bpl KillELoop - rts - -;-------------------------------- - -FrenzyIDData: - .db FlyCheepCheepFrenzy, BBill_CCheep_Frenzy, Stop_Frenzy - -AreaFrenzy: ldx $00 ;use area object identifier bit as offset - lda FrenzyIDData-8,x ;note that it starts at 8, thus weird address here - ldy #$05 -FreCompLoop: dey ;check regular slots of enemy object buffer - bmi ExitAFrenzy ;if all slots checked and enemy object not found, branch to store - cmp Enemy_ID,y ;check for enemy object in buffer versus frenzy object - bne FreCompLoop - lda #$00 ;if enemy object already present, nullify queue and leave -ExitAFrenzy: sta EnemyFrenzyQueue ;store enemy into frenzy queue - rts - -;-------------------------------- -;$06 - used by MushroomLedge to store length - -AreaStyleObject: - lda AreaStyle ;load level object style and jump to the right sub - jsr JumpEngine - .dw TreeLedge ;also used for cloud type levels - .dw MushroomLedge - .dw BulletBillCannon - -TreeLedge: - jsr GetLrgObjAttrib ;get row and length of green ledge - lda AreaObjectLength,x ;check length counter for expiration - beq EndTreeL - bpl MidTreeL - tya - sta AreaObjectLength,x ;store lower nybble into buffer flag as length of ledge - lda CurrentPageLoc - ora CurrentColumnPos ;are we at the start of the level? - beq MidTreeL - lda #$16 ;render start of tree ledge - jmp NoUnder -MidTreeL: ldx $07 - lda #$17 ;render middle of tree ledge - sta MetatileBuffer,x ;note that this is also used if ledge position is - lda #$4c ;at the start of level for continuous effect - jmp AllUnder ;now render the part underneath -EndTreeL: lda #$18 ;render end of tree ledge - jmp NoUnder - -MushroomLedge: - jsr ChkLrgObjLength ;get shroom dimensions - sty $06 ;store length here for now - bcc EndMushL - lda AreaObjectLength,x ;divide length by 2 and store elsewhere - lsr - sta MushroomLedgeHalfLen,x - lda #$19 ;render start of mushroom - jmp NoUnder -EndMushL: lda #$1b ;if at the end, render end of mushroom - ldy AreaObjectLength,x - beq NoUnder - lda MushroomLedgeHalfLen,x ;get divided length and store where length - sta $06 ;was stored originally - ldx $07 - lda #$1a - sta MetatileBuffer,x ;render middle of mushroom - cpy $06 ;are we smack dab in the center? - bne MushLExit ;if not, branch to leave - inx - lda #$4f - sta MetatileBuffer,x ;render stem top of mushroom underneath the middle - lda #$50 -AllUnder: inx - ldy #$0f ;set $0f to render all way down - jmp RenderUnderPart ;now render the stem of mushroom -NoUnder: ldx $07 ;load row of ledge - ldy #$00 ;set 0 for no bottom on this part - jmp RenderUnderPart - -;-------------------------------- - -;tiles used by pulleys and rope object -PulleyRopeMetatiles: - .db $42, $41, $43 - -PulleyRopeObject: - jsr ChkLrgObjLength ;get length of pulley/rope object - ldy #$00 ;initialize metatile offset - bcs RenderPul ;if starting, render left pulley - iny - lda AreaObjectLength,x ;if not at the end, render rope - bne RenderPul - iny ;otherwise render right pulley -RenderPul: lda PulleyRopeMetatiles,y - sta MetatileBuffer ;render at the top of the screen -MushLExit: rts ;and leave - -;-------------------------------- -;$06 - used to store upper limit of rows for CastleObject - -CastleMetatiles: - .db $00, $45, $45, $45, $00 - .db $00, $48, $47, $46, $00 - .db $45, $49, $49, $49, $45 - .db $47, $47, $4a, $47, $47 - .db $47, $47, $4b, $47, $47 - .db $49, $49, $49, $49, $49 - .db $47, $4a, $47, $4a, $47 - .db $47, $4b, $47, $4b, $47 - .db $47, $47, $47, $47, $47 - .db $4a, $47, $4a, $47, $4a - .db $4b, $47, $4b, $47, $4b - -CastleObject: - jsr GetLrgObjAttrib ;save lower nybble as starting row - sty $07 ;if starting row is above $0a, game will crash!!! - ldy #$04 - jsr ChkLrgObjFixedLength ;load length of castle if not already loaded - txa - pha ;save obj buffer offset to stack - ldy AreaObjectLength,x ;use current length as offset for castle data - ldx $07 ;begin at starting row - lda #$0b - sta $06 ;load upper limit of number of rows to print -CRendLoop: lda CastleMetatiles,y ;load current byte using offset - sta MetatileBuffer,x - inx ;store in buffer and increment buffer offset - lda $06 - beq ChkCFloor ;have we reached upper limit yet? - iny ;if not, increment column-wise - iny ;to byte in next row - iny - iny - iny - dec $06 ;move closer to upper limit -ChkCFloor: cpx #$0b ;have we reached the row just before floor? - bne CRendLoop ;if not, go back and do another row - pla - tax ;get obj buffer offset from before - lda CurrentPageLoc - beq ExitCastle ;if we're at page 0, we do not need to do anything else - lda AreaObjectLength,x ;check length - cmp #$01 ;if length almost about to expire, put brick at floor - beq PlayerStop - ldy $07 ;check starting row for tall castle ($00) - bne NotTall - cmp #$03 ;if found, then check to see if we're at the second column - beq PlayerStop -NotTall: cmp #$02 ;if not tall castle, check to see if we're at the third column - bne ExitCastle ;if we aren't and the castle is tall, don't create flag yet - jsr GetAreaObjXPosition ;otherwise, obtain and save horizontal pixel coordinate - pha - jsr FindEmptyEnemySlot ;find an empty place on the enemy object buffer - pla - sta Enemy_X_Position,x ;then write horizontal coordinate for star flag - lda CurrentPageLoc - sta Enemy_PageLoc,x ;set page location for star flag - lda #$01 - sta Enemy_Y_HighPos,x ;set vertical high byte - sta Enemy_Flag,x ;set flag for buffer - lda #$90 - sta Enemy_Y_Position,x ;set vertical coordinate - lda #StarFlagObject ;set star flag value in buffer itself - sta Enemy_ID,x - rts -PlayerStop: ldy #$52 ;put brick at floor to stop player at end of level - sty MetatileBuffer+10 ;this is only done if we're on the second column -ExitCastle: rts - -;-------------------------------- - -WaterPipe: - jsr GetLrgObjAttrib ;get row and lower nybble - ldy AreaObjectLength,x ;get length (residual code, water pipe is 1 col thick) - ldx $07 ;get row - lda #$6b - sta MetatileBuffer,x ;draw something here and below it - lda #$6c - sta MetatileBuffer+1,x - rts - -;-------------------------------- -;$05 - used to store length of vertical shaft in RenderSidewaysPipe -;$06 - used to store leftover horizontal length in RenderSidewaysPipe -; and vertical length in VerticalPipe and GetPipeHeight - -IntroPipe: - ldy #$03 ;check if length set, if not set, set it - jsr ChkLrgObjFixedLength - ldy #$0a ;set fixed value and render the sideways part - jsr RenderSidewaysPipe - bcs NoBlankP ;if carry flag set, not time to draw vertical pipe part - ldx #$06 ;blank everything above the vertical pipe part -VPipeSectLoop: lda #$00 ;all the way to the top of the screen - sta MetatileBuffer,x ;because otherwise it will look like exit pipe - dex - bpl VPipeSectLoop - lda VerticalPipeData,y ;draw the end of the vertical pipe part - sta MetatileBuffer+7 -NoBlankP: rts - -SidePipeShaftData: - .db $15, $14 ;used to control whether or not vertical pipe shaft - .db $00, $00 ;is drawn, and if so, controls the metatile number -SidePipeTopPart: - .db $15, $1e ;top part of sideways part of pipe - .db $1d, $1c -SidePipeBottomPart: - .db $15, $21 ;bottom part of sideways part of pipe - .db $20, $1f - -ExitPipe: - ldy #$03 ;check if length set, if not set, set it - jsr ChkLrgObjFixedLength - jsr GetLrgObjAttrib ;get vertical length, then plow on through RenderSidewaysPipe - -RenderSidewaysPipe: - dey ;decrement twice to make room for shaft at bottom - dey ;and store here for now as vertical length - sty $05 - ldy AreaObjectLength,x ;get length left over and store here - sty $06 - ldx $05 ;get vertical length plus one, use as buffer offset - inx - lda SidePipeShaftData,y ;check for value $00 based on horizontal offset - cmp #$00 - beq DrawSidePart ;if found, do not draw the vertical pipe shaft - ldx #$00 - ldy $05 ;init buffer offset and get vertical length - jsr RenderUnderPart ;and render vertical shaft using tile number in A - clc ;clear carry flag to be used by IntroPipe -DrawSidePart: ldy $06 ;render side pipe part at the bottom - lda SidePipeTopPart,y - sta MetatileBuffer,x ;note that the pipe parts are stored - lda SidePipeBottomPart,y ;backwards horizontally - sta MetatileBuffer+1,x - rts - -VerticalPipeData: - .db $11, $10 ;used by pipes that lead somewhere - .db $15, $14 - .db $13, $12 ;used by decoration pipes - .db $15, $14 - -VerticalPipe: - jsr GetPipeHeight - lda $00 ;check to see if value was nullified earlier - beq WarpPipe ;(if d3, the usage control bit of second byte, was set) - iny - iny - iny - iny ;add four if usage control bit was not set -WarpPipe: tya ;save value in stack - pha - lda AreaNumber - ora WorldNumber ;if at world 1-1, do not add piranha plant ever - beq DrawPipe - ldy AreaObjectLength,x ;if on second column of pipe, branch - beq DrawPipe ;(because we only need to do this once) - jsr FindEmptyEnemySlot ;check for an empty moving data buffer space - bcs DrawPipe ;if not found, too many enemies, thus skip - jsr GetAreaObjXPosition ;get horizontal pixel coordinate - clc - adc #$08 ;add eight to put the piranha plant in the center - sta Enemy_X_Position,x ;store as enemy's horizontal coordinate - lda CurrentPageLoc ;add carry to current page number - adc #$00 - sta Enemy_PageLoc,x ;store as enemy's page coordinate - lda #$01 - sta Enemy_Y_HighPos,x - sta Enemy_Flag,x ;activate enemy flag - jsr GetAreaObjYPosition ;get piranha plant's vertical coordinate and store here - sta Enemy_Y_Position,x - lda #PiranhaPlant ;write piranha plant's value into buffer - sta Enemy_ID,x - jsr InitPiranhaPlant -DrawPipe: pla ;get value saved earlier and use as Y - tay - ldx $07 ;get buffer offset - lda VerticalPipeData,y ;draw the appropriate pipe with the Y we loaded earlier - sta MetatileBuffer,x ;render the top of the pipe - inx - lda VerticalPipeData+2,y ;render the rest of the pipe - ldy $06 ;subtract one from length and render the part underneath - dey - jmp RenderUnderPart - -GetPipeHeight: - ldy #$01 ;check for length loaded, if not, load - jsr ChkLrgObjFixedLength ;pipe length of 2 (horizontal) - jsr GetLrgObjAttrib - tya ;get saved lower nybble as height - and #$07 ;save only the three lower bits as - sta $06 ;vertical length, then load Y with - ldy AreaObjectLength,x ;length left over - rts - -FindEmptyEnemySlot: - ldx #$00 ;start at first enemy slot -EmptyChkLoop: clc ;clear carry flag by default - lda Enemy_Flag,x ;check enemy buffer for nonzero - beq ExitEmptyChk ;if zero, leave - inx - cpx #$05 ;if nonzero, check next value - bne EmptyChkLoop -ExitEmptyChk: rts ;if all values nonzero, carry flag is set - -;-------------------------------- - -Hole_Water: - jsr ChkLrgObjLength ;get low nybble and save as length - lda #$86 ;render waves - sta MetatileBuffer+10 - ldx #$0b - ldy #$01 ;now render the water underneath - lda #$87 - jmp RenderUnderPart - -;-------------------------------- - -QuestionBlockRow_High: - lda #$03 ;start on the fourth row - .db $2c ;BIT instruction opcode - -QuestionBlockRow_Low: - lda #$07 ;start on the eighth row - pha ;save whatever row to the stack for now - jsr ChkLrgObjLength ;get low nybble and save as length - pla - tax ;render question boxes with coins - lda #$c0 - sta MetatileBuffer,x - rts - -;-------------------------------- - -Bridge_High: - lda #$06 ;start on the seventh row from top of screen - .db $2c ;BIT instruction opcode - -Bridge_Middle: - lda #$07 ;start on the eighth row - .db $2c ;BIT instruction opcode - -Bridge_Low: - lda #$09 ;start on the tenth row - pha ;save whatever row to the stack for now - jsr ChkLrgObjLength ;get low nybble and save as length - pla - tax ;render bridge railing - lda #$0b - sta MetatileBuffer,x - inx - ldy #$00 ;now render the bridge itself - lda #$63 - jmp RenderUnderPart - -;-------------------------------- - -FlagBalls_Residual: - jsr GetLrgObjAttrib ;get low nybble from object byte - ldx #$02 ;render flag balls on third row from top - lda #$6d ;of screen downwards based on low nybble - jmp RenderUnderPart - -;-------------------------------- - -FlagpoleObject: - lda #$24 ;render flagpole ball on top - sta MetatileBuffer - ldx #$01 ;now render the flagpole shaft - ldy #$08 - lda #$25 - jsr RenderUnderPart - lda #$61 ;render solid block at the bottom - sta MetatileBuffer+10 - jsr GetAreaObjXPosition - sec ;get pixel coordinate of where the flagpole is, - sbc #$08 ;subtract eight pixels and use as horizontal - sta Enemy_X_Position+5 ;coordinate for the flag - lda CurrentPageLoc - sbc #$00 ;subtract borrow from page location and use as - sta Enemy_PageLoc+5 ;page location for the flag - lda #$30 - sta Enemy_Y_Position+5 ;set vertical coordinate for flag - lda #$b0 - sta FlagpoleFNum_Y_Pos ;set initial vertical coordinate for flagpole's floatey number - lda #FlagpoleFlagObject - sta Enemy_ID+5 ;set flag identifier, note that identifier and coordinates - inc Enemy_Flag+5 ;use last space in enemy object buffer - rts - -;-------------------------------- - -EndlessRope: - ldx #$00 ;render rope from the top to the bottom of screen - ldy #$0f - jmp DrawRope - -BalancePlatRope: - txa ;save object buffer offset for now - pha - ldx #$01 ;blank out all from second row to the bottom - ldy #$0f ;with blank used for balance platform rope - lda #$44 - jsr RenderUnderPart - pla ;get back object buffer offset - tax - jsr GetLrgObjAttrib ;get vertical length from lower nybble - ldx #$01 -DrawRope: lda #$40 ;render the actual rope - jmp RenderUnderPart - -;-------------------------------- - -CoinMetatileData: - .db $c3, $c2, $c2, $c2 - -RowOfCoins: - ldy AreaType ;get area type - lda CoinMetatileData,y ;load appropriate coin metatile - jmp GetRow - -;-------------------------------- - -C_ObjectRow: - .db $06, $07, $08 - -C_ObjectMetatile: - .db $c5, $0c, $89 - -CastleBridgeObj: - ldy #$0c ;load length of 13 columns - jsr ChkLrgObjFixedLength - jmp ChainObj - -AxeObj: - lda #$08 ;load bowser's palette into sprite portion of palette - sta VRAM_Buffer_AddrCtrl - -ChainObj: - ldy $00 ;get value loaded earlier from decoder - ldx C_ObjectRow-2,y ;get appropriate row and metatile for object - lda C_ObjectMetatile-2,y - jmp ColObj - -EmptyBlock: - jsr GetLrgObjAttrib ;get row location - ldx $07 - lda #$c4 -ColObj: ldy #$00 ;column length of 1 - jmp RenderUnderPart - -;-------------------------------- - -SolidBlockMetatiles: - .db $69, $61, $61, $62 - -BrickMetatiles: - .db $22, $51, $52, $52 - .db $88 ;used only by row of bricks object - -RowOfBricks: - ldy AreaType ;load area type obtained from area offset pointer - lda CloudTypeOverride ;check for cloud type override - beq DrawBricks - ldy #$04 ;if cloud type, override area type -DrawBricks: lda BrickMetatiles,y ;get appropriate metatile - jmp GetRow ;and go render it - -RowOfSolidBlocks: - ldy AreaType ;load area type obtained from area offset pointer - lda SolidBlockMetatiles,y ;get metatile -GetRow: pha ;store metatile here - jsr ChkLrgObjLength ;get row number, load length -DrawRow: ldx $07 - ldy #$00 ;set vertical height of 1 - pla - jmp RenderUnderPart ;render object - -ColumnOfBricks: - ldy AreaType ;load area type obtained from area offset - lda BrickMetatiles,y ;get metatile (no cloud override as for row) - jmp GetRow2 - -ColumnOfSolidBlocks: - ldy AreaType ;load area type obtained from area offset - lda SolidBlockMetatiles,y ;get metatile -GetRow2: pha ;save metatile to stack for now - jsr GetLrgObjAttrib ;get length and row - pla ;restore metatile - ldx $07 ;get starting row - jmp RenderUnderPart ;now render the column - -;-------------------------------- - -BulletBillCannon: - jsr GetLrgObjAttrib ;get row and length of bullet bill cannon - ldx $07 ;start at first row - lda #$64 ;render bullet bill cannon - sta MetatileBuffer,x - inx - dey ;done yet? - bmi SetupCannon - lda #$65 ;if not, render middle part - sta MetatileBuffer,x - inx - dey ;done yet? - bmi SetupCannon - lda #$66 ;if not, render bottom until length expires - jsr RenderUnderPart -SetupCannon: ldx Cannon_Offset ;get offset for data used by cannons and whirlpools - jsr GetAreaObjYPosition ;get proper vertical coordinate for cannon - sta Cannon_Y_Position,x ;and store it here - lda CurrentPageLoc - sta Cannon_PageLoc,x ;store page number for cannon here - jsr GetAreaObjXPosition ;get proper horizontal coordinate for cannon - sta Cannon_X_Position,x ;and store it here - inx - cpx #$06 ;increment and check offset - bcc StrCOffset ;if not yet reached sixth cannon, branch to save offset - ldx #$00 ;otherwise initialize it -StrCOffset: stx Cannon_Offset ;save new offset and leave - rts - -;-------------------------------- - -StaircaseHeightData: - .db $07, $07, $06, $05, $04, $03, $02, $01, $00 - -StaircaseRowData: - .db $03, $03, $04, $05, $06, $07, $08, $09, $0a - -StaircaseObject: - jsr ChkLrgObjLength ;check and load length - bcc NextStair ;if length already loaded, skip init part - lda #$09 ;start past the end for the bottom - sta StaircaseControl ;of the staircase -NextStair: dec StaircaseControl ;move onto next step (or first if starting) - ldy StaircaseControl - ldx StaircaseRowData,y ;get starting row and height to render - lda StaircaseHeightData,y - tay - lda #$61 ;now render solid block staircase - jmp RenderUnderPart - -;-------------------------------- - -Jumpspring: - jsr GetLrgObjAttrib - jsr FindEmptyEnemySlot ;find empty space in enemy object buffer - jsr GetAreaObjXPosition ;get horizontal coordinate for jumpspring - sta Enemy_X_Position,x ;and store - lda CurrentPageLoc ;store page location of jumpspring - sta Enemy_PageLoc,x - jsr GetAreaObjYPosition ;get vertical coordinate for jumpspring - sta Enemy_Y_Position,x ;and store - sta Jumpspring_FixedYPos,x ;store as permanent coordinate here - lda #JumpspringObject - sta Enemy_ID,x ;write jumpspring object to enemy object buffer - ldy #$01 - sty Enemy_Y_HighPos,x ;store vertical high byte - inc Enemy_Flag,x ;set flag for enemy object buffer - ldx $07 - lda #$67 ;draw metatiles in two rows where jumpspring is - sta MetatileBuffer,x - lda #$68 - sta MetatileBuffer+1,x - rts - -;-------------------------------- -;$07 - used to save ID of brick object - -Hidden1UpBlock: - lda Hidden1UpFlag ;if flag not set, do not render object - beq ExitDecBlock - lda #$00 ;if set, init for the next one - sta Hidden1UpFlag - jmp BrickWithItem ;jump to code shared with unbreakable bricks - -QuestionBlock: - jsr GetAreaObjectID ;get value from level decoder routine - jmp DrawQBlk ;go to render it - -BrickWithCoins: - lda #$00 ;initialize multi-coin timer flag - sta BrickCoinTimerFlag - -BrickWithItem: - jsr GetAreaObjectID ;save area object ID - sty $07 - lda #$00 ;load default adder for bricks with lines - ldy AreaType ;check level type for ground level - dey - beq BWithL ;if ground type, do not start with 5 - lda #$05 ;otherwise use adder for bricks without lines -BWithL: clc ;add object ID to adder - adc $07 - tay ;use as offset for metatile -DrawQBlk: lda BrickQBlockMetatiles,y ;get appropriate metatile for brick (question block - pha ;if branched to here from question block routine) - jsr GetLrgObjAttrib ;get row from location byte - jmp DrawRow ;now render the object - -GetAreaObjectID: - lda $00 ;get value saved from area parser routine - sec - sbc #$00 ;possibly residual code - tay ;save to Y -ExitDecBlock: rts - -;-------------------------------- - -HoleMetatiles: - .db $87, $00, $00, $00 - -Hole_Empty: - jsr ChkLrgObjLength ;get lower nybble and save as length - bcc NoWhirlP ;skip this part if length already loaded - lda AreaType ;check for water type level - bne NoWhirlP ;if not water type, skip this part - ldx Whirlpool_Offset ;get offset for data used by cannons and whirlpools - jsr GetAreaObjXPosition ;get proper vertical coordinate of where we're at - sec - sbc #$10 ;subtract 16 pixels - sta Whirlpool_LeftExtent,x ;store as left extent of whirlpool - lda CurrentPageLoc ;get page location of where we're at - sbc #$00 ;subtract borrow - sta Whirlpool_PageLoc,x ;save as page location of whirlpool - iny - iny ;increment length by 2 - tya - asl ;multiply by 16 to get size of whirlpool - asl ;note that whirlpool will always be - asl ;two blocks bigger than actual size of hole - asl ;and extend one block beyond each edge - sta Whirlpool_Length,x ;save size of whirlpool here - inx - cpx #$05 ;increment and check offset - bcc StrWOffset ;if not yet reached fifth whirlpool, branch to save offset - ldx #$00 ;otherwise initialize it -StrWOffset: stx Whirlpool_Offset ;save new offset here -NoWhirlP: ldx AreaType ;get appropriate metatile, then - lda HoleMetatiles,x ;render the hole proper - ldx #$08 - ldy #$0f ;start at ninth row and go to bottom, run RenderUnderPart - -;-------------------------------- - -RenderUnderPart: - sty AreaObjectHeight ;store vertical length to render - ldy MetatileBuffer,x ;check current spot to see if there's something - beq DrawThisRow ;we need to keep, if nothing, go ahead - cpy #$17 - beq WaitOneRow ;if middle part (tree ledge), wait until next row - cpy #$1a - beq WaitOneRow ;if middle part (mushroom ledge), wait until next row - cpy #$c0 - beq DrawThisRow ;if question block w/ coin, overwrite - cpy #$c0 - bcs WaitOneRow ;if any other metatile with palette 3, wait until next row - cpy #$54 - bne DrawThisRow ;if cracked rock terrain, overwrite - cmp #$50 - beq WaitOneRow ;if stem top of mushroom, wait until next row -DrawThisRow: sta MetatileBuffer,x ;render contents of A from routine that called this -WaitOneRow: inx - cpx #$0d ;stop rendering if we're at the bottom of the screen - bcs ExitUPartR - ldy AreaObjectHeight ;decrement, and stop rendering if there is no more length - dey - bpl RenderUnderPart -ExitUPartR: rts - -;-------------------------------- - -ChkLrgObjLength: - jsr GetLrgObjAttrib ;get row location and size (length if branched to from here) - -ChkLrgObjFixedLength: - lda AreaObjectLength,x ;check for set length counter - clc ;clear carry flag for not just starting - bpl LenSet ;if counter not set, load it, otherwise leave alone - tya ;save length into length counter - sta AreaObjectLength,x - sec ;set carry flag if just starting -LenSet: rts - -GetLrgObjAttrib: - ldy AreaObjOffsetBuffer,x ;get offset saved from area obj decoding routine - lda (AreaData),y ;get first byte of level object - and #%00001111 - sta $07 ;save row location - iny - lda (AreaData),y ;get next byte, save lower nybble (length or height) - and #%00001111 ;as Y, then leave - tay - rts - -;-------------------------------- - -GetAreaObjXPosition: - lda CurrentColumnPos ;multiply current offset where we're at by 16 - asl ;to obtain horizontal pixel coordinate - asl - asl - asl - rts - -;-------------------------------- - -GetAreaObjYPosition: - lda $07 ;multiply value by 16 - asl - asl ;this will give us the proper vertical pixel coordinate - asl - asl - clc - adc #32 ;add 32 pixels for the status bar - rts - -;------------------------------------------------------------------------------------- -;$06-$07 - used to store block buffer address used as indirect - -BlockBufferAddr: - .db Block_Buffer_1, >Block_Buffer_2 - -GetBlockBufferAddr: - pha ;take value of A, save - lsr ;move high nybble to low - lsr - lsr - lsr - tay ;use nybble as pointer to high byte - lda BlockBufferAddr+2,y ;of indirect here - sta $07 - pla - and #%00001111 ;pull from stack, mask out high nybble - clc - adc BlockBufferAddr,y ;add to low byte - sta $06 ;store here and leave - rts - -;------------------------------------------------------------------------------------- - -;unused space - .db $ff, $ff - -;------------------------------------------------------------------------------------- - -AreaDataOfsLoopback: - .db $12, $36, $0e, $0e, $0e, $32, $32, $32, $0a, $26, $40 - -;------------------------------------------------------------------------------------- - -LoadAreaPointer: - jsr FindAreaPointer ;find it and store it here - sta AreaPointer -GetAreaType: and #%01100000 ;mask out all but d6 and d5 - asl - rol - rol - rol ;make %0xx00000 into %000000xx - sta AreaType ;save 2 MSB as area type - rts - -FindAreaPointer: - ldy WorldNumber ;load offset from world variable - lda WorldAddrOffsets,y - clc ;add area number used to find data - adc AreaNumber - tay - lda AreaAddrOffsets,y ;from there we have our area pointer - rts - - -GetAreaDataAddrs: - lda AreaPointer ;use 2 MSB for Y - jsr GetAreaType - tay - lda AreaPointer ;mask out all but 5 LSB - and #%00011111 - sta AreaAddrsLOffset ;save as low offset - lda EnemyAddrHOffsets,y ;load base value with 2 altered MSB, - clc ;then add base value to 5 LSB, result - adc AreaAddrsLOffset ;becomes offset for level data - tay - lda EnemyDataAddrLow,y ;use offset to load pointer - sta EnemyDataLow - lda EnemyDataAddrHigh,y - sta EnemyDataHigh - ldy AreaType ;use area type as offset - lda AreaDataHOffsets,y ;do the same thing but with different base value - clc - adc AreaAddrsLOffset - tay - lda AreaDataAddrLow,y ;use this offset to load another pointer - sta AreaDataLow - lda AreaDataAddrHigh,y - sta AreaDataHigh - ldy #$00 ;load first byte of header - lda (AreaData),y - pha ;save it to the stack for now - and #%00000111 ;save 3 LSB for foreground scenery or bg color control - cmp #$04 - bcc StoreFore - sta BackgroundColorCtrl ;if 4 or greater, save value here as bg color control - lda #$00 -StoreFore: sta ForegroundScenery ;if less, save value here as foreground scenery - pla ;pull byte from stack and push it back - pha - and #%00111000 ;save player entrance control bits - lsr ;shift bits over to LSBs - lsr - lsr - sta PlayerEntranceCtrl ;save value here as player entrance control - pla ;pull byte again but do not push it back - and #%11000000 ;save 2 MSB for game timer setting - clc - rol ;rotate bits over to LSBs - rol - rol - sta GameTimerSetting ;save value here as game timer setting - iny - lda (AreaData),y ;load second byte of header - pha ;save to stack - and #%00001111 ;mask out all but lower nybble - sta TerrainControl - pla ;pull and push byte to copy it to A - pha - and #%00110000 ;save 2 MSB for background scenery type - lsr - lsr ;shift bits to LSBs - lsr - lsr - sta BackgroundScenery ;save as background scenery - pla - and #%11000000 - clc - rol ;rotate bits over to LSBs - rol - rol - cmp #%00000011 ;if set to 3, store here - bne StoreStyle ;and nullify other value - sta CloudTypeOverride ;otherwise store value in other place - lda #$00 -StoreStyle: sta AreaStyle - lda AreaDataLow ;increment area data address by 2 bytes - clc - adc #$02 - sta AreaDataLow - lda AreaDataHigh - adc #$00 - sta AreaDataHigh - rts - -;------------------------------------------------------------------------------------- -;GAME LEVELS DATA - -WorldAddrOffsets: - .db World1Areas-AreaAddrOffsets, World2Areas-AreaAddrOffsets - .db World3Areas-AreaAddrOffsets, World4Areas-AreaAddrOffsets - .db World5Areas-AreaAddrOffsets, World6Areas-AreaAddrOffsets - .db World7Areas-AreaAddrOffsets, World8Areas-AreaAddrOffsets - -AreaAddrOffsets: -World1Areas: .db $25, $29, $c0, $26, $60 -World2Areas: .db $28, $29, $01, $27, $62 -World3Areas: .db $24, $35, $20, $63 -World4Areas: .db $22, $29, $41, $2c, $61 -World5Areas: .db $2a, $31, $26, $62 -World6Areas: .db $2e, $23, $2d, $60 -World7Areas: .db $33, $29, $01, $27, $64 -World8Areas: .db $30, $32, $21, $65 - -;bonus area data offsets, included here for comparison purposes -;underground bonus area - c2 -;cloud area 1 (day) - 2b -;cloud area 2 (night) - 34 -;water area (5-2/6-2) - 00 -;water area (8-4) - 02 -;warp zone area (4-2) - 2f - -EnemyAddrHOffsets: - .db $1f, $06, $1c, $00 - -EnemyDataAddrLow: - .db E_CastleArea1, >E_CastleArea2, >E_CastleArea3, >E_CastleArea4, >E_CastleArea5, >E_CastleArea6 - .db >E_GroundArea1, >E_GroundArea2, >E_GroundArea3, >E_GroundArea4, >E_GroundArea5, >E_GroundArea6 - .db >E_GroundArea7, >E_GroundArea8, >E_GroundArea9, >E_GroundArea10, >E_GroundArea11, >E_GroundArea12 - .db >E_GroundArea13, >E_GroundArea14, >E_GroundArea15, >E_GroundArea16, >E_GroundArea17, >E_GroundArea18 - .db >E_GroundArea19, >E_GroundArea20, >E_GroundArea21, >E_GroundArea22, >E_UndergroundArea1 - .db >E_UndergroundArea2, >E_UndergroundArea3, >E_WaterArea1, >E_WaterArea2, >E_WaterArea3 - -AreaDataHOffsets: - .db $00, $03, $19, $1c - -AreaDataAddrLow: - .db L_WaterArea1, >L_WaterArea2, >L_WaterArea3, >L_GroundArea1, >L_GroundArea2, >L_GroundArea3 - .db >L_GroundArea4, >L_GroundArea5, >L_GroundArea6, >L_GroundArea7, >L_GroundArea8, >L_GroundArea9 - .db >L_GroundArea10, >L_GroundArea11, >L_GroundArea12, >L_GroundArea13, >L_GroundArea14, >L_GroundArea15 - .db >L_GroundArea16, >L_GroundArea17, >L_GroundArea18, >L_GroundArea19, >L_GroundArea20, >L_GroundArea21 - .db >L_GroundArea22, >L_UndergroundArea1, >L_UndergroundArea2, >L_UndergroundArea3, >L_CastleArea1 - .db >L_CastleArea2, >L_CastleArea3, >L_CastleArea4, >L_CastleArea5, >L_CastleArea6 - -;ENEMY OBJECT DATA - -;level 1-4/6-4 -E_CastleArea1: - .db $76, $dd, $bb, $4c, $ea, $1d, $1b, $cc, $56, $5d - .db $16, $9d, $c6, $1d, $36, $9d, $c9, $1d, $04, $db - .db $49, $1d, $84, $1b, $c9, $5d, $88, $95, $0f, $08 - .db $30, $4c, $78, $2d, $a6, $28, $90, $b5 - .db $ff - -;level 4-4 -E_CastleArea2: - .db $0f, $03, $56, $1b, $c9, $1b, $0f, $07, $36, $1b - .db $aa, $1b, $48, $95, $0f, $0a, $2a, $1b, $5b, $0c - .db $78, $2d, $90, $b5 - .db $ff - -;level 2-4/5-4 -E_CastleArea3: - .db $0b, $8c, $4b, $4c, $77, $5f, $eb, $0c, $bd, $db - .db $19, $9d, $75, $1d, $7d, $5b, $d9, $1d, $3d, $dd - .db $99, $1d, $26, $9d, $5a, $2b, $8a, $2c, $ca, $1b - .db $20, $95, $7b, $5c, $db, $4c, $1b, $cc, $3b, $cc - .db $78, $2d, $a6, $28, $90, $b5 - .db $ff - -;level 3-4 -E_CastleArea4: - .db $0b, $8c, $3b, $1d, $8b, $1d, $ab, $0c, $db, $1d - .db $0f, $03, $65, $1d, $6b, $1b, $05, $9d, $0b, $1b - .db $05, $9b, $0b, $1d, $8b, $0c, $1b, $8c, $70, $15 - .db $7b, $0c, $db, $0c, $0f, $08, $78, $2d, $a6, $28 - .db $90, $b5 - .db $ff - -;level 7-4 -E_CastleArea5: - .db $27, $a9, $4b, $0c, $68, $29, $0f, $06, $77, $1b - .db $0f, $0b, $60, $15, $4b, $8c, $78, $2d, $90, $b5 - .db $ff - -;level 8-4 -E_CastleArea6: - .db $0f, $03, $8e, $65, $e1, $bb, $38, $6d, $a8, $3e, $e5, $e7 - .db $0f, $08, $0b, $02, $2b, $02, $5e, $65, $e1, $bb, $0e - .db $db, $0e, $bb, $8e, $db, $0e, $fe, $65, $ec, $0f, $0d - .db $4e, $65, $e1, $0f, $0e, $4e, $02, $e0, $0f, $10, $fe, $e5, $e1 - .db $1b, $85, $7b, $0c, $5b, $95, $78, $2d, $90, $b5 - .db $ff - -;level 3-3 -E_GroundArea1: - .db $a5, $86, $e4, $28, $18, $a8, $45, $83, $69, $03 - .db $c6, $29, $9b, $83, $16, $a4, $88, $24, $e9, $28 - .db $05, $a8, $7b, $28, $24, $8f, $c8, $03, $e8, $03 - .db $46, $a8, $85, $24, $c8, $24 - .db $ff - -;level 8-3 -E_GroundArea2: - .db $eb, $8e, $0f, $03, $fb, $05, $17, $85, $db, $8e - .db $0f, $07, $57, $05, $7b, $05, $9b, $80, $2b, $85 - .db $fb, $05, $0f, $0b, $1b, $05, $9b, $05 - .db $ff - -;level 4-1 -E_GroundArea3: - .db $2e, $c2, $66, $e2, $11, $0f, $07, $02, $11, $0f, $0c - .db $12, $11 - .db $ff - -;level 6-2 -E_GroundArea4: - .db $0e, $c2, $a8, $ab, $00, $bb, $8e, $6b, $82, $de, $00, $a0 - .db $33, $86, $43, $06, $3e, $b4, $a0, $cb, $02, $0f, $07 - .db $7e, $42, $a6, $83, $02, $0f, $0a, $3b, $02, $cb, $37 - .db $0f, $0c, $e3, $0e - .db $ff - -;level 3-1 -E_GroundArea5: - .db $9b, $8e, $ca, $0e, $ee, $42, $44, $5b, $86, $80, $b8 - .db $1b, $80, $50, $ba, $10, $b7, $5b, $00, $17, $85 - .db $4b, $05, $fe, $34, $40, $b7, $86, $c6, $06, $5b, $80 - .db $83, $00, $d0, $38, $5b, $8e, $8a, $0e, $a6, $00 - .db $bb, $0e, $c5, $80, $f3, $00 - .db $ff - -;level 1-1 -E_GroundArea6: - .db $1e, $c2, $00, $6b, $06, $8b, $86, $63, $b7, $0f, $05 - .db $03, $06, $23, $06, $4b, $b7, $bb, $00, $5b, $b7 - .db $fb, $37, $3b, $b7, $0f, $0b, $1b, $37 - .db $ff - -;level 1-3/5-3 -E_GroundArea7: - .db $2b, $d7, $e3, $03, $c2, $86, $e2, $06, $76, $a5 - .db $a3, $8f, $03, $86, $2b, $57, $68, $28, $e9, $28 - .db $e5, $83, $24, $8f, $36, $a8, $5b, $03 - .db $ff - -;level 2-3/7-3 -E_GroundArea8: - .db $0f, $02, $78, $40, $48, $ce, $f8, $c3, $f8, $c3 - .db $0f, $07, $7b, $43, $c6, $d0, $0f, $8a, $c8, $50 - .db $ff - -;level 2-1 -E_GroundArea9: - .db $85, $86, $0b, $80, $1b, $00, $db, $37, $77, $80 - .db $eb, $37, $fe, $2b, $20, $2b, $80, $7b, $38, $ab, $b8 - .db $77, $86, $fe, $42, $20, $49, $86, $8b, $06, $9b, $80 - .db $7b, $8e, $5b, $b7, $9b, $0e, $bb, $0e, $9b, $80 -;pipe intro area -E_GroundArea10: - .db $ff - -;level 5-1 -E_GroundArea11: - .db $0b, $80, $60, $38, $10, $b8, $c0, $3b, $db, $8e - .db $40, $b8, $f0, $38, $7b, $8e, $a0, $b8, $c0, $b8 - .db $fb, $00, $a0, $b8, $30, $bb, $ee, $42, $88, $0f, $0b - .db $2b, $0e, $67, $0e - .db $ff - -;cloud level used in levels 2-1 and 5-2 -E_GroundArea12: - .db $0a, $aa, $0e, $28, $2a, $0e, $31, $88 - .db $ff - -;level 4-3 -E_GroundArea13: - .db $c7, $83, $d7, $03, $42, $8f, $7a, $03, $05, $a4 - .db $78, $24, $a6, $25, $e4, $25, $4b, $83, $e3, $03 - .db $05, $a4, $89, $24, $b5, $24, $09, $a4, $65, $24 - .db $c9, $24, $0f, $08, $85, $25 - .db $ff - -;level 6-3 -E_GroundArea14: - .db $cd, $a5, $b5, $a8, $07, $a8, $76, $28, $cc, $25 - .db $65, $a4, $a9, $24, $e5, $24, $19, $a4, $0f, $07 - .db $95, $28, $e6, $24, $19, $a4, $d7, $29, $16, $a9 - .db $58, $29, $97, $29 - .db $ff - -;level 6-1 -E_GroundArea15: - .db $0f, $02, $02, $11, $0f, $07, $02, $11 - .db $ff - -;warp zone area used in level 4-2 -E_GroundArea16: - .db $ff - -;level 8-1 -E_GroundArea17: - .db $2b, $82, $ab, $38, $de, $42, $e2, $1b, $b8, $eb - .db $3b, $db, $80, $8b, $b8, $1b, $82, $fb, $b8, $7b - .db $80, $fb, $3c, $5b, $bc, $7b, $b8, $1b, $8e, $cb - .db $0e, $1b, $8e, $0f, $0d, $2b, $3b, $bb, $b8, $eb, $82 - .db $4b, $b8, $bb, $38, $3b, $b7, $bb, $02, $0f, $13 - .db $1b, $00, $cb, $80, $6b, $bc - .db $ff - -;level 5-2 -E_GroundArea18: - .db $7b, $80, $ae, $00, $80, $8b, $8e, $e8, $05, $f9, $86 - .db $17, $86, $16, $85, $4e, $2b, $80, $ab, $8e, $87, $85 - .db $c3, $05, $8b, $82, $9b, $02, $ab, $02, $bb, $86 - .db $cb, $06, $d3, $03, $3b, $8e, $6b, $0e, $a7, $8e - .db $ff - -;level 8-2 -E_GroundArea19: - .db $29, $8e, $52, $11, $83, $0e, $0f, $03, $9b, $0e - .db $2b, $8e, $5b, $0e, $cb, $8e, $fb, $0e, $fb, $82 - .db $9b, $82, $bb, $02, $fe, $42, $e8, $bb, $8e, $0f, $0a - .db $ab, $0e, $cb, $0e, $f9, $0e, $88, $86, $a6, $06 - .db $db, $02, $b6, $8e - .db $ff - -;level 7-1 -E_GroundArea20: - .db $ab, $ce, $de, $42, $c0, $cb, $ce, $5b, $8e, $1b, $ce - .db $4b, $85, $67, $45, $0f, $07, $2b, $00, $7b, $85 - .db $97, $05, $0f, $0a, $92, $02 - .db $ff - -;cloud level used in levels 3-1 and 6-2 -E_GroundArea21: - .db $0a, $aa, $0e, $24, $4a, $1e, $23, $aa - .db $ff - -;level 3-2 -E_GroundArea22: - .db $1b, $80, $bb, $38, $4b, $bc, $eb, $3b, $0f, $04 - .db $2b, $00, $ab, $38, $eb, $00, $cb, $8e, $fb, $80 - .db $ab, $b8, $6b, $80, $fb, $3c, $9b, $bb, $5b, $bc - .db $fb, $00, $6b, $b8, $fb, $38 - .db $ff - -;level 1-2 -E_UndergroundArea1: - .db $0b, $86, $1a, $06, $db, $06, $de, $c2, $02, $f0, $3b - .db $bb, $80, $eb, $06, $0b, $86, $93, $06, $f0, $39 - .db $0f, $06, $60, $b8, $1b, $86, $a0, $b9, $b7, $27 - .db $bd, $27, $2b, $83, $a1, $26, $a9, $26, $ee, $25, $0b - .db $27, $b4 - .db $ff - -;level 4-2 -E_UndergroundArea2: - .db $0f, $02, $1e, $2f, $60, $e0, $3a, $a5, $a7, $db, $80 - .db $3b, $82, $8b, $02, $fe, $42, $68, $70, $bb, $25, $a7 - .db $2c, $27, $b2, $26, $b9, $26, $9b, $80, $a8, $82 - .db $b5, $27, $bc, $27, $b0, $bb, $3b, $82, $87, $34 - .db $ee, $25, $6b - .db $ff - -;underground bonus rooms area used in many levels -E_UndergroundArea3: - .db $1e, $a5, $0a, $2e, $28, $27, $2e, $33, $c7, $0f, $03, $1e, $40, $07 - .db $2e, $30, $e7, $0f, $05, $1e, $24, $44, $0f, $07, $1e, $22, $6a - .db $2e, $23, $ab, $0f, $09, $1e, $41, $68, $1e, $2a, $8a, $2e, $23, $a2 - .db $2e, $32, $ea - .db $ff - -;water area used in levels 5-2 and 6-2 -E_WaterArea1: - .db $3b, $87, $66, $27, $cc, $27, $ee, $31, $87, $ee, $23, $a7 - .db $3b, $87, $db, $07 - .db $ff - -;level 2-2/7-2 -E_WaterArea2: - .db $0f, $01, $2e, $25, $2b, $2e, $25, $4b, $4e, $25, $cb, $6b, $07 - .db $97, $47, $e9, $87, $47, $c7, $7a, $07, $d6, $c7 - .db $78, $07, $38, $87, $ab, $47, $e3, $07, $9b, $87 - .db $0f, $09, $68, $47, $db, $c7, $3b, $c7 - .db $ff - -;water area used in level 8-4 -E_WaterArea3: - .db $47, $9b, $cb, $07, $fa, $1d, $86, $9b, $3a, $87 - .db $56, $07, $88, $1b, $07, $9d, $2e, $65, $f0 - .db $ff - -;AREA OBJECT DATA - -;level 1-4/6-4 -L_CastleArea1: - .db $9b, $07 - .db $05, $32, $06, $33, $07, $34, $ce, $03, $dc, $51 - .db $ee, $07, $73, $e0, $74, $0a, $7e, $06, $9e, $0a - .db $ce, $06, $e4, $00, $e8, $0a, $fe, $0a, $2e, $89 - .db $4e, $0b, $54, $0a, $14, $8a, $c4, $0a, $34, $8a - .db $7e, $06, $c7, $0a, $01, $e0, $02, $0a, $47, $0a - .db $81, $60, $82, $0a, $c7, $0a, $0e, $87, $7e, $02 - .db $a7, $02, $b3, $02, $d7, $02, $e3, $02, $07, $82 - .db $13, $02, $3e, $06, $7e, $02, $ae, $07, $fe, $0a - .db $0d, $c4, $cd, $43, $ce, $09, $de, $0b, $dd, $42 - .db $fe, $02, $5d, $c7 - .db $fd - -;level 4-4 -L_CastleArea2: - .db $5b, $07 - .db $05, $32, $06, $33, $07, $34, $5e, $0a, $68, $64 - .db $98, $64, $a8, $64, $ce, $06, $fe, $02, $0d, $01 - .db $1e, $0e, $7e, $02, $94, $63, $b4, $63, $d4, $63 - .db $f4, $63, $14, $e3, $2e, $0e, $5e, $02, $64, $35 - .db $88, $72, $be, $0e, $0d, $04, $ae, $02, $ce, $08 - .db $cd, $4b, $fe, $02, $0d, $05, $68, $31, $7e, $0a - .db $96, $31, $a9, $63, $a8, $33, $d5, $30, $ee, $02 - .db $e6, $62, $f4, $61, $04, $b1, $08, $3f, $44, $33 - .db $94, $63, $a4, $31, $e4, $31, $04, $bf, $08, $3f - .db $04, $bf, $08, $3f, $cd, $4b, $03, $e4, $0e, $03 - .db $2e, $01, $7e, $06, $be, $02, $de, $06, $fe, $0a - .db $0d, $c4, $cd, $43, $ce, $09, $de, $0b, $dd, $42 - .db $fe, $02, $5d, $c7 - .db $fd - -;level 2-4/5-4 -L_CastleArea3: - .db $9b, $07 - .db $05, $32, $06, $33, $07, $34, $fe, $00, $27, $b1 - .db $65, $32, $75, $0a, $71, $00, $b7, $31, $08, $e4 - .db $18, $64, $1e, $04, $57, $3b, $bb, $0a, $17, $8a - .db $27, $3a, $73, $0a, $7b, $0a, $d7, $0a, $e7, $3a - .db $3b, $8a, $97, $0a, $fe, $08, $24, $8a, $2e, $00 - .db $3e, $40, $38, $64, $6f, $00, $9f, $00, $be, $43 - .db $c8, $0a, $c9, $63, $ce, $07, $fe, $07, $2e, $81 - .db $66, $42, $6a, $42, $79, $0a, $be, $00, $c8, $64 - .db $f8, $64, $08, $e4, $2e, $07, $7e, $03, $9e, $07 - .db $be, $03, $de, $07, $fe, $0a, $03, $a5, $0d, $44 - .db $cd, $43, $ce, $09, $dd, $42, $de, $0b, $fe, $02 - .db $5d, $c7 - .db $fd - -;level 3-4 -L_CastleArea4: - .db $9b, $07 - .db $05, $32, $06, $33, $07, $34, $fe, $06, $0c, $81 - .db $39, $0a, $5c, $01, $89, $0a, $ac, $01, $d9, $0a - .db $fc, $01, $2e, $83, $a7, $01, $b7, $00, $c7, $01 - .db $de, $0a, $fe, $02, $4e, $83, $5a, $32, $63, $0a - .db $69, $0a, $7e, $02, $ee, $03, $fa, $32, $03, $8a - .db $09, $0a, $1e, $02, $ee, $03, $fa, $32, $03, $8a - .db $09, $0a, $14, $42, $1e, $02, $7e, $0a, $9e, $07 - .db $fe, $0a, $2e, $86, $5e, $0a, $8e, $06, $be, $0a - .db $ee, $07, $3e, $83, $5e, $07, $fe, $0a, $0d, $c4 - .db $41, $52, $51, $52, $cd, $43, $ce, $09, $de, $0b - .db $dd, $42, $fe, $02, $5d, $c7 - .db $fd - -;level 7-4 -L_CastleArea5: - .db $5b, $07 - .db $05, $32, $06, $33, $07, $34, $fe, $0a, $ae, $86 - .db $be, $07, $fe, $02, $0d, $02, $27, $32, $46, $61 - .db $55, $62, $5e, $0e, $1e, $82, $68, $3c, $74, $3a - .db $7d, $4b, $5e, $8e, $7d, $4b, $7e, $82, $84, $62 - .db $94, $61, $a4, $31, $bd, $4b, $ce, $06, $fe, $02 - .db $0d, $06, $34, $31, $3e, $0a, $64, $32, $75, $0a - .db $7b, $61, $a4, $33, $ae, $02, $de, $0e, $3e, $82 - .db $64, $32, $78, $32, $b4, $36, $c8, $36, $dd, $4b - .db $44, $b2, $58, $32, $94, $63, $a4, $3e, $ba, $30 - .db $c9, $61, $ce, $06, $dd, $4b, $ce, $86, $dd, $4b - .db $fe, $02, $2e, $86, $5e, $02, $7e, $06, $fe, $02 - .db $1e, $86, $3e, $02, $5e, $06, $7e, $02, $9e, $06 - .db $fe, $0a, $0d, $c4, $cd, $43, $ce, $09, $de, $0b - .db $dd, $42, $fe, $02, $5d, $c7 - .db $fd - -;level 8-4 -L_CastleArea6: - .db $5b, $06 - .db $05, $32, $06, $33, $07, $34, $5e, $0a, $ae, $02 - .db $0d, $01, $39, $73, $0d, $03, $39, $7b, $4d, $4b - .db $de, $06, $1e, $8a, $ae, $06, $c4, $33, $16, $fe - .db $a5, $77, $fe, $02, $fe, $82, $0d, $07, $39, $73 - .db $a8, $74, $ed, $4b, $49, $fb, $e8, $74, $fe, $0a - .db $2e, $82, $67, $02, $84, $7a, $87, $31, $0d, $0b - .db $fe, $02, $0d, $0c, $39, $73, $5e, $06, $c6, $76 - .db $45, $ff, $be, $0a, $dd, $48, $fe, $06, $3d, $cb - .db $46, $7e, $ad, $4a, $fe, $82, $39, $f3, $a9, $7b - .db $4e, $8a, $9e, $07, $fe, $0a, $0d, $c4, $cd, $43 - .db $ce, $09, $de, $0b, $dd, $42, $fe, $02, $5d, $c7 - .db $fd - -;level 3-3 -L_GroundArea1: - .db $94, $11 - .db $0f, $26, $fe, $10, $28, $94, $65, $15, $eb, $12 - .db $fa, $41, $4a, $96, $54, $40, $a4, $42, $b7, $13 - .db $e9, $19, $f5, $15, $11, $80, $47, $42, $71, $13 - .db $80, $41, $15, $92, $1b, $1f, $24, $40, $55, $12 - .db $64, $40, $95, $12, $a4, $40, $d2, $12, $e1, $40 - .db $13, $c0, $2c, $17, $2f, $12, $49, $13, $83, $40 - .db $9f, $14, $a3, $40, $17, $92, $83, $13, $92, $41 - .db $b9, $14, $c5, $12, $c8, $40, $d4, $40, $4b, $92 - .db $78, $1b, $9c, $94, $9f, $11, $df, $14, $fe, $11 - .db $7d, $c1, $9e, $42, $cf, $20 - .db $fd - -;level 8-3 -L_GroundArea2: - .db $90, $b1 - .db $0f, $26, $29, $91, $7e, $42, $fe, $40, $28, $92 - .db $4e, $42, $2e, $c0, $57, $73, $c3, $25, $c7, $27 - .db $23, $84, $33, $20, $5c, $01, $77, $63, $88, $62 - .db $99, $61, $aa, $60, $bc, $01, $ee, $42, $4e, $c0 - .db $69, $11, $7e, $42, $de, $40, $f8, $62, $0e, $c2 - .db $ae, $40, $d7, $63, $e7, $63, $33, $a7, $37, $27 - .db $43, $04, $cc, $01, $e7, $73, $0c, $81, $3e, $42 - .db $0d, $0a, $5e, $40, $88, $72, $be, $42, $e7, $87 - .db $fe, $40, $39, $e1, $4e, $00, $69, $60, $87, $60 - .db $a5, $60, $c3, $31, $fe, $31, $6d, $c1, $be, $42 - .db $ef, $20 - .db $fd - -;level 4-1 -L_GroundArea3: - .db $52, $21 - .db $0f, $20, $6e, $40, $58, $f2, $93, $01, $97, $00 - .db $0c, $81, $97, $40, $a6, $41, $c7, $40, $0d, $04 - .db $03, $01, $07, $01, $23, $01, $27, $01, $ec, $03 - .db $ac, $f3, $c3, $03, $78, $e2, $94, $43, $47, $f3 - .db $74, $43, $47, $fb, $74, $43, $2c, $f1, $4c, $63 - .db $47, $00, $57, $21, $5c, $01, $7c, $72, $39, $f1 - .db $ec, $02, $4c, $81, $d8, $62, $ec, $01, $0d, $0d - .db $0f, $38, $c7, $07, $ed, $4a, $1d, $c1, $5f, $26 - .db $fd - -;level 6-2 -L_GroundArea4: - .db $54, $21 - .db $0f, $26, $a7, $22, $37, $fb, $73, $20, $83, $07 - .db $87, $02, $93, $20, $c7, $73, $04, $f1, $06, $31 - .db $39, $71, $59, $71, $e7, $73, $37, $a0, $47, $04 - .db $86, $7c, $e5, $71, $e7, $31, $33, $a4, $39, $71 - .db $a9, $71, $d3, $23, $08, $f2, $13, $05, $27, $02 - .db $49, $71, $75, $75, $e8, $72, $67, $f3, $99, $71 - .db $e7, $20, $f4, $72, $f7, $31, $17, $a0, $33, $20 - .db $39, $71, $73, $28, $bc, $05, $39, $f1, $79, $71 - .db $a6, $21, $c3, $06, $d3, $20, $dc, $00, $fc, $00 - .db $07, $a2, $13, $21, $5f, $32, $8c, $00, $98, $7a - .db $c7, $63, $d9, $61, $03, $a2, $07, $22, $74, $72 - .db $77, $31, $e7, $73, $39, $f1, $58, $72, $77, $73 - .db $d8, $72, $7f, $b1, $97, $73, $b6, $64, $c5, $65 - .db $d4, $66, $e3, $67, $f3, $67, $8d, $c1, $cf, $26 - .db $fd - -;level 3-1 -L_GroundArea5: - .db $52, $31 - .db $0f, $20, $6e, $66, $07, $81, $36, $01, $66, $00 - .db $a7, $22, $08, $f2, $67, $7b, $dc, $02, $98, $f2 - .db $d7, $20, $39, $f1, $9f, $33, $dc, $27, $dc, $57 - .db $23, $83, $57, $63, $6c, $51, $87, $63, $99, $61 - .db $a3, $06, $b3, $21, $77, $f3, $f3, $21, $f7, $2a - .db $13, $81, $23, $22, $53, $00, $63, $22, $e9, $0b - .db $0c, $83, $13, $21, $16, $22, $33, $05, $8f, $35 - .db $ec, $01, $63, $a0, $67, $20, $73, $01, $77, $01 - .db $83, $20, $87, $20, $b3, $20, $b7, $20, $c3, $01 - .db $c7, $00, $d3, $20, $d7, $20, $67, $a0, $77, $07 - .db $87, $22, $e8, $62, $f5, $65, $1c, $82, $7f, $38 - .db $8d, $c1, $cf, $26 - .db $fd - -;level 1-1 -L_GroundArea6: - .db $50, $21 - .db $07, $81, $47, $24, $57, $00, $63, $01, $77, $01 - .db $c9, $71, $68, $f2, $e7, $73, $97, $fb, $06, $83 - .db $5c, $01, $d7, $22, $e7, $00, $03, $a7, $6c, $02 - .db $b3, $22, $e3, $01, $e7, $07, $47, $a0, $57, $06 - .db $a7, $01, $d3, $00, $d7, $01, $07, $81, $67, $20 - .db $93, $22, $03, $a3, $1c, $61, $17, $21, $6f, $33 - .db $c7, $63, $d8, $62, $e9, $61, $fa, $60, $4f, $b3 - .db $87, $63, $9c, $01, $b7, $63, $c8, $62, $d9, $61 - .db $ea, $60, $39, $f1, $87, $21, $a7, $01, $b7, $20 - .db $39, $f1, $5f, $38, $6d, $c1, $af, $26 - .db $fd - -;level 1-3/5-3 -L_GroundArea7: - .db $90, $11 - .db $0f, $26, $fe, $10, $2a, $93, $87, $17, $a3, $14 - .db $b2, $42, $0a, $92, $19, $40, $36, $14, $50, $41 - .db $82, $16, $2b, $93, $24, $41, $bb, $14, $b8, $00 - .db $c2, $43, $c3, $13, $1b, $94, $67, $12, $c4, $15 - .db $53, $c1, $d2, $41, $12, $c1, $29, $13, $85, $17 - .db $1b, $92, $1a, $42, $47, $13, $83, $41, $a7, $13 - .db $0e, $91, $a7, $63, $b7, $63, $c5, $65, $d5, $65 - .db $dd, $4a, $e3, $67, $f3, $67, $8d, $c1, $ae, $42 - .db $df, $20 - .db $fd - -;level 2-3/7-3 -L_GroundArea8: - .db $90, $11 - .db $0f, $26, $6e, $10, $8b, $17, $af, $32, $d8, $62 - .db $e8, $62, $fc, $3f, $ad, $c8, $f8, $64, $0c, $be - .db $43, $43, $f8, $64, $0c, $bf, $73, $40, $84, $40 - .db $93, $40, $a4, $40, $b3, $40, $f8, $64, $48, $e4 - .db $5c, $39, $83, $40, $92, $41, $b3, $40, $f8, $64 - .db $48, $e4, $5c, $39, $f8, $64, $13, $c2, $37, $65 - .db $4c, $24, $63, $00, $97, $65, $c3, $42, $0b, $97 - .db $ac, $32, $f8, $64, $0c, $be, $53, $45, $9d, $48 - .db $f8, $64, $2a, $e2, $3c, $47, $56, $43, $ba, $62 - .db $f8, $64, $0c, $b7, $88, $64, $bc, $31, $d4, $45 - .db $fc, $31, $3c, $b1, $78, $64, $8c, $38, $0b, $9c - .db $1a, $33, $18, $61, $28, $61, $39, $60, $5d, $4a - .db $ee, $11, $0f, $b8, $1d, $c1, $3e, $42, $6f, $20 - .db $fd - -;level 2-1 -L_GroundArea9: - .db $52, $31 - .db $0f, $20, $6e, $40, $f7, $20, $07, $84, $17, $20 - .db $4f, $34, $c3, $03, $c7, $02, $d3, $22, $27, $e3 - .db $39, $61, $e7, $73, $5c, $e4, $57, $00, $6c, $73 - .db $47, $a0, $53, $06, $63, $22, $a7, $73, $fc, $73 - .db $13, $a1, $33, $05, $43, $21, $5c, $72, $c3, $23 - .db $cc, $03, $77, $fb, $ac, $02, $39, $f1, $a7, $73 - .db $d3, $04, $e8, $72, $e3, $22, $26, $f4, $bc, $02 - .db $8c, $81, $a8, $62, $17, $87, $43, $24, $a7, $01 - .db $c3, $04, $08, $f2, $97, $21, $a3, $02, $c9, $0b - .db $e1, $69, $f1, $69, $8d, $c1, $cf, $26 - .db $fd - -;pipe intro area -L_GroundArea10: - .db $38, $11 - .db $0f, $26, $ad, $40, $3d, $c7 - .db $fd - -;level 5-1 -L_GroundArea11: - .db $95, $b1 - .db $0f, $26, $0d, $02, $c8, $72, $1c, $81, $38, $72 - .db $0d, $05, $97, $34, $98, $62, $a3, $20, $b3, $06 - .db $c3, $20, $cc, $03, $f9, $91, $2c, $81, $48, $62 - .db $0d, $09, $37, $63, $47, $03, $57, $21, $8c, $02 - .db $c5, $79, $c7, $31, $f9, $11, $39, $f1, $a9, $11 - .db $6f, $b4, $d3, $65, $e3, $65, $7d, $c1, $bf, $26 - .db $fd - -;cloud level used in levels 2-1 and 5-2 -L_GroundArea12: - .db $00, $c1 - .db $4c, $00, $f4, $4f, $0d, $02, $02, $42, $43, $4f - .db $52, $c2, $de, $00, $5a, $c2, $4d, $c7 - .db $fd - -;level 4-3 -L_GroundArea13: - .db $90, $51 - .db $0f, $26, $ee, $10, $0b, $94, $33, $14, $42, $42 - .db $77, $16, $86, $44, $02, $92, $4a, $16, $69, $42 - .db $73, $14, $b0, $00, $c7, $12, $05, $c0, $1c, $17 - .db $1f, $11, $36, $12, $8f, $14, $91, $40, $1b, $94 - .db $35, $12, $34, $42, $60, $42, $61, $12, $87, $12 - .db $96, $40, $a3, $14, $1c, $98, $1f, $11, $47, $12 - .db $9f, $15, $cc, $15, $cf, $11, $05, $c0, $1f, $15 - .db $39, $12, $7c, $16, $7f, $11, $82, $40, $98, $12 - .db $df, $15, $16, $c4, $17, $14, $54, $12, $9b, $16 - .db $28, $94, $ce, $01, $3d, $c1, $5e, $42, $8f, $20 - .db $fd - -;level 6-3 -L_GroundArea14: - .db $97, $11 - .db $0f, $26, $fe, $10, $2b, $92, $57, $12, $8b, $12 - .db $c0, $41, $f7, $13, $5b, $92, $69, $0b, $bb, $12 - .db $b2, $46, $19, $93, $71, $00, $17, $94, $7c, $14 - .db $7f, $11, $93, $41, $bf, $15, $fc, $13, $ff, $11 - .db $2f, $95, $50, $42, $51, $12, $58, $14, $a6, $12 - .db $db, $12, $1b, $93, $46, $43, $7b, $12, $8d, $49 - .db $b7, $14, $1b, $94, $49, $0b, $bb, $12, $fc, $13 - .db $ff, $12, $03, $c1, $2f, $15, $43, $12, $4b, $13 - .db $77, $13, $9d, $4a, $15, $c1, $a1, $41, $c3, $12 - .db $fe, $01, $7d, $c1, $9e, $42, $cf, $20 - .db $fd - -;level 6-1 -L_GroundArea15: - .db $52, $21 - .db $0f, $20, $6e, $44, $0c, $f1, $4c, $01, $aa, $35 - .db $d9, $34, $ee, $20, $08, $b3, $37, $32, $43, $04 - .db $4e, $21, $53, $20, $7c, $01, $97, $21, $b7, $07 - .db $9c, $81, $e7, $42, $5f, $b3, $97, $63, $ac, $02 - .db $c5, $41, $49, $e0, $58, $61, $76, $64, $85, $65 - .db $94, $66, $a4, $22, $a6, $03, $c8, $22, $dc, $02 - .db $68, $f2, $96, $42, $13, $82, $17, $02, $af, $34 - .db $f6, $21, $fc, $06, $26, $80, $2a, $24, $36, $01 - .db $8c, $00, $ff, $35, $4e, $a0, $55, $21, $77, $20 - .db $87, $07, $89, $22, $ae, $21, $4c, $82, $9f, $34 - .db $ec, $01, $03, $e7, $13, $67, $8d, $4a, $ad, $41 - .db $0f, $a6 - .db $fd - -;warp zone area used in level 4-2 -L_GroundArea16: - .db $10, $51 - .db $4c, $00, $c7, $12, $c6, $42, $03, $92, $02, $42 - .db $29, $12, $63, $12, $62, $42, $69, $14, $a5, $12 - .db $a4, $42, $e2, $14, $e1, $44, $f8, $16, $37, $c1 - .db $8f, $38, $02, $bb, $28, $7a, $68, $7a, $a8, $7a - .db $e0, $6a, $f0, $6a, $6d, $c5 - .db $fd - -;level 8-1 -L_GroundArea17: - .db $92, $31 - .db $0f, $20, $6e, $40, $0d, $02, $37, $73, $ec, $00 - .db $0c, $80, $3c, $00, $6c, $00, $9c, $00, $06, $c0 - .db $c7, $73, $06, $83, $28, $72, $96, $40, $e7, $73 - .db $26, $c0, $87, $7b, $d2, $41, $39, $f1, $c8, $f2 - .db $97, $e3, $a3, $23, $e7, $02, $e3, $07, $f3, $22 - .db $37, $e3, $9c, $00, $bc, $00, $ec, $00, $0c, $80 - .db $3c, $00, $86, $21, $a6, $06, $b6, $24, $5c, $80 - .db $7c, $00, $9c, $00, $29, $e1, $dc, $05, $f6, $41 - .db $dc, $80, $e8, $72, $0c, $81, $27, $73, $4c, $01 - .db $66, $74, $0d, $11, $3f, $35, $b6, $41, $2c, $82 - .db $36, $40, $7c, $02, $86, $40, $f9, $61, $39, $e1 - .db $ac, $04, $c6, $41, $0c, $83, $16, $41, $88, $f2 - .db $39, $f1, $7c, $00, $89, $61, $9c, $00, $a7, $63 - .db $bc, $00, $c5, $65, $dc, $00, $e3, $67, $f3, $67 - .db $8d, $c1, $cf, $26 - .db $fd - -;level 5-2 -L_GroundArea18: - .db $55, $b1 - .db $0f, $26, $cf, $33, $07, $b2, $15, $11, $52, $42 - .db $99, $0b, $ac, $02, $d3, $24, $d6, $42, $d7, $25 - .db $23, $84, $cf, $33, $07, $e3, $19, $61, $78, $7a - .db $ef, $33, $2c, $81, $46, $64, $55, $65, $65, $65 - .db $ec, $74, $47, $82, $53, $05, $63, $21, $62, $41 - .db $96, $22, $9a, $41, $cc, $03, $b9, $91, $39, $f1 - .db $63, $26, $67, $27, $d3, $06, $fc, $01, $18, $e2 - .db $d9, $07, $e9, $04, $0c, $86, $37, $22, $93, $24 - .db $87, $84, $ac, $02, $c2, $41, $c3, $23, $d9, $71 - .db $fc, $01, $7f, $b1, $9c, $00, $a7, $63, $b6, $64 - .db $cc, $00, $d4, $66, $e3, $67, $f3, $67, $8d, $c1 - .db $cf, $26 - .db $fd - -;level 8-2 -L_GroundArea19: - .db $50, $b1 - .db $0f, $26, $fc, $00, $1f, $b3, $5c, $00, $65, $65 - .db $74, $66, $83, $67, $93, $67, $dc, $73, $4c, $80 - .db $b3, $20, $c9, $0b, $c3, $08, $d3, $2f, $dc, $00 - .db $2c, $80, $4c, $00, $8c, $00, $d3, $2e, $ed, $4a - .db $fc, $00, $d7, $a1, $ec, $01, $4c, $80, $59, $11 - .db $d8, $11, $da, $10, $37, $a0, $47, $04, $99, $11 - .db $e7, $21, $3a, $90, $67, $20, $76, $10, $77, $60 - .db $87, $07, $d8, $12, $39, $f1, $ac, $00, $e9, $71 - .db $0c, $80, $2c, $00, $4c, $05, $c7, $7b, $39, $f1 - .db $ec, $00, $f9, $11, $0c, $82, $6f, $34, $f8, $11 - .db $fa, $10, $7f, $b2, $ac, $00, $b6, $64, $cc, $01 - .db $e3, $67, $f3, $67, $8d, $c1, $cf, $26 - .db $fd - -;level 7-1 -L_GroundArea20: - .db $52, $b1 - .db $0f, $20, $6e, $45, $39, $91, $b3, $04, $c3, $21 - .db $c8, $11, $ca, $10, $49, $91, $7c, $73, $e8, $12 - .db $88, $91, $8a, $10, $e7, $21, $05, $91, $07, $30 - .db $17, $07, $27, $20, $49, $11, $9c, $01, $c8, $72 - .db $23, $a6, $27, $26, $d3, $03, $d8, $7a, $89, $91 - .db $d8, $72, $39, $f1, $a9, $11, $09, $f1, $63, $24 - .db $67, $24, $d8, $62, $28, $91, $2a, $10, $56, $21 - .db $70, $04, $79, $0b, $8c, $00, $94, $21, $9f, $35 - .db $2f, $b8, $3d, $c1, $7f, $26 - .db $fd - -;cloud level used in levels 3-1 and 6-2 -L_GroundArea21: - .db $06, $c1 - .db $4c, $00, $f4, $4f, $0d, $02, $06, $20, $24, $4f - .db $35, $a0, $36, $20, $53, $46, $d5, $20, $d6, $20 - .db $34, $a1, $73, $49, $74, $20, $94, $20, $b4, $20 - .db $d4, $20, $f4, $20, $2e, $80, $59, $42, $4d, $c7 - .db $fd - -;level 3-2 -L_GroundArea22: - .db $96, $31 - .db $0f, $26, $0d, $03, $1a, $60, $77, $42, $c4, $00 - .db $c8, $62, $b9, $e1, $d3, $06, $d7, $07, $f9, $61 - .db $0c, $81, $4e, $b1, $8e, $b1, $bc, $01, $e4, $50 - .db $e9, $61, $0c, $81, $0d, $0a, $84, $43, $98, $72 - .db $0d, $0c, $0f, $38, $1d, $c1, $5f, $26 - .db $fd - -;level 1-2 -L_UndergroundArea1: - .db $48, $0f - .db $0e, $01, $5e, $02, $a7, $00, $bc, $73, $1a, $e0 - .db $39, $61, $58, $62, $77, $63, $97, $63, $b8, $62 - .db $d6, $07, $f8, $62, $19, $e1, $75, $52, $86, $40 - .db $87, $50, $95, $52, $93, $43, $a5, $21, $c5, $52 - .db $d6, $40, $d7, $20, $e5, $06, $e6, $51, $3e, $8d - .db $5e, $03, $67, $52, $77, $52, $7e, $02, $9e, $03 - .db $a6, $43, $a7, $23, $de, $05, $fe, $02, $1e, $83 - .db $33, $54, $46, $40, $47, $21, $56, $04, $5e, $02 - .db $83, $54, $93, $52, $96, $07, $97, $50, $be, $03 - .db $c7, $23, $fe, $02, $0c, $82, $43, $45, $45, $24 - .db $46, $24, $90, $08, $95, $51, $78, $fa, $d7, $73 - .db $39, $f1, $8c, $01, $a8, $52, $b8, $52, $cc, $01 - .db $5f, $b3, $97, $63, $9e, $00, $0e, $81, $16, $24 - .db $66, $04, $8e, $00, $fe, $01, $08, $d2, $0e, $06 - .db $6f, $47, $9e, $0f, $0e, $82, $2d, $47, $28, $7a - .db $68, $7a, $a8, $7a, $ae, $01, $de, $0f, $6d, $c5 - .db $fd - -;level 4-2 -L_UndergroundArea2: - .db $48, $0f - .db $0e, $01, $5e, $02, $bc, $01, $fc, $01, $2c, $82 - .db $41, $52, $4e, $04, $67, $25, $68, $24, $69, $24 - .db $ba, $42, $c7, $04, $de, $0b, $b2, $87, $fe, $02 - .db $2c, $e1, $2c, $71, $67, $01, $77, $00, $87, $01 - .db $8e, $00, $ee, $01, $f6, $02, $03, $85, $05, $02 - .db $13, $21, $16, $02, $27, $02, $2e, $02, $88, $72 - .db $c7, $20, $d7, $07, $e4, $76, $07, $a0, $17, $06 - .db $48, $7a, $76, $20, $98, $72, $79, $e1, $88, $62 - .db $9c, $01, $b7, $73, $dc, $01, $f8, $62, $fe, $01 - .db $08, $e2, $0e, $00, $6e, $02, $73, $20, $77, $23 - .db $83, $04, $93, $20, $ae, $00, $fe, $0a, $0e, $82 - .db $39, $71, $a8, $72, $e7, $73, $0c, $81, $8f, $32 - .db $ae, $00, $fe, $04, $04, $d1, $17, $04, $26, $49 - .db $27, $29, $df, $33, $fe, $02, $44, $f6, $7c, $01 - .db $8e, $06, $bf, $47, $ee, $0f, $4d, $c7, $0e, $82 - .db $68, $7a, $ae, $01, $de, $0f, $6d, $c5 - .db $fd - -;underground bonus rooms area used in many levels -L_UndergroundArea3: - .db $48, $01 - .db $0e, $01, $00, $5a, $3e, $06, $45, $46, $47, $46 - .db $53, $44, $ae, $01, $df, $4a, $4d, $c7, $0e, $81 - .db $00, $5a, $2e, $04, $37, $28, $3a, $48, $46, $47 - .db $c7, $07, $ce, $0f, $df, $4a, $4d, $c7, $0e, $81 - .db $00, $5a, $33, $53, $43, $51, $46, $40, $47, $50 - .db $53, $04, $55, $40, $56, $50, $62, $43, $64, $40 - .db $65, $50, $71, $41, $73, $51, $83, $51, $94, $40 - .db $95, $50, $a3, $50, $a5, $40, $a6, $50, $b3, $51 - .db $b6, $40, $b7, $50, $c3, $53, $df, $4a, $4d, $c7 - .db $0e, $81, $00, $5a, $2e, $02, $36, $47, $37, $52 - .db $3a, $49, $47, $25, $a7, $52, $d7, $04, $df, $4a - .db $4d, $c7, $0e, $81, $00, $5a, $3e, $02, $44, $51 - .db $53, $44, $54, $44, $55, $24, $a1, $54, $ae, $01 - .db $b4, $21, $df, $4a, $e5, $07, $4d, $c7 - .db $fd - -;water area used in levels 5-2 and 6-2 -L_WaterArea1: - .db $41, $01 - .db $b4, $34, $c8, $52, $f2, $51, $47, $d3, $6c, $03 - .db $65, $49, $9e, $07, $be, $01, $cc, $03, $fe, $07 - .db $0d, $c9, $1e, $01, $6c, $01, $62, $35, $63, $53 - .db $8a, $41, $ac, $01, $b3, $53, $e9, $51, $26, $c3 - .db $27, $33, $63, $43, $64, $33, $ba, $60, $c9, $61 - .db $ce, $0b, $e5, $09, $ee, $0f, $7d, $ca, $7d, $47 - .db $fd - -;level 2-2/7-2 -L_WaterArea2: - .db $41, $01 - .db $b8, $52, $ea, $41, $27, $b2, $b3, $42, $16, $d4 - .db $4a, $42, $a5, $51, $a7, $31, $27, $d3, $08, $e2 - .db $16, $64, $2c, $04, $38, $42, $76, $64, $88, $62 - .db $de, $07, $fe, $01, $0d, $c9, $23, $32, $31, $51 - .db $98, $52, $0d, $c9, $59, $42, $63, $53, $67, $31 - .db $14, $c2, $36, $31, $87, $53, $17, $e3, $29, $61 - .db $30, $62, $3c, $08, $42, $37, $59, $40, $6a, $42 - .db $99, $40, $c9, $61, $d7, $63, $39, $d1, $58, $52 - .db $c3, $67, $d3, $31, $dc, $06, $f7, $42, $fa, $42 - .db $23, $b1, $43, $67, $c3, $34, $c7, $34, $d1, $51 - .db $43, $b3, $47, $33, $9a, $30, $a9, $61, $b8, $62 - .db $be, $0b, $d5, $09, $de, $0f, $0d, $ca, $7d, $47 - .db $fd - -;water area used in level 8-4 -L_WaterArea3: - .db $49, $0f - .db $1e, $01, $39, $73, $5e, $07, $ae, $0b, $1e, $82 - .db $6e, $88, $9e, $02, $0d, $04, $2e, $0b, $45, $09 - .db $4e, $0f, $ed, $47 - .db $fd - -;------------------------------------------------------------------------------------- - -;unused space - .db $ff - -;------------------------------------------------------------------------------------- - -;indirect jump routine called when -;$0770 is set to 1 -GameMode: - lda OperMode_Task - jsr JumpEngine - - .dw InitializeArea - .dw ScreenRoutines - .dw SecondaryGameSetup - .dw GameCoreRoutine - -;------------------------------------------------------------------------------------- - -GameCoreRoutine: - ldx CurrentPlayer ;get which player is on the screen - lda SavedJoypadBits,x ;use appropriate player's controller bits - sta SavedJoypadBits ;as the master controller bits - jsr GameRoutines ;execute one of many possible subs - lda OperMode_Task ;check major task of operating mode - cmp #$03 ;if we are supposed to be here, - bcs GameEngine ;branch to the game engine itself - rts - -GameEngine: - jsr ProcFireball_Bubble ;process fireballs and air bubbles - ldx #$00 -ProcELoop: stx ObjectOffset ;put incremented offset in X as enemy object offset - jsr EnemiesAndLoopsCore ;process enemy objects - jsr FloateyNumbersRoutine ;process floatey numbers - inx - cpx #$06 ;do these two subroutines until the whole buffer is done - bne ProcELoop - jsr GetPlayerOffscreenBits ;get offscreen bits for player object - jsr RelativePlayerPosition ;get relative coordinates for player object - jsr PlayerGfxHandler ;draw the player - jsr BlockObjMT_Updater ;replace block objects with metatiles if necessary - ldx #$01 - stx ObjectOffset ;set offset for second - jsr BlockObjectsCore ;process second block object - dex - stx ObjectOffset ;set offset for first - jsr BlockObjectsCore ;process first block object - jsr MiscObjectsCore ;process misc objects (hammer, jumping coins) - jsr ProcessCannons ;process bullet bill cannons - jsr ProcessWhirlpools ;process whirlpools - jsr FlagpoleRoutine ;process the flagpole - jsr RunGameTimer ;count down the game timer - jsr ColorRotation ;cycle one of the background colors - lda Player_Y_HighPos - cmp #$02 ;if player is below the screen, don't bother with the music - bpl NoChgMus - lda StarInvincibleTimer ;if star mario invincibility timer at zero, - beq ClrPlrPal ;skip this part - cmp #$04 - bne NoChgMus ;if not yet at a certain point, continue - lda IntervalTimerControl ;if interval timer not yet expired, - bne NoChgMus ;branch ahead, don't bother with the music - jsr GetAreaMusic ;to re-attain appropriate level music -NoChgMus: ldy StarInvincibleTimer ;get invincibility timer - lda FrameCounter ;get frame counter - cpy #$08 ;if timer still above certain point, - bcs CycleTwo ;branch to cycle player's palette quickly - lsr ;otherwise, divide by 8 to cycle every eighth frame - lsr -CycleTwo: lsr ;if branched here, divide by 2 to cycle every other frame - jsr CyclePlayerPalette ;do sub to cycle the palette (note: shares fire flower code) - jmp SaveAB ;then skip this sub to finish up the game engine -ClrPlrPal: jsr ResetPalStar ;do sub to clear player's palette bits in attributes -SaveAB: lda A_B_Buttons ;save current A and B button - sta PreviousA_B_Buttons ;into temp variable to be used on next frame - lda #$00 - sta Left_Right_Buttons ;nullify left and right buttons temp variable -UpdScrollVar: lda VRAM_Buffer_AddrCtrl - cmp #$06 ;if vram address controller set to 6 (one of two $0341s) - beq ExitEng ;then branch to leave - lda AreaParserTaskNum ;otherwise check number of tasks - bne RunParser - lda ScrollThirtyTwo ;get horizontal scroll in 0-31 or $00-$20 range - cmp #$20 ;check to see if exceeded $21 - bmi ExitEng ;branch to leave if not - lda ScrollThirtyTwo - sbc #$20 ;otherwise subtract $20 to set appropriately - sta ScrollThirtyTwo ;and store - lda #$00 ;reset vram buffer offset used in conjunction with - sta VRAM_Buffer2_Offset ;level graphics buffer at $0341-$035f -RunParser: jsr AreaParserTaskHandler ;update the name table with more level graphics -ExitEng: rts ;and after all that, we're finally done! - -;------------------------------------------------------------------------------------- - -ScrollHandler: - lda Player_X_Scroll ;load value saved here - clc - adc Platform_X_Scroll ;add value used by left/right platforms - sta Player_X_Scroll ;save as new value here to impose force on scroll - lda ScrollLock ;check scroll lock flag - bne InitScrlAmt ;skip a bunch of code here if set - lda Player_Pos_ForScroll - cmp #$50 ;check player's horizontal screen position - bcc InitScrlAmt ;if less than 80 pixels to the right, branch - lda SideCollisionTimer ;if timer related to player's side collision - bne InitScrlAmt ;not expired, branch - ldy Player_X_Scroll ;get value and decrement by one - dey ;if value originally set to zero or otherwise - bmi InitScrlAmt ;negative for left movement, branch - iny - cpy #$02 ;if value $01, branch and do not decrement - bcc ChkNearMid - dey ;otherwise decrement by one -ChkNearMid: lda Player_Pos_ForScroll - cmp #$70 ;check player's horizontal screen position - bcc ScrollScreen ;if less than 112 pixels to the right, branch - ldy Player_X_Scroll ;otherwise get original value undecremented - -ScrollScreen: - tya - sta ScrollAmount ;save value here - clc - adc ScrollThirtyTwo ;add to value already set here - sta ScrollThirtyTwo ;save as new value here - tya - clc - adc ScreenLeft_X_Pos ;add to left side coordinate - sta ScreenLeft_X_Pos ;save as new left side coordinate - sta HorizontalScroll ;save here also - lda ScreenLeft_PageLoc - adc #$00 ;add carry to page location for left - sta ScreenLeft_PageLoc ;side of the screen - and #$01 ;get LSB of page location - sta $00 ;save as temp variable for PPU register 1 mirror - lda Mirror_PPU_CTRL_REG1 ;get PPU register 1 mirror - and #%11111110 ;save all bits except d0 - ora $00 ;get saved bit here and save in PPU register 1 - sta Mirror_PPU_CTRL_REG1 ;mirror to be used to set name table later - jsr GetScreenPosition ;figure out where the right side is - lda #$08 - sta ScrollIntervalTimer ;set scroll timer (residual, not used elsewhere) - jmp ChkPOffscr ;skip this part -InitScrlAmt: lda #$00 - sta ScrollAmount ;initialize value here -ChkPOffscr: ldx #$00 ;set X for player offset - jsr GetXOffscreenBits ;get horizontal offscreen bits for player - sta $00 ;save them here - ldy #$00 ;load default offset (left side) - asl ;if d7 of offscreen bits are set, - bcs KeepOnscr ;branch with default offset - iny ;otherwise use different offset (right side) - lda $00 - and #%00100000 ;check offscreen bits for d5 set - beq InitPlatScrl ;if not set, branch ahead of this part -KeepOnscr: lda ScreenEdge_X_Pos,y ;get left or right side coordinate based on offset - sec - sbc X_SubtracterData,y ;subtract amount based on offset - sta Player_X_Position ;store as player position to prevent movement further - lda ScreenEdge_PageLoc,y ;get left or right page location based on offset - sbc #$00 ;subtract borrow - sta Player_PageLoc ;save as player's page location - lda Left_Right_Buttons ;check saved controller bits - cmp OffscrJoypadBitsData,y ;against bits based on offset - beq InitPlatScrl ;if not equal, branch - lda #$00 - sta Player_X_Speed ;otherwise nullify horizontal speed of player -InitPlatScrl: lda #$00 ;nullify platform force imposed on scroll - sta Platform_X_Scroll - rts - -X_SubtracterData: - .db $00, $10 - -OffscrJoypadBitsData: - .db $01, $02 - -;------------------------------------------------------------------------------------- - -GetScreenPosition: - lda ScreenLeft_X_Pos ;get coordinate of screen's left boundary - clc - adc #$ff ;add 255 pixels - sta ScreenRight_X_Pos ;store as coordinate of screen's right boundary - lda ScreenLeft_PageLoc ;get page number where left boundary is - adc #$00 ;add carry from before - sta ScreenRight_PageLoc ;store as page number where right boundary is - rts - -;------------------------------------------------------------------------------------- - -GameRoutines: - lda GameEngineSubroutine ;run routine based on number (a few of these routines are - jsr JumpEngine ;merely placeholders as conditions for other routines) - - .dw Entrance_GameTimerSetup - .dw Vine_AutoClimb - .dw SideExitPipeEntry - .dw VerticalPipeEntry - .dw FlagpoleSlide - .dw PlayerEndLevel - .dw PlayerLoseLife - .dw PlayerEntrance - .dw PlayerCtrlRoutine - .dw PlayerChangeSize - .dw PlayerInjuryBlink - .dw PlayerDeath - .dw PlayerFireFlower - -;------------------------------------------------------------------------------------- - -PlayerEntrance: - lda AltEntranceControl ;check for mode of alternate entry - cmp #$02 - beq EntrMode2 ;if found, branch to enter from pipe or with vine - lda #$00 - ldy Player_Y_Position ;if vertical position above a certain - cpy #$30 ;point, nullify controller bits and continue - bcc AutoControlPlayer ;with player movement code, do not return - lda PlayerEntranceCtrl ;check player entry bits from header - cmp #$06 - beq ChkBehPipe ;if set to 6 or 7, execute pipe intro code - cmp #$07 ;otherwise branch to normal entry - bne PlayerRdy -ChkBehPipe: lda Player_SprAttrib ;check for sprite attributes - bne IntroEntr ;branch if found - lda #$01 - jmp AutoControlPlayer ;force player to walk to the right -IntroEntr: jsr EnterSidePipe ;execute sub to move player to the right - dec ChangeAreaTimer ;decrement timer for change of area - bne ExitEntr ;branch to exit if not yet expired - inc DisableIntermediate ;set flag to skip world and lives display - jmp NextArea ;jump to increment to next area and set modes -EntrMode2: lda JoypadOverride ;if controller override bits set here, - bne VineEntr ;branch to enter with vine - lda #$ff ;otherwise, set value here then execute sub - jsr MovePlayerYAxis ;to move player upwards - lda Player_Y_Position ;check to see if player is at a specific coordinate - cmp #$91 ;if player risen to a certain point (this requires pipes - bcc PlayerRdy ;to be at specific height to look/function right) branch - rts ;to the last part, otherwise leave -VineEntr: lda VineHeight - cmp #$60 ;check vine height - bne ExitEntr ;if vine not yet reached maximum height, branch to leave - lda Player_Y_Position ;get player's vertical coordinate - cmp #$99 ;check player's vertical coordinate against preset value - ldy #$00 ;load default values to be written to - lda #$01 ;this value moves player to the right off the vine - bcc OffVine ;if vertical coordinate < preset value, use defaults - lda #$03 - sta Player_State ;otherwise set player state to climbing - iny ;increment value in Y - lda #$08 ;set block in block buffer to cover hole, then - sta Block_Buffer_1+$b4 ;use same value to force player to climb -OffVine: sty DisableCollisionDet ;set collision detection disable flag - jsr AutoControlPlayer ;use contents of A to move player up or right, execute sub - lda Player_X_Position - cmp #$48 ;check player's horizontal position - bcc ExitEntr ;if not far enough to the right, branch to leave -PlayerRdy: lda #$08 ;set routine to be executed by game engine next frame - sta GameEngineSubroutine - lda #$01 ;set to face player to the right - sta PlayerFacingDir - lsr ;init A - sta AltEntranceControl ;init mode of entry - sta DisableCollisionDet ;init collision detection disable flag - sta JoypadOverride ;nullify controller override bits -ExitEntr: rts ;leave! - -;------------------------------------------------------------------------------------- -;$07 - used to hold upper limit of high byte when player falls down hole - -AutoControlPlayer: - sta SavedJoypadBits ;override controller bits with contents of A if executing here - -PlayerCtrlRoutine: - lda GameEngineSubroutine ;check task here - cmp #$0b ;if certain value is set, branch to skip controller bit loading - beq SizeChk - lda AreaType ;are we in a water type area? - bne SaveJoyp ;if not, branch - ldy Player_Y_HighPos - dey ;if not in vertical area between - bne DisJoyp ;status bar and bottom, branch - lda Player_Y_Position - cmp #$d0 ;if nearing the bottom of the screen or - bcc SaveJoyp ;not in the vertical area between status bar or bottom, -DisJoyp: lda #$00 ;disable controller bits - sta SavedJoypadBits -SaveJoyp: lda SavedJoypadBits ;otherwise store A and B buttons in $0a - and #%11000000 - sta A_B_Buttons - lda SavedJoypadBits ;store left and right buttons in $0c - and #%00000011 - sta Left_Right_Buttons - lda SavedJoypadBits ;store up and down buttons in $0b - and #%00001100 - sta Up_Down_Buttons - and #%00000100 ;check for pressing down - beq SizeChk ;if not, branch - lda Player_State ;check player's state - bne SizeChk ;if not on the ground, branch - ldy Left_Right_Buttons ;check left and right - beq SizeChk ;if neither pressed, branch - lda #$00 - sta Left_Right_Buttons ;if pressing down while on the ground, - sta Up_Down_Buttons ;nullify directional bits -SizeChk: jsr PlayerMovementSubs ;run movement subroutines - ldy #$01 ;is player small? - lda PlayerSize - bne ChkMoveDir - ldy #$00 ;check for if crouching - lda CrouchingFlag - beq ChkMoveDir ;if not, branch ahead - ldy #$02 ;if big and crouching, load y with 2 -ChkMoveDir: sty Player_BoundBoxCtrl ;set contents of Y as player's bounding box size control - lda #$01 ;set moving direction to right by default - ldy Player_X_Speed ;check player's horizontal speed - beq PlayerSubs ;if not moving at all horizontally, skip this part - bpl SetMoveDir ;if moving to the right, use default moving direction - asl ;otherwise change to move to the left -SetMoveDir: sta Player_MovingDir ;set moving direction -PlayerSubs: jsr ScrollHandler ;move the screen if necessary - jsr GetPlayerOffscreenBits ;get player's offscreen bits - jsr RelativePlayerPosition ;get coordinates relative to the screen - ldx #$00 ;set offset for player object - jsr BoundingBoxCore ;get player's bounding box coordinates - jsr PlayerBGCollision ;do collision detection and process - lda Player_Y_Position - cmp #$40 ;check to see if player is higher than 64th pixel - bcc PlayerHole ;if so, branch ahead - lda GameEngineSubroutine - cmp #$05 ;if running end-of-level routine, branch ahead - beq PlayerHole - cmp #$07 ;if running player entrance routine, branch ahead - beq PlayerHole - cmp #$04 ;if running routines $00-$03, branch ahead - bcc PlayerHole - lda Player_SprAttrib - and #%11011111 ;otherwise nullify player's - sta Player_SprAttrib ;background priority flag -PlayerHole: lda Player_Y_HighPos ;check player's vertical high byte - cmp #$02 ;for below the screen - bmi ExitCtrl ;branch to leave if not that far down - ldx #$01 - stx ScrollLock ;set scroll lock - ldy #$04 - sty $07 ;set value here - ldx #$00 ;use X as flag, and clear for cloud level - ldy GameTimerExpiredFlag ;check game timer expiration flag - bne HoleDie ;if set, branch - ldy CloudTypeOverride ;check for cloud type override - bne ChkHoleX ;skip to last part if found -HoleDie: inx ;set flag in X for player death - ldy GameEngineSubroutine - cpy #$0b ;check for some other routine running - beq ChkHoleX ;if so, branch ahead - ldy DeathMusicLoaded ;check value here - bne HoleBottom ;if already set, branch to next part - iny - sty EventMusicQueue ;otherwise play death music - sty DeathMusicLoaded ;and set value here -HoleBottom: ldy #$06 - sty $07 ;change value here -ChkHoleX: cmp $07 ;compare vertical high byte with value set here - bmi ExitCtrl ;if less, branch to leave - dex ;otherwise decrement flag in X - bmi CloudExit ;if flag was clear, branch to set modes and other values - ldy EventMusicBuffer ;check to see if music is still playing - bne ExitCtrl ;branch to leave if so - lda #$06 ;otherwise set to run lose life routine - sta GameEngineSubroutine ;on next frame -ExitCtrl: rts ;leave - -CloudExit: - lda #$00 - sta JoypadOverride ;clear controller override bits if any are set - jsr SetEntr ;do sub to set secondary mode - inc AltEntranceControl ;set mode of entry to 3 - rts - -;------------------------------------------------------------------------------------- - -Vine_AutoClimb: - lda Player_Y_HighPos ;check to see whether player reached position - bne AutoClimb ;above the status bar yet and if so, set modes - lda Player_Y_Position - cmp #$e4 - bcc SetEntr -AutoClimb: lda #%00001000 ;set controller bits override to up - sta JoypadOverride - ldy #$03 ;set player state to climbing - sty Player_State - jmp AutoControlPlayer -SetEntr: lda #$02 ;set starting position to override - sta AltEntranceControl - jmp ChgAreaMode ;set modes - -;------------------------------------------------------------------------------------- - -VerticalPipeEntry: - lda #$01 ;set 1 as movement amount - jsr MovePlayerYAxis ;do sub to move player downwards - jsr ScrollHandler ;do sub to scroll screen with saved force if necessary - ldy #$00 ;load default mode of entry - lda WarpZoneControl ;check warp zone control variable/flag - bne ChgAreaPipe ;if set, branch to use mode 0 - iny - lda AreaType ;check for castle level type - cmp #$03 - bne ChgAreaPipe ;if not castle type level, use mode 1 - iny - jmp ChgAreaPipe ;otherwise use mode 2 - -MovePlayerYAxis: - clc - adc Player_Y_Position ;add contents of A to player position - sta Player_Y_Position - rts - -;------------------------------------------------------------------------------------- - -SideExitPipeEntry: - jsr EnterSidePipe ;execute sub to move player to the right - ldy #$02 -ChgAreaPipe: dec ChangeAreaTimer ;decrement timer for change of area - bne ExitCAPipe - sty AltEntranceControl ;when timer expires set mode of alternate entry -ChgAreaMode: inc DisableScreenFlag ;set flag to disable screen output - lda #$00 - sta OperMode_Task ;set secondary mode of operation - sta Sprite0HitDetectFlag ;disable sprite 0 check -ExitCAPipe: rts ;leave - -EnterSidePipe: - lda #$08 ;set player's horizontal speed - sta Player_X_Speed - ldy #$01 ;set controller right button by default - lda Player_X_Position ;mask out higher nybble of player's - and #%00001111 ;horizontal position - bne RightPipe - sta Player_X_Speed ;if lower nybble = 0, set as horizontal speed - tay ;and nullify controller bit override here -RightPipe: tya ;use contents of Y to - jsr AutoControlPlayer ;execute player control routine with ctrl bits nulled - rts - -;------------------------------------------------------------------------------------- - -PlayerChangeSize: - lda TimerControl ;check master timer control - cmp #$f8 ;for specific moment in time - bne EndChgSize ;branch if before or after that point - jmp InitChangeSize ;otherwise run code to get growing/shrinking going -EndChgSize: cmp #$c4 ;check again for another specific moment - bne ExitChgSize ;and branch to leave if before or after that point - jsr DonePlayerTask ;otherwise do sub to init timer control and set routine -ExitChgSize: rts ;and then leave - -;------------------------------------------------------------------------------------- - -PlayerInjuryBlink: - lda TimerControl ;check master timer control - cmp #$f0 ;for specific moment in time - bcs ExitBlink ;branch if before that point - cmp #$c8 ;check again for another specific point - beq DonePlayerTask ;branch if at that point, and not before or after - jmp PlayerCtrlRoutine ;otherwise run player control routine -ExitBlink: bne ExitBoth ;do unconditional branch to leave - -InitChangeSize: - ldy PlayerChangeSizeFlag ;if growing/shrinking flag already set - bne ExitBoth ;then branch to leave - sty PlayerAnimCtrl ;otherwise initialize player's animation frame control - inc PlayerChangeSizeFlag ;set growing/shrinking flag - lda PlayerSize - eor #$01 ;invert player's size - sta PlayerSize -ExitBoth: rts ;leave - -;------------------------------------------------------------------------------------- -;$00 - used in CyclePlayerPalette to store current palette to cycle - -PlayerDeath: - lda TimerControl ;check master timer control - cmp #$f0 ;for specific moment in time - bcs ExitDeath ;branch to leave if before that point - jmp PlayerCtrlRoutine ;otherwise run player control routine - -DonePlayerTask: - lda #$00 - sta TimerControl ;initialize master timer control to continue timers - lda #$08 - sta GameEngineSubroutine ;set player control routine to run next frame - rts ;leave - -PlayerFireFlower: - lda TimerControl ;check master timer control - cmp #$c0 ;for specific moment in time - beq ResetPalFireFlower ;branch if at moment, not before or after - lda FrameCounter ;get frame counter - lsr - lsr ;divide by four to change every four frames - -CyclePlayerPalette: - and #$03 ;mask out all but d1-d0 (previously d3-d2) - sta $00 ;store result here to use as palette bits - lda Player_SprAttrib ;get player attributes - and #%11111100 ;save any other bits but palette bits - ora $00 ;add palette bits - sta Player_SprAttrib ;store as new player attributes - rts ;and leave - -ResetPalFireFlower: - jsr DonePlayerTask ;do sub to init timer control and run player control routine - -ResetPalStar: - lda Player_SprAttrib ;get player attributes - and #%11111100 ;mask out palette bits to force palette 0 - sta Player_SprAttrib ;store as new player attributes - rts ;and leave - -ExitDeath: - rts ;leave from death routine - -;------------------------------------------------------------------------------------- - -FlagpoleSlide: - lda Enemy_ID+5 ;check special use enemy slot - cmp #FlagpoleFlagObject ;for flagpole flag object - bne NoFPObj ;if not found, branch to something residual - lda FlagpoleSoundQueue ;load flagpole sound - sta Square1SoundQueue ;into square 1's sfx queue - lda #$00 - sta FlagpoleSoundQueue ;init flagpole sound queue - ldy Player_Y_Position - cpy #$9e ;check to see if player has slid down - bcs SlidePlayer ;far enough, and if so, branch with no controller bits set - lda #$04 ;otherwise force player to climb down (to slide) -SlidePlayer: jmp AutoControlPlayer ;jump to player control routine -NoFPObj: inc GameEngineSubroutine ;increment to next routine (this may - rts ;be residual code) - -;------------------------------------------------------------------------------------- - -Hidden1UpCoinAmts: - .db $15, $23, $16, $1b, $17, $18, $23, $63 - -PlayerEndLevel: - lda #$01 ;force player to walk to the right - jsr AutoControlPlayer - lda Player_Y_Position ;check player's vertical position - cmp #$ae - bcc ChkStop ;if player is not yet off the flagpole, skip this part - lda ScrollLock ;if scroll lock not set, branch ahead to next part - beq ChkStop ;because we only need to do this part once - lda #EndOfLevelMusic - sta EventMusicQueue ;load win level music in event music queue - lda #$00 - sta ScrollLock ;turn off scroll lock to skip this part later -ChkStop: lda Player_CollisionBits ;get player collision bits - lsr ;check for d0 set - bcs RdyNextA ;if d0 set, skip to next part - lda StarFlagTaskControl ;if star flag task control already set, - bne InCastle ;go ahead with the rest of the code - inc StarFlagTaskControl ;otherwise set task control now (this gets ball rolling!) -InCastle: lda #%00100000 ;set player's background priority bit to - sta Player_SprAttrib ;give illusion of being inside the castle -RdyNextA: lda StarFlagTaskControl - cmp #$05 ;if star flag task control not yet set - bne ExitNA ;beyond last valid task number, branch to leave - inc LevelNumber ;increment level number used for game logic - lda LevelNumber - cmp #$03 ;check to see if we have yet reached level -4 - bne NextArea ;and skip this last part here if not - ldy WorldNumber ;get world number as offset - lda CoinTallyFor1Ups ;check third area coin tally for bonus 1-ups - cmp Hidden1UpCoinAmts,y ;against minimum value, if player has not collected - bcc NextArea ;at least this number of coins, leave flag clear - inc Hidden1UpFlag ;otherwise set hidden 1-up box control flag -NextArea: inc AreaNumber ;increment area number used for address loader - jsr LoadAreaPointer ;get new level pointer - inc FetchNewGameTimerFlag ;set flag to load new game timer - jsr ChgAreaMode ;do sub to set secondary mode, disable screen and sprite 0 - sta HalfwayPage ;reset halfway page to 0 (beginning) - lda #Silence - sta EventMusicQueue ;silence music and leave -ExitNA: rts - -;------------------------------------------------------------------------------------- - -PlayerMovementSubs: - lda #$00 ;set A to init crouch flag by default - ldy PlayerSize ;is player small? - bne SetCrouch ;if so, branch - lda Player_State ;check state of player - bne ProcMove ;if not on the ground, branch - lda Up_Down_Buttons ;load controller bits for up and down - and #%00000100 ;single out bit for down button -SetCrouch: sta CrouchingFlag ;store value in crouch flag -ProcMove: jsr PlayerPhysicsSub ;run sub related to jumping and swimming - lda PlayerChangeSizeFlag ;if growing/shrinking flag set, - bne NoMoveSub ;branch to leave - lda Player_State - cmp #$03 ;get player state - beq MoveSubs ;if climbing, branch ahead, leave timer unset - ldy #$18 - sty ClimbSideTimer ;otherwise reset timer now -MoveSubs: jsr JumpEngine - - .dw OnGroundStateSub - .dw JumpSwimSub - .dw FallingSub - .dw ClimbingSub - -NoMoveSub: rts - -;------------------------------------------------------------------------------------- -;$00 - used by ClimbingSub to store high vertical adder - -OnGroundStateSub: - jsr GetPlayerAnimSpeed ;do a sub to set animation frame timing - lda Left_Right_Buttons - beq GndMove ;if left/right controller bits not set, skip instruction - sta PlayerFacingDir ;otherwise set new facing direction -GndMove: jsr ImposeFriction ;do a sub to impose friction on player's walk/run - jsr MovePlayerHorizontally ;do another sub to move player horizontally - sta Player_X_Scroll ;set returned value as player's movement speed for scroll - rts - -;-------------------------------- - -FallingSub: - lda VerticalForceDown - sta VerticalForce ;dump vertical movement force for falling into main one - jmp LRAir ;movement force, then skip ahead to process left/right movement - -;-------------------------------- - -JumpSwimSub: - ldy Player_Y_Speed ;if player's vertical speed zero - bpl DumpFall ;or moving downwards, branch to falling - lda A_B_Buttons - and #A_Button ;check to see if A button is being pressed - and PreviousA_B_Buttons ;and was pressed in previous frame - bne ProcSwim ;if so, branch elsewhere - lda JumpOrigin_Y_Position ;get vertical position player jumped from - sec - sbc Player_Y_Position ;subtract current from original vertical coordinate - cmp DiffToHaltJump ;compare to value set here to see if player is in mid-jump - bcc ProcSwim ;or just starting to jump, if just starting, skip ahead -DumpFall: lda VerticalForceDown ;otherwise dump falling into main fractional - sta VerticalForce -ProcSwim: lda SwimmingFlag ;if swimming flag not set, - beq LRAir ;branch ahead to last part - jsr GetPlayerAnimSpeed ;do a sub to get animation frame timing - lda Player_Y_Position - cmp #$14 ;check vertical position against preset value - bcs LRWater ;if not yet reached a certain position, branch ahead - lda #$18 - sta VerticalForce ;otherwise set fractional -LRWater: lda Left_Right_Buttons ;check left/right controller bits (check for swimming) - beq LRAir ;if not pressing any, skip - sta PlayerFacingDir ;otherwise set facing direction accordingly -LRAir: lda Left_Right_Buttons ;check left/right controller bits (check for jumping/falling) - beq JSMove ;if not pressing any, skip - jsr ImposeFriction ;otherwise process horizontal movement -JSMove: jsr MovePlayerHorizontally ;do a sub to move player horizontally - sta Player_X_Scroll ;set player's speed here, to be used for scroll later - lda GameEngineSubroutine - cmp #$0b ;check for specific routine selected - bne ExitMov1 ;branch if not set to run - lda #$28 - sta VerticalForce ;otherwise set fractional -ExitMov1: jmp MovePlayerVertically ;jump to move player vertically, then leave - -;-------------------------------- - -ClimbAdderLow: - .db $0e, $04, $fc, $f2 -ClimbAdderHigh: - .db $00, $00, $ff, $ff - -ClimbingSub: - lda Player_YMF_Dummy - clc ;add movement force to dummy variable - adc Player_Y_MoveForce ;save with carry - sta Player_YMF_Dummy - ldy #$00 ;set default adder here - lda Player_Y_Speed ;get player's vertical speed - bpl MoveOnVine ;if not moving upwards, branch - dey ;otherwise set adder to $ff -MoveOnVine: sty $00 ;store adder here - adc Player_Y_Position ;add carry to player's vertical position - sta Player_Y_Position ;and store to move player up or down - lda Player_Y_HighPos - adc $00 ;add carry to player's page location - sta Player_Y_HighPos ;and store - lda Left_Right_Buttons ;compare left/right controller bits - and Player_CollisionBits ;to collision flag - beq InitCSTimer ;if not set, skip to end - ldy ClimbSideTimer ;otherwise check timer - bne ExitCSub ;if timer not expired, branch to leave - ldy #$18 - sty ClimbSideTimer ;otherwise set timer now - ldx #$00 ;set default offset here - ldy PlayerFacingDir ;get facing direction - lsr ;move right button controller bit to carry - bcs ClimbFD ;if controller right pressed, branch ahead - inx - inx ;otherwise increment offset by 2 bytes -ClimbFD: dey ;check to see if facing right - beq CSetFDir ;if so, branch, do not increment - inx ;otherwise increment by 1 byte -CSetFDir: lda Player_X_Position - clc ;add or subtract from player's horizontal position - adc ClimbAdderLow,x ;using value here as adder and X as offset - sta Player_X_Position - lda Player_PageLoc ;add or subtract carry or borrow using value here - adc ClimbAdderHigh,x ;from the player's page location - sta Player_PageLoc - lda Left_Right_Buttons ;get left/right controller bits again - eor #%00000011 ;invert them and store them while player - sta PlayerFacingDir ;is on vine to face player in opposite direction -ExitCSub: rts ;then leave -InitCSTimer: sta ClimbSideTimer ;initialize timer here - rts - -;------------------------------------------------------------------------------------- -;$00 - used to store offset to friction data - -JumpMForceData: - .db $20, $20, $1e, $28, $28, $0d, $04 - -FallMForceData: - .db $70, $70, $60, $90, $90, $0a, $09 - -PlayerYSpdData: - .db $fc, $fc, $fc, $fb, $fb, $fe, $ff - -InitMForceData: - .db $00, $00, $00, $00, $00, $80, $00 - -MaxLeftXSpdData: - .db $d8, $e8, $f0 - -MaxRightXSpdData: - .db $28, $18, $10 - .db $0c ;used for pipe intros - -FrictionData: - .db $e4, $98, $d0 - -Climb_Y_SpeedData: - .db $00, $ff, $01 - -Climb_Y_MForceData: - .db $00, $20, $ff - -PlayerPhysicsSub: - lda Player_State ;check player state - cmp #$03 - bne CheckForJumping ;if not climbing, branch - ldy #$00 - lda Up_Down_Buttons ;get controller bits for up/down - and Player_CollisionBits ;check against player's collision detection bits - beq ProcClimb ;if not pressing up or down, branch - iny - and #%00001000 ;check for pressing up - bne ProcClimb - iny -ProcClimb: ldx Climb_Y_MForceData,y ;load value here - stx Player_Y_MoveForce ;store as vertical movement force - lda #$08 ;load default animation timing - ldx Climb_Y_SpeedData,y ;load some other value here - stx Player_Y_Speed ;store as vertical speed - bmi SetCAnim ;if climbing down, use default animation timing value - lsr ;otherwise divide timer setting by 2 -SetCAnim: sta PlayerAnimTimerSet ;store animation timer setting and leave - rts - -CheckForJumping: - lda JumpspringAnimCtrl ;if jumpspring animating, - bne NoJump ;skip ahead to something else - lda A_B_Buttons ;check for A button press - and #A_Button - beq NoJump ;if not, branch to something else - and PreviousA_B_Buttons ;if button not pressed in previous frame, branch - beq ProcJumping -NoJump: jmp X_Physics ;otherwise, jump to something else - -ProcJumping: - lda Player_State ;check player state - beq InitJS ;if on the ground, branch - lda SwimmingFlag ;if swimming flag not set, jump to do something else - beq NoJump ;to prevent midair jumping, otherwise continue - lda JumpSwimTimer ;if jump/swim timer nonzero, branch - bne InitJS - lda Player_Y_Speed ;check player's vertical speed - bpl InitJS ;if player's vertical speed motionless or down, branch - jmp X_Physics ;if timer at zero and player still rising, do not swim -InitJS: lda #$20 ;set jump/swim timer - sta JumpSwimTimer - ldy #$00 ;initialize vertical force and dummy variable - sty Player_YMF_Dummy - sty Player_Y_MoveForce - lda Player_Y_HighPos ;get vertical high and low bytes of jump origin - sta JumpOrigin_Y_HighPos ;and store them next to each other here - lda Player_Y_Position - sta JumpOrigin_Y_Position - lda #$01 ;set player state to jumping/swimming - sta Player_State - lda Player_XSpeedAbsolute ;check value related to walking/running speed - cmp #$09 - bcc ChkWtr ;branch if below certain values, increment Y - iny ;for each amount equal or exceeded - cmp #$10 - bcc ChkWtr - iny - cmp #$19 - bcc ChkWtr - iny - cmp #$1c - bcc ChkWtr ;note that for jumping, range is 0-4 for Y - iny -ChkWtr: lda #$01 ;set value here (apparently always set to 1) - sta DiffToHaltJump - lda SwimmingFlag ;if swimming flag disabled, branch - beq GetYPhy - ldy #$05 ;otherwise set Y to 5, range is 5-6 - lda Whirlpool_Flag ;if whirlpool flag not set, branch - beq GetYPhy - iny ;otherwise increment to 6 -GetYPhy: lda JumpMForceData,y ;store appropriate jump/swim - sta VerticalForce ;data here - lda FallMForceData,y - sta VerticalForceDown - lda InitMForceData,y - sta Player_Y_MoveForce - lda PlayerYSpdData,y - sta Player_Y_Speed - lda SwimmingFlag ;if swimming flag disabled, branch - beq PJumpSnd - lda #Sfx_EnemyStomp ;load swim/goomba stomp sound into - sta Square1SoundQueue ;square 1's sfx queue - lda Player_Y_Position - cmp #$14 ;check vertical low byte of player position - bcs X_Physics ;if below a certain point, branch - lda #$00 ;otherwise reset player's vertical speed - sta Player_Y_Speed ;and jump to something else to keep player - jmp X_Physics ;from swimming above water level -PJumpSnd: lda #Sfx_BigJump ;load big mario's jump sound by default - ldy PlayerSize ;is mario big? - beq SJumpSnd - lda #Sfx_SmallJump ;if not, load small mario's jump sound -SJumpSnd: sta Square1SoundQueue ;store appropriate jump sound in square 1 sfx queue -X_Physics: ldy #$00 - sty $00 ;init value here - lda Player_State ;if mario is on the ground, branch - beq ProcPRun - lda Player_XSpeedAbsolute ;check something that seems to be related - cmp #$19 ;to mario's speed - bcs GetXPhy ;if =>$19 branch here - bcc ChkRFast ;if not branch elsewhere -ProcPRun: iny ;if mario on the ground, increment Y - lda AreaType ;check area type - beq ChkRFast ;if water type, branch - dey ;decrement Y by default for non-water type area - lda Left_Right_Buttons ;get left/right controller bits - cmp Player_MovingDir ;check against moving direction - bne ChkRFast ;if controller bits <> moving direction, skip this part - lda A_B_Buttons ;check for b button pressed - and #B_Button - bne SetRTmr ;if pressed, skip ahead to set timer - lda RunningTimer ;check for running timer set - bne GetXPhy ;if set, branch -ChkRFast: iny ;if running timer not set or level type is water, - inc $00 ;increment Y again and temp variable in memory - lda RunningSpeed - bne FastXSp ;if running speed set here, branch - lda Player_XSpeedAbsolute - cmp #$21 ;otherwise check player's walking/running speed - bcc GetXPhy ;if less than a certain amount, branch ahead -FastXSp: inc $00 ;if running speed set or speed => $21 increment $00 - jmp GetXPhy ;and jump ahead -SetRTmr: lda #$0a ;if b button pressed, set running timer - sta RunningTimer -GetXPhy: lda MaxLeftXSpdData,y ;get maximum speed to the left - sta MaximumLeftSpeed - lda GameEngineSubroutine ;check for specific routine running - cmp #$07 ;(player entrance) - bne GetXPhy2 ;if not running, skip and use old value of Y - ldy #$03 ;otherwise set Y to 3 -GetXPhy2: lda MaxRightXSpdData,y ;get maximum speed to the right - sta MaximumRightSpeed - ldy $00 ;get other value in memory - lda FrictionData,y ;get value using value in memory as offset - sta FrictionAdderLow - lda #$00 - sta FrictionAdderHigh ;init something here - lda PlayerFacingDir - cmp Player_MovingDir ;check facing direction against moving direction - beq ExitPhy ;if the same, branch to leave - asl FrictionAdderLow ;otherwise multiply friction by 2 - rol FrictionAdderHigh ;then leave -ExitPhy: rts - -;------------------------------------------------------------------------------------- - -PlayerAnimTmrData: - .db $02, $04, $07 - -GetPlayerAnimSpeed: - ldy #$00 ;initialize offset in Y - lda Player_XSpeedAbsolute ;check player's walking/running speed - cmp #$1c ;against preset amount - bcs SetRunSpd ;if greater than a certain amount, branch ahead - iny ;otherwise increment Y - cmp #$0e ;compare against lower amount - bcs ChkSkid ;if greater than this but not greater than first, skip increment - iny ;otherwise increment Y again -ChkSkid: lda SavedJoypadBits ;get controller bits - and #%01111111 ;mask out A button - beq SetAnimSpd ;if no other buttons pressed, branch ahead of all this - and #$03 ;mask out all others except left and right - cmp Player_MovingDir ;check against moving direction - bne ProcSkid ;if left/right controller bits <> moving direction, branch - lda #$00 ;otherwise set zero value here -SetRunSpd: sta RunningSpeed ;store zero or running speed here - jmp SetAnimSpd -ProcSkid: lda Player_XSpeedAbsolute ;check player's walking/running speed - cmp #$0b ;against one last amount - bcs SetAnimSpd ;if greater than this amount, branch - lda PlayerFacingDir - sta Player_MovingDir ;otherwise use facing direction to set moving direction - lda #$00 - sta Player_X_Speed ;nullify player's horizontal speed - sta Player_X_MoveForce ;and dummy variable for player -SetAnimSpd: lda PlayerAnimTmrData,y ;get animation timer setting using Y as offset - sta PlayerAnimTimerSet - rts - -;------------------------------------------------------------------------------------- - -ImposeFriction: - and Player_CollisionBits ;perform AND between left/right controller bits and collision flag - cmp #$00 ;then compare to zero (this instruction is redundant) - bne JoypFrict ;if any bits set, branch to next part - lda Player_X_Speed - beq SetAbsSpd ;if player has no horizontal speed, branch ahead to last part - bpl RghtFrict ;if player moving to the right, branch to slow - bmi LeftFrict ;otherwise logic dictates player moving left, branch to slow -JoypFrict: lsr ;put right controller bit into carry - bcc RghtFrict ;if left button pressed, carry = 0, thus branch -LeftFrict: lda Player_X_MoveForce ;load value set here - clc - adc FrictionAdderLow ;add to it another value set here - sta Player_X_MoveForce ;store here - lda Player_X_Speed - adc FrictionAdderHigh ;add value plus carry to horizontal speed - sta Player_X_Speed ;set as new horizontal speed - cmp MaximumRightSpeed ;compare against maximum value for right movement - bmi XSpdSign ;if horizontal speed greater negatively, branch - lda MaximumRightSpeed ;otherwise set preset value as horizontal speed - sta Player_X_Speed ;thus slowing the player's left movement down - jmp SetAbsSpd ;skip to the end -RghtFrict: lda Player_X_MoveForce ;load value set here - sec - sbc FrictionAdderLow ;subtract from it another value set here - sta Player_X_MoveForce ;store here - lda Player_X_Speed - sbc FrictionAdderHigh ;subtract value plus borrow from horizontal speed - sta Player_X_Speed ;set as new horizontal speed - cmp MaximumLeftSpeed ;compare against maximum value for left movement - bpl XSpdSign ;if horizontal speed greater positively, branch - lda MaximumLeftSpeed ;otherwise set preset value as horizontal speed - sta Player_X_Speed ;thus slowing the player's right movement down -XSpdSign: cmp #$00 ;if player not moving or moving to the right, - bpl SetAbsSpd ;branch and leave horizontal speed value unmodified - eor #$ff - clc ;otherwise get two's compliment to get absolute - adc #$01 ;unsigned walking/running speed -SetAbsSpd: sta Player_XSpeedAbsolute ;store walking/running speed here and leave - rts - -;------------------------------------------------------------------------------------- -;$00 - used to store downward movement force in FireballObjCore -;$02 - used to store maximum vertical speed in FireballObjCore -;$07 - used to store pseudorandom bit in BubbleCheck - -ProcFireball_Bubble: - lda PlayerStatus ;check player's status - cmp #$02 - bcc ProcAirBubbles ;if not fiery, branch - lda A_B_Buttons - and #B_Button ;check for b button pressed - beq ProcFireballs ;branch if not pressed - and PreviousA_B_Buttons - bne ProcFireballs ;if button pressed in previous frame, branch - lda FireballCounter ;load fireball counter - and #%00000001 ;get LSB and use as offset for buffer - tax - lda Fireball_State,x ;load fireball state - bne ProcFireballs ;if not inactive, branch - ldy Player_Y_HighPos ;if player too high or too low, branch - dey - bne ProcFireballs - lda CrouchingFlag ;if player crouching, branch - bne ProcFireballs - lda Player_State ;if player's state = climbing, branch - cmp #$03 - beq ProcFireballs - lda #Sfx_Fireball ;play fireball sound effect - sta Square1SoundQueue - lda #$02 ;load state - sta Fireball_State,x - ldy PlayerAnimTimerSet ;copy animation frame timer setting - sty FireballThrowingTimer ;into fireball throwing timer - dey - sty PlayerAnimTimer ;decrement and store in player's animation timer - inc FireballCounter ;increment fireball counter - -ProcFireballs: - ldx #$00 - jsr FireballObjCore ;process first fireball object - ldx #$01 - jsr FireballObjCore ;process second fireball object, then do air bubbles - -ProcAirBubbles: - lda AreaType ;if not water type level, skip the rest of this - bne BublExit - ldx #$02 ;otherwise load counter and use as offset -BublLoop: stx ObjectOffset ;store offset - jsr BubbleCheck ;check timers and coordinates, create air bubble - jsr RelativeBubblePosition ;get relative coordinates - jsr GetBubbleOffscreenBits ;get offscreen information - jsr DrawBubble ;draw the air bubble - dex - bpl BublLoop ;do this until all three are handled -BublExit: rts ;then leave - -FireballXSpdData: - .db $40, $c0 - -FireballObjCore: - stx ObjectOffset ;store offset as current object - lda Fireball_State,x ;check for d7 = 1 - asl - bcs FireballExplosion ;if so, branch to get relative coordinates and draw explosion - ldy Fireball_State,x ;if fireball inactive, branch to leave - beq NoFBall - dey ;if fireball state set to 1, skip this part and just run it - beq RunFB - lda Player_X_Position ;get player's horizontal position - adc #$04 ;add four pixels and store as fireball's horizontal position - sta Fireball_X_Position,x - lda Player_PageLoc ;get player's page location - adc #$00 ;add carry and store as fireball's page location - sta Fireball_PageLoc,x - lda Player_Y_Position ;get player's vertical position and store - sta Fireball_Y_Position,x - lda #$01 ;set high byte of vertical position - sta Fireball_Y_HighPos,x - ldy PlayerFacingDir ;get player's facing direction - dey ;decrement to use as offset here - lda FireballXSpdData,y ;set horizontal speed of fireball accordingly - sta Fireball_X_Speed,x - lda #$04 ;set vertical speed of fireball - sta Fireball_Y_Speed,x - lda #$07 - sta Fireball_BoundBoxCtrl,x ;set bounding box size control for fireball - dec Fireball_State,x ;decrement state to 1 to skip this part from now on -RunFB: txa ;add 7 to offset to use - clc ;as fireball offset for next routines - adc #$07 - tax - lda #$50 ;set downward movement force here - sta $00 - lda #$03 ;set maximum speed here - sta $02 - lda #$00 - jsr ImposeGravity ;do sub here to impose gravity on fireball and move vertically - jsr MoveObjectHorizontally ;do another sub to move it horizontally - ldx ObjectOffset ;return fireball offset to X - jsr RelativeFireballPosition ;get relative coordinates - jsr GetFireballOffscreenBits ;get offscreen information - jsr GetFireballBoundBox ;get bounding box coordinates - jsr FireballBGCollision ;do fireball to background collision detection - lda FBall_OffscreenBits ;get fireball offscreen bits - and #%11001100 ;mask out certain bits - bne EraseFB ;if any bits still set, branch to kill fireball - jsr FireballEnemyCollision ;do fireball to enemy collision detection and deal with collisions - jmp DrawFireball ;draw fireball appropriately and leave -EraseFB: lda #$00 ;erase fireball state - sta Fireball_State,x -NoFBall: rts ;leave - -FireballExplosion: - jsr RelativeFireballPosition - jmp DrawExplosion_Fireball - -BubbleCheck: - lda PseudoRandomBitReg+1,x ;get part of LSFR - and #$01 - sta $07 ;store pseudorandom bit here - lda Bubble_Y_Position,x ;get vertical coordinate for air bubble - cmp #$f8 ;if offscreen coordinate not set, - bne MoveBubl ;branch to move air bubble - lda AirBubbleTimer ;if air bubble timer not expired, - bne ExitBubl ;branch to leave, otherwise create new air bubble - -SetupBubble: - ldy #$00 ;load default value here - lda PlayerFacingDir ;get player's facing direction - lsr ;move d0 to carry - bcc PosBubl ;branch to use default value if facing left - ldy #$08 ;otherwise load alternate value here -PosBubl: tya ;use value loaded as adder - adc Player_X_Position ;add to player's horizontal position - sta Bubble_X_Position,x ;save as horizontal position for airbubble - lda Player_PageLoc - adc #$00 ;add carry to player's page location - sta Bubble_PageLoc,x ;save as page location for airbubble - lda Player_Y_Position - clc ;add eight pixels to player's vertical position - adc #$08 - sta Bubble_Y_Position,x ;save as vertical position for air bubble - lda #$01 - sta Bubble_Y_HighPos,x ;set vertical high byte for air bubble - ldy $07 ;get pseudorandom bit, use as offset - lda BubbleTimerData,y ;get data for air bubble timer - sta AirBubbleTimer ;set air bubble timer -MoveBubl: ldy $07 ;get pseudorandom bit again, use as offset - lda Bubble_YMF_Dummy,x - sec ;subtract pseudorandom amount from dummy variable - sbc Bubble_MForceData,y - sta Bubble_YMF_Dummy,x ;save dummy variable - lda Bubble_Y_Position,x - sbc #$00 ;subtract borrow from airbubble's vertical coordinate - cmp #$20 ;if below the status bar, - bcs Y_Bubl ;branch to go ahead and use to move air bubble upwards - lda #$f8 ;otherwise set offscreen coordinate -Y_Bubl: sta Bubble_Y_Position,x ;store as new vertical coordinate for air bubble -ExitBubl: rts ;leave - -Bubble_MForceData: - .db $ff, $50 - -BubbleTimerData: - .db $40, $20 - -;------------------------------------------------------------------------------------- - -RunGameTimer: - lda OperMode ;get primary mode of operation - beq ExGTimer ;branch to leave if in title screen mode - lda GameEngineSubroutine - cmp #$08 ;if routine number less than eight running, - bcc ExGTimer ;branch to leave - cmp #$0b ;if running death routine, - beq ExGTimer ;branch to leave - lda Player_Y_HighPos - cmp #$02 ;if player below the screen, - bcs ExGTimer ;branch to leave regardless of level type - lda GameTimerCtrlTimer ;if game timer control not yet expired, - bne ExGTimer ;branch to leave - lda GameTimerDisplay - ora GameTimerDisplay+1 ;otherwise check game timer digits - ora GameTimerDisplay+2 - beq TimeUpOn ;if game timer digits at 000, branch to time-up code - ldy GameTimerDisplay ;otherwise check first digit - dey ;if first digit not on 1, - bne ResGTCtrl ;branch to reset game timer control - lda GameTimerDisplay+1 ;otherwise check second and third digits - ora GameTimerDisplay+2 - bne ResGTCtrl ;if timer not at 100, branch to reset game timer control - lda #TimeRunningOutMusic - sta EventMusicQueue ;otherwise load time running out music -ResGTCtrl: lda #$18 ;reset game timer control - sta GameTimerCtrlTimer - ldy #$23 ;set offset for last digit - lda #$ff ;set value to decrement game timer digit - sta DigitModifier+5 - jsr DigitsMathRoutine ;do sub to decrement game timer slowly - lda #$a4 ;set status nybbles to update game timer display - jmp PrintStatusBarNumbers ;do sub to update the display -TimeUpOn: sta PlayerStatus ;init player status (note A will always be zero here) - jsr ForceInjury ;do sub to kill the player (note player is small here) - inc GameTimerExpiredFlag ;set game timer expiration flag -ExGTimer: rts ;leave - -;------------------------------------------------------------------------------------- - -WarpZoneObject: - lda ScrollLock ;check for scroll lock flag - beq ExGTimer ;branch if not set to leave - lda Player_Y_Position ;check to see if player's vertical coordinate has - and Player_Y_HighPos ;same bits set as in vertical high byte (why?) - bne ExGTimer ;if so, branch to leave - sta ScrollLock ;otherwise nullify scroll lock flag - inc WarpZoneControl ;increment warp zone flag to make warp pipes for warp zone - jmp EraseEnemyObject ;kill this object - -;------------------------------------------------------------------------------------- -;$00 - used in WhirlpoolActivate to store whirlpool length / 2, page location of center of whirlpool -;and also to store movement force exerted on player -;$01 - used in ProcessWhirlpools to store page location of right extent of whirlpool -;and in WhirlpoolActivate to store center of whirlpool -;$02 - used in ProcessWhirlpools to store right extent of whirlpool and in -;WhirlpoolActivate to store maximum vertical speed - -ProcessWhirlpools: - lda AreaType ;check for water type level - bne ExitWh ;branch to leave if not found - sta Whirlpool_Flag ;otherwise initialize whirlpool flag - lda TimerControl ;if master timer control set, - bne ExitWh ;branch to leave - ldy #$04 ;otherwise start with last whirlpool data -WhLoop: lda Whirlpool_LeftExtent,y ;get left extent of whirlpool - clc - adc Whirlpool_Length,y ;add length of whirlpool - sta $02 ;store result as right extent here - lda Whirlpool_PageLoc,y ;get page location - beq NextWh ;if none or page 0, branch to get next data - adc #$00 ;add carry - sta $01 ;store result as page location of right extent here - lda Player_X_Position ;get player's horizontal position - sec - sbc Whirlpool_LeftExtent,y ;subtract left extent - lda Player_PageLoc ;get player's page location - sbc Whirlpool_PageLoc,y ;subtract borrow - bmi NextWh ;if player too far left, branch to get next data - lda $02 ;otherwise get right extent - sec - sbc Player_X_Position ;subtract player's horizontal coordinate - lda $01 ;get right extent's page location - sbc Player_PageLoc ;subtract borrow - bpl WhirlpoolActivate ;if player within right extent, branch to whirlpool code -NextWh: dey ;move onto next whirlpool data - bpl WhLoop ;do this until all whirlpools are checked -ExitWh: rts ;leave - -WhirlpoolActivate: - lda Whirlpool_Length,y ;get length of whirlpool - lsr ;divide by 2 - sta $00 ;save here - lda Whirlpool_LeftExtent,y ;get left extent of whirlpool - clc - adc $00 ;add length divided by 2 - sta $01 ;save as center of whirlpool - lda Whirlpool_PageLoc,y ;get page location - adc #$00 ;add carry - sta $00 ;save as page location of whirlpool center - lda FrameCounter ;get frame counter - lsr ;shift d0 into carry (to run on every other frame) - bcc WhPull ;if d0 not set, branch to last part of code - lda $01 ;get center - sec - sbc Player_X_Position ;subtract player's horizontal coordinate - lda $00 ;get page location of center - sbc Player_PageLoc ;subtract borrow - bpl LeftWh ;if player to the left of center, branch - lda Player_X_Position ;otherwise slowly pull player left, towards the center - sec - sbc #$01 ;subtract one pixel - sta Player_X_Position ;set player's new horizontal coordinate - lda Player_PageLoc - sbc #$00 ;subtract borrow - jmp SetPWh ;jump to set player's new page location -LeftWh: lda Player_CollisionBits ;get player's collision bits - lsr ;shift d0 into carry - bcc WhPull ;if d0 not set, branch - lda Player_X_Position ;otherwise slowly pull player right, towards the center - clc - adc #$01 ;add one pixel - sta Player_X_Position ;set player's new horizontal coordinate - lda Player_PageLoc - adc #$00 ;add carry -SetPWh: sta Player_PageLoc ;set player's new page location -WhPull: lda #$10 - sta $00 ;set vertical movement force - lda #$01 - sta Whirlpool_Flag ;set whirlpool flag to be used later - sta $02 ;also set maximum vertical speed - lsr - tax ;set X for player offset - jmp ImposeGravity ;jump to put whirlpool effect on player vertically, do not return - -;------------------------------------------------------------------------------------- - -FlagpoleScoreMods: - .db $05, $02, $08, $04, $01 - -FlagpoleScoreDigits: - .db $03, $03, $04, $04, $04 - -FlagpoleRoutine: - ldx #$05 ;set enemy object offset - stx ObjectOffset ;to special use slot - lda Enemy_ID,x - cmp #FlagpoleFlagObject ;if flagpole flag not found, - bne ExitFlagP ;branch to leave - lda GameEngineSubroutine - cmp #$04 ;if flagpole slide routine not running, - bne SkipScore ;branch to near the end of code - lda Player_State - cmp #$03 ;if player state not climbing, - bne SkipScore ;branch to near the end of code - lda Enemy_Y_Position,x ;check flagpole flag's vertical coordinate - cmp #$aa ;if flagpole flag down to a certain point, - bcs GiveFPScr ;branch to end the level - lda Player_Y_Position ;check player's vertical coordinate - cmp #$a2 ;if player down to a certain point, - bcs GiveFPScr ;branch to end the level - lda Enemy_YMF_Dummy,x - adc #$ff ;add movement amount to dummy variable - sta Enemy_YMF_Dummy,x ;save dummy variable - lda Enemy_Y_Position,x ;get flag's vertical coordinate - adc #$01 ;add 1 plus carry to move flag, and - sta Enemy_Y_Position,x ;store vertical coordinate - lda FlagpoleFNum_YMFDummy - sec ;subtract movement amount from dummy variable - sbc #$ff - sta FlagpoleFNum_YMFDummy ;save dummy variable - lda FlagpoleFNum_Y_Pos - sbc #$01 ;subtract one plus borrow to move floatey number, - sta FlagpoleFNum_Y_Pos ;and store vertical coordinate here -SkipScore: jmp FPGfx ;jump to skip ahead and draw flag and floatey number -GiveFPScr: ldy FlagpoleScore ;get score offset from earlier (when player touched flagpole) - lda FlagpoleScoreMods,y ;get amount to award player points - ldx FlagpoleScoreDigits,y ;get digit with which to award points - sta DigitModifier,x ;store in digit modifier - jsr AddToScore ;do sub to award player points depending on height of collision - lda #$05 - sta GameEngineSubroutine ;set to run end-of-level subroutine on next frame -FPGfx: jsr GetEnemyOffscreenBits ;get offscreen information - jsr RelativeEnemyPosition ;get relative coordinates - jsr FlagpoleGfxHandler ;draw flagpole flag and floatey number -ExitFlagP: rts - -;------------------------------------------------------------------------------------- - -Jumpspring_Y_PosData: - .db $08, $10, $08, $00 - -JumpspringHandler: - jsr GetEnemyOffscreenBits ;get offscreen information - lda TimerControl ;check master timer control - bne DrawJSpr ;branch to last section if set - lda JumpspringAnimCtrl ;check jumpspring frame control - beq DrawJSpr ;branch to last section if not set - tay - dey ;subtract one from frame control, - tya ;the only way a poor nmos 6502 can - and #%00000010 ;mask out all but d1, original value still in Y - bne DownJSpr ;if set, branch to move player up - inc Player_Y_Position - inc Player_Y_Position ;move player's vertical position down two pixels - jmp PosJSpr ;skip to next part -DownJSpr: dec Player_Y_Position ;move player's vertical position up two pixels - dec Player_Y_Position -PosJSpr: lda Jumpspring_FixedYPos,x ;get permanent vertical position - clc - adc Jumpspring_Y_PosData,y ;add value using frame control as offset - sta Enemy_Y_Position,x ;store as new vertical position - cpy #$01 ;check frame control offset (second frame is $00) - bcc BounceJS ;if offset not yet at third frame ($01), skip to next part - lda A_B_Buttons - and #A_Button ;check saved controller bits for A button press - beq BounceJS ;skip to next part if A not pressed - and PreviousA_B_Buttons ;check for A button pressed in previous frame - bne BounceJS ;skip to next part if so - lda #$f4 - sta JumpspringForce ;otherwise write new jumpspring force here -BounceJS: cpy #$03 ;check frame control offset again - bne DrawJSpr ;skip to last part if not yet at fifth frame ($03) - lda JumpspringForce - sta Player_Y_Speed ;store jumpspring force as player's new vertical speed - lda #$00 - sta JumpspringAnimCtrl ;initialize jumpspring frame control -DrawJSpr: jsr RelativeEnemyPosition ;get jumpspring's relative coordinates - jsr EnemyGfxHandler ;draw jumpspring - jsr OffscreenBoundsCheck ;check to see if we need to kill it - lda JumpspringAnimCtrl ;if frame control at zero, don't bother - beq ExJSpring ;trying to animate it, just leave - lda JumpspringTimer - bne ExJSpring ;if jumpspring timer not expired yet, leave - lda #$04 - sta JumpspringTimer ;otherwise initialize jumpspring timer - inc JumpspringAnimCtrl ;increment frame control to animate jumpspring -ExJSpring: rts ;leave - -;------------------------------------------------------------------------------------- - -Setup_Vine: - lda #VineObject ;load identifier for vine object - sta Enemy_ID,x ;store in buffer - lda #$01 - sta Enemy_Flag,x ;set flag for enemy object buffer - lda Block_PageLoc,y - sta Enemy_PageLoc,x ;copy page location from previous object - lda Block_X_Position,y - sta Enemy_X_Position,x ;copy horizontal coordinate from previous object - lda Block_Y_Position,y - sta Enemy_Y_Position,x ;copy vertical coordinate from previous object - ldy VineFlagOffset ;load vine flag/offset to next available vine slot - bne NextVO ;if set at all, don't bother to store vertical - sta VineStart_Y_Position ;otherwise store vertical coordinate here -NextVO: txa ;store object offset to next available vine slot - sta VineObjOffset,y ;using vine flag as offset - inc VineFlagOffset ;increment vine flag offset - lda #Sfx_GrowVine - sta Square2SoundQueue ;load vine grow sound - rts - -;------------------------------------------------------------------------------------- -;$06-$07 - used as address to block buffer data -;$02 - used as vertical high nybble of block buffer offset - -VineHeightData: - .db $30, $60 - -VineObjectHandler: - cpx #$05 ;check enemy offset for special use slot - bne ExitVH ;if not in last slot, branch to leave - ldy VineFlagOffset - dey ;decrement vine flag in Y, use as offset - lda VineHeight - cmp VineHeightData,y ;if vine has reached certain height, - beq RunVSubs ;branch ahead to skip this part - lda FrameCounter ;get frame counter - lsr ;shift d1 into carry - lsr - bcc RunVSubs ;if d1 not set (2 frames every 4) skip this part - lda Enemy_Y_Position+5 - sbc #$01 ;subtract vertical position of vine - sta Enemy_Y_Position+5 ;one pixel every frame it's time - inc VineHeight ;increment vine height -RunVSubs: lda VineHeight ;if vine still very small, - cmp #$08 ;branch to leave - bcc ExitVH - jsr RelativeEnemyPosition ;get relative coordinates of vine, - jsr GetEnemyOffscreenBits ;and any offscreen bits - ldy #$00 ;initialize offset used in draw vine sub -VDrawLoop: jsr DrawVine ;draw vine - iny ;increment offset - cpy VineFlagOffset ;if offset in Y and offset here - bne VDrawLoop ;do not yet match, loop back to draw more vine - lda Enemy_OffscreenBits - and #%00001100 ;mask offscreen bits - beq WrCMTile ;if none of the saved offscreen bits set, skip ahead - dey ;otherwise decrement Y to get proper offset again -KillVine: ldx VineObjOffset,y ;get enemy object offset for this vine object - jsr EraseEnemyObject ;kill this vine object - dey ;decrement Y - bpl KillVine ;if any vine objects left, loop back to kill it - sta VineFlagOffset ;initialize vine flag/offset - sta VineHeight ;initialize vine height -WrCMTile: lda VineHeight ;check vine height - cmp #$20 ;if vine small (less than 32 pixels tall) - bcc ExitVH ;then branch ahead to leave - ldx #$06 ;set offset in X to last enemy slot - lda #$01 ;set A to obtain horizontal in $04, but we don't care - ldy #$1b ;set Y to offset to get block at ($04, $10) of coordinates - jsr BlockBufferCollision ;do a sub to get block buffer address set, return contents - ldy $02 - cpy #$d0 ;if vertical high nybble offset beyond extent of - bcs ExitVH ;current block buffer, branch to leave, do not write - lda ($06),y ;otherwise check contents of block buffer at - bne ExitVH ;current offset, if not empty, branch to leave - lda #$26 - sta ($06),y ;otherwise, write climbing metatile to block buffer -ExitVH: ldx ObjectOffset ;get enemy object offset and leave - rts - -;------------------------------------------------------------------------------------- - -CannonBitmasks: - .db %00001111, %00000111 - -ProcessCannons: - lda AreaType ;get area type - beq ExCannon ;if water type area, branch to leave - ldx #$02 -ThreeSChk: stx ObjectOffset ;start at third enemy slot - lda Enemy_Flag,x ;check enemy buffer flag - bne Chk_BB ;if set, branch to check enemy - lda PseudoRandomBitReg+1,x ;otherwise get part of LSFR - ldy SecondaryHardMode ;get secondary hard mode flag, use as offset - and CannonBitmasks,y ;mask out bits of LSFR as decided by flag - cmp #$06 ;check to see if lower nybble is above certain value - bcs Chk_BB ;if so, branch to check enemy - tay ;transfer masked contents of LSFR to Y as pseudorandom offset - lda Cannon_PageLoc,y ;get page location - beq Chk_BB ;if not set or on page 0, branch to check enemy - lda Cannon_Timer,y ;get cannon timer - beq FireCannon ;if expired, branch to fire cannon - sbc #$00 ;otherwise subtract borrow (note carry will always be clear here) - sta Cannon_Timer,y ;to count timer down - jmp Chk_BB ;then jump ahead to check enemy - -FireCannon: - lda TimerControl ;if master timer control set, - bne Chk_BB ;branch to check enemy - lda #$0e ;otherwise we start creating one - sta Cannon_Timer,y ;first, reset cannon timer - lda Cannon_PageLoc,y ;get page location of cannon - sta Enemy_PageLoc,x ;save as page location of bullet bill - lda Cannon_X_Position,y ;get horizontal coordinate of cannon - sta Enemy_X_Position,x ;save as horizontal coordinate of bullet bill - lda Cannon_Y_Position,y ;get vertical coordinate of cannon - sec - sbc #$08 ;subtract eight pixels (because enemies are 24 pixels tall) - sta Enemy_Y_Position,x ;save as vertical coordinate of bullet bill - lda #$01 - sta Enemy_Y_HighPos,x ;set vertical high byte of bullet bill - sta Enemy_Flag,x ;set buffer flag - lsr ;shift right once to init A - sta Enemy_State,x ;then initialize enemy's state - lda #$09 - sta Enemy_BoundBoxCtrl,x ;set bounding box size control for bullet bill - lda #BulletBill_CannonVar - sta Enemy_ID,x ;load identifier for bullet bill (cannon variant) - jmp Next3Slt ;move onto next slot -Chk_BB: lda Enemy_ID,x ;check enemy identifier for bullet bill (cannon variant) - cmp #BulletBill_CannonVar - bne Next3Slt ;if not found, branch to get next slot - jsr OffscreenBoundsCheck ;otherwise, check to see if it went offscreen - lda Enemy_Flag,x ;check enemy buffer flag - beq Next3Slt ;if not set, branch to get next slot - jsr GetEnemyOffscreenBits ;otherwise, get offscreen information - jsr BulletBillHandler ;then do sub to handle bullet bill -Next3Slt: dex ;move onto next slot - bpl ThreeSChk ;do this until first three slots are checked -ExCannon: rts ;then leave - -;-------------------------------- - -BulletBillXSpdData: - .db $18, $e8 - -BulletBillHandler: - lda TimerControl ;if master timer control set, - bne RunBBSubs ;branch to run subroutines except movement sub - lda Enemy_State,x - bne ChkDSte ;if bullet bill's state set, branch to check defeated state - lda Enemy_OffscreenBits ;otherwise load offscreen bits - and #%00001100 ;mask out bits - cmp #%00001100 ;check to see if all bits are set - beq KillBB ;if so, branch to kill this object - ldy #$01 ;set to move right by default - jsr PlayerEnemyDiff ;get horizontal difference between player and bullet bill - bmi SetupBB ;if enemy to the left of player, branch - iny ;otherwise increment to move left -SetupBB: sty Enemy_MovingDir,x ;set bullet bill's moving direction - dey ;decrement to use as offset - lda BulletBillXSpdData,y ;get horizontal speed based on moving direction - sta Enemy_X_Speed,x ;and store it - lda $00 ;get horizontal difference - adc #$28 ;add 40 pixels - cmp #$50 ;if less than a certain amount, player is too close - bcc KillBB ;to cannon either on left or right side, thus branch - lda #$01 - sta Enemy_State,x ;otherwise set bullet bill's state - lda #$0a - sta EnemyFrameTimer,x ;set enemy frame timer - lda #Sfx_Blast - sta Square2SoundQueue ;play fireworks/gunfire sound -ChkDSte: lda Enemy_State,x ;check enemy state for d5 set - and #%00100000 - beq BBFly ;if not set, skip to move horizontally - jsr MoveD_EnemyVertically ;otherwise do sub to move bullet bill vertically -BBFly: jsr MoveEnemyHorizontally ;do sub to move bullet bill horizontally -RunBBSubs: jsr GetEnemyOffscreenBits ;get offscreen information - jsr RelativeEnemyPosition ;get relative coordinates - jsr GetEnemyBoundBox ;get bounding box coordinates - jsr PlayerEnemyCollision ;handle player to enemy collisions - jmp EnemyGfxHandler ;draw the bullet bill and leave -KillBB: jsr EraseEnemyObject ;kill bullet bill and leave - rts - -;------------------------------------------------------------------------------------- - -HammerEnemyOfsData: - .db $04, $04, $04, $05, $05, $05 - .db $06, $06, $06 - -HammerXSpdData: - .db $10, $f0 - -SpawnHammerObj: - lda PseudoRandomBitReg+1 ;get pseudorandom bits from - and #%00000111 ;second part of LSFR - bne SetMOfs ;if any bits are set, branch and use as offset - lda PseudoRandomBitReg+1 - and #%00001000 ;get d3 from same part of LSFR -SetMOfs: tay ;use either d3 or d2-d0 for offset here - lda Misc_State,y ;if any values loaded in - bne NoHammer ;$2a-$32 where offset is then leave with carry clear - ldx HammerEnemyOfsData,y ;get offset of enemy slot to check using Y as offset - lda Enemy_Flag,x ;check enemy buffer flag at offset - bne NoHammer ;if buffer flag set, branch to leave with carry clear - ldx ObjectOffset ;get original enemy object offset - txa - sta HammerEnemyOffset,y ;save here - lda #$90 - sta Misc_State,y ;save hammer's state here - lda #$07 - sta Misc_BoundBoxCtrl,y ;set something else entirely, here - sec ;return with carry set - rts -NoHammer: ldx ObjectOffset ;get original enemy object offset - clc ;return with carry clear - rts - -;-------------------------------- -;$00 - used to set downward force -;$01 - used to set upward force (residual) -;$02 - used to set maximum speed - -ProcHammerObj: - lda TimerControl ;if master timer control set - bne RunHSubs ;skip all of this code and go to last subs at the end - lda Misc_State,x ;otherwise get hammer's state - and #%01111111 ;mask out d7 - ldy HammerEnemyOffset,x ;get enemy object offset that spawned this hammer - cmp #$02 ;check hammer's state - beq SetHSpd ;if currently at 2, branch - bcs SetHPos ;if greater than 2, branch elsewhere - txa - clc ;add 13 bytes to use - adc #$0d ;proper misc object - tax ;return offset to X - lda #$10 - sta $00 ;set downward movement force - lda #$0f - sta $01 ;set upward movement force (not used) - lda #$04 - sta $02 ;set maximum vertical speed - lda #$00 ;set A to impose gravity on hammer - jsr ImposeGravity ;do sub to impose gravity on hammer and move vertically - jsr MoveObjectHorizontally ;do sub to move it horizontally - ldx ObjectOffset ;get original misc object offset - jmp RunAllH ;branch to essential subroutines -SetHSpd: lda #$fe - sta Misc_Y_Speed,x ;set hammer's vertical speed - lda Enemy_State,y ;get enemy object state - and #%11110111 ;mask out d3 - sta Enemy_State,y ;store new state - ldx Enemy_MovingDir,y ;get enemy's moving direction - dex ;decrement to use as offset - lda HammerXSpdData,x ;get proper speed to use based on moving direction - ldx ObjectOffset ;reobtain hammer's buffer offset - sta Misc_X_Speed,x ;set hammer's horizontal speed -SetHPos: dec Misc_State,x ;decrement hammer's state - lda Enemy_X_Position,y ;get enemy's horizontal position - clc - adc #$02 ;set position 2 pixels to the right - sta Misc_X_Position,x ;store as hammer's horizontal position - lda Enemy_PageLoc,y ;get enemy's page location - adc #$00 ;add carry - sta Misc_PageLoc,x ;store as hammer's page location - lda Enemy_Y_Position,y ;get enemy's vertical position - sec - sbc #$0a ;move position 10 pixels upward - sta Misc_Y_Position,x ;store as hammer's vertical position - lda #$01 - sta Misc_Y_HighPos,x ;set hammer's vertical high byte - bne RunHSubs ;unconditional branch to skip first routine -RunAllH: jsr PlayerHammerCollision ;handle collisions -RunHSubs: jsr GetMiscOffscreenBits ;get offscreen information - jsr RelativeMiscPosition ;get relative coordinates - jsr GetMiscBoundBox ;get bounding box coordinates - jsr DrawHammer ;draw the hammer - rts ;and we are done here - -;------------------------------------------------------------------------------------- -;$02 - used to store vertical high nybble offset from block buffer routine -;$06 - used to store low byte of block buffer address - -CoinBlock: - jsr FindEmptyMiscSlot ;set offset for empty or last misc object buffer slot - lda Block_PageLoc,x ;get page location of block object - sta Misc_PageLoc,y ;store as page location of misc object - lda Block_X_Position,x ;get horizontal coordinate of block object - ora #$05 ;add 5 pixels - sta Misc_X_Position,y ;store as horizontal coordinate of misc object - lda Block_Y_Position,x ;get vertical coordinate of block object - sbc #$10 ;subtract 16 pixels - sta Misc_Y_Position,y ;store as vertical coordinate of misc object - jmp JCoinC ;jump to rest of code as applies to this misc object - -SetupJumpCoin: - jsr FindEmptyMiscSlot ;set offset for empty or last misc object buffer slot - lda Block_PageLoc2,x ;get page location saved earlier - sta Misc_PageLoc,y ;and save as page location for misc object - lda $06 ;get low byte of block buffer offset - asl - asl ;multiply by 16 to use lower nybble - asl - asl - ora #$05 ;add five pixels - sta Misc_X_Position,y ;save as horizontal coordinate for misc object - lda $02 ;get vertical high nybble offset from earlier - adc #$20 ;add 32 pixels for the status bar - sta Misc_Y_Position,y ;store as vertical coordinate -JCoinC: lda #$fb - sta Misc_Y_Speed,y ;set vertical speed - lda #$01 - sta Misc_Y_HighPos,y ;set vertical high byte - sta Misc_State,y ;set state for misc object - sta Square2SoundQueue ;load coin grab sound - stx ObjectOffset ;store current control bit as misc object offset - jsr GiveOneCoin ;update coin tally on the screen and coin amount variable - inc CoinTallyFor1Ups ;increment coin tally used to activate 1-up block flag - rts - -FindEmptyMiscSlot: - ldy #$08 ;start at end of misc objects buffer -FMiscLoop: lda Misc_State,y ;get misc object state - beq UseMiscS ;branch if none found to use current offset - dey ;decrement offset - cpy #$05 ;do this for three slots - bne FMiscLoop ;do this until all slots are checked - ldy #$08 ;if no empty slots found, use last slot -UseMiscS: sty JumpCoinMiscOffset ;store offset of misc object buffer here (residual) - rts - -;------------------------------------------------------------------------------------- - -MiscObjectsCore: - ldx #$08 ;set at end of misc object buffer -MiscLoop: stx ObjectOffset ;store misc object offset here - lda Misc_State,x ;check misc object state - beq MiscLoopBack ;branch to check next slot - asl ;otherwise shift d7 into carry - bcc ProcJumpCoin ;if d7 not set, jumping coin, thus skip to rest of code here - jsr ProcHammerObj ;otherwise go to process hammer, - jmp MiscLoopBack ;then check next slot - -;-------------------------------- -;$00 - used to set downward force -;$01 - used to set upward force (residual) -;$02 - used to set maximum speed - -ProcJumpCoin: - ldy Misc_State,x ;check misc object state - dey ;decrement to see if it's set to 1 - beq JCoinRun ;if so, branch to handle jumping coin - inc Misc_State,x ;otherwise increment state to either start off or as timer - lda Misc_X_Position,x ;get horizontal coordinate for misc object - clc ;whether its jumping coin (state 0 only) or floatey number - adc ScrollAmount ;add current scroll speed - sta Misc_X_Position,x ;store as new horizontal coordinate - lda Misc_PageLoc,x ;get page location - adc #$00 ;add carry - sta Misc_PageLoc,x ;store as new page location - lda Misc_State,x - cmp #$30 ;check state of object for preset value - bne RunJCSubs ;if not yet reached, branch to subroutines - lda #$00 - sta Misc_State,x ;otherwise nullify object state - jmp MiscLoopBack ;and move onto next slot -JCoinRun: txa - clc ;add 13 bytes to offset for next subroutine - adc #$0d - tax - lda #$50 ;set downward movement amount - sta $00 - lda #$06 ;set maximum vertical speed - sta $02 - lsr ;divide by 2 and set - sta $01 ;as upward movement amount (apparently residual) - lda #$00 ;set A to impose gravity on jumping coin - jsr ImposeGravity ;do sub to move coin vertically and impose gravity on it - ldx ObjectOffset ;get original misc object offset - lda Misc_Y_Speed,x ;check vertical speed - cmp #$05 - bne RunJCSubs ;if not moving downward fast enough, keep state as-is - inc Misc_State,x ;otherwise increment state to change to floatey number -RunJCSubs: jsr RelativeMiscPosition ;get relative coordinates - jsr GetMiscOffscreenBits ;get offscreen information - jsr GetMiscBoundBox ;get bounding box coordinates (why?) - jsr JCoinGfxHandler ;draw the coin or floatey number - -MiscLoopBack: - dex ;decrement misc object offset - bpl MiscLoop ;loop back until all misc objects handled - rts ;then leave - -;------------------------------------------------------------------------------------- - -CoinTallyOffsets: - .db $17, $1d - -ScoreOffsets: - .db $0b, $11 - -StatusBarNybbles: - .db $02, $13 - -GiveOneCoin: - lda #$01 ;set digit modifier to add 1 coin - sta DigitModifier+5 ;to the current player's coin tally - ldx CurrentPlayer ;get current player on the screen - ldy CoinTallyOffsets,x ;get offset for player's coin tally - jsr DigitsMathRoutine ;update the coin tally - inc CoinTally ;increment onscreen player's coin amount - lda CoinTally - cmp #100 ;does player have 100 coins yet? - bne CoinPoints ;if not, skip all of this - lda #$00 - sta CoinTally ;otherwise, reinitialize coin amount - inc NumberofLives ;give the player an extra life - lda #Sfx_ExtraLife - sta Square2SoundQueue ;play 1-up sound - -CoinPoints: - lda #$02 ;set digit modifier to award - sta DigitModifier+4 ;200 points to the player - -AddToScore: - ldx CurrentPlayer ;get current player - ldy ScoreOffsets,x ;get offset for player's score - jsr DigitsMathRoutine ;update the score internally with value in digit modifier - -GetSBNybbles: - ldy CurrentPlayer ;get current player - lda StatusBarNybbles,y ;get nybbles based on player, use to update score and coins - -UpdateNumber: - jsr PrintStatusBarNumbers ;print status bar numbers based on nybbles, whatever they be - ldy VRAM_Buffer1_Offset - lda VRAM_Buffer1-6,y ;check highest digit of score - bne NoZSup ;if zero, overwrite with space tile for zero suppression - lda #$24 - sta VRAM_Buffer1-6,y -NoZSup: ldx ObjectOffset ;get enemy object buffer offset - rts - -;------------------------------------------------------------------------------------- - -SetupPowerUp: - lda #PowerUpObject ;load power-up identifier into - sta Enemy_ID+5 ;special use slot of enemy object buffer - lda Block_PageLoc,x ;store page location of block object - sta Enemy_PageLoc+5 ;as page location of power-up object - lda Block_X_Position,x ;store horizontal coordinate of block object - sta Enemy_X_Position+5 ;as horizontal coordinate of power-up object - lda #$01 - sta Enemy_Y_HighPos+5 ;set vertical high byte of power-up object - lda Block_Y_Position,x ;get vertical coordinate of block object - sec - sbc #$08 ;subtract 8 pixels - sta Enemy_Y_Position+5 ;and use as vertical coordinate of power-up object -PwrUpJmp: lda #$01 ;this is a residual jump point in enemy object jump table - sta Enemy_State+5 ;set power-up object's state - sta Enemy_Flag+5 ;set buffer flag - lda #$03 - sta Enemy_BoundBoxCtrl+5 ;set bounding box size control for power-up object - lda PowerUpType - cmp #$02 ;check currently loaded power-up type - bcs PutBehind ;if star or 1-up, branch ahead - lda PlayerStatus ;otherwise check player's current status - cmp #$02 - bcc StrType ;if player not fiery, use status as power-up type - lsr ;otherwise shift right to force fire flower type -StrType: sta PowerUpType ;store type here -PutBehind: lda #%00100000 - sta Enemy_SprAttrib+5 ;set background priority bit - lda #Sfx_GrowPowerUp - sta Square2SoundQueue ;load power-up reveal sound and leave - rts - -;------------------------------------------------------------------------------------- - -PowerUpObjHandler: - ldx #$05 ;set object offset for last slot in enemy object buffer - stx ObjectOffset - lda Enemy_State+5 ;check power-up object's state - beq ExitPUp ;if not set, branch to leave - asl ;shift to check if d7 was set in object state - bcc GrowThePowerUp ;if not set, branch ahead to skip this part - lda TimerControl ;if master timer control set, - bne RunPUSubs ;branch ahead to enemy object routines - lda PowerUpType ;check power-up type - beq ShroomM ;if normal mushroom, branch ahead to move it - cmp #$03 - beq ShroomM ;if 1-up mushroom, branch ahead to move it - cmp #$02 - bne RunPUSubs ;if not star, branch elsewhere to skip movement - jsr MoveJumpingEnemy ;otherwise impose gravity on star power-up and make it jump - jsr EnemyJump ;note that green paratroopa shares the same code here - jmp RunPUSubs ;then jump to other power-up subroutines -ShroomM: jsr MoveNormalEnemy ;do sub to make mushrooms move - jsr EnemyToBGCollisionDet ;deal with collisions - jmp RunPUSubs ;run the other subroutines - -GrowThePowerUp: - lda FrameCounter ;get frame counter - and #$03 ;mask out all but 2 LSB - bne ChkPUSte ;if any bits set here, branch - dec Enemy_Y_Position+5 ;otherwise decrement vertical coordinate slowly - lda Enemy_State+5 ;load power-up object state - inc Enemy_State+5 ;increment state for next frame (to make power-up rise) - cmp #$11 ;if power-up object state not yet past 16th pixel, - bcc ChkPUSte ;branch ahead to last part here - lda #$10 - sta Enemy_X_Speed,x ;otherwise set horizontal speed - lda #%10000000 - sta Enemy_State+5 ;and then set d7 in power-up object's state - asl ;shift once to init A - sta Enemy_SprAttrib+5 ;initialize background priority bit set here - rol ;rotate A to set right moving direction - sta Enemy_MovingDir,x ;set moving direction -ChkPUSte: lda Enemy_State+5 ;check power-up object's state - cmp #$06 ;for if power-up has risen enough - bcc ExitPUp ;if not, don't even bother running these routines -RunPUSubs: jsr RelativeEnemyPosition ;get coordinates relative to screen - jsr GetEnemyOffscreenBits ;get offscreen bits - jsr GetEnemyBoundBox ;get bounding box coordinates - jsr DrawPowerUp ;draw the power-up object - jsr PlayerEnemyCollision ;check for collision with player - jsr OffscreenBoundsCheck ;check to see if it went offscreen -ExitPUp: rts ;and we're done - -;------------------------------------------------------------------------------------- -;These apply to all routines in this section unless otherwise noted: -;$00 - used to store metatile from block buffer routine -;$02 - used to store vertical high nybble offset from block buffer routine -;$05 - used to store metatile stored in A at beginning of PlayerHeadCollision -;$06-$07 - used as block buffer address indirect - -BlockYPosAdderData: - .db $04, $12 - -PlayerHeadCollision: - pha ;store metatile number to stack - lda #$11 ;load unbreakable block object state by default - ldx SprDataOffset_Ctrl ;load offset control bit here - ldy PlayerSize ;check player's size - bne DBlockSte ;if small, branch - lda #$12 ;otherwise load breakable block object state -DBlockSte: sta Block_State,x ;store into block object buffer - jsr DestroyBlockMetatile ;store blank metatile in vram buffer to write to name table - ldx SprDataOffset_Ctrl ;load offset control bit - lda $02 ;get vertical high nybble offset used in block buffer routine - sta Block_Orig_YPos,x ;set as vertical coordinate for block object - tay - lda $06 ;get low byte of block buffer address used in same routine - sta Block_BBuf_Low,x ;save as offset here to be used later - lda ($06),y ;get contents of block buffer at old address at $06, $07 - jsr BlockBumpedChk ;do a sub to check which block player bumped head on - sta $00 ;store metatile here - ldy PlayerSize ;check player's size - bne ChkBrick ;if small, use metatile itself as contents of A - tya ;otherwise init A (note: big = 0) -ChkBrick: bcc PutMTileB ;if no match was found in previous sub, skip ahead - ldy #$11 ;otherwise load unbreakable state into block object buffer - sty Block_State,x ;note this applies to both player sizes - lda #$c4 ;load empty block metatile into A for now - ldy $00 ;get metatile from before - cpy #$58 ;is it brick with coins (with line)? - beq StartBTmr ;if so, branch - cpy #$5d ;is it brick with coins (without line)? - bne PutMTileB ;if not, branch ahead to store empty block metatile -StartBTmr: lda BrickCoinTimerFlag ;check brick coin timer flag - bne ContBTmr ;if set, timer expired or counting down, thus branch - lda #$0b - sta BrickCoinTimer ;if not set, set brick coin timer - inc BrickCoinTimerFlag ;and set flag linked to it -ContBTmr: lda BrickCoinTimer ;check brick coin timer - bne PutOldMT ;if not yet expired, branch to use current metatile - ldy #$c4 ;otherwise use empty block metatile -PutOldMT: tya ;put metatile into A -PutMTileB: sta Block_Metatile,x ;store whatever metatile be appropriate here - jsr InitBlock_XY_Pos ;get block object horizontal coordinates saved - ldy $02 ;get vertical high nybble offset - lda #$23 - sta ($06),y ;write blank metatile $23 to block buffer - lda #$10 - sta BlockBounceTimer ;set block bounce timer - pla ;pull original metatile from stack - sta $05 ;and save here - ldy #$00 ;set default offset - lda CrouchingFlag ;is player crouching? - bne SmallBP ;if so, branch to increment offset - lda PlayerSize ;is player big? - beq BigBP ;if so, branch to use default offset -SmallBP: iny ;increment for small or big and crouching -BigBP: lda Player_Y_Position ;get player's vertical coordinate - clc - adc BlockYPosAdderData,y ;add value determined by size - and #$f0 ;mask out low nybble to get 16-pixel correspondence - sta Block_Y_Position,x ;save as vertical coordinate for block object - ldy Block_State,x ;get block object state - cpy #$11 - beq Unbreak ;if set to value loaded for unbreakable, branch - jsr BrickShatter ;execute code for breakable brick - jmp InvOBit ;skip subroutine to do last part of code here -Unbreak: jsr BumpBlock ;execute code for unbreakable brick or question block -InvOBit: lda SprDataOffset_Ctrl ;invert control bit used by block objects - eor #$01 ;and floatey numbers - sta SprDataOffset_Ctrl - rts ;leave! - -;-------------------------------- - -InitBlock_XY_Pos: - lda Player_X_Position ;get player's horizontal coordinate - clc - adc #$08 ;add eight pixels - and #$f0 ;mask out low nybble to give 16-pixel correspondence - sta Block_X_Position,x ;save as horizontal coordinate for block object - lda Player_PageLoc - adc #$00 ;add carry to page location of player - sta Block_PageLoc,x ;save as page location of block object - sta Block_PageLoc2,x ;save elsewhere to be used later - lda Player_Y_HighPos - sta Block_Y_HighPos,x ;save vertical high byte of player into - rts ;vertical high byte of block object and leave - -;-------------------------------- - -BumpBlock: - jsr CheckTopOfBlock ;check to see if there's a coin directly above this block - lda #Sfx_Bump - sta Square1SoundQueue ;play bump sound - lda #$00 - sta Block_X_Speed,x ;initialize horizontal speed for block object - sta Block_Y_MoveForce,x ;init fractional movement force - sta Player_Y_Speed ;init player's vertical speed - lda #$fe - sta Block_Y_Speed,x ;set vertical speed for block object - lda $05 ;get original metatile from stack - jsr BlockBumpedChk ;do a sub to check which block player bumped head on - bcc ExitBlockChk ;if no match was found, branch to leave - tya ;move block number to A - cmp #$09 ;if block number was within 0-8 range, - bcc BlockCode ;branch to use current number - sbc #$05 ;otherwise subtract 5 for second set to get proper number -BlockCode: jsr JumpEngine ;run appropriate subroutine depending on block number - - .dw MushFlowerBlock - .dw CoinBlock - .dw CoinBlock - .dw ExtraLifeMushBlock - .dw MushFlowerBlock - .dw VineBlock - .dw StarBlock - .dw CoinBlock - .dw ExtraLifeMushBlock - -;-------------------------------- - -MushFlowerBlock: - lda #$00 ;load mushroom/fire flower into power-up type - .db $2c ;BIT instruction opcode - -StarBlock: - lda #$02 ;load star into power-up type - .db $2c ;BIT instruction opcode - -ExtraLifeMushBlock: - lda #$03 ;load 1-up mushroom into power-up type - sta $39 ;store correct power-up type - jmp SetupPowerUp - -VineBlock: - ldx #$05 ;load last slot for enemy object buffer - ldy SprDataOffset_Ctrl ;get control bit - jsr Setup_Vine ;set up vine object - -ExitBlockChk: - rts ;leave - -;-------------------------------- - -BrickQBlockMetatiles: - .db $c1, $c0, $5f, $60 ;used by question blocks - - ;these two sets are functionally identical, but look different - .db $55, $56, $57, $58, $59 ;used by ground level types - .db $5a, $5b, $5c, $5d, $5e ;used by other level types - -BlockBumpedChk: - ldy #$0d ;start at end of metatile data -BumpChkLoop: cmp BrickQBlockMetatiles,y ;check to see if current metatile matches - beq MatchBump ;metatile found in block buffer, branch if so - dey ;otherwise move onto next metatile - bpl BumpChkLoop ;do this until all metatiles are checked - clc ;if none match, return with carry clear -MatchBump: rts ;note carry is set if found match - -;-------------------------------- - -BrickShatter: - jsr CheckTopOfBlock ;check to see if there's a coin directly above this block - lda #Sfx_BrickShatter - sta Block_RepFlag,x ;set flag for block object to immediately replace metatile - sta NoiseSoundQueue ;load brick shatter sound - jsr SpawnBrickChunks ;create brick chunk objects - lda #$fe - sta Player_Y_Speed ;set vertical speed for player - lda #$05 - sta DigitModifier+5 ;set digit modifier to give player 50 points - jsr AddToScore ;do sub to update the score - ldx SprDataOffset_Ctrl ;load control bit and leave - rts - -;-------------------------------- - -CheckTopOfBlock: - ldx SprDataOffset_Ctrl ;load control bit - ldy $02 ;get vertical high nybble offset used in block buffer - beq TopEx ;branch to leave if set to zero, because we're at the top - tya ;otherwise set to A - sec - sbc #$10 ;subtract $10 to move up one row in the block buffer - sta $02 ;store as new vertical high nybble offset - tay - lda ($06),y ;get contents of block buffer in same column, one row up - cmp #$c2 ;is it a coin? (not underwater) - bne TopEx ;if not, branch to leave - lda #$00 - sta ($06),y ;otherwise put blank metatile where coin was - jsr RemoveCoin_Axe ;write blank metatile to vram buffer - ldx SprDataOffset_Ctrl ;get control bit - jsr SetupJumpCoin ;create jumping coin object and update coin variables -TopEx: rts ;leave! - -;-------------------------------- - -SpawnBrickChunks: - lda Block_X_Position,x ;set horizontal coordinate of block object - sta Block_Orig_XPos,x ;as original horizontal coordinate here - lda #$f0 - sta Block_X_Speed,x ;set horizontal speed for brick chunk objects - sta Block_X_Speed+2,x - lda #$fa - sta Block_Y_Speed,x ;set vertical speed for one - lda #$fc - sta Block_Y_Speed+2,x ;set lower vertical speed for the other - lda #$00 - sta Block_Y_MoveForce,x ;init fractional movement force for both - sta Block_Y_MoveForce+2,x - lda Block_PageLoc,x - sta Block_PageLoc+2,x ;copy page location - lda Block_X_Position,x - sta Block_X_Position+2,x ;copy horizontal coordinate - lda Block_Y_Position,x - clc ;add 8 pixels to vertical coordinate - adc #$08 ;and save as vertical coordinate for one of them - sta Block_Y_Position+2,x - lda #$fa - sta Block_Y_Speed,x ;set vertical speed...again??? (redundant) - rts - -;------------------------------------------------------------------------------------- - -BlockObjectsCore: - lda Block_State,x ;get state of block object - beq UpdSte ;if not set, branch to leave - and #$0f ;mask out high nybble - pha ;push to stack - tay ;put in Y for now - txa - clc - adc #$09 ;add 9 bytes to offset (note two block objects are created - tax ;when using brick chunks, but only one offset for both) - dey ;decrement Y to check for solid block state - beq BouncingBlockHandler ;branch if found, otherwise continue for brick chunks - jsr ImposeGravityBlock ;do sub to impose gravity on one block object object - jsr MoveObjectHorizontally ;do another sub to move horizontally - txa - clc ;move onto next block object - adc #$02 - tax - jsr ImposeGravityBlock ;do sub to impose gravity on other block object - jsr MoveObjectHorizontally ;do another sub to move horizontally - ldx ObjectOffset ;get block object offset used for both - jsr RelativeBlockPosition ;get relative coordinates - jsr GetBlockOffscreenBits ;get offscreen information - jsr DrawBrickChunks ;draw the brick chunks - pla ;get lower nybble of saved state - ldy Block_Y_HighPos,x ;check vertical high byte of block object - beq UpdSte ;if above the screen, branch to kill it - pha ;otherwise save state back into stack - lda #$f0 - cmp Block_Y_Position+2,x ;check to see if bottom block object went - bcs ChkTop ;to the bottom of the screen, and branch if not - sta Block_Y_Position+2,x ;otherwise set offscreen coordinate -ChkTop: lda Block_Y_Position,x ;get top block object's vertical coordinate - cmp #$f0 ;see if it went to the bottom of the screen - pla ;pull block object state from stack - bcc UpdSte ;if not, branch to save state - bcs KillBlock ;otherwise do unconditional branch to kill it - -BouncingBlockHandler: - jsr ImposeGravityBlock ;do sub to impose gravity on block object - ldx ObjectOffset ;get block object offset - jsr RelativeBlockPosition ;get relative coordinates - jsr GetBlockOffscreenBits ;get offscreen information - jsr DrawBlock ;draw the block - lda Block_Y_Position,x ;get vertical coordinate - and #$0f ;mask out high nybble - cmp #$05 ;check to see if low nybble wrapped around - pla ;pull state from stack - bcs UpdSte ;if still above amount, not time to kill block yet, thus branch - lda #$01 - sta Block_RepFlag,x ;otherwise set flag to replace metatile -KillBlock: lda #$00 ;if branched here, nullify object state -UpdSte: sta Block_State,x ;store contents of A in block object state - rts - -;------------------------------------------------------------------------------------- -;$02 - used to store offset to block buffer -;$06-$07 - used to store block buffer address - -BlockObjMT_Updater: - ldx #$01 ;set offset to start with second block object -UpdateLoop: stx ObjectOffset ;set offset here - lda VRAM_Buffer1 ;if vram buffer already being used here, - bne NextBUpd ;branch to move onto next block object - lda Block_RepFlag,x ;if flag for block object already clear, - beq NextBUpd ;branch to move onto next block object - lda Block_BBuf_Low,x ;get low byte of block buffer - sta $06 ;store into block buffer address - lda #$05 - sta $07 ;set high byte of block buffer address - lda Block_Orig_YPos,x ;get original vertical coordinate of block object - sta $02 ;store here and use as offset to block buffer - tay - lda Block_Metatile,x ;get metatile to be written - sta ($06),y ;write it to the block buffer - jsr ReplaceBlockMetatile ;do sub to replace metatile where block object is - lda #$00 - sta Block_RepFlag,x ;clear block object flag -NextBUpd: dex ;decrement block object offset - bpl UpdateLoop ;do this until both block objects are dealt with - rts ;then leave - -;------------------------------------------------------------------------------------- -;$00 - used to store high nybble of horizontal speed as adder -;$01 - used to store low nybble of horizontal speed -;$02 - used to store adder to page location - -MoveEnemyHorizontally: - inx ;increment offset for enemy offset - jsr MoveObjectHorizontally ;position object horizontally according to - ldx ObjectOffset ;counters, return with saved value in A, - rts ;put enemy offset back in X and leave - -MovePlayerHorizontally: - lda JumpspringAnimCtrl ;if jumpspring currently animating, - bne ExXMove ;branch to leave - tax ;otherwise set zero for offset to use player's stuff - -MoveObjectHorizontally: - lda SprObject_X_Speed,x ;get currently saved value (horizontal - asl ;speed, secondary counter, whatever) - asl ;and move low nybble to high - asl - asl - sta $01 ;store result here - lda SprObject_X_Speed,x ;get saved value again - lsr ;move high nybble to low - lsr - lsr - lsr - cmp #$08 ;if < 8, branch, do not change - bcc SaveXSpd - ora #%11110000 ;otherwise alter high nybble -SaveXSpd: sta $00 ;save result here - ldy #$00 ;load default Y value here - cmp #$00 ;if result positive, leave Y alone - bpl UseAdder - dey ;otherwise decrement Y -UseAdder: sty $02 ;save Y here - lda SprObject_X_MoveForce,x ;get whatever number's here - clc - adc $01 ;add low nybble moved to high - sta SprObject_X_MoveForce,x ;store result here - lda #$00 ;init A - rol ;rotate carry into d0 - pha ;push onto stack - ror ;rotate d0 back onto carry - lda SprObject_X_Position,x - adc $00 ;add carry plus saved value (high nybble moved to low - sta SprObject_X_Position,x ;plus $f0 if necessary) to object's horizontal position - lda SprObject_PageLoc,x - adc $02 ;add carry plus other saved value to the - sta SprObject_PageLoc,x ;object's page location and save - pla - clc ;pull old carry from stack and add - adc $00 ;to high nybble moved to low -ExXMove: rts ;and leave - -;------------------------------------------------------------------------------------- -;$00 - used for downward force -;$01 - used for upward force -;$02 - used for maximum vertical speed - -MovePlayerVertically: - ldx #$00 ;set X for player offset - lda TimerControl - bne NoJSChk ;if master timer control set, branch ahead - lda JumpspringAnimCtrl ;otherwise check to see if jumpspring is animating - bne ExXMove ;branch to leave if so -NoJSChk: lda VerticalForce ;dump vertical force - sta $00 - lda #$04 ;set maximum vertical speed here - jmp ImposeGravitySprObj ;then jump to move player vertically - -;-------------------------------- - -MoveD_EnemyVertically: - ldy #$3d ;set quick movement amount downwards - lda Enemy_State,x ;then check enemy state - cmp #$05 ;if not set to unique state for spiny's egg, go ahead - bne ContVMove ;and use, otherwise set different movement amount, continue on - -MoveFallingPlatform: - ldy #$20 ;set movement amount -ContVMove: jmp SetHiMax ;jump to skip the rest of this - -;-------------------------------- - -MoveRedPTroopaDown: - ldy #$00 ;set Y to move downwards - jmp MoveRedPTroopa ;skip to movement routine - -MoveRedPTroopaUp: - ldy #$01 ;set Y to move upwards - -MoveRedPTroopa: - inx ;increment X for enemy offset - lda #$03 - sta $00 ;set downward movement amount here - lda #$06 - sta $01 ;set upward movement amount here - lda #$02 - sta $02 ;set maximum speed here - tya ;set movement direction in A, and - jmp RedPTroopaGrav ;jump to move this thing - -;-------------------------------- - -MoveDropPlatform: - ldy #$7f ;set movement amount for drop platform - bne SetMdMax ;skip ahead of other value set here - -MoveEnemySlowVert: - ldy #$0f ;set movement amount for bowser/other objects -SetMdMax: lda #$02 ;set maximum speed in A - bne SetXMoveAmt ;unconditional branch - -;-------------------------------- - -MoveJ_EnemyVertically: - ldy #$1c ;set movement amount for podoboo/other objects -SetHiMax: lda #$03 ;set maximum speed in A -SetXMoveAmt: sty $00 ;set movement amount here - inx ;increment X for enemy offset - jsr ImposeGravitySprObj ;do a sub to move enemy object downwards - ldx ObjectOffset ;get enemy object buffer offset and leave - rts - -;-------------------------------- - -MaxSpdBlockData: - .db $06, $08 - -ResidualGravityCode: - ldy #$00 ;this part appears to be residual, - .db $2c ;no code branches or jumps to it... - -ImposeGravityBlock: - ldy #$01 ;set offset for maximum speed - lda #$50 ;set movement amount here - sta $00 - lda MaxSpdBlockData,y ;get maximum speed - -ImposeGravitySprObj: - sta $02 ;set maximum speed here - lda #$00 ;set value to move downwards - jmp ImposeGravity ;jump to the code that actually moves it - -;-------------------------------- - -MovePlatformDown: - lda #$00 ;save value to stack (if branching here, execute next - .db $2c ;part as BIT instruction) - -MovePlatformUp: - lda #$01 ;save value to stack - pha - ldy Enemy_ID,x ;get enemy object identifier - inx ;increment offset for enemy object - lda #$05 ;load default value here - cpy #$29 ;residual comparison, object #29 never executes - bne SetDplSpd ;this code, thus unconditional branch here - lda #$09 ;residual code -SetDplSpd: sta $00 ;save downward movement amount here - lda #$0a ;save upward movement amount here - sta $01 - lda #$03 ;save maximum vertical speed here - sta $02 - pla ;get value from stack - tay ;use as Y, then move onto code shared by red koopa - -RedPTroopaGrav: - jsr ImposeGravity ;do a sub to move object gradually - ldx ObjectOffset ;get enemy object offset and leave - rts - -;------------------------------------------------------------------------------------- -;$00 - used for downward force -;$01 - used for upward force -;$07 - used as adder for vertical position - -ImposeGravity: - pha ;push value to stack - lda SprObject_YMF_Dummy,x - clc ;add value in movement force to contents of dummy variable - adc SprObject_Y_MoveForce,x - sta SprObject_YMF_Dummy,x - ldy #$00 ;set Y to zero by default - lda SprObject_Y_Speed,x ;get current vertical speed - bpl AlterYP ;if currently moving downwards, do not decrement Y - dey ;otherwise decrement Y -AlterYP: sty $07 ;store Y here - adc SprObject_Y_Position,x ;add vertical position to vertical speed plus carry - sta SprObject_Y_Position,x ;store as new vertical position - lda SprObject_Y_HighPos,x - adc $07 ;add carry plus contents of $07 to vertical high byte - sta SprObject_Y_HighPos,x ;store as new vertical high byte - lda SprObject_Y_MoveForce,x - clc - adc $00 ;add downward movement amount to contents of $0433 - sta SprObject_Y_MoveForce,x - lda SprObject_Y_Speed,x ;add carry to vertical speed and store - adc #$00 - sta SprObject_Y_Speed,x - cmp $02 ;compare to maximum speed - bmi ChkUpM ;if less than preset value, skip this part - lda SprObject_Y_MoveForce,x - cmp #$80 ;if less positively than preset maximum, skip this part - bcc ChkUpM - lda $02 - sta SprObject_Y_Speed,x ;keep vertical speed within maximum value - lda #$00 - sta SprObject_Y_MoveForce,x ;clear fractional -ChkUpM: pla ;get value from stack - beq ExVMove ;if set to zero, branch to leave - lda $02 - eor #%11111111 ;otherwise get two's compliment of maximum speed - tay - iny - sty $07 ;store two's compliment here - lda SprObject_Y_MoveForce,x - sec ;subtract upward movement amount from contents - sbc $01 ;of movement force, note that $01 is twice as large as $00, - sta SprObject_Y_MoveForce,x ;thus it effectively undoes add we did earlier - lda SprObject_Y_Speed,x - sbc #$00 ;subtract borrow from vertical speed and store - sta SprObject_Y_Speed,x - cmp $07 ;compare vertical speed to two's compliment - bpl ExVMove ;if less negatively than preset maximum, skip this part - lda SprObject_Y_MoveForce,x - cmp #$80 ;check if fractional part is above certain amount, - bcs ExVMove ;and if so, branch to leave - lda $07 - sta SprObject_Y_Speed,x ;keep vertical speed within maximum value - lda #$ff - sta SprObject_Y_MoveForce,x ;clear fractional -ExVMove: rts ;leave! - -;------------------------------------------------------------------------------------- - -EnemiesAndLoopsCore: - lda Enemy_Flag,x ;check data here for MSB set - pha ;save in stack - asl - bcs ChkBowserF ;if MSB set in enemy flag, branch ahead of jumps - pla ;get from stack - beq ChkAreaTsk ;if data zero, branch - jmp RunEnemyObjectsCore ;otherwise, jump to run enemy subroutines -ChkAreaTsk: lda AreaParserTaskNum ;check number of tasks to perform - and #$07 - cmp #$07 ;if at a specific task, jump and leave - beq ExitELCore - jmp ProcLoopCommand ;otherwise, jump to process loop command/load enemies -ChkBowserF: pla ;get data from stack - and #%00001111 ;mask out high nybble - tay - lda Enemy_Flag,y ;use as pointer and load same place with different offset - bne ExitELCore - sta Enemy_Flag,x ;if second enemy flag not set, also clear first one -ExitELCore: rts - -;-------------------------------- - -;loop command data -LoopCmdWorldNumber: - .db $03, $03, $06, $06, $06, $06, $06, $06, $07, $07, $07 - -LoopCmdPageNumber: - .db $05, $09, $04, $05, $06, $08, $09, $0a, $06, $0b, $10 - -LoopCmdYPosition: - .db $40, $b0, $b0, $80, $40, $40, $80, $40, $f0, $f0, $f0 - -ExecGameLoopback: - lda Player_PageLoc ;send player back four pages - sec - sbc #$04 - sta Player_PageLoc - lda CurrentPageLoc ;send current page back four pages - sec - sbc #$04 - sta CurrentPageLoc - lda ScreenLeft_PageLoc ;subtract four from page location - sec ;of screen's left border - sbc #$04 - sta ScreenLeft_PageLoc - lda ScreenRight_PageLoc ;do the same for the page location - sec ;of screen's right border - sbc #$04 - sta ScreenRight_PageLoc - lda AreaObjectPageLoc ;subtract four from page control - sec ;for area objects - sbc #$04 - sta AreaObjectPageLoc - lda #$00 ;initialize page select for both - sta EnemyObjectPageSel ;area and enemy objects - sta AreaObjectPageSel - sta EnemyDataOffset ;initialize enemy object data offset - sta EnemyObjectPageLoc ;and enemy object page control - lda AreaDataOfsLoopback,y ;adjust area object offset based on - sta AreaDataOffset ;which loop command we encountered - rts - -ProcLoopCommand: - lda LoopCommand ;check if loop command was found - beq ChkEnemyFrenzy - lda CurrentColumnPos ;check to see if we're still on the first page - bne ChkEnemyFrenzy ;if not, do not loop yet - ldy #$0b ;start at the end of each set of loop data -FindLoop: dey - bmi ChkEnemyFrenzy ;if all data is checked and not match, do not loop - lda WorldNumber ;check to see if one of the world numbers - cmp LoopCmdWorldNumber,y ;matches our current world number - bne FindLoop - lda CurrentPageLoc ;check to see if one of the page numbers - cmp LoopCmdPageNumber,y ;matches the page we're currently on - bne FindLoop - lda Player_Y_Position ;check to see if the player is at the correct position - cmp LoopCmdYPosition,y ;if not, branch to check for world 7 - bne WrongChk - lda Player_State ;check to see if the player is - cmp #$00 ;on solid ground (i.e. not jumping or falling) - bne WrongChk ;if not, player fails to pass loop, and loopback - lda WorldNumber ;are we in world 7? (check performed on correct - cmp #World7 ;vertical position and on solid ground) - bne InitMLp ;if not, initialize flags used there, otherwise - inc MultiLoopCorrectCntr ;increment counter for correct progression -IncMLoop: inc MultiLoopPassCntr ;increment master multi-part counter - lda MultiLoopPassCntr ;have we done all three parts? - cmp #$03 - bne InitLCmd ;if not, skip this part - lda MultiLoopCorrectCntr ;if so, have we done them all correctly? - cmp #$03 - beq InitMLp ;if so, branch past unnecessary check here - bne DoLpBack ;unconditional branch if previous branch fails -WrongChk: lda WorldNumber ;are we in world 7? (check performed on - cmp #World7 ;incorrect vertical position or not on solid ground) - beq IncMLoop -DoLpBack: jsr ExecGameLoopback ;if player is not in right place, loop back - jsr KillAllEnemies -InitMLp: lda #$00 ;initialize counters used for multi-part loop commands - sta MultiLoopPassCntr - sta MultiLoopCorrectCntr -InitLCmd: lda #$00 ;initialize loop command flag - sta LoopCommand - -;-------------------------------- - -ChkEnemyFrenzy: - lda EnemyFrenzyQueue ;check for enemy object in frenzy queue - beq ProcessEnemyData ;if not, skip this part - sta Enemy_ID,x ;store as enemy object identifier here - lda #$01 - sta Enemy_Flag,x ;activate enemy object flag - lda #$00 - sta Enemy_State,x ;initialize state and frenzy queue - sta EnemyFrenzyQueue - jmp InitEnemyObject ;and then jump to deal with this enemy - -;-------------------------------- -;$06 - used to hold page location of extended right boundary -;$07 - used to hold high nybble of position of extended right boundary - -ProcessEnemyData: - ldy EnemyDataOffset ;get offset of enemy object data - lda (EnemyData),y ;load first byte - cmp #$ff ;check for EOD terminator - bne CheckEndofBuffer - jmp CheckFrenzyBuffer ;if found, jump to check frenzy buffer, otherwise - -CheckEndofBuffer: - and #%00001111 ;check for special row $0e - cmp #$0e - beq CheckRightBounds ;if found, branch, otherwise - cpx #$05 ;check for end of buffer - bcc CheckRightBounds ;if not at end of buffer, branch - iny - lda (EnemyData),y ;check for specific value here - and #%00111111 ;not sure what this was intended for, exactly - cmp #$2e ;this part is quite possibly residual code - beq CheckRightBounds ;but it has the effect of keeping enemies out of - rts ;the sixth slot - -CheckRightBounds: - lda ScreenRight_X_Pos ;add 48 to pixel coordinate of right boundary - clc - adc #$30 - and #%11110000 ;store high nybble - sta $07 - lda ScreenRight_PageLoc ;add carry to page location of right boundary - adc #$00 - sta $06 ;store page location + carry - ldy EnemyDataOffset - iny - lda (EnemyData),y ;if MSB of enemy object is clear, branch to check for row $0f - asl - bcc CheckPageCtrlRow - lda EnemyObjectPageSel ;if page select already set, do not set again - bne CheckPageCtrlRow - inc EnemyObjectPageSel ;otherwise, if MSB is set, set page select - inc EnemyObjectPageLoc ;and increment page control - -CheckPageCtrlRow: - dey - lda (EnemyData),y ;reread first byte - and #$0f - cmp #$0f ;check for special row $0f - bne PositionEnemyObj ;if not found, branch to position enemy object - lda EnemyObjectPageSel ;if page select set, - bne PositionEnemyObj ;branch without reading second byte - iny - lda (EnemyData),y ;otherwise, get second byte, mask out 2 MSB - and #%00111111 - sta EnemyObjectPageLoc ;store as page control for enemy object data - inc EnemyDataOffset ;increment enemy object data offset 2 bytes - inc EnemyDataOffset - inc EnemyObjectPageSel ;set page select for enemy object data and - jmp ProcLoopCommand ;jump back to process loop commands again - -PositionEnemyObj: - lda EnemyObjectPageLoc ;store page control as page location - sta Enemy_PageLoc,x ;for enemy object - lda (EnemyData),y ;get first byte of enemy object - and #%11110000 - sta Enemy_X_Position,x ;store column position - cmp ScreenRight_X_Pos ;check column position against right boundary - lda Enemy_PageLoc,x ;without subtracting, then subtract borrow - sbc ScreenRight_PageLoc ;from page location - bcs CheckRightExtBounds ;if enemy object beyond or at boundary, branch - lda (EnemyData),y - and #%00001111 ;check for special row $0e - cmp #$0e ;if found, jump elsewhere - beq ParseRow0e - jmp CheckThreeBytes ;if not found, unconditional jump - -CheckRightExtBounds: - lda $07 ;check right boundary + 48 against - cmp Enemy_X_Position,x ;column position without subtracting, - lda $06 ;then subtract borrow from page control temp - sbc Enemy_PageLoc,x ;plus carry - bcc CheckFrenzyBuffer ;if enemy object beyond extended boundary, branch - lda #$01 ;store value in vertical high byte - sta Enemy_Y_HighPos,x - lda (EnemyData),y ;get first byte again - asl ;multiply by four to get the vertical - asl ;coordinate - asl - asl - sta Enemy_Y_Position,x - cmp #$e0 ;do one last check for special row $0e - beq ParseRow0e ;(necessary if branched to $c1cb) - iny - lda (EnemyData),y ;get second byte of object - and #%01000000 ;check to see if hard mode bit is set - beq CheckForEnemyGroup ;if not, branch to check for group enemy objects - lda SecondaryHardMode ;if set, check to see if secondary hard mode flag - beq Inc2B ;is on, and if not, branch to skip this object completely - -CheckForEnemyGroup: - lda (EnemyData),y ;get second byte and mask out 2 MSB - and #%00111111 - cmp #$37 ;check for value below $37 - bcc BuzzyBeetleMutate - cmp #$3f ;if $37 or greater, check for value - bcc DoGroup ;below $3f, branch if below $3f - -BuzzyBeetleMutate: - cmp #Goomba ;if below $37, check for goomba - bne StrID ;value ($3f or more always fails) - ldy PrimaryHardMode ;check if primary hard mode flag is set - beq StrID ;and if so, change goomba to buzzy beetle - lda #BuzzyBeetle -StrID: sta Enemy_ID,x ;store enemy object number into buffer - lda #$01 - sta Enemy_Flag,x ;set flag for enemy in buffer - jsr InitEnemyObject - lda Enemy_Flag,x ;check to see if flag is set - bne Inc2B ;if not, leave, otherwise branch - rts - -CheckFrenzyBuffer: - lda EnemyFrenzyBuffer ;if enemy object stored in frenzy buffer - bne StrFre ;then branch ahead to store in enemy object buffer - lda VineFlagOffset ;otherwise check vine flag offset - cmp #$01 - bne ExEPar ;if other value <> 1, leave - lda #VineObject ;otherwise put vine in enemy identifier -StrFre: sta Enemy_ID,x ;store contents of frenzy buffer into enemy identifier value - -InitEnemyObject: - lda #$00 ;initialize enemy state - sta Enemy_State,x - jsr CheckpointEnemyID ;jump ahead to run jump engine and subroutines -ExEPar: rts ;then leave - -DoGroup: - jmp HandleGroupEnemies ;handle enemy group objects - -ParseRow0e: - iny ;increment Y to load third byte of object - iny - lda (EnemyData),y - lsr ;move 3 MSB to the bottom, effectively - lsr ;making %xxx00000 into %00000xxx - lsr - lsr - lsr - cmp WorldNumber ;is it the same world number as we're on? - bne NotUse ;if not, do not use (this allows multiple uses - dey ;of the same area, like the underground bonus areas) - lda (EnemyData),y ;otherwise, get second byte and use as offset - sta AreaPointer ;to addresses for level and enemy object data - iny - lda (EnemyData),y ;get third byte again, and this time mask out - and #%00011111 ;the 3 MSB from before, save as page number to be - sta EntrancePage ;used upon entry to area, if area is entered -NotUse: jmp Inc3B - -CheckThreeBytes: - ldy EnemyDataOffset ;load current offset for enemy object data - lda (EnemyData),y ;get first byte - and #%00001111 ;check for special row $0e - cmp #$0e - bne Inc2B -Inc3B: inc EnemyDataOffset ;if row = $0e, increment three bytes -Inc2B: inc EnemyDataOffset ;otherwise increment two bytes - inc EnemyDataOffset - lda #$00 ;init page select for enemy objects - sta EnemyObjectPageSel - ldx ObjectOffset ;reload current offset in enemy buffers - rts ;and leave - -CheckpointEnemyID: - lda Enemy_ID,x - cmp #$15 ;check enemy object identifier for $15 or greater - bcs InitEnemyRoutines ;and branch straight to the jump engine if found - tay ;save identifier in Y register for now - lda Enemy_Y_Position,x - adc #$08 ;add eight pixels to what will eventually be the - sta Enemy_Y_Position,x ;enemy object's vertical coordinate ($00-$14 only) - lda #$01 - sta EnemyOffscrBitsMasked,x ;set offscreen masked bit - tya ;get identifier back and use as offset for jump engine - -InitEnemyRoutines: - jsr JumpEngine - -;jump engine table for newly loaded enemy objects - - .dw InitNormalEnemy ;for objects $00-$0f - .dw InitNormalEnemy - .dw InitNormalEnemy - .dw InitRedKoopa - .dw NoInitCode - .dw InitHammerBro - .dw InitGoomba - .dw InitBloober - .dw InitBulletBill - .dw NoInitCode - .dw InitCheepCheep - .dw InitCheepCheep - .dw InitPodoboo - .dw InitPiranhaPlant - .dw InitJumpGPTroopa - .dw InitRedPTroopa - - .dw InitHorizFlySwimEnemy ;for objects $10-$1f - .dw InitLakitu - .dw InitEnemyFrenzy - .dw NoInitCode - .dw InitEnemyFrenzy - .dw InitEnemyFrenzy - .dw InitEnemyFrenzy - .dw InitEnemyFrenzy - .dw EndFrenzy - .dw NoInitCode - .dw NoInitCode - .dw InitShortFirebar - .dw InitShortFirebar - .dw InitShortFirebar - .dw InitShortFirebar - .dw InitLongFirebar - - .dw NoInitCode ;for objects $20-$2f - .dw NoInitCode - .dw NoInitCode - .dw NoInitCode - .dw InitBalPlatform - .dw InitVertPlatform - .dw LargeLiftUp - .dw LargeLiftDown - .dw InitHoriPlatform - .dw InitDropPlatform - .dw InitHoriPlatform - .dw PlatLiftUp - .dw PlatLiftDown - .dw InitBowser - .dw PwrUpJmp ;possibly dummy value - .dw Setup_Vine - - .dw NoInitCode ;for objects $30-$36 - .dw NoInitCode - .dw NoInitCode - .dw NoInitCode - .dw NoInitCode - .dw InitRetainerObj - .dw EndOfEnemyInitCode - -;------------------------------------------------------------------------------------- - -NoInitCode: - rts ;this executed when enemy object has no init code - -;-------------------------------- - -InitGoomba: - jsr InitNormalEnemy ;set appropriate horizontal speed - jmp SmallBBox ;set $09 as bounding box control, set other values - -;-------------------------------- - -InitPodoboo: - lda #$02 ;set enemy position to below - sta Enemy_Y_HighPos,x ;the bottom of the screen - sta Enemy_Y_Position,x - lsr - sta EnemyIntervalTimer,x ;set timer for enemy - lsr - sta Enemy_State,x ;initialize enemy state, then jump to use - jmp SmallBBox ;$09 as bounding box size and set other things - -;-------------------------------- - -InitRetainerObj: - lda #$b8 ;set fixed vertical position for - sta Enemy_Y_Position,x ;princess/mushroom retainer object - rts - -;-------------------------------- - -NormalXSpdData: - .db $f8, $f4 - -InitNormalEnemy: - ldy #$01 ;load offset of 1 by default - lda PrimaryHardMode ;check for primary hard mode flag set - bne GetESpd - dey ;if not set, decrement offset -GetESpd: lda NormalXSpdData,y ;get appropriate horizontal speed -SetESpd: sta Enemy_X_Speed,x ;store as speed for enemy object - jmp TallBBox ;branch to set bounding box control and other data - -;-------------------------------- - -InitRedKoopa: - jsr InitNormalEnemy ;load appropriate horizontal speed - lda #$01 ;set enemy state for red koopa troopa $03 - sta Enemy_State,x - rts - -;-------------------------------- - -HBroWalkingTimerData: - .db $80, $50 - -InitHammerBro: - lda #$00 ;init horizontal speed and timer used by hammer bro - sta HammerThrowingTimer,x ;apparently to time hammer throwing - sta Enemy_X_Speed,x - ldy SecondaryHardMode ;get secondary hard mode flag - lda HBroWalkingTimerData,y - sta EnemyIntervalTimer,x ;set value as delay for hammer bro to walk left - lda #$0b ;set specific value for bounding box size control - jmp SetBBox - -;-------------------------------- - -InitHorizFlySwimEnemy: - lda #$00 ;initialize horizontal speed - jmp SetESpd - -;-------------------------------- - -InitBloober: - lda #$00 ;initialize horizontal speed - sta BlooperMoveSpeed,x -SmallBBox: lda #$09 ;set specific bounding box size control - bne SetBBox ;unconditional branch - -;-------------------------------- - -InitRedPTroopa: - ldy #$30 ;load central position adder for 48 pixels down - lda Enemy_Y_Position,x ;set vertical coordinate into location to - sta RedPTroopaOrigXPos,x ;be used as original vertical coordinate - bpl GetCent ;if vertical coordinate < $80 - ldy #$e0 ;if => $80, load position adder for 32 pixels up -GetCent: tya ;send central position adder to A - adc Enemy_Y_Position,x ;add to current vertical coordinate - sta RedPTroopaCenterYPos,x ;store as central vertical coordinate -TallBBox: lda #$03 ;set specific bounding box size control -SetBBox: sta Enemy_BoundBoxCtrl,x ;set bounding box control here - lda #$02 ;set moving direction for left - sta Enemy_MovingDir,x -InitVStf: lda #$00 ;initialize vertical speed - sta Enemy_Y_Speed,x ;and movement force - sta Enemy_Y_MoveForce,x - rts - -;-------------------------------- - -InitBulletBill: - lda #$02 ;set moving direction for left - sta Enemy_MovingDir,x - lda #$09 ;set bounding box control for $09 - sta Enemy_BoundBoxCtrl,x - rts - -;-------------------------------- - -InitCheepCheep: - jsr SmallBBox ;set vertical bounding box, speed, init others - lda PseudoRandomBitReg,x ;check one portion of LSFR - and #%00010000 ;get d4 from it - sta CheepCheepMoveMFlag,x ;save as movement flag of some sort - lda Enemy_Y_Position,x - sta CheepCheepOrigYPos,x ;save original vertical coordinate here - rts - -;-------------------------------- - -InitLakitu: - lda EnemyFrenzyBuffer ;check to see if an enemy is already in - bne KillLakitu ;the frenzy buffer, and branch to kill lakitu if so - -SetupLakitu: - lda #$00 ;erase counter for lakitu's reappearance - sta LakituReappearTimer - jsr InitHorizFlySwimEnemy ;set $03 as bounding box, set other attributes - jmp TallBBox2 ;set $03 as bounding box again (not necessary) and leave - -KillLakitu: - jmp EraseEnemyObject - -;-------------------------------- -;$01-$03 - used to hold pseudorandom difference adjusters - -PRDiffAdjustData: - .db $26, $2c, $32, $38 - .db $20, $22, $24, $26 - .db $13, $14, $15, $16 - -LakituAndSpinyHandler: - lda FrenzyEnemyTimer ;if timer here not expired, leave - bne ExLSHand - cpx #$05 ;if we are on the special use slot, leave - bcs ExLSHand - lda #$80 ;set timer - sta FrenzyEnemyTimer - ldy #$04 ;start with the last enemy slot -ChkLak: lda Enemy_ID,y ;check all enemy slots to see - cmp #Lakitu ;if lakitu is on one of them - beq CreateSpiny ;if so, branch out of this loop - dey ;otherwise check another slot - bpl ChkLak ;loop until all slots are checked - inc LakituReappearTimer ;increment reappearance timer - lda LakituReappearTimer - cmp #$07 ;check to see if we're up to a certain value yet - bcc ExLSHand ;if not, leave - ldx #$04 ;start with the last enemy slot again -ChkNoEn: lda Enemy_Flag,x ;check enemy buffer flag for non-active enemy slot - beq CreateL ;branch out of loop if found - dex ;otherwise check next slot - bpl ChkNoEn ;branch until all slots are checked - bmi RetEOfs ;if no empty slots were found, branch to leave -CreateL: lda #$00 ;initialize enemy state - sta Enemy_State,x - lda #Lakitu ;create lakitu enemy object - sta Enemy_ID,x - jsr SetupLakitu ;do a sub to set up lakitu - lda #$20 - jsr PutAtRightExtent ;finish setting up lakitu -RetEOfs: ldx ObjectOffset ;get enemy object buffer offset again and leave -ExLSHand: rts - -;-------------------------------- - -CreateSpiny: - lda Player_Y_Position ;if player above a certain point, branch to leave - cmp #$2c - bcc ExLSHand - lda Enemy_State,y ;if lakitu is not in normal state, branch to leave - bne ExLSHand - lda Enemy_PageLoc,y ;store horizontal coordinates (high and low) of lakitu - sta Enemy_PageLoc,x ;into the coordinates of the spiny we're going to create - lda Enemy_X_Position,y - sta Enemy_X_Position,x - lda #$01 ;put spiny within vertical screen unit - sta Enemy_Y_HighPos,x - lda Enemy_Y_Position,y ;put spiny eight pixels above where lakitu is - sec - sbc #$08 - sta Enemy_Y_Position,x - lda PseudoRandomBitReg,x ;get 2 LSB of LSFR and save to Y - and #%00000011 - tay - ldx #$02 -DifLoop: lda PRDiffAdjustData,y ;get three values and save them - sta $01,x ;to $01-$03 - iny - iny ;increment Y four bytes for each value - iny - iny - dex ;decrement X for each one - bpl DifLoop ;loop until all three are written - ldx ObjectOffset ;get enemy object buffer offset - jsr PlayerLakituDiff ;move enemy, change direction, get value - difference - ldy Player_X_Speed ;check player's horizontal speed - cpy #$08 - bcs SetSpSpd ;if moving faster than a certain amount, branch elsewhere - tay ;otherwise save value in A to Y for now - lda PseudoRandomBitReg+1,x - and #%00000011 ;get one of the LSFR parts and save the 2 LSB - beq UsePosv ;branch if neither bits are set - tya - eor #%11111111 ;otherwise get two's compliment of Y - tay - iny -UsePosv: tya ;put value from A in Y back to A (they will be lost anyway) -SetSpSpd: jsr SmallBBox ;set bounding box control, init attributes, lose contents of A - ldy #$02 ;(putting this call elsewhere will preserve A) - sta Enemy_X_Speed,x ;set horizontal speed to zero because previous contents - cmp #$00 ;of A were lost...branch here will never be taken for - bmi SpinyRte ;the same reason - dey -SpinyRte: sty Enemy_MovingDir,x ;set moving direction to the right - lda #$fd - sta Enemy_Y_Speed,x ;set vertical speed to move upwards - lda #$01 - sta Enemy_Flag,x ;enable enemy object by setting flag - lda #$05 - sta Enemy_State,x ;put spiny in egg state and leave -ChpChpEx: rts - -;-------------------------------- - -FirebarSpinSpdData: - .db $28, $38, $28, $38, $28 - -FirebarSpinDirData: - .db $00, $00, $10, $10, $00 - -InitLongFirebar: - jsr DuplicateEnemyObj ;create enemy object for long firebar - -InitShortFirebar: - lda #$00 ;initialize low byte of spin state - sta FirebarSpinState_Low,x - lda Enemy_ID,x ;subtract $1b from enemy identifier - sec ;to get proper offset for firebar data - sbc #$1b - tay - lda FirebarSpinSpdData,y ;get spinning speed of firebar - sta FirebarSpinSpeed,x - lda FirebarSpinDirData,y ;get spinning direction of firebar - sta FirebarSpinDirection,x - lda Enemy_Y_Position,x - clc ;add four pixels to vertical coordinate - adc #$04 - sta Enemy_Y_Position,x - lda Enemy_X_Position,x - clc ;add four pixels to horizontal coordinate - adc #$04 - sta Enemy_X_Position,x - lda Enemy_PageLoc,x - adc #$00 ;add carry to page location - sta Enemy_PageLoc,x - jmp TallBBox2 ;set bounding box control (not used) and leave - -;-------------------------------- -;$00-$01 - used to hold pseudorandom bits - -FlyCCXPositionData: - .db $80, $30, $40, $80 - .db $30, $50, $50, $70 - .db $20, $40, $80, $a0 - .db $70, $40, $90, $68 - -FlyCCXSpeedData: - .db $0e, $05, $06, $0e - .db $1c, $20, $10, $0c - .db $1e, $22, $18, $14 - -FlyCCTimerData: - .db $10, $60, $20, $48 - -InitFlyingCheepCheep: - lda FrenzyEnemyTimer ;if timer here not expired yet, branch to leave - bne ChpChpEx - jsr SmallBBox ;jump to set bounding box size $09 and init other values - lda PseudoRandomBitReg+1,x - and #%00000011 ;set pseudorandom offset here - tay - lda FlyCCTimerData,y ;load timer with pseudorandom offset - sta FrenzyEnemyTimer - ldy #$03 ;load Y with default value - lda SecondaryHardMode - beq MaxCC ;if secondary hard mode flag not set, do not increment Y - iny ;otherwise, increment Y to allow as many as four onscreen -MaxCC: sty $00 ;store whatever pseudorandom bits are in Y - cpx $00 ;compare enemy object buffer offset with Y - bcs ChpChpEx ;if X => Y, branch to leave - lda PseudoRandomBitReg,x - and #%00000011 ;get last two bits of LSFR, first part - sta $00 ;and store in two places - sta $01 - lda #$fb ;set vertical speed for cheep-cheep - sta Enemy_Y_Speed,x - lda #$00 ;load default value - ldy Player_X_Speed ;check player's horizontal speed - beq GSeed ;if player not moving left or right, skip this part - lda #$04 - cpy #$19 ;if moving to the right but not very quickly, - bcc GSeed ;do not change A - asl ;otherwise, multiply A by 2 -GSeed: pha ;save to stack - clc - adc $00 ;add to last two bits of LSFR we saved earlier - sta $00 ;save it there - lda PseudoRandomBitReg+1,x - and #%00000011 ;if neither of the last two bits of second LSFR set, - beq RSeed ;skip this part and save contents of $00 - lda PseudoRandomBitReg+2,x - and #%00001111 ;otherwise overwrite with lower nybble of - sta $00 ;third LSFR part -RSeed: pla ;get value from stack we saved earlier - clc - adc $01 ;add to last two bits of LSFR we saved in other place - tay ;use as pseudorandom offset here - lda FlyCCXSpeedData,y ;get horizontal speed using pseudorandom offset - sta Enemy_X_Speed,x - lda #$01 ;set to move towards the right - sta Enemy_MovingDir,x - lda Player_X_Speed ;if player moving left or right, branch ahead of this part - bne D2XPos1 - ldy $00 ;get first LSFR or third LSFR lower nybble - tya ;and check for d1 set - and #%00000010 - beq D2XPos1 ;if d1 not set, branch - lda Enemy_X_Speed,x - eor #$ff ;if d1 set, change horizontal speed - clc ;into two's compliment, thus moving in the opposite - adc #$01 ;direction - sta Enemy_X_Speed,x - inc Enemy_MovingDir,x ;increment to move towards the left -D2XPos1: tya ;get first LSFR or third LSFR lower nybble again - and #%00000010 - beq D2XPos2 ;check for d1 set again, branch again if not set - lda Player_X_Position ;get player's horizontal position - clc - adc FlyCCXPositionData,y ;if d1 set, add value obtained from pseudorandom offset - sta Enemy_X_Position,x ;and save as enemy's horizontal position - lda Player_PageLoc ;get player's page location - adc #$00 ;add carry and jump past this part - jmp FinCCSt -D2XPos2: lda Player_X_Position ;get player's horizontal position - sec - sbc FlyCCXPositionData,y ;if d1 not set, subtract value obtained from pseudorandom - sta Enemy_X_Position,x ;offset and save as enemy's horizontal position - lda Player_PageLoc ;get player's page location - sbc #$00 ;subtract borrow -FinCCSt: sta Enemy_PageLoc,x ;save as enemy's page location - lda #$01 - sta Enemy_Flag,x ;set enemy's buffer flag - sta Enemy_Y_HighPos,x ;set enemy's high vertical byte - lda #$f8 - sta Enemy_Y_Position,x ;put enemy below the screen, and we are done - rts - -;-------------------------------- - -InitBowser: - jsr DuplicateEnemyObj ;jump to create another bowser object - stx BowserFront_Offset ;save offset of first here - lda #$00 - sta BowserBodyControls ;initialize bowser's body controls - sta BridgeCollapseOffset ;and bridge collapse offset - lda Enemy_X_Position,x - sta BowserOrigXPos ;store original horizontal position here - lda #$df - sta BowserFireBreathTimer ;store something here - sta Enemy_MovingDir,x ;and in moving direction - lda #$20 - sta BowserFeetCounter ;set bowser's feet timer and in enemy timer - sta EnemyFrameTimer,x - lda #$05 - sta BowserHitPoints ;give bowser 5 hit points - lsr - sta BowserMovementSpeed ;set default movement speed here - rts - -;-------------------------------- - -DuplicateEnemyObj: - ldy #$ff ;start at beginning of enemy slots -FSLoop: iny ;increment one slot - lda Enemy_Flag,y ;check enemy buffer flag for empty slot - bne FSLoop ;if set, branch and keep checking - sty DuplicateObj_Offset ;otherwise set offset here - txa ;transfer original enemy buffer offset - ora #%10000000 ;store with d7 set as flag in new enemy - sta Enemy_Flag,y ;slot as well as enemy offset - lda Enemy_PageLoc,x - sta Enemy_PageLoc,y ;copy page location and horizontal coordinates - lda Enemy_X_Position,x ;from original enemy to new enemy - sta Enemy_X_Position,y - lda #$01 - sta Enemy_Flag,x ;set flag as normal for original enemy - sta Enemy_Y_HighPos,y ;set high vertical byte for new enemy - lda Enemy_Y_Position,x - sta Enemy_Y_Position,y ;copy vertical coordinate from original to new -FlmEx: rts ;and then leave - -;-------------------------------- - -FlameYPosData: - .db $90, $80, $70, $90 - -FlameYMFAdderData: - .db $ff, $01 - -InitBowserFlame: - lda FrenzyEnemyTimer ;if timer not expired yet, branch to leave - bne FlmEx - sta Enemy_Y_MoveForce,x ;reset something here - lda NoiseSoundQueue - ora #Sfx_BowserFlame ;load bowser's flame sound into queue - sta NoiseSoundQueue - ldy BowserFront_Offset ;get bowser's buffer offset - lda Enemy_ID,y ;check for bowser - cmp #Bowser - beq SpawnFromMouth ;branch if found - jsr SetFlameTimer ;get timer data based on flame counter - clc - adc #$20 ;add 32 frames by default - ldy SecondaryHardMode - beq SetFrT ;if secondary mode flag not set, use as timer setting - sec - sbc #$10 ;otherwise subtract 16 frames for secondary hard mode -SetFrT: sta FrenzyEnemyTimer ;set timer accordingly - lda PseudoRandomBitReg,x - and #%00000011 ;get 2 LSB from first part of LSFR - sta BowserFlamePRandomOfs,x ;set here - tay ;use as offset - lda FlameYPosData,y ;load vertical position based on pseudorandom offset - -PutAtRightExtent: - sta Enemy_Y_Position,x ;set vertical position - lda ScreenRight_X_Pos - clc - adc #$20 ;place enemy 32 pixels beyond right side of screen - sta Enemy_X_Position,x - lda ScreenRight_PageLoc - adc #$00 ;add carry - sta Enemy_PageLoc,x - jmp FinishFlame ;skip this part to finish setting values - -SpawnFromMouth: - lda Enemy_X_Position,y ;get bowser's horizontal position - sec - sbc #$0e ;subtract 14 pixels - sta Enemy_X_Position,x ;save as flame's horizontal position - lda Enemy_PageLoc,y - sta Enemy_PageLoc,x ;copy page location from bowser to flame - lda Enemy_Y_Position,y - clc ;add 8 pixels to bowser's vertical position - adc #$08 - sta Enemy_Y_Position,x ;save as flame's vertical position - lda PseudoRandomBitReg,x - and #%00000011 ;get 2 LSB from first part of LSFR - sta Enemy_YMF_Dummy,x ;save here - tay ;use as offset - lda FlameYPosData,y ;get value here using bits as offset - ldy #$00 ;load default offset - cmp Enemy_Y_Position,x ;compare value to flame's current vertical position - bcc SetMF ;if less, do not increment offset - iny ;otherwise increment now -SetMF: lda FlameYMFAdderData,y ;get value here and save - sta Enemy_Y_MoveForce,x ;to vertical movement force - lda #$00 - sta EnemyFrenzyBuffer ;clear enemy frenzy buffer - -FinishFlame: - lda #$08 ;set $08 for bounding box control - sta Enemy_BoundBoxCtrl,x - lda #$01 ;set high byte of vertical and - sta Enemy_Y_HighPos,x ;enemy buffer flag - sta Enemy_Flag,x - lsr - sta Enemy_X_MoveForce,x ;initialize horizontal movement force, and - sta Enemy_State,x ;enemy state - rts - -;-------------------------------- - -FireworksXPosData: - .db $00, $30, $60, $60, $00, $20 - -FireworksYPosData: - .db $60, $40, $70, $40, $60, $30 - -InitFireworks: - lda FrenzyEnemyTimer ;if timer not expired yet, branch to leave - bne ExitFWk - lda #$20 ;otherwise reset timer - sta FrenzyEnemyTimer - dec FireworksCounter ;decrement for each explosion - ldy #$06 ;start at last slot -StarFChk: dey - lda Enemy_ID,y ;check for presence of star flag object - cmp #StarFlagObject ;if there isn't a star flag object, - bne StarFChk ;routine goes into infinite loop = crash - lda Enemy_X_Position,y - sec ;get horizontal coordinate of star flag object, then - sbc #$30 ;subtract 48 pixels from it and save to - pha ;the stack - lda Enemy_PageLoc,y - sbc #$00 ;subtract the carry from the page location - sta $00 ;of the star flag object - lda FireworksCounter ;get fireworks counter - clc - adc Enemy_State,y ;add state of star flag object (possibly not necessary) - tay ;use as offset - pla ;get saved horizontal coordinate of star flag - 48 pixels - clc - adc FireworksXPosData,y ;add number based on offset of fireworks counter - sta Enemy_X_Position,x ;store as the fireworks object horizontal coordinate - lda $00 - adc #$00 ;add carry and store as page location for - sta Enemy_PageLoc,x ;the fireworks object - lda FireworksYPosData,y ;get vertical position using same offset - sta Enemy_Y_Position,x ;and store as vertical coordinate for fireworks object - lda #$01 - sta Enemy_Y_HighPos,x ;store in vertical high byte - sta Enemy_Flag,x ;and activate enemy buffer flag - lsr - sta ExplosionGfxCounter,x ;initialize explosion counter - lda #$08 - sta ExplosionTimerCounter,x ;set explosion timing counter -ExitFWk: rts - -;-------------------------------- - -Bitmasks: - .db %00000001, %00000010, %00000100, %00001000, %00010000, %00100000, %01000000, %10000000 - -Enemy17YPosData: - .db $40, $30, $90, $50, $20, $60, $a0, $70 - -SwimCC_IDData: - .db $0a, $0b - -BulletBillCheepCheep: - lda FrenzyEnemyTimer ;if timer not expired yet, branch to leave - bne ExF17 - lda AreaType ;are we in a water-type level? - bne DoBulletBills ;if not, branch elsewhere - cpx #$03 ;are we past third enemy slot? - bcs ExF17 ;if so, branch to leave - ldy #$00 ;load default offset - lda PseudoRandomBitReg,x - cmp #$aa ;check first part of LSFR against preset value - bcc ChkW2 ;if less than preset, do not increment offset - iny ;otherwise increment -ChkW2: lda WorldNumber ;check world number - cmp #World2 - beq Get17ID ;if we're on world 2, do not increment offset - iny ;otherwise increment -Get17ID: tya - and #%00000001 ;mask out all but last bit of offset - tay - lda SwimCC_IDData,y ;load identifier for cheep-cheeps -Set17ID: sta Enemy_ID,x ;store whatever's in A as enemy identifier - lda BitMFilter - cmp #$ff ;if not all bits set, skip init part and compare bits - bne GetRBit - lda #$00 ;initialize vertical position filter - sta BitMFilter -GetRBit: lda PseudoRandomBitReg,x ;get first part of LSFR - and #%00000111 ;mask out all but 3 LSB -ChkRBit: tay ;use as offset - lda Bitmasks,y ;load bitmask - bit BitMFilter ;perform AND on filter without changing it - beq AddFBit - iny ;increment offset - tya - and #%00000111 ;mask out all but 3 LSB thus keeping it 0-7 - jmp ChkRBit ;do another check -AddFBit: ora BitMFilter ;add bit to already set bits in filter - sta BitMFilter ;and store - lda Enemy17YPosData,y ;load vertical position using offset - jsr PutAtRightExtent ;set vertical position and other values - sta Enemy_YMF_Dummy,x ;initialize dummy variable - lda #$20 ;set timer - sta FrenzyEnemyTimer - jmp CheckpointEnemyID ;process our new enemy object - -DoBulletBills: - ldy #$ff ;start at beginning of enemy slots -BB_SLoop: iny ;move onto the next slot - cpy #$05 ;branch to play sound if we've done all slots - bcs FireBulletBill - lda Enemy_Flag,y ;if enemy buffer flag not set, - beq BB_SLoop ;loop back and check another slot - lda Enemy_ID,y - cmp #BulletBill_FrenzyVar ;check enemy identifier for - bne BB_SLoop ;bullet bill object (frenzy variant) -ExF17: rts ;if found, leave - -FireBulletBill: - lda Square2SoundQueue - ora #Sfx_Blast ;play fireworks/gunfire sound - sta Square2SoundQueue - lda #BulletBill_FrenzyVar ;load identifier for bullet bill object - bne Set17ID ;unconditional branch - -;-------------------------------- -;$00 - used to store Y position of group enemies -;$01 - used to store enemy ID -;$02 - used to store page location of right side of screen -;$03 - used to store X position of right side of screen - -HandleGroupEnemies: - ldy #$00 ;load value for green koopa troopa - sec - sbc #$37 ;subtract $37 from second byte read - pha ;save result in stack for now - cmp #$04 ;was byte in $3b-$3e range? - bcs SnglID ;if so, branch - pha ;save another copy to stack - ldy #Goomba ;load value for goomba enemy - lda PrimaryHardMode ;if primary hard mode flag not set, - beq PullID ;branch, otherwise change to value - ldy #BuzzyBeetle ;for buzzy beetle -PullID: pla ;get second copy from stack -SnglID: sty $01 ;save enemy id here - ldy #$b0 ;load default y coordinate - and #$02 ;check to see if d1 was set - beq SetYGp ;if so, move y coordinate up, - ldy #$70 ;otherwise branch and use default -SetYGp: sty $00 ;save y coordinate here - lda ScreenRight_PageLoc ;get page number of right edge of screen - sta $02 ;save here - lda ScreenRight_X_Pos ;get pixel coordinate of right edge - sta $03 ;save here - ldy #$02 ;load two enemies by default - pla ;get first copy from stack - lsr ;check to see if d0 was set - bcc CntGrp ;if not, use default value - iny ;otherwise increment to three enemies -CntGrp: sty NumberofGroupEnemies ;save number of enemies here -GrLoop: ldx #$ff ;start at beginning of enemy buffers -GSltLp: inx ;increment and branch if past - cpx #$05 ;end of buffers - bcs NextED - lda Enemy_Flag,x ;check to see if enemy is already - bne GSltLp ;stored in buffer, and branch if so - lda $01 - sta Enemy_ID,x ;store enemy object identifier - lda $02 - sta Enemy_PageLoc,x ;store page location for enemy object - lda $03 - sta Enemy_X_Position,x ;store x coordinate for enemy object - clc - adc #$18 ;add 24 pixels for next enemy - sta $03 - lda $02 ;add carry to page location for - adc #$00 ;next enemy - sta $02 - lda $00 ;store y coordinate for enemy object - sta Enemy_Y_Position,x - lda #$01 ;activate flag for buffer, and - sta Enemy_Y_HighPos,x ;put enemy within the screen vertically - sta Enemy_Flag,x - jsr CheckpointEnemyID ;process each enemy object separately - dec NumberofGroupEnemies ;do this until we run out of enemy objects - bne GrLoop -NextED: jmp Inc2B ;jump to increment data offset and leave - -;-------------------------------- - -InitPiranhaPlant: - lda #$01 ;set initial speed - sta PiranhaPlant_Y_Speed,x - lsr - sta Enemy_State,x ;initialize enemy state and what would normally - sta PiranhaPlant_MoveFlag,x ;be used as vertical speed, but not in this case - lda Enemy_Y_Position,x - sta PiranhaPlantDownYPos,x ;save original vertical coordinate here - sec - sbc #$18 - sta PiranhaPlantUpYPos,x ;save original vertical coordinate - 24 pixels here - lda #$09 - jmp SetBBox2 ;set specific value for bounding box control - -;-------------------------------- - -InitEnemyFrenzy: - lda Enemy_ID,x ;load enemy identifier - sta EnemyFrenzyBuffer ;save in enemy frenzy buffer - sec - sbc #$12 ;subtract 12 and use as offset for jump engine - jsr JumpEngine - -;frenzy object jump table - .dw LakituAndSpinyHandler - .dw NoFrenzyCode - .dw InitFlyingCheepCheep - .dw InitBowserFlame - .dw InitFireworks - .dw BulletBillCheepCheep - -;-------------------------------- - -NoFrenzyCode: - rts - -;-------------------------------- - -EndFrenzy: - ldy #$05 ;start at last slot -LakituChk: lda Enemy_ID,y ;check enemy identifiers - cmp #Lakitu ;for lakitu - bne NextFSlot - lda #$01 ;if found, set state - sta Enemy_State,y -NextFSlot: dey ;move onto the next slot - bpl LakituChk ;do this until all slots are checked - lda #$00 - sta EnemyFrenzyBuffer ;empty enemy frenzy buffer - sta Enemy_Flag,x ;disable enemy buffer flag for this object - rts - -;-------------------------------- - -InitJumpGPTroopa: - lda #$02 ;set for movement to the left - sta Enemy_MovingDir,x - lda #$f8 ;set horizontal speed - sta Enemy_X_Speed,x -TallBBox2: lda #$03 ;set specific value for bounding box control -SetBBox2: sta Enemy_BoundBoxCtrl,x ;set bounding box control then leave - rts - -;-------------------------------- - -InitBalPlatform: - dec Enemy_Y_Position,x ;raise vertical position by two pixels - dec Enemy_Y_Position,x - ldy SecondaryHardMode ;if secondary hard mode flag not set, - bne AlignP ;branch ahead - ldy #$02 ;otherwise set value here - jsr PosPlatform ;do a sub to add or subtract pixels -AlignP: ldy #$ff ;set default value here for now - lda BalPlatformAlignment ;get current balance platform alignment - sta Enemy_State,x ;set platform alignment to object state here - bpl SetBPA ;if old alignment $ff, put $ff as alignment for negative - txa ;if old contents already $ff, put - tay ;object offset as alignment to make next positive -SetBPA: sty BalPlatformAlignment ;store whatever value's in Y here - lda #$00 - sta Enemy_MovingDir,x ;init moving direction - tay ;init Y - jsr PosPlatform ;do a sub to add 8 pixels, then run shared code here - -;-------------------------------- - -InitDropPlatform: - lda #$ff - sta PlatformCollisionFlag,x ;set some value here - jmp CommonPlatCode ;then jump ahead to execute more code - -;-------------------------------- - -InitHoriPlatform: - lda #$00 - sta XMoveSecondaryCounter,x ;init one of the moving counters - jmp CommonPlatCode ;jump ahead to execute more code - -;-------------------------------- - -InitVertPlatform: - ldy #$40 ;set default value here - lda Enemy_Y_Position,x ;check vertical position - bpl SetYO ;if above a certain point, skip this part - eor #$ff - clc ;otherwise get two's compliment - adc #$01 - ldy #$c0 ;get alternate value to add to vertical position -SetYO: sta YPlatformTopYPos,x ;save as top vertical position - tya - clc ;load value from earlier, add number of pixels - adc Enemy_Y_Position,x ;to vertical position - sta YPlatformCenterYPos,x ;save result as central vertical position - -;-------------------------------- - -CommonPlatCode: - jsr InitVStf ;do a sub to init certain other values -SPBBox: lda #$05 ;set default bounding box size control - ldy AreaType - cpy #$03 ;check for castle-type level - beq CasPBB ;use default value if found - ldy SecondaryHardMode ;otherwise check for secondary hard mode flag - bne CasPBB ;if set, use default value - lda #$06 ;use alternate value if not castle or secondary not set -CasPBB: sta Enemy_BoundBoxCtrl,x ;set bounding box size control here and leave - rts - -;-------------------------------- - -LargeLiftUp: - jsr PlatLiftUp ;execute code for platforms going up - jmp LargeLiftBBox ;overwrite bounding box for large platforms - -LargeLiftDown: - jsr PlatLiftDown ;execute code for platforms going down - -LargeLiftBBox: - jmp SPBBox ;jump to overwrite bounding box size control - -;-------------------------------- - -PlatLiftUp: - lda #$10 ;set movement amount here - sta Enemy_Y_MoveForce,x - lda #$ff ;set moving speed for platforms going up - sta Enemy_Y_Speed,x - jmp CommonSmallLift ;skip ahead to part we should be executing - -;-------------------------------- - -PlatLiftDown: - lda #$f0 ;set movement amount here - sta Enemy_Y_MoveForce,x - lda #$00 ;set moving speed for platforms going down - sta Enemy_Y_Speed,x - -;-------------------------------- - -CommonSmallLift: - ldy #$01 - jsr PosPlatform ;do a sub to add 12 pixels due to preset value - lda #$04 - sta Enemy_BoundBoxCtrl,x ;set bounding box control for small platforms - rts - -;-------------------------------- - -PlatPosDataLow: - .db $08,$0c,$f8 - -PlatPosDataHigh: - .db $00,$00,$ff - -PosPlatform: - lda Enemy_X_Position,x ;get horizontal coordinate - clc - adc PlatPosDataLow,y ;add or subtract pixels depending on offset - sta Enemy_X_Position,x ;store as new horizontal coordinate - lda Enemy_PageLoc,x - adc PlatPosDataHigh,y ;add or subtract page location depending on offset - sta Enemy_PageLoc,x ;store as new page location - rts ;and go back - -;-------------------------------- - -EndOfEnemyInitCode: - rts - -;------------------------------------------------------------------------------------- - -RunEnemyObjectsCore: - ldx ObjectOffset ;get offset for enemy object buffer - lda #$00 ;load value 0 for jump engine by default - ldy Enemy_ID,x - cpy #$15 ;if enemy object < $15, use default value - bcc JmpEO - tya ;otherwise subtract $14 from the value and use - sbc #$14 ;as value for jump engine -JmpEO: jsr JumpEngine - - .dw RunNormalEnemies ;for objects $00-$14 - - .dw RunBowserFlame ;for objects $15-$1f - .dw RunFireworks - .dw NoRunCode - .dw NoRunCode - .dw NoRunCode - .dw NoRunCode - .dw RunFirebarObj - .dw RunFirebarObj - .dw RunFirebarObj - .dw RunFirebarObj - .dw RunFirebarObj - - .dw RunFirebarObj ;for objects $20-$2f - .dw RunFirebarObj - .dw RunFirebarObj - .dw NoRunCode - .dw RunLargePlatform - .dw RunLargePlatform - .dw RunLargePlatform - .dw RunLargePlatform - .dw RunLargePlatform - .dw RunLargePlatform - .dw RunLargePlatform - .dw RunSmallPlatform - .dw RunSmallPlatform - .dw RunBowser - .dw PowerUpObjHandler - .dw VineObjectHandler - - .dw NoRunCode ;for objects $30-$35 - .dw RunStarFlagObj - .dw JumpspringHandler - .dw NoRunCode - .dw WarpZoneObject - .dw RunRetainerObj - -;-------------------------------- - -NoRunCode: - rts - -;-------------------------------- - -RunRetainerObj: - jsr GetEnemyOffscreenBits - jsr RelativeEnemyPosition - jmp EnemyGfxHandler - -;-------------------------------- - -RunNormalEnemies: - lda #$00 ;init sprite attributes - sta Enemy_SprAttrib,x - jsr GetEnemyOffscreenBits - jsr RelativeEnemyPosition - jsr EnemyGfxHandler - jsr GetEnemyBoundBox - jsr EnemyToBGCollisionDet - jsr EnemiesCollision - jsr PlayerEnemyCollision - ldy TimerControl ;if master timer control set, skip to last routine - bne SkipMove - jsr EnemyMovementSubs -SkipMove: jmp OffscreenBoundsCheck - -EnemyMovementSubs: - lda Enemy_ID,x - jsr JumpEngine - - .dw MoveNormalEnemy ;only objects $00-$14 use this table - .dw MoveNormalEnemy - .dw MoveNormalEnemy - .dw MoveNormalEnemy - .dw MoveNormalEnemy - .dw ProcHammerBro - .dw MoveNormalEnemy - .dw MoveBloober - .dw MoveBulletBill - .dw NoMoveCode - .dw MoveSwimmingCheepCheep - .dw MoveSwimmingCheepCheep - .dw MovePodoboo - .dw MovePiranhaPlant - .dw MoveJumpingEnemy - .dw ProcMoveRedPTroopa - .dw MoveFlyGreenPTroopa - .dw MoveLakitu - .dw MoveNormalEnemy - .dw NoMoveCode ;dummy - .dw MoveFlyingCheepCheep - -;-------------------------------- - -NoMoveCode: - rts - -;-------------------------------- - -RunBowserFlame: - jsr ProcBowserFlame - jsr GetEnemyOffscreenBits - jsr RelativeEnemyPosition - jsr GetEnemyBoundBox - jsr PlayerEnemyCollision - jmp OffscreenBoundsCheck - -;-------------------------------- - -RunFirebarObj: - jsr ProcFirebar - jmp OffscreenBoundsCheck - -;-------------------------------- - -RunSmallPlatform: - jsr GetEnemyOffscreenBits - jsr RelativeEnemyPosition - jsr SmallPlatformBoundBox - jsr SmallPlatformCollision - jsr RelativeEnemyPosition - jsr DrawSmallPlatform - jsr MoveSmallPlatform - jmp OffscreenBoundsCheck - -;-------------------------------- - -RunLargePlatform: - jsr GetEnemyOffscreenBits - jsr RelativeEnemyPosition - jsr LargePlatformBoundBox - jsr LargePlatformCollision - lda TimerControl ;if master timer control set, - bne SkipPT ;skip subroutine tree - jsr LargePlatformSubroutines -SkipPT: jsr RelativeEnemyPosition - jsr DrawLargePlatform - jmp OffscreenBoundsCheck - -;-------------------------------- - -LargePlatformSubroutines: - lda Enemy_ID,x ;subtract $24 to get proper offset for jump table - sec - sbc #$24 - jsr JumpEngine - - .dw BalancePlatform ;table used by objects $24-$2a - .dw YMovingPlatform - .dw MoveLargeLiftPlat - .dw MoveLargeLiftPlat - .dw XMovingPlatform - .dw DropPlatform - .dw RightPlatform - -;------------------------------------------------------------------------------------- - -EraseEnemyObject: - lda #$00 ;clear all enemy object variables - sta Enemy_Flag,x - sta Enemy_ID,x - sta Enemy_State,x - sta FloateyNum_Control,x - sta EnemyIntervalTimer,x - sta ShellChainCounter,x - sta Enemy_SprAttrib,x - sta EnemyFrameTimer,x - rts - -;------------------------------------------------------------------------------------- - -MovePodoboo: - lda EnemyIntervalTimer,x ;check enemy timer - bne PdbM ;branch to move enemy if not expired - jsr InitPodoboo ;otherwise set up podoboo again - lda PseudoRandomBitReg+1,x ;get part of LSFR - ora #%10000000 ;set d7 - sta Enemy_Y_MoveForce,x ;store as movement force - and #%00001111 ;mask out high nybble - ora #$06 ;set for at least six intervals - sta EnemyIntervalTimer,x ;store as new enemy timer - lda #$f9 - sta Enemy_Y_Speed,x ;set vertical speed to move podoboo upwards -PdbM: jmp MoveJ_EnemyVertically ;branch to impose gravity on podoboo - -;-------------------------------- -;$00 - used in HammerBroJumpCode as bitmask - -HammerThrowTmrData: - .db $30, $1c - -XSpeedAdderData: - .db $00, $e8, $00, $18 - -RevivedXSpeed: - .db $08, $f8, $0c, $f4 - -ProcHammerBro: - lda Enemy_State,x ;check hammer bro's enemy state for d5 set - and #%00100000 - beq ChkJH ;if not set, go ahead with code - jmp MoveDefeatedEnemy ;otherwise jump to something else -ChkJH: lda HammerBroJumpTimer,x ;check jump timer - beq HammerBroJumpCode ;if expired, branch to jump - dec HammerBroJumpTimer,x ;otherwise decrement jump timer - lda Enemy_OffscreenBits - and #%00001100 ;check offscreen bits - bne MoveHammerBroXDir ;if hammer bro a little offscreen, skip to movement code - lda HammerThrowingTimer,x ;check hammer throwing timer - bne DecHT ;if not expired, skip ahead, do not throw hammer - ldy SecondaryHardMode ;otherwise get secondary hard mode flag - lda HammerThrowTmrData,y ;get timer data using flag as offset - sta HammerThrowingTimer,x ;set as new timer - jsr SpawnHammerObj ;do a sub here to spawn hammer object - bcc DecHT ;if carry clear, hammer not spawned, skip to decrement timer - lda Enemy_State,x - ora #%00001000 ;set d3 in enemy state for hammer throw - sta Enemy_State,x - jmp MoveHammerBroXDir ;jump to move hammer bro -DecHT: dec HammerThrowingTimer,x ;decrement timer - jmp MoveHammerBroXDir ;jump to move hammer bro - -HammerBroJumpLData: - .db $20, $37 - -HammerBroJumpCode: - lda Enemy_State,x ;get hammer bro's enemy state - and #%00000111 ;mask out all but 3 LSB - cmp #$01 ;check for d0 set (for jumping) - beq MoveHammerBroXDir ;if set, branch ahead to moving code - lda #$00 ;load default value here - sta $00 ;save into temp variable for now - ldy #$fa ;set default vertical speed - lda Enemy_Y_Position,x ;check hammer bro's vertical coordinate - bmi SetHJ ;if on the bottom half of the screen, use current speed - ldy #$fd ;otherwise set alternate vertical speed - cmp #$70 ;check to see if hammer bro is above the middle of screen - inc $00 ;increment preset value to $01 - bcc SetHJ ;if above the middle of the screen, use current speed and $01 - dec $00 ;otherwise return value to $00 - lda PseudoRandomBitReg+1,x ;get part of LSFR, mask out all but LSB - and #$01 - bne SetHJ ;if d0 of LSFR set, branch and use current speed and $00 - ldy #$fa ;otherwise reset to default vertical speed -SetHJ: sty Enemy_Y_Speed,x ;set vertical speed for jumping - lda Enemy_State,x ;set d0 in enemy state for jumping - ora #$01 - sta Enemy_State,x - lda $00 ;load preset value here to use as bitmask - and PseudoRandomBitReg+2,x ;and do bit-wise comparison with part of LSFR - tay ;then use as offset - lda SecondaryHardMode ;check secondary hard mode flag - bne HJump - tay ;if secondary hard mode flag clear, set offset to 0 -HJump: lda HammerBroJumpLData,y ;get jump length timer data using offset from before - sta EnemyFrameTimer,x ;save in enemy timer - lda PseudoRandomBitReg+1,x - ora #%11000000 ;get contents of part of LSFR, set d7 and d6, then - sta HammerBroJumpTimer,x ;store in jump timer - -MoveHammerBroXDir: - ldy #$fc ;move hammer bro a little to the left - lda FrameCounter - and #%01000000 ;change hammer bro's direction every 64 frames - bne Shimmy - ldy #$04 ;if d6 set in counter, move him a little to the right -Shimmy: sty Enemy_X_Speed,x ;store horizontal speed - ldy #$01 ;set to face right by default - jsr PlayerEnemyDiff ;get horizontal difference between player and hammer bro - bmi SetShim ;if enemy to the left of player, skip this part - iny ;set to face left - lda EnemyIntervalTimer,x ;check walking timer - bne SetShim ;if not yet expired, skip to set moving direction - lda #$f8 - sta Enemy_X_Speed,x ;otherwise, make the hammer bro walk left towards player -SetShim: sty Enemy_MovingDir,x ;set moving direction - -MoveNormalEnemy: - ldy #$00 ;init Y to leave horizontal movement as-is - lda Enemy_State,x - and #%01000000 ;check enemy state for d6 set, if set skip - bne FallE ;to move enemy vertically, then horizontally if necessary - lda Enemy_State,x - asl ;check enemy state for d7 set - bcs SteadM ;if set, branch to move enemy horizontally - lda Enemy_State,x - and #%00100000 ;check enemy state for d5 set - bne MoveDefeatedEnemy ;if set, branch to move defeated enemy object - lda Enemy_State,x - and #%00000111 ;check d2-d0 of enemy state for any set bits - beq SteadM ;if enemy in normal state, branch to move enemy horizontally - cmp #$05 - beq FallE ;if enemy in state used by spiny's egg, go ahead here - cmp #$03 - bcs ReviveStunned ;if enemy in states $03 or $04, skip ahead to yet another part -FallE: jsr MoveD_EnemyVertically ;do a sub here to move enemy downwards - ldy #$00 - lda Enemy_State,x ;check for enemy state $02 - cmp #$02 - beq MEHor ;if found, branch to move enemy horizontally - and #%01000000 ;check for d6 set - beq SteadM ;if not set, branch to something else - lda Enemy_ID,x - cmp #PowerUpObject ;check for power-up object - beq SteadM - bne SlowM ;if any other object where d6 set, jump to set Y -MEHor: jmp MoveEnemyHorizontally ;jump here to move enemy horizontally for <> $2e and d6 set - -SlowM: ldy #$01 ;if branched here, increment Y to slow horizontal movement -SteadM: lda Enemy_X_Speed,x ;get current horizontal speed - pha ;save to stack - bpl AddHS ;if not moving or moving right, skip, leave Y alone - iny - iny ;otherwise increment Y to next data -AddHS: clc - adc XSpeedAdderData,y ;add value here to slow enemy down if necessary - sta Enemy_X_Speed,x ;save as horizontal speed temporarily - jsr MoveEnemyHorizontally ;then do a sub to move horizontally - pla - sta Enemy_X_Speed,x ;get old horizontal speed from stack and return to - rts ;original memory location, then leave - -ReviveStunned: - lda EnemyIntervalTimer,x ;if enemy timer not expired yet, - bne ChkKillGoomba ;skip ahead to something else - sta Enemy_State,x ;otherwise initialize enemy state to normal - lda FrameCounter - and #$01 ;get d0 of frame counter - tay ;use as Y and increment for movement direction - iny - sty Enemy_MovingDir,x ;store as pseudorandom movement direction - dey ;decrement for use as pointer - lda PrimaryHardMode ;check primary hard mode flag - beq SetRSpd ;if not set, use pointer as-is - iny - iny ;otherwise increment 2 bytes to next data -SetRSpd: lda RevivedXSpeed,y ;load and store new horizontal speed - sta Enemy_X_Speed,x ;and leave - rts - -MoveDefeatedEnemy: - jsr MoveD_EnemyVertically ;execute sub to move defeated enemy downwards - jmp MoveEnemyHorizontally ;now move defeated enemy horizontally - -ChkKillGoomba: - cmp #$0e ;check to see if enemy timer has reached - bne NKGmba ;a certain point, and branch to leave if not - lda Enemy_ID,x - cmp #Goomba ;check for goomba object - bne NKGmba ;branch if not found - jsr EraseEnemyObject ;otherwise, kill this goomba object -NKGmba: rts ;leave! - -;-------------------------------- - -MoveJumpingEnemy: - jsr MoveJ_EnemyVertically ;do a sub to impose gravity on green paratroopa - jmp MoveEnemyHorizontally ;jump to move enemy horizontally - -;-------------------------------- - -ProcMoveRedPTroopa: - lda Enemy_Y_Speed,x - ora Enemy_Y_MoveForce,x ;check for any vertical force or speed - bne MoveRedPTUpOrDown ;branch if any found - sta Enemy_YMF_Dummy,x ;initialize something here - lda Enemy_Y_Position,x ;check current vs. original vertical coordinate - cmp RedPTroopaOrigXPos,x - bcs MoveRedPTUpOrDown ;if current => original, skip ahead to more code - lda FrameCounter ;get frame counter - and #%00000111 ;mask out all but 3 LSB - bne NoIncPT ;if any bits set, branch to leave - inc Enemy_Y_Position,x ;otherwise increment red paratroopa's vertical position -NoIncPT: rts ;leave - -MoveRedPTUpOrDown: - lda Enemy_Y_Position,x ;check current vs. central vertical coordinate - cmp RedPTroopaCenterYPos,x - bcc MovPTDwn ;if current < central, jump to move downwards - jmp MoveRedPTroopaUp ;otherwise jump to move upwards -MovPTDwn: jmp MoveRedPTroopaDown ;move downwards - -;-------------------------------- -;$00 - used to store adder for movement, also used as adder for platform -;$01 - used to store maximum value for secondary counter - -MoveFlyGreenPTroopa: - jsr XMoveCntr_GreenPTroopa ;do sub to increment primary and secondary counters - jsr MoveWithXMCntrs ;do sub to move green paratroopa accordingly, and horizontally - ldy #$01 ;set Y to move green paratroopa down - lda FrameCounter - and #%00000011 ;check frame counter 2 LSB for any bits set - bne NoMGPT ;branch to leave if set to move up/down every fourth frame - lda FrameCounter - and #%01000000 ;check frame counter for d6 set - bne YSway ;branch to move green paratroopa down if set - ldy #$ff ;otherwise set Y to move green paratroopa up -YSway: sty $00 ;store adder here - lda Enemy_Y_Position,x - clc ;add or subtract from vertical position - adc $00 ;to give green paratroopa a wavy flight - sta Enemy_Y_Position,x -NoMGPT: rts ;leave! - -XMoveCntr_GreenPTroopa: - lda #$13 ;load preset maximum value for secondary counter - -XMoveCntr_Platform: - sta $01 ;store value here - lda FrameCounter - and #%00000011 ;branch to leave if not on - bne NoIncXM ;every fourth frame - ldy XMoveSecondaryCounter,x ;get secondary counter - lda XMovePrimaryCounter,x ;get primary counter - lsr - bcs DecSeXM ;if d0 of primary counter set, branch elsewhere - cpy $01 ;compare secondary counter to preset maximum value - beq IncPXM ;if equal, branch ahead of this part - inc XMoveSecondaryCounter,x ;increment secondary counter and leave -NoIncXM: rts -IncPXM: inc XMovePrimaryCounter,x ;increment primary counter and leave - rts -DecSeXM: tya ;put secondary counter in A - beq IncPXM ;if secondary counter at zero, branch back - dec XMoveSecondaryCounter,x ;otherwise decrement secondary counter and leave - rts - -MoveWithXMCntrs: - lda XMoveSecondaryCounter,x ;save secondary counter to stack - pha - ldy #$01 ;set value here by default - lda XMovePrimaryCounter,x - and #%00000010 ;if d1 of primary counter is - bne XMRight ;set, branch ahead of this part here - lda XMoveSecondaryCounter,x - eor #$ff ;otherwise change secondary - clc ;counter to two's compliment - adc #$01 - sta XMoveSecondaryCounter,x - ldy #$02 ;load alternate value here -XMRight: sty Enemy_MovingDir,x ;store as moving direction - jsr MoveEnemyHorizontally - sta $00 ;save value obtained from sub here - pla ;get secondary counter from stack - sta XMoveSecondaryCounter,x ;and return to original place - rts - -;-------------------------------- - -BlooberBitmasks: - .db %00111111, %00000011 - -MoveBloober: - lda Enemy_State,x - and #%00100000 ;check enemy state for d5 set - bne MoveDefeatedBloober ;branch if set to move defeated bloober - ldy SecondaryHardMode ;use secondary hard mode flag as offset - lda PseudoRandomBitReg+1,x ;get LSFR - and BlooberBitmasks,y ;mask out bits in LSFR using bitmask loaded with offset - bne BlooberSwim ;if any bits set, skip ahead to make swim - txa - lsr ;check to see if on second or fourth slot (1 or 3) - bcc FBLeft ;if not, branch to figure out moving direction - ldy Player_MovingDir ;otherwise, load player's moving direction and - bcs SBMDir ;do an unconditional branch to set -FBLeft: ldy #$02 ;set left moving direction by default - jsr PlayerEnemyDiff ;get horizontal difference between player and bloober - bpl SBMDir ;if enemy to the right of player, keep left - dey ;otherwise decrement to set right moving direction -SBMDir: sty Enemy_MovingDir,x ;set moving direction of bloober, then continue on here - -BlooberSwim: - jsr ProcSwimmingB ;execute sub to make bloober swim characteristically - lda Enemy_Y_Position,x ;get vertical coordinate - sec - sbc Enemy_Y_MoveForce,x ;subtract movement force - cmp #$20 ;check to see if position is above edge of status bar - bcc SwimX ;if so, don't do it - sta Enemy_Y_Position,x ;otherwise, set new vertical position, make bloober swim -SwimX: ldy Enemy_MovingDir,x ;check moving direction - dey - bne LeftSwim ;if moving to the left, branch to second part - lda Enemy_X_Position,x - clc ;add movement speed to horizontal coordinate - adc BlooperMoveSpeed,x - sta Enemy_X_Position,x ;store result as new horizontal coordinate - lda Enemy_PageLoc,x - adc #$00 ;add carry to page location - sta Enemy_PageLoc,x ;store as new page location and leave - rts - -LeftSwim: - lda Enemy_X_Position,x - sec ;subtract movement speed from horizontal coordinate - sbc BlooperMoveSpeed,x - sta Enemy_X_Position,x ;store result as new horizontal coordinate - lda Enemy_PageLoc,x - sbc #$00 ;subtract borrow from page location - sta Enemy_PageLoc,x ;store as new page location and leave - rts - -MoveDefeatedBloober: - jmp MoveEnemySlowVert ;jump to move defeated bloober downwards - -ProcSwimmingB: - lda BlooperMoveCounter,x ;get enemy's movement counter - and #%00000010 ;check for d1 set - bne ChkForFloatdown ;branch if set - lda FrameCounter - and #%00000111 ;get 3 LSB of frame counter - pha ;and save it to the stack - lda BlooperMoveCounter,x ;get enemy's movement counter - lsr ;check for d0 set - bcs SlowSwim ;branch if set - pla ;pull 3 LSB of frame counter from the stack - bne BSwimE ;branch to leave, execute code only every eighth frame - lda Enemy_Y_MoveForce,x - clc ;add to movement force to speed up swim - adc #$01 - sta Enemy_Y_MoveForce,x ;set movement force - sta BlooperMoveSpeed,x ;set as movement speed - cmp #$02 - bne BSwimE ;if certain horizontal speed, branch to leave - inc BlooperMoveCounter,x ;otherwise increment movement counter -BSwimE: rts - -SlowSwim: - pla ;pull 3 LSB of frame counter from the stack - bne NoSSw ;branch to leave, execute code only every eighth frame - lda Enemy_Y_MoveForce,x - sec ;subtract from movement force to slow swim - sbc #$01 - sta Enemy_Y_MoveForce,x ;set movement force - sta BlooperMoveSpeed,x ;set as movement speed - bne NoSSw ;if any speed, branch to leave - inc BlooperMoveCounter,x ;otherwise increment movement counter - lda #$02 - sta EnemyIntervalTimer,x ;set enemy's timer -NoSSw: rts ;leave - -ChkForFloatdown: - lda EnemyIntervalTimer,x ;get enemy timer - beq ChkNearPlayer ;branch if expired - -Floatdown: - lda FrameCounter ;get frame counter - lsr ;check for d0 set - bcs NoFD ;branch to leave on every other frame - inc Enemy_Y_Position,x ;otherwise increment vertical coordinate -NoFD: rts ;leave - -ChkNearPlayer: - lda Enemy_Y_Position,x ;get vertical coordinate - adc #$10 ;add sixteen pixels - cmp Player_Y_Position ;compare result with player's vertical coordinate - bcc Floatdown ;if modified vertical less than player's, branch - lda #$00 - sta BlooperMoveCounter,x ;otherwise nullify movement counter - rts - -;-------------------------------- - -MoveBulletBill: - lda Enemy_State,x ;check bullet bill's enemy object state for d5 set - and #%00100000 - beq NotDefB ;if not set, continue with movement code - jmp MoveJ_EnemyVertically ;otherwise jump to move defeated bullet bill downwards -NotDefB: lda #$e8 ;set bullet bill's horizontal speed - sta Enemy_X_Speed,x ;and move it accordingly (note: this bullet bill - jmp MoveEnemyHorizontally ;object occurs in frenzy object $17, not from cannons) - -;-------------------------------- -;$02 - used to hold preset values -;$03 - used to hold enemy state - -SwimCCXMoveData: - .db $40, $80 - .db $04, $04 ;residual data, not used - -MoveSwimmingCheepCheep: - lda Enemy_State,x ;check cheep-cheep's enemy object state - and #%00100000 ;for d5 set - beq CCSwim ;if not set, continue with movement code - jmp MoveEnemySlowVert ;otherwise jump to move defeated cheep-cheep downwards -CCSwim: sta $03 ;save enemy state in $03 - lda Enemy_ID,x ;get enemy identifier - sec - sbc #$0a ;subtract ten for cheep-cheep identifiers - tay ;use as offset - lda SwimCCXMoveData,y ;load value here - sta $02 - lda Enemy_X_MoveForce,x ;load horizontal force - sec - sbc $02 ;subtract preset value from horizontal force - sta Enemy_X_MoveForce,x ;store as new horizontal force - lda Enemy_X_Position,x ;get horizontal coordinate - sbc #$00 ;subtract borrow (thus moving it slowly) - sta Enemy_X_Position,x ;and save as new horizontal coordinate - lda Enemy_PageLoc,x - sbc #$00 ;subtract borrow again, this time from the - sta Enemy_PageLoc,x ;page location, then save - lda #$20 - sta $02 ;save new value here - cpx #$02 ;check enemy object offset - bcc ExSwCC ;if in first or second slot, branch to leave - lda CheepCheepMoveMFlag,x ;check movement flag - cmp #$10 ;if movement speed set to $00, - bcc CCSwimUpwards ;branch to move upwards - lda Enemy_YMF_Dummy,x - clc - adc $02 ;add preset value to dummy variable to get carry - sta Enemy_YMF_Dummy,x ;and save dummy - lda Enemy_Y_Position,x ;get vertical coordinate - adc $03 ;add carry to it plus enemy state to slowly move it downwards - sta Enemy_Y_Position,x ;save as new vertical coordinate - lda Enemy_Y_HighPos,x - adc #$00 ;add carry to page location and - jmp ChkSwimYPos ;jump to end of movement code - -CCSwimUpwards: - lda Enemy_YMF_Dummy,x - sec - sbc $02 ;subtract preset value to dummy variable to get borrow - sta Enemy_YMF_Dummy,x ;and save dummy - lda Enemy_Y_Position,x ;get vertical coordinate - sbc $03 ;subtract borrow to it plus enemy state to slowly move it upwards - sta Enemy_Y_Position,x ;save as new vertical coordinate - lda Enemy_Y_HighPos,x - sbc #$00 ;subtract borrow from page location - -ChkSwimYPos: - sta Enemy_Y_HighPos,x ;save new page location here - ldy #$00 ;load movement speed to upwards by default - lda Enemy_Y_Position,x ;get vertical coordinate - sec - sbc CheepCheepOrigYPos,x ;subtract original coordinate from current - bpl YPDiff ;if result positive, skip to next part - ldy #$10 ;otherwise load movement speed to downwards - eor #$ff - clc ;get two's compliment of result - adc #$01 ;to obtain total difference of original vs. current -YPDiff: cmp #$0f ;if difference between original vs. current vertical - bcc ExSwCC ;coordinates < 15 pixels, leave movement speed alone - tya - sta CheepCheepMoveMFlag,x ;otherwise change movement speed -ExSwCC: rts ;leave - -;-------------------------------- -;$00 - used as counter for firebar parts -;$01 - used for oscillated high byte of spin state or to hold horizontal adder -;$02 - used for oscillated high byte of spin state or to hold vertical adder -;$03 - used for mirror data -;$04 - used to store player's sprite 1 X coordinate -;$05 - used to evaluate mirror data -;$06 - used to store either screen X coordinate or sprite data offset -;$07 - used to store screen Y coordinate -;$ed - used to hold maximum length of firebar -;$ef - used to hold high byte of spinstate - -;horizontal adder is at first byte + high byte of spinstate, -;vertical adder is same + 8 bytes, two's compliment -;if greater than $08 for proper oscillation -FirebarPosLookupTbl: - .db $00, $01, $03, $04, $05, $06, $07, $07, $08 - .db $00, $03, $06, $09, $0b, $0d, $0e, $0f, $10 - .db $00, $04, $09, $0d, $10, $13, $16, $17, $18 - .db $00, $06, $0c, $12, $16, $1a, $1d, $1f, $20 - .db $00, $07, $0f, $16, $1c, $21, $25, $27, $28 - .db $00, $09, $12, $1b, $21, $27, $2c, $2f, $30 - .db $00, $0b, $15, $1f, $27, $2e, $33, $37, $38 - .db $00, $0c, $18, $24, $2d, $35, $3b, $3e, $40 - .db $00, $0e, $1b, $28, $32, $3b, $42, $46, $48 - .db $00, $0f, $1f, $2d, $38, $42, $4a, $4e, $50 - .db $00, $11, $22, $31, $3e, $49, $51, $56, $58 - -FirebarMirrorData: - .db $01, $03, $02, $00 - -FirebarTblOffsets: - .db $00, $09, $12, $1b, $24, $2d - .db $36, $3f, $48, $51, $5a, $63 - -FirebarYPos: - .db $0c, $18 - -ProcFirebar: - jsr GetEnemyOffscreenBits ;get offscreen information - lda Enemy_OffscreenBits ;check for d3 set - and #%00001000 ;if so, branch to leave - bne SkipFBar - lda TimerControl ;if master timer control set, branch - bne SusFbar ;ahead of this part - lda FirebarSpinSpeed,x ;load spinning speed of firebar - jsr FirebarSpin ;modify current spinstate - and #%00011111 ;mask out all but 5 LSB - sta FirebarSpinState_High,x ;and store as new high byte of spinstate -SusFbar: lda FirebarSpinState_High,x ;get high byte of spinstate - ldy Enemy_ID,x ;check enemy identifier - cpy #$1f - bcc SetupGFB ;if < $1f (long firebar), branch - cmp #$08 ;check high byte of spinstate - beq SkpFSte ;if eight, branch to change - cmp #$18 - bne SetupGFB ;if not at twenty-four branch to not change -SkpFSte: clc - adc #$01 ;add one to spinning thing to avoid horizontal state - sta FirebarSpinState_High,x -SetupGFB: sta $ef ;save high byte of spinning thing, modified or otherwise - jsr RelativeEnemyPosition ;get relative coordinates to screen - jsr GetFirebarPosition ;do a sub here (residual, too early to be used now) - ldy Enemy_SprDataOffset,x ;get OAM data offset - lda Enemy_Rel_YPos ;get relative vertical coordinate - sta Sprite_Y_Position,y ;store as Y in OAM data - sta $07 ;also save here - lda Enemy_Rel_XPos ;get relative horizontal coordinate - sta Sprite_X_Position,y ;store as X in OAM data - sta $06 ;also save here - lda #$01 - sta $00 ;set $01 value here (not necessary) - jsr FirebarCollision ;draw fireball part and do collision detection - ldy #$05 ;load value for short firebars by default - lda Enemy_ID,x - cmp #$1f ;are we doing a long firebar? - bcc SetMFbar ;no, branch then - ldy #$0b ;otherwise load value for long firebars -SetMFbar: sty $ed ;store maximum value for length of firebars - lda #$00 - sta $00 ;initialize counter here -DrawFbar: lda $ef ;load high byte of spinstate - jsr GetFirebarPosition ;get fireball position data depending on firebar part - jsr DrawFirebar_Collision ;position it properly, draw it and do collision detection - lda $00 ;check which firebar part - cmp #$04 - bne NextFbar - ldy DuplicateObj_Offset ;if we arrive at fifth firebar part, - lda Enemy_SprDataOffset,y ;get offset from long firebar and load OAM data offset - sta $06 ;using long firebar offset, then store as new one here -NextFbar: inc $00 ;move onto the next firebar part - lda $00 - cmp $ed ;if we end up at the maximum part, go on and leave - bcc DrawFbar ;otherwise go back and do another -SkipFBar: rts - -DrawFirebar_Collision: - lda $03 ;store mirror data elsewhere - sta $05 - ldy $06 ;load OAM data offset for firebar - lda $01 ;load horizontal adder we got from position loader - lsr $05 ;shift LSB of mirror data - bcs AddHA ;if carry was set, skip this part - eor #$ff - adc #$01 ;otherwise get two's compliment of horizontal adder -AddHA: clc ;add horizontal coordinate relative to screen to - adc Enemy_Rel_XPos ;horizontal adder, modified or otherwise - sta Sprite_X_Position,y ;store as X coordinate here - sta $06 ;store here for now, note offset is saved in Y still - cmp Enemy_Rel_XPos ;compare X coordinate of sprite to original X of firebar - bcs SubtR1 ;if sprite coordinate => original coordinate, branch - lda Enemy_Rel_XPos - sec ;otherwise subtract sprite X from the - sbc $06 ;original one and skip this part - jmp ChkFOfs -SubtR1: sec ;subtract original X from the - sbc Enemy_Rel_XPos ;current sprite X -ChkFOfs: cmp #$59 ;if difference of coordinates within a certain range, - bcc VAHandl ;continue by handling vertical adder - lda #$f8 ;otherwise, load offscreen Y coordinate - bne SetVFbr ;and unconditionally branch to move sprite offscreen -VAHandl: lda Enemy_Rel_YPos ;if vertical relative coordinate offscreen, - cmp #$f8 ;skip ahead of this part and write into sprite Y coordinate - beq SetVFbr - lda $02 ;load vertical adder we got from position loader - lsr $05 ;shift LSB of mirror data one more time - bcs AddVA ;if carry was set, skip this part - eor #$ff - adc #$01 ;otherwise get two's compliment of second part -AddVA: clc ;add vertical coordinate relative to screen to - adc Enemy_Rel_YPos ;the second data, modified or otherwise -SetVFbr: sta Sprite_Y_Position,y ;store as Y coordinate here - sta $07 ;also store here for now - -FirebarCollision: - jsr DrawFirebar ;run sub here to draw current tile of firebar - tya ;return OAM data offset and save - pha ;to the stack for now - lda StarInvincibleTimer ;if star mario invincibility timer - ora TimerControl ;or master timer controls set - bne NoColFB ;then skip all of this - sta $05 ;otherwise initialize counter - ldy Player_Y_HighPos - dey ;if player's vertical high byte offscreen, - bne NoColFB ;skip all of this - ldy Player_Y_Position ;get player's vertical position - lda PlayerSize ;get player's size - bne AdjSm ;if player small, branch to alter variables - lda CrouchingFlag - beq BigJp ;if player big and not crouching, jump ahead -AdjSm: inc $05 ;if small or big but crouching, execute this part - inc $05 ;first increment our counter twice (setting $02 as flag) - tya - clc ;then add 24 pixels to the player's - adc #$18 ;vertical coordinate - tay -BigJp: tya ;get vertical coordinate, altered or otherwise, from Y -FBCLoop: sec ;subtract vertical position of firebar - sbc $07 ;from the vertical coordinate of the player - bpl ChkVFBD ;if player lower on the screen than firebar, - eor #$ff ;skip two's compliment part - clc ;otherwise get two's compliment - adc #$01 -ChkVFBD: cmp #$08 ;if difference => 8 pixels, skip ahead of this part - bcs Chk2Ofs - lda $06 ;if firebar on far right on the screen, skip this, - cmp #$f0 ;because, really, what's the point? - bcs Chk2Ofs - lda Sprite_X_Position+4 ;get OAM X coordinate for sprite #1 - clc - adc #$04 ;add four pixels - sta $04 ;store here - sec ;subtract horizontal coordinate of firebar - sbc $06 ;from the X coordinate of player's sprite 1 - bpl ChkFBCl ;if modded X coordinate to the right of firebar - eor #$ff ;skip two's compliment part - clc ;otherwise get two's compliment - adc #$01 -ChkFBCl: cmp #$08 ;if difference < 8 pixels, collision, thus branch - bcc ChgSDir ;to process -Chk2Ofs: lda $05 ;if value of $02 was set earlier for whatever reason, - cmp #$02 ;branch to increment OAM offset and leave, no collision - beq NoColFB - ldy $05 ;otherwise get temp here and use as offset - lda Player_Y_Position - clc - adc FirebarYPos,y ;add value loaded with offset to player's vertical coordinate - inc $05 ;then increment temp and jump back - jmp FBCLoop -ChgSDir: ldx #$01 ;set movement direction by default - lda $04 ;if OAM X coordinate of player's sprite 1 - cmp $06 ;is greater than horizontal coordinate of firebar - bcs SetSDir ;then do not alter movement direction - inx ;otherwise increment it -SetSDir: stx Enemy_MovingDir ;store movement direction here - ldx #$00 - lda $00 ;save value written to $00 to stack - pha - jsr InjurePlayer ;perform sub to hurt or kill player - pla - sta $00 ;get value of $00 from stack -NoColFB: pla ;get OAM data offset - clc ;add four to it and save - adc #$04 - sta $06 - ldx ObjectOffset ;get enemy object buffer offset and leave - rts - -GetFirebarPosition: - pha ;save high byte of spinstate to the stack - and #%00001111 ;mask out low nybble - cmp #$09 - bcc GetHAdder ;if lower than $09, branch ahead - eor #%00001111 ;otherwise get two's compliment to oscillate - clc - adc #$01 -GetHAdder: sta $01 ;store result, modified or not, here - ldy $00 ;load number of firebar ball where we're at - lda FirebarTblOffsets,y ;load offset to firebar position data - clc - adc $01 ;add oscillated high byte of spinstate - tay ;to offset here and use as new offset - lda FirebarPosLookupTbl,y ;get data here and store as horizontal adder - sta $01 - pla ;pull whatever was in A from the stack - pha ;save it again because we still need it - clc - adc #$08 ;add eight this time, to get vertical adder - and #%00001111 ;mask out high nybble - cmp #$09 ;if lower than $09, branch ahead - bcc GetVAdder - eor #%00001111 ;otherwise get two's compliment - clc - adc #$01 -GetVAdder: sta $02 ;store result here - ldy $00 - lda FirebarTblOffsets,y ;load offset to firebar position data again - clc - adc $02 ;this time add value in $02 to offset here and use as offset - tay - lda FirebarPosLookupTbl,y ;get data here and store as vertica adder - sta $02 - pla ;pull out whatever was in A one last time - lsr ;divide by eight or shift three to the right - lsr - lsr - tay ;use as offset - lda FirebarMirrorData,y ;load mirroring data here - sta $03 ;store - rts - -;-------------------------------- - -PRandomSubtracter: - .db $f8, $a0, $70, $bd, $00 - -FlyCCBPriority: - .db $20, $20, $20, $00, $00 - -MoveFlyingCheepCheep: - lda Enemy_State,x ;check cheep-cheep's enemy state - and #%00100000 ;for d5 set - beq FlyCC ;branch to continue code if not set - lda #$00 - sta Enemy_SprAttrib,x ;otherwise clear sprite attributes - jmp MoveJ_EnemyVertically ;and jump to move defeated cheep-cheep downwards -FlyCC: jsr MoveEnemyHorizontally ;move cheep-cheep horizontally based on speed and force - ldy #$0d ;set vertical movement amount - lda #$05 ;set maximum speed - jsr SetXMoveAmt ;branch to impose gravity on flying cheep-cheep - lda Enemy_Y_MoveForce,x - lsr ;get vertical movement force and - lsr ;move high nybble to low - lsr - lsr - tay ;save as offset (note this tends to go into reach of code) - lda Enemy_Y_Position,x ;get vertical position - sec ;subtract pseudorandom value based on offset from position - sbc PRandomSubtracter,y - bpl AddCCF ;if result within top half of screen, skip this part - eor #$ff - clc ;otherwise get two's compliment - adc #$01 -AddCCF: cmp #$08 ;if result or two's compliment greater than eight, - bcs BPGet ;skip to the end without changing movement force - lda Enemy_Y_MoveForce,x - clc - adc #$10 ;otherwise add to it - sta Enemy_Y_MoveForce,x - lsr ;move high nybble to low again - lsr - lsr - lsr - tay -BPGet: lda FlyCCBPriority,y ;load bg priority data and store (this is very likely - sta Enemy_SprAttrib,x ;broken or residual code, value is overwritten before - rts ;drawing it next frame), then leave - -;-------------------------------- -;$00 - used to hold horizontal difference -;$01-$03 - used to hold difference adjusters - -LakituDiffAdj: - .db $15, $30, $40 - -MoveLakitu: - lda Enemy_State,x ;check lakitu's enemy state - and #%00100000 ;for d5 set - beq ChkLS ;if not set, continue with code - jmp MoveD_EnemyVertically ;otherwise jump to move defeated lakitu downwards -ChkLS: lda Enemy_State,x ;if lakitu's enemy state not set at all, - beq Fr12S ;go ahead and continue with code - lda #$00 - sta LakituMoveDirection,x ;otherwise initialize moving direction to move to left - sta EnemyFrenzyBuffer ;initialize frenzy buffer - lda #$10 - bne SetLSpd ;load horizontal speed and do unconditional branch -Fr12S: lda #Spiny - sta EnemyFrenzyBuffer ;set spiny identifier in frenzy buffer - ldy #$02 -LdLDa: lda LakituDiffAdj,y ;load values - sta $0001,y ;store in zero page - dey - bpl LdLDa ;do this until all values are stired - jsr PlayerLakituDiff ;execute sub to set speed and create spinys -SetLSpd: sta LakituMoveSpeed,x ;set movement speed returned from sub - ldy #$01 ;set moving direction to right by default - lda LakituMoveDirection,x - and #$01 ;get LSB of moving direction - bne SetLMov ;if set, branch to the end to use moving direction - lda LakituMoveSpeed,x - eor #$ff ;get two's compliment of moving speed - clc - adc #$01 - sta LakituMoveSpeed,x ;store as new moving speed - iny ;increment moving direction to left -SetLMov: sty Enemy_MovingDir,x ;store moving direction - jmp MoveEnemyHorizontally ;move lakitu horizontally - -PlayerLakituDiff: - ldy #$00 ;set Y for default value - jsr PlayerEnemyDiff ;get horizontal difference between enemy and player - bpl ChkLakDif ;branch if enemy is to the right of the player - iny ;increment Y for left of player - lda $00 - eor #$ff ;get two's compliment of low byte of horizontal difference - clc - adc #$01 ;store two's compliment as horizontal difference - sta $00 -ChkLakDif: lda $00 ;get low byte of horizontal difference - cmp #$3c ;if within a certain distance of player, branch - bcc ChkPSpeed - lda #$3c ;otherwise set maximum distance - sta $00 - lda Enemy_ID,x ;check if lakitu is in our current enemy slot - cmp #Lakitu - bne ChkPSpeed ;if not, branch elsewhere - tya ;compare contents of Y, now in A - cmp LakituMoveDirection,x ;to what is being used as horizontal movement direction - beq ChkPSpeed ;if moving toward the player, branch, do not alter - lda LakituMoveDirection,x ;if moving to the left beyond maximum distance, - beq SetLMovD ;branch and alter without delay - dec LakituMoveSpeed,x ;decrement horizontal speed - lda LakituMoveSpeed,x ;if horizontal speed not yet at zero, branch to leave - bne ExMoveLak -SetLMovD: tya ;set horizontal direction depending on horizontal - sta LakituMoveDirection,x ;difference between enemy and player if necessary -ChkPSpeed: lda $00 - and #%00111100 ;mask out all but four bits in the middle - lsr ;divide masked difference by four - lsr - sta $00 ;store as new value - ldy #$00 ;init offset - lda Player_X_Speed - beq SubDifAdj ;if player not moving horizontally, branch - lda ScrollAmount - beq SubDifAdj ;if scroll speed not set, branch to same place - iny ;otherwise increment offset - lda Player_X_Speed - cmp #$19 ;if player not running, branch - bcc ChkSpinyO - lda ScrollAmount - cmp #$02 ;if scroll speed below a certain amount, branch - bcc ChkSpinyO ;to same place - iny ;otherwise increment once more -ChkSpinyO: lda Enemy_ID,x ;check for spiny object - cmp #Spiny - bne ChkEmySpd ;branch if not found - lda Player_X_Speed ;if player not moving, skip this part - bne SubDifAdj -ChkEmySpd: lda Enemy_Y_Speed,x ;check vertical speed - bne SubDifAdj ;branch if nonzero - ldy #$00 ;otherwise reinit offset -SubDifAdj: lda $0001,y ;get one of three saved values from earlier - ldy $00 ;get saved horizontal difference -SPixelLak: sec ;subtract one for each pixel of horizontal difference - sbc #$01 ;from one of three saved values - dey - bpl SPixelLak ;branch until all pixels are subtracted, to adjust difference -ExMoveLak: rts ;leave!!! - -;------------------------------------------------------------------------------------- -;$04-$05 - used to store name table address in little endian order - -BridgeCollapseData: - .db $1a ;axe - .db $58 ;chain - .db $98, $96, $94, $92, $90, $8e, $8c ;bridge - .db $8a, $88, $86, $84, $82, $80 - -BridgeCollapse: - ldx BowserFront_Offset ;get enemy offset for bowser - lda Enemy_ID,x ;check enemy object identifier for bowser - cmp #Bowser ;if not found, branch ahead, - bne SetM2 ;metatile removal not necessary - stx ObjectOffset ;store as enemy offset here - lda Enemy_State,x ;if bowser in normal state, skip all of this - beq RemoveBridge - and #%01000000 ;if bowser's state has d6 clear, skip to silence music - beq SetM2 - lda Enemy_Y_Position,x ;check bowser's vertical coordinate - cmp #$e0 ;if bowser not yet low enough, skip this part ahead - bcc MoveD_Bowser -SetM2: lda #Silence ;silence music - sta EventMusicQueue - inc OperMode_Task ;move onto next secondary mode in autoctrl mode - jmp KillAllEnemies ;jump to empty all enemy slots and then leave - -MoveD_Bowser: - jsr MoveEnemySlowVert ;do a sub to move bowser downwards - jmp BowserGfxHandler ;jump to draw bowser's front and rear, then leave - -RemoveBridge: - dec BowserFeetCounter ;decrement timer to control bowser's feet - bne NoBFall ;if not expired, skip all of this - lda #$04 - sta BowserFeetCounter ;otherwise, set timer now - lda BowserBodyControls - eor #$01 ;invert bit to control bowser's feet - sta BowserBodyControls - lda #$22 ;put high byte of name table address here for now - sta $05 - ldy BridgeCollapseOffset ;get bridge collapse offset here - lda BridgeCollapseData,y ;load low byte of name table address and store here - sta $04 - ldy VRAM_Buffer1_Offset ;increment vram buffer offset - iny - ldx #$0c ;set offset for tile data for sub to draw blank metatile - jsr RemBridge ;do sub here to remove bowser's bridge metatiles - ldx ObjectOffset ;get enemy offset - jsr MoveVOffset ;set new vram buffer offset - lda #Sfx_Blast ;load the fireworks/gunfire sound into the square 2 sfx - sta Square2SoundQueue ;queue while at the same time loading the brick - lda #Sfx_BrickShatter ;shatter sound into the noise sfx queue thus - sta NoiseSoundQueue ;producing the unique sound of the bridge collapsing - inc BridgeCollapseOffset ;increment bridge collapse offset - lda BridgeCollapseOffset - cmp #$0f ;if bridge collapse offset has not yet reached - bne NoBFall ;the end, go ahead and skip this part - jsr InitVStf ;initialize whatever vertical speed bowser has - lda #%01000000 - sta Enemy_State,x ;set bowser's state to one of defeated states (d6 set) - lda #Sfx_BowserFall - sta Square2SoundQueue ;play bowser defeat sound -NoBFall: jmp BowserGfxHandler ;jump to code that draws bowser - -;-------------------------------- - -PRandomRange: - .db $21, $41, $11, $31 - -RunBowser: - lda Enemy_State,x ;if d5 in enemy state is not set - and #%00100000 ;then branch elsewhere to run bowser - beq BowserControl - lda Enemy_Y_Position,x ;otherwise check vertical position - cmp #$e0 ;if above a certain point, branch to move defeated bowser - bcc MoveD_Bowser ;otherwise proceed to KillAllEnemies - -KillAllEnemies: - ldx #$04 ;start with last enemy slot -KillLoop: jsr EraseEnemyObject ;branch to kill enemy objects - dex ;move onto next enemy slot - bpl KillLoop ;do this until all slots are emptied - sta EnemyFrenzyBuffer ;empty frenzy buffer - ldx ObjectOffset ;get enemy object offset and leave - rts - -BowserControl: - lda #$00 - sta EnemyFrenzyBuffer ;empty frenzy buffer - lda TimerControl ;if master timer control not set, - beq ChkMouth ;skip jump and execute code here - jmp SkipToFB ;otherwise, jump over a bunch of code -ChkMouth: lda BowserBodyControls ;check bowser's mouth - bpl FeetTmr ;if bit clear, go ahead with code here - jmp HammerChk ;otherwise skip a whole section starting here -FeetTmr: dec BowserFeetCounter ;decrement timer to control bowser's feet - bne ResetMDr ;if not expired, skip this part - lda #$20 ;otherwise, reset timer - sta BowserFeetCounter - lda BowserBodyControls ;and invert bit used - eor #%00000001 ;to control bowser's feet - sta BowserBodyControls -ResetMDr: lda FrameCounter ;check frame counter - and #%00001111 ;if not on every sixteenth frame, skip - bne B_FaceP ;ahead to continue code - lda #$02 ;otherwise reset moving/facing direction every - sta Enemy_MovingDir,x ;sixteen frames -B_FaceP: lda EnemyFrameTimer,x ;if timer set here expired, - beq GetPRCmp ;branch to next section - jsr PlayerEnemyDiff ;get horizontal difference between player and bowser, - bpl GetPRCmp ;and branch if bowser to the right of the player - lda #$01 - sta Enemy_MovingDir,x ;set bowser to move and face to the right - lda #$02 - sta BowserMovementSpeed ;set movement speed - lda #$20 - sta EnemyFrameTimer,x ;set timer here - sta BowserFireBreathTimer ;set timer used for bowser's flame - lda Enemy_X_Position,x - cmp #$c8 ;if bowser to the right past a certain point, - bcs HammerChk ;skip ahead to some other section -GetPRCmp: lda FrameCounter ;get frame counter - and #%00000011 - bne HammerChk ;execute this code every fourth frame, otherwise branch - lda Enemy_X_Position,x - cmp BowserOrigXPos ;if bowser not at original horizontal position, - bne GetDToO ;branch to skip this part - lda PseudoRandomBitReg,x - and #%00000011 ;get pseudorandom offset - tay - lda PRandomRange,y ;load value using pseudorandom offset - sta MaxRangeFromOrigin ;and store here -GetDToO: lda Enemy_X_Position,x - clc ;add movement speed to bowser's horizontal - adc BowserMovementSpeed ;coordinate and save as new horizontal position - sta Enemy_X_Position,x - ldy Enemy_MovingDir,x - cpy #$01 ;if bowser moving and facing to the right, skip ahead - beq HammerChk - ldy #$ff ;set default movement speed here (move left) - sec ;get difference of current vs. original - sbc BowserOrigXPos ;horizontal position - bpl CompDToO ;if current position to the right of original, skip ahead - eor #$ff - clc ;get two's compliment - adc #$01 - ldy #$01 ;set alternate movement speed here (move right) -CompDToO: cmp MaxRangeFromOrigin ;compare difference with pseudorandom value - bcc HammerChk ;if difference < pseudorandom value, leave speed alone - sty BowserMovementSpeed ;otherwise change bowser's movement speed -HammerChk: lda EnemyFrameTimer,x ;if timer set here not expired yet, skip ahead to - bne MakeBJump ;some other section of code - jsr MoveEnemySlowVert ;otherwise start by moving bowser downwards - lda WorldNumber ;check world number - cmp #World6 - bcc SetHmrTmr ;if world 1-5, skip this part (not time to throw hammers yet) - lda FrameCounter - and #%00000011 ;check to see if it's time to execute sub - bne SetHmrTmr ;if not, skip sub, otherwise - jsr SpawnHammerObj ;execute sub on every fourth frame to spawn misc object (hammer) -SetHmrTmr: lda Enemy_Y_Position,x ;get current vertical position - cmp #$80 ;if still above a certain point - bcc ChkFireB ;then skip to world number check for flames - lda PseudoRandomBitReg,x - and #%00000011 ;get pseudorandom offset - tay - lda PRandomRange,y ;get value using pseudorandom offset - sta EnemyFrameTimer,x ;set for timer here -SkipToFB: jmp ChkFireB ;jump to execute flames code -MakeBJump: cmp #$01 ;if timer not yet about to expire, - bne ChkFireB ;skip ahead to next part - dec Enemy_Y_Position,x ;otherwise decrement vertical coordinate - jsr InitVStf ;initialize movement amount - lda #$fe - sta Enemy_Y_Speed,x ;set vertical speed to move bowser upwards -ChkFireB: lda WorldNumber ;check world number here - cmp #World8 ;world 8? - beq SpawnFBr ;if so, execute this part here - cmp #World6 ;world 6-7? - bcs BowserGfxHandler ;if so, skip this part here -SpawnFBr: lda BowserFireBreathTimer ;check timer here - bne BowserGfxHandler ;if not expired yet, skip all of this - lda #$20 - sta BowserFireBreathTimer ;set timer here - lda BowserBodyControls - eor #%10000000 ;invert bowser's mouth bit to open - sta BowserBodyControls ;and close bowser's mouth - bmi ChkFireB ;if bowser's mouth open, loop back - jsr SetFlameTimer ;get timing for bowser's flame - ldy SecondaryHardMode - beq SetFBTmr ;if secondary hard mode flag not set, skip this - sec - sbc #$10 ;otherwise subtract from value in A -SetFBTmr: sta BowserFireBreathTimer ;set value as timer here - lda #BowserFlame ;put bowser's flame identifier - sta EnemyFrenzyBuffer ;in enemy frenzy buffer - -;-------------------------------- - -BowserGfxHandler: - jsr ProcessBowserHalf ;do a sub here to process bowser's front - ldy #$10 ;load default value here to position bowser's rear - lda Enemy_MovingDir,x ;check moving direction - lsr - bcc CopyFToR ;if moving left, use default - ldy #$f0 ;otherwise load alternate positioning value here -CopyFToR: tya ;move bowser's rear object position value to A - clc - adc Enemy_X_Position,x ;add to bowser's front object horizontal coordinate - ldy DuplicateObj_Offset ;get bowser's rear object offset - sta Enemy_X_Position,y ;store A as bowser's rear horizontal coordinate - lda Enemy_Y_Position,x - clc ;add eight pixels to bowser's front object - adc #$08 ;vertical coordinate and store as vertical coordinate - sta Enemy_Y_Position,y ;for bowser's rear - lda Enemy_State,x - sta Enemy_State,y ;copy enemy state directly from front to rear - lda Enemy_MovingDir,x - sta Enemy_MovingDir,y ;copy moving direction also - lda ObjectOffset ;save enemy object offset of front to stack - pha - ldx DuplicateObj_Offset ;put enemy object offset of rear as current - stx ObjectOffset - lda #Bowser ;set bowser's enemy identifier - sta Enemy_ID,x ;store in bowser's rear object - jsr ProcessBowserHalf ;do a sub here to process bowser's rear - pla - sta ObjectOffset ;get original enemy object offset - tax - lda #$00 ;nullify bowser's front/rear graphics flag - sta BowserGfxFlag -ExBGfxH: rts ;leave! - -ProcessBowserHalf: - inc BowserGfxFlag ;increment bowser's graphics flag, then run subroutines - jsr RunRetainerObj ;to get offscreen bits, relative position and draw bowser (finally!) - lda Enemy_State,x - bne ExBGfxH ;if either enemy object not in normal state, branch to leave - lda #$0a - sta Enemy_BoundBoxCtrl,x ;set bounding box size control - jsr GetEnemyBoundBox ;get bounding box coordinates - jmp PlayerEnemyCollision ;do player-to-enemy collision detection - -;------------------------------------------------------------------------------------- -;$00 - used to hold movement force and tile number -;$01 - used to hold sprite attribute data - -FlameTimerData: - .db $bf, $40, $bf, $bf, $bf, $40, $40, $bf - -SetFlameTimer: - ldy BowserFlameTimerCtrl ;load counter as offset - inc BowserFlameTimerCtrl ;increment - lda BowserFlameTimerCtrl ;mask out all but 3 LSB - and #%00000111 ;to keep in range of 0-7 - sta BowserFlameTimerCtrl - lda FlameTimerData,y ;load value to be used then leave -ExFl: rts - -ProcBowserFlame: - lda TimerControl ;if master timer control flag set, - bne SetGfxF ;skip all of this - lda #$40 ;load default movement force - ldy SecondaryHardMode - beq SFlmX ;if secondary hard mode flag not set, use default - lda #$60 ;otherwise load alternate movement force to go faster -SFlmX: sta $00 ;store value here - lda Enemy_X_MoveForce,x - sec ;subtract value from movement force - sbc $00 - sta Enemy_X_MoveForce,x ;save new value - lda Enemy_X_Position,x - sbc #$01 ;subtract one from horizontal position to move - sta Enemy_X_Position,x ;to the left - lda Enemy_PageLoc,x - sbc #$00 ;subtract borrow from page location - sta Enemy_PageLoc,x - ldy BowserFlamePRandomOfs,x ;get some value here and use as offset - lda Enemy_Y_Position,x ;load vertical coordinate - cmp FlameYPosData,y ;compare against coordinate data using $0417,x as offset - beq SetGfxF ;if equal, branch and do not modify coordinate - clc - adc Enemy_Y_MoveForce,x ;otherwise add value here to coordinate and store - sta Enemy_Y_Position,x ;as new vertical coordinate -SetGfxF: jsr RelativeEnemyPosition ;get new relative coordinates - lda Enemy_State,x ;if bowser's flame not in normal state, - bne ExFl ;branch to leave - lda #$51 ;otherwise, continue - sta $00 ;write first tile number - ldy #$02 ;load attributes without vertical flip by default - lda FrameCounter - and #%00000010 ;invert vertical flip bit every 2 frames - beq FlmeAt ;if d1 not set, write default value - ldy #$82 ;otherwise write value with vertical flip bit set -FlmeAt: sty $01 ;set bowser's flame sprite attributes here - ldy Enemy_SprDataOffset,x ;get OAM data offset - ldx #$00 - -DrawFlameLoop: - lda Enemy_Rel_YPos ;get Y relative coordinate of current enemy object - sta Sprite_Y_Position,y ;write into Y coordinate of OAM data - lda $00 - sta Sprite_Tilenumber,y ;write current tile number into OAM data - inc $00 ;increment tile number to draw more bowser's flame - lda $01 - sta Sprite_Attributes,y ;write saved attributes into OAM data - lda Enemy_Rel_XPos - sta Sprite_X_Position,y ;write X relative coordinate of current enemy object - clc - adc #$08 - sta Enemy_Rel_XPos ;then add eight to it and store - iny - iny - iny - iny ;increment Y four times to move onto the next OAM - inx ;move onto the next OAM, and branch if three - cpx #$03 ;have not yet been done - bcc DrawFlameLoop - ldx ObjectOffset ;reload original enemy offset - jsr GetEnemyOffscreenBits ;get offscreen information - ldy Enemy_SprDataOffset,x ;get OAM data offset - lda Enemy_OffscreenBits ;get enemy object offscreen bits - lsr ;move d0 to carry and result to stack - pha - bcc M3FOfs ;branch if carry not set - lda #$f8 ;otherwise move sprite offscreen, this part likely - sta Sprite_Y_Position+12,y ;residual since flame is only made of three sprites -M3FOfs: pla ;get bits from stack - lsr ;move d1 to carry and move bits back to stack - pha - bcc M2FOfs ;branch if carry not set again - lda #$f8 ;otherwise move third sprite offscreen - sta Sprite_Y_Position+8,y -M2FOfs: pla ;get bits from stack again - lsr ;move d2 to carry and move bits back to stack again - pha - bcc M1FOfs ;branch if carry not set yet again - lda #$f8 ;otherwise move second sprite offscreen - sta Sprite_Y_Position+4,y -M1FOfs: pla ;get bits from stack one last time - lsr ;move d3 to carry - bcc ExFlmeD ;branch if carry not set one last time - lda #$f8 - sta Sprite_Y_Position,y ;otherwise move first sprite offscreen -ExFlmeD: rts ;leave - -;-------------------------------- - -RunFireworks: - dec ExplosionTimerCounter,x ;decrement explosion timing counter here - bne SetupExpl ;if not expired, skip this part - lda #$08 - sta ExplosionTimerCounter,x ;reset counter - inc ExplosionGfxCounter,x ;increment explosion graphics counter - lda ExplosionGfxCounter,x - cmp #$03 ;check explosion graphics counter - bcs FireworksSoundScore ;if at a certain point, branch to kill this object -SetupExpl: jsr RelativeEnemyPosition ;get relative coordinates of explosion - lda Enemy_Rel_YPos ;copy relative coordinates - sta Fireball_Rel_YPos ;from the enemy object to the fireball object - lda Enemy_Rel_XPos ;first vertical, then horizontal - sta Fireball_Rel_XPos - ldy Enemy_SprDataOffset,x ;get OAM data offset - lda ExplosionGfxCounter,x ;get explosion graphics counter - jsr DrawExplosion_Fireworks ;do a sub to draw the explosion then leave - rts - -FireworksSoundScore: - lda #$00 ;disable enemy buffer flag - sta Enemy_Flag,x - lda #Sfx_Blast ;play fireworks/gunfire sound - sta Square2SoundQueue - lda #$05 ;set part of score modifier for 500 points - sta DigitModifier+4 - jmp EndAreaPoints ;jump to award points accordingly then leave - -;-------------------------------- - -StarFlagYPosAdder: - .db $00, $00, $08, $08 - -StarFlagXPosAdder: - .db $00, $08, $00, $08 - -StarFlagTileData: - .db $54, $55, $56, $57 - -RunStarFlagObj: - lda #$00 ;initialize enemy frenzy buffer - sta EnemyFrenzyBuffer - lda StarFlagTaskControl ;check star flag object task number here - cmp #$05 ;if greater than 5, branch to exit - bcs StarFlagExit - jsr JumpEngine ;otherwise jump to appropriate sub - - .dw StarFlagExit - .dw GameTimerFireworks - .dw AwardGameTimerPoints - .dw RaiseFlagSetoffFWorks - .dw DelayToAreaEnd - -GameTimerFireworks: - ldy #$05 ;set default state for star flag object - lda GameTimerDisplay+2 ;get game timer's last digit - cmp #$01 - beq SetFWC ;if last digit of game timer set to 1, skip ahead - ldy #$03 ;otherwise load new value for state - cmp #$03 - beq SetFWC ;if last digit of game timer set to 3, skip ahead - ldy #$00 ;otherwise load one more potential value for state - cmp #$06 - beq SetFWC ;if last digit of game timer set to 6, skip ahead - lda #$ff ;otherwise set value for no fireworks -SetFWC: sta FireworksCounter ;set fireworks counter here - sty Enemy_State,x ;set whatever state we have in star flag object - -IncrementSFTask1: - inc StarFlagTaskControl ;increment star flag object task number - -StarFlagExit: - rts ;leave - -AwardGameTimerPoints: - lda GameTimerDisplay ;check all game timer digits for any intervals left - ora GameTimerDisplay+1 - ora GameTimerDisplay+2 - beq IncrementSFTask1 ;if no time left on game timer at all, branch to next task - lda FrameCounter - and #%00000100 ;check frame counter for d2 set (skip ahead - beq NoTTick ;for four frames every four frames) branch if not set - lda #Sfx_TimerTick - sta Square2SoundQueue ;load timer tick sound -NoTTick: ldy #$23 ;set offset here to subtract from game timer's last digit - lda #$ff ;set adder here to $ff, or -1, to subtract one - sta DigitModifier+5 ;from the last digit of the game timer - jsr DigitsMathRoutine ;subtract digit - lda #$05 ;set now to add 50 points - sta DigitModifier+5 ;per game timer interval subtracted - -EndAreaPoints: - ldy #$0b ;load offset for mario's score by default - lda CurrentPlayer ;check player on the screen - beq ELPGive ;if mario, do not change - ldy #$11 ;otherwise load offset for luigi's score -ELPGive: jsr DigitsMathRoutine ;award 50 points per game timer interval - lda CurrentPlayer ;get player on the screen (or 500 points per - asl ;fireworks explosion if branched here from there) - asl ;shift to high nybble - asl - asl - ora #%00000100 ;add four to set nybble for game timer - jmp UpdateNumber ;jump to print the new score and game timer - -RaiseFlagSetoffFWorks: - lda Enemy_Y_Position,x ;check star flag's vertical position - cmp #$72 ;against preset value - bcc SetoffF ;if star flag higher vertically, branch to other code - dec Enemy_Y_Position,x ;otherwise, raise star flag by one pixel - jmp DrawStarFlag ;and skip this part here -SetoffF: lda FireworksCounter ;check fireworks counter - beq DrawFlagSetTimer ;if no fireworks left to go off, skip this part - bmi DrawFlagSetTimer ;if no fireworks set to go off, skip this part - lda #Fireworks - sta EnemyFrenzyBuffer ;otherwise set fireworks object in frenzy queue - -DrawStarFlag: - jsr RelativeEnemyPosition ;get relative coordinates of star flag - ldy Enemy_SprDataOffset,x ;get OAM data offset - ldx #$03 ;do four sprites -DSFLoop: lda Enemy_Rel_YPos ;get relative vertical coordinate - clc - adc StarFlagYPosAdder,x ;add Y coordinate adder data - sta Sprite_Y_Position,y ;store as Y coordinate - lda StarFlagTileData,x ;get tile number - sta Sprite_Tilenumber,y ;store as tile number - lda #$22 ;set palette and background priority bits - sta Sprite_Attributes,y ;store as attributes - lda Enemy_Rel_XPos ;get relative horizontal coordinate - clc - adc StarFlagXPosAdder,x ;add X coordinate adder data - sta Sprite_X_Position,y ;store as X coordinate - iny - iny ;increment OAM data offset four bytes - iny ;for next sprite - iny - dex ;move onto next sprite - bpl DSFLoop ;do this until all sprites are done - ldx ObjectOffset ;get enemy object offset and leave - rts - -DrawFlagSetTimer: - jsr DrawStarFlag ;do sub to draw star flag - lda #$06 - sta EnemyIntervalTimer,x ;set interval timer here - -IncrementSFTask2: - inc StarFlagTaskControl ;move onto next task - rts - -DelayToAreaEnd: - jsr DrawStarFlag ;do sub to draw star flag - lda EnemyIntervalTimer,x ;if interval timer set in previous task - bne StarFlagExit2 ;not yet expired, branch to leave - lda EventMusicBuffer ;if event music buffer empty, - beq IncrementSFTask2 ;branch to increment task - -StarFlagExit2: - rts ;otherwise leave - -;-------------------------------- -;$00 - used to store horizontal difference between player and piranha plant - -MovePiranhaPlant: - lda Enemy_State,x ;check enemy state - bne PutinPipe ;if set at all, branch to leave - lda EnemyFrameTimer,x ;check enemy's timer here - bne PutinPipe ;branch to end if not yet expired - lda PiranhaPlant_MoveFlag,x ;check movement flag - bne SetupToMovePPlant ;if moving, skip to part ahead - lda PiranhaPlant_Y_Speed,x ;if currently rising, branch - bmi ReversePlantSpeed ;to move enemy upwards out of pipe - jsr PlayerEnemyDiff ;get horizontal difference between player and - bpl ChkPlayerNearPipe ;piranha plant, and branch if enemy to right of player - lda $00 ;otherwise get saved horizontal difference - eor #$ff - clc ;and change to two's compliment - adc #$01 - sta $00 ;save as new horizontal difference - -ChkPlayerNearPipe: - lda $00 ;get saved horizontal difference - cmp #$21 - bcc PutinPipe ;if player within a certain distance, branch to leave - -ReversePlantSpeed: - lda PiranhaPlant_Y_Speed,x ;get vertical speed - eor #$ff - clc ;change to two's compliment - adc #$01 - sta PiranhaPlant_Y_Speed,x ;save as new vertical speed - inc PiranhaPlant_MoveFlag,x ;increment to set movement flag - -SetupToMovePPlant: - lda PiranhaPlantDownYPos,x ;get original vertical coordinate (lowest point) - ldy PiranhaPlant_Y_Speed,x ;get vertical speed - bpl RiseFallPiranhaPlant ;branch if moving downwards - lda PiranhaPlantUpYPos,x ;otherwise get other vertical coordinate (highest point) - -RiseFallPiranhaPlant: - sta $00 ;save vertical coordinate here - lda FrameCounter ;get frame counter - lsr - bcc PutinPipe ;branch to leave if d0 set (execute code every other frame) - lda TimerControl ;get master timer control - bne PutinPipe ;branch to leave if set (likely not necessary) - lda Enemy_Y_Position,x ;get current vertical coordinate - clc - adc PiranhaPlant_Y_Speed,x ;add vertical speed to move up or down - sta Enemy_Y_Position,x ;save as new vertical coordinate - cmp $00 ;compare against low or high coordinate - bne PutinPipe ;branch to leave if not yet reached - lda #$00 - sta PiranhaPlant_MoveFlag,x ;otherwise clear movement flag - lda #$40 - sta EnemyFrameTimer,x ;set timer to delay piranha plant movement - -PutinPipe: - lda #%00100000 ;set background priority bit in sprite - sta Enemy_SprAttrib,x ;attributes to give illusion of being inside pipe - rts ;then leave - -;------------------------------------------------------------------------------------- -;$07 - spinning speed - -FirebarSpin: - sta $07 ;save spinning speed here - lda FirebarSpinDirection,x ;check spinning direction - bne SpinCounterClockwise ;if moving counter-clockwise, branch to other part - ldy #$18 ;possibly residual ldy - lda FirebarSpinState_Low,x - clc ;add spinning speed to what would normally be - adc $07 ;the horizontal speed - sta FirebarSpinState_Low,x - lda FirebarSpinState_High,x ;add carry to what would normally be the vertical speed - adc #$00 - rts - -SpinCounterClockwise: - ldy #$08 ;possibly residual ldy - lda FirebarSpinState_Low,x - sec ;subtract spinning speed to what would normally be - sbc $07 ;the horizontal speed - sta FirebarSpinState_Low,x - lda FirebarSpinState_High,x ;add carry to what would normally be the vertical speed - sbc #$00 - rts - -;------------------------------------------------------------------------------------- -;$00 - used to hold collision flag, Y movement force + 5 or low byte of name table for rope -;$01 - used to hold high byte of name table for rope -;$02 - used to hold page location of rope - -BalancePlatform: - lda Enemy_Y_HighPos,x ;check high byte of vertical position - cmp #$03 - bne DoBPl - jmp EraseEnemyObject ;if far below screen, kill the object -DoBPl: lda Enemy_State,x ;get object's state (set to $ff or other platform offset) - bpl CheckBalPlatform ;if doing other balance platform, branch to leave - rts - -CheckBalPlatform: - tay ;save offset from state as Y - lda PlatformCollisionFlag,x ;get collision flag of platform - sta $00 ;store here - lda Enemy_MovingDir,x ;get moving direction - beq ChkForFall - jmp PlatformFall ;if set, jump here - -ChkForFall: - lda #$2d ;check if platform is above a certain point - cmp Enemy_Y_Position,x - bcc ChkOtherForFall ;if not, branch elsewhere - cpy $00 ;if collision flag is set to same value as - beq MakePlatformFall ;enemy state, branch to make platforms fall - clc - adc #$02 ;otherwise add 2 pixels to vertical position - sta Enemy_Y_Position,x ;of current platform and branch elsewhere - jmp StopPlatforms ;to make platforms stop - -MakePlatformFall: - jmp InitPlatformFall ;make platforms fall - -ChkOtherForFall: - cmp Enemy_Y_Position,y ;check if other platform is above a certain point - bcc ChkToMoveBalPlat ;if not, branch elsewhere - cpx $00 ;if collision flag is set to same value as - beq MakePlatformFall ;enemy state, branch to make platforms fall - clc - adc #$02 ;otherwise add 2 pixels to vertical position - sta Enemy_Y_Position,y ;of other platform and branch elsewhere - jmp StopPlatforms ;jump to stop movement and do not return - -ChkToMoveBalPlat: - lda Enemy_Y_Position,x ;save vertical position to stack - pha - lda PlatformCollisionFlag,x ;get collision flag - bpl ColFlg ;branch if collision - lda Enemy_Y_MoveForce,x - clc ;add $05 to contents of moveforce, whatever they be - adc #$05 - sta $00 ;store here - lda Enemy_Y_Speed,x - adc #$00 ;add carry to vertical speed - bmi PlatDn ;branch if moving downwards - bne PlatUp ;branch elsewhere if moving upwards - lda $00 - cmp #$0b ;check if there's still a little force left - bcc PlatSt ;if not enough, branch to stop movement - bcs PlatUp ;otherwise keep branch to move upwards -ColFlg: cmp ObjectOffset ;if collision flag matches - beq PlatDn ;current enemy object offset, branch -PlatUp: jsr MovePlatformUp ;do a sub to move upwards - jmp DoOtherPlatform ;jump ahead to remaining code -PlatSt: jsr StopPlatforms ;do a sub to stop movement - jmp DoOtherPlatform ;jump ahead to remaining code -PlatDn: jsr MovePlatformDown ;do a sub to move downwards - -DoOtherPlatform: - ldy Enemy_State,x ;get offset of other platform - pla ;get old vertical coordinate from stack - sec - sbc Enemy_Y_Position,x ;get difference of old vs. new coordinate - clc - adc Enemy_Y_Position,y ;add difference to vertical coordinate of other - sta Enemy_Y_Position,y ;platform to move it in the opposite direction - lda PlatformCollisionFlag,x ;if no collision, skip this part here - bmi DrawEraseRope - tax ;put offset which collision occurred here - jsr PositionPlayerOnVPlat ;and use it to position player accordingly - -DrawEraseRope: - ldy ObjectOffset ;get enemy object offset - lda Enemy_Y_Speed,y ;check to see if current platform is - ora Enemy_Y_MoveForce,y ;moving at all - beq ExitRp ;if not, skip all of this and branch to leave - ldx VRAM_Buffer1_Offset ;get vram buffer offset - cpx #$20 ;if offset beyond a certain point, go ahead - bcs ExitRp ;and skip this, branch to leave - lda Enemy_Y_Speed,y - pha ;save two copies of vertical speed to stack - pha - jsr SetupPlatformRope ;do a sub to figure out where to put new bg tiles - lda $01 ;write name table address to vram buffer - sta VRAM_Buffer1,x ;first the high byte, then the low - lda $00 - sta VRAM_Buffer1+1,x - lda #$02 ;set length for 2 bytes - sta VRAM_Buffer1+2,x - lda Enemy_Y_Speed,y ;if platform moving upwards, branch - bmi EraseR1 ;to do something else - lda #$a2 - sta VRAM_Buffer1+3,x ;otherwise put tile numbers for left - lda #$a3 ;and right sides of rope in vram buffer - sta VRAM_Buffer1+4,x - jmp OtherRope ;jump to skip this part -EraseR1: lda #$24 ;put blank tiles in vram buffer - sta VRAM_Buffer1+3,x ;to erase rope - sta VRAM_Buffer1+4,x - -OtherRope: - lda Enemy_State,y ;get offset of other platform from state - tay ;use as Y here - pla ;pull second copy of vertical speed from stack - eor #$ff ;invert bits to reverse speed - jsr SetupPlatformRope ;do sub again to figure out where to put bg tiles - lda $01 ;write name table address to vram buffer - sta VRAM_Buffer1+5,x ;this time we're doing putting tiles for - lda $00 ;the other platform - sta VRAM_Buffer1+6,x - lda #$02 - sta VRAM_Buffer1+7,x ;set length again for 2 bytes - pla ;pull first copy of vertical speed from stack - bpl EraseR2 ;if moving upwards (note inversion earlier), skip this - lda #$a2 - sta VRAM_Buffer1+8,x ;otherwise put tile numbers for left - lda #$a3 ;and right sides of rope in vram - sta VRAM_Buffer1+9,x ;transfer buffer - jmp EndRp ;jump to skip this part -EraseR2: lda #$24 ;put blank tiles in vram buffer - sta VRAM_Buffer1+8,x ;to erase rope - sta VRAM_Buffer1+9,x -EndRp: lda #$00 ;put null terminator at the end - sta VRAM_Buffer1+10,x - lda VRAM_Buffer1_Offset ;add ten bytes to the vram buffer offset - clc ;and store - adc #10 - sta VRAM_Buffer1_Offset -ExitRp: ldx ObjectOffset ;get enemy object buffer offset and leave - rts - -SetupPlatformRope: - pha ;save second/third copy to stack - lda Enemy_X_Position,y ;get horizontal coordinate - clc - adc #$08 ;add eight pixels - ldx SecondaryHardMode ;if secondary hard mode flag set, - bne GetLRp ;use coordinate as-is - clc - adc #$10 ;otherwise add sixteen more pixels -GetLRp: pha ;save modified horizontal coordinate to stack - lda Enemy_PageLoc,y - adc #$00 ;add carry to page location - sta $02 ;and save here - pla ;pull modified horizontal coordinate - and #%11110000 ;from the stack, mask out low nybble - lsr ;and shift three bits to the right - lsr - lsr - sta $00 ;store result here as part of name table low byte - ldx Enemy_Y_Position,y ;get vertical coordinate - pla ;get second/third copy of vertical speed from stack - bpl GetHRp ;skip this part if moving downwards or not at all - txa - clc - adc #$08 ;add eight to vertical coordinate and - tax ;save as X -GetHRp: txa ;move vertical coordinate to A - ldx VRAM_Buffer1_Offset ;get vram buffer offset - asl - rol ;rotate d7 to d0 and d6 into carry - pha ;save modified vertical coordinate to stack - rol ;rotate carry to d0, thus d7 and d6 are at 2 LSB - and #%00000011 ;mask out all bits but d7 and d6, then set - ora #%00100000 ;d5 to get appropriate high byte of name table - sta $01 ;address, then store - lda $02 ;get saved page location from earlier - and #$01 ;mask out all but LSB - asl - asl ;shift twice to the left and save with the - ora $01 ;rest of the bits of the high byte, to get - sta $01 ;the proper name table and the right place on it - pla ;get modified vertical coordinate from stack - and #%11100000 ;mask out low nybble and LSB of high nybble - clc - adc $00 ;add to horizontal part saved here - sta $00 ;save as name table low byte - lda Enemy_Y_Position,y - cmp #$e8 ;if vertical position not below the - bcc ExPRp ;bottom of the screen, we're done, branch to leave - lda $00 - and #%10111111 ;mask out d6 of low byte of name table address - sta $00 -ExPRp: rts ;leave! - -InitPlatformFall: - tya ;move offset of other platform from Y to X - tax - jsr GetEnemyOffscreenBits ;get offscreen bits - lda #$06 - jsr SetupFloateyNumber ;award 1000 points to player - lda Player_Rel_XPos - sta FloateyNum_X_Pos,x ;put floatey number coordinates where player is - lda Player_Y_Position - sta FloateyNum_Y_Pos,x - lda #$01 ;set moving direction as flag for - sta Enemy_MovingDir,x ;falling platforms - -StopPlatforms: - jsr InitVStf ;initialize vertical speed and low byte - sta Enemy_Y_Speed,y ;for both platforms and leave - sta Enemy_Y_MoveForce,y - rts - -PlatformFall: - tya ;save offset for other platform to stack - pha - jsr MoveFallingPlatform ;make current platform fall - pla - tax ;pull offset from stack and save to X - jsr MoveFallingPlatform ;make other platform fall - ldx ObjectOffset - lda PlatformCollisionFlag,x ;if player not standing on either platform, - bmi ExPF ;skip this part - tax ;transfer collision flag offset as offset to X - jsr PositionPlayerOnVPlat ;and position player appropriately -ExPF: ldx ObjectOffset ;get enemy object buffer offset and leave - rts - -;-------------------------------- - -YMovingPlatform: - lda Enemy_Y_Speed,x ;if platform moving up or down, skip ahead to - ora Enemy_Y_MoveForce,x ;check on other position - bne ChkYCenterPos - sta Enemy_YMF_Dummy,x ;initialize dummy variable - lda Enemy_Y_Position,x - cmp YPlatformTopYPos,x ;if current vertical position => top position, branch - bcs ChkYCenterPos ;ahead of all this - lda FrameCounter - and #%00000111 ;check for every eighth frame - bne SkipIY - inc Enemy_Y_Position,x ;increase vertical position every eighth frame -SkipIY: jmp ChkYPCollision ;skip ahead to last part - -ChkYCenterPos: - lda Enemy_Y_Position,x ;if current vertical position < central position, branch - cmp YPlatformCenterYPos,x ;to slow ascent/move downwards - bcc YMDown - jsr MovePlatformUp ;otherwise start slowing descent/moving upwards - jmp ChkYPCollision -YMDown: jsr MovePlatformDown ;start slowing ascent/moving downwards - -ChkYPCollision: - lda PlatformCollisionFlag,x ;if collision flag not set here, branch - bmi ExYPl ;to leave - jsr PositionPlayerOnVPlat ;otherwise position player appropriately -ExYPl: rts ;leave - -;-------------------------------- -;$00 - used as adder to position player hotizontally - -XMovingPlatform: - lda #$0e ;load preset maximum value for secondary counter - jsr XMoveCntr_Platform ;do a sub to increment counters for movement - jsr MoveWithXMCntrs ;do a sub to move platform accordingly, and return value - lda PlatformCollisionFlag,x ;if no collision with player, - bmi ExXMP ;branch ahead to leave - -PositionPlayerOnHPlat: - lda Player_X_Position - clc ;add saved value from second subroutine to - adc $00 ;current player's position to position - sta Player_X_Position ;player accordingly in horizontal position - lda Player_PageLoc ;get player's page location - ldy $00 ;check to see if saved value here is positive or negative - bmi PPHSubt ;if negative, branch to subtract - adc #$00 ;otherwise add carry to page location - jmp SetPVar ;jump to skip subtraction -PPHSubt: sbc #$00 ;subtract borrow from page location -SetPVar: sta Player_PageLoc ;save result to player's page location - sty Platform_X_Scroll ;put saved value from second sub here to be used later - jsr PositionPlayerOnVPlat ;position player vertically and appropriately -ExXMP: rts ;and we are done here - -;-------------------------------- - -DropPlatform: - lda PlatformCollisionFlag,x ;if no collision between platform and player - bmi ExDPl ;occurred, just leave without moving anything - jsr MoveDropPlatform ;otherwise do a sub to move platform down very quickly - jsr PositionPlayerOnVPlat ;do a sub to position player appropriately -ExDPl: rts ;leave - -;-------------------------------- -;$00 - residual value from sub - -RightPlatform: - jsr MoveEnemyHorizontally ;move platform with current horizontal speed, if any - sta $00 ;store saved value here (residual code) - lda PlatformCollisionFlag,x ;check collision flag, if no collision between player - bmi ExRPl ;and platform, branch ahead, leave speed unaltered - lda #$10 - sta Enemy_X_Speed,x ;otherwise set new speed (gets moving if motionless) - jsr PositionPlayerOnHPlat ;use saved value from earlier sub to position player -ExRPl: rts ;then leave - -;-------------------------------- - -MoveLargeLiftPlat: - jsr MoveLiftPlatforms ;execute common to all large and small lift platforms - jmp ChkYPCollision ;branch to position player correctly - -MoveSmallPlatform: - jsr MoveLiftPlatforms ;execute common to all large and small lift platforms - jmp ChkSmallPlatCollision ;branch to position player correctly - -MoveLiftPlatforms: - lda TimerControl ;if master timer control set, skip all of this - bne ExLiftP ;and branch to leave - lda Enemy_YMF_Dummy,x - clc ;add contents of movement amount to whatever's here - adc Enemy_Y_MoveForce,x - sta Enemy_YMF_Dummy,x - lda Enemy_Y_Position,x ;add whatever vertical speed is set to current - adc Enemy_Y_Speed,x ;vertical position plus carry to move up or down - sta Enemy_Y_Position,x ;and then leave - rts - -ChkSmallPlatCollision: - lda PlatformCollisionFlag,x ;get bounding box counter saved in collision flag - beq ExLiftP ;if none found, leave player position alone - jsr PositionPlayerOnS_Plat ;use to position player correctly -ExLiftP: rts ;then leave - -;------------------------------------------------------------------------------------- -;$00 - page location of extended left boundary -;$01 - extended left boundary position -;$02 - page location of extended right boundary -;$03 - extended right boundary position - -OffscreenBoundsCheck: - lda Enemy_ID,x ;check for cheep-cheep object - cmp #FlyingCheepCheep ;branch to leave if found - beq ExScrnBd - lda ScreenLeft_X_Pos ;get horizontal coordinate for left side of screen - ldy Enemy_ID,x - cpy #HammerBro ;check for hammer bro object - beq LimitB - cpy #PiranhaPlant ;check for piranha plant object - bne ExtendLB ;these two will be erased sooner than others if too far left -LimitB: adc #$38 ;add 56 pixels to coordinate if hammer bro or piranha plant -ExtendLB: sbc #$48 ;subtract 72 pixels regardless of enemy object - sta $01 ;store result here - lda ScreenLeft_PageLoc - sbc #$00 ;subtract borrow from page location of left side - sta $00 ;store result here - lda ScreenRight_X_Pos ;add 72 pixels to the right side horizontal coordinate - adc #$48 - sta $03 ;store result here - lda ScreenRight_PageLoc - adc #$00 ;then add the carry to the page location - sta $02 ;and store result here - lda Enemy_X_Position,x ;compare horizontal coordinate of the enemy object - cmp $01 ;to modified horizontal left edge coordinate to get carry - lda Enemy_PageLoc,x - sbc $00 ;then subtract it from the page coordinate of the enemy object - bmi TooFar ;if enemy object is too far left, branch to erase it - lda Enemy_X_Position,x ;compare horizontal coordinate of the enemy object - cmp $03 ;to modified horizontal right edge coordinate to get carry - lda Enemy_PageLoc,x - sbc $02 ;then subtract it from the page coordinate of the enemy object - bmi ExScrnBd ;if enemy object is on the screen, leave, do not erase enemy - lda Enemy_State,x ;if at this point, enemy is offscreen to the right, so check - cmp #HammerBro ;if in state used by spiny's egg, do not erase - beq ExScrnBd - cpy #PiranhaPlant ;if piranha plant, do not erase - beq ExScrnBd - cpy #FlagpoleFlagObject ;if flagpole flag, do not erase - beq ExScrnBd - cpy #StarFlagObject ;if star flag, do not erase - beq ExScrnBd - cpy #JumpspringObject ;if jumpspring, do not erase - beq ExScrnBd ;erase all others too far to the right -TooFar: jsr EraseEnemyObject ;erase object if necessary -ExScrnBd: rts ;leave - -;------------------------------------------------------------------------------------- - -;some unused space - .db $ff, $ff, $ff - -;------------------------------------------------------------------------------------- -;$01 - enemy buffer offset - -FireballEnemyCollision: - lda Fireball_State,x ;check to see if fireball state is set at all - beq ExitFBallEnemy ;branch to leave if not - asl - bcs ExitFBallEnemy ;branch to leave also if d7 in state is set - lda FrameCounter - lsr ;get LSB of frame counter - bcs ExitFBallEnemy ;branch to leave if set (do routine every other frame) - txa - asl ;multiply fireball offset by four - asl - clc - adc #$1c ;then add $1c or 28 bytes to it - tay ;to use fireball's bounding box coordinates - ldx #$04 - -FireballEnemyCDLoop: - stx $01 ;store enemy object offset here - tya - pha ;push fireball offset to the stack - lda Enemy_State,x - and #%00100000 ;check to see if d5 is set in enemy state - bne NoFToECol ;if so, skip to next enemy slot - lda Enemy_Flag,x ;check to see if buffer flag is set - beq NoFToECol ;if not, skip to next enemy slot - lda Enemy_ID,x ;check enemy identifier - cmp #$24 - bcc GoombaDie ;if < $24, branch to check further - cmp #$2b - bcc NoFToECol ;if in range $24-$2a, skip to next enemy slot -GoombaDie: cmp #Goomba ;check for goomba identifier - bne NotGoomba ;if not found, continue with code - lda Enemy_State,x ;otherwise check for defeated state - cmp #$02 ;if stomped or otherwise defeated, - bcs NoFToECol ;skip to next enemy slot -NotGoomba: lda EnemyOffscrBitsMasked,x ;if any masked offscreen bits set, - bne NoFToECol ;skip to next enemy slot - txa - asl ;otherwise multiply enemy offset by four - asl - clc - adc #$04 ;add 4 bytes to it - tax ;to use enemy's bounding box coordinates - jsr SprObjectCollisionCore ;do fireball-to-enemy collision detection - ldx ObjectOffset ;return fireball's original offset - bcc NoFToECol ;if carry clear, no collision, thus do next enemy slot - lda #%10000000 - sta Fireball_State,x ;set d7 in enemy state - ldx $01 ;get enemy offset - jsr HandleEnemyFBallCol ;jump to handle fireball to enemy collision -NoFToECol: pla ;pull fireball offset from stack - tay ;put it in Y - ldx $01 ;get enemy object offset - dex ;decrement it - bpl FireballEnemyCDLoop ;loop back until collision detection done on all enemies - -ExitFBallEnemy: - ldx ObjectOffset ;get original fireball offset and leave - rts - -BowserIdentities: - .db Goomba, GreenKoopa, BuzzyBeetle, Spiny, Lakitu, Bloober, HammerBro, Bowser - -HandleEnemyFBallCol: - jsr RelativeEnemyPosition ;get relative coordinate of enemy - ldx $01 ;get current enemy object offset - lda Enemy_Flag,x ;check buffer flag for d7 set - bpl ChkBuzzyBeetle ;branch if not set to continue - and #%00001111 ;otherwise mask out high nybble and - tax ;use low nybble as enemy offset - lda Enemy_ID,x - cmp #Bowser ;check enemy identifier for bowser - beq HurtBowser ;branch if found - ldx $01 ;otherwise retrieve current enemy offset - -ChkBuzzyBeetle: - lda Enemy_ID,x - cmp #BuzzyBeetle ;check for buzzy beetle - beq ExHCF ;branch if found to leave (buzzy beetles fireproof) - cmp #Bowser ;check for bowser one more time (necessary if d7 of flag was clear) - bne ChkOtherEnemies ;if not found, branch to check other enemies - -HurtBowser: - dec BowserHitPoints ;decrement bowser's hit points - bne ExHCF ;if bowser still has hit points, branch to leave - jsr InitVStf ;otherwise do sub to init vertical speed and movement force - sta Enemy_X_Speed,x ;initialize horizontal speed - sta EnemyFrenzyBuffer ;init enemy frenzy buffer - lda #$fe - sta Enemy_Y_Speed,x ;set vertical speed to make defeated bowser jump a little - ldy WorldNumber ;use world number as offset - lda BowserIdentities,y ;get enemy identifier to replace bowser with - sta Enemy_ID,x ;set as new enemy identifier - lda #$20 ;set A to use starting value for state - cpy #$03 ;check to see if using offset of 3 or more - bcs SetDBSte ;branch if so - ora #$03 ;otherwise add 3 to enemy state -SetDBSte: sta Enemy_State,x ;set defeated enemy state - lda #Sfx_BowserFall - sta Square2SoundQueue ;load bowser defeat sound - ldx $01 ;get enemy offset - lda #$09 ;award 5000 points to player for defeating bowser - bne EnemySmackScore ;unconditional branch to award points - -ChkOtherEnemies: - cmp #BulletBill_FrenzyVar - beq ExHCF ;branch to leave if bullet bill (frenzy variant) - cmp #Podoboo - beq ExHCF ;branch to leave if podoboo - cmp #$15 - bcs ExHCF ;branch to leave if identifier => $15 - -ShellOrBlockDefeat: - lda Enemy_ID,x ;check for piranha plant - cmp #PiranhaPlant - bne StnE ;branch if not found - lda Enemy_Y_Position,x - adc #$18 ;add 24 pixels to enemy object's vertical position - sta Enemy_Y_Position,x -StnE: jsr ChkToStunEnemies ;do yet another sub - lda Enemy_State,x - and #%00011111 ;mask out 2 MSB of enemy object's state - ora #%00100000 ;set d5 to defeat enemy and save as new state - sta Enemy_State,x - lda #$02 ;award 200 points by default - ldy Enemy_ID,x ;check for hammer bro - cpy #HammerBro - bne GoombaPoints ;branch if not found - lda #$06 ;award 1000 points for hammer bro - -GoombaPoints: - cpy #Goomba ;check for goomba - bne EnemySmackScore ;branch if not found - lda #$01 ;award 100 points for goomba - -EnemySmackScore: - jsr SetupFloateyNumber ;update necessary score variables - lda #Sfx_EnemySmack ;play smack enemy sound - sta Square1SoundQueue -ExHCF: rts ;and now let's leave - -;------------------------------------------------------------------------------------- - -PlayerHammerCollision: - lda FrameCounter ;get frame counter - lsr ;shift d0 into carry - bcc ExPHC ;branch to leave if d0 not set to execute every other frame - lda TimerControl ;if either master timer control - ora Misc_OffscreenBits ;or any offscreen bits for hammer are set, - bne ExPHC ;branch to leave - txa - asl ;multiply misc object offset by four - asl - clc - adc #$24 ;add 36 or $24 bytes to get proper offset - tay ;for misc object bounding box coordinates - jsr PlayerCollisionCore ;do player-to-hammer collision detection - ldx ObjectOffset ;get misc object offset - bcc ClHCol ;if no collision, then branch - lda Misc_Collision_Flag,x ;otherwise read collision flag - bne ExPHC ;if collision flag already set, branch to leave - lda #$01 - sta Misc_Collision_Flag,x ;otherwise set collision flag now - lda Misc_X_Speed,x - eor #$ff ;get two's compliment of - clc ;hammer's horizontal speed - adc #$01 - sta Misc_X_Speed,x ;set to send hammer flying the opposite direction - lda StarInvincibleTimer ;if star mario invincibility timer set, - bne ExPHC ;branch to leave - jmp InjurePlayer ;otherwise jump to hurt player, do not return -ClHCol: lda #$00 ;clear collision flag - sta Misc_Collision_Flag,x -ExPHC: rts - -;------------------------------------------------------------------------------------- - -HandlePowerUpCollision: - jsr EraseEnemyObject ;erase the power-up object - lda #$06 - jsr SetupFloateyNumber ;award 1000 points to player by default - lda #Sfx_PowerUpGrab - sta Square2SoundQueue ;play the power-up sound - lda PowerUpType ;check power-up type - cmp #$02 - bcc Shroom_Flower_PUp ;if mushroom or fire flower, branch - cmp #$03 - beq SetFor1Up ;if 1-up mushroom, branch - lda #$23 ;otherwise set star mario invincibility - sta StarInvincibleTimer ;timer, and load the star mario music - lda #StarPowerMusic ;into the area music queue, then leave - sta AreaMusicQueue - rts - -Shroom_Flower_PUp: - lda PlayerStatus ;if player status = small, branch - beq UpToSuper - cmp #$01 ;if player status not super, leave - bne NoPUp - ldx ObjectOffset ;get enemy offset, not necessary - lda #$02 ;set player status to fiery - sta PlayerStatus - jsr GetPlayerColors ;run sub to change colors of player - ldx ObjectOffset ;get enemy offset again, and again not necessary - lda #$0c ;set value to be used by subroutine tree (fiery) - jmp UpToFiery ;jump to set values accordingly - -SetFor1Up: - lda #$0b ;change 1000 points into 1-up instead - sta FloateyNum_Control,x ;and then leave - rts - -UpToSuper: - lda #$01 ;set player status to super - sta PlayerStatus - lda #$09 ;set value to be used by subroutine tree (super) - -UpToFiery: - ldy #$00 ;set value to be used as new player state - jsr SetPRout ;set values to stop certain things in motion -NoPUp: rts - -;-------------------------------- - -ResidualXSpdData: - .db $18, $e8 - -KickedShellXSpdData: - .db $30, $d0 - -DemotedKoopaXSpdData: - .db $08, $f8 - -PlayerEnemyCollision: - lda FrameCounter ;check counter for d0 set - lsr - bcs NoPUp ;if set, branch to leave - jsr CheckPlayerVertical ;if player object is completely offscreen or - bcs NoPECol ;if down past 224th pixel row, branch to leave - lda EnemyOffscrBitsMasked,x ;if current enemy is offscreen by any amount, - bne NoPECol ;go ahead and branch to leave - lda GameEngineSubroutine - cmp #$08 ;if not set to run player control routine - bne NoPECol ;on next frame, branch to leave - lda Enemy_State,x - and #%00100000 ;if enemy state has d5 set, branch to leave - bne NoPECol - jsr GetEnemyBoundBoxOfs ;get bounding box offset for current enemy object - jsr PlayerCollisionCore ;do collision detection on player vs. enemy - ldx ObjectOffset ;get enemy object buffer offset - bcs CheckForPUpCollision ;if collision, branch past this part here - lda Enemy_CollisionBits,x - and #%11111110 ;otherwise, clear d0 of current enemy object's - sta Enemy_CollisionBits,x ;collision bit -NoPECol: rts - -CheckForPUpCollision: - ldy Enemy_ID,x - cpy #PowerUpObject ;check for power-up object - bne EColl ;if not found, branch to next part - jmp HandlePowerUpCollision ;otherwise, unconditional jump backwards -EColl: lda StarInvincibleTimer ;if star mario invincibility timer expired, - beq HandlePECollisions ;perform task here, otherwise kill enemy like - jmp ShellOrBlockDefeat ;hit with a shell, or from beneath - -KickedShellPtsData: - .db $0a, $06, $04 - -HandlePECollisions: - lda Enemy_CollisionBits,x ;check enemy collision bits for d0 set - and #%00000001 ;or for being offscreen at all - ora EnemyOffscrBitsMasked,x - bne ExPEC ;branch to leave if either is true - lda #$01 - ora Enemy_CollisionBits,x ;otherwise set d0 now - sta Enemy_CollisionBits,x - cpy #Spiny ;branch if spiny - beq ChkForPlayerInjury - cpy #PiranhaPlant ;branch if piranha plant - beq InjurePlayer - cpy #Podoboo ;branch if podoboo - beq InjurePlayer - cpy #BulletBill_CannonVar ;branch if bullet bill - beq ChkForPlayerInjury - cpy #$15 ;branch if object => $15 - bcs InjurePlayer - lda AreaType ;branch if water type level - beq InjurePlayer - lda Enemy_State,x ;branch if d7 of enemy state was set - asl - bcs ChkForPlayerInjury - lda Enemy_State,x ;mask out all but 3 LSB of enemy state - and #%00000111 - cmp #$02 ;branch if enemy is in normal or falling state - bcc ChkForPlayerInjury - lda Enemy_ID,x ;branch to leave if goomba in defeated state - cmp #Goomba - beq ExPEC - lda #Sfx_EnemySmack ;play smack enemy sound - sta Square1SoundQueue - lda Enemy_State,x ;set d7 in enemy state, thus become moving shell - ora #%10000000 - sta Enemy_State,x - jsr EnemyFacePlayer ;set moving direction and get offset - lda KickedShellXSpdData,y ;load and set horizontal speed data with offset - sta Enemy_X_Speed,x - lda #$03 ;add three to whatever the stomp counter contains - clc ;to give points for kicking the shell - adc StompChainCounter - ldy EnemyIntervalTimer,x ;check shell enemy's timer - cpy #$03 ;if above a certain point, branch using the points - bcs KSPts ;data obtained from the stomp counter + 3 - lda KickedShellPtsData,y ;otherwise, set points based on proximity to timer expiration -KSPts: jsr SetupFloateyNumber ;set values for floatey number now -ExPEC: rts ;leave!!! - -ChkForPlayerInjury: - lda Player_Y_Speed ;check player's vertical speed - bmi ChkInj ;perform procedure below if player moving upwards - bne EnemyStomped ;or not at all, and branch elsewhere if moving downwards -ChkInj: lda Enemy_ID,x ;branch if enemy object < $07 - cmp #Bloober - bcc ChkETmrs - lda Player_Y_Position ;add 12 pixels to player's vertical position - clc - adc #$0c - cmp Enemy_Y_Position,x ;compare modified player's position to enemy's position - bcc EnemyStomped ;branch if this player's position above (less than) enemy's -ChkETmrs: lda StompTimer ;check stomp timer - bne EnemyStomped ;branch if set - lda InjuryTimer ;check to see if injured invincibility timer still - bne ExInjColRoutines ;counting down, and branch elsewhere to leave if so - lda Player_Rel_XPos - cmp Enemy_Rel_XPos ;if player's relative position to the left of enemy's - bcc TInjE ;relative position, branch here - jmp ChkEnemyFaceRight ;otherwise do a jump here -TInjE: lda Enemy_MovingDir,x ;if enemy moving towards the left, - cmp #$01 ;branch, otherwise do a jump here - bne InjurePlayer ;to turn the enemy around - jmp LInj - -InjurePlayer: - lda InjuryTimer ;check again to see if injured invincibility timer is - bne ExInjColRoutines ;at zero, and branch to leave if so - -ForceInjury: - ldx PlayerStatus ;check player's status - beq KillPlayer ;branch if small - sta PlayerStatus ;otherwise set player's status to small - lda #$08 - sta InjuryTimer ;set injured invincibility timer - asl - sta Square1SoundQueue ;play pipedown/injury sound - jsr GetPlayerColors ;change player's palette if necessary - lda #$0a ;set subroutine to run on next frame -SetKRout: ldy #$01 ;set new player state -SetPRout: sta GameEngineSubroutine ;load new value to run subroutine on next frame - sty Player_State ;store new player state - ldy #$ff - sty TimerControl ;set master timer control flag to halt timers - iny - sty ScrollAmount ;initialize scroll speed - -ExInjColRoutines: - ldx ObjectOffset ;get enemy offset and leave - rts - -KillPlayer: - stx Player_X_Speed ;halt player's horizontal movement by initializing speed - inx - stx EventMusicQueue ;set event music queue to death music - lda #$fc - sta Player_Y_Speed ;set new vertical speed - lda #$0b ;set subroutine to run on next frame - bne SetKRout ;branch to set player's state and other things - -StompedEnemyPtsData: - .db $02, $06, $05, $06 - -EnemyStomped: - lda Enemy_ID,x ;check for spiny, branch to hurt player - cmp #Spiny ;if found - beq InjurePlayer - lda #Sfx_EnemyStomp ;otherwise play stomp/swim sound - sta Square1SoundQueue - lda Enemy_ID,x - ldy #$00 ;initialize points data offset for stomped enemies - cmp #FlyingCheepCheep ;branch for cheep-cheep - beq EnemyStompedPts - cmp #BulletBill_FrenzyVar ;branch for either bullet bill object - beq EnemyStompedPts - cmp #BulletBill_CannonVar - beq EnemyStompedPts - cmp #Podoboo ;branch for podoboo (this branch is logically impossible - beq EnemyStompedPts ;for cpu to take due to earlier checking of podoboo) - iny ;increment points data offset - cmp #HammerBro ;branch for hammer bro - beq EnemyStompedPts - iny ;increment points data offset - cmp #Lakitu ;branch for lakitu - beq EnemyStompedPts - iny ;increment points data offset - cmp #Bloober ;branch if NOT bloober - bne ChkForDemoteKoopa - -EnemyStompedPts: - lda StompedEnemyPtsData,y ;load points data using offset in Y - jsr SetupFloateyNumber ;run sub to set floatey number controls - lda Enemy_MovingDir,x - pha ;save enemy movement direction to stack - jsr SetStun ;run sub to kill enemy - pla - sta Enemy_MovingDir,x ;return enemy movement direction from stack - lda #%00100000 - sta Enemy_State,x ;set d5 in enemy state - jsr InitVStf ;nullify vertical speed, physics-related thing, - sta Enemy_X_Speed,x ;and horizontal speed - lda #$fd ;set player's vertical speed, to give bounce - sta Player_Y_Speed - rts - -ChkForDemoteKoopa: - cmp #$09 ;branch elsewhere if enemy object < $09 - bcc HandleStompedShellE - and #%00000001 ;demote koopa paratroopas to ordinary troopas - sta Enemy_ID,x - ldy #$00 ;return enemy to normal state - sty Enemy_State,x - lda #$03 ;award 400 points to the player - jsr SetupFloateyNumber - jsr InitVStf ;nullify physics-related thing and vertical speed - jsr EnemyFacePlayer ;turn enemy around if necessary - lda DemotedKoopaXSpdData,y - sta Enemy_X_Speed,x ;set appropriate moving speed based on direction - jmp SBnce ;then move onto something else - -RevivalRateData: - .db $10, $0b - -HandleStompedShellE: - lda #$04 ;set defeated state for enemy - sta Enemy_State,x - inc StompChainCounter ;increment the stomp counter - lda StompChainCounter ;add whatever is in the stomp counter - clc ;to whatever is in the stomp timer - adc StompTimer - jsr SetupFloateyNumber ;award points accordingly - inc StompTimer ;increment stomp timer of some sort - ldy PrimaryHardMode ;check primary hard mode flag - lda RevivalRateData,y ;load timer setting according to flag - sta EnemyIntervalTimer,x ;set as enemy timer to revive stomped enemy -SBnce: lda #$fc ;set player's vertical speed for bounce - sta Player_Y_Speed ;and then leave!!! - rts - -ChkEnemyFaceRight: - lda Enemy_MovingDir,x ;check to see if enemy is moving to the right - cmp #$01 - bne LInj ;if not, branch - jmp InjurePlayer ;otherwise go back to hurt player -LInj: jsr EnemyTurnAround ;turn the enemy around, if necessary - jmp InjurePlayer ;go back to hurt player - - -EnemyFacePlayer: - ldy #$01 ;set to move right by default - jsr PlayerEnemyDiff ;get horizontal difference between player and enemy - bpl SFcRt ;if enemy is to the right of player, do not increment - iny ;otherwise, increment to set to move to the left -SFcRt: sty Enemy_MovingDir,x ;set moving direction here - dey ;then decrement to use as a proper offset - rts - -SetupFloateyNumber: - sta FloateyNum_Control,x ;set number of points control for floatey numbers - lda #$30 - sta FloateyNum_Timer,x ;set timer for floatey numbers - lda Enemy_Y_Position,x - sta FloateyNum_Y_Pos,x ;set vertical coordinate - lda Enemy_Rel_XPos - sta FloateyNum_X_Pos,x ;set horizontal coordinate and leave -ExSFN: rts - -;------------------------------------------------------------------------------------- -;$01 - used to hold enemy offset for second enemy - -SetBitsMask: - .db %10000000, %01000000, %00100000, %00010000, %00001000, %00000100, %00000010 - -ClearBitsMask: - .db %01111111, %10111111, %11011111, %11101111, %11110111, %11111011, %11111101 - -EnemiesCollision: - lda FrameCounter ;check counter for d0 set - lsr - bcc ExSFN ;if d0 not set, leave - lda AreaType - beq ExSFN ;if water area type, leave - lda Enemy_ID,x - cmp #$15 ;if enemy object => $15, branch to leave - bcs ExitECRoutine - cmp #Lakitu ;if lakitu, branch to leave - beq ExitECRoutine - cmp #PiranhaPlant ;if piranha plant, branch to leave - beq ExitECRoutine - lda EnemyOffscrBitsMasked,x ;if masked offscreen bits nonzero, branch to leave - bne ExitECRoutine - jsr GetEnemyBoundBoxOfs ;otherwise, do sub, get appropriate bounding box offset for - dex ;first enemy we're going to compare, then decrement for second - bmi ExitECRoutine ;branch to leave if there are no other enemies -ECLoop: stx $01 ;save enemy object buffer offset for second enemy here - tya ;save first enemy's bounding box offset to stack - pha - lda Enemy_Flag,x ;check enemy object enable flag - beq ReadyNextEnemy ;branch if flag not set - lda Enemy_ID,x - cmp #$15 ;check for enemy object => $15 - bcs ReadyNextEnemy ;branch if true - cmp #Lakitu - beq ReadyNextEnemy ;branch if enemy object is lakitu - cmp #PiranhaPlant - beq ReadyNextEnemy ;branch if enemy object is piranha plant - lda EnemyOffscrBitsMasked,x - bne ReadyNextEnemy ;branch if masked offscreen bits set - txa ;get second enemy object's bounding box offset - asl ;multiply by four, then add four - asl - clc - adc #$04 - tax ;use as new contents of X - jsr SprObjectCollisionCore ;do collision detection using the two enemies here - ldx ObjectOffset ;use first enemy offset for X - ldy $01 ;use second enemy offset for Y - bcc NoEnemyCollision ;if carry clear, no collision, branch ahead of this - lda Enemy_State,x - ora Enemy_State,y ;check both enemy states for d7 set - and #%10000000 - bne YesEC ;branch if at least one of them is set - lda Enemy_CollisionBits,y ;load first enemy's collision-related bits - and SetBitsMask,x ;check to see if bit connected to second enemy is - bne ReadyNextEnemy ;already set, and move onto next enemy slot if set - lda Enemy_CollisionBits,y - ora SetBitsMask,x ;if the bit is not set, set it now - sta Enemy_CollisionBits,y -YesEC: jsr ProcEnemyCollisions ;react according to the nature of collision - jmp ReadyNextEnemy ;move onto next enemy slot - -NoEnemyCollision: - lda Enemy_CollisionBits,y ;load first enemy's collision-related bits - and ClearBitsMask,x ;clear bit connected to second enemy - sta Enemy_CollisionBits,y ;then move onto next enemy slot - -ReadyNextEnemy: - pla ;get first enemy's bounding box offset from the stack - tay ;use as Y again - ldx $01 ;get and decrement second enemy's object buffer offset - dex - bpl ECLoop ;loop until all enemy slots have been checked - -ExitECRoutine: - ldx ObjectOffset ;get enemy object buffer offset - rts ;leave - -ProcEnemyCollisions: - lda Enemy_State,y ;check both enemy states for d5 set - ora Enemy_State,x - and #%00100000 ;if d5 is set in either state, or both, branch - bne ExitProcessEColl ;to leave and do nothing else at this point - lda Enemy_State,x - cmp #$06 ;if second enemy state < $06, branch elsewhere - bcc ProcSecondEnemyColl - lda Enemy_ID,x ;check second enemy identifier for hammer bro - cmp #HammerBro ;if hammer bro found in alt state, branch to leave - beq ExitProcessEColl - lda Enemy_State,y ;check first enemy state for d7 set - asl - bcc ShellCollisions ;branch if d7 is clear - lda #$06 - jsr SetupFloateyNumber ;award 1000 points for killing enemy - jsr ShellOrBlockDefeat ;then kill enemy, then load - ldy $01 ;original offset of second enemy - -ShellCollisions: - tya ;move Y to X - tax - jsr ShellOrBlockDefeat ;kill second enemy - ldx ObjectOffset - lda ShellChainCounter,x ;get chain counter for shell - clc - adc #$04 ;add four to get appropriate point offset - ldx $01 - jsr SetupFloateyNumber ;award appropriate number of points for second enemy - ldx ObjectOffset ;load original offset of first enemy - inc ShellChainCounter,x ;increment chain counter for additional enemies - -ExitProcessEColl: - rts ;leave!!! - -ProcSecondEnemyColl: - lda Enemy_State,y ;if first enemy state < $06, branch elsewhere - cmp #$06 - bcc MoveEOfs - lda Enemy_ID,y ;check first enemy identifier for hammer bro - cmp #HammerBro ;if hammer bro found in alt state, branch to leave - beq ExitProcessEColl - jsr ShellOrBlockDefeat ;otherwise, kill first enemy - ldy $01 - lda ShellChainCounter,y ;get chain counter for shell - clc - adc #$04 ;add four to get appropriate point offset - ldx ObjectOffset - jsr SetupFloateyNumber ;award appropriate number of points for first enemy - ldx $01 ;load original offset of second enemy - inc ShellChainCounter,x ;increment chain counter for additional enemies - rts ;leave!!! - -MoveEOfs: - tya ;move Y ($01) to X - tax - jsr EnemyTurnAround ;do the sub here using value from $01 - ldx ObjectOffset ;then do it again using value from $08 - -EnemyTurnAround: - lda Enemy_ID,x ;check for specific enemies - cmp #PiranhaPlant - beq ExTA ;if piranha plant, leave - cmp #Lakitu - beq ExTA ;if lakitu, leave - cmp #HammerBro - beq ExTA ;if hammer bro, leave - cmp #Spiny - beq RXSpd ;if spiny, turn it around - cmp #GreenParatroopaJump - beq RXSpd ;if green paratroopa, turn it around - cmp #$07 - bcs ExTA ;if any OTHER enemy object => $07, leave -RXSpd: lda Enemy_X_Speed,x ;load horizontal speed - eor #$ff ;get two's compliment for horizontal speed - tay - iny - sty Enemy_X_Speed,x ;store as new horizontal speed - lda Enemy_MovingDir,x - eor #%00000011 ;invert moving direction and store, then leave - sta Enemy_MovingDir,x ;thus effectively turning the enemy around -ExTA: rts ;leave!!! - -;------------------------------------------------------------------------------------- -;$00 - vertical position of platform - -LargePlatformCollision: - lda #$ff ;save value here - sta PlatformCollisionFlag,x - lda TimerControl ;check master timer control - bne ExLPC ;if set, branch to leave - lda Enemy_State,x ;if d7 set in object state, - bmi ExLPC ;branch to leave - lda Enemy_ID,x - cmp #$24 ;check enemy object identifier for - bne ChkForPlayerC_LargeP ;balance platform, branch if not found - lda Enemy_State,x - tax ;set state as enemy offset here - jsr ChkForPlayerC_LargeP ;perform code with state offset, then original offset, in X - -ChkForPlayerC_LargeP: - jsr CheckPlayerVertical ;figure out if player is below a certain point - bcs ExLPC ;or offscreen, branch to leave if true - txa - jsr GetEnemyBoundBoxOfsArg ;get bounding box offset in Y - lda Enemy_Y_Position,x ;store vertical coordinate in - sta $00 ;temp variable for now - txa ;send offset we're on to the stack - pha - jsr PlayerCollisionCore ;do player-to-platform collision detection - pla ;retrieve offset from the stack - tax - bcc ExLPC ;if no collision, branch to leave - jsr ProcLPlatCollisions ;otherwise collision, perform sub -ExLPC: ldx ObjectOffset ;get enemy object buffer offset and leave - rts - -;-------------------------------- -;$00 - counter for bounding boxes - -SmallPlatformCollision: - lda TimerControl ;if master timer control set, - bne ExSPC ;branch to leave - sta PlatformCollisionFlag,x ;otherwise initialize collision flag - jsr CheckPlayerVertical ;do a sub to see if player is below a certain point - bcs ExSPC ;or entirely offscreen, and branch to leave if true - lda #$02 - sta $00 ;load counter here for 2 bounding boxes - -ChkSmallPlatLoop: - ldx ObjectOffset ;get enemy object offset - jsr GetEnemyBoundBoxOfs ;get bounding box offset in Y - and #%00000010 ;if d1 of offscreen lower nybble bits was set - bne ExSPC ;then branch to leave - lda BoundingBox_UL_YPos,y ;check top of platform's bounding box for being - cmp #$20 ;above a specific point - bcc MoveBoundBox ;if so, branch, don't do collision detection - jsr PlayerCollisionCore ;otherwise, perform player-to-platform collision detection - bcs ProcSPlatCollisions ;skip ahead if collision - -MoveBoundBox: - lda BoundingBox_UL_YPos,y ;move bounding box vertical coordinates - clc ;128 pixels downwards - adc #$80 - sta BoundingBox_UL_YPos,y - lda BoundingBox_DR_YPos,y - clc - adc #$80 - sta BoundingBox_DR_YPos,y - dec $00 ;decrement counter we set earlier - bne ChkSmallPlatLoop ;loop back until both bounding boxes are checked -ExSPC: ldx ObjectOffset ;get enemy object buffer offset, then leave - rts - -;-------------------------------- - -ProcSPlatCollisions: - ldx ObjectOffset ;return enemy object buffer offset to X, then continue - -ProcLPlatCollisions: - lda BoundingBox_DR_YPos,y ;get difference by subtracting the top - sec ;of the player's bounding box from the bottom - sbc BoundingBox_UL_YPos ;of the platform's bounding box - cmp #$04 ;if difference too large or negative, - bcs ChkForTopCollision ;branch, do not alter vertical speed of player - lda Player_Y_Speed ;check to see if player's vertical speed is moving down - bpl ChkForTopCollision ;if so, don't mess with it - lda #$01 ;otherwise, set vertical - sta Player_Y_Speed ;speed of player to kill jump - -ChkForTopCollision: - lda BoundingBox_DR_YPos ;get difference by subtracting the top - sec ;of the platform's bounding box from the bottom - sbc BoundingBox_UL_YPos,y ;of the player's bounding box - cmp #$06 - bcs PlatformSideCollisions ;if difference not close enough, skip all of this - lda Player_Y_Speed - bmi PlatformSideCollisions ;if player's vertical speed moving upwards, skip this - lda $00 ;get saved bounding box counter from earlier - ldy Enemy_ID,x - cpy #$2b ;if either of the two small platform objects are found, - beq SetCollisionFlag ;regardless of which one, branch to use bounding box counter - cpy #$2c ;as contents of collision flag - beq SetCollisionFlag - txa ;otherwise use enemy object buffer offset - -SetCollisionFlag: - ldx ObjectOffset ;get enemy object buffer offset - sta PlatformCollisionFlag,x ;save either bounding box counter or enemy offset here - lda #$00 - sta Player_State ;set player state to normal then leave - rts - -PlatformSideCollisions: - lda #$01 ;set value here to indicate possible horizontal - sta $00 ;collision on left side of platform - lda BoundingBox_DR_XPos ;get difference by subtracting platform's left edge - sec ;from player's right edge - sbc BoundingBox_UL_XPos,y - cmp #$08 ;if difference close enough, skip all of this - bcc SideC - inc $00 ;otherwise increment value set here for right side collision - lda BoundingBox_DR_XPos,y ;get difference by subtracting player's left edge - clc ;from platform's right edge - sbc BoundingBox_UL_XPos - cmp #$09 ;if difference not close enough, skip subroutine - bcs NoSideC ;and instead branch to leave (no collision) -SideC: jsr ImpedePlayerMove ;deal with horizontal collision -NoSideC: ldx ObjectOffset ;return with enemy object buffer offset - rts - -;------------------------------------------------------------------------------------- - -PlayerPosSPlatData: - .db $80, $00 - -PositionPlayerOnS_Plat: - tay ;use bounding box counter saved in collision flag - lda Enemy_Y_Position,x ;for offset - clc ;add positioning data using offset to the vertical - adc PlayerPosSPlatData-1,y ;coordinate - .db $2c ;BIT instruction opcode - -PositionPlayerOnVPlat: - lda Enemy_Y_Position,x ;get vertical coordinate - ldy GameEngineSubroutine - cpy #$0b ;if certain routine being executed on this frame, - beq ExPlPos ;skip all of this - ldy Enemy_Y_HighPos,x - cpy #$01 ;if vertical high byte offscreen, skip this - bne ExPlPos - sec ;subtract 32 pixels from vertical coordinate - sbc #$20 ;for the player object's height - sta Player_Y_Position ;save as player's new vertical coordinate - tya - sbc #$00 ;subtract borrow and store as player's - sta Player_Y_HighPos ;new vertical high byte - lda #$00 - sta Player_Y_Speed ;initialize vertical speed and low byte of force - sta Player_Y_MoveForce ;and then leave -ExPlPos: rts - -;------------------------------------------------------------------------------------- - -CheckPlayerVertical: - lda Player_OffscreenBits ;if player object is completely offscreen - cmp #$f0 ;vertically, leave this routine - bcs ExCPV - ldy Player_Y_HighPos ;if player high vertical byte is not - dey ;within the screen, leave this routine - bne ExCPV - lda Player_Y_Position ;if on the screen, check to see how far down - cmp #$d0 ;the player is vertically -ExCPV: rts - -;------------------------------------------------------------------------------------- - -GetEnemyBoundBoxOfs: - lda ObjectOffset ;get enemy object buffer offset - -GetEnemyBoundBoxOfsArg: - asl ;multiply A by four, then add four - asl ;to skip player's bounding box - clc - adc #$04 - tay ;send to Y - lda Enemy_OffscreenBits ;get offscreen bits for enemy object - and #%00001111 ;save low nybble - cmp #%00001111 ;check for all bits set - rts - -;------------------------------------------------------------------------------------- -;$00-$01 - used to hold many values, essentially temp variables -;$04 - holds lower nybble of vertical coordinate from block buffer routine -;$eb - used to hold block buffer adder - -PlayerBGUpperExtent: - .db $20, $10 - -PlayerBGCollision: - lda DisableCollisionDet ;if collision detection disabled flag set, - bne ExPBGCol ;branch to leave - lda GameEngineSubroutine - cmp #$0b ;if running routine #11 or $0b - beq ExPBGCol ;branch to leave - cmp #$04 - bcc ExPBGCol ;if running routines $00-$03 branch to leave - lda #$01 ;load default player state for swimming - ldy SwimmingFlag ;if swimming flag set, - bne SetPSte ;branch ahead to set default state - lda Player_State ;if player in normal state, - beq SetFallS ;branch to set default state for falling - cmp #$03 - bne ChkOnScr ;if in any other state besides climbing, skip to next part -SetFallS: lda #$02 ;load default player state for falling -SetPSte: sta Player_State ;set whatever player state is appropriate -ChkOnScr: lda Player_Y_HighPos - cmp #$01 ;check player's vertical high byte for still on the screen - bne ExPBGCol ;branch to leave if not - lda #$ff - sta Player_CollisionBits ;initialize player's collision flag - lda Player_Y_Position - cmp #$cf ;check player's vertical coordinate - bcc ChkCollSize ;if not too close to the bottom of screen, continue -ExPBGCol: rts ;otherwise leave - -ChkCollSize: - ldy #$02 ;load default offset - lda CrouchingFlag - bne GBBAdr ;if player crouching, skip ahead - lda PlayerSize - bne GBBAdr ;if player small, skip ahead - dey ;otherwise decrement offset for big player not crouching - lda SwimmingFlag - bne GBBAdr ;if swimming flag set, skip ahead - dey ;otherwise decrement offset -GBBAdr: lda BlockBufferAdderData,y ;get value using offset - sta $eb ;store value here - tay ;put value into Y, as offset for block buffer routine - ldx PlayerSize ;get player's size as offset - lda CrouchingFlag - beq HeadChk ;if player not crouching, branch ahead - inx ;otherwise increment size as offset -HeadChk: lda Player_Y_Position ;get player's vertical coordinate - cmp PlayerBGUpperExtent,x ;compare with upper extent value based on offset - bcc DoFootCheck ;if player is too high, skip this part - jsr BlockBufferColli_Head ;do player-to-bg collision detection on top of - beq DoFootCheck ;player, and branch if nothing above player's head - jsr CheckForCoinMTiles ;check to see if player touched coin with their head - bcs AwardTouchedCoin ;if so, branch to some other part of code - ldy Player_Y_Speed ;check player's vertical speed - bpl DoFootCheck ;if player not moving upwards, branch elsewhere - ldy $04 ;check lower nybble of vertical coordinate returned - cpy #$04 ;from collision detection routine - bcc DoFootCheck ;if low nybble < 4, branch - jsr CheckForSolidMTiles ;check to see what player's head bumped on - bcs SolidOrClimb ;if player collided with solid metatile, branch - ldy AreaType ;otherwise check area type - beq NYSpd ;if water level, branch ahead - ldy BlockBounceTimer ;if block bounce timer not expired, - bne NYSpd ;branch ahead, do not process collision - jsr PlayerHeadCollision ;otherwise do a sub to process collision - jmp DoFootCheck ;jump ahead to skip these other parts here - -SolidOrClimb: - cmp #$26 ;if climbing metatile, - beq NYSpd ;branch ahead and do not play sound - lda #Sfx_Bump - sta Square1SoundQueue ;otherwise load bump sound -NYSpd: lda #$01 ;set player's vertical speed to nullify - sta Player_Y_Speed ;jump or swim - -DoFootCheck: - ldy $eb ;get block buffer adder offset - lda Player_Y_Position - cmp #$cf ;check to see how low player is - bcs DoPlayerSideCheck ;if player is too far down on screen, skip all of this - jsr BlockBufferColli_Feet ;do player-to-bg collision detection on bottom left of player - jsr CheckForCoinMTiles ;check to see if player touched coin with their left foot - bcs AwardTouchedCoin ;if so, branch to some other part of code - pha ;save bottom left metatile to stack - jsr BlockBufferColli_Feet ;do player-to-bg collision detection on bottom right of player - sta $00 ;save bottom right metatile here - pla - sta $01 ;pull bottom left metatile and save here - bne ChkFootMTile ;if anything here, skip this part - lda $00 ;otherwise check for anything in bottom right metatile - beq DoPlayerSideCheck ;and skip ahead if not - jsr CheckForCoinMTiles ;check to see if player touched coin with their right foot - bcc ChkFootMTile ;if not, skip unconditional jump and continue code - -AwardTouchedCoin: - jmp HandleCoinMetatile ;follow the code to erase coin and award to player 1 coin - -ChkFootMTile: - jsr CheckForClimbMTiles ;check to see if player landed on climbable metatiles - bcs DoPlayerSideCheck ;if so, branch - ldy Player_Y_Speed ;check player's vertical speed - bmi DoPlayerSideCheck ;if player moving upwards, branch - cmp #$c5 - bne ContChk ;if player did not touch axe, skip ahead - jmp HandleAxeMetatile ;otherwise jump to set modes of operation -ContChk: jsr ChkInvisibleMTiles ;do sub to check for hidden coin or 1-up blocks - beq DoPlayerSideCheck ;if either found, branch - ldy JumpspringAnimCtrl ;if jumpspring animating right now, - bne InitSteP ;branch ahead - ldy $04 ;check lower nybble of vertical coordinate returned - cpy #$05 ;from collision detection routine - bcc LandPlyr ;if lower nybble < 5, branch - lda Player_MovingDir - sta $00 ;use player's moving direction as temp variable - jmp ImpedePlayerMove ;jump to impede player's movement in that direction -LandPlyr: jsr ChkForLandJumpSpring ;do sub to check for jumpspring metatiles and deal with it - lda #$f0 - and Player_Y_Position ;mask out lower nybble of player's vertical position - sta Player_Y_Position ;and store as new vertical position to land player properly - jsr HandlePipeEntry ;do sub to process potential pipe entry - lda #$00 - sta Player_Y_Speed ;initialize vertical speed and fractional - sta Player_Y_MoveForce ;movement force to stop player's vertical movement - sta StompChainCounter ;initialize enemy stomp counter -InitSteP: lda #$00 - sta Player_State ;set player's state to normal - -DoPlayerSideCheck: - ldy $eb ;get block buffer adder offset - iny - iny ;increment offset 2 bytes to use adders for side collisions - lda #$02 ;set value here to be used as counter - sta $00 - -SideCheckLoop: - iny ;move onto the next one - sty $eb ;store it - lda Player_Y_Position - cmp #$20 ;check player's vertical position - bcc BHalf ;if player is in status bar area, branch ahead to skip this part - cmp #$e4 - bcs ExSCH ;branch to leave if player is too far down - jsr BlockBufferColli_Side ;do player-to-bg collision detection on one half of player - beq BHalf ;branch ahead if nothing found - cmp #$1c ;otherwise check for pipe metatiles - beq BHalf ;if collided with sideways pipe (top), branch ahead - cmp #$6b - beq BHalf ;if collided with water pipe (top), branch ahead - jsr CheckForClimbMTiles ;do sub to see if player bumped into anything climbable - bcc CheckSideMTiles ;if not, branch to alternate section of code -BHalf: ldy $eb ;load block adder offset - iny ;increment it - lda Player_Y_Position ;get player's vertical position - cmp #$08 - bcc ExSCH ;if too high, branch to leave - cmp #$d0 - bcs ExSCH ;if too low, branch to leave - jsr BlockBufferColli_Side ;do player-to-bg collision detection on other half of player - bne CheckSideMTiles ;if something found, branch - dec $00 ;otherwise decrement counter - bne SideCheckLoop ;run code until both sides of player are checked -ExSCH: rts ;leave - -CheckSideMTiles: - jsr ChkInvisibleMTiles ;check for hidden or coin 1-up blocks - beq ExCSM ;branch to leave if either found - jsr CheckForClimbMTiles ;check for climbable metatiles - bcc ContSChk ;if not found, skip and continue with code - jmp HandleClimbing ;otherwise jump to handle climbing -ContSChk: jsr CheckForCoinMTiles ;check to see if player touched coin - bcs HandleCoinMetatile ;if so, execute code to erase coin and award to player 1 coin - jsr ChkJumpspringMetatiles ;check for jumpspring metatiles - bcc ChkPBtm ;if not found, branch ahead to continue cude - lda JumpspringAnimCtrl ;otherwise check jumpspring animation control - bne ExCSM ;branch to leave if set - jmp StopPlayerMove ;otherwise jump to impede player's movement -ChkPBtm: ldy Player_State ;get player's state - cpy #$00 ;check for player's state set to normal - bne StopPlayerMove ;if not, branch to impede player's movement - ldy PlayerFacingDir ;get player's facing direction - dey - bne StopPlayerMove ;if facing left, branch to impede movement - cmp #$6c ;otherwise check for pipe metatiles - beq PipeDwnS ;if collided with sideways pipe (bottom), branch - cmp #$1f ;if collided with water pipe (bottom), continue - bne StopPlayerMove ;otherwise branch to impede player's movement -PipeDwnS: lda Player_SprAttrib ;check player's attributes - bne PlyrPipe ;if already set, branch, do not play sound again - ldy #Sfx_PipeDown_Injury - sty Square1SoundQueue ;otherwise load pipedown/injury sound -PlyrPipe: ora #%00100000 - sta Player_SprAttrib ;set background priority bit in player attributes - lda Player_X_Position - and #%00001111 ;get lower nybble of player's horizontal coordinate - beq ChkGERtn ;if at zero, branch ahead to skip this part - ldy #$00 ;set default offset for timer setting data - lda ScreenLeft_PageLoc ;load page location for left side of screen - beq SetCATmr ;if at page zero, use default offset - iny ;otherwise increment offset -SetCATmr: lda AreaChangeTimerData,y ;set timer for change of area as appropriate - sta ChangeAreaTimer -ChkGERtn: lda GameEngineSubroutine ;get number of game engine routine running - cmp #$07 - beq ExCSM ;if running player entrance routine or - cmp #$08 ;player control routine, go ahead and branch to leave - bne ExCSM - lda #$02 - sta GameEngineSubroutine ;otherwise set sideways pipe entry routine to run - rts ;and leave - -;-------------------------------- -;$02 - high nybble of vertical coordinate from block buffer -;$04 - low nybble of horizontal coordinate from block buffer -;$06-$07 - block buffer address - -StopPlayerMove: - jsr ImpedePlayerMove ;stop player's movement -ExCSM: rts ;leave - -AreaChangeTimerData: - .db $a0, $34 - -HandleCoinMetatile: - jsr ErACM ;do sub to erase coin metatile from block buffer - inc CoinTallyFor1Ups ;increment coin tally used for 1-up blocks - jmp GiveOneCoin ;update coin amount and tally on the screen - -HandleAxeMetatile: - lda #$00 - sta OperMode_Task ;reset secondary mode - lda #$02 - sta OperMode ;set primary mode to autoctrl mode - lda #$18 - sta Player_X_Speed ;set horizontal speed and continue to erase axe metatile -ErACM: ldy $02 ;load vertical high nybble offset for block buffer - lda #$00 ;load blank metatile - sta ($06),y ;store to remove old contents from block buffer - jmp RemoveCoin_Axe ;update the screen accordingly - -;-------------------------------- -;$02 - high nybble of vertical coordinate from block buffer -;$04 - low nybble of horizontal coordinate from block buffer -;$06-$07 - block buffer address - -ClimbXPosAdder: - .db $f9, $07 - -ClimbPLocAdder: - .db $ff, $00 - -FlagpoleYPosData: - .db $18, $22, $50, $68, $90 - -HandleClimbing: - ldy $04 ;check low nybble of horizontal coordinate returned from - cpy #$06 ;collision detection routine against certain values, this - bcc ExHC ;makes actual physical part of vine or flagpole thinner - cpy #$0a ;than 16 pixels - bcc ChkForFlagpole -ExHC: rts ;leave if too far left or too far right - -ChkForFlagpole: - cmp #$24 ;check climbing metatiles - beq FlagpoleCollision ;branch if flagpole ball found - cmp #$25 - bne VineCollision ;branch to alternate code if flagpole shaft not found - -FlagpoleCollision: - lda GameEngineSubroutine - cmp #$05 ;check for end-of-level routine running - beq PutPlayerOnVine ;if running, branch to end of climbing code - lda #$01 - sta PlayerFacingDir ;set player's facing direction to right - inc ScrollLock ;set scroll lock flag - lda GameEngineSubroutine - cmp #$04 ;check for flagpole slide routine running - beq RunFR ;if running, branch to end of flagpole code here - lda #BulletBill_CannonVar ;load identifier for bullet bills (cannon variant) - jsr KillEnemies ;get rid of them - lda #Silence - sta EventMusicQueue ;silence music - lsr - sta FlagpoleSoundQueue ;load flagpole sound into flagpole sound queue - ldx #$04 ;start at end of vertical coordinate data - lda Player_Y_Position - sta FlagpoleCollisionYPos ;store player's vertical coordinate here to be used later - -ChkFlagpoleYPosLoop: - cmp FlagpoleYPosData,x ;compare with current vertical coordinate data - bcs MtchF ;if player's => current, branch to use current offset - dex ;otherwise decrement offset to use - bne ChkFlagpoleYPosLoop ;do this until all data is checked (use last one if all checked) -MtchF: stx FlagpoleScore ;store offset here to be used later -RunFR: lda #$04 - sta GameEngineSubroutine ;set value to run flagpole slide routine - jmp PutPlayerOnVine ;jump to end of climbing code - -VineCollision: - cmp #$26 ;check for climbing metatile used on vines - bne PutPlayerOnVine - lda Player_Y_Position ;check player's vertical coordinate - cmp #$20 ;for being in status bar area - bcs PutPlayerOnVine ;branch if not that far up - lda #$01 - sta GameEngineSubroutine ;otherwise set to run autoclimb routine next frame - -PutPlayerOnVine: - lda #$03 ;set player state to climbing - sta Player_State - lda #$00 ;nullify player's horizontal speed - sta Player_X_Speed ;and fractional horizontal movement force - sta Player_X_MoveForce - lda Player_X_Position ;get player's horizontal coordinate - sec - sbc ScreenLeft_X_Pos ;subtract from left side horizontal coordinate - cmp #$10 - bcs SetVXPl ;if 16 or more pixels difference, do not alter facing direction - lda #$02 - sta PlayerFacingDir ;otherwise force player to face left -SetVXPl: ldy PlayerFacingDir ;get current facing direction, use as offset - lda $06 ;get low byte of block buffer address - asl - asl ;move low nybble to high - asl - asl - clc - adc ClimbXPosAdder-1,y ;add pixels depending on facing direction - sta Player_X_Position ;store as player's horizontal coordinate - lda $06 ;get low byte of block buffer address again - bne ExPVne ;if not zero, branch - lda ScreenRight_PageLoc ;load page location of right side of screen - clc - adc ClimbPLocAdder-1,y ;add depending on facing location - sta Player_PageLoc ;store as player's page location -ExPVne: rts ;finally, we're done! - -;-------------------------------- - -ChkInvisibleMTiles: - cmp #$5f ;check for hidden coin block - beq ExCInvT ;branch to leave if found - cmp #$60 ;check for hidden 1-up block -ExCInvT: rts ;leave with zero flag set if either found - -;-------------------------------- -;$00-$01 - used to hold bottom right and bottom left metatiles (in that order) -;$00 - used as flag by ImpedePlayerMove to restrict specific movement - -ChkForLandJumpSpring: - jsr ChkJumpspringMetatiles ;do sub to check if player landed on jumpspring - bcc ExCJSp ;if carry not set, jumpspring not found, therefore leave - lda #$70 - sta VerticalForce ;otherwise set vertical movement force for player - lda #$f9 - sta JumpspringForce ;set default jumpspring force - lda #$03 - sta JumpspringTimer ;set jumpspring timer to be used later - lsr - sta JumpspringAnimCtrl ;set jumpspring animation control to start animating -ExCJSp: rts ;and leave - -ChkJumpspringMetatiles: - cmp #$67 ;check for top jumpspring metatile - beq JSFnd ;branch to set carry if found - cmp #$68 ;check for bottom jumpspring metatile - clc ;clear carry flag - bne NoJSFnd ;branch to use cleared carry if not found -JSFnd: sec ;set carry if found -NoJSFnd: rts ;leave - -HandlePipeEntry: - lda Up_Down_Buttons ;check saved controller bits from earlier - and #%00000100 ;for pressing down - beq ExPipeE ;if not pressing down, branch to leave - lda $00 - cmp #$11 ;check right foot metatile for warp pipe right metatile - bne ExPipeE ;branch to leave if not found - lda $01 - cmp #$10 ;check left foot metatile for warp pipe left metatile - bne ExPipeE ;branch to leave if not found - lda #$30 - sta ChangeAreaTimer ;set timer for change of area - lda #$03 - sta GameEngineSubroutine ;set to run vertical pipe entry routine on next frame - lda #Sfx_PipeDown_Injury - sta Square1SoundQueue ;load pipedown/injury sound - lda #%00100000 - sta Player_SprAttrib ;set background priority bit in player's attributes - lda WarpZoneControl ;check warp zone control - beq ExPipeE ;branch to leave if none found - and #%00000011 ;mask out all but 2 LSB - asl - asl ;multiply by four - tax ;save as offset to warp zone numbers (starts at left pipe) - lda Player_X_Position ;get player's horizontal position - cmp #$60 - bcc GetWNum ;if player at left, not near middle, use offset and skip ahead - inx ;otherwise increment for middle pipe - cmp #$a0 - bcc GetWNum ;if player at middle, but not too far right, use offset and skip - inx ;otherwise increment for last pipe -GetWNum: ldy WarpZoneNumbers,x ;get warp zone numbers - dey ;decrement for use as world number - sty WorldNumber ;store as world number and offset - ldx WorldAddrOffsets,y ;get offset to where this world's area offsets are - lda AreaAddrOffsets,x ;get area offset based on world offset - sta AreaPointer ;store area offset here to be used to change areas - lda #Silence - sta EventMusicQueue ;silence music - lda #$00 - sta EntrancePage ;initialize starting page number - sta AreaNumber ;initialize area number used for area address offset - sta LevelNumber ;initialize level number used for world display - sta AltEntranceControl ;initialize mode of entry - inc Hidden1UpFlag ;set flag for hidden 1-up blocks - inc FetchNewGameTimerFlag ;set flag to load new game timer -ExPipeE: rts ;leave!!! - -ImpedePlayerMove: - lda #$00 ;initialize value here - ldy Player_X_Speed ;get player's horizontal speed - ldx $00 ;check value set earlier for - dex ;left side collision - bne RImpd ;if right side collision, skip this part - inx ;return value to X - cpy #$00 ;if player moving to the left, - bmi ExIPM ;branch to invert bit and leave - lda #$ff ;otherwise load A with value to be used later - jmp NXSpd ;and jump to affect movement -RImpd: ldx #$02 ;return $02 to X - cpy #$01 ;if player moving to the right, - bpl ExIPM ;branch to invert bit and leave - lda #$01 ;otherwise load A with value to be used here -NXSpd: ldy #$10 - sty SideCollisionTimer ;set timer of some sort - ldy #$00 - sty Player_X_Speed ;nullify player's horizontal speed - cmp #$00 ;if value set in A not set to $ff, - bpl PlatF ;branch ahead, do not decrement Y - dey ;otherwise decrement Y now -PlatF: sty $00 ;store Y as high bits of horizontal adder - clc - adc Player_X_Position ;add contents of A to player's horizontal - sta Player_X_Position ;position to move player left or right - lda Player_PageLoc - adc $00 ;add high bits and carry to - sta Player_PageLoc ;page location if necessary -ExIPM: txa ;invert contents of X - eor #$ff - and Player_CollisionBits ;mask out bit that was set here - sta Player_CollisionBits ;store to clear bit - rts - -;-------------------------------- - -SolidMTileUpperExt: - .db $10, $61, $88, $c4 - -CheckForSolidMTiles: - jsr GetMTileAttrib ;find appropriate offset based on metatile's 2 MSB - cmp SolidMTileUpperExt,x ;compare current metatile with solid metatiles - rts - -ClimbMTileUpperExt: - .db $24, $6d, $8a, $c6 - -CheckForClimbMTiles: - jsr GetMTileAttrib ;find appropriate offset based on metatile's 2 MSB - cmp ClimbMTileUpperExt,x ;compare current metatile with climbable metatiles - rts - -CheckForCoinMTiles: - cmp #$c2 ;check for regular coin - beq CoinSd ;branch if found - cmp #$c3 ;check for underwater coin - beq CoinSd ;branch if found - clc ;otherwise clear carry and leave - rts -CoinSd: lda #Sfx_CoinGrab - sta Square2SoundQueue ;load coin grab sound and leave - rts - -GetMTileAttrib: - tay ;save metatile value into Y - and #%11000000 ;mask out all but 2 MSB - asl - rol ;shift and rotate d7-d6 to d1-d0 - rol - tax ;use as offset for metatile data - tya ;get original metatile value back -ExEBG: rts ;leave - -;------------------------------------------------------------------------------------- -;$06-$07 - address from block buffer routine - -EnemyBGCStateData: - .db $01, $01, $02, $02, $02, $05 - -EnemyBGCXSpdData: - .db $10, $f0 - -EnemyToBGCollisionDet: - lda Enemy_State,x ;check enemy state for d6 set - and #%00100000 - bne ExEBG ;if set, branch to leave - jsr SubtEnemyYPos ;otherwise, do a subroutine here - bcc ExEBG ;if enemy vertical coord + 62 < 68, branch to leave - ldy Enemy_ID,x - cpy #Spiny ;if enemy object is not spiny, branch elsewhere - bne DoIDCheckBGColl - lda Enemy_Y_Position,x - cmp #$25 ;if enemy vertical coordinate < 36 branch to leave - bcc ExEBG - -DoIDCheckBGColl: - cpy #GreenParatroopaJump ;check for some other enemy object - bne HBChk ;branch if not found - jmp EnemyJump ;otherwise jump elsewhere -HBChk: cpy #HammerBro ;check for hammer bro - bne CInvu ;branch if not found - jmp HammerBroBGColl ;otherwise jump elsewhere -CInvu: cpy #Spiny ;if enemy object is spiny, branch - beq YesIn - cpy #PowerUpObject ;if special power-up object, branch - beq YesIn - cpy #$07 ;if enemy object =>$07, branch to leave - bcs ExEBGChk -YesIn: jsr ChkUnderEnemy ;if enemy object < $07, or = $12 or $2e, do this sub - bne HandleEToBGCollision ;if block underneath enemy, branch - -NoEToBGCollision: - jmp ChkForRedKoopa ;otherwise skip and do something else - -;-------------------------------- -;$02 - vertical coordinate from block buffer routine - -HandleEToBGCollision: - jsr ChkForNonSolids ;if something is underneath enemy, find out what - beq NoEToBGCollision ;if blank $26, coins, or hidden blocks, jump, enemy falls through - cmp #$23 - bne LandEnemyProperly ;check for blank metatile $23 and branch if not found - ldy $02 ;get vertical coordinate used to find block - lda #$00 ;store default blank metatile in that spot so we won't - sta ($06),y ;trigger this routine accidentally again - lda Enemy_ID,x - cmp #$15 ;if enemy object => $15, branch ahead - bcs ChkToStunEnemies - cmp #Goomba ;if enemy object not goomba, branch ahead of this routine - bne GiveOEPoints - jsr KillEnemyAboveBlock ;if enemy object IS goomba, do this sub - -GiveOEPoints: - lda #$01 ;award 100 points for hitting block beneath enemy - jsr SetupFloateyNumber - -ChkToStunEnemies: - cmp #$09 ;perform many comparisons on enemy object identifier - bcc SetStun - cmp #$11 ;if the enemy object identifier is equal to the values - bcs SetStun ;$09, $0e, $0f or $10, it will be modified, and not - cmp #$0a ;modified if not any of those values, note that piranha plant will - bcc Demote ;always fail this test because A will still have vertical - cmp #PiranhaPlant ;coordinate from previous addition, also these comparisons - bcc SetStun ;are only necessary if branching from $d7a1 -Demote: and #%00000001 ;erase all but LSB, essentially turning enemy object - sta Enemy_ID,x ;into green or red koopa troopa to demote them -SetStun: lda Enemy_State,x ;load enemy state - and #%11110000 ;save high nybble - ora #%00000010 - sta Enemy_State,x ;set d1 of enemy state - dec Enemy_Y_Position,x - dec Enemy_Y_Position,x ;subtract two pixels from enemy's vertical position - lda Enemy_ID,x - cmp #Bloober ;check for bloober object - beq SetWYSpd - lda #$fd ;set default vertical speed - ldy AreaType - bne SetNotW ;if area type not water, set as speed, otherwise -SetWYSpd: lda #$ff ;change the vertical speed -SetNotW: sta Enemy_Y_Speed,x ;set vertical speed now - ldy #$01 - jsr PlayerEnemyDiff ;get horizontal difference between player and enemy object - bpl ChkBBill ;branch if enemy is to the right of player - iny ;increment Y if not -ChkBBill: lda Enemy_ID,x - cmp #BulletBill_CannonVar ;check for bullet bill (cannon variant) - beq NoCDirF - cmp #BulletBill_FrenzyVar ;check for bullet bill (frenzy variant) - beq NoCDirF ;branch if either found, direction does not change - sty Enemy_MovingDir,x ;store as moving direction -NoCDirF: dey ;decrement and use as offset - lda EnemyBGCXSpdData,y ;get proper horizontal speed - sta Enemy_X_Speed,x ;and store, then leave -ExEBGChk: rts - -;-------------------------------- -;$04 - low nybble of vertical coordinate from block buffer routine - -LandEnemyProperly: - lda $04 ;check lower nybble of vertical coordinate saved earlier - sec - sbc #$08 ;subtract eight pixels - cmp #$05 ;used to determine whether enemy landed from falling - bcs ChkForRedKoopa ;branch if lower nybble in range of $0d-$0f before subtract - lda Enemy_State,x - and #%01000000 ;branch if d6 in enemy state is set - bne LandEnemyInitState - lda Enemy_State,x - asl ;branch if d7 in enemy state is not set - bcc ChkLandedEnemyState -SChkA: jmp DoEnemySideCheck ;if lower nybble < $0d, d7 set but d6 not set, jump here - -ChkLandedEnemyState: - lda Enemy_State,x ;if enemy in normal state, branch back to jump here - beq SChkA - cmp #$05 ;if in state used by spiny's egg - beq ProcEnemyDirection ;then branch elsewhere - cmp #$03 ;if already in state used by koopas and buzzy beetles - bcs ExSteChk ;or in higher numbered state, branch to leave - lda Enemy_State,x ;load enemy state again (why?) - cmp #$02 ;if not in $02 state (used by koopas and buzzy beetles) - bne ProcEnemyDirection ;then branch elsewhere - lda #$10 ;load default timer here - ldy Enemy_ID,x ;check enemy identifier for spiny - cpy #Spiny - bne SetForStn ;branch if not found - lda #$00 ;set timer for $00 if spiny -SetForStn: sta EnemyIntervalTimer,x ;set timer here - lda #$03 ;set state here, apparently used to render - sta Enemy_State,x ;upside-down koopas and buzzy beetles - jsr EnemyLanding ;then land it properly -ExSteChk: rts ;then leave - -ProcEnemyDirection: - lda Enemy_ID,x ;check enemy identifier for goomba - cmp #Goomba ;branch if found - beq LandEnemyInitState - cmp #Spiny ;check for spiny - bne InvtD ;branch if not found - lda #$01 - sta Enemy_MovingDir,x ;send enemy moving to the right by default - lda #$08 - sta Enemy_X_Speed,x ;set horizontal speed accordingly - lda FrameCounter - and #%00000111 ;if timed appropriately, spiny will skip over - beq LandEnemyInitState ;trying to face the player -InvtD: ldy #$01 ;load 1 for enemy to face the left (inverted here) - jsr PlayerEnemyDiff ;get horizontal difference between player and enemy - bpl CNwCDir ;if enemy to the right of player, branch - iny ;if to the left, increment by one for enemy to face right (inverted) -CNwCDir: tya - cmp Enemy_MovingDir,x ;compare direction in A with current direction in memory - bne LandEnemyInitState - jsr ChkForBump_HammerBroJ ;if equal, not facing in correct dir, do sub to turn around - -LandEnemyInitState: - jsr EnemyLanding ;land enemy properly - lda Enemy_State,x - and #%10000000 ;if d7 of enemy state is set, branch - bne NMovShellFallBit - lda #$00 ;otherwise initialize enemy state and leave - sta Enemy_State,x ;note this will also turn spiny's egg into spiny - rts - -NMovShellFallBit: - lda Enemy_State,x ;nullify d6 of enemy state, save other bits - and #%10111111 ;and store, then leave - sta Enemy_State,x - rts - -;-------------------------------- - -ChkForRedKoopa: - lda Enemy_ID,x ;check for red koopa troopa $03 - cmp #RedKoopa - bne Chk2MSBSt ;branch if not found - lda Enemy_State,x - beq ChkForBump_HammerBroJ ;if enemy found and in normal state, branch -Chk2MSBSt: lda Enemy_State,x ;save enemy state into Y - tay - asl ;check for d7 set - bcc GetSteFromD ;branch if not set - lda Enemy_State,x - ora #%01000000 ;set d6 - jmp SetD6Ste ;jump ahead of this part -GetSteFromD: lda EnemyBGCStateData,y ;load new enemy state with old as offset -SetD6Ste: sta Enemy_State,x ;set as new state - -;-------------------------------- -;$00 - used to store bitmask (not used but initialized here) -;$eb - used in DoEnemySideCheck as counter and to compare moving directions - -DoEnemySideCheck: - lda Enemy_Y_Position,x ;if enemy within status bar, branch to leave - cmp #$20 ;because there's nothing there that impedes movement - bcc ExESdeC - ldy #$16 ;start by finding block to the left of enemy ($00,$14) - lda #$02 ;set value here in what is also used as - sta $eb ;OAM data offset -SdeCLoop: lda $eb ;check value - cmp Enemy_MovingDir,x ;compare value against moving direction - bne NextSdeC ;branch if different and do not seek block there - lda #$01 ;set flag in A for save horizontal coordinate - jsr BlockBufferChk_Enemy ;find block to left or right of enemy object - beq NextSdeC ;if nothing found, branch - jsr ChkForNonSolids ;check for non-solid blocks - bne ChkForBump_HammerBroJ ;branch if not found -NextSdeC: dec $eb ;move to the next direction - iny - cpy #$18 ;increment Y, loop only if Y < $18, thus we check - bcc SdeCLoop ;enemy ($00, $14) and ($10, $14) pixel coordinates -ExESdeC: rts - -ChkForBump_HammerBroJ: - cpx #$05 ;check if we're on the special use slot - beq NoBump ;and if so, branch ahead and do not play sound - lda Enemy_State,x ;if enemy state d7 not set, branch - asl ;ahead and do not play sound - bcc NoBump - lda #Sfx_Bump ;otherwise, play bump sound - sta Square1SoundQueue ;sound will never be played if branching from ChkForRedKoopa -NoBump: lda Enemy_ID,x ;check for hammer bro - cmp #$05 - bne InvEnemyDir ;branch if not found - lda #$00 - sta $00 ;initialize value here for bitmask - ldy #$fa ;load default vertical speed for jumping - jmp SetHJ ;jump to code that makes hammer bro jump - -InvEnemyDir: - jmp RXSpd ;jump to turn the enemy around - -;-------------------------------- -;$00 - used to hold horizontal difference between player and enemy - -PlayerEnemyDiff: - lda Enemy_X_Position,x ;get distance between enemy object's - sec ;horizontal coordinate and the player's - sbc Player_X_Position ;horizontal coordinate - sta $00 ;and store here - lda Enemy_PageLoc,x - sbc Player_PageLoc ;subtract borrow, then leave - rts - -;-------------------------------- - -EnemyLanding: - jsr InitVStf ;do something here to vertical speed and something else - lda Enemy_Y_Position,x - and #%11110000 ;save high nybble of vertical coordinate, and - ora #%00001000 ;set d3, then store, probably used to set enemy object - sta Enemy_Y_Position,x ;neatly on whatever it's landing on - rts - -SubtEnemyYPos: - lda Enemy_Y_Position,x ;add 62 pixels to enemy object's - clc ;vertical coordinate - adc #$3e - cmp #$44 ;compare against a certain range - rts ;and leave with flags set for conditional branch - -EnemyJump: - jsr SubtEnemyYPos ;do a sub here - bcc DoSide ;if enemy vertical coord + 62 < 68, branch to leave - lda Enemy_Y_Speed,x - clc ;add two to vertical speed - adc #$02 - cmp #$03 ;if green paratroopa not falling, branch ahead - bcc DoSide - jsr ChkUnderEnemy ;otherwise, check to see if green paratroopa is - beq DoSide ;standing on anything, then branch to same place if not - jsr ChkForNonSolids ;check for non-solid blocks - beq DoSide ;branch if found - jsr EnemyLanding ;change vertical coordinate and speed - lda #$fd - sta Enemy_Y_Speed,x ;make the paratroopa jump again -DoSide: jmp DoEnemySideCheck ;check for horizontal blockage, then leave - -;-------------------------------- - -HammerBroBGColl: - jsr ChkUnderEnemy ;check to see if hammer bro is standing on anything - beq NoUnderHammerBro - cmp #$23 ;check for blank metatile $23 and branch if not found - bne UnderHammerBro - -KillEnemyAboveBlock: - jsr ShellOrBlockDefeat ;do this sub to kill enemy - lda #$fc ;alter vertical speed of enemy and leave - sta Enemy_Y_Speed,x - rts - -UnderHammerBro: - lda EnemyFrameTimer,x ;check timer used by hammer bro - bne NoUnderHammerBro ;branch if not expired - lda Enemy_State,x - and #%10001000 ;save d7 and d3 from enemy state, nullify other bits - sta Enemy_State,x ;and store - jsr EnemyLanding ;modify vertical coordinate, speed and something else - jmp DoEnemySideCheck ;then check for horizontal blockage and leave - -NoUnderHammerBro: - lda Enemy_State,x ;if hammer bro is not standing on anything, set d0 - ora #$01 ;in the enemy state to indicate jumping or falling, then leave - sta Enemy_State,x - rts - -ChkUnderEnemy: - lda #$00 ;set flag in A for save vertical coordinate - ldy #$15 ;set Y to check the bottom middle (8,18) of enemy object - jmp BlockBufferChk_Enemy ;hop to it! - -ChkForNonSolids: - cmp #$26 ;blank metatile used for vines? - beq NSFnd - cmp #$c2 ;regular coin? - beq NSFnd - cmp #$c3 ;underwater coin? - beq NSFnd - cmp #$5f ;hidden coin block? - beq NSFnd - cmp #$60 ;hidden 1-up block? -NSFnd: rts - -;------------------------------------------------------------------------------------- - -FireballBGCollision: - lda Fireball_Y_Position,x ;check fireball's vertical coordinate - cmp #$18 - bcc ClearBounceFlag ;if within the status bar area of the screen, branch ahead - jsr BlockBufferChk_FBall ;do fireball to background collision detection on bottom of it - beq ClearBounceFlag ;if nothing underneath fireball, branch - jsr ChkForNonSolids ;check for non-solid metatiles - beq ClearBounceFlag ;branch if any found - lda Fireball_Y_Speed,x ;if fireball's vertical speed set to move upwards, - bmi InitFireballExplode ;branch to set exploding bit in fireball's state - lda FireballBouncingFlag,x ;if bouncing flag already set, - bne InitFireballExplode ;branch to set exploding bit in fireball's state - lda #$fd - sta Fireball_Y_Speed,x ;otherwise set vertical speed to move upwards (give it bounce) - lda #$01 - sta FireballBouncingFlag,x ;set bouncing flag - lda Fireball_Y_Position,x - and #$f8 ;modify vertical coordinate to land it properly - sta Fireball_Y_Position,x ;store as new vertical coordinate - rts ;leave - -ClearBounceFlag: - lda #$00 - sta FireballBouncingFlag,x ;clear bouncing flag by default - rts ;leave - -InitFireballExplode: - lda #$80 - sta Fireball_State,x ;set exploding flag in fireball's state - lda #Sfx_Bump - sta Square1SoundQueue ;load bump sound - rts ;leave - -;------------------------------------------------------------------------------------- -;$00 - used to hold one of bitmasks, or offset -;$01 - used for relative X coordinate, also used to store middle screen page location -;$02 - used for relative Y coordinate, also used to store middle screen coordinate - -;this data added to relative coordinates of sprite objects -;stored in order: left edge, top edge, right edge, bottom edge -BoundBoxCtrlData: - .db $02, $08, $0e, $20 - .db $03, $14, $0d, $20 - .db $02, $14, $0e, $20 - .db $02, $09, $0e, $15 - .db $00, $00, $18, $06 - .db $00, $00, $20, $0d - .db $00, $00, $30, $0d - .db $00, $00, $08, $08 - .db $06, $04, $0a, $08 - .db $03, $0e, $0d, $14 - .db $00, $02, $10, $15 - .db $04, $04, $0c, $1c - -GetFireballBoundBox: - txa ;add seven bytes to offset - clc ;to use in routines as offset for fireball - adc #$07 - tax - ldy #$02 ;set offset for relative coordinates - bne FBallB ;unconditional branch - -GetMiscBoundBox: - txa ;add nine bytes to offset - clc ;to use in routines as offset for misc object - adc #$09 - tax - ldy #$06 ;set offset for relative coordinates -FBallB: jsr BoundingBoxCore ;get bounding box coordinates - jmp CheckRightScreenBBox ;jump to handle any offscreen coordinates - -GetEnemyBoundBox: - ldy #$48 ;store bitmask here for now - sty $00 - ldy #$44 ;store another bitmask here for now and jump - jmp GetMaskedOffScrBits - -SmallPlatformBoundBox: - ldy #$08 ;store bitmask here for now - sty $00 - ldy #$04 ;store another bitmask here for now - -GetMaskedOffScrBits: - lda Enemy_X_Position,x ;get enemy object position relative - sec ;to the left side of the screen - sbc ScreenLeft_X_Pos - sta $01 ;store here - lda Enemy_PageLoc,x ;subtract borrow from current page location - sbc ScreenLeft_PageLoc ;of left side - bmi CMBits ;if enemy object is beyond left edge, branch - ora $01 - beq CMBits ;if precisely at the left edge, branch - ldy $00 ;if to the right of left edge, use value in $00 for A -CMBits: tya ;otherwise use contents of Y - and Enemy_OffscreenBits ;preserve bitwise whatever's in here - sta EnemyOffscrBitsMasked,x ;save masked offscreen bits here - bne MoveBoundBoxOffscreen ;if anything set here, branch - jmp SetupEOffsetFBBox ;otherwise, do something else - -LargePlatformBoundBox: - inx ;increment X to get the proper offset - jsr GetXOffscreenBits ;then jump directly to the sub for horizontal offscreen bits - dex ;decrement to return to original offset - cmp #$fe ;if completely offscreen, branch to put entire bounding - bcs MoveBoundBoxOffscreen ;box offscreen, otherwise start getting coordinates - -SetupEOffsetFBBox: - txa ;add 1 to offset to properly address - clc ;the enemy object memory locations - adc #$01 - tax - ldy #$01 ;load 1 as offset here, same reason - jsr BoundingBoxCore ;do a sub to get the coordinates of the bounding box - jmp CheckRightScreenBBox ;jump to handle offscreen coordinates of bounding box - -MoveBoundBoxOffscreen: - txa ;multiply offset by 4 - asl - asl - tay ;use as offset here - lda #$ff - sta EnemyBoundingBoxCoord,y ;load value into four locations here and leave - sta EnemyBoundingBoxCoord+1,y - sta EnemyBoundingBoxCoord+2,y - sta EnemyBoundingBoxCoord+3,y - rts - -BoundingBoxCore: - stx $00 ;save offset here - lda SprObject_Rel_YPos,y ;store object coordinates relative to screen - sta $02 ;vertically and horizontally, respectively - lda SprObject_Rel_XPos,y - sta $01 - txa ;multiply offset by four and save to stack - asl - asl - pha - tay ;use as offset for Y, X is left alone - lda SprObj_BoundBoxCtrl,x ;load value here to be used as offset for X - asl ;multiply that by four and use as X - asl - tax - lda $01 ;add the first number in the bounding box data to the - clc ;relative horizontal coordinate using enemy object offset - adc BoundBoxCtrlData,x ;and store somewhere using same offset * 4 - sta BoundingBox_UL_Corner,y ;store here - lda $01 - clc - adc BoundBoxCtrlData+2,x ;add the third number in the bounding box data to the - sta BoundingBox_LR_Corner,y ;relative horizontal coordinate and store - inx ;increment both offsets - iny - lda $02 ;add the second number to the relative vertical coordinate - clc ;using incremented offset and store using the other - adc BoundBoxCtrlData,x ;incremented offset - sta BoundingBox_UL_Corner,y - lda $02 - clc - adc BoundBoxCtrlData+2,x ;add the fourth number to the relative vertical coordinate - sta BoundingBox_LR_Corner,y ;and store - pla ;get original offset loaded into $00 * y from stack - tay ;use as Y - ldx $00 ;get original offset and use as X again - rts - -CheckRightScreenBBox: - lda ScreenLeft_X_Pos ;add 128 pixels to left side of screen - clc ;and store as horizontal coordinate of middle - adc #$80 - sta $02 - lda ScreenLeft_PageLoc ;add carry to page location of left side of screen - adc #$00 ;and store as page location of middle - sta $01 - lda SprObject_X_Position,x ;get horizontal coordinate - cmp $02 ;compare against middle horizontal coordinate - lda SprObject_PageLoc,x ;get page location - sbc $01 ;subtract from middle page location - bcc CheckLeftScreenBBox ;if object is on the left side of the screen, branch - lda BoundingBox_DR_XPos,y ;check right-side edge of bounding box for offscreen - bmi NoOfs ;coordinates, branch if still on the screen - lda #$ff ;load offscreen value here to use on one or both horizontal sides - ldx BoundingBox_UL_XPos,y ;check left-side edge of bounding box for offscreen - bmi SORte ;coordinates, and branch if still on the screen - sta BoundingBox_UL_XPos,y ;store offscreen value for left side -SORte: sta BoundingBox_DR_XPos,y ;store offscreen value for right side -NoOfs: ldx ObjectOffset ;get object offset and leave - rts - -CheckLeftScreenBBox: - lda BoundingBox_UL_XPos,y ;check left-side edge of bounding box for offscreen - bpl NoOfs2 ;coordinates, and branch if still on the screen - cmp #$a0 ;check to see if left-side edge is in the middle of the - bcc NoOfs2 ;screen or really offscreen, and branch if still on - lda #$00 - ldx BoundingBox_DR_XPos,y ;check right-side edge of bounding box for offscreen - bpl SOLft ;coordinates, branch if still onscreen - sta BoundingBox_DR_XPos,y ;store offscreen value for right side -SOLft: sta BoundingBox_UL_XPos,y ;store offscreen value for left side -NoOfs2: ldx ObjectOffset ;get object offset and leave - rts - -;------------------------------------------------------------------------------------- -;$06 - second object's offset -;$07 - counter - -PlayerCollisionCore: - ldx #$00 ;initialize X to use player's bounding box for comparison - -SprObjectCollisionCore: - sty $06 ;save contents of Y here - lda #$01 - sta $07 ;save value 1 here as counter, compare horizontal coordinates first - -CollisionCoreLoop: - lda BoundingBox_UL_Corner,y ;compare left/top coordinates - cmp BoundingBox_UL_Corner,x ;of first and second objects' bounding boxes - bcs FirstBoxGreater ;if first left/top => second, branch - cmp BoundingBox_LR_Corner,x ;otherwise compare to right/bottom of second - bcc SecondBoxVerticalChk ;if first left/top < second right/bottom, branch elsewhere - beq CollisionFound ;if somehow equal, collision, thus branch - lda BoundingBox_LR_Corner,y ;if somehow greater, check to see if bottom of - cmp BoundingBox_UL_Corner,y ;first object's bounding box is greater than its top - bcc CollisionFound ;if somehow less, vertical wrap collision, thus branch - cmp BoundingBox_UL_Corner,x ;otherwise compare bottom of first bounding box to the top - bcs CollisionFound ;of second box, and if equal or greater, collision, thus branch - ldy $06 ;otherwise return with carry clear and Y = $0006 - rts ;note horizontal wrapping never occurs - -SecondBoxVerticalChk: - lda BoundingBox_LR_Corner,x ;check to see if the vertical bottom of the box - cmp BoundingBox_UL_Corner,x ;is greater than the vertical top - bcc CollisionFound ;if somehow less, vertical wrap collision, thus branch - lda BoundingBox_LR_Corner,y ;otherwise compare horizontal right or vertical bottom - cmp BoundingBox_UL_Corner,x ;of first box with horizontal left or vertical top of second box - bcs CollisionFound ;if equal or greater, collision, thus branch - ldy $06 ;otherwise return with carry clear and Y = $0006 - rts - -FirstBoxGreater: - cmp BoundingBox_UL_Corner,x ;compare first and second box horizontal left/vertical top again - beq CollisionFound ;if first coordinate = second, collision, thus branch - cmp BoundingBox_LR_Corner,x ;if not, compare with second object right or bottom edge - bcc CollisionFound ;if left/top of first less than or equal to right/bottom of second - beq CollisionFound ;then collision, thus branch - cmp BoundingBox_LR_Corner,y ;otherwise check to see if top of first box is greater than bottom - bcc NoCollisionFound ;if less than or equal, no collision, branch to end - beq NoCollisionFound - lda BoundingBox_LR_Corner,y ;otherwise compare bottom of first to top of second - cmp BoundingBox_UL_Corner,x ;if bottom of first is greater than top of second, vertical wrap - bcs CollisionFound ;collision, and branch, otherwise, proceed onwards here - -NoCollisionFound: - clc ;clear carry, then load value set earlier, then leave - ldy $06 ;like previous ones, if horizontal coordinates do not collide, we do - rts ;not bother checking vertical ones, because what's the point? - -CollisionFound: - inx ;increment offsets on both objects to check - iny ;the vertical coordinates - dec $07 ;decrement counter to reflect this - bpl CollisionCoreLoop ;if counter not expired, branch to loop - sec ;otherwise we already did both sets, therefore collision, so set carry - ldy $06 ;load original value set here earlier, then leave - rts - -;------------------------------------------------------------------------------------- -;$02 - modified y coordinate -;$03 - stores metatile involved in block buffer collisions -;$04 - comes in with offset to block buffer adder data, goes out with low nybble x/y coordinate -;$05 - modified x coordinate -;$06-$07 - block buffer address - -BlockBufferChk_Enemy: - pha ;save contents of A to stack - txa - clc ;add 1 to X to run sub with enemy offset in mind - adc #$01 - tax - pla ;pull A from stack and jump elsewhere - jmp BBChk_E - -ResidualMiscObjectCode: - txa - clc ;supposedly used once to set offset for - adc #$0d ;miscellaneous objects - tax - ldy #$1b ;supposedly used once to set offset for block buffer data - jmp ResJmpM ;probably used in early stages to do misc to bg collision detection - -BlockBufferChk_FBall: - ldy #$1a ;set offset for block buffer adder data - txa - clc - adc #$07 ;add seven bytes to use - tax -ResJmpM: lda #$00 ;set A to return vertical coordinate -BBChk_E: jsr BlockBufferCollision ;do collision detection subroutine for sprite object - ldx ObjectOffset ;get object offset - cmp #$00 ;check to see if object bumped into anything - rts - -BlockBufferAdderData: - .db $00, $07, $0e - -BlockBuffer_X_Adder: - .db $08, $03, $0c, $02, $02, $0d, $0d, $08 - .db $03, $0c, $02, $02, $0d, $0d, $08, $03 - .db $0c, $02, $02, $0d, $0d, $08, $00, $10 - .db $04, $14, $04, $04 - -BlockBuffer_Y_Adder: - .db $04, $20, $20, $08, $18, $08, $18, $02 - .db $20, $20, $08, $18, $08, $18, $12, $20 - .db $20, $18, $18, $18, $18, $18, $14, $14 - .db $06, $06, $08, $10 - -BlockBufferColli_Feet: - iny ;if branched here, increment to next set of adders - -BlockBufferColli_Head: - lda #$00 ;set flag to return vertical coordinate - .db $2c ;BIT instruction opcode - -BlockBufferColli_Side: - lda #$01 ;set flag to return horizontal coordinate - ldx #$00 ;set offset for player object - -BlockBufferCollision: - pha ;save contents of A to stack - sty $04 ;save contents of Y here - lda BlockBuffer_X_Adder,y ;add horizontal coordinate - clc ;of object to value obtained using Y as offset - adc SprObject_X_Position,x - sta $05 ;store here - lda SprObject_PageLoc,x - adc #$00 ;add carry to page location - and #$01 ;get LSB, mask out all other bits - lsr ;move to carry - ora $05 ;get stored value - ror ;rotate carry to MSB of A - lsr ;and effectively move high nybble to - lsr ;lower, LSB which became MSB will be - lsr ;d4 at this point - jsr GetBlockBufferAddr ;get address of block buffer into $06, $07 - ldy $04 ;get old contents of Y - lda SprObject_Y_Position,x ;get vertical coordinate of object - clc - adc BlockBuffer_Y_Adder,y ;add it to value obtained using Y as offset - and #%11110000 ;mask out low nybble - sec - sbc #$20 ;subtract 32 pixels for the status bar - sta $02 ;store result here - tay ;use as offset for block buffer - lda ($06),y ;check current content of block buffer - sta $03 ;and store here - ldy $04 ;get old contents of Y again - pla ;pull A from stack - bne RetXC ;if A = 1, branch - lda SprObject_Y_Position,x ;if A = 0, load vertical coordinate - jmp RetYC ;and jump -RetXC: lda SprObject_X_Position,x ;otherwise load horizontal coordinate -RetYC: and #%00001111 ;and mask out high nybble - sta $04 ;store masked out result here - lda $03 ;get saved content of block buffer - rts ;and leave - -;------------------------------------------------------------------------------------- - -;unused byte - .db $ff - -;------------------------------------------------------------------------------------- -;$00 - offset to vine Y coordinate adder -;$02 - offset to sprite data - -VineYPosAdder: - .db $00, $30 - -DrawVine: - sty $00 ;save offset here - lda Enemy_Rel_YPos ;get relative vertical coordinate - clc - adc VineYPosAdder,y ;add value using offset in Y to get value - ldx VineObjOffset,y ;get offset to vine - ldy Enemy_SprDataOffset,x ;get sprite data offset - sty $02 ;store sprite data offset here - jsr SixSpriteStacker ;stack six sprites on top of each other vertically - lda Enemy_Rel_XPos ;get relative horizontal coordinate - sta Sprite_X_Position,y ;store in first, third and fifth sprites - sta Sprite_X_Position+8,y - sta Sprite_X_Position+16,y - clc - adc #$06 ;add six pixels to second, fourth and sixth sprites - sta Sprite_X_Position+4,y ;to give characteristic staggered vine shape to - sta Sprite_X_Position+12,y ;our vertical stack of sprites - sta Sprite_X_Position+20,y - lda #%00100001 ;set bg priority and palette attribute bits - sta Sprite_Attributes,y ;set in first, third and fifth sprites - sta Sprite_Attributes+8,y - sta Sprite_Attributes+16,y - ora #%01000000 ;additionally, set horizontal flip bit - sta Sprite_Attributes+4,y ;for second, fourth and sixth sprites - sta Sprite_Attributes+12,y - sta Sprite_Attributes+20,y - ldx #$05 ;set tiles for six sprites -VineTL: lda #$e1 ;set tile number for sprite - sta Sprite_Tilenumber,y - iny ;move offset to next sprite data - iny - iny - iny - dex ;move onto next sprite - bpl VineTL ;loop until all sprites are done - ldy $02 ;get original offset - lda $00 ;get offset to vine adding data - bne SkpVTop ;if offset not zero, skip this part - lda #$e0 - sta Sprite_Tilenumber,y ;set other tile number for top of vine -SkpVTop: ldx #$00 ;start with the first sprite again -ChkFTop: lda VineStart_Y_Position ;get original starting vertical coordinate - sec - sbc Sprite_Y_Position,y ;subtract top-most sprite's Y coordinate - cmp #$64 ;if two coordinates are less than 100/$64 pixels - bcc NextVSp ;apart, skip this to leave sprite alone - lda #$f8 - sta Sprite_Y_Position,y ;otherwise move sprite offscreen -NextVSp: iny ;move offset to next OAM data - iny - iny - iny - inx ;move onto next sprite - cpx #$06 ;do this until all sprites are checked - bne ChkFTop - ldy $00 ;return offset set earlier - rts - -SixSpriteStacker: - ldx #$06 ;do six sprites -StkLp: sta Sprite_Data,y ;store X or Y coordinate into OAM data - clc - adc #$08 ;add eight pixels - iny - iny ;move offset four bytes forward - iny - iny - dex ;do another sprite - bne StkLp ;do this until all sprites are done - ldy $02 ;get saved OAM data offset and leave - rts - -;------------------------------------------------------------------------------------- - -FirstSprXPos: - .db $04, $00, $04, $00 - -FirstSprYPos: - .db $00, $04, $00, $04 - -SecondSprXPos: - .db $00, $08, $00, $08 - -SecondSprYPos: - .db $08, $00, $08, $00 - -FirstSprTilenum: - .db $80, $82, $81, $83 - -SecondSprTilenum: - .db $81, $83, $80, $82 - -HammerSprAttrib: - .db $03, $03, $c3, $c3 - -DrawHammer: - ldy Misc_SprDataOffset,x ;get misc object OAM data offset - lda TimerControl - bne ForceHPose ;if master timer control set, skip this part - lda Misc_State,x ;otherwise get hammer's state - and #%01111111 ;mask out d7 - cmp #$01 ;check to see if set to 1 yet - beq GetHPose ;if so, branch -ForceHPose: ldx #$00 ;reset offset here - beq RenderH ;do unconditional branch to rendering part -GetHPose: lda FrameCounter ;get frame counter - lsr ;move d3-d2 to d1-d0 - lsr - and #%00000011 ;mask out all but d1-d0 (changes every four frames) - tax ;use as timing offset -RenderH: lda Misc_Rel_YPos ;get relative vertical coordinate - clc - adc FirstSprYPos,x ;add first sprite vertical adder based on offset - sta Sprite_Y_Position,y ;store as sprite Y coordinate for first sprite - clc - adc SecondSprYPos,x ;add second sprite vertical adder based on offset - sta Sprite_Y_Position+4,y ;store as sprite Y coordinate for second sprite - lda Misc_Rel_XPos ;get relative horizontal coordinate - clc - adc FirstSprXPos,x ;add first sprite horizontal adder based on offset - sta Sprite_X_Position,y ;store as sprite X coordinate for first sprite - clc - adc SecondSprXPos,x ;add second sprite horizontal adder based on offset - sta Sprite_X_Position+4,y ;store as sprite X coordinate for second sprite - lda FirstSprTilenum,x - sta Sprite_Tilenumber,y ;get and store tile number of first sprite - lda SecondSprTilenum,x - sta Sprite_Tilenumber+4,y ;get and store tile number of second sprite - lda HammerSprAttrib,x - sta Sprite_Attributes,y ;get and store attribute bytes for both - sta Sprite_Attributes+4,y ;note in this case they use the same data - ldx ObjectOffset ;get misc object offset - lda Misc_OffscreenBits - and #%11111100 ;check offscreen bits - beq NoHOffscr ;if all bits clear, leave object alone - lda #$00 - sta Misc_State,x ;otherwise nullify misc object state - lda #$f8 - jsr DumpTwoSpr ;do sub to move hammer sprites offscreen -NoHOffscr: rts ;leave - -;------------------------------------------------------------------------------------- -;$00-$01 - used to hold tile numbers ($01 addressed in draw floatey number part) -;$02 - used to hold Y coordinate for floatey number -;$03 - residual byte used for flip (but value set here affects nothing) -;$04 - attribute byte for floatey number -;$05 - used as X coordinate for floatey number - -FlagpoleScoreNumTiles: - .db $f9, $50 - .db $f7, $50 - .db $fa, $fb - .db $f8, $fb - .db $f6, $fb - -FlagpoleGfxHandler: - ldy Enemy_SprDataOffset,x ;get sprite data offset for flagpole flag - lda Enemy_Rel_XPos ;get relative horizontal coordinate - sta Sprite_X_Position,y ;store as X coordinate for first sprite - clc - adc #$08 ;add eight pixels and store - sta Sprite_X_Position+4,y ;as X coordinate for second and third sprites - sta Sprite_X_Position+8,y - clc - adc #$0c ;add twelve more pixels and - sta $05 ;store here to be used later by floatey number - lda Enemy_Y_Position,x ;get vertical coordinate - jsr DumpTwoSpr ;and do sub to dump into first and second sprites - adc #$08 ;add eight pixels - sta Sprite_Y_Position+8,y ;and store into third sprite - lda FlagpoleFNum_Y_Pos ;get vertical coordinate for floatey number - sta $02 ;store it here - lda #$01 - sta $03 ;set value for flip which will not be used, and - sta $04 ;attribute byte for floatey number - sta Sprite_Attributes,y ;set attribute bytes for all three sprites - sta Sprite_Attributes+4,y - sta Sprite_Attributes+8,y - lda #$7e - sta Sprite_Tilenumber,y ;put triangle shaped tile - sta Sprite_Tilenumber+8,y ;into first and third sprites - lda #$7f - sta Sprite_Tilenumber+4,y ;put skull tile into second sprite - lda FlagpoleCollisionYPos ;get vertical coordinate at time of collision - beq ChkFlagOffscreen ;if zero, branch ahead - tya - clc ;add 12 bytes to sprite data offset - adc #$0c - tay ;put back in Y - lda FlagpoleScore ;get offset used to award points for touching flagpole - asl ;multiply by 2 to get proper offset here - tax - lda FlagpoleScoreNumTiles,x ;get appropriate tile data - sta $00 - lda FlagpoleScoreNumTiles+1,x - jsr DrawOneSpriteRow ;use it to render floatey number - -ChkFlagOffscreen: - ldx ObjectOffset ;get object offset for flag - ldy Enemy_SprDataOffset,x ;get OAM data offset - lda Enemy_OffscreenBits ;get offscreen bits - and #%00001110 ;mask out all but d3-d1 - beq ExitDumpSpr ;if none of these bits set, branch to leave - -;------------------------------------------------------------------------------------- - -MoveSixSpritesOffscreen: - lda #$f8 ;set offscreen coordinate if jumping here - -DumpSixSpr: - sta Sprite_Data+20,y ;dump A contents - sta Sprite_Data+16,y ;into third row sprites - -DumpFourSpr: - sta Sprite_Data+12,y ;into second row sprites - -DumpThreeSpr: - sta Sprite_Data+8,y - -DumpTwoSpr: - sta Sprite_Data+4,y ;and into first row sprites - sta Sprite_Data,y - -ExitDumpSpr: - rts - -;------------------------------------------------------------------------------------- - -DrawLargePlatform: - ldy Enemy_SprDataOffset,x ;get OAM data offset - sty $02 ;store here - iny ;add 3 to it for offset - iny ;to X coordinate - iny - lda Enemy_Rel_XPos ;get horizontal relative coordinate - jsr SixSpriteStacker ;store X coordinates using A as base, stack horizontally - ldx ObjectOffset - lda Enemy_Y_Position,x ;get vertical coordinate - jsr DumpFourSpr ;dump into first four sprites as Y coordinate - ldy AreaType - cpy #$03 ;check for castle-type level - beq ShrinkPlatform - ldy SecondaryHardMode ;check for secondary hard mode flag set - beq SetLast2Platform ;branch if not set elsewhere - -ShrinkPlatform: - lda #$f8 ;load offscreen coordinate if flag set or castle-type level - -SetLast2Platform: - ldy Enemy_SprDataOffset,x ;get OAM data offset - sta Sprite_Y_Position+16,y ;store vertical coordinate or offscreen - sta Sprite_Y_Position+20,y ;coordinate into last two sprites as Y coordinate - lda #$5b ;load default tile for platform (girder) - ldx CloudTypeOverride - beq SetPlatformTilenum ;if cloud level override flag not set, use - lda #$75 ;otherwise load other tile for platform (puff) - -SetPlatformTilenum: - ldx ObjectOffset ;get enemy object buffer offset - iny ;increment Y for tile offset - jsr DumpSixSpr ;dump tile number into all six sprites - lda #$02 ;set palette controls - iny ;increment Y for sprite attributes - jsr DumpSixSpr ;dump attributes into all six sprites - inx ;increment X for enemy objects - jsr GetXOffscreenBits ;get offscreen bits again - dex - ldy Enemy_SprDataOffset,x ;get OAM data offset - asl ;rotate d7 into carry, save remaining - pha ;bits to the stack - bcc SChk2 - lda #$f8 ;if d7 was set, move first sprite offscreen - sta Sprite_Y_Position,y -SChk2: pla ;get bits from stack - asl ;rotate d6 into carry - pha ;save to stack - bcc SChk3 - lda #$f8 ;if d6 was set, move second sprite offscreen - sta Sprite_Y_Position+4,y -SChk3: pla ;get bits from stack - asl ;rotate d5 into carry - pha ;save to stack - bcc SChk4 - lda #$f8 ;if d5 was set, move third sprite offscreen - sta Sprite_Y_Position+8,y -SChk4: pla ;get bits from stack - asl ;rotate d4 into carry - pha ;save to stack - bcc SChk5 - lda #$f8 ;if d4 was set, move fourth sprite offscreen - sta Sprite_Y_Position+12,y -SChk5: pla ;get bits from stack - asl ;rotate d3 into carry - pha ;save to stack - bcc SChk6 - lda #$f8 ;if d3 was set, move fifth sprite offscreen - sta Sprite_Y_Position+16,y -SChk6: pla ;get bits from stack - asl ;rotate d2 into carry - bcc SLChk ;save to stack - lda #$f8 - sta Sprite_Y_Position+20,y ;if d2 was set, move sixth sprite offscreen -SLChk: lda Enemy_OffscreenBits ;check d7 of offscreen bits - asl ;and if d7 is not set, skip sub - bcc ExDLPl - jsr MoveSixSpritesOffscreen ;otherwise branch to move all sprites offscreen -ExDLPl: rts - -;------------------------------------------------------------------------------------- - -DrawFloateyNumber_Coin: - lda FrameCounter ;get frame counter - lsr ;divide by 2 - bcs NotRsNum ;branch if d0 not set to raise number every other frame - dec Misc_Y_Position,x ;otherwise, decrement vertical coordinate -NotRsNum: lda Misc_Y_Position,x ;get vertical coordinate - jsr DumpTwoSpr ;dump into both sprites - lda Misc_Rel_XPos ;get relative horizontal coordinate - sta Sprite_X_Position,y ;store as X coordinate for first sprite - clc - adc #$08 ;add eight pixels - sta Sprite_X_Position+4,y ;store as X coordinate for second sprite - lda #$02 - sta Sprite_Attributes,y ;store attribute byte in both sprites - sta Sprite_Attributes+4,y - lda #$f7 - sta Sprite_Tilenumber,y ;put tile numbers into both sprites - lda #$fb ;that resemble "200" - sta Sprite_Tilenumber+4,y - jmp ExJCGfx ;then jump to leave (why not an rts here instead?) - -JumpingCoinTiles: - .db $60, $61, $62, $63 - -JCoinGfxHandler: - ldy Misc_SprDataOffset,x ;get coin/floatey number's OAM data offset - lda Misc_State,x ;get state of misc object - cmp #$02 ;if 2 or greater, - bcs DrawFloateyNumber_Coin ;branch to draw floatey number - lda Misc_Y_Position,x ;store vertical coordinate as - sta Sprite_Y_Position,y ;Y coordinate for first sprite - clc - adc #$08 ;add eight pixels - sta Sprite_Y_Position+4,y ;store as Y coordinate for second sprite - lda Misc_Rel_XPos ;get relative horizontal coordinate - sta Sprite_X_Position,y - sta Sprite_X_Position+4,y ;store as X coordinate for first and second sprites - lda FrameCounter ;get frame counter - lsr ;divide by 2 to alter every other frame - and #%00000011 ;mask out d2-d1 - tax ;use as graphical offset - lda JumpingCoinTiles,x ;load tile number - iny ;increment OAM data offset to write tile numbers - jsr DumpTwoSpr ;do sub to dump tile number into both sprites - dey ;decrement to get old offset - lda #$02 - sta Sprite_Attributes,y ;set attribute byte in first sprite - lda #$82 - sta Sprite_Attributes+4,y ;set attribute byte with vertical flip in second sprite - ldx ObjectOffset ;get misc object offset -ExJCGfx: rts ;leave - -;------------------------------------------------------------------------------------- -;$00-$01 - used to hold tiles for drawing the power-up, $00 also used to hold power-up type -;$02 - used to hold bottom row Y position -;$03 - used to hold flip control (not used here) -;$04 - used to hold sprite attributes -;$05 - used to hold X position -;$07 - counter - -;tiles arranged in top left, right, bottom left, right order -PowerUpGfxTable: - .db $76, $77, $78, $79 ;regular mushroom - .db $d6, $d6, $d9, $d9 ;fire flower - .db $8d, $8d, $e4, $e4 ;star - .db $76, $77, $78, $79 ;1-up mushroom - -PowerUpAttributes: - .db $02, $01, $02, $01 - -DrawPowerUp: - ldy Enemy_SprDataOffset+5 ;get power-up's sprite data offset - lda Enemy_Rel_YPos ;get relative vertical coordinate - clc - adc #$08 ;add eight pixels - sta $02 ;store result here - lda Enemy_Rel_XPos ;get relative horizontal coordinate - sta $05 ;store here - ldx PowerUpType ;get power-up type - lda PowerUpAttributes,x ;get attribute data for power-up type - ora Enemy_SprAttrib+5 ;add background priority bit if set - sta $04 ;store attributes here - txa - pha ;save power-up type to the stack - asl - asl ;multiply by four to get proper offset - tax ;use as X - lda #$01 - sta $07 ;set counter here to draw two rows of sprite object - sta $03 ;init d1 of flip control - -PUpDrawLoop: - lda PowerUpGfxTable,x ;load left tile of power-up object - sta $00 - lda PowerUpGfxTable+1,x ;load right tile - jsr DrawOneSpriteRow ;branch to draw one row of our power-up object - dec $07 ;decrement counter - bpl PUpDrawLoop ;branch until two rows are drawn - ldy Enemy_SprDataOffset+5 ;get sprite data offset again - pla ;pull saved power-up type from the stack - beq PUpOfs ;if regular mushroom, branch, do not change colors or flip - cmp #$03 - beq PUpOfs ;if 1-up mushroom, branch, do not change colors or flip - sta $00 ;store power-up type here now - lda FrameCounter ;get frame counter - lsr ;divide by 2 to change colors every two frames - and #%00000011 ;mask out all but d1 and d0 (previously d2 and d1) - ora Enemy_SprAttrib+5 ;add background priority bit if any set - sta Sprite_Attributes,y ;set as new palette bits for top left and - sta Sprite_Attributes+4,y ;top right sprites for fire flower and star - ldx $00 - dex ;check power-up type for fire flower - beq FlipPUpRightSide ;if found, skip this part - sta Sprite_Attributes+8,y ;otherwise set new palette bits for bottom left - sta Sprite_Attributes+12,y ;and bottom right sprites as well for star only - -FlipPUpRightSide: - lda Sprite_Attributes+4,y - ora #%01000000 ;set horizontal flip bit for top right sprite - sta Sprite_Attributes+4,y - lda Sprite_Attributes+12,y - ora #%01000000 ;set horizontal flip bit for bottom right sprite - sta Sprite_Attributes+12,y ;note these are only done for fire flower and star power-ups -PUpOfs: jmp SprObjectOffscrChk ;jump to check to see if power-up is offscreen at all, then leave - -;------------------------------------------------------------------------------------- -;$00-$01 - used in DrawEnemyObjRow to hold sprite tile numbers -;$02 - used to store Y position -;$03 - used to store moving direction, used to flip enemies horizontally -;$04 - used to store enemy's sprite attributes -;$05 - used to store X position -;$eb - used to hold sprite data offset -;$ec - used to hold either altered enemy state or special value used in gfx handler as condition -;$ed - used to hold enemy state from buffer -;$ef - used to hold enemy code used in gfx handler (may or may not resemble Enemy_ID values) - -;tiles arranged in top left, right, middle left, right, bottom left, right order -EnemyGraphicsTable: - .db $fc, $fc, $aa, $ab, $ac, $ad ;buzzy beetle frame 1 - .db $fc, $fc, $ae, $af, $b0, $b1 ; frame 2 - .db $fc, $a5, $a6, $a7, $a8, $a9 ;koopa troopa frame 1 - .db $fc, $a0, $a1, $a2, $a3, $a4 ; frame 2 - .db $69, $a5, $6a, $a7, $a8, $a9 ;koopa paratroopa frame 1 - .db $6b, $a0, $6c, $a2, $a3, $a4 ; frame 2 - .db $fc, $fc, $96, $97, $98, $99 ;spiny frame 1 - .db $fc, $fc, $9a, $9b, $9c, $9d ; frame 2 - .db $fc, $fc, $8f, $8e, $8e, $8f ;spiny's egg frame 1 - .db $fc, $fc, $95, $94, $94, $95 ; frame 2 - .db $fc, $fc, $dc, $dc, $df, $df ;bloober frame 1 - .db $dc, $dc, $dd, $dd, $de, $de ; frame 2 - .db $fc, $fc, $b2, $b3, $b4, $b5 ;cheep-cheep frame 1 - .db $fc, $fc, $b6, $b3, $b7, $b5 ; frame 2 - .db $fc, $fc, $70, $71, $72, $73 ;goomba - .db $fc, $fc, $6e, $6e, $6f, $6f ;koopa shell frame 1 (upside-down) - .db $fc, $fc, $6d, $6d, $6f, $6f ; frame 2 - .db $fc, $fc, $6f, $6f, $6e, $6e ;koopa shell frame 1 (rightsideup) - .db $fc, $fc, $6f, $6f, $6d, $6d ; frame 2 - .db $fc, $fc, $f4, $f4, $f5, $f5 ;buzzy beetle shell frame 1 (rightsideup) - .db $fc, $fc, $f4, $f4, $f5, $f5 ; frame 2 - .db $fc, $fc, $f5, $f5, $f4, $f4 ;buzzy beetle shell frame 1 (upside-down) - .db $fc, $fc, $f5, $f5, $f4, $f4 ; frame 2 - .db $fc, $fc, $fc, $fc, $ef, $ef ;defeated goomba - .db $b9, $b8, $bb, $ba, $bc, $bc ;lakitu frame 1 - .db $fc, $fc, $bd, $bd, $bc, $bc ; frame 2 - .db $7a, $7b, $da, $db, $d8, $d8 ;princess - .db $cd, $cd, $ce, $ce, $cf, $cf ;mushroom retainer - .db $7d, $7c, $d1, $8c, $d3, $d2 ;hammer bro frame 1 - .db $7d, $7c, $89, $88, $8b, $8a ; frame 2 - .db $d5, $d4, $e3, $e2, $d3, $d2 ; frame 3 - .db $d5, $d4, $e3, $e2, $8b, $8a ; frame 4 - .db $e5, $e5, $e6, $e6, $eb, $eb ;piranha plant frame 1 - .db $ec, $ec, $ed, $ed, $ee, $ee ; frame 2 - .db $fc, $fc, $d0, $d0, $d7, $d7 ;podoboo - .db $bf, $be, $c1, $c0, $c2, $fc ;bowser front frame 1 - .db $c4, $c3, $c6, $c5, $c8, $c7 ;bowser rear frame 1 - .db $bf, $be, $ca, $c9, $c2, $fc ; front frame 2 - .db $c4, $c3, $c6, $c5, $cc, $cb ; rear frame 2 - .db $fc, $fc, $e8, $e7, $ea, $e9 ;bullet bill - .db $f2, $f2, $f3, $f3, $f2, $f2 ;jumpspring frame 1 - .db $f1, $f1, $f1, $f1, $fc, $fc ; frame 2 - .db $f0, $f0, $fc, $fc, $fc, $fc ; frame 3 - -EnemyGfxTableOffsets: - .db $0c, $0c, $00, $0c, $0c, $a8, $54, $3c - .db $ea, $18, $48, $48, $cc, $c0, $18, $18 - .db $18, $90, $24, $ff, $48, $9c, $d2, $d8 - .db $f0, $f6, $fc - -EnemyAttributeData: - .db $01, $02, $03, $02, $01, $01, $03, $03 - .db $03, $01, $01, $02, $02, $21, $01, $02 - .db $01, $01, $02, $ff, $02, $02, $01, $01 - .db $02, $02, $02 - -EnemyAnimTimingBMask: - .db $08, $18 - -JumpspringFrameOffsets: - .db $18, $19, $1a, $19, $18 - -EnemyGfxHandler: - lda Enemy_Y_Position,x ;get enemy object vertical position - sta $02 - lda Enemy_Rel_XPos ;get enemy object horizontal position - sta $05 ;relative to screen - ldy Enemy_SprDataOffset,x - sty $eb ;get sprite data offset - lda #$00 - sta VerticalFlipFlag ;initialize vertical flip flag by default - lda Enemy_MovingDir,x - sta $03 ;get enemy object moving direction - lda Enemy_SprAttrib,x - sta $04 ;get enemy object sprite attributes - lda Enemy_ID,x - cmp #PiranhaPlant ;is enemy object piranha plant? - bne CheckForRetainerObj ;if not, branch - ldy PiranhaPlant_Y_Speed,x - bmi CheckForRetainerObj ;if piranha plant moving upwards, branch - ldy EnemyFrameTimer,x - beq CheckForRetainerObj ;if timer for movement expired, branch - rts ;if all conditions fail, leave - -CheckForRetainerObj: - lda Enemy_State,x ;store enemy state - sta $ed - and #%00011111 ;nullify all but 5 LSB and use as Y - tay - lda Enemy_ID,x ;check for mushroom retainer/princess object - cmp #RetainerObject - bne CheckForBulletBillCV ;if not found, branch - ldy #$00 ;if found, nullify saved state in Y - lda #$01 ;set value that will not be used - sta $03 - lda #$15 ;set value $15 as code for mushroom retainer/princess object - -CheckForBulletBillCV: - cmp #BulletBill_CannonVar ;otherwise check for bullet bill object - bne CheckForJumpspring ;if not found, branch again - dec $02 ;decrement saved vertical position - lda #$03 - ldy EnemyFrameTimer,x ;get timer for enemy object - beq SBBAt ;if expired, do not set priority bit - ora #%00100000 ;otherwise do so -SBBAt: sta $04 ;set new sprite attributes - ldy #$00 ;nullify saved enemy state both in Y and in - sty $ed ;memory location here - lda #$08 ;set specific value to unconditionally branch once - -CheckForJumpspring: - cmp #JumpspringObject ;check for jumpspring object - bne CheckForPodoboo - ldy #$03 ;set enemy state -2 MSB here for jumpspring object - ldx JumpspringAnimCtrl ;get current frame number for jumpspring object - lda JumpspringFrameOffsets,x ;load data using frame number as offset - -CheckForPodoboo: - sta $ef ;store saved enemy object value here - sty $ec ;and Y here (enemy state -2 MSB if not changed) - ldx ObjectOffset ;get enemy object offset - cmp #$0c ;check for podoboo object - bne CheckBowserGfxFlag ;branch if not found - lda Enemy_Y_Speed,x ;if moving upwards, branch - bmi CheckBowserGfxFlag - inc VerticalFlipFlag ;otherwise, set flag for vertical flip - -CheckBowserGfxFlag: - lda BowserGfxFlag ;if not drawing bowser at all, skip to something else - beq CheckForGoomba - ldy #$16 ;if set to 1, draw bowser's front - cmp #$01 - beq SBwsrGfxOfs - iny ;otherwise draw bowser's rear -SBwsrGfxOfs: sty $ef - -CheckForGoomba: - ldy $ef ;check value for goomba object - cpy #Goomba - bne CheckBowserFront ;branch if not found - lda Enemy_State,x - cmp #$02 ;check for defeated state - bcc GmbaAnim ;if not defeated, go ahead and animate - ldx #$04 ;if defeated, write new value here - stx $ec -GmbaAnim: and #%00100000 ;check for d5 set in enemy object state - ora TimerControl ;or timer disable flag set - bne CheckBowserFront ;if either condition true, do not animate goomba - lda FrameCounter - and #%00001000 ;check for every eighth frame - bne CheckBowserFront - lda $03 - eor #%00000011 ;invert bits to flip horizontally every eight frames - sta $03 ;leave alone otherwise - -CheckBowserFront: - lda EnemyAttributeData,y ;load sprite attribute using enemy object - ora $04 ;as offset, and add to bits already loaded - sta $04 - lda EnemyGfxTableOffsets,y ;load value based on enemy object as offset - tax ;save as X - ldy $ec ;get previously saved value - lda BowserGfxFlag - beq CheckForSpiny ;if not drawing bowser object at all, skip all of this - cmp #$01 - bne CheckBowserRear ;if not drawing front part, branch to draw the rear part - lda BowserBodyControls ;check bowser's body control bits - bpl ChkFrontSte ;branch if d7 not set (control's bowser's mouth) - ldx #$de ;otherwise load offset for second frame -ChkFrontSte: lda $ed ;check saved enemy state - and #%00100000 ;if bowser not defeated, do not set flag - beq DrawBowser - -FlipBowserOver: - stx VerticalFlipFlag ;set vertical flip flag to nonzero - -DrawBowser: - jmp DrawEnemyObject ;draw bowser's graphics now - -CheckBowserRear: - lda BowserBodyControls ;check bowser's body control bits - and #$01 - beq ChkRearSte ;branch if d0 not set (control's bowser's feet) - ldx #$e4 ;otherwise load offset for second frame -ChkRearSte: lda $ed ;check saved enemy state - and #%00100000 ;if bowser not defeated, do not set flag - beq DrawBowser - lda $02 ;subtract 16 pixels from - sec ;saved vertical coordinate - sbc #$10 - sta $02 - jmp FlipBowserOver ;jump to set vertical flip flag - -CheckForSpiny: - cpx #$24 ;check if value loaded is for spiny - bne CheckForLakitu ;if not found, branch - cpy #$05 ;if enemy state set to $05, do this, - bne NotEgg ;otherwise branch - ldx #$30 ;set to spiny egg offset - lda #$02 - sta $03 ;set enemy direction to reverse sprites horizontally - lda #$05 - sta $ec ;set enemy state -NotEgg: jmp CheckForHammerBro ;skip a big chunk of this if we found spiny but not in egg - -CheckForLakitu: - cpx #$90 ;check value for lakitu's offset loaded - bne CheckUpsideDownShell ;branch if not loaded - lda $ed - and #%00100000 ;check for d5 set in enemy state - bne NoLAFr ;branch if set - lda FrenzyEnemyTimer - cmp #$10 ;check timer to see if we've reached a certain range - bcs NoLAFr ;branch if not - ldx #$96 ;if d6 not set and timer in range, load alt frame for lakitu -NoLAFr: jmp CheckDefeatedState ;skip this next part if we found lakitu but alt frame not needed - -CheckUpsideDownShell: - lda $ef ;check for enemy object => $04 - cmp #$04 - bcs CheckRightSideUpShell ;branch if true - cpy #$02 - bcc CheckRightSideUpShell ;branch if enemy state < $02 - ldx #$5a ;set for upside-down koopa shell by default - ldy $ef - cpy #BuzzyBeetle ;check for buzzy beetle object - bne CheckRightSideUpShell - ldx #$7e ;set for upside-down buzzy beetle shell if found - inc $02 ;increment vertical position by one pixel - -CheckRightSideUpShell: - lda $ec ;check for value set here - cmp #$04 ;if enemy state < $02, do not change to shell, if - bne CheckForHammerBro ;enemy state => $02 but not = $04, leave shell upside-down - ldx #$72 ;set right-side up buzzy beetle shell by default - inc $02 ;increment saved vertical position by one pixel - ldy $ef - cpy #BuzzyBeetle ;check for buzzy beetle object - beq CheckForDefdGoomba ;branch if found - ldx #$66 ;change to right-side up koopa shell if not found - inc $02 ;and increment saved vertical position again - -CheckForDefdGoomba: - cpy #Goomba ;check for goomba object (necessary if previously - bne CheckForHammerBro ;failed buzzy beetle object test) - ldx #$54 ;load for regular goomba - lda $ed ;note that this only gets performed if enemy state => $02 - and #%00100000 ;check saved enemy state for d5 set - bne CheckForHammerBro ;branch if set - ldx #$8a ;load offset for defeated goomba - dec $02 ;set different value and decrement saved vertical position - -CheckForHammerBro: - ldy ObjectOffset - lda $ef ;check for hammer bro object - cmp #HammerBro - bne CheckForBloober ;branch if not found - lda $ed - beq CheckToAnimateEnemy ;branch if not in normal enemy state - and #%00001000 - beq CheckDefeatedState ;if d3 not set, branch further away - ldx #$b4 ;otherwise load offset for different frame - bne CheckToAnimateEnemy ;unconditional branch - -CheckForBloober: - cpx #$48 ;check for cheep-cheep offset loaded - beq CheckToAnimateEnemy ;branch if found - lda EnemyIntervalTimer,y - cmp #$05 - bcs CheckDefeatedState ;branch if some timer is above a certain point - cpx #$3c ;check for bloober offset loaded - bne CheckToAnimateEnemy ;branch if not found this time - cmp #$01 - beq CheckDefeatedState ;branch if timer is set to certain point - inc $02 ;increment saved vertical coordinate three pixels - inc $02 - inc $02 - jmp CheckAnimationStop ;and do something else - -CheckToAnimateEnemy: - lda $ef ;check for specific enemy objects - cmp #Goomba - beq CheckDefeatedState ;branch if goomba - cmp #$08 - beq CheckDefeatedState ;branch if bullet bill (note both variants use $08 here) - cmp #Podoboo - beq CheckDefeatedState ;branch if podoboo - cmp #$18 ;branch if => $18 - bcs CheckDefeatedState - ldy #$00 - cmp #$15 ;check for mushroom retainer/princess object - bne CheckForSecondFrame ;which uses different code here, branch if not found - iny ;residual instruction - lda WorldNumber ;are we on world 8? - cmp #World8 - bcs CheckDefeatedState ;if so, leave the offset alone (use princess) - ldx #$a2 ;otherwise, set for mushroom retainer object instead - lda #$03 ;set alternate state here - sta $ec - bne CheckDefeatedState ;unconditional branch - -CheckForSecondFrame: - lda FrameCounter ;load frame counter - and EnemyAnimTimingBMask,y ;mask it (partly residual, one byte not ever used) - bne CheckDefeatedState ;branch if timing is off - -CheckAnimationStop: - lda $ed ;check saved enemy state - and #%10100000 ;for d7 or d5, or check for timers stopped - ora TimerControl - bne CheckDefeatedState ;if either condition true, branch - txa - clc - adc #$06 ;add $06 to current enemy offset - tax ;to animate various enemy objects - -CheckDefeatedState: - lda $ed ;check saved enemy state - and #%00100000 ;for d5 set - beq DrawEnemyObject ;branch if not set - lda $ef - cmp #$04 ;check for saved enemy object => $04 - bcc DrawEnemyObject ;branch if less - ldy #$01 - sty VerticalFlipFlag ;set vertical flip flag - dey - sty $ec ;init saved value here - -DrawEnemyObject: - ldy $eb ;load sprite data offset - jsr DrawEnemyObjRow ;draw six tiles of data - jsr DrawEnemyObjRow ;into sprite data - jsr DrawEnemyObjRow - ldx ObjectOffset ;get enemy object offset - ldy Enemy_SprDataOffset,x ;get sprite data offset - lda $ef - cmp #$08 ;get saved enemy object and check - bne CheckForVerticalFlip ;for bullet bill, branch if not found - -SkipToOffScrChk: - jmp SprObjectOffscrChk ;jump if found - -CheckForVerticalFlip: - lda VerticalFlipFlag ;check if vertical flip flag is set here - beq CheckForESymmetry ;branch if not - lda Sprite_Attributes,y ;get attributes of first sprite we dealt with - ora #%10000000 ;set bit for vertical flip - iny - iny ;increment two bytes so that we store the vertical flip - jsr DumpSixSpr ;in attribute bytes of enemy obj sprite data - dey - dey ;now go back to the Y coordinate offset - tya - tax ;give offset to X - lda $ef - cmp #HammerBro ;check saved enemy object for hammer bro - beq FlipEnemyVertically - cmp #Lakitu ;check saved enemy object for lakitu - beq FlipEnemyVertically ;branch for hammer bro or lakitu - cmp #$15 - bcs FlipEnemyVertically ;also branch if enemy object => $15 - txa - clc - adc #$08 ;if not selected objects or => $15, set - tax ;offset in X for next row - -FlipEnemyVertically: - lda Sprite_Tilenumber,x ;load first or second row tiles - pha ;and save tiles to the stack - lda Sprite_Tilenumber+4,x - pha - lda Sprite_Tilenumber+16,y ;exchange third row tiles - sta Sprite_Tilenumber,x ;with first or second row tiles - lda Sprite_Tilenumber+20,y - sta Sprite_Tilenumber+4,x - pla ;pull first or second row tiles from stack - sta Sprite_Tilenumber+20,y ;and save in third row - pla - sta Sprite_Tilenumber+16,y - -CheckForESymmetry: - lda BowserGfxFlag ;are we drawing bowser at all? - bne SkipToOffScrChk ;branch if so - lda $ef - ldx $ec ;get alternate enemy state - cmp #$05 ;check for hammer bro object - bne ContES - jmp SprObjectOffscrChk ;jump if found -ContES: cmp #Bloober ;check for bloober object - beq MirrorEnemyGfx - cmp #PiranhaPlant ;check for piranha plant object - beq MirrorEnemyGfx - cmp #Podoboo ;check for podoboo object - beq MirrorEnemyGfx ;branch if either of three are found - cmp #Spiny ;check for spiny object - bne ESRtnr ;branch closer if not found - cpx #$05 ;check spiny's state - bne CheckToMirrorLakitu ;branch if not an egg, otherwise -ESRtnr: cmp #$15 ;check for princess/mushroom retainer object - bne SpnySC - lda #$42 ;set horizontal flip on bottom right sprite - sta Sprite_Attributes+20,y ;note that palette bits were already set earlier -SpnySC: cpx #$02 ;if alternate enemy state set to 1 or 0, branch - bcc CheckToMirrorLakitu - -MirrorEnemyGfx: - lda BowserGfxFlag ;if enemy object is bowser, skip all of this - bne CheckToMirrorLakitu - lda Sprite_Attributes,y ;load attribute bits of first sprite - and #%10100011 - sta Sprite_Attributes,y ;save vertical flip, priority, and palette bits - sta Sprite_Attributes+8,y ;in left sprite column of enemy object OAM data - sta Sprite_Attributes+16,y - ora #%01000000 ;set horizontal flip - cpx #$05 ;check for state used by spiny's egg - bne EggExc ;if alternate state not set to $05, branch - ora #%10000000 ;otherwise set vertical flip -EggExc: sta Sprite_Attributes+4,y ;set bits of right sprite column - sta Sprite_Attributes+12,y ;of enemy object sprite data - sta Sprite_Attributes+20,y - cpx #$04 ;check alternate enemy state - bne CheckToMirrorLakitu ;branch if not $04 - lda Sprite_Attributes+8,y ;get second row left sprite attributes - ora #%10000000 - sta Sprite_Attributes+8,y ;store bits with vertical flip in - sta Sprite_Attributes+16,y ;second and third row left sprites - ora #%01000000 - sta Sprite_Attributes+12,y ;store with horizontal and vertical flip in - sta Sprite_Attributes+20,y ;second and third row right sprites - -CheckToMirrorLakitu: - lda $ef ;check for lakitu enemy object - cmp #Lakitu - bne CheckToMirrorJSpring ;branch if not found - lda VerticalFlipFlag - bne NVFLak ;branch if vertical flip flag set - lda Sprite_Attributes+16,y ;save vertical flip and palette bits - and #%10000001 ;in third row left sprite - sta Sprite_Attributes+16,y - lda Sprite_Attributes+20,y ;set horizontal flip and palette bits - ora #%01000001 ;in third row right sprite - sta Sprite_Attributes+20,y - ldx FrenzyEnemyTimer ;check timer - cpx #$10 - bcs SprObjectOffscrChk ;branch if timer has not reached a certain range - sta Sprite_Attributes+12,y ;otherwise set same for second row right sprite - and #%10000001 - sta Sprite_Attributes+8,y ;preserve vertical flip and palette bits for left sprite - bcc SprObjectOffscrChk ;unconditional branch -NVFLak: lda Sprite_Attributes,y ;get first row left sprite attributes - and #%10000001 - sta Sprite_Attributes,y ;save vertical flip and palette bits - lda Sprite_Attributes+4,y ;get first row right sprite attributes - ora #%01000001 ;set horizontal flip and palette bits - sta Sprite_Attributes+4,y ;note that vertical flip is left as-is - -CheckToMirrorJSpring: - lda $ef ;check for jumpspring object (any frame) - cmp #$18 - bcc SprObjectOffscrChk ;branch if not jumpspring object at all - lda #$82 - sta Sprite_Attributes+8,y ;set vertical flip and palette bits of - sta Sprite_Attributes+16,y ;second and third row left sprites - ora #%01000000 - sta Sprite_Attributes+12,y ;set, in addition to those, horizontal flip - sta Sprite_Attributes+20,y ;for second and third row right sprites - -SprObjectOffscrChk: - ldx ObjectOffset ;get enemy buffer offset - lda Enemy_OffscreenBits ;check offscreen information - lsr - lsr ;shift three times to the right - lsr ;which puts d2 into carry - pha ;save to stack - bcc LcChk ;branch if not set - lda #$04 ;set for right column sprites - jsr MoveESprColOffscreen ;and move them offscreen -LcChk: pla ;get from stack - lsr ;move d3 to carry - pha ;save to stack - bcc Row3C ;branch if not set - lda #$00 ;set for left column sprites, - jsr MoveESprColOffscreen ;move them offscreen -Row3C: pla ;get from stack again - lsr ;move d5 to carry this time - lsr - pha ;save to stack again - bcc Row23C ;branch if carry not set - lda #$10 ;set for third row of sprites - jsr MoveESprRowOffscreen ;and move them offscreen -Row23C: pla ;get from stack - lsr ;move d6 into carry - pha ;save to stack - bcc AllRowC - lda #$08 ;set for second and third rows - jsr MoveESprRowOffscreen ;move them offscreen -AllRowC: pla ;get from stack once more - lsr ;move d7 into carry - bcc ExEGHandler - jsr MoveESprRowOffscreen ;move all sprites offscreen (A should be 0 by now) - lda Enemy_ID,x - cmp #Podoboo ;check enemy identifier for podoboo - beq ExEGHandler ;skip this part if found, we do not want to erase podoboo! - lda Enemy_Y_HighPos,x ;check high byte of vertical position - cmp #$02 ;if not yet past the bottom of the screen, branch - bne ExEGHandler - jsr EraseEnemyObject ;what it says - -ExEGHandler: - rts - -DrawEnemyObjRow: - lda EnemyGraphicsTable,x ;load two tiles of enemy graphics - sta $00 - lda EnemyGraphicsTable+1,x - -DrawOneSpriteRow: - sta $01 - jmp DrawSpriteObject ;draw them - -MoveESprRowOffscreen: - clc ;add A to enemy object OAM data offset - adc Enemy_SprDataOffset,x - tay ;use as offset - lda #$f8 - jmp DumpTwoSpr ;move first row of sprites offscreen - -MoveESprColOffscreen: - clc ;add A to enemy object OAM data offset - adc Enemy_SprDataOffset,x - tay ;use as offset - jsr MoveColOffscreen ;move first and second row sprites in column offscreen - sta Sprite_Data+16,y ;move third row sprite in column offscreen - rts - -;------------------------------------------------------------------------------------- -;$00-$01 - tile numbers -;$02 - relative Y position -;$03 - horizontal flip flag (not used here) -;$04 - attributes -;$05 - relative X position - -DefaultBlockObjTiles: - .db $85, $85, $86, $86 ;brick w/ line (these are sprite tiles, not BG!) - -DrawBlock: - lda Block_Rel_YPos ;get relative vertical coordinate of block object - sta $02 ;store here - lda Block_Rel_XPos ;get relative horizontal coordinate of block object - sta $05 ;store here - lda #$03 - sta $04 ;set attribute byte here - lsr - sta $03 ;set horizontal flip bit here (will not be used) - ldy Block_SprDataOffset,x ;get sprite data offset - ldx #$00 ;reset X for use as offset to tile data -DBlkLoop: lda DefaultBlockObjTiles,x ;get left tile number - sta $00 ;set here - lda DefaultBlockObjTiles+1,x ;get right tile number - jsr DrawOneSpriteRow ;do sub to write tile numbers to first row of sprites - cpx #$04 ;check incremented offset - bne DBlkLoop ;and loop back until all four sprites are done - ldx ObjectOffset ;get block object offset - ldy Block_SprDataOffset,x ;get sprite data offset - lda AreaType - cmp #$01 ;check for ground level type area - beq ChkRep ;if found, branch to next part - lda #$86 - sta Sprite_Tilenumber,y ;otherwise remove brick tiles with lines - sta Sprite_Tilenumber+4,y ;and replace then with lineless brick tiles -ChkRep: lda Block_Metatile,x ;check replacement metatile - cmp #$c4 ;if not used block metatile, then - bne BlkOffscr ;branch ahead to use current graphics - lda #$87 ;set A for used block tile - iny ;increment Y to write to tile bytes - jsr DumpFourSpr ;do sub to dump into all four sprites - dey ;return Y to original offset - lda #$03 ;set palette bits - ldx AreaType - dex ;check for ground level type area again - beq SetBFlip ;if found, use current palette bits - lsr ;otherwise set to $01 -SetBFlip: ldx ObjectOffset ;put block object offset back in X - sta Sprite_Attributes,y ;store attribute byte as-is in first sprite - ora #%01000000 - sta Sprite_Attributes+4,y ;set horizontal flip bit for second sprite - ora #%10000000 - sta Sprite_Attributes+12,y ;set both flip bits for fourth sprite - and #%10000011 - sta Sprite_Attributes+8,y ;set vertical flip bit for third sprite -BlkOffscr: lda Block_OffscreenBits ;get offscreen bits for block object - pha ;save to stack - and #%00000100 ;check to see if d2 in offscreen bits are set - beq PullOfsB ;if not set, branch, otherwise move sprites offscreen - lda #$f8 ;move offscreen two OAMs - sta Sprite_Y_Position+4,y ;on the right side - sta Sprite_Y_Position+12,y -PullOfsB: pla ;pull offscreen bits from stack -ChkLeftCo: and #%00001000 ;check to see if d3 in offscreen bits are set - beq ExDBlk ;if not set, branch, otherwise move sprites offscreen - -MoveColOffscreen: - lda #$f8 ;move offscreen two OAMs - sta Sprite_Y_Position,y ;on the left side (or two rows of enemy on either side - sta Sprite_Y_Position+8,y ;if branched here from enemy graphics handler) -ExDBlk: rts - -;------------------------------------------------------------------------------------- -;$00 - used to hold palette bits for attribute byte or relative X position - -DrawBrickChunks: - lda #$02 ;set palette bits here - sta $00 - lda #$75 ;set tile number for ball (something residual, likely) - ldy GameEngineSubroutine - cpy #$05 ;if end-of-level routine running, - beq DChunks ;use palette and tile number assigned - lda #$03 ;otherwise set different palette bits - sta $00 - lda #$84 ;and set tile number for brick chunks -DChunks: ldy Block_SprDataOffset,x ;get OAM data offset - iny ;increment to start with tile bytes in OAM - jsr DumpFourSpr ;do sub to dump tile number into all four sprites - lda FrameCounter ;get frame counter - asl - asl - asl ;move low nybble to high - asl - and #$c0 ;get what was originally d3-d2 of low nybble - ora $00 ;add palette bits - iny ;increment offset for attribute bytes - jsr DumpFourSpr ;do sub to dump attribute data into all four sprites - dey - dey ;decrement offset to Y coordinate - lda Block_Rel_YPos ;get first block object's relative vertical coordinate - jsr DumpTwoSpr ;do sub to dump current Y coordinate into two sprites - lda Block_Rel_XPos ;get first block object's relative horizontal coordinate - sta Sprite_X_Position,y ;save into X coordinate of first sprite - lda Block_Orig_XPos,x ;get original horizontal coordinate - sec - sbc ScreenLeft_X_Pos ;subtract coordinate of left side from original coordinate - sta $00 ;store result as relative horizontal coordinate of original - sec - sbc Block_Rel_XPos ;get difference of relative positions of original - current - adc $00 ;add original relative position to result - adc #$06 ;plus 6 pixels to position second brick chunk correctly - sta Sprite_X_Position+4,y ;save into X coordinate of second sprite - lda Block_Rel_YPos+1 ;get second block object's relative vertical coordinate - sta Sprite_Y_Position+8,y - sta Sprite_Y_Position+12,y ;dump into Y coordinates of third and fourth sprites - lda Block_Rel_XPos+1 ;get second block object's relative horizontal coordinate - sta Sprite_X_Position+8,y ;save into X coordinate of third sprite - lda $00 ;use original relative horizontal position - sec - sbc Block_Rel_XPos+1 ;get difference of relative positions of original - current - adc $00 ;add original relative position to result - adc #$06 ;plus 6 pixels to position fourth brick chunk correctly - sta Sprite_X_Position+12,y ;save into X coordinate of fourth sprite - lda Block_OffscreenBits ;get offscreen bits for block object - jsr ChkLeftCo ;do sub to move left half of sprites offscreen if necessary - lda Block_OffscreenBits ;get offscreen bits again - asl ;shift d7 into carry - bcc ChnkOfs ;if d7 not set, branch to last part - lda #$f8 - jsr DumpTwoSpr ;otherwise move top sprites offscreen -ChnkOfs: lda $00 ;if relative position on left side of screen, - bpl ExBCDr ;go ahead and leave - lda Sprite_X_Position,y ;otherwise compare left-side X coordinate - cmp Sprite_X_Position+4,y ;to right-side X coordinate - bcc ExBCDr ;branch to leave if less - lda #$f8 ;otherwise move right half of sprites offscreen - sta Sprite_Y_Position+4,y - sta Sprite_Y_Position+12,y -ExBCDr: rts ;leave - -;------------------------------------------------------------------------------------- - -DrawFireball: - ldy FBall_SprDataOffset,x ;get fireball's sprite data offset - lda Fireball_Rel_YPos ;get relative vertical coordinate - sta Sprite_Y_Position,y ;store as sprite Y coordinate - lda Fireball_Rel_XPos ;get relative horizontal coordinate - sta Sprite_X_Position,y ;store as sprite X coordinate, then do shared code - -DrawFirebar: - lda FrameCounter ;get frame counter - lsr ;divide by four - lsr - pha ;save result to stack - and #$01 ;mask out all but last bit - eor #$64 ;set either tile $64 or $65 as fireball tile - sta Sprite_Tilenumber,y ;thus tile changes every four frames - pla ;get from stack - lsr ;divide by four again - lsr - lda #$02 ;load value $02 to set palette in attrib byte - bcc FireA ;if last bit shifted out was not set, skip this - ora #%11000000 ;otherwise flip both ways every eight frames -FireA: sta Sprite_Attributes,y ;store attribute byte and leave - rts - -;------------------------------------------------------------------------------------- - -ExplosionTiles: - .db $68, $67, $66 - -DrawExplosion_Fireball: - ldy Alt_SprDataOffset,x ;get OAM data offset of alternate sort for fireball's explosion - lda Fireball_State,x ;load fireball state - inc Fireball_State,x ;increment state for next frame - lsr ;divide by 2 - and #%00000111 ;mask out all but d3-d1 - cmp #$03 ;check to see if time to kill fireball - bcs KillFireBall ;branch if so, otherwise continue to draw explosion - -DrawExplosion_Fireworks: - tax ;use whatever's in A for offset - lda ExplosionTiles,x ;get tile number using offset - iny ;increment Y (contains sprite data offset) - jsr DumpFourSpr ;and dump into tile number part of sprite data - dey ;decrement Y so we have the proper offset again - ldx ObjectOffset ;return enemy object buffer offset to X - lda Fireball_Rel_YPos ;get relative vertical coordinate - sec ;subtract four pixels vertically - sbc #$04 ;for first and third sprites - sta Sprite_Y_Position,y - sta Sprite_Y_Position+8,y - clc ;add eight pixels vertically - adc #$08 ;for second and fourth sprites - sta Sprite_Y_Position+4,y - sta Sprite_Y_Position+12,y - lda Fireball_Rel_XPos ;get relative horizontal coordinate - sec ;subtract four pixels horizontally - sbc #$04 ;for first and second sprites - sta Sprite_X_Position,y - sta Sprite_X_Position+4,y - clc ;add eight pixels horizontally - adc #$08 ;for third and fourth sprites - sta Sprite_X_Position+8,y - sta Sprite_X_Position+12,y - lda #$02 ;set palette attributes for all sprites, but - sta Sprite_Attributes,y ;set no flip at all for first sprite - lda #$82 - sta Sprite_Attributes+4,y ;set vertical flip for second sprite - lda #$42 - sta Sprite_Attributes+8,y ;set horizontal flip for third sprite - lda #$c2 - sta Sprite_Attributes+12,y ;set both flips for fourth sprite - rts ;we are done - -KillFireBall: - lda #$00 ;clear fireball state to kill it - sta Fireball_State,x - rts - -;------------------------------------------------------------------------------------- - -DrawSmallPlatform: - ldy Enemy_SprDataOffset,x ;get OAM data offset - lda #$5b ;load tile number for small platforms - iny ;increment offset for tile numbers - jsr DumpSixSpr ;dump tile number into all six sprites - iny ;increment offset for attributes - lda #$02 ;load palette controls - jsr DumpSixSpr ;dump attributes into all six sprites - dey ;decrement for original offset - dey - lda Enemy_Rel_XPos ;get relative horizontal coordinate - sta Sprite_X_Position,y - sta Sprite_X_Position+12,y ;dump as X coordinate into first and fourth sprites - clc - adc #$08 ;add eight pixels - sta Sprite_X_Position+4,y ;dump into second and fifth sprites - sta Sprite_X_Position+16,y - clc - adc #$08 ;add eight more pixels - sta Sprite_X_Position+8,y ;dump into third and sixth sprites - sta Sprite_X_Position+20,y - lda Enemy_Y_Position,x ;get vertical coordinate - tax - pha ;save to stack - cpx #$20 ;if vertical coordinate below status bar, - bcs TopSP ;do not mess with it - lda #$f8 ;otherwise move first three sprites offscreen -TopSP: jsr DumpThreeSpr ;dump vertical coordinate into Y coordinates - pla ;pull from stack - clc - adc #$80 ;add 128 pixels - tax - cpx #$20 ;if below status bar (taking wrap into account) - bcs BotSP ;then do not change altered coordinate - lda #$f8 ;otherwise move last three sprites offscreen -BotSP: sta Sprite_Y_Position+12,y ;dump vertical coordinate + 128 pixels - sta Sprite_Y_Position+16,y ;into Y coordinates - sta Sprite_Y_Position+20,y - lda Enemy_OffscreenBits ;get offscreen bits - pha ;save to stack - and #%00001000 ;check d3 - beq SOfs - lda #$f8 ;if d3 was set, move first and - sta Sprite_Y_Position,y ;fourth sprites offscreen - sta Sprite_Y_Position+12,y -SOfs: pla ;move out and back into stack - pha - and #%00000100 ;check d2 - beq SOfs2 - lda #$f8 ;if d2 was set, move second and - sta Sprite_Y_Position+4,y ;fifth sprites offscreen - sta Sprite_Y_Position+16,y -SOfs2: pla ;get from stack - and #%00000010 ;check d1 - beq ExSPl - lda #$f8 ;if d1 was set, move third and - sta Sprite_Y_Position+8,y ;sixth sprites offscreen - sta Sprite_Y_Position+20,y -ExSPl: ldx ObjectOffset ;get enemy object offset and leave - rts - -;------------------------------------------------------------------------------------- - -DrawBubble: - ldy Player_Y_HighPos ;if player's vertical high position - dey ;not within screen, skip all of this - bne ExDBub - lda Bubble_OffscreenBits ;check air bubble's offscreen bits - and #%00001000 - bne ExDBub ;if bit set, branch to leave - ldy Bubble_SprDataOffset,x ;get air bubble's OAM data offset - lda Bubble_Rel_XPos ;get relative horizontal coordinate - sta Sprite_X_Position,y ;store as X coordinate here - lda Bubble_Rel_YPos ;get relative vertical coordinate - sta Sprite_Y_Position,y ;store as Y coordinate here - lda #$74 - sta Sprite_Tilenumber,y ;put air bubble tile into OAM data - lda #$02 - sta Sprite_Attributes,y ;set attribute byte -ExDBub: rts ;leave - -;------------------------------------------------------------------------------------- -;$00 - used to store player's vertical offscreen bits - -PlayerGfxTblOffsets: - .db $20, $28, $c8, $18, $00, $40, $50, $58 - .db $80, $88, $b8, $78, $60, $a0, $b0, $b8 - -;tiles arranged in order, 2 tiles per row, top to bottom - -PlayerGraphicsTable: -;big player table - .db $00, $01, $02, $03, $04, $05, $06, $07 ;walking frame 1 - .db $08, $09, $0a, $0b, $0c, $0d, $0e, $0f ; frame 2 - .db $10, $11, $12, $13, $14, $15, $16, $17 ; frame 3 - .db $18, $19, $1a, $1b, $1c, $1d, $1e, $1f ;skidding - .db $20, $21, $22, $23, $24, $25, $26, $27 ;jumping - .db $08, $09, $28, $29, $2a, $2b, $2c, $2d ;swimming frame 1 - .db $08, $09, $0a, $0b, $0c, $30, $2c, $2d ; frame 2 - .db $08, $09, $0a, $0b, $2e, $2f, $2c, $2d ; frame 3 - .db $08, $09, $28, $29, $2a, $2b, $5c, $5d ;climbing frame 1 - .db $08, $09, $0a, $0b, $0c, $0d, $5e, $5f ; frame 2 - .db $fc, $fc, $08, $09, $58, $59, $5a, $5a ;crouching - .db $08, $09, $28, $29, $2a, $2b, $0e, $0f ;fireball throwing - -;small player table - .db $fc, $fc, $fc, $fc, $32, $33, $34, $35 ;walking frame 1 - .db $fc, $fc, $fc, $fc, $36, $37, $38, $39 ; frame 2 - .db $fc, $fc, $fc, $fc, $3a, $37, $3b, $3c ; frame 3 - .db $fc, $fc, $fc, $fc, $3d, $3e, $3f, $40 ;skidding - .db $fc, $fc, $fc, $fc, $32, $41, $42, $43 ;jumping - .db $fc, $fc, $fc, $fc, $32, $33, $44, $45 ;swimming frame 1 - .db $fc, $fc, $fc, $fc, $32, $33, $44, $47 ; frame 2 - .db $fc, $fc, $fc, $fc, $32, $33, $48, $49 ; frame 3 - .db $fc, $fc, $fc, $fc, $32, $33, $90, $91 ;climbing frame 1 - .db $fc, $fc, $fc, $fc, $3a, $37, $92, $93 ; frame 2 - .db $fc, $fc, $fc, $fc, $9e, $9e, $9f, $9f ;killed - -;used by both player sizes - .db $fc, $fc, $fc, $fc, $3a, $37, $4f, $4f ;small player standing - .db $fc, $fc, $00, $01, $4c, $4d, $4e, $4e ;intermediate grow frame - .db $00, $01, $4c, $4d, $4a, $4a, $4b, $4b ;big player standing - -SwimKickTileNum: - .db $31, $46 - -PlayerGfxHandler: - lda InjuryTimer ;if player's injured invincibility timer - beq CntPl ;not set, skip checkpoint and continue code - lda FrameCounter - lsr ;otherwise check frame counter and branch - bcs ExPGH ;to leave on every other frame (when d0 is set) -CntPl: lda GameEngineSubroutine ;if executing specific game engine routine, - cmp #$0b ;branch ahead to some other part - beq PlayerKilled - lda PlayerChangeSizeFlag ;if grow/shrink flag set - bne DoChangeSize ;then branch to some other code - ldy SwimmingFlag ;if swimming flag set, branch to - beq FindPlayerAction ;different part, do not return - lda Player_State - cmp #$00 ;if player status normal, - beq FindPlayerAction ;branch and do not return - jsr FindPlayerAction ;otherwise jump and return - lda FrameCounter - and #%00000100 ;check frame counter for d2 set (8 frames every - bne ExPGH ;eighth frame), and branch if set to leave - tax ;initialize X to zero - ldy Player_SprDataOffset ;get player sprite data offset - lda PlayerFacingDir ;get player's facing direction - lsr - bcs SwimKT ;if player facing to the right, use current offset - iny - iny ;otherwise move to next OAM data - iny - iny -SwimKT: lda PlayerSize ;check player's size - beq BigKTS ;if big, use first tile - lda Sprite_Tilenumber+24,y ;check tile number of seventh/eighth sprite - cmp SwimTileRepOffset ;against tile number in player graphics table - beq ExPGH ;if spr7/spr8 tile number = value, branch to leave - inx ;otherwise increment X for second tile -BigKTS: lda SwimKickTileNum,x ;overwrite tile number in sprite 7/8 - sta Sprite_Tilenumber+24,y ;to animate player's feet when swimming -ExPGH: rts ;then leave - -FindPlayerAction: - jsr ProcessPlayerAction ;find proper offset to graphics table by player's actions - jmp PlayerGfxProcessing ;draw player, then process for fireball throwing - -DoChangeSize: - jsr HandleChangeSize ;find proper offset to graphics table for grow/shrink - jmp PlayerGfxProcessing ;draw player, then process for fireball throwing - -PlayerKilled: - ldy #$0e ;load offset for player killed - lda PlayerGfxTblOffsets,y ;get offset to graphics table - -PlayerGfxProcessing: - sta PlayerGfxOffset ;store offset to graphics table here - lda #$04 - jsr RenderPlayerSub ;draw player based on offset loaded - jsr ChkForPlayerAttrib ;set horizontal flip bits as necessary - lda FireballThrowingTimer - beq PlayerOffscreenChk ;if fireball throw timer not set, skip to the end - ldy #$00 ;set value to initialize by default - lda PlayerAnimTimer ;get animation frame timer - cmp FireballThrowingTimer ;compare to fireball throw timer - sty FireballThrowingTimer ;initialize fireball throw timer - bcs PlayerOffscreenChk ;if animation frame timer => fireball throw timer skip to end - sta FireballThrowingTimer ;otherwise store animation timer into fireball throw timer - ldy #$07 ;load offset for throwing - lda PlayerGfxTblOffsets,y ;get offset to graphics table - sta PlayerGfxOffset ;store it for use later - ldy #$04 ;set to update four sprite rows by default - lda Player_X_Speed - ora Left_Right_Buttons ;check for horizontal speed or left/right button press - beq SUpdR ;if no speed or button press, branch using set value in Y - dey ;otherwise set to update only three sprite rows -SUpdR: tya ;save in A for use - jsr RenderPlayerSub ;in sub, draw player object again - -PlayerOffscreenChk: - lda Player_OffscreenBits ;get player's offscreen bits - lsr - lsr ;move vertical bits to low nybble - lsr - lsr - sta $00 ;store here - ldx #$03 ;check all four rows of player sprites - lda Player_SprDataOffset ;get player's sprite data offset - clc - adc #$18 ;add 24 bytes to start at bottom row - tay ;set as offset here -PROfsLoop: lda #$f8 ;load offscreen Y coordinate just in case - lsr $00 ;shift bit into carry - bcc NPROffscr ;if bit not set, skip, do not move sprites - jsr DumpTwoSpr ;otherwise dump offscreen Y coordinate into sprite data -NPROffscr: tya - sec ;subtract eight bytes to do - sbc #$08 ;next row up - tay - dex ;decrement row counter - bpl PROfsLoop ;do this until all sprite rows are checked - rts ;then we are done! - -;------------------------------------------------------------------------------------- - -IntermediatePlayerData: - .db $58, $01, $00, $60, $ff, $04 - -DrawPlayer_Intermediate: - ldx #$05 ;store data into zero page memory -PIntLoop: lda IntermediatePlayerData,x ;load data to display player as he always - sta $02,x ;appears on world/lives display - dex - bpl PIntLoop ;do this until all data is loaded - ldx #$b8 ;load offset for small standing - ldy #$04 ;load sprite data offset - jsr DrawPlayerLoop ;draw player accordingly - lda Sprite_Attributes+36 ;get empty sprite attributes - ora #%01000000 ;set horizontal flip bit for bottom-right sprite - sta Sprite_Attributes+32 ;store and leave - rts - -;------------------------------------------------------------------------------------- -;$00-$01 - used to hold tile numbers, $00 also used to hold upper extent of animation frames -;$02 - vertical position -;$03 - facing direction, used as horizontal flip control -;$04 - attributes -;$05 - horizontal position -;$07 - number of rows to draw -;these also used in IntermediatePlayerData - -RenderPlayerSub: - sta $07 ;store number of rows of sprites to draw - lda Player_Rel_XPos - sta Player_Pos_ForScroll ;store player's relative horizontal position - sta $05 ;store it here also - lda Player_Rel_YPos - sta $02 ;store player's vertical position - lda PlayerFacingDir - sta $03 ;store player's facing direction - lda Player_SprAttrib - sta $04 ;store player's sprite attributes - ldx PlayerGfxOffset ;load graphics table offset - ldy Player_SprDataOffset ;get player's sprite data offset - -DrawPlayerLoop: - lda PlayerGraphicsTable,x ;load player's left side - sta $00 - lda PlayerGraphicsTable+1,x ;now load right side - jsr DrawOneSpriteRow - dec $07 ;decrement rows of sprites to draw - bne DrawPlayerLoop ;do this until all rows are drawn - rts - -ProcessPlayerAction: - lda Player_State ;get player's state - cmp #$03 - beq ActionClimbing ;if climbing, branch here - cmp #$02 - beq ActionFalling ;if falling, branch here - cmp #$01 - bne ProcOnGroundActs ;if not jumping, branch here - lda SwimmingFlag - bne ActionSwimming ;if swimming flag set, branch elsewhere - ldy #$06 ;load offset for crouching - lda CrouchingFlag ;get crouching flag - bne NonAnimatedActs ;if set, branch to get offset for graphics table - ldy #$00 ;otherwise load offset for jumping - jmp NonAnimatedActs ;go to get offset to graphics table - -ProcOnGroundActs: - ldy #$06 ;load offset for crouching - lda CrouchingFlag ;get crouching flag - bne NonAnimatedActs ;if set, branch to get offset for graphics table - ldy #$02 ;load offset for standing - lda Player_X_Speed ;check player's horizontal speed - ora Left_Right_Buttons ;and left/right controller bits - beq NonAnimatedActs ;if no speed or buttons pressed, use standing offset - lda Player_XSpeedAbsolute ;load walking/running speed - cmp #$09 - bcc ActionWalkRun ;if less than a certain amount, branch, too slow to skid - lda Player_MovingDir ;otherwise check to see if moving direction - and PlayerFacingDir ;and facing direction are the same - bne ActionWalkRun ;if moving direction = facing direction, branch, don't skid - iny ;otherwise increment to skid offset ($03) - -NonAnimatedActs: - jsr GetGfxOffsetAdder ;do a sub here to get offset adder for graphics table - lda #$00 - sta PlayerAnimCtrl ;initialize animation frame control - lda PlayerGfxTblOffsets,y ;load offset to graphics table using size as offset - rts - -ActionFalling: - ldy #$04 ;load offset for walking/running - jsr GetGfxOffsetAdder ;get offset to graphics table - jmp GetCurrentAnimOffset ;execute instructions for falling state - -ActionWalkRun: - ldy #$04 ;load offset for walking/running - jsr GetGfxOffsetAdder ;get offset to graphics table - jmp FourFrameExtent ;execute instructions for normal state - -ActionClimbing: - ldy #$05 ;load offset for climbing - lda Player_Y_Speed ;check player's vertical speed - beq NonAnimatedActs ;if no speed, branch, use offset as-is - jsr GetGfxOffsetAdder ;otherwise get offset for graphics table - jmp ThreeFrameExtent ;then skip ahead to more code - -ActionSwimming: - ldy #$01 ;load offset for swimming - jsr GetGfxOffsetAdder - lda JumpSwimTimer ;check jump/swim timer - ora PlayerAnimCtrl ;and animation frame control - bne FourFrameExtent ;if any one of these set, branch ahead - lda A_B_Buttons - asl ;check for A button pressed - bcs FourFrameExtent ;branch to same place if A button pressed - -GetCurrentAnimOffset: - lda PlayerAnimCtrl ;get animation frame control - jmp GetOffsetFromAnimCtrl ;jump to get proper offset to graphics table - -FourFrameExtent: - lda #$03 ;load upper extent for frame control - jmp AnimationControl ;jump to get offset and animate player object - -ThreeFrameExtent: - lda #$02 ;load upper extent for frame control for climbing - -AnimationControl: - sta $00 ;store upper extent here - jsr GetCurrentAnimOffset ;get proper offset to graphics table - pha ;save offset to stack - lda PlayerAnimTimer ;load animation frame timer - bne ExAnimC ;branch if not expired - lda PlayerAnimTimerSet ;get animation frame timer amount - sta PlayerAnimTimer ;and set timer accordingly - lda PlayerAnimCtrl - clc ;add one to animation frame control - adc #$01 - cmp $00 ;compare to upper extent - bcc SetAnimC ;if frame control + 1 < upper extent, use as next - lda #$00 ;otherwise initialize frame control -SetAnimC: sta PlayerAnimCtrl ;store as new animation frame control -ExAnimC: pla ;get offset to graphics table from stack and leave - rts - -GetGfxOffsetAdder: - lda PlayerSize ;get player's size - beq SzOfs ;if player big, use current offset as-is - tya ;for big player - clc ;otherwise add eight bytes to offset - adc #$08 ;for small player - tay -SzOfs: rts ;go back - -ChangeSizeOffsetAdder: - .db $00, $01, $00, $01, $00, $01, $02, $00, $01, $02 - .db $02, $00, $02, $00, $02, $00, $02, $00, $02, $00 - -HandleChangeSize: - ldy PlayerAnimCtrl ;get animation frame control - lda FrameCounter - and #%00000011 ;get frame counter and execute this code every - bne GorSLog ;fourth frame, otherwise branch ahead - iny ;increment frame control - cpy #$0a ;check for preset upper extent - bcc CSzNext ;if not there yet, skip ahead to use - ldy #$00 ;otherwise initialize both grow/shrink flag - sty PlayerChangeSizeFlag ;and animation frame control -CSzNext: sty PlayerAnimCtrl ;store proper frame control -GorSLog: lda PlayerSize ;get player's size - bne ShrinkPlayer ;if player small, skip ahead to next part - lda ChangeSizeOffsetAdder,y ;get offset adder based on frame control as offset - ldy #$0f ;load offset for player growing - -GetOffsetFromAnimCtrl: - asl ;multiply animation frame control - asl ;by eight to get proper amount - asl ;to add to our offset - adc PlayerGfxTblOffsets,y ;add to offset to graphics table - rts ;and return with result in A - -ShrinkPlayer: - tya ;add ten bytes to frame control as offset - clc - adc #$0a ;this thing apparently uses two of the swimming frames - tax ;to draw the player shrinking - ldy #$09 ;load offset for small player swimming - lda ChangeSizeOffsetAdder,x ;get what would normally be offset adder - bne ShrPlF ;and branch to use offset if nonzero - ldy #$01 ;otherwise load offset for big player swimming -ShrPlF: lda PlayerGfxTblOffsets,y ;get offset to graphics table based on offset loaded - rts ;and leave - -ChkForPlayerAttrib: - ldy Player_SprDataOffset ;get sprite data offset - lda GameEngineSubroutine - cmp #$0b ;if executing specific game engine routine, - beq KilledAtt ;branch to change third and fourth row OAM attributes - lda PlayerGfxOffset ;get graphics table offset - cmp #$50 - beq C_S_IGAtt ;if crouch offset, either standing offset, - cmp #$b8 ;or intermediate growing offset, - beq C_S_IGAtt ;go ahead and execute code to change - cmp #$c0 ;fourth row OAM attributes only - beq C_S_IGAtt - cmp #$c8 - bne ExPlyrAt ;if none of these, branch to leave -KilledAtt: lda Sprite_Attributes+16,y - and #%00111111 ;mask out horizontal and vertical flip bits - sta Sprite_Attributes+16,y ;for third row sprites and save - lda Sprite_Attributes+20,y - and #%00111111 - ora #%01000000 ;set horizontal flip bit for second - sta Sprite_Attributes+20,y ;sprite in the third row -C_S_IGAtt: lda Sprite_Attributes+24,y - and #%00111111 ;mask out horizontal and vertical flip bits - sta Sprite_Attributes+24,y ;for fourth row sprites and save - lda Sprite_Attributes+28,y - and #%00111111 - ora #%01000000 ;set horizontal flip bit for second - sta Sprite_Attributes+28,y ;sprite in the fourth row -ExPlyrAt: rts ;leave - -;------------------------------------------------------------------------------------- -;$00 - used in adding to get proper offset - -RelativePlayerPosition: - ldx #$00 ;set offsets for relative cooordinates - ldy #$00 ;routine to correspond to player object - jmp RelWOfs ;get the coordinates - -RelativeBubblePosition: - ldy #$01 ;set for air bubble offsets - jsr GetProperObjOffset ;modify X to get proper air bubble offset - ldy #$03 - jmp RelWOfs ;get the coordinates - -RelativeFireballPosition: - ldy #$00 ;set for fireball offsets - jsr GetProperObjOffset ;modify X to get proper fireball offset - ldy #$02 -RelWOfs: jsr GetObjRelativePosition ;get the coordinates - ldx ObjectOffset ;return original offset - rts ;leave - -RelativeMiscPosition: - ldy #$02 ;set for misc object offsets - jsr GetProperObjOffset ;modify X to get proper misc object offset - ldy #$06 - jmp RelWOfs ;get the coordinates - -RelativeEnemyPosition: - lda #$01 ;get coordinates of enemy object - ldy #$01 ;relative to the screen - jmp VariableObjOfsRelPos - -RelativeBlockPosition: - lda #$09 ;get coordinates of one block object - ldy #$04 ;relative to the screen - jsr VariableObjOfsRelPos - inx ;adjust offset for other block object if any - inx - lda #$09 - iny ;adjust other and get coordinates for other one - -VariableObjOfsRelPos: - stx $00 ;store value to add to A here - clc - adc $00 ;add A to value stored - tax ;use as enemy offset - jsr GetObjRelativePosition - ldx ObjectOffset ;reload old object offset and leave - rts - -GetObjRelativePosition: - lda SprObject_Y_Position,x ;load vertical coordinate low - sta SprObject_Rel_YPos,y ;store here - lda SprObject_X_Position,x ;load horizontal coordinate - sec ;subtract left edge coordinate - sbc ScreenLeft_X_Pos - sta SprObject_Rel_XPos,y ;store result here - rts - -;------------------------------------------------------------------------------------- -;$00 - used as temp variable to hold offscreen bits - -GetPlayerOffscreenBits: - ldx #$00 ;set offsets for player-specific variables - ldy #$00 ;and get offscreen information about player - jmp GetOffScreenBitsSet - -GetFireballOffscreenBits: - ldy #$00 ;set for fireball offsets - jsr GetProperObjOffset ;modify X to get proper fireball offset - ldy #$02 ;set other offset for fireball's offscreen bits - jmp GetOffScreenBitsSet ;and get offscreen information about fireball - -GetBubbleOffscreenBits: - ldy #$01 ;set for air bubble offsets - jsr GetProperObjOffset ;modify X to get proper air bubble offset - ldy #$03 ;set other offset for airbubble's offscreen bits - jmp GetOffScreenBitsSet ;and get offscreen information about air bubble - -GetMiscOffscreenBits: - ldy #$02 ;set for misc object offsets - jsr GetProperObjOffset ;modify X to get proper misc object offset - ldy #$06 ;set other offset for misc object's offscreen bits - jmp GetOffScreenBitsSet ;and get offscreen information about misc object - -ObjOffsetData: - .db $07, $16, $0d - -GetProperObjOffset: - txa ;move offset to A - clc - adc ObjOffsetData,y ;add amount of bytes to offset depending on setting in Y - tax ;put back in X and leave - rts - -GetEnemyOffscreenBits: - lda #$01 ;set A to add 1 byte in order to get enemy offset - ldy #$01 ;set Y to put offscreen bits in Enemy_OffscreenBits - jmp SetOffscrBitsOffset - -GetBlockOffscreenBits: - lda #$09 ;set A to add 9 bytes in order to get block obj offset - ldy #$04 ;set Y to put offscreen bits in Block_OffscreenBits - -SetOffscrBitsOffset: - stx $00 - clc ;add contents of X to A to get - adc $00 ;appropriate offset, then give back to X - tax - -GetOffScreenBitsSet: - tya ;save offscreen bits offset to stack for now - pha - jsr RunOffscrBitsSubs - asl ;move low nybble to high nybble - asl - asl - asl - ora $00 ;mask together with previously saved low nybble - sta $00 ;store both here - pla ;get offscreen bits offset from stack - tay - lda $00 ;get value here and store elsewhere - sta SprObject_OffscrBits,y - ldx ObjectOffset - rts - -RunOffscrBitsSubs: - jsr GetXOffscreenBits ;do subroutine here - lsr ;move high nybble to low - lsr - lsr - lsr - sta $00 ;store here - jmp GetYOffscreenBits - -;-------------------------------- -;(these apply to these three subsections) -;$04 - used to store offset to sprite object data -;$05 - used as adder in DividePDiff -;$06 - used to store constant used to compare to pixel difference in $07 -;$07 - used to store pixel difference between X positions of object and screen edges - -XOffscreenBitsData: - .db $7f, $3f, $1f, $0f, $07, $03, $01, $00 - .db $80, $c0, $e0, $f0, $f8, $fc, $fe, $ff - -DefaultXOnscreenOfs: - .db $07, $0f, $07 - -GetXOffscreenBits: - stx $04 ;save position in buffer to here - ldy #$01 ;start with right side of screen -XOfsLoop: lda ScreenEdge_X_Pos,y ;get pixel coordinate of edge - sec ;get difference between pixel coordinate of edge - sbc SprObject_X_Position,x ;and pixel coordinate of object position - sta $07 ;store here - lda ScreenEdge_PageLoc,y ;get page location of edge - sbc SprObject_PageLoc,x ;subtract page location of object position from it - ldx DefaultXOnscreenOfs,y ;load offset value here - cmp #$00 - bmi XLdBData ;if beyond right edge or in front of left edge, branch - ldx DefaultXOnscreenOfs+1,y ;if not, load alternate offset value here - cmp #$01 - bpl XLdBData ;if one page or more to the left of either edge, branch - lda #$38 ;if no branching, load value here and store - sta $06 - lda #$08 ;load some other value and execute subroutine - jsr DividePDiff -XLdBData: lda XOffscreenBitsData,x ;get bits here - ldx $04 ;reobtain position in buffer - cmp #$00 ;if bits not zero, branch to leave - bne ExXOfsBS - dey ;otherwise, do left side of screen now - bpl XOfsLoop ;branch if not already done with left side -ExXOfsBS: rts - -;-------------------------------- - -YOffscreenBitsData: - .db $00, $08, $0c, $0e - .db $0f, $07, $03, $01 - .db $00 - -DefaultYOnscreenOfs: - .db $04, $00, $04 - -HighPosUnitData: - .db $ff, $00 - -GetYOffscreenBits: - stx $04 ;save position in buffer to here - ldy #$01 ;start with top of screen -YOfsLoop: lda HighPosUnitData,y ;load coordinate for edge of vertical unit - sec - sbc SprObject_Y_Position,x ;subtract from vertical coordinate of object - sta $07 ;store here - lda #$01 ;subtract one from vertical high byte of object - sbc SprObject_Y_HighPos,x - ldx DefaultYOnscreenOfs,y ;load offset value here - cmp #$00 - bmi YLdBData ;if under top of the screen or beyond bottom, branch - ldx DefaultYOnscreenOfs+1,y ;if not, load alternate offset value here - cmp #$01 - bpl YLdBData ;if one vertical unit or more above the screen, branch - lda #$20 ;if no branching, load value here and store - sta $06 - lda #$04 ;load some other value and execute subroutine - jsr DividePDiff -YLdBData: lda YOffscreenBitsData,x ;get offscreen data bits using offset - ldx $04 ;reobtain position in buffer - cmp #$00 - bne ExYOfsBS ;if bits not zero, branch to leave - dey ;otherwise, do bottom of the screen now - bpl YOfsLoop -ExYOfsBS: rts - -;-------------------------------- - -DividePDiff: - sta $05 ;store current value in A here - lda $07 ;get pixel difference - cmp $06 ;compare to preset value - bcs ExDivPD ;if pixel difference >= preset value, branch - lsr ;divide by eight to get tile difference - lsr - lsr - and #$07 ;mask out all but 3 LSB - cpy #$01 ;right side of the screen or top? - bcs SetOscrO ;if so, branch, use difference / 8 as offset - adc $05 ;if not, add value to difference / 8 -SetOscrO: tax ;use as offset -ExDivPD: rts ;leave - -;------------------------------------------------------------------------------------- -;$00-$01 - tile numbers -;$02 - Y coordinate -;$03 - flip control -;$04 - sprite attributes -;$05 - X coordinate - -DrawSpriteObject: - lda $03 ;get saved flip control bits - lsr - lsr ;move d1 into carry - lda $00 - bcc NoHFlip ;if d1 not set, branch - sta Sprite_Tilenumber+4,y ;store first tile into second sprite - lda $01 ;and second into first sprite - sta Sprite_Tilenumber,y - lda #$40 ;activate horizontal flip OAM attribute - bne SetHFAt ;and unconditionally branch -NoHFlip: sta Sprite_Tilenumber,y ;store first tile into first sprite - lda $01 ;and second into second sprite - sta Sprite_Tilenumber+4,y - lda #$00 ;clear bit for horizontal flip -SetHFAt: ora $04 ;add other OAM attributes if necessary - sta Sprite_Attributes,y ;store sprite attributes - sta Sprite_Attributes+4,y - lda $02 ;now the y coordinates - sta Sprite_Y_Position,y ;note because they are - sta Sprite_Y_Position+4,y ;side by side, they are the same - lda $05 - sta Sprite_X_Position,y ;store x coordinate, then - clc ;add 8 pixels and store another to - adc #$08 ;put them side by side - sta Sprite_X_Position+4,y - lda $02 ;add eight pixels to the next y - clc ;coordinate - adc #$08 - sta $02 - tya ;add eight to the offset in Y to - clc ;move to the next two sprites - adc #$08 - tay - inx ;increment offset to return it to the - inx ;routine that called this subroutine - rts - -;------------------------------------------------------------------------------------- - -;unused space - .db $ff, $ff, $ff, $ff, $ff, $ff - -;------------------------------------------------------------------------------------- - -SoundEngine: - lda OperMode ;are we in title screen mode? - bne SndOn - sta SND_MASTERCTRL_REG ;if so, disable sound and leave - rts -SndOn: lda #$ff - sta JOYPAD_PORT2 ;disable irqs and set frame counter mode??? - lda #$0f - sta SND_MASTERCTRL_REG ;enable first four channels - lda PauseModeFlag ;is sound already in pause mode? - bne InPause - lda PauseSoundQueue ;if not, check pause sfx queue - cmp #$01 - bne RunSoundSubroutines ;if queue is empty, skip pause mode routine -InPause: lda PauseSoundBuffer ;check pause sfx buffer - bne ContPau - lda PauseSoundQueue ;check pause queue - beq SkipSoundSubroutines - sta PauseSoundBuffer ;if queue full, store in buffer and activate - sta PauseModeFlag ;pause mode to interrupt game sounds - lda #$00 ;disable sound and clear sfx buffers - sta SND_MASTERCTRL_REG - sta Square1SoundBuffer - sta Square2SoundBuffer - sta NoiseSoundBuffer - lda #$0f - sta SND_MASTERCTRL_REG ;enable sound again - lda #$2a ;store length of sound in pause counter - sta Squ1_SfxLenCounter -PTone1F: lda #$44 ;play first tone - bne PTRegC ;unconditional branch -ContPau: lda Squ1_SfxLenCounter ;check pause length left - cmp #$24 ;time to play second? - beq PTone2F - cmp #$1e ;time to play first again? - beq PTone1F - cmp #$18 ;time to play second again? - bne DecPauC ;only load regs during times, otherwise skip -PTone2F: lda #$64 ;store reg contents and play the pause sfx -PTRegC: ldx #$84 - ldy #$7f - jsr PlaySqu1Sfx -DecPauC: dec Squ1_SfxLenCounter ;decrement pause sfx counter - bne SkipSoundSubroutines - lda #$00 ;disable sound if in pause mode and - sta SND_MASTERCTRL_REG ;not currently playing the pause sfx - lda PauseSoundBuffer ;if no longer playing pause sfx, check to see - cmp #$02 ;if we need to be playing sound again - bne SkipPIn - lda #$00 ;clear pause mode to allow game sounds again - sta PauseModeFlag -SkipPIn: lda #$00 ;clear pause sfx buffer - sta PauseSoundBuffer - beq SkipSoundSubroutines - -RunSoundSubroutines: - jsr Square1SfxHandler ;play sfx on square channel 1 - jsr Square2SfxHandler ; '' '' '' square channel 2 - jsr NoiseSfxHandler ; '' '' '' noise channel - jsr MusicHandler ;play music on all channels - lda #$00 ;clear the music queues - sta AreaMusicQueue - sta EventMusicQueue - -SkipSoundSubroutines: - lda #$00 ;clear the sound effects queues - sta Square1SoundQueue - sta Square2SoundQueue - sta NoiseSoundQueue - sta PauseSoundQueue - ldy DAC_Counter ;load some sort of counter - lda AreaMusicBuffer - and #%00000011 ;check for specific music - beq NoIncDAC - inc DAC_Counter ;increment and check counter - cpy #$30 - bcc StrWave ;if not there yet, just store it -NoIncDAC: tya - beq StrWave ;if we are at zero, do not decrement - dec DAC_Counter ;decrement counter -StrWave: sty SND_DELTA_REG+1 ;store into DMC load register (??) - rts ;we are done here - -;-------------------------------- - -Dump_Squ1_Regs: - sty SND_SQUARE1_REG+1 ;dump the contents of X and Y into square 1's control regs - stx SND_SQUARE1_REG - rts - -PlaySqu1Sfx: - jsr Dump_Squ1_Regs ;do sub to set ctrl regs for square 1, then set frequency regs - -SetFreq_Squ1: - ldx #$00 ;set frequency reg offset for square 1 sound channel - -Dump_Freq_Regs: - tay - lda FreqRegLookupTbl+1,y ;use previous contents of A for sound reg offset - beq NoTone ;if zero, then do not load - sta SND_REGISTER+2,x ;first byte goes into LSB of frequency divider - lda FreqRegLookupTbl,y ;second byte goes into 3 MSB plus extra bit for - ora #%00001000 ;length counter - sta SND_REGISTER+3,x -NoTone: rts - -Dump_Sq2_Regs: - stx SND_SQUARE2_REG ;dump the contents of X and Y into square 2's control regs - sty SND_SQUARE2_REG+1 - rts - -PlaySqu2Sfx: - jsr Dump_Sq2_Regs ;do sub to set ctrl regs for square 2, then set frequency regs - -SetFreq_Squ2: - ldx #$04 ;set frequency reg offset for square 2 sound channel - bne Dump_Freq_Regs ;unconditional branch - -SetFreq_Tri: - ldx #$08 ;set frequency reg offset for triangle sound channel - bne Dump_Freq_Regs ;unconditional branch - -;-------------------------------- - -SwimStompEnvelopeData: - .db $9f, $9b, $98, $96, $95, $94, $92, $90 - .db $90, $9a, $97, $95, $93, $92 - -PlayFlagpoleSlide: - lda #$40 ;store length of flagpole sound - sta Squ1_SfxLenCounter - lda #$62 ;load part of reg contents for flagpole sound - jsr SetFreq_Squ1 - ldx #$99 ;now load the rest - bne FPS2nd - -PlaySmallJump: - lda #$26 ;branch here for small mario jumping sound - bne JumpRegContents - -PlayBigJump: - lda #$18 ;branch here for big mario jumping sound - -JumpRegContents: - ldx #$82 ;note that small and big jump borrow each others' reg contents - ldy #$a7 ;anyway, this loads the first part of mario's jumping sound - jsr PlaySqu1Sfx - lda #$28 ;store length of sfx for both jumping sounds - sta Squ1_SfxLenCounter ;then continue on here - -ContinueSndJump: - lda Squ1_SfxLenCounter ;jumping sounds seem to be composed of three parts - cmp #$25 ;check for time to play second part yet - bne N2Prt - ldx #$5f ;load second part - ldy #$f6 - bne DmpJpFPS ;unconditional branch -N2Prt: cmp #$20 ;check for third part - bne DecJpFPS - ldx #$48 ;load third part -FPS2nd: ldy #$bc ;the flagpole slide sound shares part of third part -DmpJpFPS: jsr Dump_Squ1_Regs - bne DecJpFPS ;unconditional branch outta here - -PlayFireballThrow: - lda #$05 - ldy #$99 ;load reg contents for fireball throw sound - bne Fthrow ;unconditional branch - -PlayBump: - lda #$0a ;load length of sfx and reg contents for bump sound - ldy #$93 -Fthrow: ldx #$9e ;the fireball sound shares reg contents with the bump sound - sta Squ1_SfxLenCounter - lda #$0c ;load offset for bump sound - jsr PlaySqu1Sfx - -ContinueBumpThrow: - lda Squ1_SfxLenCounter ;check for second part of bump sound - cmp #$06 - bne DecJpFPS - lda #$bb ;load second part directly - sta SND_SQUARE1_REG+1 -DecJpFPS: bne BranchToDecLength1 ;unconditional branch - - -Square1SfxHandler: - ldy Square1SoundQueue ;check for sfx in queue - beq CheckSfx1Buffer - sty Square1SoundBuffer ;if found, put in buffer - bmi PlaySmallJump ;small jump - lsr Square1SoundQueue - bcs PlayBigJump ;big jump - lsr Square1SoundQueue - bcs PlayBump ;bump - lsr Square1SoundQueue - bcs PlaySwimStomp ;swim/stomp - lsr Square1SoundQueue - bcs PlaySmackEnemy ;smack enemy - lsr Square1SoundQueue - bcs PlayPipeDownInj ;pipedown/injury - lsr Square1SoundQueue - bcs PlayFireballThrow ;fireball throw - lsr Square1SoundQueue - bcs PlayFlagpoleSlide ;slide flagpole - -CheckSfx1Buffer: - lda Square1SoundBuffer ;check for sfx in buffer - beq ExS1H ;if not found, exit sub - bmi ContinueSndJump ;small mario jump - lsr - bcs ContinueSndJump ;big mario jump - lsr - bcs ContinueBumpThrow ;bump - lsr - bcs ContinueSwimStomp ;swim/stomp - lsr - bcs ContinueSmackEnemy ;smack enemy - lsr - bcs ContinuePipeDownInj ;pipedown/injury - lsr - bcs ContinueBumpThrow ;fireball throw - lsr - bcs DecrementSfx1Length ;slide flagpole -ExS1H: rts - -PlaySwimStomp: - lda #$0e ;store length of swim/stomp sound - sta Squ1_SfxLenCounter - ldy #$9c ;store reg contents for swim/stomp sound - ldx #$9e - lda #$26 - jsr PlaySqu1Sfx - -ContinueSwimStomp: - ldy Squ1_SfxLenCounter ;look up reg contents in data section based on - lda SwimStompEnvelopeData-1,y ;length of sound left, used to control sound's - sta SND_SQUARE1_REG ;envelope - cpy #$06 - bne BranchToDecLength1 - lda #$9e ;when the length counts down to a certain point, put this - sta SND_SQUARE1_REG+2 ;directly into the LSB of square 1's frequency divider - -BranchToDecLength1: - bne DecrementSfx1Length ;unconditional branch (regardless of how we got here) - -PlaySmackEnemy: - lda #$0e ;store length of smack enemy sound - ldy #$cb - ldx #$9f - sta Squ1_SfxLenCounter - lda #$28 ;store reg contents for smack enemy sound - jsr PlaySqu1Sfx - bne DecrementSfx1Length ;unconditional branch - -ContinueSmackEnemy: - ldy Squ1_SfxLenCounter ;check about halfway through - cpy #$08 - bne SmSpc - lda #$a0 ;if we're at the about-halfway point, make the second tone - sta SND_SQUARE1_REG+2 ;in the smack enemy sound - lda #$9f - bne SmTick -SmSpc: lda #$90 ;this creates spaces in the sound, giving it its distinct noise -SmTick: sta SND_SQUARE1_REG - -DecrementSfx1Length: - dec Squ1_SfxLenCounter ;decrement length of sfx - bne ExSfx1 - -StopSquare1Sfx: - ldx #$00 ;if end of sfx reached, clear buffer - stx $f1 ;and stop making the sfx - ldx #$0e - stx SND_MASTERCTRL_REG - ldx #$0f - stx SND_MASTERCTRL_REG -ExSfx1: rts - -PlayPipeDownInj: - lda #$2f ;load length of pipedown sound - sta Squ1_SfxLenCounter - -ContinuePipeDownInj: - lda Squ1_SfxLenCounter ;some bitwise logic, forces the regs - lsr ;to be written to only during six specific times - bcs NoPDwnL ;during which d3 must be set and d1-0 must be clear - lsr - bcs NoPDwnL - and #%00000010 - beq NoPDwnL - ldy #$91 ;and this is where it actually gets written in - ldx #$9a - lda #$44 - jsr PlaySqu1Sfx -NoPDwnL: jmp DecrementSfx1Length - -;-------------------------------- - -ExtraLifeFreqData: - .db $58, $02, $54, $56, $4e, $44 - -PowerUpGrabFreqData: - .db $4c, $52, $4c, $48, $3e, $36, $3e, $36, $30 - .db $28, $4a, $50, $4a, $64, $3c, $32, $3c, $32 - .db $2c, $24, $3a, $64, $3a, $34, $2c, $22, $2c - -;residual frequency data - .db $22, $1c, $14 - -PUp_VGrow_FreqData: - .db $14, $04, $22, $24, $16, $04, $24, $26 ;used by both - .db $18, $04, $26, $28, $1a, $04, $28, $2a - .db $1c, $04, $2a, $2c, $1e, $04, $2c, $2e ;used by vinegrow - .db $20, $04, $2e, $30, $22, $04, $30, $32 - -PlayCoinGrab: - lda #$35 ;load length of coin grab sound - ldx #$8d ;and part of reg contents - bne CGrab_TTickRegL - -PlayTimerTick: - lda #$06 ;load length of timer tick sound - ldx #$98 ;and part of reg contents - -CGrab_TTickRegL: - sta Squ2_SfxLenCounter - ldy #$7f ;load the rest of reg contents - lda #$42 ;of coin grab and timer tick sound - jsr PlaySqu2Sfx - -ContinueCGrabTTick: - lda Squ2_SfxLenCounter ;check for time to play second tone yet - cmp #$30 ;timer tick sound also executes this, not sure why - bne N2Tone - lda #$54 ;if so, load the tone directly into the reg - sta SND_SQUARE2_REG+2 -N2Tone: bne DecrementSfx2Length - -PlayBlast: - lda #$20 ;load length of fireworks/gunfire sound - sta Squ2_SfxLenCounter - ldy #$94 ;load reg contents of fireworks/gunfire sound - lda #$5e - bne SBlasJ - -ContinueBlast: - lda Squ2_SfxLenCounter ;check for time to play second part - cmp #$18 - bne DecrementSfx2Length - ldy #$93 ;load second part reg contents then - lda #$18 -SBlasJ: bne BlstSJp ;unconditional branch to load rest of reg contents - -PlayPowerUpGrab: - lda #$36 ;load length of power-up grab sound - sta Squ2_SfxLenCounter - -ContinuePowerUpGrab: - lda Squ2_SfxLenCounter ;load frequency reg based on length left over - lsr ;divide by 2 - bcs DecrementSfx2Length ;alter frequency every other frame - tay - lda PowerUpGrabFreqData-1,y ;use length left over / 2 for frequency offset - ldx #$5d ;store reg contents of power-up grab sound - ldy #$7f - -LoadSqu2Regs: - jsr PlaySqu2Sfx - -DecrementSfx2Length: - dec Squ2_SfxLenCounter ;decrement length of sfx - bne ExSfx2 - -EmptySfx2Buffer: - ldx #$00 ;initialize square 2's sound effects buffer - stx Square2SoundBuffer - -StopSquare2Sfx: - ldx #$0d ;stop playing the sfx - stx SND_MASTERCTRL_REG - ldx #$0f - stx SND_MASTERCTRL_REG -ExSfx2: rts - -Square2SfxHandler: - lda Square2SoundBuffer ;special handling for the 1-up sound to keep it - and #Sfx_ExtraLife ;from being interrupted by other sounds on square 2 - bne ContinueExtraLife - ldy Square2SoundQueue ;check for sfx in queue - beq CheckSfx2Buffer - sty Square2SoundBuffer ;if found, put in buffer and check for the following - bmi PlayBowserFall ;bowser fall - lsr Square2SoundQueue - bcs PlayCoinGrab ;coin grab - lsr Square2SoundQueue - bcs PlayGrowPowerUp ;power-up reveal - lsr Square2SoundQueue - bcs PlayGrowVine ;vine grow - lsr Square2SoundQueue - bcs PlayBlast ;fireworks/gunfire - lsr Square2SoundQueue - bcs PlayTimerTick ;timer tick - lsr Square2SoundQueue - bcs PlayPowerUpGrab ;power-up grab - lsr Square2SoundQueue - bcs PlayExtraLife ;1-up - -CheckSfx2Buffer: - lda Square2SoundBuffer ;check for sfx in buffer - beq ExS2H ;if not found, exit sub - bmi ContinueBowserFall ;bowser fall - lsr - bcs Cont_CGrab_TTick ;coin grab - lsr - bcs ContinueGrowItems ;power-up reveal - lsr - bcs ContinueGrowItems ;vine grow - lsr - bcs ContinueBlast ;fireworks/gunfire - lsr - bcs Cont_CGrab_TTick ;timer tick - lsr - bcs ContinuePowerUpGrab ;power-up grab - lsr - bcs ContinueExtraLife ;1-up -ExS2H: rts - -Cont_CGrab_TTick: - jmp ContinueCGrabTTick - -JumpToDecLength2: - jmp DecrementSfx2Length - -PlayBowserFall: - lda #$38 ;load length of bowser defeat sound - sta Squ2_SfxLenCounter - ldy #$c4 ;load contents of reg for bowser defeat sound - lda #$18 -BlstSJp: bne PBFRegs - -ContinueBowserFall: - lda Squ2_SfxLenCounter ;check for almost near the end - cmp #$08 - bne DecrementSfx2Length - ldy #$a4 ;if so, load the rest of reg contents for bowser defeat sound - lda #$5a -PBFRegs: ldx #$9f ;the fireworks/gunfire sound shares part of reg contents here -EL_LRegs: bne LoadSqu2Regs ;this is an unconditional branch outta here - -PlayExtraLife: - lda #$30 ;load length of 1-up sound - sta Squ2_SfxLenCounter - -ContinueExtraLife: - lda Squ2_SfxLenCounter - ldx #$03 ;load new tones only every eight frames -DivLLoop: lsr - bcs JumpToDecLength2 ;if any bits set here, branch to dec the length - dex - bne DivLLoop ;do this until all bits checked, if none set, continue - tay - lda ExtraLifeFreqData-1,y ;load our reg contents - ldx #$82 - ldy #$7f - bne EL_LRegs ;unconditional branch - -PlayGrowPowerUp: - lda #$10 ;load length of power-up reveal sound - bne GrowItemRegs - -PlayGrowVine: - lda #$20 ;load length of vine grow sound - -GrowItemRegs: - sta Squ2_SfxLenCounter - lda #$7f ;load contents of reg for both sounds directly - sta SND_SQUARE2_REG+1 - lda #$00 ;start secondary counter for both sounds - sta Sfx_SecondaryCounter - -ContinueGrowItems: - inc Sfx_SecondaryCounter ;increment secondary counter for both sounds - lda Sfx_SecondaryCounter ;this sound doesn't decrement the usual counter - lsr ;divide by 2 to get the offset - tay - cpy Squ2_SfxLenCounter ;have we reached the end yet? - beq StopGrowItems ;if so, branch to jump, and stop playing sounds - lda #$9d ;load contents of other reg directly - sta SND_SQUARE2_REG - lda PUp_VGrow_FreqData,y ;use secondary counter / 2 as offset for frequency regs - jsr SetFreq_Squ2 - rts - -StopGrowItems: - jmp EmptySfx2Buffer ;branch to stop playing sounds - -;-------------------------------- - -BrickShatterFreqData: - .db $01, $0e, $0e, $0d, $0b, $06, $0c, $0f - .db $0a, $09, $03, $0d, $08, $0d, $06, $0c - -PlayBrickShatter: - lda #$20 ;load length of brick shatter sound - sta Noise_SfxLenCounter - -ContinueBrickShatter: - lda Noise_SfxLenCounter - lsr ;divide by 2 and check for bit set to use offset - bcc DecrementSfx3Length - tay - ldx BrickShatterFreqData,y ;load reg contents of brick shatter sound - lda BrickShatterEnvData,y - -PlayNoiseSfx: - sta SND_NOISE_REG ;play the sfx - stx SND_NOISE_REG+2 - lda #$18 - sta SND_NOISE_REG+3 - -DecrementSfx3Length: - dec Noise_SfxLenCounter ;decrement length of sfx - bne ExSfx3 - lda #$f0 ;if done, stop playing the sfx - sta SND_NOISE_REG - lda #$00 - sta NoiseSoundBuffer -ExSfx3: rts - -NoiseSfxHandler: - ldy NoiseSoundQueue ;check for sfx in queue - beq CheckNoiseBuffer - sty NoiseSoundBuffer ;if found, put in buffer - lsr NoiseSoundQueue - bcs PlayBrickShatter ;brick shatter - lsr NoiseSoundQueue - bcs PlayBowserFlame ;bowser flame - -CheckNoiseBuffer: - lda NoiseSoundBuffer ;check for sfx in buffer - beq ExNH ;if not found, exit sub - lsr - bcs ContinueBrickShatter ;brick shatter - lsr - bcs ContinueBowserFlame ;bowser flame -ExNH: rts - -PlayBowserFlame: - lda #$40 ;load length of bowser flame sound - sta Noise_SfxLenCounter - -ContinueBowserFlame: - lda Noise_SfxLenCounter - lsr - tay - ldx #$0f ;load reg contents of bowser flame sound - lda BowserFlameEnvData-1,y - bne PlayNoiseSfx ;unconditional branch here - -;-------------------------------- - -ContinueMusic: - jmp HandleSquare2Music ;if we have music, start with square 2 channel - -MusicHandler: - lda EventMusicQueue ;check event music queue - bne LoadEventMusic - lda AreaMusicQueue ;check area music queue - bne LoadAreaMusic - lda EventMusicBuffer ;check both buffers - ora AreaMusicBuffer - bne ContinueMusic - rts ;no music, then leave - -LoadEventMusic: - sta EventMusicBuffer ;copy event music queue contents to buffer - cmp #DeathMusic ;is it death music? - bne NoStopSfx ;if not, jump elsewhere - jsr StopSquare1Sfx ;stop sfx in square 1 and 2 - jsr StopSquare2Sfx ;but clear only square 1's sfx buffer -NoStopSfx: ldx AreaMusicBuffer - stx AreaMusicBuffer_Alt ;save current area music buffer to be re-obtained later - ldy #$00 - sty NoteLengthTblAdder ;default value for additional length byte offset - sty AreaMusicBuffer ;clear area music buffer - cmp #TimeRunningOutMusic ;is it time running out music? - bne FindEventMusicHeader - ldx #$08 ;load offset to be added to length byte of header - stx NoteLengthTblAdder - bne FindEventMusicHeader ;unconditional branch - -LoadAreaMusic: - cmp #$04 ;is it underground music? - bne NoStop1 ;no, do not stop square 1 sfx - jsr StopSquare1Sfx -NoStop1: ldy #$10 ;start counter used only by ground level music -GMLoopB: sty GroundMusicHeaderOfs - -HandleAreaMusicLoopB: - ldy #$00 ;clear event music buffer - sty EventMusicBuffer - sta AreaMusicBuffer ;copy area music queue contents to buffer - cmp #$01 ;is it ground level music? - bne FindAreaMusicHeader - inc GroundMusicHeaderOfs ;increment but only if playing ground level music - ldy GroundMusicHeaderOfs ;is it time to loopback ground level music? - cpy #$32 - bne LoadHeader ;branch ahead with alternate offset - ldy #$11 - bne GMLoopB ;unconditional branch - -FindAreaMusicHeader: - ldy #$08 ;load Y for offset of area music - sty MusicOffset_Square2 ;residual instruction here - -FindEventMusicHeader: - iny ;increment Y pointer based on previously loaded queue contents - lsr ;bit shift and increment until we find a set bit for music - bcc FindEventMusicHeader - -LoadHeader: - lda MusicHeaderOffsetData,y ;load offset for header - tay - lda MusicHeaderData,y ;now load the header - sta NoteLenLookupTblOfs - lda MusicHeaderData+1,y - sta MusicDataLow - lda MusicHeaderData+2,y - sta MusicDataHigh - lda MusicHeaderData+3,y - sta MusicOffset_Triangle - lda MusicHeaderData+4,y - sta MusicOffset_Square1 - lda MusicHeaderData+5,y - sta MusicOffset_Noise - sta NoiseDataLoopbackOfs - lda #$01 ;initialize music note counters - sta Squ2_NoteLenCounter - sta Squ1_NoteLenCounter - sta Tri_NoteLenCounter - sta Noise_BeatLenCounter - lda #$00 ;initialize music data offset for square 2 - sta MusicOffset_Square2 - sta AltRegContentFlag ;initialize alternate control reg data used by square 1 - lda #$0b ;disable triangle channel and reenable it - sta SND_MASTERCTRL_REG - lda #$0f - sta SND_MASTERCTRL_REG - -HandleSquare2Music: - dec Squ2_NoteLenCounter ;decrement square 2 note length - bne MiscSqu2MusicTasks ;is it time for more data? if not, branch to end tasks - ldy MusicOffset_Square2 ;increment square 2 music offset and fetch data - inc MusicOffset_Square2 - lda (MusicData),y - beq EndOfMusicData ;if zero, the data is a null terminator - bpl Squ2NoteHandler ;if non-negative, data is a note - bne Squ2LengthHandler ;otherwise it is length data - -EndOfMusicData: - lda EventMusicBuffer ;check secondary buffer for time running out music - cmp #TimeRunningOutMusic - bne NotTRO - lda AreaMusicBuffer_Alt ;load previously saved contents of primary buffer - bne MusicLoopBack ;and start playing the song again if there is one -NotTRO: and #VictoryMusic ;check for victory music (the only secondary that loops) - bne VictoryMLoopBack - lda AreaMusicBuffer ;check primary buffer for any music except pipe intro - and #%01011111 - bne MusicLoopBack ;if any area music except pipe intro, music loops - lda #$00 ;clear primary and secondary buffers and initialize - sta AreaMusicBuffer ;control regs of square and triangle channels - sta EventMusicBuffer - sta SND_TRIANGLE_REG - lda #$90 - sta SND_SQUARE1_REG - sta SND_SQUARE2_REG - rts - -MusicLoopBack: - jmp HandleAreaMusicLoopB - -VictoryMLoopBack: - jmp LoadEventMusic - -Squ2LengthHandler: - jsr ProcessLengthData ;store length of note - sta Squ2_NoteLenBuffer - ldy MusicOffset_Square2 ;fetch another byte (MUST NOT BE LENGTH BYTE!) - inc MusicOffset_Square2 - lda (MusicData),y - -Squ2NoteHandler: - ldx Square2SoundBuffer ;is there a sound playing on this channel? - bne SkipFqL1 - jsr SetFreq_Squ2 ;no, then play the note - beq Rest ;check to see if note is rest - jsr LoadControlRegs ;if not, load control regs for square 2 -Rest: sta Squ2_EnvelopeDataCtrl ;save contents of A - jsr Dump_Sq2_Regs ;dump X and Y into square 2 control regs -SkipFqL1: lda Squ2_NoteLenBuffer ;save length in square 2 note counter - sta Squ2_NoteLenCounter - -MiscSqu2MusicTasks: - lda Square2SoundBuffer ;is there a sound playing on square 2? - bne HandleSquare1Music - lda EventMusicBuffer ;check for death music or d4 set on secondary buffer - and #%10010001 ;note that regs for death music or d4 are loaded by default - bne HandleSquare1Music - ldy Squ2_EnvelopeDataCtrl ;check for contents saved from LoadControlRegs - beq NoDecEnv1 - dec Squ2_EnvelopeDataCtrl ;decrement unless already zero -NoDecEnv1: jsr LoadEnvelopeData ;do a load of envelope data to replace default - sta SND_SQUARE2_REG ;based on offset set by first load unless playing - ldx #$7f ;death music or d4 set on secondary buffer - stx SND_SQUARE2_REG+1 - -HandleSquare1Music: - ldy MusicOffset_Square1 ;is there a nonzero offset here? - beq HandleTriangleMusic ;if not, skip ahead to the triangle channel - dec Squ1_NoteLenCounter ;decrement square 1 note length - bne MiscSqu1MusicTasks ;is it time for more data? - -FetchSqu1MusicData: - ldy MusicOffset_Square1 ;increment square 1 music offset and fetch data - inc MusicOffset_Square1 - lda (MusicData),y - bne Squ1NoteHandler ;if nonzero, then skip this part - lda #$83 - sta SND_SQUARE1_REG ;store some data into control regs for square 1 - lda #$94 ;and fetch another byte of data, used to give - sta SND_SQUARE1_REG+1 ;death music its unique sound - sta AltRegContentFlag - bne FetchSqu1MusicData ;unconditional branch - -Squ1NoteHandler: - jsr AlternateLengthHandler - sta Squ1_NoteLenCounter ;save contents of A in square 1 note counter - ldy Square1SoundBuffer ;is there a sound playing on square 1? - bne HandleTriangleMusic - txa - and #%00111110 ;change saved data to appropriate note format - jsr SetFreq_Squ1 ;play the note - beq SkipCtrlL - jsr LoadControlRegs -SkipCtrlL: sta Squ1_EnvelopeDataCtrl ;save envelope offset - jsr Dump_Squ1_Regs - -MiscSqu1MusicTasks: - lda Square1SoundBuffer ;is there a sound playing on square 1? - bne HandleTriangleMusic - lda EventMusicBuffer ;check for death music or d4 set on secondary buffer - and #%10010001 - bne DeathMAltReg - ldy Squ1_EnvelopeDataCtrl ;check saved envelope offset - beq NoDecEnv2 - dec Squ1_EnvelopeDataCtrl ;decrement unless already zero -NoDecEnv2: jsr LoadEnvelopeData ;do a load of envelope data - sta SND_SQUARE1_REG ;based on offset set by first load -DeathMAltReg: lda AltRegContentFlag ;check for alternate control reg data - bne DoAltLoad - lda #$7f ;load this value if zero, the alternate value -DoAltLoad: sta SND_SQUARE1_REG+1 ;if nonzero, and let's move on - -HandleTriangleMusic: - lda MusicOffset_Triangle - dec Tri_NoteLenCounter ;decrement triangle note length - bne HandleNoiseMusic ;is it time for more data? - ldy MusicOffset_Triangle ;increment triangle music offset and fetch data - inc MusicOffset_Triangle - lda (MusicData),y - beq LoadTriCtrlReg ;if zero, skip all this and move on to noise - bpl TriNoteHandler ;if non-negative, data is note - jsr ProcessLengthData ;otherwise, it is length data - sta Tri_NoteLenBuffer ;save contents of A - lda #$1f - sta SND_TRIANGLE_REG ;load some default data for triangle control reg - ldy MusicOffset_Triangle ;fetch another byte - inc MusicOffset_Triangle - lda (MusicData),y - beq LoadTriCtrlReg ;check once more for nonzero data - -TriNoteHandler: - jsr SetFreq_Tri - ldx Tri_NoteLenBuffer ;save length in triangle note counter - stx Tri_NoteLenCounter - lda EventMusicBuffer - and #%01101110 ;check for death music or d4 set on secondary buffer - bne NotDOrD4 ;if playing any other secondary, skip primary buffer check - lda AreaMusicBuffer ;check primary buffer for water or castle level music - and #%00001010 - beq HandleNoiseMusic ;if playing any other primary, or death or d4, go on to noise routine -NotDOrD4: txa ;if playing water or castle music or any secondary - cmp #$12 ;besides death music or d4 set, check length of note - bcs LongN - lda EventMusicBuffer ;check for win castle music again if not playing a long note - and #EndOfCastleMusic - beq MediN - lda #$0f ;load value $0f if playing the win castle music and playing a short - bne LoadTriCtrlReg ;note, load value $1f if playing water or castle level music or any -MediN: lda #$1f ;secondary besides death and d4 except win castle or win castle and playing - bne LoadTriCtrlReg ;a short note, and load value $ff if playing a long note on water, castle -LongN: lda #$ff ;or any secondary (including win castle) except death and d4 - -LoadTriCtrlReg: - sta SND_TRIANGLE_REG ;save final contents of A into control reg for triangle - -HandleNoiseMusic: - lda AreaMusicBuffer ;check if playing underground or castle music - and #%11110011 - beq ExitMusicHandler ;if so, skip the noise routine - dec Noise_BeatLenCounter ;decrement noise beat length - bne ExitMusicHandler ;is it time for more data? - -FetchNoiseBeatData: - ldy MusicOffset_Noise ;increment noise beat offset and fetch data - inc MusicOffset_Noise - lda (MusicData),y ;get noise beat data, if nonzero, branch to handle - bne NoiseBeatHandler - lda NoiseDataLoopbackOfs ;if data is zero, reload original noise beat offset - sta MusicOffset_Noise ;and loopback next time around - bne FetchNoiseBeatData ;unconditional branch - -NoiseBeatHandler: - jsr AlternateLengthHandler - sta Noise_BeatLenCounter ;store length in noise beat counter - txa - and #%00111110 ;reload data and erase length bits - beq SilentBeat ;if no beat data, silence - cmp #$30 ;check the beat data and play the appropriate - beq LongBeat ;noise accordingly - cmp #$20 - beq StrongBeat - and #%00010000 - beq SilentBeat - lda #$1c ;short beat data - ldx #$03 - ldy #$18 - bne PlayBeat - -StrongBeat: - lda #$1c ;strong beat data - ldx #$0c - ldy #$18 - bne PlayBeat - -LongBeat: - lda #$1c ;long beat data - ldx #$03 - ldy #$58 - bne PlayBeat - -SilentBeat: - lda #$10 ;silence - -PlayBeat: - sta SND_NOISE_REG ;load beat data into noise regs - stx SND_NOISE_REG+2 - sty SND_NOISE_REG+3 - -ExitMusicHandler: - rts - -AlternateLengthHandler: - tax ;save a copy of original byte into X - ror ;save LSB from original byte into carry - txa ;reload original byte and rotate three times - rol ;turning xx00000x into 00000xxx, with the - rol ;bit in carry as the MSB here - rol - -ProcessLengthData: - and #%00000111 ;clear all but the three LSBs - clc - adc NoteLenLookupTblOfs ;add offset loaded from first header byte - adc NoteLengthTblAdder ;add extra if time running out music - tay - lda MusicLengthLookupTbl,y ;load length - rts - -LoadControlRegs: - lda EventMusicBuffer ;check secondary buffer for win castle music - and #EndOfCastleMusic - beq NotECstlM - lda #$04 ;this value is only used for win castle music - bne AllMus ;unconditional branch -NotECstlM: lda AreaMusicBuffer - and #%01111101 ;check primary buffer for water music - beq WaterMus - lda #$08 ;this is the default value for all other music - bne AllMus -WaterMus: lda #$28 ;this value is used for water music and all other event music -AllMus: ldx #$82 ;load contents of other sound regs for square 2 - ldy #$7f - rts - -LoadEnvelopeData: - lda EventMusicBuffer ;check secondary buffer for win castle music - and #EndOfCastleMusic - beq LoadUsualEnvData - lda EndOfCastleMusicEnvData,y ;load data from offset for win castle music - rts - -LoadUsualEnvData: - lda AreaMusicBuffer ;check primary buffer for water music - and #%01111101 - beq LoadWaterEventMusEnvData - lda AreaMusicEnvData,y ;load default data from offset for all other music - rts - -LoadWaterEventMusEnvData: - lda WaterEventMusEnvData,y ;load data from offset for water music and all other event music - rts - -;-------------------------------- - -;music header offsets - -MusicHeaderData: - .db DeathMusHdr-MHD ;event music - .db GameOverMusHdr-MHD - .db VictoryMusHdr-MHD - .db WinCastleMusHdr-MHD - .db GameOverMusHdr-MHD - .db EndOfLevelMusHdr-MHD - .db TimeRunningOutHdr-MHD - .db SilenceHdr-MHD - - .db GroundLevelPart1Hdr-MHD ;area music - .db WaterMusHdr-MHD - .db UndergroundMusHdr-MHD - .db CastleMusHdr-MHD - .db Star_CloudHdr-MHD - .db GroundLevelLeadInHdr-MHD - .db Star_CloudHdr-MHD - .db SilenceHdr-MHD - - .db GroundLevelLeadInHdr-MHD ;ground level music layout - .db GroundLevelPart1Hdr-MHD, GroundLevelPart1Hdr-MHD - .db GroundLevelPart2AHdr-MHD, GroundLevelPart2BHdr-MHD, GroundLevelPart2AHdr-MHD, GroundLevelPart2CHdr-MHD - .db GroundLevelPart2AHdr-MHD, GroundLevelPart2BHdr-MHD, GroundLevelPart2AHdr-MHD, GroundLevelPart2CHdr-MHD - .db GroundLevelPart3AHdr-MHD, GroundLevelPart3BHdr-MHD, GroundLevelPart3AHdr-MHD, GroundLevelLeadInHdr-MHD - .db GroundLevelPart1Hdr-MHD, GroundLevelPart1Hdr-MHD - .db GroundLevelPart4AHdr-MHD, GroundLevelPart4BHdr-MHD, GroundLevelPart4AHdr-MHD, GroundLevelPart4CHdr-MHD - .db GroundLevelPart4AHdr-MHD, GroundLevelPart4BHdr-MHD, GroundLevelPart4AHdr-MHD, GroundLevelPart4CHdr-MHD - .db GroundLevelPart3AHdr-MHD, GroundLevelPart3BHdr-MHD, GroundLevelPart3AHdr-MHD, GroundLevelLeadInHdr-MHD - .db GroundLevelPart4AHdr-MHD, GroundLevelPart4BHdr-MHD, GroundLevelPart4AHdr-MHD, GroundLevelPart4CHdr-MHD - -;music headers -;header format is as follows: -;1 byte - length byte offset -;2 bytes - music data address -;1 byte - triangle data offset -;1 byte - square 1 data offset -;1 byte - noise data offset (not used by secondary music) - -TimeRunningOutHdr: .db $08, TimeRunOutMusData, $27, $18 -Star_CloudHdr: .db $20, Star_CloudMData, $2e, $1a, $40 -EndOfLevelMusHdr: .db $20, WinLevelMusData, $3d, $21 -ResidualHeaderData: .db $20, $c4, $fc, $3f, $1d -UndergroundMusHdr: .db $18, UndergroundMusData, $00, $00 -SilenceHdr: .db $08, SilenceData, $00 -CastleMusHdr: .db $00, CastleMusData, $93, $62 -VictoryMusHdr: .db $10, VictoryMusData, $24, $14 -GameOverMusHdr: .db $18, GameOverMusData, $1e, $14 -WaterMusHdr: .db $08, WaterMusData, $a0, $70, $68 -WinCastleMusHdr: .db $08, EndOfCastleMusData, $4c, $24 -GroundLevelPart1Hdr: .db $18, GroundM_P1Data, $2d, $1c, $b8 -GroundLevelPart2AHdr: .db $18, GroundM_P2AData, $20, $12, $70 -GroundLevelPart2BHdr: .db $18, GroundM_P2BData, $1b, $10, $44 -GroundLevelPart2CHdr: .db $18, GroundM_P2CData, $11, $0a, $1c -GroundLevelPart3AHdr: .db $18, GroundM_P3AData, $2d, $10, $58 -GroundLevelPart3BHdr: .db $18, GroundM_P3BData, $14, $0d, $3f -GroundLevelLeadInHdr: .db $18, GroundMLdInData, $15, $0d, $21 -GroundLevelPart4AHdr: .db $18, GroundM_P4AData, $18, $10, $7a -GroundLevelPart4BHdr: .db $18, GroundM_P4BData, $19, $0f, $54 -GroundLevelPart4CHdr: .db $18, GroundM_P4CData, $1e, $12, $2b -DeathMusHdr: .db $18, DeathMusData, $1e, $0f, $2d - -;-------------------------------- - -;MUSIC DATA -;square 2/triangle format -;d7 - length byte flag (0-note, 1-length) -;if d7 is set to 0 and d6-d0 is nonzero: -;d6-d0 - note offset in frequency look-up table (must be even) -;if d7 is set to 1: -;d6-d3 - unused -;d2-d0 - length offset in length look-up table -;value of $00 in square 2 data is used as null terminator, affects all sound channels -;value of $00 in triangle data causes routine to skip note - -;square 1 format -;d7-d6, d0 - length offset in length look-up table (bit order is d0,d7,d6) -;d5-d1 - note offset in frequency look-up table -;value of $00 in square 1 data is flag alternate control reg data to be loaded - -;noise format -;d7-d6, d0 - length offset in length look-up table (bit order is d0,d7,d6) -;d5-d4 - beat type (0 - rest, 1 - short, 2 - strong, 3 - long) -;d3-d1 - unused -;value of $00 in noise data is used as null terminator, affects only noise - -;all music data is organized into sections (unless otherwise stated): -;square 2, square 1, triangle, noise - -Star_CloudMData: - .db $84, $2c, $2c, $2c, $82, $04, $2c, $04, $85, $2c, $84, $2c, $2c - .db $2a, $2a, $2a, $82, $04, $2a, $04, $85, $2a, $84, $2a, $2a, $00 - - .db $1f, $1f, $1f, $98, $1f, $1f, $98, $9e, $98, $1f - .db $1d, $1d, $1d, $94, $1d, $1d, $94, $9c, $94, $1d - - .db $86, $18, $85, $26, $30, $84, $04, $26, $30 - .db $86, $14, $85, $22, $2c, $84, $04, $22, $2c - - .db $21, $d0, $c4, $d0, $31, $d0, $c4, $d0, $00 - -GroundM_P1Data: - .db $85, $2c, $22, $1c, $84, $26, $2a, $82, $28, $26, $04 - .db $87, $22, $34, $3a, $82, $40, $04, $36, $84, $3a, $34 - .db $82, $2c, $30, $85, $2a - -SilenceData: - .db $00 - - .db $5d, $55, $4d, $15, $19, $96, $15, $d5, $e3, $eb - .db $2d, $a6, $2b, $27, $9c, $9e, $59 - - .db $85, $22, $1c, $14, $84, $1e, $22, $82, $20, $1e, $04, $87 - .db $1c, $2c, $34, $82, $36, $04, $30, $34, $04, $2c, $04, $26 - .db $2a, $85, $22 - -GroundM_P2AData: - .db $84, $04, $82, $3a, $38, $36, $32, $04, $34 - .db $04, $24, $26, $2c, $04, $26, $2c, $30, $00 - - .db $05, $b4, $b2, $b0, $2b, $ac, $84 - .db $9c, $9e, $a2, $84, $94, $9c, $9e - - .db $85, $14, $22, $84, $2c, $85, $1e - .db $82, $2c, $84, $2c, $1e - -GroundM_P2BData: - .db $84, $04, $82, $3a, $38, $36, $32, $04, $34 - .db $04, $64, $04, $64, $86, $64, $00 - - .db $05, $b4, $b2, $b0, $2b, $ac, $84 - .db $37, $b6, $b6, $45 - - .db $85, $14, $1c, $82, $22, $84, $2c - .db $4e, $82, $4e, $84, $4e, $22 - -GroundM_P2CData: - .db $84, $04, $85, $32, $85, $30, $86, $2c, $04, $00 - - .db $05, $a4, $05, $9e, $05, $9d, $85 - - .db $84, $14, $85, $24, $28, $2c, $82 - .db $22, $84, $22, $14 - - .db $21, $d0, $c4, $d0, $31, $d0, $c4, $d0, $00 - -GroundM_P3AData: - .db $82, $2c, $84, $2c, $2c, $82, $2c, $30 - .db $04, $34, $2c, $04, $26, $86, $22, $00 - - .db $a4, $25, $25, $a4, $29, $a2, $1d, $9c, $95 - -GroundM_P3BData: - .db $82, $2c, $2c, $04, $2c, $04, $2c, $30, $85, $34, $04, $04, $00 - - .db $a4, $25, $25, $a4, $a8, $63, $04 - -;triangle data used by both sections of third part - .db $85, $0e, $1a, $84, $24, $85, $22, $14, $84, $0c - -GroundMLdInData: - .db $82, $34, $84, $34, $34, $82, $2c, $84, $34, $86, $3a, $04, $00 - - .db $a0, $21, $21, $a0, $21, $2b, $05, $a3 - - .db $82, $18, $84, $18, $18, $82, $18, $18, $04, $86, $3a, $22 - -;noise data used by lead-in and third part sections - .db $31, $90, $31, $90, $31, $71, $31, $90, $90, $90, $00 - -GroundM_P4AData: - .db $82, $34, $84, $2c, $85, $22, $84, $24 - .db $82, $26, $36, $04, $36, $86, $26, $00 - - .db $ac, $27, $5d, $1d, $9e, $2d, $ac, $9f - - .db $85, $14, $82, $20, $84, $22, $2c - .db $1e, $1e, $82, $2c, $2c, $1e, $04 - -GroundM_P4BData: - .db $87, $2a, $40, $40, $40, $3a, $36 - .db $82, $34, $2c, $04, $26, $86, $22, $00 - - .db $e3, $f7, $f7, $f7, $f5, $f1, $ac, $27, $9e, $9d - - .db $85, $18, $82, $1e, $84, $22, $2a - .db $22, $22, $82, $2c, $2c, $22, $04 - -DeathMusData: - .db $86, $04 ;death music share data with fourth part c of ground level music - -GroundM_P4CData: - .db $82, $2a, $36, $04, $36, $87, $36, $34, $30, $86, $2c, $04, $00 - - .db $00, $68, $6a, $6c, $45 ;death music only - - .db $a2, $31, $b0, $f1, $ed, $eb, $a2, $1d, $9c, $95 - - .db $86, $04 ;death music only - - .db $85, $22, $82, $22, $87, $22, $26, $2a, $84, $2c, $22, $86, $14 - -;noise data used by fourth part sections - .db $51, $90, $31, $11, $00 - -CastleMusData: - .db $80, $22, $28, $22, $26, $22, $24, $22, $26 - .db $22, $28, $22, $2a, $22, $28, $22, $26 - .db $22, $28, $22, $26, $22, $24, $22, $26 - .db $22, $28, $22, $2a, $22, $28, $22, $26 - .db $20, $26, $20, $24, $20, $26, $20, $28 - .db $20, $26, $20, $28, $20, $26, $20, $24 - .db $20, $26, $20, $24, $20, $26, $20, $28 - .db $20, $26, $20, $28, $20, $26, $20, $24 - .db $28, $30, $28, $32, $28, $30, $28, $2e - .db $28, $30, $28, $2e, $28, $2c, $28, $2e - .db $28, $30, $28, $32, $28, $30, $28, $2e - .db $28, $30, $28, $2e, $28, $2c, $28, $2e, $00 - - .db $04, $70, $6e, $6c, $6e, $70, $72, $70, $6e - .db $70, $6e, $6c, $6e, $70, $72, $70, $6e - .db $6e, $6c, $6e, $70, $6e, $70, $6e, $6c - .db $6e, $6c, $6e, $70, $6e, $70, $6e, $6c - .db $76, $78, $76, $74, $76, $74, $72, $74 - .db $76, $78, $76, $74, $76, $74, $72, $74 - - .db $84, $1a, $83, $18, $20, $84, $1e, $83, $1c, $28 - .db $26, $1c, $1a, $1c - -GameOverMusData: - .db $82, $2c, $04, $04, $22, $04, $04, $84, $1c, $87 - .db $26, $2a, $26, $84, $24, $28, $24, $80, $22, $00 - - .db $9c, $05, $94, $05, $0d, $9f, $1e, $9c, $98, $9d - - .db $82, $22, $04, $04, $1c, $04, $04, $84, $14 - .db $86, $1e, $80, $16, $80, $14 - -TimeRunOutMusData: - .db $81, $1c, $30, $04, $30, $30, $04, $1e, $32, $04, $32, $32 - .db $04, $20, $34, $04, $34, $34, $04, $36, $04, $84, $36, $00 - - .db $46, $a4, $64, $a4, $48, $a6, $66, $a6, $4a, $a8, $68, $a8 - .db $6a, $44, $2b - - .db $81, $2a, $42, $04, $42, $42, $04, $2c, $64, $04, $64, $64 - .db $04, $2e, $46, $04, $46, $46, $04, $22, $04, $84, $22 - -WinLevelMusData: - .db $87, $04, $06, $0c, $14, $1c, $22, $86, $2c, $22 - .db $87, $04, $60, $0e, $14, $1a, $24, $86, $2c, $24 - .db $87, $04, $08, $10, $18, $1e, $28, $86, $30, $30 - .db $80, $64, $00 - - .db $cd, $d5, $dd, $e3, $ed, $f5, $bb, $b5, $cf, $d5 - .db $db, $e5, $ed, $f3, $bd, $b3, $d1, $d9, $df, $e9 - .db $f1, $f7, $bf, $ff, $ff, $ff, $34 - .db $00 ;unused byte - - .db $86, $04, $87, $14, $1c, $22, $86, $34, $84, $2c - .db $04, $04, $04, $87, $14, $1a, $24, $86, $32, $84 - .db $2c, $04, $86, $04, $87, $18, $1e, $28, $86, $36 - .db $87, $30, $30, $30, $80, $2c - -;square 2 and triangle use the same data, square 1 is unused -UndergroundMusData: - .db $82, $14, $2c, $62, $26, $10, $28, $80, $04 - .db $82, $14, $2c, $62, $26, $10, $28, $80, $04 - .db $82, $08, $1e, $5e, $18, $60, $1a, $80, $04 - .db $82, $08, $1e, $5e, $18, $60, $1a, $86, $04 - .db $83, $1a, $18, $16, $84, $14, $1a, $18, $0e, $0c - .db $16, $83, $14, $20, $1e, $1c, $28, $26, $87 - .db $24, $1a, $12, $10, $62, $0e, $80, $04, $04 - .db $00 - -;noise data directly follows square 2 here unlike in other songs -WaterMusData: - .db $82, $18, $1c, $20, $22, $26, $28 - .db $81, $2a, $2a, $2a, $04, $2a, $04, $83, $2a, $82, $22 - .db $86, $34, $32, $34, $81, $04, $22, $26, $2a, $2c, $30 - .db $86, $34, $83, $32, $82, $36, $84, $34, $85, $04, $81, $22 - .db $86, $30, $2e, $30, $81, $04, $22, $26, $2a, $2c, $2e - .db $86, $30, $83, $22, $82, $36, $84, $34, $85, $04, $81, $22 - .db $86, $3a, $3a, $3a, $82, $3a, $81, $40, $82, $04, $81, $3a - .db $86, $36, $36, $36, $82, $36, $81, $3a, $82, $04, $81, $36 - .db $86, $34, $82, $26, $2a, $36 - .db $81, $34, $34, $85, $34, $81, $2a, $86, $2c, $00 - - .db $84, $90, $b0, $84, $50, $50, $b0, $00 - - .db $98, $96, $94, $92, $94, $96, $58, $58, $58, $44 - .db $5c, $44, $9f, $a3, $a1, $a3, $85, $a3, $e0, $a6 - .db $23, $c4, $9f, $9d, $9f, $85, $9f, $d2, $a6, $23 - .db $c4, $b5, $b1, $af, $85, $b1, $af, $ad, $85, $95 - .db $9e, $a2, $aa, $6a, $6a, $6b, $5e, $9d - - .db $84, $04, $04, $82, $22, $86, $22 - .db $82, $14, $22, $2c, $12, $22, $2a, $14, $22, $2c - .db $1c, $22, $2c, $14, $22, $2c, $12, $22, $2a, $14 - .db $22, $2c, $1c, $22, $2c, $18, $22, $2a, $16, $20 - .db $28, $18, $22, $2a, $12, $22, $2a, $18, $22, $2a - .db $12, $22, $2a, $14, $22, $2c, $0c, $22, $2c, $14, $22, $34, $12 - .db $22, $30, $10, $22, $2e, $16, $22, $34, $18, $26 - .db $36, $16, $26, $36, $14, $26, $36, $12, $22, $36 - .db $5c, $22, $34, $0c, $22, $22, $81, $1e, $1e, $85, $1e - .db $81, $12, $86, $14 - -EndOfCastleMusData: - .db $81, $2c, $22, $1c, $2c, $22, $1c, $85, $2c, $04 - .db $81, $2e, $24, $1e, $2e, $24, $1e, $85, $2e, $04 - .db $81, $32, $28, $22, $32, $28, $22, $85, $32 - .db $87, $36, $36, $36, $84, $3a, $00 - - .db $5c, $54, $4c, $5c, $54, $4c - .db $5c, $1c, $1c, $5c, $5c, $5c, $5c - .db $5e, $56, $4e, $5e, $56, $4e - .db $5e, $1e, $1e, $5e, $5e, $5e, $5e - .db $62, $5a, $50, $62, $5a, $50 - .db $62, $22, $22, $62, $e7, $e7, $e7, $2b - - .db $86, $14, $81, $14, $80, $14, $14, $81, $14, $14, $14, $14 - .db $86, $16, $81, $16, $80, $16, $16, $81, $16, $16, $16, $16 - .db $81, $28, $22, $1a, $28, $22, $1a, $28, $80, $28, $28 - .db $81, $28, $87, $2c, $2c, $2c, $84, $30 - -VictoryMusData: - .db $83, $04, $84, $0c, $83, $62, $10, $84, $12 - .db $83, $1c, $22, $1e, $22, $26, $18, $1e, $04, $1c, $00 - - .db $e3, $e1, $e3, $1d, $de, $e0, $23 - .db $ec, $75, $74, $f0, $f4, $f6, $ea, $31, $2d - - .db $83, $12, $14, $04, $18, $1a, $1c, $14 - .db $26, $22, $1e, $1c, $18, $1e, $22, $0c, $14 - -;unused space - .db $ff, $ff, $ff - -FreqRegLookupTbl: - .db $00, $88, $00, $2f, $00, $00 - .db $02, $a6, $02, $80, $02, $5c, $02, $3a - .db $02, $1a, $01, $df, $01, $c4, $01, $ab - .db $01, $93, $01, $7c, $01, $67, $01, $53 - .db $01, $40, $01, $2e, $01, $1d, $01, $0d - .db $00, $fe, $00, $ef, $00, $e2, $00, $d5 - .db $00, $c9, $00, $be, $00, $b3, $00, $a9 - .db $00, $a0, $00, $97, $00, $8e, $00, $86 - .db $00, $77, $00, $7e, $00, $71, $00, $54 - .db $00, $64, $00, $5f, $00, $59, $00, $50 - .db $00, $47, $00, $43, $00, $3b, $00, $35 - .db $00, $2a, $00, $23, $04, $75, $03, $57 - .db $02, $f9, $02, $cf, $01, $fc, $00, $6a - -MusicLengthLookupTbl: - .db $05, $0a, $14, $28, $50, $1e, $3c, $02 - .db $04, $08, $10, $20, $40, $18, $30, $0c - .db $03, $06, $0c, $18, $30, $12, $24, $08 - .db $36, $03, $09, $06, $12, $1b, $24, $0c - .db $24, $02, $06, $04, $0c, $12, $18, $08 - .db $12, $01, $03, $02, $06, $09, $0c, $04 - -EndOfCastleMusicEnvData: - .db $98, $99, $9a, $9b - -AreaMusicEnvData: - .db $90, $94, $94, $95, $95, $96, $97, $98 - -WaterEventMusEnvData: - .db $90, $91, $92, $92, $93, $93, $93, $94 - .db $94, $94, $94, $94, $94, $95, $95, $95 - .db $95, $95, $95, $96, $96, $96, $96, $96 - .db $96, $96, $96, $96, $96, $96, $96, $96 - .db $96, $96, $96, $96, $95, $95, $94, $93 - -BowserFlameEnvData: - .db $15, $16, $16, $17, $17, $18, $19, $19 - .db $1a, $1a, $1c, $1d, $1d, $1e, $1e, $1f - .db $1f, $1f, $1f, $1e, $1d, $1c, $1e, $1f - .db $1f, $1e, $1d, $1c, $1a, $18, $16, $14 - -BrickShatterEnvData: - .db $15, $16, $16, $17, $17, $18, $19, $19 - .db $1a, $1a, $1c, $1d, $1d, $1e, $1e, $1f - -;------------------------------------------------------------------------------------- -;INTERRUPT VECTORS - - .dw NonMaskableInterrupt - .dw Start - .dw $fff0 ;unused \ No newline at end of file +;SMBDIS.ASM - A COMPREHENSIVE SUPER MARIO BROS. DISASSEMBLY +;by doppelganger (doppelheathen@gmail.com) + +;This file is provided for your own use as-is. It will require the character rom data +;and an iNES file header to get it to work. + +;There are so many people I have to thank for this, and without their help this would +;probably not be possible. So I thank all the peeps in the nesdev scene whose insight into +;the 6502 and the NES helped me learn how it works (you guys know who you are, there's no +;way I could have done this without your help), as well as the authors of x816 and SMB +;Utility, and the reverse-engineers who did the original Super Mario Bros. Hacking Project, +;which I compared notes with but did not copy from. Last but certainly not least, I thank +;Nintendo for creating this game and the NES, without which this disassembly would +;only be theory. + +;Update: removed residual note under ChkStart label. Thanks to ShaneM for pointing out +;the error. + +;Assembles with x816. + +;------------------------------------------------------------------------------------- +;DEFINES + +;NES specific hardware defines + +PPU_CTRL_REG1 = $2000 +PPU_CTRL_REG2 = $2001 +PPU_STATUS = $2002 +PPU_SPR_ADDR = $2003 +PPU_SPR_DATA = $2004 +PPU_SCROLL_REG = $2005 +PPU_ADDRESS = $2006 +PPU_DATA = $2007 + +SND_REGISTER = $4000 +SND_SQUARE1_REG = $4000 +SND_SQUARE2_REG = $4004 +SND_TRIANGLE_REG = $4008 +SND_NOISE_REG = $400c +SND_DELTA_REG = $4010 +SND_MASTERCTRL_REG = $4015 + +SPR_DMA = $4014 +JOYPAD_PORT = $4016 +JOYPAD_PORT1 = $4016 +JOYPAD_PORT2 = $4017 + +; GAME SPECIFIC DEFINES + +ObjectOffset = $08 + +FrameCounter = $09 + +SavedJoypadBits = $06fc +SavedJoypad1Bits = $06fc +SavedJoypad2Bits = $06fd +JoypadBitMask = $074a +JoypadOverride = $0758 + +A_B_Buttons = $0a +PreviousA_B_Buttons = $0d +Up_Down_Buttons = $0b +Left_Right_Buttons = $0c + +GameEngineSubroutine = $0e + +Mirror_PPU_CTRL_REG1 = $0778 +Mirror_PPU_CTRL_REG2 = $0779 + +OperMode = $0770 +OperMode_Task = $0772 +ScreenRoutineTask = $073c + +GamePauseStatus = $0776 +GamePauseTimer = $0777 + +DemoAction = $0717 +DemoActionTimer = $0718 + +TimerControl = $0747 +IntervalTimerControl = $077f + +Timers = $0780 +SelectTimer = $0780 +PlayerAnimTimer = $0781 +JumpSwimTimer = $0782 +RunningTimer = $0783 +BlockBounceTimer = $0784 +SideCollisionTimer = $0785 +JumpspringTimer = $0786 +GameTimerCtrlTimer = $0787 +ClimbSideTimer = $0789 +EnemyFrameTimer = $078a +FrenzyEnemyTimer = $078f +BowserFireBreathTimer = $0790 +StompTimer = $0791 +AirBubbleTimer = $0792 +ScrollIntervalTimer = $0795 +EnemyIntervalTimer = $0796 +BrickCoinTimer = $079d +InjuryTimer = $079e +StarInvincibleTimer = $079f +ScreenTimer = $07a0 +WorldEndTimer = $07a1 +DemoTimer = $07a2 + +Sprite_Data = $0200 + +Sprite_Y_Position = $0200 +Sprite_Tilenumber = $0201 +Sprite_Attributes = $0202 +Sprite_X_Position = $0203 + +ScreenEdge_PageLoc = $071a +ScreenEdge_X_Pos = $071c +ScreenLeft_PageLoc = $071a +ScreenRight_PageLoc = $071b +ScreenLeft_X_Pos = $071c +ScreenRight_X_Pos = $071d + +PlayerFacingDir = $33 +DestinationPageLoc = $34 +VictoryWalkControl = $35 +ScrollFractional = $0768 +PrimaryMsgCounter = $0719 +SecondaryMsgCounter = $0749 + +HorizontalScroll = $073f +VerticalScroll = $0740 +ScrollLock = $0723 +ScrollThirtyTwo = $073d +Player_X_Scroll = $06ff +Player_Pos_ForScroll = $0755 +ScrollAmount = $0775 + +AreaData = $e7 +AreaDataLow = $e7 +AreaDataHigh = $e8 +EnemyData = $e9 +EnemyDataLow = $e9 +EnemyDataHigh = $ea + +AreaParserTaskNum = $071f +ColumnSets = $071e +CurrentPageLoc = $0725 +CurrentColumnPos = $0726 +BackloadingFlag = $0728 +BehindAreaParserFlag = $0729 +AreaObjectPageLoc = $072a +AreaObjectPageSel = $072b +AreaDataOffset = $072c +AreaObjOffsetBuffer = $072d +AreaObjectLength = $0730 +StaircaseControl = $0734 +AreaObjectHeight = $0735 +MushroomLedgeHalfLen = $0736 +EnemyDataOffset = $0739 +EnemyObjectPageLoc = $073a +EnemyObjectPageSel = $073b +MetatileBuffer = $06a1 +BlockBufferColumnPos = $06a0 +CurrentNTAddr_Low = $0721 +CurrentNTAddr_High = $0720 +AttributeBuffer = $03f9 + +LoopCommand = $0745 + +DisplayDigits = $07d7 +TopScoreDisplay = $07d7 +ScoreAndCoinDisplay = $07dd +PlayerScoreDisplay = $07dd +GameTimerDisplay = $07f8 +DigitModifier = $0134 + +VerticalFlipFlag = $0109 +FloateyNum_Control = $0110 +ShellChainCounter = $0125 +FloateyNum_Timer = $012c +FloateyNum_X_Pos = $0117 +FloateyNum_Y_Pos = $011e +FlagpoleFNum_Y_Pos = $010d +FlagpoleFNum_YMFDummy = $010e +FlagpoleScore = $010f +FlagpoleCollisionYPos = $070f +StompChainCounter = $0484 + +VRAM_Buffer1_Offset = $0300 +VRAM_Buffer1 = $0301 +VRAM_Buffer2_Offset = $0340 +VRAM_Buffer2 = $0341 +VRAM_Buffer_AddrCtrl = $0773 +Sprite0HitDetectFlag = $0722 +DisableScreenFlag = $0774 +DisableIntermediate = $0769 +ColorRotateOffset = $06d4 + +TerrainControl = $0727 +AreaStyle = $0733 +ForegroundScenery = $0741 +BackgroundScenery = $0742 +CloudTypeOverride = $0743 +BackgroundColorCtrl = $0744 +AreaType = $074e +AreaAddrsLOffset = $074f +AreaPointer = $0750 + +PlayerEntranceCtrl = $0710 +GameTimerSetting = $0715 +AltEntranceControl = $0752 +EntrancePage = $0751 +NumberOfPlayers = $077a +WarpZoneControl = $06d6 +ChangeAreaTimer = $06de + +MultiLoopCorrectCntr = $06d9 +MultiLoopPassCntr = $06da + +FetchNewGameTimerFlag = $0757 +GameTimerExpiredFlag = $0759 + +PrimaryHardMode = $076a +SecondaryHardMode = $06cc +WorldSelectNumber = $076b +WorldSelectEnableFlag = $07fc +ContinueWorld = $07fd + +CurrentPlayer = $0753 +PlayerSize = $0754 +PlayerStatus = $0756 + +OnscreenPlayerInfo = $075a +NumberofLives = $075a ;used by current player +HalfwayPage = $075b +LevelNumber = $075c ;the actual dash number +Hidden1UpFlag = $075d +CoinTally = $075e +WorldNumber = $075f +AreaNumber = $0760 ;internal number used to find areas + +CoinTallyFor1Ups = $0748 + +OffscreenPlayerInfo = $0761 +OffScr_NumberofLives = $0761 ;used by offscreen player +OffScr_HalfwayPage = $0762 +OffScr_LevelNumber = $0763 +OffScr_Hidden1UpFlag = $0764 +OffScr_CoinTally = $0765 +OffScr_WorldNumber = $0766 +OffScr_AreaNumber = $0767 + +BalPlatformAlignment = $03a0 +Platform_X_Scroll = $03a1 +PlatformCollisionFlag = $03a2 +YPlatformTopYPos = $0401 +YPlatformCenterYPos = $58 + +BrickCoinTimerFlag = $06bc +StarFlagTaskControl = $0746 + +PseudoRandomBitReg = $07a7 +WarmBootValidation = $07ff + +SprShuffleAmtOffset = $06e0 +SprShuffleAmt = $06e1 +SprDataOffset = $06e4 +Player_SprDataOffset = $06e4 +Enemy_SprDataOffset = $06e5 +Block_SprDataOffset = $06ec +Alt_SprDataOffset = $06ec +Bubble_SprDataOffset = $06ee +FBall_SprDataOffset = $06f1 +Misc_SprDataOffset = $06f3 +SprDataOffset_Ctrl = $03ee + +Player_State = $1d +Enemy_State = $1e +Fireball_State = $24 +Block_State = $26 +Misc_State = $2a + +Player_MovingDir = $45 +Enemy_MovingDir = $46 + +SprObject_X_Speed = $57 +Player_X_Speed = $57 +Enemy_X_Speed = $58 +Fireball_X_Speed = $5e +Block_X_Speed = $60 +Misc_X_Speed = $64 + +Jumpspring_FixedYPos = $58 +JumpspringAnimCtrl = $070e +JumpspringForce = $06db + +SprObject_PageLoc = $6d +Player_PageLoc = $6d +Enemy_PageLoc = $6e +Fireball_PageLoc = $74 +Block_PageLoc = $76 +Misc_PageLoc = $7a +Bubble_PageLoc = $83 + +SprObject_X_Position = $86 +Player_X_Position = $86 +Enemy_X_Position = $87 +Fireball_X_Position = $8d +Block_X_Position = $8f +Misc_X_Position = $93 +Bubble_X_Position = $9c + +SprObject_Y_Speed = $9f +Player_Y_Speed = $9f +Enemy_Y_Speed = $a0 +Fireball_Y_Speed = $a6 +Block_Y_Speed = $a8 +Misc_Y_Speed = $ac + +SprObject_Y_HighPos = $b5 +Player_Y_HighPos = $b5 +Enemy_Y_HighPos = $b6 +Fireball_Y_HighPos = $bc +Block_Y_HighPos = $be +Misc_Y_HighPos = $c2 +Bubble_Y_HighPos = $cb + +SprObject_Y_Position = $ce +Player_Y_Position = $ce +Enemy_Y_Position = $cf +Fireball_Y_Position = $d5 +Block_Y_Position = $d7 +Misc_Y_Position = $db +Bubble_Y_Position = $e4 + +SprObject_Rel_XPos = $03ad +Player_Rel_XPos = $03ad +Enemy_Rel_XPos = $03ae +Fireball_Rel_XPos = $03af +Bubble_Rel_XPos = $03b0 +Block_Rel_XPos = $03b1 +Misc_Rel_XPos = $03b3 + +SprObject_Rel_YPos = $03b8 +Player_Rel_YPos = $03b8 +Enemy_Rel_YPos = $03b9 +Fireball_Rel_YPos = $03ba +Bubble_Rel_YPos = $03bb +Block_Rel_YPos = $03bc +Misc_Rel_YPos = $03be + +SprObject_SprAttrib = $03c4 +Player_SprAttrib = $03c4 +Enemy_SprAttrib = $03c5 + +SprObject_X_MoveForce = $0400 +Enemy_X_MoveForce = $0401 + +SprObject_YMF_Dummy = $0416 +Player_YMF_Dummy = $0416 +Enemy_YMF_Dummy = $0417 +Bubble_YMF_Dummy = $042c + +SprObject_Y_MoveForce = $0433 +Player_Y_MoveForce = $0433 +Enemy_Y_MoveForce = $0434 +Block_Y_MoveForce = $043c + +DisableCollisionDet = $0716 +Player_CollisionBits = $0490 +Enemy_CollisionBits = $0491 + +SprObj_BoundBoxCtrl = $0499 +Player_BoundBoxCtrl = $0499 +Enemy_BoundBoxCtrl = $049a +Fireball_BoundBoxCtrl = $04a0 +Misc_BoundBoxCtrl = $04a2 + +EnemyFrenzyBuffer = $06cb +EnemyFrenzyQueue = $06cd +Enemy_Flag = $0f +Enemy_ID = $16 + +PlayerGfxOffset = $06d5 +Player_XSpeedAbsolute = $0700 +FrictionAdderHigh = $0701 +FrictionAdderLow = $0702 +RunningSpeed = $0703 +SwimmingFlag = $0704 +Player_X_MoveForce = $0705 +DiffToHaltJump = $0706 +JumpOrigin_Y_HighPos = $0707 +JumpOrigin_Y_Position = $0708 +VerticalForce = $0709 +VerticalForceDown = $070a +PlayerChangeSizeFlag = $070b +PlayerAnimTimerSet = $070c +PlayerAnimCtrl = $070d +DeathMusicLoaded = $0712 +FlagpoleSoundQueue = $0713 +CrouchingFlag = $0714 +MaximumLeftSpeed = $0450 +MaximumRightSpeed = $0456 + +SprObject_OffscrBits = $03d0 +Player_OffscreenBits = $03d0 +Enemy_OffscreenBits = $03d1 +FBall_OffscreenBits = $03d2 +Bubble_OffscreenBits = $03d3 +Block_OffscreenBits = $03d4 +Misc_OffscreenBits = $03d6 +EnemyOffscrBitsMasked = $03d8 + +Cannon_Offset = $046a +Cannon_PageLoc = $046b +Cannon_X_Position = $0471 +Cannon_Y_Position = $0477 +Cannon_Timer = $047d + +Whirlpool_Offset = $046a +Whirlpool_PageLoc = $046b +Whirlpool_LeftExtent = $0471 +Whirlpool_Length = $0477 +Whirlpool_Flag = $047d + +VineFlagOffset = $0398 +VineHeight = $0399 +VineObjOffset = $039a +VineStart_Y_Position = $039d + +Block_Orig_YPos = $03e4 +Block_BBuf_Low = $03e6 +Block_Metatile = $03e8 +Block_PageLoc2 = $03ea +Block_RepFlag = $03ec +Block_ResidualCounter = $03f0 +Block_Orig_XPos = $03f1 + +BoundingBox_UL_XPos = $04ac +BoundingBox_UL_YPos = $04ad +BoundingBox_DR_XPos = $04ae +BoundingBox_DR_YPos = $04af +BoundingBox_UL_Corner = $04ac +BoundingBox_LR_Corner = $04ae +EnemyBoundingBoxCoord = $04b0 + +PowerUpType = $39 + +FireballBouncingFlag = $3a +FireballCounter = $06ce +FireballThrowingTimer = $0711 + +HammerEnemyOffset = $06ae +JumpCoinMiscOffset = $06b7 + +Block_Buffer_1 = $0500 +Block_Buffer_2 = $05d0 + +HammerThrowingTimer = $03a2 +HammerBroJumpTimer = $3c +Misc_Collision_Flag = $06be + +RedPTroopaOrigXPos = $0401 +RedPTroopaCenterYPos = $58 + +XMovePrimaryCounter = $a0 +XMoveSecondaryCounter = $58 + +CheepCheepMoveMFlag = $58 +CheepCheepOrigYPos = $0434 +BitMFilter = $06dd + +LakituReappearTimer = $06d1 +LakituMoveSpeed = $58 +LakituMoveDirection = $a0 + +FirebarSpinState_Low = $58 +FirebarSpinState_High = $a0 +FirebarSpinSpeed = $0388 +FirebarSpinDirection = $34 + +DuplicateObj_Offset = $06cf +NumberofGroupEnemies = $06d3 + +BlooperMoveCounter = $a0 +BlooperMoveSpeed = $58 + +BowserBodyControls = $0363 +BowserFeetCounter = $0364 +BowserMovementSpeed = $0365 +BowserOrigXPos = $0366 +BowserFlameTimerCtrl = $0367 +BowserFront_Offset = $0368 +BridgeCollapseOffset = $0369 +BowserGfxFlag = $036a +BowserHitPoints = $0483 +MaxRangeFromOrigin = $06dc + +BowserFlamePRandomOfs = $0417 + +PiranhaPlantUpYPos = $0417 +PiranhaPlantDownYPos = $0434 +PiranhaPlant_Y_Speed = $58 +PiranhaPlant_MoveFlag = $a0 + +FireworksCounter = $06d7 +ExplosionGfxCounter = $58 +ExplosionTimerCounter = $a0 + +;sound related defines +Squ2_NoteLenBuffer = $07b3 +Squ2_NoteLenCounter = $07b4 +Squ2_EnvelopeDataCtrl = $07b5 +Squ1_NoteLenCounter = $07b6 +Squ1_EnvelopeDataCtrl = $07b7 +Tri_NoteLenBuffer = $07b8 +Tri_NoteLenCounter = $07b9 +Noise_BeatLenCounter = $07ba +Squ1_SfxLenCounter = $07bb +Squ2_SfxLenCounter = $07bd +Sfx_SecondaryCounter = $07be +Noise_SfxLenCounter = $07bf + +PauseSoundQueue = $fa +Square1SoundQueue = $ff +Square2SoundQueue = $fe +NoiseSoundQueue = $fd +AreaMusicQueue = $fb +EventMusicQueue = $fc + +Square1SoundBuffer = $f1 +Square2SoundBuffer = $f2 +NoiseSoundBuffer = $f3 +AreaMusicBuffer = $f4 +EventMusicBuffer = $07b1 +PauseSoundBuffer = $07b2 + +MusicData = $f5 +MusicDataLow = $f5 +MusicDataHigh = $f6 +MusicOffset_Square2 = $f7 +MusicOffset_Square1 = $f8 +MusicOffset_Triangle = $f9 +MusicOffset_Noise = $07b0 + +NoteLenLookupTblOfs = $f0 +DAC_Counter = $07c0 +NoiseDataLoopbackOfs = $07c1 +NoteLengthTblAdder = $07c4 +AreaMusicBuffer_Alt = $07c5 +PauseModeFlag = $07c6 +GroundMusicHeaderOfs = $07c7 +AltRegContentFlag = $07ca + +;------------------------------------------------------------------------------------- +;CONSTANTS + +;sound effects constants +Sfx_SmallJump = %10000000 +Sfx_Flagpole = %01000000 +Sfx_Fireball = %00100000 +Sfx_PipeDown_Injury = %00010000 +Sfx_EnemySmack = %00001000 +Sfx_EnemyStomp = %00000100 +Sfx_Bump = %00000010 +Sfx_BigJump = %00000001 + +Sfx_BowserFall = %10000000 +Sfx_ExtraLife = %01000000 +Sfx_PowerUpGrab = %00100000 +Sfx_TimerTick = %00010000 +Sfx_Blast = %00001000 +Sfx_GrowVine = %00000100 +Sfx_GrowPowerUp = %00000010 +Sfx_CoinGrab = %00000001 + +Sfx_BowserFlame = %00000010 +Sfx_BrickShatter = %00000001 + +;music constants +Silence = %10000000 + +StarPowerMusic = %01000000 +PipeIntroMusic = %00100000 +CloudMusic = %00010000 +CastleMusic = %00001000 +UndergroundMusic = %00000100 +WaterMusic = %00000010 +GroundMusic = %00000001 + +TimeRunningOutMusic = %01000000 +EndOfLevelMusic = %00100000 +AltGameOverMusic = %00010000 +EndOfCastleMusic = %00001000 +VictoryMusic = %00000100 +GameOverMusic = %00000010 +DeathMusic = %00000001 + +;enemy object constants +GreenKoopa = $00 +BuzzyBeetle = $02 +RedKoopa = $03 +HammerBro = $05 +Goomba = $06 +Bloober = $07 +BulletBill_FrenzyVar = $08 +GreyCheepCheep = $0a +RedCheepCheep = $0b +Podoboo = $0c +PiranhaPlant = $0d +GreenParatroopaJump = $0e +RedParatroopa = $0f +GreenParatroopaFly = $10 +Lakitu = $11 +Spiny = $12 +FlyCheepCheepFrenzy = $14 +FlyingCheepCheep = $14 +BowserFlame = $15 +Fireworks = $16 +BBill_CCheep_Frenzy = $17 +Stop_Frenzy = $18 +Bowser = $2d +PowerUpObject = $2e +VineObject = $2f +FlagpoleFlagObject = $30 +StarFlagObject = $31 +JumpspringObject = $32 +BulletBill_CannonVar = $33 +RetainerObject = $35 +TallEnemy = $09 + +;other constants +World1 = 0 +World2 = 1 +World3 = 2 +World4 = 3 +World5 = 4 +World6 = 5 +World7 = 6 +World8 = 7 +Level1 = 0 +Level2 = 1 +Level3 = 2 +Level4 = 3 + +WarmBootOffset = <$07d6 +ColdBootOffset = <$07fe +TitleScreenDataOffset = $1ec0 +SoundMemory = $07b0 +SwimTileRepOffset = PlayerGraphicsTable + $9e +MusicHeaderOffsetData = MusicHeaderData - 1 +MHD = MusicHeaderData + +A_Button = %10000000 +B_Button = %01000000 +Select_Button = %00100000 +Start_Button = %00010000 +Up_Dir = %00001000 +Down_Dir = %00000100 +Left_Dir = %00000010 +Right_Dir = %00000001 + +TitleScreenModeValue = 0 +GameModeValue = 1 +VictoryModeValue = 2 +GameOverModeValue = 3 + +;------------------------------------------------------------------------------------- +;DIRECTIVES + +; .index 8 +; .mem 8 + + .org $8000 + +;------------------------------------------------------------------------------------- + +Start: + sei ;pretty standard 6502 type init here + cld + lda #%00010000 ;init PPU control register 1 + sta PPU_CTRL_REG1 + ldx #$ff ;reset stack pointer + txs +VBlank1: lda PPU_STATUS ;wait two frames + bpl VBlank1 +VBlank2: lda PPU_STATUS + bpl VBlank2 + ldy #ColdBootOffset ;load default cold boot pointer + ldx #$05 ;this is where we check for a warm boot +WBootCheck: lda TopScoreDisplay,x ;check each score digit in the top score + cmp #10 ;to see if we have a valid digit + bcs ColdBoot ;if not, give up and proceed with cold boot + dex + bpl WBootCheck + lda WarmBootValidation ;second checkpoint, check to see if + cmp #$a5 ;another location has a specific value + bne ColdBoot + ldy #WarmBootOffset ;if passed both, load warm boot pointer +ColdBoot: jsr InitializeMemory ;clear memory using pointer in Y + sta SND_DELTA_REG+1 ;reset delta counter load register + sta OperMode ;reset primary mode of operation + lda #$a5 ;set warm boot flag + sta WarmBootValidation + sta PseudoRandomBitReg ;set seed for pseudorandom register + lda #%00001111 + sta SND_MASTERCTRL_REG ;enable all sound channels except dmc + lda #%00000110 + sta PPU_CTRL_REG2 ;turn off clipping for OAM and background + jsr MoveAllSpritesOffscreen + jsr InitializeNameTables ;initialize both name tables + inc DisableScreenFlag ;set flag to disable screen output + lda Mirror_PPU_CTRL_REG1 + ora #%10000000 ;enable NMIs + jsr WritePPUReg1 +EndlessLoop: jmp EndlessLoop ;endless loop, need I say more? + +;------------------------------------------------------------------------------------- +;$00 - vram buffer address table low, also used for pseudorandom bit +;$01 - vram buffer address table high + +VRAM_AddrTable_Low: + .db VRAM_Buffer1, >WaterPaletteData, >GroundPaletteData + .db >UndergroundPaletteData, >CastlePaletteData, >VRAM_Buffer1_Offset + .db >VRAM_Buffer2, >VRAM_Buffer2, >BowserPaletteData + .db >DaySnowPaletteData, >NightSnowPaletteData, >MushroomPaletteData + .db >MarioThanksMessage, >LuigiThanksMessage, >MushroomRetainerSaved + .db >PrincessSaved1, >PrincessSaved2, >WorldSelectMessage1 + .db >WorldSelectMessage2 + +VRAM_Buffer_Offset: + .db $09 + lda Enemy_State,x + cmp #$02 ;if enemy state defeated or otherwise + bcs FloateyPart ;$02 or greater, branch beyond this part +GetAltOffset: ldx SprDataOffset_Ctrl ;load some kind of control bit + ldy Alt_SprDataOffset,x ;get alternate OAM data offset + ldx ObjectOffset ;get enemy object offset again +FloateyPart: lda FloateyNum_Y_Pos,x ;get vertical coordinate for + cmp #$18 ;floatey number, if coordinate in the + bcc SetupNumSpr ;status bar, branch + sbc #$01 + sta FloateyNum_Y_Pos,x ;otherwise subtract one and store as new +SetupNumSpr: lda FloateyNum_Y_Pos,x ;get vertical coordinate + sbc #$08 ;subtract eight and dump into the + jsr DumpTwoSpr ;left and right sprite's Y coordinates + lda FloateyNum_X_Pos,x ;get horizontal coordinate + sta Sprite_X_Position,y ;store into X coordinate of left sprite + clc + adc #$08 ;add eight pixels and store into X + sta Sprite_X_Position+4,y ;coordinate of right sprite + lda #$02 + sta Sprite_Attributes,y ;set palette control in attribute bytes + sta Sprite_Attributes+4,y ;of left and right sprites + lda FloateyNum_Control,x + asl ;multiply our floatey number control by 2 + tax ;and use as offset for look-up table + lda FloateyNumTileData,x + sta Sprite_Tilenumber,y ;display first half of number of points + lda FloateyNumTileData+1,x + sta Sprite_Tilenumber+4,y ;display the second half + ldx ObjectOffset ;get enemy object offset and leave + rts + +;------------------------------------------------------------------------------------- + +ScreenRoutines: + lda ScreenRoutineTask ;run one of the following subroutines + jsr JumpEngine + + .dw InitScreen + .dw SetupIntermediate + .dw WriteTopStatusLine + .dw WriteBottomStatusLine + .dw DisplayTimeUp + .dw ResetSpritesAndScreenTimer + .dw DisplayIntermediate + .dw ResetSpritesAndScreenTimer + .dw AreaParserTaskControl + .dw GetAreaPalette + .dw GetBackgroundColor + .dw GetAlternatePalette1 + .dw DrawTitleScreen + .dw ClearBuffersDrawIcon + .dw WriteTopScore + +;------------------------------------------------------------------------------------- + +InitScreen: + jsr MoveAllSpritesOffscreen ;initialize all sprites including sprite #0 + jsr InitializeNameTables ;and erase both name and attribute tables + lda OperMode + beq NextSubtask ;if mode still 0, do not load + ldx #$03 ;into buffer pointer + jmp SetVRAMAddr_A + +;------------------------------------------------------------------------------------- + +SetupIntermediate: + lda BackgroundColorCtrl ;save current background color control + pha ;and player status to stack + lda PlayerStatus + pha + lda #$00 ;set background color to black + sta PlayerStatus ;and player status to not fiery + lda #$02 ;this is the ONLY time background color control + sta BackgroundColorCtrl ;is set to less than 4 + jsr GetPlayerColors + pla ;we only execute this routine for + sta PlayerStatus ;the intermediate lives display + pla ;and once we're done, we return bg + sta BackgroundColorCtrl ;color ctrl and player status from stack + jmp IncSubtask ;then move onto the next task + +;------------------------------------------------------------------------------------- + +AreaPalette: + .db $01, $02, $03, $04 + +GetAreaPalette: + ldy AreaType ;select appropriate palette to load + ldx AreaPalette,y ;based on area type +SetVRAMAddr_A: stx VRAM_Buffer_AddrCtrl ;store offset into buffer control +NextSubtask: jmp IncSubtask ;move onto next task + +;------------------------------------------------------------------------------------- +;$00 - used as temp counter in GetPlayerColors + +BGColorCtrl_Addr: + .db $00, $09, $0a, $04 + +BackgroundColors: + .db $22, $22, $0f, $0f ;used by area type if bg color ctrl not set + .db $0f, $22, $0f, $0f ;used by background color control if set + +PlayerColors: + .db $22, $16, $27, $18 ;mario's colors + .db $22, $30, $27, $19 ;luigi's colors + .db $22, $37, $27, $16 ;fiery (used by both) + +GetBackgroundColor: + ldy BackgroundColorCtrl ;check background color control + beq NoBGColor ;if not set, increment task and fetch palette + lda BGColorCtrl_Addr-4,y ;put appropriate palette into vram + sta VRAM_Buffer_AddrCtrl ;note that if set to 5-7, $0301 will not be read +NoBGColor: inc ScreenRoutineTask ;increment to next subtask and plod on through + +GetPlayerColors: + ldx VRAM_Buffer1_Offset ;get current buffer offset + ldy #$00 + lda CurrentPlayer ;check which player is on the screen + beq ChkFiery + ldy #$04 ;load offset for luigi +ChkFiery: lda PlayerStatus ;check player status + cmp #$02 + bne StartClrGet ;if fiery, load alternate offset for fiery player + ldy #$08 +StartClrGet: lda #$03 ;do four colors + sta $00 +ClrGetLoop: lda PlayerColors,y ;fetch player colors and store them + sta VRAM_Buffer1+3,x ;in the buffer + iny + inx + dec $00 + bpl ClrGetLoop + ldx VRAM_Buffer1_Offset ;load original offset from before + ldy BackgroundColorCtrl ;if this value is four or greater, it will be set + bne SetBGColor ;therefore use it as offset to background color + ldy AreaType ;otherwise use area type bits from area offset as offset +SetBGColor: lda BackgroundColors,y ;to background color instead + sta VRAM_Buffer1+3,x + lda #$3f ;set for sprite palette address + sta VRAM_Buffer1,x ;save to buffer + lda #$10 + sta VRAM_Buffer1+1,x + lda #$04 ;write length byte to buffer + sta VRAM_Buffer1+2,x + lda #$00 ;now the null terminator + sta VRAM_Buffer1+7,x + txa ;move the buffer pointer ahead 7 bytes + clc ;in case we want to write anything else later + adc #$07 +SetVRAMOffset: sta VRAM_Buffer1_Offset ;store as new vram buffer offset + rts + +;------------------------------------------------------------------------------------- + +GetAlternatePalette1: + lda AreaStyle ;check for mushroom level style + cmp #$01 + bne NoAltPal + lda #$0b ;if found, load appropriate palette +SetVRAMAddr_B: sta VRAM_Buffer_AddrCtrl +NoAltPal: jmp IncSubtask ;now onto the next task + +;------------------------------------------------------------------------------------- + +WriteTopStatusLine: + lda #$00 ;select main status bar + jsr WriteGameText ;output it + jmp IncSubtask ;onto the next task + +;------------------------------------------------------------------------------------- + +WriteBottomStatusLine: + jsr GetSBNybbles ;write player's score and coin tally to screen + ldx VRAM_Buffer1_Offset + lda #$20 ;write address for world-area number on screen + sta VRAM_Buffer1,x + lda #$73 + sta VRAM_Buffer1+1,x + lda #$03 ;write length for it + sta VRAM_Buffer1+2,x + ldy WorldNumber ;first the world number + iny + tya + sta VRAM_Buffer1+3,x + lda #$28 ;next the dash + sta VRAM_Buffer1+4,x + ldy LevelNumber ;next the level number + iny ;increment for proper number display + tya + sta VRAM_Buffer1+5,x + lda #$00 ;put null terminator on + sta VRAM_Buffer1+6,x + txa ;move the buffer offset up by 6 bytes + clc + adc #$06 + sta VRAM_Buffer1_Offset + jmp IncSubtask + +;------------------------------------------------------------------------------------- + +DisplayTimeUp: + lda GameTimerExpiredFlag ;if game timer not expired, increment task + beq NoTimeUp ;control 2 tasks forward, otherwise, stay here + lda #$00 + sta GameTimerExpiredFlag ;reset timer expiration flag + lda #$02 ;output time-up screen to buffer + jmp OutputInter +NoTimeUp: inc ScreenRoutineTask ;increment control task 2 tasks forward + jmp IncSubtask + +;------------------------------------------------------------------------------------- + +DisplayIntermediate: + lda OperMode ;check primary mode of operation + beq NoInter ;if in title screen mode, skip this + cmp #GameOverModeValue ;are we in game over mode? + beq GameOverInter ;if so, proceed to display game over screen + lda AltEntranceControl ;otherwise check for mode of alternate entry + bne NoInter ;and branch if found + ldy AreaType ;check if we are on castle level + cpy #$03 ;and if so, branch (possibly residual) + beq PlayerInter + lda DisableIntermediate ;if this flag is set, skip intermediate lives display + bne NoInter ;and jump to specific task, otherwise +PlayerInter: jsr DrawPlayer_Intermediate ;put player in appropriate place for + lda #$01 ;lives display, then output lives display to buffer +OutputInter: jsr WriteGameText + jsr ResetScreenTimer + lda #$00 + sta DisableScreenFlag ;reenable screen output + rts +GameOverInter: lda #$12 ;set screen timer + sta ScreenTimer + lda #$03 ;output game over screen to buffer + jsr WriteGameText + jmp IncModeTask_B +NoInter: lda #$08 ;set for specific task and leave + sta ScreenRoutineTask + rts + +;------------------------------------------------------------------------------------- + +AreaParserTaskControl: + inc DisableScreenFlag ;turn off screen +TaskLoop: jsr AreaParserTaskHandler ;render column set of current area + lda AreaParserTaskNum ;check number of tasks + bne TaskLoop ;if tasks still not all done, do another one + dec ColumnSets ;do we need to render more column sets? + bpl OutputCol + inc ScreenRoutineTask ;if not, move on to the next task +OutputCol: lda #$06 ;set vram buffer to output rendered column set + sta VRAM_Buffer_AddrCtrl ;on next NMI + rts + +;------------------------------------------------------------------------------------- + +;$00 - vram buffer address table low +;$01 - vram buffer address table high + +DrawTitleScreen: + lda OperMode ;are we in title screen mode? + bne IncModeTask_B ;if not, exit + lda #>TitleScreenDataOffset ;load address $1ec0 into + sta PPU_ADDRESS ;the vram address register + lda #Palette0_MTiles, >Palette1_MTiles, >Palette2_MTiles, >Palette3_MTiles + +Palette0_MTiles: + .db $24, $24, $24, $24 ;blank + .db $27, $27, $27, $27 ;black metatile + .db $24, $24, $24, $35 ;bush left + .db $36, $25, $37, $25 ;bush middle + .db $24, $38, $24, $24 ;bush right + .db $24, $30, $30, $26 ;mountain left + .db $26, $26, $34, $26 ;mountain left bottom/middle center + .db $24, $31, $24, $32 ;mountain middle top + .db $33, $26, $24, $33 ;mountain right + .db $34, $26, $26, $26 ;mountain right bottom + .db $26, $26, $26, $26 ;mountain middle bottom + .db $24, $c0, $24, $c0 ;bridge guardrail + .db $24, $7f, $7f, $24 ;chain + .db $b8, $ba, $b9, $bb ;tall tree top, top half + .db $b8, $bc, $b9, $bd ;short tree top + .db $ba, $bc, $bb, $bd ;tall tree top, bottom half + .db $60, $64, $61, $65 ;warp pipe end left, points up + .db $62, $66, $63, $67 ;warp pipe end right, points up + .db $60, $64, $61, $65 ;decoration pipe end left, points up + .db $62, $66, $63, $67 ;decoration pipe end right, points up + .db $68, $68, $69, $69 ;pipe shaft left + .db $26, $26, $6a, $6a ;pipe shaft right + .db $4b, $4c, $4d, $4e ;tree ledge left edge + .db $4d, $4f, $4d, $4f ;tree ledge middle + .db $4d, $4e, $50, $51 ;tree ledge right edge + .db $6b, $70, $2c, $2d ;mushroom left edge + .db $6c, $71, $6d, $72 ;mushroom middle + .db $6e, $73, $6f, $74 ;mushroom right edge + .db $86, $8a, $87, $8b ;sideways pipe end top + .db $88, $8c, $88, $8c ;sideways pipe shaft top + .db $89, $8d, $69, $69 ;sideways pipe joint top + .db $8e, $91, $8f, $92 ;sideways pipe end bottom + .db $26, $93, $26, $93 ;sideways pipe shaft bottom + .db $90, $94, $69, $69 ;sideways pipe joint bottom + .db $a4, $e9, $ea, $eb ;seaplant + .db $24, $24, $24, $24 ;blank, used on bricks or blocks that are hit + .db $24, $2f, $24, $3d ;flagpole ball + .db $a2, $a2, $a3, $a3 ;flagpole shaft + .db $24, $24, $24, $24 ;blank, used in conjunction with vines + +Palette1_MTiles: + .db $a2, $a2, $a3, $a3 ;vertical rope + .db $99, $24, $99, $24 ;horizontal rope + .db $24, $a2, $3e, $3f ;left pulley + .db $5b, $5c, $24, $a3 ;right pulley + .db $24, $24, $24, $24 ;blank used for balance rope + .db $9d, $47, $9e, $47 ;castle top + .db $47, $47, $27, $27 ;castle window left + .db $47, $47, $47, $47 ;castle brick wall + .db $27, $27, $47, $47 ;castle window right + .db $a9, $47, $aa, $47 ;castle top w/ brick + .db $9b, $27, $9c, $27 ;entrance top + .db $27, $27, $27, $27 ;entrance bottom + .db $52, $52, $52, $52 ;green ledge stump + .db $80, $a0, $81, $a1 ;fence + .db $be, $be, $bf, $bf ;tree trunk + .db $75, $ba, $76, $bb ;mushroom stump top + .db $ba, $ba, $bb, $bb ;mushroom stump bottom + .db $45, $47, $45, $47 ;breakable brick w/ line + .db $47, $47, $47, $47 ;breakable brick + .db $45, $47, $45, $47 ;breakable brick (not used) + .db $b4, $b6, $b5, $b7 ;cracked rock terrain + .db $45, $47, $45, $47 ;brick with line (power-up) + .db $45, $47, $45, $47 ;brick with line (vine) + .db $45, $47, $45, $47 ;brick with line (star) + .db $45, $47, $45, $47 ;brick with line (coins) + .db $45, $47, $45, $47 ;brick with line (1-up) + .db $47, $47, $47, $47 ;brick (power-up) + .db $47, $47, $47, $47 ;brick (vine) + .db $47, $47, $47, $47 ;brick (star) + .db $47, $47, $47, $47 ;brick (coins) + .db $47, $47, $47, $47 ;brick (1-up) + .db $24, $24, $24, $24 ;hidden block (1 coin) + .db $24, $24, $24, $24 ;hidden block (1-up) + .db $ab, $ac, $ad, $ae ;solid block (3-d block) + .db $5d, $5e, $5d, $5e ;solid block (white wall) + .db $c1, $24, $c1, $24 ;bridge + .db $c6, $c8, $c7, $c9 ;bullet bill cannon barrel + .db $ca, $cc, $cb, $cd ;bullet bill cannon top + .db $2a, $2a, $40, $40 ;bullet bill cannon bottom + .db $24, $24, $24, $24 ;blank used for jumpspring + .db $24, $47, $24, $47 ;half brick used for jumpspring + .db $82, $83, $84, $85 ;solid block (water level, green rock) + .db $24, $47, $24, $47 ;half brick (???) + .db $86, $8a, $87, $8b ;water pipe top + .db $8e, $91, $8f, $92 ;water pipe bottom + .db $24, $2f, $24, $3d ;flag ball (residual object) + +Palette2_MTiles: + .db $24, $24, $24, $35 ;cloud left + .db $36, $25, $37, $25 ;cloud middle + .db $24, $38, $24, $24 ;cloud right + .db $24, $24, $39, $24 ;cloud bottom left + .db $3a, $24, $3b, $24 ;cloud bottom middle + .db $3c, $24, $24, $24 ;cloud bottom right + .db $41, $26, $41, $26 ;water/lava top + .db $26, $26, $26, $26 ;water/lava + .db $b0, $b1, $b2, $b3 ;cloud level terrain + .db $77, $79, $77, $79 ;bowser's bridge + +Palette3_MTiles: + .db $53, $55, $54, $56 ;question block (coin) + .db $53, $55, $54, $56 ;question block (power-up) + .db $a5, $a7, $a6, $a8 ;coin + .db $c2, $c4, $c3, $c5 ;underwater coin + .db $57, $59, $58, $5a ;empty block + .db $7b, $7d, $7c, $7e ;axe + +;------------------------------------------------------------------------------------- +;VRAM BUFFER DATA FOR LOCATIONS IN PRG-ROM + +WaterPaletteData: + .db $3f, $00, $20 + .db $0f, $15, $12, $25 + .db $0f, $3a, $1a, $0f + .db $0f, $30, $12, $0f + .db $0f, $27, $12, $0f + .db $22, $16, $27, $18 + .db $0f, $10, $30, $27 + .db $0f, $16, $30, $27 + .db $0f, $0f, $30, $10 + .db $00 + +GroundPaletteData: + .db $3f, $00, $20 + .db $0f, $29, $1a, $0f + .db $0f, $36, $17, $0f + .db $0f, $30, $21, $0f + .db $0f, $27, $17, $0f + .db $0f, $16, $27, $18 + .db $0f, $1a, $30, $27 + .db $0f, $16, $30, $27 + .db $0f, $0f, $36, $17 + .db $00 + +UndergroundPaletteData: + .db $3f, $00, $20 + .db $0f, $29, $1a, $09 + .db $0f, $3c, $1c, $0f + .db $0f, $30, $21, $1c + .db $0f, $27, $17, $1c + .db $0f, $16, $27, $18 + .db $0f, $1c, $36, $17 + .db $0f, $16, $30, $27 + .db $0f, $0c, $3c, $1c + .db $00 + +CastlePaletteData: + .db $3f, $00, $20 + .db $0f, $30, $10, $00 + .db $0f, $30, $10, $00 + .db $0f, $30, $16, $00 + .db $0f, $27, $17, $00 + .db $0f, $16, $27, $18 + .db $0f, $1c, $36, $17 + .db $0f, $16, $30, $27 + .db $0f, $00, $30, $10 + .db $00 + +DaySnowPaletteData: + .db $3f, $00, $04 + .db $22, $30, $00, $10 + .db $00 + +NightSnowPaletteData: + .db $3f, $00, $04 + .db $0f, $30, $00, $10 + .db $00 + +MushroomPaletteData: + .db $3f, $00, $04 + .db $22, $27, $16, $0f + .db $00 + +BowserPaletteData: + .db $3f, $14, $04 + .db $0f, $1a, $30, $27 + .db $00 + +MarioThanksMessage: +;"THANK YOU MARIO!" + .db $25, $48, $10 + .db $1d, $11, $0a, $17, $14, $24 + .db $22, $18, $1e, $24 + .db $16, $0a, $1b, $12, $18, $2b + .db $00 + +LuigiThanksMessage: +;"THANK YOU LUIGI!" + .db $25, $48, $10 + .db $1d, $11, $0a, $17, $14, $24 + .db $22, $18, $1e, $24 + .db $15, $1e, $12, $10, $12, $2b + .db $00 + +MushroomRetainerSaved: +;"BUT OUR PRINCESS IS IN" + .db $25, $c5, $16 + .db $0b, $1e, $1d, $24, $18, $1e, $1b, $24 + .db $19, $1b, $12, $17, $0c, $0e, $1c, $1c, $24 + .db $12, $1c, $24, $12, $17 +;"ANOTHER CASTLE!" + .db $26, $05, $0f + .db $0a, $17, $18, $1d, $11, $0e, $1b, $24 + .db $0c, $0a, $1c, $1d, $15, $0e, $2b, $00 + +PrincessSaved1: +;"YOUR QUEST IS OVER." + .db $25, $a7, $13 + .db $22, $18, $1e, $1b, $24 + .db $1a, $1e, $0e, $1c, $1d, $24 + .db $12, $1c, $24, $18, $1f, $0e, $1b, $af + .db $00 + +PrincessSaved2: +;"WE PRESENT YOU A NEW QUEST." + .db $25, $e3, $1b + .db $20, $0e, $24 + .db $19, $1b, $0e, $1c, $0e, $17, $1d, $24 + .db $22, $18, $1e, $24, $0a, $24, $17, $0e, $20, $24 + .db $1a, $1e, $0e, $1c, $1d, $af + .db $00 + +WorldSelectMessage1: +;"PUSH BUTTON B" + .db $26, $4a, $0d + .db $19, $1e, $1c, $11, $24 + .db $0b, $1e, $1d, $1d, $18, $17, $24, $0b + .db $00 + +WorldSelectMessage2: +;"TO SELECT A WORLD" + .db $26, $88, $11 + .db $1d, $18, $24, $1c, $0e, $15, $0e, $0c, $1d, $24 + .db $0a, $24, $20, $18, $1b, $15, $0d + .db $00 + +;------------------------------------------------------------------------------------- +;$04 - address low to jump address +;$05 - address high to jump address +;$06 - jump address low +;$07 - jump address high + +JumpEngine: + asl ;shift bit from contents of A + tay + pla ;pull saved return address from stack + sta $04 ;save to indirect + pla + sta $05 + iny + lda ($04),y ;load pointer from indirect + sta $06 ;note that if an RTS is performed in next routine + iny ;it will return to the execution before the sub + lda ($04),y ;that called this routine + sta $07 + jmp ($0006) ;jump to the address we loaded + +;------------------------------------------------------------------------------------- + +InitializeNameTables: + lda PPU_STATUS ;reset flip-flop + lda Mirror_PPU_CTRL_REG1 ;load mirror of ppu reg $2000 + ora #%00010000 ;set sprites for first 4k and background for second 4k + and #%11110000 ;clear rest of lower nybble, leave higher alone + jsr WritePPUReg1 + lda #$24 ;set vram address to start of name table 1 + jsr WriteNTAddr + lda #$20 ;and then set it to name table 0 +WriteNTAddr: sta PPU_ADDRESS + lda #$00 + sta PPU_ADDRESS + ldx #$04 ;clear name table with blank tile #24 + ldy #$c0 + lda #$24 +InitNTLoop: sta PPU_DATA ;count out exactly 768 tiles + dey + bne InitNTLoop + dex + bne InitNTLoop + ldy #64 ;now to clear the attribute table (with zero this time) + txa + sta VRAM_Buffer1_Offset ;init vram buffer 1 offset + sta VRAM_Buffer1 ;init vram buffer 1 +InitATLoop: sta PPU_DATA + dey + bne InitATLoop + sta HorizontalScroll ;reset scroll variables + sta VerticalScroll + jmp InitScroll ;initialize scroll registers to zero + +;------------------------------------------------------------------------------------- +;$00 - temp joypad bit + +ReadJoypads: + lda #$01 ;reset and clear strobe of joypad ports + sta JOYPAD_PORT + lsr + tax ;start with joypad 1's port + sta JOYPAD_PORT + jsr ReadPortBits + inx ;increment for joypad 2's port +ReadPortBits: ldy #$08 +PortLoop: pha ;push previous bit onto stack + lda JOYPAD_PORT,x ;read current bit on joypad port + sta $00 ;check d1 and d0 of port output + lsr ;this is necessary on the old + ora $00 ;famicom systems in japan + lsr + pla ;read bits from stack + rol ;rotate bit from carry flag + dey + bne PortLoop ;count down bits left + sta SavedJoypadBits,x ;save controller status here always + pha + and #%00110000 ;check for select or start + and JoypadBitMask,x ;if neither saved state nor current state + beq Save8Bits ;have any of these two set, branch + pla + and #%11001111 ;otherwise store without select + sta SavedJoypadBits,x ;or start bits and leave + rts +Save8Bits: pla + sta JoypadBitMask,x ;save with all bits in another place and leave + rts + +;------------------------------------------------------------------------------------- +;$00 - vram buffer address table low +;$01 - vram buffer address table high + +WriteBufferToScreen: + sta PPU_ADDRESS ;store high byte of vram address + iny + lda ($00),y ;load next byte (second) + sta PPU_ADDRESS ;store low byte of vram address + iny + lda ($00),y ;load next byte (third) + asl ;shift to left and save in stack + pha + lda Mirror_PPU_CTRL_REG1 ;load mirror of $2000, + ora #%00000100 ;set ppu to increment by 32 by default + bcs SetupWrites ;if d7 of third byte was clear, ppu will + and #%11111011 ;only increment by 1 +SetupWrites: jsr WritePPUReg1 ;write to register + pla ;pull from stack and shift to left again + asl + bcc GetLength ;if d6 of third byte was clear, do not repeat byte + ora #%00000010 ;otherwise set d1 and increment Y + iny +GetLength: lsr ;shift back to the right to get proper length + lsr ;note that d1 will now be in carry + tax +OutputToVRAM: bcs RepeatByte ;if carry set, repeat loading the same byte + iny ;otherwise increment Y to load next byte +RepeatByte: lda ($00),y ;load more data from buffer and write to vram + sta PPU_DATA + dex ;done writing? + bne OutputToVRAM + sec + tya + adc $00 ;add end length plus one to the indirect at $00 + sta $00 ;to allow this routine to read another set of updates + lda #$00 + adc $01 + sta $01 + lda #$3f ;sets vram address to $3f00 + sta PPU_ADDRESS + lda #$00 + sta PPU_ADDRESS + sta PPU_ADDRESS ;then reinitializes it for some reason + sta PPU_ADDRESS +UpdateScreen: ldx PPU_STATUS ;reset flip-flop + ldy #$00 ;load first byte from indirect as a pointer + lda ($00),y + bne WriteBufferToScreen ;if byte is zero we have no further updates to make here +InitScroll: sta PPU_SCROLL_REG ;store contents of A into scroll registers + sta PPU_SCROLL_REG ;and end whatever subroutine led us here + rts + +;------------------------------------------------------------------------------------- + +WritePPUReg1: + sta PPU_CTRL_REG1 ;write contents of A to PPU register 1 + sta Mirror_PPU_CTRL_REG1 ;and its mirror + rts + +;------------------------------------------------------------------------------------- +;$00 - used to store status bar nybbles +;$02 - used as temp vram offset +;$03 - used to store length of status bar number + +;status bar name table offset and length data +StatusBarData: + .db $f0, $06 ; top score display on title screen + .db $62, $06 ; player score + .db $62, $06 + .db $6d, $02 ; coin tally + .db $6d, $02 + .db $7a, $03 ; game timer + +StatusBarOffset: + .db $06, $0c, $12, $18, $1e, $24 + +PrintStatusBarNumbers: + sta $00 ;store player-specific offset + jsr OutputNumbers ;use first nybble to print the coin display + lda $00 ;move high nybble to low + lsr ;and print to score display + lsr + lsr + lsr + +OutputNumbers: + clc ;add 1 to low nybble + adc #$01 + and #%00001111 ;mask out high nybble + cmp #$06 + bcs ExitOutputN + pha ;save incremented value to stack for now and + asl ;shift to left and use as offset + tay + ldx VRAM_Buffer1_Offset ;get current buffer pointer + lda #$20 ;put at top of screen by default + cpy #$00 ;are we writing top score on title screen? + bne SetupNums + lda #$22 ;if so, put further down on the screen +SetupNums: sta VRAM_Buffer1,x + lda StatusBarData,y ;write low vram address and length of thing + sta VRAM_Buffer1+1,x ;we're printing to the buffer + lda StatusBarData+1,y + sta VRAM_Buffer1+2,x + sta $03 ;save length byte in counter + stx $02 ;and buffer pointer elsewhere for now + pla ;pull original incremented value from stack + tax + lda StatusBarOffset,x ;load offset to value we want to write + sec + sbc StatusBarData+1,y ;subtract from length byte we read before + tay ;use value as offset to display digits + ldx $02 +DigitPLoop: lda DisplayDigits,y ;write digits to the buffer + sta VRAM_Buffer1+3,x + inx + iny + dec $03 ;do this until all the digits are written + bne DigitPLoop + lda #$00 ;put null terminator at end + sta VRAM_Buffer1+3,x + inx ;increment buffer pointer by 3 + inx + inx + stx VRAM_Buffer1_Offset ;store it in case we want to use it again +ExitOutputN: rts + +;------------------------------------------------------------------------------------- + +DigitsMathRoutine: + lda OperMode ;check mode of operation + cmp #TitleScreenModeValue + beq EraseDMods ;if in title screen mode, branch to lock score + ldx #$05 +AddModLoop: lda DigitModifier,x ;load digit amount to increment + clc + adc DisplayDigits,y ;add to current digit + bmi BorrowOne ;if result is a negative number, branch to subtract + cmp #10 + bcs CarryOne ;if digit greater than $09, branch to add +StoreNewD: sta DisplayDigits,y ;store as new score or game timer digit + dey ;move onto next digits in score or game timer + dex ;and digit amounts to increment + bpl AddModLoop ;loop back if we're not done yet +EraseDMods: lda #$00 ;store zero here + ldx #$06 ;start with the last digit +EraseMLoop: sta DigitModifier-1,x ;initialize the digit amounts to increment + dex + bpl EraseMLoop ;do this until they're all reset, then leave + rts +BorrowOne: dec DigitModifier-1,x ;decrement the previous digit, then put $09 in + lda #$09 ;the game timer digit we're currently on to "borrow + bne StoreNewD ;the one", then do an unconditional branch back +CarryOne: sec ;subtract ten from our digit to make it a + sbc #10 ;proper BCD number, then increment the digit + inc DigitModifier-1,x ;preceding current digit to "carry the one" properly + jmp StoreNewD ;go back to just after we branched here + +;------------------------------------------------------------------------------------- + +UpdateTopScore: + ldx #$05 ;start with mario's score + jsr TopScoreCheck + ldx #$0b ;now do luigi's score + +TopScoreCheck: + ldy #$05 ;start with the lowest digit + sec +GetScoreDiff: lda PlayerScoreDisplay,x ;subtract each player digit from each high score digit + sbc TopScoreDisplay,y ;from lowest to highest, if any top score digit exceeds + dex ;any player digit, borrow will be set until a subsequent + dey ;subtraction clears it (player digit is higher than top) + bpl GetScoreDiff + bcc NoTopSc ;check to see if borrow is still set, if so, no new high score + inx ;increment X and Y once to the start of the score + iny +CopyScore: lda PlayerScoreDisplay,x ;store player's score digits into high score memory area + sta TopScoreDisplay,y + inx + iny + cpy #$06 ;do this until we have stored them all + bcc CopyScore +NoTopSc: rts + +;------------------------------------------------------------------------------------- + +DefaultSprOffsets: + .db $04, $30, $48, $60, $78, $90, $a8, $c0 + .db $d8, $e8, $24, $f8, $fc, $28, $2c + +Sprite0Data: + .db $18, $ff, $23, $58 + +;------------------------------------------------------------------------------------- + +InitializeGame: + ldy #$6f ;clear all memory as in initialization procedure, + jsr InitializeMemory ;but this time, clear only as far as $076f + ldy #$1f +ClrSndLoop: sta SoundMemory,y ;clear out memory used + dey ;by the sound engines + bpl ClrSndLoop + lda #$18 ;set demo timer + sta DemoTimer + jsr LoadAreaPointer + +InitializeArea: + ldy #$4b ;clear all memory again, only as far as $074b + jsr InitializeMemory ;this is only necessary in game mode + ldx #$21 + lda #$00 +ClrTimersLoop: sta Timers,x ;clear out memory between + dex ;$0780 and $07a1 + bpl ClrTimersLoop + lda HalfwayPage + ldy AltEntranceControl ;if AltEntranceControl not set, use halfway page, if any found + beq StartPage + lda EntrancePage ;otherwise use saved entry page number here +StartPage: sta ScreenLeft_PageLoc ;set as value here + sta CurrentPageLoc ;also set as current page + sta BackloadingFlag ;set flag here if halfway page or saved entry page number found + jsr GetScreenPosition ;get pixel coordinates for screen borders + ldy #$20 ;if on odd numbered page, use $2480 as start of rendering + and #%00000001 ;otherwise use $2080, this address used later as name table + beq SetInitNTHigh ;address for rendering of game area + ldy #$24 +SetInitNTHigh: sty CurrentNTAddr_High ;store name table address + ldy #$80 + sty CurrentNTAddr_Low + asl ;store LSB of page number in high nybble + asl ;of block buffer column position + asl + asl + sta BlockBufferColumnPos + dec AreaObjectLength ;set area object lengths for all empty + dec AreaObjectLength+1 + dec AreaObjectLength+2 + lda #$0b ;set value for renderer to update 12 column sets + sta ColumnSets ;12 column sets = 24 metatile columns = 1 1/2 screens + jsr GetAreaDataAddrs ;get enemy and level addresses and load header + lda PrimaryHardMode ;check to see if primary hard mode has been activated + bne SetSecHard ;if so, activate the secondary no matter where we're at + lda WorldNumber ;otherwise check world number + cmp #World5 ;if less than 5, do not activate secondary + bcc CheckHalfway + bne SetSecHard ;if not equal to, then world > 5, thus activate + lda LevelNumber ;otherwise, world 5, so check level number + cmp #Level3 ;if 1 or 2, do not set secondary hard mode flag + bcc CheckHalfway +SetSecHard: inc SecondaryHardMode ;set secondary hard mode flag for areas 5-3 and beyond +CheckHalfway: lda HalfwayPage + beq DoneInitArea + lda #$02 ;if halfway page set, overwrite start position from header + sta PlayerEntranceCtrl +DoneInitArea: lda #Silence ;silence music + sta AreaMusicQueue + lda #$01 ;disable screen output + sta DisableScreenFlag + inc OperMode_Task ;increment one of the modes + rts + +;------------------------------------------------------------------------------------- + +PrimaryGameSetup: + lda #$01 + sta FetchNewGameTimerFlag ;set flag to load game timer from header + sta PlayerSize ;set player's size to small + lda #$02 + sta NumberofLives ;give each player three lives + sta OffScr_NumberofLives + +SecondaryGameSetup: + lda #$00 + sta DisableScreenFlag ;enable screen output + tay +ClearVRLoop: sta VRAM_Buffer1-1,y ;clear buffer at $0300-$03ff + iny + bne ClearVRLoop + sta GameTimerExpiredFlag ;clear game timer exp flag + sta DisableIntermediate ;clear skip lives display flag + sta BackloadingFlag ;clear value here + lda #$ff + sta BalPlatformAlignment ;initialize balance platform assignment flag + lda ScreenLeft_PageLoc ;get left side page location + lsr Mirror_PPU_CTRL_REG1 ;shift LSB of ppu register #1 mirror out + and #$01 ;mask out all but LSB of page location + ror ;rotate LSB of page location into carry then onto mirror + rol Mirror_PPU_CTRL_REG1 ;this is to set the proper PPU name table + jsr GetAreaMusic ;load proper music into queue + lda #$38 ;load sprite shuffle amounts to be used later + sta SprShuffleAmt+2 + lda #$48 + sta SprShuffleAmt+1 + lda #$58 + sta SprShuffleAmt + ldx #$0e ;load default OAM offsets into $06e4-$06f2 +ShufAmtLoop: lda DefaultSprOffsets,x + sta SprDataOffset,x + dex ;do this until they're all set + bpl ShufAmtLoop + ldy #$03 ;set up sprite #0 +ISpr0Loop: lda Sprite0Data,y + sta Sprite_Data,y + dey + bpl ISpr0Loop + jsr DoNothing2 ;these jsrs doesn't do anything useful + jsr DoNothing1 + inc Sprite0HitDetectFlag ;set sprite #0 check flag + inc OperMode_Task ;increment to next task + rts + +;------------------------------------------------------------------------------------- + +;$06 - RAM address low +;$07 - RAM address high + +InitializeMemory: + ldx #$07 ;set initial high byte to $0700-$07ff + lda #$00 ;set initial low byte to start of page (at $00 of page) + sta $06 +InitPageLoop: stx $07 +InitByteLoop: cpx #$01 ;check to see if we're on the stack ($0100-$01ff) + bne InitByte ;if not, go ahead anyway + cpy #$60 ;otherwise, check to see if we're at $0160-$01ff + bcs SkipByte ;if so, skip write +InitByte: sta ($06),y ;otherwise, initialize byte with current low byte in Y +SkipByte: dey + cpy #$ff ;do this until all bytes in page have been erased + bne InitByteLoop + dex ;go onto the next page + bpl InitPageLoop ;do this until all pages of memory have been erased + rts + +;------------------------------------------------------------------------------------- + +MusicSelectData: + .db WaterMusic, GroundMusic, UndergroundMusic, CastleMusic + .db CloudMusic, PipeIntroMusic + +GetAreaMusic: + lda OperMode ;if in title screen mode, leave + beq ExitGetM + lda AltEntranceControl ;check for specific alternate mode of entry + cmp #$02 ;if found, branch without checking starting position + beq ChkAreaType ;from area object data header + ldy #$05 ;select music for pipe intro scene by default + lda PlayerEntranceCtrl ;check value from level header for certain values + cmp #$06 + beq StoreMusic ;load music for pipe intro scene if header + cmp #$07 ;start position either value $06 or $07 + beq StoreMusic +ChkAreaType: ldy AreaType ;load area type as offset for music bit + lda CloudTypeOverride + beq StoreMusic ;check for cloud type override + ldy #$04 ;select music for cloud type level if found +StoreMusic: lda MusicSelectData,y ;otherwise select appropriate music for level type + sta AreaMusicQueue ;store in queue and leave +ExitGetM: rts + +;------------------------------------------------------------------------------------- + +PlayerStarting_X_Pos: + .db $28, $18 + .db $38, $28 + +AltYPosOffset: + .db $08, $00 + +PlayerStarting_Y_Pos: + .db $00, $20, $b0, $50, $00, $00, $b0, $b0 + .db $f0 + +PlayerBGPriorityData: + .db $00, $20, $00, $00, $00, $00, $00, $00 + +GameTimerData: + .db $20 ;dummy byte, used as part of bg priority data + .db $04, $03, $02 + +Entrance_GameTimerSetup: + lda ScreenLeft_PageLoc ;set current page for area objects + sta Player_PageLoc ;as page location for player + lda #$28 ;store value here + sta VerticalForceDown ;for fractional movement downwards if necessary + lda #$01 ;set high byte of player position and + sta PlayerFacingDir ;set facing direction so that player faces right + sta Player_Y_HighPos + lda #$00 ;set player state to on the ground by default + sta Player_State + dec Player_CollisionBits ;initialize player's collision bits + ldy #$00 ;initialize halfway page + sty HalfwayPage + lda AreaType ;check area type + bne ChkStPos ;if water type, set swimming flag, otherwise do not set + iny +ChkStPos: sty SwimmingFlag + ldx PlayerEntranceCtrl ;get starting position loaded from header + ldy AltEntranceControl ;check alternate mode of entry flag for 0 or 1 + beq SetStPos + cpy #$01 + beq SetStPos + ldx AltYPosOffset-2,y ;if not 0 or 1, override $0710 with new offset in X +SetStPos: lda PlayerStarting_X_Pos,y ;load appropriate horizontal position + sta Player_X_Position ;and vertical positions for the player, using + lda PlayerStarting_Y_Pos,x ;AltEntranceControl as offset for horizontal and either $0710 + sta Player_Y_Position ;or value that overwrote $0710 as offset for vertical + lda PlayerBGPriorityData,x + sta Player_SprAttrib ;set player sprite attributes using offset in X + jsr GetPlayerColors ;get appropriate player palette + ldy GameTimerSetting ;get timer control value from header + beq ChkOverR ;if set to zero, branch (do not use dummy byte for this) + lda FetchNewGameTimerFlag ;do we need to set the game timer? if not, use + beq ChkOverR ;old game timer setting + lda GameTimerData,y ;if game timer is set and game timer flag is also set, + sta GameTimerDisplay ;use value of game timer control for first digit of game timer + lda #$01 + sta GameTimerDisplay+2 ;set last digit of game timer to 1 + lsr + sta GameTimerDisplay+1 ;set second digit of game timer + sta FetchNewGameTimerFlag ;clear flag for game timer reset + sta StarInvincibleTimer ;clear star mario timer +ChkOverR: ldy JoypadOverride ;if controller bits not set, branch to skip this part + beq ChkSwimE + lda #$03 ;set player state to climbing + sta Player_State + ldx #$00 ;set offset for first slot, for block object + jsr InitBlock_XY_Pos + lda #$f0 ;set vertical coordinate for block object + sta Block_Y_Position + ldx #$05 ;set offset in X for last enemy object buffer slot + ldy #$00 ;set offset in Y for object coordinates used earlier + jsr Setup_Vine ;do a sub to grow vine +ChkSwimE: ldy AreaType ;if level not water-type, + bne SetPESub ;skip this subroutine + jsr SetupBubble ;otherwise, execute sub to set up air bubbles +SetPESub: lda #$07 ;set to run player entrance subroutine + sta GameEngineSubroutine ;on the next frame of game engine + rts + +;------------------------------------------------------------------------------------- + +;page numbers are in order from -1 to -4 +HalfwayPageNybbles: + .db $56, $40 + .db $65, $70 + .db $66, $40 + .db $66, $40 + .db $66, $40 + .db $66, $60 + .db $65, $70 + .db $00, $00 + +PlayerLoseLife: + inc DisableScreenFlag ;disable screen and sprite 0 check + lda #$00 + sta Sprite0HitDetectFlag + lda #Silence ;silence music + sta EventMusicQueue + dec NumberofLives ;take one life from player + bpl StillInGame ;if player still has lives, branch + lda #$00 + sta OperMode_Task ;initialize mode task, + lda #GameOverModeValue ;switch to game over mode + sta OperMode ;and leave + rts +StillInGame: lda WorldNumber ;multiply world number by 2 and use + asl ;as offset + tax + lda LevelNumber ;if in area -3 or -4, increment + and #$02 ;offset by one byte, otherwise + beq GetHalfway ;leave offset alone + inx +GetHalfway: ldy HalfwayPageNybbles,x ;get halfway page number with offset + lda LevelNumber ;check area number's LSB + lsr + tya ;if in area -2 or -4, use lower nybble + bcs MaskHPNyb + lsr ;move higher nybble to lower if area + lsr ;number is -1 or -3 + lsr + lsr +MaskHPNyb: and #%00001111 ;mask out all but lower nybble + cmp ScreenLeft_PageLoc + beq SetHalfway ;left side of screen must be at the halfway page, + bcc SetHalfway ;otherwise player must start at the + lda #$00 ;beginning of the level +SetHalfway: sta HalfwayPage ;store as halfway page for player + jsr TransposePlayers ;switch players around if 2-player game + jmp ContinueGame ;continue the game + +;------------------------------------------------------------------------------------- + +GameOverMode: + lda OperMode_Task + jsr JumpEngine + + .dw SetupGameOver + .dw ScreenRoutines + .dw RunGameOver + +;------------------------------------------------------------------------------------- + +SetupGameOver: + lda #$00 ;reset screen routine task control for title screen, game, + sta ScreenRoutineTask ;and game over modes + sta Sprite0HitDetectFlag ;disable sprite 0 check + lda #GameOverMusic + sta EventMusicQueue ;put game over music in secondary queue + inc DisableScreenFlag ;disable screen output + inc OperMode_Task ;set secondary mode to 1 + rts + +;------------------------------------------------------------------------------------- + +RunGameOver: + lda #$00 ;reenable screen + sta DisableScreenFlag + lda SavedJoypad1Bits ;check controller for start pressed + and #Start_Button + bne TerminateGame + lda ScreenTimer ;if not pressed, wait for + bne GameIsOn ;screen timer to expire +TerminateGame: + lda #Silence ;silence music + sta EventMusicQueue + jsr TransposePlayers ;check if other player can keep + bcc ContinueGame ;going, and do so if possible + lda WorldNumber ;otherwise put world number of current + sta ContinueWorld ;player into secret continue function variable + lda #$00 + asl ;residual ASL instruction + sta OperMode_Task ;reset all modes to title screen and + sta ScreenTimer ;leave + sta OperMode + rts + +ContinueGame: + jsr LoadAreaPointer ;update level pointer with + lda #$01 ;actual world and area numbers, then + sta PlayerSize ;reset player's size, status, and + inc FetchNewGameTimerFlag ;set game timer flag to reload + lda #$00 ;game timer from header + sta TimerControl ;also set flag for timers to count again + sta PlayerStatus + sta GameEngineSubroutine ;reset task for game core + sta OperMode_Task ;set modes and leave + lda #$01 ;if in game over mode, switch back to + sta OperMode ;game mode, because game is still on +GameIsOn: rts + +TransposePlayers: + sec ;set carry flag by default to end game + lda NumberOfPlayers ;if only a 1 player game, leave + beq ExTrans + lda OffScr_NumberofLives ;does offscreen player have any lives left? + bmi ExTrans ;branch if not + lda CurrentPlayer ;invert bit to update + eor #%00000001 ;which player is on the screen + sta CurrentPlayer + ldx #$06 +TransLoop: lda OnscreenPlayerInfo,x ;transpose the information + pha ;of the onscreen player + lda OffscreenPlayerInfo,x ;with that of the offscreen player + sta OnscreenPlayerInfo,x + pla + sta OffscreenPlayerInfo,x + dex + bpl TransLoop + clc ;clear carry flag to get game going +ExTrans: rts + +;------------------------------------------------------------------------------------- + +DoNothing1: + lda #$ff ;this is residual code, this value is + sta $06c9 ;not used anywhere in the program +DoNothing2: + rts + +;------------------------------------------------------------------------------------- + +AreaParserTaskHandler: + ldy AreaParserTaskNum ;check number of tasks here + bne DoAPTasks ;if already set, go ahead + ldy #$08 + sty AreaParserTaskNum ;otherwise, set eight by default +DoAPTasks: dey + tya + jsr AreaParserTasks + dec AreaParserTaskNum ;if all tasks not complete do not + bne SkipATRender ;render attribute table yet + jsr RenderAttributeTables +SkipATRender: rts + +AreaParserTasks: + jsr JumpEngine + + .dw IncrementColumnPos + .dw RenderAreaGraphics + .dw RenderAreaGraphics + .dw AreaParserCore + .dw IncrementColumnPos + .dw RenderAreaGraphics + .dw RenderAreaGraphics + .dw AreaParserCore + +;------------------------------------------------------------------------------------- + +IncrementColumnPos: + inc CurrentColumnPos ;increment column where we're at + lda CurrentColumnPos + and #%00001111 ;mask out higher nybble + bne NoColWrap + sta CurrentColumnPos ;if no bits left set, wrap back to zero (0-f) + inc CurrentPageLoc ;and increment page number where we're at +NoColWrap: inc BlockBufferColumnPos ;increment column offset where we're at + lda BlockBufferColumnPos + and #%00011111 ;mask out all but 5 LSB (0-1f) + sta BlockBufferColumnPos ;and save + rts + +;------------------------------------------------------------------------------------- +;$00 - used as counter, store for low nybble for background, ceiling byte for terrain +;$01 - used to store floor byte for terrain +;$07 - used to store terrain metatile +;$06-$07 - used to store block buffer address + +BSceneDataOffsets: + .db $00, $30, $60 + +BackSceneryData: + .db $93, $00, $00, $11, $12, $12, $13, $00 ;clouds + .db $00, $51, $52, $53, $00, $00, $00, $00 + .db $00, $00, $01, $02, $02, $03, $00, $00 + .db $00, $00, $00, $00, $91, $92, $93, $00 + .db $00, $00, $00, $51, $52, $53, $41, $42 + .db $43, $00, $00, $00, $00, $00, $91, $92 + + .db $97, $87, $88, $89, $99, $00, $00, $00 ;mountains and bushes + .db $11, $12, $13, $a4, $a5, $a5, $a5, $a6 + .db $97, $98, $99, $01, $02, $03, $00, $a4 + .db $a5, $a6, $00, $11, $12, $12, $12, $13 + .db $00, $00, $00, $00, $01, $02, $02, $03 + .db $00, $a4, $a5, $a5, $a6, $00, $00, $00 + + .db $11, $12, $12, $13, $00, $00, $00, $00 ;trees and fences + .db $00, $00, $00, $9c, $00, $8b, $aa, $aa + .db $aa, $aa, $11, $12, $13, $8b, $00, $9c + .db $9c, $00, $00, $01, $02, $03, $11, $12 + .db $12, $13, $00, $00, $00, $00, $aa, $aa + .db $9c, $aa, $00, $8b, $00, $01, $02, $03 + +BackSceneryMetatiles: + .db $80, $83, $00 ;cloud left + .db $81, $84, $00 ;cloud middle + .db $82, $85, $00 ;cloud right + .db $02, $00, $00 ;bush left + .db $03, $00, $00 ;bush middle + .db $04, $00, $00 ;bush right + .db $00, $05, $06 ;mountain left + .db $07, $06, $0a ;mountain middle + .db $00, $08, $09 ;mountain right + .db $4d, $00, $00 ;fence + .db $0d, $0f, $4e ;tall tree + .db $0e, $4e, $4e ;short tree + +FSceneDataOffsets: + .db $00, $0d, $1a + +ForeSceneryData: + .db $86, $87, $87, $87, $87, $87, $87 ;in water + .db $87, $87, $87, $87, $69, $69 + + .db $00, $00, $00, $00, $00, $45, $47 ;wall + .db $47, $47, $47, $47, $00, $00 + + .db $00, $00, $00, $00, $00, $00, $00 ;over water + .db $00, $00, $00, $00, $86, $87 + +TerrainMetatiles: + .db $69, $54, $52, $62 + +TerrainRenderBits: + .db %00000000, %00000000 ;no ceiling or floor + .db %00000000, %00011000 ;no ceiling, floor 2 + .db %00000001, %00011000 ;ceiling 1, floor 2 + .db %00000111, %00011000 ;ceiling 3, floor 2 + .db %00001111, %00011000 ;ceiling 4, floor 2 + .db %11111111, %00011000 ;ceiling 8, floor 2 + .db %00000001, %00011111 ;ceiling 1, floor 5 + .db %00000111, %00011111 ;ceiling 3, floor 5 + .db %00001111, %00011111 ;ceiling 4, floor 5 + .db %10000001, %00011111 ;ceiling 1, floor 6 + .db %00000001, %00000000 ;ceiling 1, no floor + .db %10001111, %00011111 ;ceiling 4, floor 6 + .db %11110001, %00011111 ;ceiling 1, floor 9 + .db %11111001, %00011000 ;ceiling 1, middle 5, floor 2 + .db %11110001, %00011000 ;ceiling 1, middle 4, floor 2 + .db %11111111, %00011111 ;completely solid top to bottom + +AreaParserCore: + lda BackloadingFlag ;check to see if we are starting right of start + beq RenderSceneryTerrain ;if not, go ahead and render background, foreground and terrain + jsr ProcessAreaData ;otherwise skip ahead and load level data + +RenderSceneryTerrain: + ldx #$0c + lda #$00 +ClrMTBuf: sta MetatileBuffer,x ;clear out metatile buffer + dex + bpl ClrMTBuf + ldy BackgroundScenery ;do we need to render the background scenery? + beq RendFore ;if not, skip to check the foreground + lda CurrentPageLoc ;otherwise check for every third page +ThirdP: cmp #$03 + bmi RendBack ;if less than three we're there + sec + sbc #$03 ;if 3 or more, subtract 3 and + bpl ThirdP ;do an unconditional branch +RendBack: asl ;move results to higher nybble + asl + asl + asl + adc BSceneDataOffsets-1,y ;add to it offset loaded from here + adc CurrentColumnPos ;add to the result our current column position + tax + lda BackSceneryData,x ;load data from sum of offsets + beq RendFore ;if zero, no scenery for that part + pha + and #$0f ;save to stack and clear high nybble + sec + sbc #$01 ;subtract one (because low nybble is $01-$0c) + sta $00 ;save low nybble + asl ;multiply by three (shift to left and add result to old one) + adc $00 ;note that since d7 was nulled, the carry flag is always clear + tax ;save as offset for background scenery metatile data + pla ;get high nybble from stack, move low + lsr + lsr + lsr + lsr + tay ;use as second offset (used to determine height) + lda #$03 ;use previously saved memory location for counter + sta $00 +SceLoop1: lda BackSceneryMetatiles,x ;load metatile data from offset of (lsb - 1) * 3 + sta MetatileBuffer,y ;store into buffer from offset of (msb / 16) + inx + iny + cpy #$0b ;if at this location, leave loop + beq RendFore + dec $00 ;decrement until counter expires, barring exception + bne SceLoop1 +RendFore: ldx ForegroundScenery ;check for foreground data needed or not + beq RendTerr ;if not, skip this part + ldy FSceneDataOffsets-1,x ;load offset from location offset by header value, then + ldx #$00 ;reinit X +SceLoop2: lda ForeSceneryData,y ;load data until counter expires + beq NoFore ;do not store if zero found + sta MetatileBuffer,x +NoFore: iny + inx + cpx #$0d ;store up to end of metatile buffer + bne SceLoop2 +RendTerr: ldy AreaType ;check world type for water level + bne TerMTile ;if not water level, skip this part + lda WorldNumber ;check world number, if not world number eight + cmp #World8 ;then skip this part + bne TerMTile + lda #$62 ;if set as water level and world number eight, + jmp StoreMT ;use castle wall metatile as terrain type +TerMTile: lda TerrainMetatiles,y ;otherwise get appropriate metatile for area type + ldy CloudTypeOverride ;check for cloud type override + beq StoreMT ;if not set, keep value otherwise + lda #$88 ;use cloud block terrain +StoreMT: sta $07 ;store value here + ldx #$00 ;initialize X, use as metatile buffer offset + lda TerrainControl ;use yet another value from the header + asl ;multiply by 2 and use as yet another offset + tay +TerrLoop: lda TerrainRenderBits,y ;get one of the terrain rendering bit data + sta $00 + iny ;increment Y and use as offset next time around + sty $01 + lda CloudTypeOverride ;skip if value here is zero + beq NoCloud2 + cpx #$00 ;otherwise, check if we're doing the ceiling byte + beq NoCloud2 + lda $00 ;if not, mask out all but d3 + and #%00001000 + sta $00 +NoCloud2: ldy #$00 ;start at beginning of bitmasks +TerrBChk: lda Bitmasks,y ;load bitmask, then perform AND on contents of first byte + bit $00 + beq NextTBit ;if not set, skip this part (do not write terrain to buffer) + lda $07 + sta MetatileBuffer,x ;load terrain type metatile number and store into buffer here +NextTBit: inx ;continue until end of buffer + cpx #$0d + beq RendBBuf ;if we're at the end, break out of this loop + lda AreaType ;check world type for underground area + cmp #$02 + bne EndUChk ;if not underground, skip this part + cpx #$0b + bne EndUChk ;if we're at the bottom of the screen, override + lda #$54 ;old terrain type with ground level terrain type + sta $07 +EndUChk: iny ;increment bitmasks offset in Y + cpy #$08 + bne TerrBChk ;if not all bits checked, loop back + ldy $01 + bne TerrLoop ;unconditional branch, use Y to load next byte +RendBBuf: jsr ProcessAreaData ;do the area data loading routine now + lda BlockBufferColumnPos + jsr GetBlockBufferAddr ;get block buffer address from where we're at + ldx #$00 + ldy #$00 ;init index regs and start at beginning of smaller buffer +ChkMTLow: sty $00 + lda MetatileBuffer,x ;load stored metatile number + and #%11000000 ;mask out all but 2 MSB + asl + rol ;make %xx000000 into %000000xx + rol + tay ;use as offset in Y + lda MetatileBuffer,x ;reload original unmasked value here + cmp BlockBuffLowBounds,y ;check for certain values depending on bits set + bcs StrBlock ;if equal or greater, branch + lda #$00 ;if less, init value before storing +StrBlock: ldy $00 ;get offset for block buffer + sta ($06),y ;store value into block buffer + tya + clc ;add 16 (move down one row) to offset + adc #$10 + tay + inx ;increment column value + cpx #$0d + bcc ChkMTLow ;continue until we pass last row, then leave + rts + +;numbers lower than these with the same attribute bits +;will not be stored in the block buffer +BlockBuffLowBounds: + .db $10, $51, $88, $c0 + +;------------------------------------------------------------------------------------- +;$00 - used to store area object identifier +;$07 - used as adder to find proper area object code + +ProcessAreaData: + ldx #$02 ;start at the end of area object buffer +ProcADLoop: stx ObjectOffset + lda #$00 ;reset flag + sta BehindAreaParserFlag + ldy AreaDataOffset ;get offset of area data pointer + lda (AreaData),y ;get first byte of area object + cmp #$fd ;if end-of-area, skip all this crap + beq RdyDecode + lda AreaObjectLength,x ;check area object buffer flag + bpl RdyDecode ;if buffer not negative, branch, otherwise + iny + lda (AreaData),y ;get second byte of area object + asl ;check for page select bit (d7), branch if not set + bcc Chk1Row13 + lda AreaObjectPageSel ;check page select + bne Chk1Row13 + inc AreaObjectPageSel ;if not already set, set it now + inc AreaObjectPageLoc ;and increment page location +Chk1Row13: dey + lda (AreaData),y ;reread first byte of level object + and #$0f ;mask out high nybble + cmp #$0d ;row 13? + bne Chk1Row14 + iny ;if so, reread second byte of level object + lda (AreaData),y + dey ;decrement to get ready to read first byte + and #%01000000 ;check for d6 set (if not, object is page control) + bne CheckRear + lda AreaObjectPageSel ;if page select is set, do not reread + bne CheckRear + iny ;if d6 not set, reread second byte + lda (AreaData),y + and #%00011111 ;mask out all but 5 LSB and store in page control + sta AreaObjectPageLoc + inc AreaObjectPageSel ;increment page select + jmp NextAObj +Chk1Row14: cmp #$0e ;row 14? + bne CheckRear + lda BackloadingFlag ;check flag for saved page number and branch if set + bne RdyDecode ;to render the object (otherwise bg might not look right) +CheckRear: lda AreaObjectPageLoc ;check to see if current page of level object is + cmp CurrentPageLoc ;behind current page of renderer + bcc SetBehind ;if so branch +RdyDecode: jsr DecodeAreaData ;do sub and do not turn on flag + jmp ChkLength +SetBehind: inc BehindAreaParserFlag ;turn on flag if object is behind renderer +NextAObj: jsr IncAreaObjOffset ;increment buffer offset and move on +ChkLength: ldx ObjectOffset ;get buffer offset + lda AreaObjectLength,x ;check object length for anything stored here + bmi ProcLoopb ;if not, branch to handle loopback + dec AreaObjectLength,x ;otherwise decrement length or get rid of it +ProcLoopb: dex ;decrement buffer offset + bpl ProcADLoop ;and loopback unless exceeded buffer + lda BehindAreaParserFlag ;check for flag set if objects were behind renderer + bne ProcessAreaData ;branch if true to load more level data, otherwise + lda BackloadingFlag ;check for flag set if starting right of page $00 + bne ProcessAreaData ;branch if true to load more level data, otherwise leave +EndAParse: rts + +IncAreaObjOffset: + inc AreaDataOffset ;increment offset of level pointer + inc AreaDataOffset + lda #$00 ;reset page select + sta AreaObjectPageSel + rts + +DecodeAreaData: + lda AreaObjectLength,x ;check current buffer flag + bmi Chk1stB + ldy AreaObjOffsetBuffer,x ;if not, get offset from buffer +Chk1stB: ldx #$10 ;load offset of 16 for special row 15 + lda (AreaData),y ;get first byte of level object again + cmp #$fd + beq EndAParse ;if end of level, leave this routine + and #$0f ;otherwise, mask out low nybble + cmp #$0f ;row 15? + beq ChkRow14 ;if so, keep the offset of 16 + ldx #$08 ;otherwise load offset of 8 for special row 12 + cmp #$0c ;row 12? + beq ChkRow14 ;if so, keep the offset value of 8 + ldx #$00 ;otherwise nullify value by default +ChkRow14: stx $07 ;store whatever value we just loaded here + ldx ObjectOffset ;get object offset again + cmp #$0e ;row 14? + bne ChkRow13 + lda #$00 ;if so, load offset with $00 + sta $07 + lda #$2e ;and load A with another value + bne NormObj ;unconditional branch +ChkRow13: cmp #$0d ;row 13? + bne ChkSRows + lda #$22 ;if so, load offset with 34 + sta $07 + iny ;get next byte + lda (AreaData),y + and #%01000000 ;mask out all but d6 (page control obj bit) + beq LeavePar ;if d6 clear, branch to leave (we handled this earlier) + lda (AreaData),y ;otherwise, get byte again + and #%01111111 ;mask out d7 + cmp #$4b ;check for loop command in low nybble + bne Mask2MSB ;(plus d6 set for object other than page control) + inc LoopCommand ;if loop command, set loop command flag +Mask2MSB: and #%00111111 ;mask out d7 and d6 + jmp NormObj ;and jump +ChkSRows: cmp #$0c ;row 12-15? + bcs SpecObj + iny ;if not, get second byte of level object + lda (AreaData),y + and #%01110000 ;mask out all but d6-d4 + bne LrgObj ;if any bits set, branch to handle large object + lda #$16 + sta $07 ;otherwise set offset of 24 for small object + lda (AreaData),y ;reload second byte of level object + and #%00001111 ;mask out higher nybble and jump + jmp NormObj +LrgObj: sta $00 ;store value here (branch for large objects) + cmp #$70 ;check for vertical pipe object + bne NotWPipe + lda (AreaData),y ;if not, reload second byte + and #%00001000 ;mask out all but d3 (usage control bit) + beq NotWPipe ;if d3 clear, branch to get original value + lda #$00 ;otherwise, nullify value for warp pipe + sta $00 +NotWPipe: lda $00 ;get value and jump ahead + jmp MoveAOId +SpecObj: iny ;branch here for rows 12-15 + lda (AreaData),y + and #%01110000 ;get next byte and mask out all but d6-d4 +MoveAOId: lsr ;move d6-d4 to lower nybble + lsr + lsr + lsr +NormObj: sta $00 ;store value here (branch for small objects and rows 13 and 14) + lda AreaObjectLength,x ;is there something stored here already? + bpl RunAObj ;if so, branch to do its particular sub + lda AreaObjectPageLoc ;otherwise check to see if the object we've loaded is on the + cmp CurrentPageLoc ;same page as the renderer, and if so, branch + beq InitRear + ldy AreaDataOffset ;if not, get old offset of level pointer + lda (AreaData),y ;and reload first byte + and #%00001111 + cmp #$0e ;row 14? + bne LeavePar + lda BackloadingFlag ;if so, check backloading flag + bne StrAObj ;if set, branch to render object, else leave +LeavePar: rts +InitRear: lda BackloadingFlag ;check backloading flag to see if it's been initialized + beq BackColC ;branch to column-wise check + lda #$00 ;if not, initialize both backloading and + sta BackloadingFlag ;behind-renderer flags and leave + sta BehindAreaParserFlag + sta ObjectOffset +LoopCmdE: rts +BackColC: ldy AreaDataOffset ;get first byte again + lda (AreaData),y + and #%11110000 ;mask out low nybble and move high to low + lsr + lsr + lsr + lsr + cmp CurrentColumnPos ;is this where we're at? + bne LeavePar ;if not, branch to leave +StrAObj: lda AreaDataOffset ;if so, load area obj offset and store in buffer + sta AreaObjOffsetBuffer,x + jsr IncAreaObjOffset ;do sub to increment to next object data +RunAObj: lda $00 ;get stored value and add offset to it + clc ;then use the jump engine with current contents of A + adc $07 + jsr JumpEngine + +;large objects (rows $00-$0b or 00-11, d6-d4 set) + .dw VerticalPipe ;used by warp pipes + .dw AreaStyleObject + .dw RowOfBricks + .dw RowOfSolidBlocks + .dw RowOfCoins + .dw ColumnOfBricks + .dw ColumnOfSolidBlocks + .dw VerticalPipe ;used by decoration pipes + +;objects for special row $0c or 12 + .dw Hole_Empty + .dw PulleyRopeObject + .dw Bridge_High + .dw Bridge_Middle + .dw Bridge_Low + .dw Hole_Water + .dw QuestionBlockRow_High + .dw QuestionBlockRow_Low + +;objects for special row $0f or 15 + .dw EndlessRope + .dw BalancePlatRope + .dw CastleObject + .dw StaircaseObject + .dw ExitPipe + .dw FlagBalls_Residual + +;small objects (rows $00-$0b or 00-11, d6-d4 all clear) + .dw QuestionBlock ;power-up + .dw QuestionBlock ;coin + .dw QuestionBlock ;hidden, coin + .dw Hidden1UpBlock ;hidden, 1-up + .dw BrickWithItem ;brick, power-up + .dw BrickWithItem ;brick, vine + .dw BrickWithItem ;brick, star + .dw BrickWithCoins ;brick, coins + .dw BrickWithItem ;brick, 1-up + .dw WaterPipe + .dw EmptyBlock + .dw Jumpspring + +;objects for special row $0d or 13 (d6 set) + .dw IntroPipe + .dw FlagpoleObject + .dw AxeObj + .dw ChainObj + .dw CastleBridgeObj + .dw ScrollLockObject_Warp + .dw ScrollLockObject + .dw ScrollLockObject + .dw AreaFrenzy ;flying cheep-cheeps + .dw AreaFrenzy ;bullet bills or swimming cheep-cheeps + .dw AreaFrenzy ;stop frenzy + .dw LoopCmdE + +;object for special row $0e or 14 + .dw AlterAreaAttributes + +;------------------------------------------------------------------------------------- +;(these apply to all area object subroutines in this section unless otherwise stated) +;$00 - used to store offset used to find object code +;$07 - starts with adder from area parser, used to store row offset + +AlterAreaAttributes: + ldy AreaObjOffsetBuffer,x ;load offset for level object data saved in buffer + iny ;load second byte + lda (AreaData),y + pha ;save in stack for now + and #%01000000 + bne Alter2 ;branch if d6 is set + pla + pha ;pull and push offset to copy to A + and #%00001111 ;mask out high nybble and store as + sta TerrainControl ;new terrain height type bits + pla + and #%00110000 ;pull and mask out all but d5 and d4 + lsr ;move bits to lower nybble and store + lsr ;as new background scenery bits + lsr + lsr + sta BackgroundScenery ;then leave + rts +Alter2: pla + and #%00000111 ;mask out all but 3 LSB + cmp #$04 ;if four or greater, set color control bits + bcc SetFore ;and nullify foreground scenery bits + sta BackgroundColorCtrl + lda #$00 +SetFore: sta ForegroundScenery ;otherwise set new foreground scenery bits + rts + +;-------------------------------- + +ScrollLockObject_Warp: + ldx #$04 ;load value of 4 for game text routine as default + lda WorldNumber ;warp zone (4-3-2), then check world number + beq WarpNum + inx ;if world number > 1, increment for next warp zone (5) + ldy AreaType ;check area type + dey + bne WarpNum ;if ground area type, increment for last warp zone + inx ;(8-7-6) and move on +WarpNum: txa + sta WarpZoneControl ;store number here to be used by warp zone routine + jsr WriteGameText ;print text and warp zone numbers + lda #PiranhaPlant + jsr KillEnemies ;load identifier for piranha plants and do sub + +ScrollLockObject: + lda ScrollLock ;invert scroll lock to turn it on + eor #%00000001 + sta ScrollLock + rts + +;-------------------------------- +;$00 - used to store enemy identifier in KillEnemies + +KillEnemies: + sta $00 ;store identifier here + lda #$00 + ldx #$04 ;check for identifier in enemy object buffer +KillELoop: ldy Enemy_ID,x + cpy $00 ;if not found, branch + bne NoKillE + sta Enemy_Flag,x ;if found, deactivate enemy object flag +NoKillE: dex ;do this until all slots are checked + bpl KillELoop + rts + +;-------------------------------- + +FrenzyIDData: + .db FlyCheepCheepFrenzy, BBill_CCheep_Frenzy, Stop_Frenzy + +AreaFrenzy: ldx $00 ;use area object identifier bit as offset + lda FrenzyIDData-8,x ;note that it starts at 8, thus weird address here + ldy #$05 +FreCompLoop: dey ;check regular slots of enemy object buffer + bmi ExitAFrenzy ;if all slots checked and enemy object not found, branch to store + cmp Enemy_ID,y ;check for enemy object in buffer versus frenzy object + bne FreCompLoop + lda #$00 ;if enemy object already present, nullify queue and leave +ExitAFrenzy: sta EnemyFrenzyQueue ;store enemy into frenzy queue + rts + +;-------------------------------- +;$06 - used by MushroomLedge to store length + +AreaStyleObject: + lda AreaStyle ;load level object style and jump to the right sub + jsr JumpEngine + .dw TreeLedge ;also used for cloud type levels + .dw MushroomLedge + .dw BulletBillCannon + +TreeLedge: + jsr GetLrgObjAttrib ;get row and length of green ledge + lda AreaObjectLength,x ;check length counter for expiration + beq EndTreeL + bpl MidTreeL + tya + sta AreaObjectLength,x ;store lower nybble into buffer flag as length of ledge + lda CurrentPageLoc + ora CurrentColumnPos ;are we at the start of the level? + beq MidTreeL + lda #$16 ;render start of tree ledge + jmp NoUnder +MidTreeL: ldx $07 + lda #$17 ;render middle of tree ledge + sta MetatileBuffer,x ;note that this is also used if ledge position is + lda #$4c ;at the start of level for continuous effect + jmp AllUnder ;now render the part underneath +EndTreeL: lda #$18 ;render end of tree ledge + jmp NoUnder + +MushroomLedge: + jsr ChkLrgObjLength ;get shroom dimensions + sty $06 ;store length here for now + bcc EndMushL + lda AreaObjectLength,x ;divide length by 2 and store elsewhere + lsr + sta MushroomLedgeHalfLen,x + lda #$19 ;render start of mushroom + jmp NoUnder +EndMushL: lda #$1b ;if at the end, render end of mushroom + ldy AreaObjectLength,x + beq NoUnder + lda MushroomLedgeHalfLen,x ;get divided length and store where length + sta $06 ;was stored originally + ldx $07 + lda #$1a + sta MetatileBuffer,x ;render middle of mushroom + cpy $06 ;are we smack dab in the center? + bne MushLExit ;if not, branch to leave + inx + lda #$4f + sta MetatileBuffer,x ;render stem top of mushroom underneath the middle + lda #$50 +AllUnder: inx + ldy #$0f ;set $0f to render all way down + jmp RenderUnderPart ;now render the stem of mushroom +NoUnder: ldx $07 ;load row of ledge + ldy #$00 ;set 0 for no bottom on this part + jmp RenderUnderPart + +;-------------------------------- + +;tiles used by pulleys and rope object +PulleyRopeMetatiles: + .db $42, $41, $43 + +PulleyRopeObject: + jsr ChkLrgObjLength ;get length of pulley/rope object + ldy #$00 ;initialize metatile offset + bcs RenderPul ;if starting, render left pulley + iny + lda AreaObjectLength,x ;if not at the end, render rope + bne RenderPul + iny ;otherwise render right pulley +RenderPul: lda PulleyRopeMetatiles,y + sta MetatileBuffer ;render at the top of the screen +MushLExit: rts ;and leave + +;-------------------------------- +;$06 - used to store upper limit of rows for CastleObject + +CastleMetatiles: + .db $00, $45, $45, $45, $00 + .db $00, $48, $47, $46, $00 + .db $45, $49, $49, $49, $45 + .db $47, $47, $4a, $47, $47 + .db $47, $47, $4b, $47, $47 + .db $49, $49, $49, $49, $49 + .db $47, $4a, $47, $4a, $47 + .db $47, $4b, $47, $4b, $47 + .db $47, $47, $47, $47, $47 + .db $4a, $47, $4a, $47, $4a + .db $4b, $47, $4b, $47, $4b + +CastleObject: + jsr GetLrgObjAttrib ;save lower nybble as starting row + sty $07 ;if starting row is above $0a, game will crash!!! + ldy #$04 + jsr ChkLrgObjFixedLength ;load length of castle if not already loaded + txa + pha ;save obj buffer offset to stack + ldy AreaObjectLength,x ;use current length as offset for castle data + ldx $07 ;begin at starting row + lda #$0b + sta $06 ;load upper limit of number of rows to print +CRendLoop: lda CastleMetatiles,y ;load current byte using offset + sta MetatileBuffer,x + inx ;store in buffer and increment buffer offset + lda $06 + beq ChkCFloor ;have we reached upper limit yet? + iny ;if not, increment column-wise + iny ;to byte in next row + iny + iny + iny + dec $06 ;move closer to upper limit +ChkCFloor: cpx #$0b ;have we reached the row just before floor? + bne CRendLoop ;if not, go back and do another row + pla + tax ;get obj buffer offset from before + lda CurrentPageLoc + beq ExitCastle ;if we're at page 0, we do not need to do anything else + lda AreaObjectLength,x ;check length + cmp #$01 ;if length almost about to expire, put brick at floor + beq PlayerStop + ldy $07 ;check starting row for tall castle ($00) + bne NotTall + cmp #$03 ;if found, then check to see if we're at the second column + beq PlayerStop +NotTall: cmp #$02 ;if not tall castle, check to see if we're at the third column + bne ExitCastle ;if we aren't and the castle is tall, don't create flag yet + jsr GetAreaObjXPosition ;otherwise, obtain and save horizontal pixel coordinate + pha + jsr FindEmptyEnemySlot ;find an empty place on the enemy object buffer + pla + sta Enemy_X_Position,x ;then write horizontal coordinate for star flag + lda CurrentPageLoc + sta Enemy_PageLoc,x ;set page location for star flag + lda #$01 + sta Enemy_Y_HighPos,x ;set vertical high byte + sta Enemy_Flag,x ;set flag for buffer + lda #$90 + sta Enemy_Y_Position,x ;set vertical coordinate + lda #StarFlagObject ;set star flag value in buffer itself + sta Enemy_ID,x + rts +PlayerStop: ldy #$52 ;put brick at floor to stop player at end of level + sty MetatileBuffer+10 ;this is only done if we're on the second column +ExitCastle: rts + +;-------------------------------- + +WaterPipe: + jsr GetLrgObjAttrib ;get row and lower nybble + ldy AreaObjectLength,x ;get length (residual code, water pipe is 1 col thick) + ldx $07 ;get row + lda #$6b + sta MetatileBuffer,x ;draw something here and below it + lda #$6c + sta MetatileBuffer+1,x + rts + +;-------------------------------- +;$05 - used to store length of vertical shaft in RenderSidewaysPipe +;$06 - used to store leftover horizontal length in RenderSidewaysPipe +; and vertical length in VerticalPipe and GetPipeHeight + +IntroPipe: + ldy #$03 ;check if length set, if not set, set it + jsr ChkLrgObjFixedLength + ldy #$0a ;set fixed value and render the sideways part + jsr RenderSidewaysPipe + bcs NoBlankP ;if carry flag set, not time to draw vertical pipe part + ldx #$06 ;blank everything above the vertical pipe part +VPipeSectLoop: lda #$00 ;all the way to the top of the screen + sta MetatileBuffer,x ;because otherwise it will look like exit pipe + dex + bpl VPipeSectLoop + lda VerticalPipeData,y ;draw the end of the vertical pipe part + sta MetatileBuffer+7 +NoBlankP: rts + +SidePipeShaftData: + .db $15, $14 ;used to control whether or not vertical pipe shaft + .db $00, $00 ;is drawn, and if so, controls the metatile number +SidePipeTopPart: + .db $15, $1e ;top part of sideways part of pipe + .db $1d, $1c +SidePipeBottomPart: + .db $15, $21 ;bottom part of sideways part of pipe + .db $20, $1f + +ExitPipe: + ldy #$03 ;check if length set, if not set, set it + jsr ChkLrgObjFixedLength + jsr GetLrgObjAttrib ;get vertical length, then plow on through RenderSidewaysPipe + +RenderSidewaysPipe: + dey ;decrement twice to make room for shaft at bottom + dey ;and store here for now as vertical length + sty $05 + ldy AreaObjectLength,x ;get length left over and store here + sty $06 + ldx $05 ;get vertical length plus one, use as buffer offset + inx + lda SidePipeShaftData,y ;check for value $00 based on horizontal offset + cmp #$00 + beq DrawSidePart ;if found, do not draw the vertical pipe shaft + ldx #$00 + ldy $05 ;init buffer offset and get vertical length + jsr RenderUnderPart ;and render vertical shaft using tile number in A + clc ;clear carry flag to be used by IntroPipe +DrawSidePart: ldy $06 ;render side pipe part at the bottom + lda SidePipeTopPart,y + sta MetatileBuffer,x ;note that the pipe parts are stored + lda SidePipeBottomPart,y ;backwards horizontally + sta MetatileBuffer+1,x + rts + +VerticalPipeData: + .db $11, $10 ;used by pipes that lead somewhere + .db $15, $14 + .db $13, $12 ;used by decoration pipes + .db $15, $14 + +VerticalPipe: + jsr GetPipeHeight + lda $00 ;check to see if value was nullified earlier + beq WarpPipe ;(if d3, the usage control bit of second byte, was set) + iny + iny + iny + iny ;add four if usage control bit was not set +WarpPipe: tya ;save value in stack + pha + lda AreaNumber + ora WorldNumber ;if at world 1-1, do not add piranha plant ever + beq DrawPipe + ldy AreaObjectLength,x ;if on second column of pipe, branch + beq DrawPipe ;(because we only need to do this once) + jsr FindEmptyEnemySlot ;check for an empty moving data buffer space + bcs DrawPipe ;if not found, too many enemies, thus skip + jsr GetAreaObjXPosition ;get horizontal pixel coordinate + clc + adc #$08 ;add eight to put the piranha plant in the center + sta Enemy_X_Position,x ;store as enemy's horizontal coordinate + lda CurrentPageLoc ;add carry to current page number + adc #$00 + sta Enemy_PageLoc,x ;store as enemy's page coordinate + lda #$01 + sta Enemy_Y_HighPos,x + sta Enemy_Flag,x ;activate enemy flag + jsr GetAreaObjYPosition ;get piranha plant's vertical coordinate and store here + sta Enemy_Y_Position,x + lda #PiranhaPlant ;write piranha plant's value into buffer + sta Enemy_ID,x + jsr InitPiranhaPlant +DrawPipe: pla ;get value saved earlier and use as Y + tay + ldx $07 ;get buffer offset + lda VerticalPipeData,y ;draw the appropriate pipe with the Y we loaded earlier + sta MetatileBuffer,x ;render the top of the pipe + inx + lda VerticalPipeData+2,y ;render the rest of the pipe + ldy $06 ;subtract one from length and render the part underneath + dey + jmp RenderUnderPart + +GetPipeHeight: + ldy #$01 ;check for length loaded, if not, load + jsr ChkLrgObjFixedLength ;pipe length of 2 (horizontal) + jsr GetLrgObjAttrib + tya ;get saved lower nybble as height + and #$07 ;save only the three lower bits as + sta $06 ;vertical length, then load Y with + ldy AreaObjectLength,x ;length left over + rts + +FindEmptyEnemySlot: + ldx #$00 ;start at first enemy slot +EmptyChkLoop: clc ;clear carry flag by default + lda Enemy_Flag,x ;check enemy buffer for nonzero + beq ExitEmptyChk ;if zero, leave + inx + cpx #$05 ;if nonzero, check next value + bne EmptyChkLoop +ExitEmptyChk: rts ;if all values nonzero, carry flag is set + +;-------------------------------- + +Hole_Water: + jsr ChkLrgObjLength ;get low nybble and save as length + lda #$86 ;render waves + sta MetatileBuffer+10 + ldx #$0b + ldy #$01 ;now render the water underneath + lda #$87 + jmp RenderUnderPart + +;-------------------------------- + +QuestionBlockRow_High: + lda #$03 ;start on the fourth row + .db $2c ;BIT instruction opcode + +QuestionBlockRow_Low: + lda #$07 ;start on the eighth row + pha ;save whatever row to the stack for now + jsr ChkLrgObjLength ;get low nybble and save as length + pla + tax ;render question boxes with coins + lda #$c0 + sta MetatileBuffer,x + rts + +;-------------------------------- + +Bridge_High: + lda #$06 ;start on the seventh row from top of screen + .db $2c ;BIT instruction opcode + +Bridge_Middle: + lda #$07 ;start on the eighth row + .db $2c ;BIT instruction opcode + +Bridge_Low: + lda #$09 ;start on the tenth row + pha ;save whatever row to the stack for now + jsr ChkLrgObjLength ;get low nybble and save as length + pla + tax ;render bridge railing + lda #$0b + sta MetatileBuffer,x + inx + ldy #$00 ;now render the bridge itself + lda #$63 + jmp RenderUnderPart + +;-------------------------------- + +FlagBalls_Residual: + jsr GetLrgObjAttrib ;get low nybble from object byte + ldx #$02 ;render flag balls on third row from top + lda #$6d ;of screen downwards based on low nybble + jmp RenderUnderPart + +;-------------------------------- + +FlagpoleObject: + lda #$24 ;render flagpole ball on top + sta MetatileBuffer + ldx #$01 ;now render the flagpole shaft + ldy #$08 + lda #$25 + jsr RenderUnderPart + lda #$61 ;render solid block at the bottom + sta MetatileBuffer+10 + jsr GetAreaObjXPosition + sec ;get pixel coordinate of where the flagpole is, + sbc #$08 ;subtract eight pixels and use as horizontal + sta Enemy_X_Position+5 ;coordinate for the flag + lda CurrentPageLoc + sbc #$00 ;subtract borrow from page location and use as + sta Enemy_PageLoc+5 ;page location for the flag + lda #$30 + sta Enemy_Y_Position+5 ;set vertical coordinate for flag + lda #$b0 + sta FlagpoleFNum_Y_Pos ;set initial vertical coordinate for flagpole's floatey number + lda #FlagpoleFlagObject + sta Enemy_ID+5 ;set flag identifier, note that identifier and coordinates + inc Enemy_Flag+5 ;use last space in enemy object buffer + rts + +;-------------------------------- + +EndlessRope: + ldx #$00 ;render rope from the top to the bottom of screen + ldy #$0f + jmp DrawRope + +BalancePlatRope: + txa ;save object buffer offset for now + pha + ldx #$01 ;blank out all from second row to the bottom + ldy #$0f ;with blank used for balance platform rope + lda #$44 + jsr RenderUnderPart + pla ;get back object buffer offset + tax + jsr GetLrgObjAttrib ;get vertical length from lower nybble + ldx #$01 +DrawRope: lda #$40 ;render the actual rope + jmp RenderUnderPart + +;-------------------------------- + +CoinMetatileData: + .db $c3, $c2, $c2, $c2 + +RowOfCoins: + ldy AreaType ;get area type + lda CoinMetatileData,y ;load appropriate coin metatile + jmp GetRow + +;-------------------------------- + +C_ObjectRow: + .db $06, $07, $08 + +C_ObjectMetatile: + .db $c5, $0c, $89 + +CastleBridgeObj: + ldy #$0c ;load length of 13 columns + jsr ChkLrgObjFixedLength + jmp ChainObj + +AxeObj: + lda #$08 ;load bowser's palette into sprite portion of palette + sta VRAM_Buffer_AddrCtrl + +ChainObj: + ldy $00 ;get value loaded earlier from decoder + ldx C_ObjectRow-2,y ;get appropriate row and metatile for object + lda C_ObjectMetatile-2,y + jmp ColObj + +EmptyBlock: + jsr GetLrgObjAttrib ;get row location + ldx $07 + lda #$c4 +ColObj: ldy #$00 ;column length of 1 + jmp RenderUnderPart + +;-------------------------------- + +SolidBlockMetatiles: + .db $69, $61, $61, $62 + +BrickMetatiles: + .db $22, $51, $52, $52 + .db $88 ;used only by row of bricks object + +RowOfBricks: + ldy AreaType ;load area type obtained from area offset pointer + lda CloudTypeOverride ;check for cloud type override + beq DrawBricks + ldy #$04 ;if cloud type, override area type +DrawBricks: lda BrickMetatiles,y ;get appropriate metatile + jmp GetRow ;and go render it + +RowOfSolidBlocks: + ldy AreaType ;load area type obtained from area offset pointer + lda SolidBlockMetatiles,y ;get metatile +GetRow: pha ;store metatile here + jsr ChkLrgObjLength ;get row number, load length +DrawRow: ldx $07 + ldy #$00 ;set vertical height of 1 + pla + jmp RenderUnderPart ;render object + +ColumnOfBricks: + ldy AreaType ;load area type obtained from area offset + lda BrickMetatiles,y ;get metatile (no cloud override as for row) + jmp GetRow2 + +ColumnOfSolidBlocks: + ldy AreaType ;load area type obtained from area offset + lda SolidBlockMetatiles,y ;get metatile +GetRow2: pha ;save metatile to stack for now + jsr GetLrgObjAttrib ;get length and row + pla ;restore metatile + ldx $07 ;get starting row + jmp RenderUnderPart ;now render the column + +;-------------------------------- + +BulletBillCannon: + jsr GetLrgObjAttrib ;get row and length of bullet bill cannon + ldx $07 ;start at first row + lda #$64 ;render bullet bill cannon + sta MetatileBuffer,x + inx + dey ;done yet? + bmi SetupCannon + lda #$65 ;if not, render middle part + sta MetatileBuffer,x + inx + dey ;done yet? + bmi SetupCannon + lda #$66 ;if not, render bottom until length expires + jsr RenderUnderPart +SetupCannon: ldx Cannon_Offset ;get offset for data used by cannons and whirlpools + jsr GetAreaObjYPosition ;get proper vertical coordinate for cannon + sta Cannon_Y_Position,x ;and store it here + lda CurrentPageLoc + sta Cannon_PageLoc,x ;store page number for cannon here + jsr GetAreaObjXPosition ;get proper horizontal coordinate for cannon + sta Cannon_X_Position,x ;and store it here + inx + cpx #$06 ;increment and check offset + bcc StrCOffset ;if not yet reached sixth cannon, branch to save offset + ldx #$00 ;otherwise initialize it +StrCOffset: stx Cannon_Offset ;save new offset and leave + rts + +;-------------------------------- + +StaircaseHeightData: + .db $07, $07, $06, $05, $04, $03, $02, $01, $00 + +StaircaseRowData: + .db $03, $03, $04, $05, $06, $07, $08, $09, $0a + +StaircaseObject: + jsr ChkLrgObjLength ;check and load length + bcc NextStair ;if length already loaded, skip init part + lda #$09 ;start past the end for the bottom + sta StaircaseControl ;of the staircase +NextStair: dec StaircaseControl ;move onto next step (or first if starting) + ldy StaircaseControl + ldx StaircaseRowData,y ;get starting row and height to render + lda StaircaseHeightData,y + tay + lda #$61 ;now render solid block staircase + jmp RenderUnderPart + +;-------------------------------- + +Jumpspring: + jsr GetLrgObjAttrib + jsr FindEmptyEnemySlot ;find empty space in enemy object buffer + jsr GetAreaObjXPosition ;get horizontal coordinate for jumpspring + sta Enemy_X_Position,x ;and store + lda CurrentPageLoc ;store page location of jumpspring + sta Enemy_PageLoc,x + jsr GetAreaObjYPosition ;get vertical coordinate for jumpspring + sta Enemy_Y_Position,x ;and store + sta Jumpspring_FixedYPos,x ;store as permanent coordinate here + lda #JumpspringObject + sta Enemy_ID,x ;write jumpspring object to enemy object buffer + ldy #$01 + sty Enemy_Y_HighPos,x ;store vertical high byte + inc Enemy_Flag,x ;set flag for enemy object buffer + ldx $07 + lda #$67 ;draw metatiles in two rows where jumpspring is + sta MetatileBuffer,x + lda #$68 + sta MetatileBuffer+1,x + rts + +;-------------------------------- +;$07 - used to save ID of brick object + +Hidden1UpBlock: + lda Hidden1UpFlag ;if flag not set, do not render object + beq ExitDecBlock + lda #$00 ;if set, init for the next one + sta Hidden1UpFlag + jmp BrickWithItem ;jump to code shared with unbreakable bricks + +QuestionBlock: + jsr GetAreaObjectID ;get value from level decoder routine + jmp DrawQBlk ;go to render it + +BrickWithCoins: + lda #$00 ;initialize multi-coin timer flag + sta BrickCoinTimerFlag + +BrickWithItem: + jsr GetAreaObjectID ;save area object ID + sty $07 + lda #$00 ;load default adder for bricks with lines + ldy AreaType ;check level type for ground level + dey + beq BWithL ;if ground type, do not start with 5 + lda #$05 ;otherwise use adder for bricks without lines +BWithL: clc ;add object ID to adder + adc $07 + tay ;use as offset for metatile +DrawQBlk: lda BrickQBlockMetatiles,y ;get appropriate metatile for brick (question block + pha ;if branched to here from question block routine) + jsr GetLrgObjAttrib ;get row from location byte + jmp DrawRow ;now render the object + +GetAreaObjectID: + lda $00 ;get value saved from area parser routine + sec + sbc #$00 ;possibly residual code + tay ;save to Y +ExitDecBlock: rts + +;-------------------------------- + +HoleMetatiles: + .db $87, $00, $00, $00 + +Hole_Empty: + jsr ChkLrgObjLength ;get lower nybble and save as length + bcc NoWhirlP ;skip this part if length already loaded + lda AreaType ;check for water type level + bne NoWhirlP ;if not water type, skip this part + ldx Whirlpool_Offset ;get offset for data used by cannons and whirlpools + jsr GetAreaObjXPosition ;get proper vertical coordinate of where we're at + sec + sbc #$10 ;subtract 16 pixels + sta Whirlpool_LeftExtent,x ;store as left extent of whirlpool + lda CurrentPageLoc ;get page location of where we're at + sbc #$00 ;subtract borrow + sta Whirlpool_PageLoc,x ;save as page location of whirlpool + iny + iny ;increment length by 2 + tya + asl ;multiply by 16 to get size of whirlpool + asl ;note that whirlpool will always be + asl ;two blocks bigger than actual size of hole + asl ;and extend one block beyond each edge + sta Whirlpool_Length,x ;save size of whirlpool here + inx + cpx #$05 ;increment and check offset + bcc StrWOffset ;if not yet reached fifth whirlpool, branch to save offset + ldx #$00 ;otherwise initialize it +StrWOffset: stx Whirlpool_Offset ;save new offset here +NoWhirlP: ldx AreaType ;get appropriate metatile, then + lda HoleMetatiles,x ;render the hole proper + ldx #$08 + ldy #$0f ;start at ninth row and go to bottom, run RenderUnderPart + +;-------------------------------- + +RenderUnderPart: + sty AreaObjectHeight ;store vertical length to render + ldy MetatileBuffer,x ;check current spot to see if there's something + beq DrawThisRow ;we need to keep, if nothing, go ahead + cpy #$17 + beq WaitOneRow ;if middle part (tree ledge), wait until next row + cpy #$1a + beq WaitOneRow ;if middle part (mushroom ledge), wait until next row + cpy #$c0 + beq DrawThisRow ;if question block w/ coin, overwrite + cpy #$c0 + bcs WaitOneRow ;if any other metatile with palette 3, wait until next row + cpy #$54 + bne DrawThisRow ;if cracked rock terrain, overwrite + cmp #$50 + beq WaitOneRow ;if stem top of mushroom, wait until next row +DrawThisRow: sta MetatileBuffer,x ;render contents of A from routine that called this +WaitOneRow: inx + cpx #$0d ;stop rendering if we're at the bottom of the screen + bcs ExitUPartR + ldy AreaObjectHeight ;decrement, and stop rendering if there is no more length + dey + bpl RenderUnderPart +ExitUPartR: rts + +;-------------------------------- + +ChkLrgObjLength: + jsr GetLrgObjAttrib ;get row location and size (length if branched to from here) + +ChkLrgObjFixedLength: + lda AreaObjectLength,x ;check for set length counter + clc ;clear carry flag for not just starting + bpl LenSet ;if counter not set, load it, otherwise leave alone + tya ;save length into length counter + sta AreaObjectLength,x + sec ;set carry flag if just starting +LenSet: rts + +GetLrgObjAttrib: + ldy AreaObjOffsetBuffer,x ;get offset saved from area obj decoding routine + lda (AreaData),y ;get first byte of level object + and #%00001111 + sta $07 ;save row location + iny + lda (AreaData),y ;get next byte, save lower nybble (length or height) + and #%00001111 ;as Y, then leave + tay + rts + +;-------------------------------- + +GetAreaObjXPosition: + lda CurrentColumnPos ;multiply current offset where we're at by 16 + asl ;to obtain horizontal pixel coordinate + asl + asl + asl + rts + +;-------------------------------- + +GetAreaObjYPosition: + lda $07 ;multiply value by 16 + asl + asl ;this will give us the proper vertical pixel coordinate + asl + asl + clc + adc #32 ;add 32 pixels for the status bar + rts + +;------------------------------------------------------------------------------------- +;$06-$07 - used to store block buffer address used as indirect + +BlockBufferAddr: + .db Block_Buffer_1, >Block_Buffer_2 + +GetBlockBufferAddr: + pha ;take value of A, save + lsr ;move high nybble to low + lsr + lsr + lsr + tay ;use nybble as pointer to high byte + lda BlockBufferAddr+2,y ;of indirect here + sta $07 + pla + and #%00001111 ;pull from stack, mask out high nybble + clc + adc BlockBufferAddr,y ;add to low byte + sta $06 ;store here and leave + rts + +;------------------------------------------------------------------------------------- + +;unused space + .db $ff, $ff + +;------------------------------------------------------------------------------------- + +AreaDataOfsLoopback: + .db $12, $36, $0e, $0e, $0e, $32, $32, $32, $0a, $26, $40 + +;------------------------------------------------------------------------------------- + +LoadAreaPointer: + jsr FindAreaPointer ;find it and store it here + sta AreaPointer +GetAreaType: and #%01100000 ;mask out all but d6 and d5 + asl + rol + rol + rol ;make %0xx00000 into %000000xx + sta AreaType ;save 2 MSB as area type + rts + +FindAreaPointer: + ldy WorldNumber ;load offset from world variable + lda WorldAddrOffsets,y + clc ;add area number used to find data + adc AreaNumber + tay + lda AreaAddrOffsets,y ;from there we have our area pointer + rts + + +GetAreaDataAddrs: + lda AreaPointer ;use 2 MSB for Y + jsr GetAreaType + tay + lda AreaPointer ;mask out all but 5 LSB + and #%00011111 + sta AreaAddrsLOffset ;save as low offset + lda EnemyAddrHOffsets,y ;load base value with 2 altered MSB, + clc ;then add base value to 5 LSB, result + adc AreaAddrsLOffset ;becomes offset for level data + tay + lda EnemyDataAddrLow,y ;use offset to load pointer + sta EnemyDataLow + lda EnemyDataAddrHigh,y + sta EnemyDataHigh + ldy AreaType ;use area type as offset + lda AreaDataHOffsets,y ;do the same thing but with different base value + clc + adc AreaAddrsLOffset + tay + lda AreaDataAddrLow,y ;use this offset to load another pointer + sta AreaDataLow + lda AreaDataAddrHigh,y + sta AreaDataHigh + ldy #$00 ;load first byte of header + lda (AreaData),y + pha ;save it to the stack for now + and #%00000111 ;save 3 LSB for foreground scenery or bg color control + cmp #$04 + bcc StoreFore + sta BackgroundColorCtrl ;if 4 or greater, save value here as bg color control + lda #$00 +StoreFore: sta ForegroundScenery ;if less, save value here as foreground scenery + pla ;pull byte from stack and push it back + pha + and #%00111000 ;save player entrance control bits + lsr ;shift bits over to LSBs + lsr + lsr + sta PlayerEntranceCtrl ;save value here as player entrance control + pla ;pull byte again but do not push it back + and #%11000000 ;save 2 MSB for game timer setting + clc + rol ;rotate bits over to LSBs + rol + rol + sta GameTimerSetting ;save value here as game timer setting + iny + lda (AreaData),y ;load second byte of header + pha ;save to stack + and #%00001111 ;mask out all but lower nybble + sta TerrainControl + pla ;pull and push byte to copy it to A + pha + and #%00110000 ;save 2 MSB for background scenery type + lsr + lsr ;shift bits to LSBs + lsr + lsr + sta BackgroundScenery ;save as background scenery + pla + and #%11000000 + clc + rol ;rotate bits over to LSBs + rol + rol + cmp #%00000011 ;if set to 3, store here + bne StoreStyle ;and nullify other value + sta CloudTypeOverride ;otherwise store value in other place + lda #$00 +StoreStyle: sta AreaStyle + lda AreaDataLow ;increment area data address by 2 bytes + clc + adc #$02 + sta AreaDataLow + lda AreaDataHigh + adc #$00 + sta AreaDataHigh + rts + +;------------------------------------------------------------------------------------- +;GAME LEVELS DATA + +WorldAddrOffsets: + .db World1Areas-AreaAddrOffsets, World2Areas-AreaAddrOffsets + .db World3Areas-AreaAddrOffsets, World4Areas-AreaAddrOffsets + .db World5Areas-AreaAddrOffsets, World6Areas-AreaAddrOffsets + .db World7Areas-AreaAddrOffsets, World8Areas-AreaAddrOffsets + +AreaAddrOffsets: +World1Areas: .db $25, $29, $c0, $26, $60 +World2Areas: .db $28, $29, $01, $27, $62 +World3Areas: .db $24, $35, $20, $63 +World4Areas: .db $22, $29, $41, $2c, $61 +World5Areas: .db $2a, $31, $26, $62 +World6Areas: .db $2e, $23, $2d, $60 +World7Areas: .db $33, $29, $01, $27, $64 +World8Areas: .db $30, $32, $21, $65 + +;bonus area data offsets, included here for comparison purposes +;underground bonus area - c2 +;cloud area 1 (day) - 2b +;cloud area 2 (night) - 34 +;water area (5-2/6-2) - 00 +;water area (8-4) - 02 +;warp zone area (4-2) - 2f + +EnemyAddrHOffsets: + .db $1f, $06, $1c, $00 + +EnemyDataAddrLow: + .db E_CastleArea1, >E_CastleArea2, >E_CastleArea3, >E_CastleArea4, >E_CastleArea5, >E_CastleArea6 + .db >E_GroundArea1, >E_GroundArea2, >E_GroundArea3, >E_GroundArea4, >E_GroundArea5, >E_GroundArea6 + .db >E_GroundArea7, >E_GroundArea8, >E_GroundArea9, >E_GroundArea10, >E_GroundArea11, >E_GroundArea12 + .db >E_GroundArea13, >E_GroundArea14, >E_GroundArea15, >E_GroundArea16, >E_GroundArea17, >E_GroundArea18 + .db >E_GroundArea19, >E_GroundArea20, >E_GroundArea21, >E_GroundArea22, >E_UndergroundArea1 + .db >E_UndergroundArea2, >E_UndergroundArea3, >E_WaterArea1, >E_WaterArea2, >E_WaterArea3 + +AreaDataHOffsets: + .db $00, $03, $19, $1c + +AreaDataAddrLow: + .db L_WaterArea1, >L_WaterArea2, >L_WaterArea3, >L_GroundArea1, >L_GroundArea2, >L_GroundArea3 + .db >L_GroundArea4, >L_GroundArea5, >L_GroundArea6, >L_GroundArea7, >L_GroundArea8, >L_GroundArea9 + .db >L_GroundArea10, >L_GroundArea11, >L_GroundArea12, >L_GroundArea13, >L_GroundArea14, >L_GroundArea15 + .db >L_GroundArea16, >L_GroundArea17, >L_GroundArea18, >L_GroundArea19, >L_GroundArea20, >L_GroundArea21 + .db >L_GroundArea22, >L_UndergroundArea1, >L_UndergroundArea2, >L_UndergroundArea3, >L_CastleArea1 + .db >L_CastleArea2, >L_CastleArea3, >L_CastleArea4, >L_CastleArea5, >L_CastleArea6 + +;ENEMY OBJECT DATA + +;level 1-4/6-4 +E_CastleArea1: + .db $76, $dd, $bb, $4c, $ea, $1d, $1b, $cc, $56, $5d + .db $16, $9d, $c6, $1d, $36, $9d, $c9, $1d, $04, $db + .db $49, $1d, $84, $1b, $c9, $5d, $88, $95, $0f, $08 + .db $30, $4c, $78, $2d, $a6, $28, $90, $b5 + .db $ff + +;level 4-4 +E_CastleArea2: + .db $0f, $03, $56, $1b, $c9, $1b, $0f, $07, $36, $1b + .db $aa, $1b, $48, $95, $0f, $0a, $2a, $1b, $5b, $0c + .db $78, $2d, $90, $b5 + .db $ff + +;level 2-4/5-4 +E_CastleArea3: + .db $0b, $8c, $4b, $4c, $77, $5f, $eb, $0c, $bd, $db + .db $19, $9d, $75, $1d, $7d, $5b, $d9, $1d, $3d, $dd + .db $99, $1d, $26, $9d, $5a, $2b, $8a, $2c, $ca, $1b + .db $20, $95, $7b, $5c, $db, $4c, $1b, $cc, $3b, $cc + .db $78, $2d, $a6, $28, $90, $b5 + .db $ff + +;level 3-4 +E_CastleArea4: + .db $0b, $8c, $3b, $1d, $8b, $1d, $ab, $0c, $db, $1d + .db $0f, $03, $65, $1d, $6b, $1b, $05, $9d, $0b, $1b + .db $05, $9b, $0b, $1d, $8b, $0c, $1b, $8c, $70, $15 + .db $7b, $0c, $db, $0c, $0f, $08, $78, $2d, $a6, $28 + .db $90, $b5 + .db $ff + +;level 7-4 +E_CastleArea5: + .db $27, $a9, $4b, $0c, $68, $29, $0f, $06, $77, $1b + .db $0f, $0b, $60, $15, $4b, $8c, $78, $2d, $90, $b5 + .db $ff + +;level 8-4 +E_CastleArea6: + .db $0f, $03, $8e, $65, $e1, $bb, $38, $6d, $a8, $3e, $e5, $e7 + .db $0f, $08, $0b, $02, $2b, $02, $5e, $65, $e1, $bb, $0e + .db $db, $0e, $bb, $8e, $db, $0e, $fe, $65, $ec, $0f, $0d + .db $4e, $65, $e1, $0f, $0e, $4e, $02, $e0, $0f, $10, $fe, $e5, $e1 + .db $1b, $85, $7b, $0c, $5b, $95, $78, $2d, $90, $b5 + .db $ff + +;level 3-3 +E_GroundArea1: + .db $a5, $86, $e4, $28, $18, $a8, $45, $83, $69, $03 + .db $c6, $29, $9b, $83, $16, $a4, $88, $24, $e9, $28 + .db $05, $a8, $7b, $28, $24, $8f, $c8, $03, $e8, $03 + .db $46, $a8, $85, $24, $c8, $24 + .db $ff + +;level 8-3 +E_GroundArea2: + .db $eb, $8e, $0f, $03, $fb, $05, $17, $85, $db, $8e + .db $0f, $07, $57, $05, $7b, $05, $9b, $80, $2b, $85 + .db $fb, $05, $0f, $0b, $1b, $05, $9b, $05 + .db $ff + +;level 4-1 +E_GroundArea3: + .db $2e, $c2, $66, $e2, $11, $0f, $07, $02, $11, $0f, $0c + .db $12, $11 + .db $ff + +;level 6-2 +E_GroundArea4: + .db $0e, $c2, $a8, $ab, $00, $bb, $8e, $6b, $82, $de, $00, $a0 + .db $33, $86, $43, $06, $3e, $b4, $a0, $cb, $02, $0f, $07 + .db $7e, $42, $a6, $83, $02, $0f, $0a, $3b, $02, $cb, $37 + .db $0f, $0c, $e3, $0e + .db $ff + +;level 3-1 +E_GroundArea5: + .db $9b, $8e, $ca, $0e, $ee, $42, $44, $5b, $86, $80, $b8 + .db $1b, $80, $50, $ba, $10, $b7, $5b, $00, $17, $85 + .db $4b, $05, $fe, $34, $40, $b7, $86, $c6, $06, $5b, $80 + .db $83, $00, $d0, $38, $5b, $8e, $8a, $0e, $a6, $00 + .db $bb, $0e, $c5, $80, $f3, $00 + .db $ff + +;level 1-1 +E_GroundArea6: + .db $1e, $c2, $00, $6b, $06, $8b, $86, $63, $b7, $0f, $05 + .db $03, $06, $23, $06, $4b, $b7, $bb, $00, $5b, $b7 + .db $fb, $37, $3b, $b7, $0f, $0b, $1b, $37 + .db $ff + +;level 1-3/5-3 +E_GroundArea7: + .db $2b, $d7, $e3, $03, $c2, $86, $e2, $06, $76, $a5 + .db $a3, $8f, $03, $86, $2b, $57, $68, $28, $e9, $28 + .db $e5, $83, $24, $8f, $36, $a8, $5b, $03 + .db $ff + +;level 2-3/7-3 +E_GroundArea8: + .db $0f, $02, $78, $40, $48, $ce, $f8, $c3, $f8, $c3 + .db $0f, $07, $7b, $43, $c6, $d0, $0f, $8a, $c8, $50 + .db $ff + +;level 2-1 +E_GroundArea9: + .db $85, $86, $0b, $80, $1b, $00, $db, $37, $77, $80 + .db $eb, $37, $fe, $2b, $20, $2b, $80, $7b, $38, $ab, $b8 + .db $77, $86, $fe, $42, $20, $49, $86, $8b, $06, $9b, $80 + .db $7b, $8e, $5b, $b7, $9b, $0e, $bb, $0e, $9b, $80 +;pipe intro area +E_GroundArea10: + .db $ff + +;level 5-1 +E_GroundArea11: + .db $0b, $80, $60, $38, $10, $b8, $c0, $3b, $db, $8e + .db $40, $b8, $f0, $38, $7b, $8e, $a0, $b8, $c0, $b8 + .db $fb, $00, $a0, $b8, $30, $bb, $ee, $42, $88, $0f, $0b + .db $2b, $0e, $67, $0e + .db $ff + +;cloud level used in levels 2-1 and 5-2 +E_GroundArea12: + .db $0a, $aa, $0e, $28, $2a, $0e, $31, $88 + .db $ff + +;level 4-3 +E_GroundArea13: + .db $c7, $83, $d7, $03, $42, $8f, $7a, $03, $05, $a4 + .db $78, $24, $a6, $25, $e4, $25, $4b, $83, $e3, $03 + .db $05, $a4, $89, $24, $b5, $24, $09, $a4, $65, $24 + .db $c9, $24, $0f, $08, $85, $25 + .db $ff + +;level 6-3 +E_GroundArea14: + .db $cd, $a5, $b5, $a8, $07, $a8, $76, $28, $cc, $25 + .db $65, $a4, $a9, $24, $e5, $24, $19, $a4, $0f, $07 + .db $95, $28, $e6, $24, $19, $a4, $d7, $29, $16, $a9 + .db $58, $29, $97, $29 + .db $ff + +;level 6-1 +E_GroundArea15: + .db $0f, $02, $02, $11, $0f, $07, $02, $11 + .db $ff + +;warp zone area used in level 4-2 +E_GroundArea16: + .db $ff + +;level 8-1 +E_GroundArea17: + .db $2b, $82, $ab, $38, $de, $42, $e2, $1b, $b8, $eb + .db $3b, $db, $80, $8b, $b8, $1b, $82, $fb, $b8, $7b + .db $80, $fb, $3c, $5b, $bc, $7b, $b8, $1b, $8e, $cb + .db $0e, $1b, $8e, $0f, $0d, $2b, $3b, $bb, $b8, $eb, $82 + .db $4b, $b8, $bb, $38, $3b, $b7, $bb, $02, $0f, $13 + .db $1b, $00, $cb, $80, $6b, $bc + .db $ff + +;level 5-2 +E_GroundArea18: + .db $7b, $80, $ae, $00, $80, $8b, $8e, $e8, $05, $f9, $86 + .db $17, $86, $16, $85, $4e, $2b, $80, $ab, $8e, $87, $85 + .db $c3, $05, $8b, $82, $9b, $02, $ab, $02, $bb, $86 + .db $cb, $06, $d3, $03, $3b, $8e, $6b, $0e, $a7, $8e + .db $ff + +;level 8-2 +E_GroundArea19: + .db $29, $8e, $52, $11, $83, $0e, $0f, $03, $9b, $0e + .db $2b, $8e, $5b, $0e, $cb, $8e, $fb, $0e, $fb, $82 + .db $9b, $82, $bb, $02, $fe, $42, $e8, $bb, $8e, $0f, $0a + .db $ab, $0e, $cb, $0e, $f9, $0e, $88, $86, $a6, $06 + .db $db, $02, $b6, $8e + .db $ff + +;level 7-1 +E_GroundArea20: + .db $ab, $ce, $de, $42, $c0, $cb, $ce, $5b, $8e, $1b, $ce + .db $4b, $85, $67, $45, $0f, $07, $2b, $00, $7b, $85 + .db $97, $05, $0f, $0a, $92, $02 + .db $ff + +;cloud level used in levels 3-1 and 6-2 +E_GroundArea21: + .db $0a, $aa, $0e, $24, $4a, $1e, $23, $aa + .db $ff + +;level 3-2 +E_GroundArea22: + .db $1b, $80, $bb, $38, $4b, $bc, $eb, $3b, $0f, $04 + .db $2b, $00, $ab, $38, $eb, $00, $cb, $8e, $fb, $80 + .db $ab, $b8, $6b, $80, $fb, $3c, $9b, $bb, $5b, $bc + .db $fb, $00, $6b, $b8, $fb, $38 + .db $ff + +;level 1-2 +E_UndergroundArea1: + .db $0b, $86, $1a, $06, $db, $06, $de, $c2, $02, $f0, $3b + .db $bb, $80, $eb, $06, $0b, $86, $93, $06, $f0, $39 + .db $0f, $06, $60, $b8, $1b, $86, $a0, $b9, $b7, $27 + .db $bd, $27, $2b, $83, $a1, $26, $a9, $26, $ee, $25, $0b + .db $27, $b4 + .db $ff + +;level 4-2 +E_UndergroundArea2: + .db $0f, $02, $1e, $2f, $60, $e0, $3a, $a5, $a7, $db, $80 + .db $3b, $82, $8b, $02, $fe, $42, $68, $70, $bb, $25, $a7 + .db $2c, $27, $b2, $26, $b9, $26, $9b, $80, $a8, $82 + .db $b5, $27, $bc, $27, $b0, $bb, $3b, $82, $87, $34 + .db $ee, $25, $6b + .db $ff + +;underground bonus rooms area used in many levels +E_UndergroundArea3: + .db $1e, $a5, $0a, $2e, $28, $27, $2e, $33, $c7, $0f, $03, $1e, $40, $07 + .db $2e, $30, $e7, $0f, $05, $1e, $24, $44, $0f, $07, $1e, $22, $6a + .db $2e, $23, $ab, $0f, $09, $1e, $41, $68, $1e, $2a, $8a, $2e, $23, $a2 + .db $2e, $32, $ea + .db $ff + +;water area used in levels 5-2 and 6-2 +E_WaterArea1: + .db $3b, $87, $66, $27, $cc, $27, $ee, $31, $87, $ee, $23, $a7 + .db $3b, $87, $db, $07 + .db $ff + +;level 2-2/7-2 +E_WaterArea2: + .db $0f, $01, $2e, $25, $2b, $2e, $25, $4b, $4e, $25, $cb, $6b, $07 + .db $97, $47, $e9, $87, $47, $c7, $7a, $07, $d6, $c7 + .db $78, $07, $38, $87, $ab, $47, $e3, $07, $9b, $87 + .db $0f, $09, $68, $47, $db, $c7, $3b, $c7 + .db $ff + +;water area used in level 8-4 +E_WaterArea3: + .db $47, $9b, $cb, $07, $fa, $1d, $86, $9b, $3a, $87 + .db $56, $07, $88, $1b, $07, $9d, $2e, $65, $f0 + .db $ff + +;AREA OBJECT DATA + +;level 1-4/6-4 +L_CastleArea1: + .db $9b, $07 + .db $05, $32, $06, $33, $07, $34, $ce, $03, $dc, $51 + .db $ee, $07, $73, $e0, $74, $0a, $7e, $06, $9e, $0a + .db $ce, $06, $e4, $00, $e8, $0a, $fe, $0a, $2e, $89 + .db $4e, $0b, $54, $0a, $14, $8a, $c4, $0a, $34, $8a + .db $7e, $06, $c7, $0a, $01, $e0, $02, $0a, $47, $0a + .db $81, $60, $82, $0a, $c7, $0a, $0e, $87, $7e, $02 + .db $a7, $02, $b3, $02, $d7, $02, $e3, $02, $07, $82 + .db $13, $02, $3e, $06, $7e, $02, $ae, $07, $fe, $0a + .db $0d, $c4, $cd, $43, $ce, $09, $de, $0b, $dd, $42 + .db $fe, $02, $5d, $c7 + .db $fd + +;level 4-4 +L_CastleArea2: + .db $5b, $07 + .db $05, $32, $06, $33, $07, $34, $5e, $0a, $68, $64 + .db $98, $64, $a8, $64, $ce, $06, $fe, $02, $0d, $01 + .db $1e, $0e, $7e, $02, $94, $63, $b4, $63, $d4, $63 + .db $f4, $63, $14, $e3, $2e, $0e, $5e, $02, $64, $35 + .db $88, $72, $be, $0e, $0d, $04, $ae, $02, $ce, $08 + .db $cd, $4b, $fe, $02, $0d, $05, $68, $31, $7e, $0a + .db $96, $31, $a9, $63, $a8, $33, $d5, $30, $ee, $02 + .db $e6, $62, $f4, $61, $04, $b1, $08, $3f, $44, $33 + .db $94, $63, $a4, $31, $e4, $31, $04, $bf, $08, $3f + .db $04, $bf, $08, $3f, $cd, $4b, $03, $e4, $0e, $03 + .db $2e, $01, $7e, $06, $be, $02, $de, $06, $fe, $0a + .db $0d, $c4, $cd, $43, $ce, $09, $de, $0b, $dd, $42 + .db $fe, $02, $5d, $c7 + .db $fd + +;level 2-4/5-4 +L_CastleArea3: + .db $9b, $07 + .db $05, $32, $06, $33, $07, $34, $fe, $00, $27, $b1 + .db $65, $32, $75, $0a, $71, $00, $b7, $31, $08, $e4 + .db $18, $64, $1e, $04, $57, $3b, $bb, $0a, $17, $8a + .db $27, $3a, $73, $0a, $7b, $0a, $d7, $0a, $e7, $3a + .db $3b, $8a, $97, $0a, $fe, $08, $24, $8a, $2e, $00 + .db $3e, $40, $38, $64, $6f, $00, $9f, $00, $be, $43 + .db $c8, $0a, $c9, $63, $ce, $07, $fe, $07, $2e, $81 + .db $66, $42, $6a, $42, $79, $0a, $be, $00, $c8, $64 + .db $f8, $64, $08, $e4, $2e, $07, $7e, $03, $9e, $07 + .db $be, $03, $de, $07, $fe, $0a, $03, $a5, $0d, $44 + .db $cd, $43, $ce, $09, $dd, $42, $de, $0b, $fe, $02 + .db $5d, $c7 + .db $fd + +;level 3-4 +L_CastleArea4: + .db $9b, $07 + .db $05, $32, $06, $33, $07, $34, $fe, $06, $0c, $81 + .db $39, $0a, $5c, $01, $89, $0a, $ac, $01, $d9, $0a + .db $fc, $01, $2e, $83, $a7, $01, $b7, $00, $c7, $01 + .db $de, $0a, $fe, $02, $4e, $83, $5a, $32, $63, $0a + .db $69, $0a, $7e, $02, $ee, $03, $fa, $32, $03, $8a + .db $09, $0a, $1e, $02, $ee, $03, $fa, $32, $03, $8a + .db $09, $0a, $14, $42, $1e, $02, $7e, $0a, $9e, $07 + .db $fe, $0a, $2e, $86, $5e, $0a, $8e, $06, $be, $0a + .db $ee, $07, $3e, $83, $5e, $07, $fe, $0a, $0d, $c4 + .db $41, $52, $51, $52, $cd, $43, $ce, $09, $de, $0b + .db $dd, $42, $fe, $02, $5d, $c7 + .db $fd + +;level 7-4 +L_CastleArea5: + .db $5b, $07 + .db $05, $32, $06, $33, $07, $34, $fe, $0a, $ae, $86 + .db $be, $07, $fe, $02, $0d, $02, $27, $32, $46, $61 + .db $55, $62, $5e, $0e, $1e, $82, $68, $3c, $74, $3a + .db $7d, $4b, $5e, $8e, $7d, $4b, $7e, $82, $84, $62 + .db $94, $61, $a4, $31, $bd, $4b, $ce, $06, $fe, $02 + .db $0d, $06, $34, $31, $3e, $0a, $64, $32, $75, $0a + .db $7b, $61, $a4, $33, $ae, $02, $de, $0e, $3e, $82 + .db $64, $32, $78, $32, $b4, $36, $c8, $36, $dd, $4b + .db $44, $b2, $58, $32, $94, $63, $a4, $3e, $ba, $30 + .db $c9, $61, $ce, $06, $dd, $4b, $ce, $86, $dd, $4b + .db $fe, $02, $2e, $86, $5e, $02, $7e, $06, $fe, $02 + .db $1e, $86, $3e, $02, $5e, $06, $7e, $02, $9e, $06 + .db $fe, $0a, $0d, $c4, $cd, $43, $ce, $09, $de, $0b + .db $dd, $42, $fe, $02, $5d, $c7 + .db $fd + +;level 8-4 +L_CastleArea6: + .db $5b, $06 + .db $05, $32, $06, $33, $07, $34, $5e, $0a, $ae, $02 + .db $0d, $01, $39, $73, $0d, $03, $39, $7b, $4d, $4b + .db $de, $06, $1e, $8a, $ae, $06, $c4, $33, $16, $fe + .db $a5, $77, $fe, $02, $fe, $82, $0d, $07, $39, $73 + .db $a8, $74, $ed, $4b, $49, $fb, $e8, $74, $fe, $0a + .db $2e, $82, $67, $02, $84, $7a, $87, $31, $0d, $0b + .db $fe, $02, $0d, $0c, $39, $73, $5e, $06, $c6, $76 + .db $45, $ff, $be, $0a, $dd, $48, $fe, $06, $3d, $cb + .db $46, $7e, $ad, $4a, $fe, $82, $39, $f3, $a9, $7b + .db $4e, $8a, $9e, $07, $fe, $0a, $0d, $c4, $cd, $43 + .db $ce, $09, $de, $0b, $dd, $42, $fe, $02, $5d, $c7 + .db $fd + +;level 3-3 +L_GroundArea1: + .db $94, $11 + .db $0f, $26, $fe, $10, $28, $94, $65, $15, $eb, $12 + .db $fa, $41, $4a, $96, $54, $40, $a4, $42, $b7, $13 + .db $e9, $19, $f5, $15, $11, $80, $47, $42, $71, $13 + .db $80, $41, $15, $92, $1b, $1f, $24, $40, $55, $12 + .db $64, $40, $95, $12, $a4, $40, $d2, $12, $e1, $40 + .db $13, $c0, $2c, $17, $2f, $12, $49, $13, $83, $40 + .db $9f, $14, $a3, $40, $17, $92, $83, $13, $92, $41 + .db $b9, $14, $c5, $12, $c8, $40, $d4, $40, $4b, $92 + .db $78, $1b, $9c, $94, $9f, $11, $df, $14, $fe, $11 + .db $7d, $c1, $9e, $42, $cf, $20 + .db $fd + +;level 8-3 +L_GroundArea2: + .db $90, $b1 + .db $0f, $26, $29, $91, $7e, $42, $fe, $40, $28, $92 + .db $4e, $42, $2e, $c0, $57, $73, $c3, $25, $c7, $27 + .db $23, $84, $33, $20, $5c, $01, $77, $63, $88, $62 + .db $99, $61, $aa, $60, $bc, $01, $ee, $42, $4e, $c0 + .db $69, $11, $7e, $42, $de, $40, $f8, $62, $0e, $c2 + .db $ae, $40, $d7, $63, $e7, $63, $33, $a7, $37, $27 + .db $43, $04, $cc, $01, $e7, $73, $0c, $81, $3e, $42 + .db $0d, $0a, $5e, $40, $88, $72, $be, $42, $e7, $87 + .db $fe, $40, $39, $e1, $4e, $00, $69, $60, $87, $60 + .db $a5, $60, $c3, $31, $fe, $31, $6d, $c1, $be, $42 + .db $ef, $20 + .db $fd + +;level 4-1 +L_GroundArea3: + .db $52, $21 + .db $0f, $20, $6e, $40, $58, $f2, $93, $01, $97, $00 + .db $0c, $81, $97, $40, $a6, $41, $c7, $40, $0d, $04 + .db $03, $01, $07, $01, $23, $01, $27, $01, $ec, $03 + .db $ac, $f3, $c3, $03, $78, $e2, $94, $43, $47, $f3 + .db $74, $43, $47, $fb, $74, $43, $2c, $f1, $4c, $63 + .db $47, $00, $57, $21, $5c, $01, $7c, $72, $39, $f1 + .db $ec, $02, $4c, $81, $d8, $62, $ec, $01, $0d, $0d + .db $0f, $38, $c7, $07, $ed, $4a, $1d, $c1, $5f, $26 + .db $fd + +;level 6-2 +L_GroundArea4: + .db $54, $21 + .db $0f, $26, $a7, $22, $37, $fb, $73, $20, $83, $07 + .db $87, $02, $93, $20, $c7, $73, $04, $f1, $06, $31 + .db $39, $71, $59, $71, $e7, $73, $37, $a0, $47, $04 + .db $86, $7c, $e5, $71, $e7, $31, $33, $a4, $39, $71 + .db $a9, $71, $d3, $23, $08, $f2, $13, $05, $27, $02 + .db $49, $71, $75, $75, $e8, $72, $67, $f3, $99, $71 + .db $e7, $20, $f4, $72, $f7, $31, $17, $a0, $33, $20 + .db $39, $71, $73, $28, $bc, $05, $39, $f1, $79, $71 + .db $a6, $21, $c3, $06, $d3, $20, $dc, $00, $fc, $00 + .db $07, $a2, $13, $21, $5f, $32, $8c, $00, $98, $7a + .db $c7, $63, $d9, $61, $03, $a2, $07, $22, $74, $72 + .db $77, $31, $e7, $73, $39, $f1, $58, $72, $77, $73 + .db $d8, $72, $7f, $b1, $97, $73, $b6, $64, $c5, $65 + .db $d4, $66, $e3, $67, $f3, $67, $8d, $c1, $cf, $26 + .db $fd + +;level 3-1 +L_GroundArea5: + .db $52, $31 + .db $0f, $20, $6e, $66, $07, $81, $36, $01, $66, $00 + .db $a7, $22, $08, $f2, $67, $7b, $dc, $02, $98, $f2 + .db $d7, $20, $39, $f1, $9f, $33, $dc, $27, $dc, $57 + .db $23, $83, $57, $63, $6c, $51, $87, $63, $99, $61 + .db $a3, $06, $b3, $21, $77, $f3, $f3, $21, $f7, $2a + .db $13, $81, $23, $22, $53, $00, $63, $22, $e9, $0b + .db $0c, $83, $13, $21, $16, $22, $33, $05, $8f, $35 + .db $ec, $01, $63, $a0, $67, $20, $73, $01, $77, $01 + .db $83, $20, $87, $20, $b3, $20, $b7, $20, $c3, $01 + .db $c7, $00, $d3, $20, $d7, $20, $67, $a0, $77, $07 + .db $87, $22, $e8, $62, $f5, $65, $1c, $82, $7f, $38 + .db $8d, $c1, $cf, $26 + .db $fd + +;level 1-1 +L_GroundArea6: + .db $50, $21 + .db $07, $81, $47, $24, $57, $00, $63, $01, $77, $01 + .db $c9, $71, $68, $f2, $e7, $73, $97, $fb, $06, $83 + .db $5c, $01, $d7, $22, $e7, $00, $03, $a7, $6c, $02 + .db $b3, $22, $e3, $01, $e7, $07, $47, $a0, $57, $06 + .db $a7, $01, $d3, $00, $d7, $01, $07, $81, $67, $20 + .db $93, $22, $03, $a3, $1c, $61, $17, $21, $6f, $33 + .db $c7, $63, $d8, $62, $e9, $61, $fa, $60, $4f, $b3 + .db $87, $63, $9c, $01, $b7, $63, $c8, $62, $d9, $61 + .db $ea, $60, $39, $f1, $87, $21, $a7, $01, $b7, $20 + .db $39, $f1, $5f, $38, $6d, $c1, $af, $26 + .db $fd + +;level 1-3/5-3 +L_GroundArea7: + .db $90, $11 + .db $0f, $26, $fe, $10, $2a, $93, $87, $17, $a3, $14 + .db $b2, $42, $0a, $92, $19, $40, $36, $14, $50, $41 + .db $82, $16, $2b, $93, $24, $41, $bb, $14, $b8, $00 + .db $c2, $43, $c3, $13, $1b, $94, $67, $12, $c4, $15 + .db $53, $c1, $d2, $41, $12, $c1, $29, $13, $85, $17 + .db $1b, $92, $1a, $42, $47, $13, $83, $41, $a7, $13 + .db $0e, $91, $a7, $63, $b7, $63, $c5, $65, $d5, $65 + .db $dd, $4a, $e3, $67, $f3, $67, $8d, $c1, $ae, $42 + .db $df, $20 + .db $fd + +;level 2-3/7-3 +L_GroundArea8: + .db $90, $11 + .db $0f, $26, $6e, $10, $8b, $17, $af, $32, $d8, $62 + .db $e8, $62, $fc, $3f, $ad, $c8, $f8, $64, $0c, $be + .db $43, $43, $f8, $64, $0c, $bf, $73, $40, $84, $40 + .db $93, $40, $a4, $40, $b3, $40, $f8, $64, $48, $e4 + .db $5c, $39, $83, $40, $92, $41, $b3, $40, $f8, $64 + .db $48, $e4, $5c, $39, $f8, $64, $13, $c2, $37, $65 + .db $4c, $24, $63, $00, $97, $65, $c3, $42, $0b, $97 + .db $ac, $32, $f8, $64, $0c, $be, $53, $45, $9d, $48 + .db $f8, $64, $2a, $e2, $3c, $47, $56, $43, $ba, $62 + .db $f8, $64, $0c, $b7, $88, $64, $bc, $31, $d4, $45 + .db $fc, $31, $3c, $b1, $78, $64, $8c, $38, $0b, $9c + .db $1a, $33, $18, $61, $28, $61, $39, $60, $5d, $4a + .db $ee, $11, $0f, $b8, $1d, $c1, $3e, $42, $6f, $20 + .db $fd + +;level 2-1 +L_GroundArea9: + .db $52, $31 + .db $0f, $20, $6e, $40, $f7, $20, $07, $84, $17, $20 + .db $4f, $34, $c3, $03, $c7, $02, $d3, $22, $27, $e3 + .db $39, $61, $e7, $73, $5c, $e4, $57, $00, $6c, $73 + .db $47, $a0, $53, $06, $63, $22, $a7, $73, $fc, $73 + .db $13, $a1, $33, $05, $43, $21, $5c, $72, $c3, $23 + .db $cc, $03, $77, $fb, $ac, $02, $39, $f1, $a7, $73 + .db $d3, $04, $e8, $72, $e3, $22, $26, $f4, $bc, $02 + .db $8c, $81, $a8, $62, $17, $87, $43, $24, $a7, $01 + .db $c3, $04, $08, $f2, $97, $21, $a3, $02, $c9, $0b + .db $e1, $69, $f1, $69, $8d, $c1, $cf, $26 + .db $fd + +;pipe intro area +L_GroundArea10: + .db $38, $11 + .db $0f, $26, $ad, $40, $3d, $c7 + .db $fd + +;level 5-1 +L_GroundArea11: + .db $95, $b1 + .db $0f, $26, $0d, $02, $c8, $72, $1c, $81, $38, $72 + .db $0d, $05, $97, $34, $98, $62, $a3, $20, $b3, $06 + .db $c3, $20, $cc, $03, $f9, $91, $2c, $81, $48, $62 + .db $0d, $09, $37, $63, $47, $03, $57, $21, $8c, $02 + .db $c5, $79, $c7, $31, $f9, $11, $39, $f1, $a9, $11 + .db $6f, $b4, $d3, $65, $e3, $65, $7d, $c1, $bf, $26 + .db $fd + +;cloud level used in levels 2-1 and 5-2 +L_GroundArea12: + .db $00, $c1 + .db $4c, $00, $f4, $4f, $0d, $02, $02, $42, $43, $4f + .db $52, $c2, $de, $00, $5a, $c2, $4d, $c7 + .db $fd + +;level 4-3 +L_GroundArea13: + .db $90, $51 + .db $0f, $26, $ee, $10, $0b, $94, $33, $14, $42, $42 + .db $77, $16, $86, $44, $02, $92, $4a, $16, $69, $42 + .db $73, $14, $b0, $00, $c7, $12, $05, $c0, $1c, $17 + .db $1f, $11, $36, $12, $8f, $14, $91, $40, $1b, $94 + .db $35, $12, $34, $42, $60, $42, $61, $12, $87, $12 + .db $96, $40, $a3, $14, $1c, $98, $1f, $11, $47, $12 + .db $9f, $15, $cc, $15, $cf, $11, $05, $c0, $1f, $15 + .db $39, $12, $7c, $16, $7f, $11, $82, $40, $98, $12 + .db $df, $15, $16, $c4, $17, $14, $54, $12, $9b, $16 + .db $28, $94, $ce, $01, $3d, $c1, $5e, $42, $8f, $20 + .db $fd + +;level 6-3 +L_GroundArea14: + .db $97, $11 + .db $0f, $26, $fe, $10, $2b, $92, $57, $12, $8b, $12 + .db $c0, $41, $f7, $13, $5b, $92, $69, $0b, $bb, $12 + .db $b2, $46, $19, $93, $71, $00, $17, $94, $7c, $14 + .db $7f, $11, $93, $41, $bf, $15, $fc, $13, $ff, $11 + .db $2f, $95, $50, $42, $51, $12, $58, $14, $a6, $12 + .db $db, $12, $1b, $93, $46, $43, $7b, $12, $8d, $49 + .db $b7, $14, $1b, $94, $49, $0b, $bb, $12, $fc, $13 + .db $ff, $12, $03, $c1, $2f, $15, $43, $12, $4b, $13 + .db $77, $13, $9d, $4a, $15, $c1, $a1, $41, $c3, $12 + .db $fe, $01, $7d, $c1, $9e, $42, $cf, $20 + .db $fd + +;level 6-1 +L_GroundArea15: + .db $52, $21 + .db $0f, $20, $6e, $44, $0c, $f1, $4c, $01, $aa, $35 + .db $d9, $34, $ee, $20, $08, $b3, $37, $32, $43, $04 + .db $4e, $21, $53, $20, $7c, $01, $97, $21, $b7, $07 + .db $9c, $81, $e7, $42, $5f, $b3, $97, $63, $ac, $02 + .db $c5, $41, $49, $e0, $58, $61, $76, $64, $85, $65 + .db $94, $66, $a4, $22, $a6, $03, $c8, $22, $dc, $02 + .db $68, $f2, $96, $42, $13, $82, $17, $02, $af, $34 + .db $f6, $21, $fc, $06, $26, $80, $2a, $24, $36, $01 + .db $8c, $00, $ff, $35, $4e, $a0, $55, $21, $77, $20 + .db $87, $07, $89, $22, $ae, $21, $4c, $82, $9f, $34 + .db $ec, $01, $03, $e7, $13, $67, $8d, $4a, $ad, $41 + .db $0f, $a6 + .db $fd + +;warp zone area used in level 4-2 +L_GroundArea16: + .db $10, $51 + .db $4c, $00, $c7, $12, $c6, $42, $03, $92, $02, $42 + .db $29, $12, $63, $12, $62, $42, $69, $14, $a5, $12 + .db $a4, $42, $e2, $14, $e1, $44, $f8, $16, $37, $c1 + .db $8f, $38, $02, $bb, $28, $7a, $68, $7a, $a8, $7a + .db $e0, $6a, $f0, $6a, $6d, $c5 + .db $fd + +;level 8-1 +L_GroundArea17: + .db $92, $31 + .db $0f, $20, $6e, $40, $0d, $02, $37, $73, $ec, $00 + .db $0c, $80, $3c, $00, $6c, $00, $9c, $00, $06, $c0 + .db $c7, $73, $06, $83, $28, $72, $96, $40, $e7, $73 + .db $26, $c0, $87, $7b, $d2, $41, $39, $f1, $c8, $f2 + .db $97, $e3, $a3, $23, $e7, $02, $e3, $07, $f3, $22 + .db $37, $e3, $9c, $00, $bc, $00, $ec, $00, $0c, $80 + .db $3c, $00, $86, $21, $a6, $06, $b6, $24, $5c, $80 + .db $7c, $00, $9c, $00, $29, $e1, $dc, $05, $f6, $41 + .db $dc, $80, $e8, $72, $0c, $81, $27, $73, $4c, $01 + .db $66, $74, $0d, $11, $3f, $35, $b6, $41, $2c, $82 + .db $36, $40, $7c, $02, $86, $40, $f9, $61, $39, $e1 + .db $ac, $04, $c6, $41, $0c, $83, $16, $41, $88, $f2 + .db $39, $f1, $7c, $00, $89, $61, $9c, $00, $a7, $63 + .db $bc, $00, $c5, $65, $dc, $00, $e3, $67, $f3, $67 + .db $8d, $c1, $cf, $26 + .db $fd + +;level 5-2 +L_GroundArea18: + .db $55, $b1 + .db $0f, $26, $cf, $33, $07, $b2, $15, $11, $52, $42 + .db $99, $0b, $ac, $02, $d3, $24, $d6, $42, $d7, $25 + .db $23, $84, $cf, $33, $07, $e3, $19, $61, $78, $7a + .db $ef, $33, $2c, $81, $46, $64, $55, $65, $65, $65 + .db $ec, $74, $47, $82, $53, $05, $63, $21, $62, $41 + .db $96, $22, $9a, $41, $cc, $03, $b9, $91, $39, $f1 + .db $63, $26, $67, $27, $d3, $06, $fc, $01, $18, $e2 + .db $d9, $07, $e9, $04, $0c, $86, $37, $22, $93, $24 + .db $87, $84, $ac, $02, $c2, $41, $c3, $23, $d9, $71 + .db $fc, $01, $7f, $b1, $9c, $00, $a7, $63, $b6, $64 + .db $cc, $00, $d4, $66, $e3, $67, $f3, $67, $8d, $c1 + .db $cf, $26 + .db $fd + +;level 8-2 +L_GroundArea19: + .db $50, $b1 + .db $0f, $26, $fc, $00, $1f, $b3, $5c, $00, $65, $65 + .db $74, $66, $83, $67, $93, $67, $dc, $73, $4c, $80 + .db $b3, $20, $c9, $0b, $c3, $08, $d3, $2f, $dc, $00 + .db $2c, $80, $4c, $00, $8c, $00, $d3, $2e, $ed, $4a + .db $fc, $00, $d7, $a1, $ec, $01, $4c, $80, $59, $11 + .db $d8, $11, $da, $10, $37, $a0, $47, $04, $99, $11 + .db $e7, $21, $3a, $90, $67, $20, $76, $10, $77, $60 + .db $87, $07, $d8, $12, $39, $f1, $ac, $00, $e9, $71 + .db $0c, $80, $2c, $00, $4c, $05, $c7, $7b, $39, $f1 + .db $ec, $00, $f9, $11, $0c, $82, $6f, $34, $f8, $11 + .db $fa, $10, $7f, $b2, $ac, $00, $b6, $64, $cc, $01 + .db $e3, $67, $f3, $67, $8d, $c1, $cf, $26 + .db $fd + +;level 7-1 +L_GroundArea20: + .db $52, $b1 + .db $0f, $20, $6e, $45, $39, $91, $b3, $04, $c3, $21 + .db $c8, $11, $ca, $10, $49, $91, $7c, $73, $e8, $12 + .db $88, $91, $8a, $10, $e7, $21, $05, $91, $07, $30 + .db $17, $07, $27, $20, $49, $11, $9c, $01, $c8, $72 + .db $23, $a6, $27, $26, $d3, $03, $d8, $7a, $89, $91 + .db $d8, $72, $39, $f1, $a9, $11, $09, $f1, $63, $24 + .db $67, $24, $d8, $62, $28, $91, $2a, $10, $56, $21 + .db $70, $04, $79, $0b, $8c, $00, $94, $21, $9f, $35 + .db $2f, $b8, $3d, $c1, $7f, $26 + .db $fd + +;cloud level used in levels 3-1 and 6-2 +L_GroundArea21: + .db $06, $c1 + .db $4c, $00, $f4, $4f, $0d, $02, $06, $20, $24, $4f + .db $35, $a0, $36, $20, $53, $46, $d5, $20, $d6, $20 + .db $34, $a1, $73, $49, $74, $20, $94, $20, $b4, $20 + .db $d4, $20, $f4, $20, $2e, $80, $59, $42, $4d, $c7 + .db $fd + +;level 3-2 +L_GroundArea22: + .db $96, $31 + .db $0f, $26, $0d, $03, $1a, $60, $77, $42, $c4, $00 + .db $c8, $62, $b9, $e1, $d3, $06, $d7, $07, $f9, $61 + .db $0c, $81, $4e, $b1, $8e, $b1, $bc, $01, $e4, $50 + .db $e9, $61, $0c, $81, $0d, $0a, $84, $43, $98, $72 + .db $0d, $0c, $0f, $38, $1d, $c1, $5f, $26 + .db $fd + +;level 1-2 +L_UndergroundArea1: + .db $48, $0f + .db $0e, $01, $5e, $02, $a7, $00, $bc, $73, $1a, $e0 + .db $39, $61, $58, $62, $77, $63, $97, $63, $b8, $62 + .db $d6, $07, $f8, $62, $19, $e1, $75, $52, $86, $40 + .db $87, $50, $95, $52, $93, $43, $a5, $21, $c5, $52 + .db $d6, $40, $d7, $20, $e5, $06, $e6, $51, $3e, $8d + .db $5e, $03, $67, $52, $77, $52, $7e, $02, $9e, $03 + .db $a6, $43, $a7, $23, $de, $05, $fe, $02, $1e, $83 + .db $33, $54, $46, $40, $47, $21, $56, $04, $5e, $02 + .db $83, $54, $93, $52, $96, $07, $97, $50, $be, $03 + .db $c7, $23, $fe, $02, $0c, $82, $43, $45, $45, $24 + .db $46, $24, $90, $08, $95, $51, $78, $fa, $d7, $73 + .db $39, $f1, $8c, $01, $a8, $52, $b8, $52, $cc, $01 + .db $5f, $b3, $97, $63, $9e, $00, $0e, $81, $16, $24 + .db $66, $04, $8e, $00, $fe, $01, $08, $d2, $0e, $06 + .db $6f, $47, $9e, $0f, $0e, $82, $2d, $47, $28, $7a + .db $68, $7a, $a8, $7a, $ae, $01, $de, $0f, $6d, $c5 + .db $fd + +;level 4-2 +L_UndergroundArea2: + .db $48, $0f + .db $0e, $01, $5e, $02, $bc, $01, $fc, $01, $2c, $82 + .db $41, $52, $4e, $04, $67, $25, $68, $24, $69, $24 + .db $ba, $42, $c7, $04, $de, $0b, $b2, $87, $fe, $02 + .db $2c, $e1, $2c, $71, $67, $01, $77, $00, $87, $01 + .db $8e, $00, $ee, $01, $f6, $02, $03, $85, $05, $02 + .db $13, $21, $16, $02, $27, $02, $2e, $02, $88, $72 + .db $c7, $20, $d7, $07, $e4, $76, $07, $a0, $17, $06 + .db $48, $7a, $76, $20, $98, $72, $79, $e1, $88, $62 + .db $9c, $01, $b7, $73, $dc, $01, $f8, $62, $fe, $01 + .db $08, $e2, $0e, $00, $6e, $02, $73, $20, $77, $23 + .db $83, $04, $93, $20, $ae, $00, $fe, $0a, $0e, $82 + .db $39, $71, $a8, $72, $e7, $73, $0c, $81, $8f, $32 + .db $ae, $00, $fe, $04, $04, $d1, $17, $04, $26, $49 + .db $27, $29, $df, $33, $fe, $02, $44, $f6, $7c, $01 + .db $8e, $06, $bf, $47, $ee, $0f, $4d, $c7, $0e, $82 + .db $68, $7a, $ae, $01, $de, $0f, $6d, $c5 + .db $fd + +;underground bonus rooms area used in many levels +L_UndergroundArea3: + .db $48, $01 + .db $0e, $01, $00, $5a, $3e, $06, $45, $46, $47, $46 + .db $53, $44, $ae, $01, $df, $4a, $4d, $c7, $0e, $81 + .db $00, $5a, $2e, $04, $37, $28, $3a, $48, $46, $47 + .db $c7, $07, $ce, $0f, $df, $4a, $4d, $c7, $0e, $81 + .db $00, $5a, $33, $53, $43, $51, $46, $40, $47, $50 + .db $53, $04, $55, $40, $56, $50, $62, $43, $64, $40 + .db $65, $50, $71, $41, $73, $51, $83, $51, $94, $40 + .db $95, $50, $a3, $50, $a5, $40, $a6, $50, $b3, $51 + .db $b6, $40, $b7, $50, $c3, $53, $df, $4a, $4d, $c7 + .db $0e, $81, $00, $5a, $2e, $02, $36, $47, $37, $52 + .db $3a, $49, $47, $25, $a7, $52, $d7, $04, $df, $4a + .db $4d, $c7, $0e, $81, $00, $5a, $3e, $02, $44, $51 + .db $53, $44, $54, $44, $55, $24, $a1, $54, $ae, $01 + .db $b4, $21, $df, $4a, $e5, $07, $4d, $c7 + .db $fd + +;water area used in levels 5-2 and 6-2 +L_WaterArea1: + .db $41, $01 + .db $b4, $34, $c8, $52, $f2, $51, $47, $d3, $6c, $03 + .db $65, $49, $9e, $07, $be, $01, $cc, $03, $fe, $07 + .db $0d, $c9, $1e, $01, $6c, $01, $62, $35, $63, $53 + .db $8a, $41, $ac, $01, $b3, $53, $e9, $51, $26, $c3 + .db $27, $33, $63, $43, $64, $33, $ba, $60, $c9, $61 + .db $ce, $0b, $e5, $09, $ee, $0f, $7d, $ca, $7d, $47 + .db $fd + +;level 2-2/7-2 +L_WaterArea2: + .db $41, $01 + .db $b8, $52, $ea, $41, $27, $b2, $b3, $42, $16, $d4 + .db $4a, $42, $a5, $51, $a7, $31, $27, $d3, $08, $e2 + .db $16, $64, $2c, $04, $38, $42, $76, $64, $88, $62 + .db $de, $07, $fe, $01, $0d, $c9, $23, $32, $31, $51 + .db $98, $52, $0d, $c9, $59, $42, $63, $53, $67, $31 + .db $14, $c2, $36, $31, $87, $53, $17, $e3, $29, $61 + .db $30, $62, $3c, $08, $42, $37, $59, $40, $6a, $42 + .db $99, $40, $c9, $61, $d7, $63, $39, $d1, $58, $52 + .db $c3, $67, $d3, $31, $dc, $06, $f7, $42, $fa, $42 + .db $23, $b1, $43, $67, $c3, $34, $c7, $34, $d1, $51 + .db $43, $b3, $47, $33, $9a, $30, $a9, $61, $b8, $62 + .db $be, $0b, $d5, $09, $de, $0f, $0d, $ca, $7d, $47 + .db $fd + +;water area used in level 8-4 +L_WaterArea3: + .db $49, $0f + .db $1e, $01, $39, $73, $5e, $07, $ae, $0b, $1e, $82 + .db $6e, $88, $9e, $02, $0d, $04, $2e, $0b, $45, $09 + .db $4e, $0f, $ed, $47 + .db $fd + +;------------------------------------------------------------------------------------- + +;unused space + .db $ff + +;------------------------------------------------------------------------------------- + +;indirect jump routine called when +;$0770 is set to 1 +GameMode: + lda OperMode_Task + jsr JumpEngine + + .dw InitializeArea + .dw ScreenRoutines + .dw SecondaryGameSetup + .dw GameCoreRoutine + +;------------------------------------------------------------------------------------- + +GameCoreRoutine: + ldx CurrentPlayer ;get which player is on the screen + lda SavedJoypadBits,x ;use appropriate player's controller bits + sta SavedJoypadBits ;as the master controller bits + jsr GameRoutines ;execute one of many possible subs + lda OperMode_Task ;check major task of operating mode + cmp #$03 ;if we are supposed to be here, + bcs GameEngine ;branch to the game engine itself + rts + +GameEngine: + jsr ProcFireball_Bubble ;process fireballs and air bubbles + ldx #$00 +ProcELoop: stx ObjectOffset ;put incremented offset in X as enemy object offset + jsr EnemiesAndLoopsCore ;process enemy objects + jsr FloateyNumbersRoutine ;process floatey numbers + inx + cpx #$06 ;do these two subroutines until the whole buffer is done + bne ProcELoop + jsr GetPlayerOffscreenBits ;get offscreen bits for player object + jsr RelativePlayerPosition ;get relative coordinates for player object + jsr PlayerGfxHandler ;draw the player + jsr BlockObjMT_Updater ;replace block objects with metatiles if necessary + ldx #$01 + stx ObjectOffset ;set offset for second + jsr BlockObjectsCore ;process second block object + dex + stx ObjectOffset ;set offset for first + jsr BlockObjectsCore ;process first block object + jsr MiscObjectsCore ;process misc objects (hammer, jumping coins) + jsr ProcessCannons ;process bullet bill cannons + jsr ProcessWhirlpools ;process whirlpools + jsr FlagpoleRoutine ;process the flagpole + jsr RunGameTimer ;count down the game timer + jsr ColorRotation ;cycle one of the background colors + lda Player_Y_HighPos + cmp #$02 ;if player is below the screen, don't bother with the music + bpl NoChgMus + lda StarInvincibleTimer ;if star mario invincibility timer at zero, + beq ClrPlrPal ;skip this part + cmp #$04 + bne NoChgMus ;if not yet at a certain point, continue + lda IntervalTimerControl ;if interval timer not yet expired, + bne NoChgMus ;branch ahead, don't bother with the music + jsr GetAreaMusic ;to re-attain appropriate level music +NoChgMus: ldy StarInvincibleTimer ;get invincibility timer + lda FrameCounter ;get frame counter + cpy #$08 ;if timer still above certain point, + bcs CycleTwo ;branch to cycle player's palette quickly + lsr ;otherwise, divide by 8 to cycle every eighth frame + lsr +CycleTwo: lsr ;if branched here, divide by 2 to cycle every other frame + jsr CyclePlayerPalette ;do sub to cycle the palette (note: shares fire flower code) + jmp SaveAB ;then skip this sub to finish up the game engine +ClrPlrPal: jsr ResetPalStar ;do sub to clear player's palette bits in attributes +SaveAB: lda A_B_Buttons ;save current A and B button + sta PreviousA_B_Buttons ;into temp variable to be used on next frame + lda #$00 + sta Left_Right_Buttons ;nullify left and right buttons temp variable +UpdScrollVar: lda VRAM_Buffer_AddrCtrl + cmp #$06 ;if vram address controller set to 6 (one of two $0341s) + beq ExitEng ;then branch to leave + lda AreaParserTaskNum ;otherwise check number of tasks + bne RunParser + lda ScrollThirtyTwo ;get horizontal scroll in 0-31 or $00-$20 range + cmp #$20 ;check to see if exceeded $21 + bmi ExitEng ;branch to leave if not + lda ScrollThirtyTwo + sbc #$20 ;otherwise subtract $20 to set appropriately + sta ScrollThirtyTwo ;and store + lda #$00 ;reset vram buffer offset used in conjunction with + sta VRAM_Buffer2_Offset ;level graphics buffer at $0341-$035f +RunParser: jsr AreaParserTaskHandler ;update the name table with more level graphics +ExitEng: rts ;and after all that, we're finally done! + +;------------------------------------------------------------------------------------- + +ScrollHandler: + lda Player_X_Scroll ;load value saved here + clc + adc Platform_X_Scroll ;add value used by left/right platforms + sta Player_X_Scroll ;save as new value here to impose force on scroll + lda ScrollLock ;check scroll lock flag + bne InitScrlAmt ;skip a bunch of code here if set + lda Player_Pos_ForScroll + cmp #$50 ;check player's horizontal screen position + bcc InitScrlAmt ;if less than 80 pixels to the right, branch + lda SideCollisionTimer ;if timer related to player's side collision + bne InitScrlAmt ;not expired, branch + ldy Player_X_Scroll ;get value and decrement by one + dey ;if value originally set to zero or otherwise + bmi InitScrlAmt ;negative for left movement, branch + iny + cpy #$02 ;if value $01, branch and do not decrement + bcc ChkNearMid + dey ;otherwise decrement by one +ChkNearMid: lda Player_Pos_ForScroll + cmp #$70 ;check player's horizontal screen position + bcc ScrollScreen ;if less than 112 pixels to the right, branch + ldy Player_X_Scroll ;otherwise get original value undecremented + +ScrollScreen: + tya + sta ScrollAmount ;save value here + clc + adc ScrollThirtyTwo ;add to value already set here + sta ScrollThirtyTwo ;save as new value here + tya + clc + adc ScreenLeft_X_Pos ;add to left side coordinate + sta ScreenLeft_X_Pos ;save as new left side coordinate + sta HorizontalScroll ;save here also + lda ScreenLeft_PageLoc + adc #$00 ;add carry to page location for left + sta ScreenLeft_PageLoc ;side of the screen + and #$01 ;get LSB of page location + sta $00 ;save as temp variable for PPU register 1 mirror + lda Mirror_PPU_CTRL_REG1 ;get PPU register 1 mirror + and #%11111110 ;save all bits except d0 + ora $00 ;get saved bit here and save in PPU register 1 + sta Mirror_PPU_CTRL_REG1 ;mirror to be used to set name table later + jsr GetScreenPosition ;figure out where the right side is + lda #$08 + sta ScrollIntervalTimer ;set scroll timer (residual, not used elsewhere) + jmp ChkPOffscr ;skip this part +InitScrlAmt: lda #$00 + sta ScrollAmount ;initialize value here +ChkPOffscr: ldx #$00 ;set X for player offset + jsr GetXOffscreenBits ;get horizontal offscreen bits for player + sta $00 ;save them here + ldy #$00 ;load default offset (left side) + asl ;if d7 of offscreen bits are set, + bcs KeepOnscr ;branch with default offset + iny ;otherwise use different offset (right side) + lda $00 + and #%00100000 ;check offscreen bits for d5 set + beq InitPlatScrl ;if not set, branch ahead of this part +KeepOnscr: lda ScreenEdge_X_Pos,y ;get left or right side coordinate based on offset + sec + sbc X_SubtracterData,y ;subtract amount based on offset + sta Player_X_Position ;store as player position to prevent movement further + lda ScreenEdge_PageLoc,y ;get left or right page location based on offset + sbc #$00 ;subtract borrow + sta Player_PageLoc ;save as player's page location + lda Left_Right_Buttons ;check saved controller bits + cmp OffscrJoypadBitsData,y ;against bits based on offset + beq InitPlatScrl ;if not equal, branch + lda #$00 + sta Player_X_Speed ;otherwise nullify horizontal speed of player +InitPlatScrl: lda #$00 ;nullify platform force imposed on scroll + sta Platform_X_Scroll + rts + +X_SubtracterData: + .db $00, $10 + +OffscrJoypadBitsData: + .db $01, $02 + +;------------------------------------------------------------------------------------- + +GetScreenPosition: + lda ScreenLeft_X_Pos ;get coordinate of screen's left boundary + clc + adc #$ff ;add 255 pixels + sta ScreenRight_X_Pos ;store as coordinate of screen's right boundary + lda ScreenLeft_PageLoc ;get page number where left boundary is + adc #$00 ;add carry from before + sta ScreenRight_PageLoc ;store as page number where right boundary is + rts + +;------------------------------------------------------------------------------------- + +GameRoutines: + lda GameEngineSubroutine ;run routine based on number (a few of these routines are + jsr JumpEngine ;merely placeholders as conditions for other routines) + + .dw Entrance_GameTimerSetup + .dw Vine_AutoClimb + .dw SideExitPipeEntry + .dw VerticalPipeEntry + .dw FlagpoleSlide + .dw PlayerEndLevel + .dw PlayerLoseLife + .dw PlayerEntrance + .dw PlayerCtrlRoutine + .dw PlayerChangeSize + .dw PlayerInjuryBlink + .dw PlayerDeath + .dw PlayerFireFlower + +;------------------------------------------------------------------------------------- + +PlayerEntrance: + lda AltEntranceControl ;check for mode of alternate entry + cmp #$02 + beq EntrMode2 ;if found, branch to enter from pipe or with vine + lda #$00 + ldy Player_Y_Position ;if vertical position above a certain + cpy #$30 ;point, nullify controller bits and continue + bcc AutoControlPlayer ;with player movement code, do not return + lda PlayerEntranceCtrl ;check player entry bits from header + cmp #$06 + beq ChkBehPipe ;if set to 6 or 7, execute pipe intro code + cmp #$07 ;otherwise branch to normal entry + bne PlayerRdy +ChkBehPipe: lda Player_SprAttrib ;check for sprite attributes + bne IntroEntr ;branch if found + lda #$01 + jmp AutoControlPlayer ;force player to walk to the right +IntroEntr: jsr EnterSidePipe ;execute sub to move player to the right + dec ChangeAreaTimer ;decrement timer for change of area + bne ExitEntr ;branch to exit if not yet expired + inc DisableIntermediate ;set flag to skip world and lives display + jmp NextArea ;jump to increment to next area and set modes +EntrMode2: lda JoypadOverride ;if controller override bits set here, + bne VineEntr ;branch to enter with vine + lda #$ff ;otherwise, set value here then execute sub + jsr MovePlayerYAxis ;to move player upwards + lda Player_Y_Position ;check to see if player is at a specific coordinate + cmp #$91 ;if player risen to a certain point (this requires pipes + bcc PlayerRdy ;to be at specific height to look/function right) branch + rts ;to the last part, otherwise leave +VineEntr: lda VineHeight + cmp #$60 ;check vine height + bne ExitEntr ;if vine not yet reached maximum height, branch to leave + lda Player_Y_Position ;get player's vertical coordinate + cmp #$99 ;check player's vertical coordinate against preset value + ldy #$00 ;load default values to be written to + lda #$01 ;this value moves player to the right off the vine + bcc OffVine ;if vertical coordinate < preset value, use defaults + lda #$03 + sta Player_State ;otherwise set player state to climbing + iny ;increment value in Y + lda #$08 ;set block in block buffer to cover hole, then + sta Block_Buffer_1+$b4 ;use same value to force player to climb +OffVine: sty DisableCollisionDet ;set collision detection disable flag + jsr AutoControlPlayer ;use contents of A to move player up or right, execute sub + lda Player_X_Position + cmp #$48 ;check player's horizontal position + bcc ExitEntr ;if not far enough to the right, branch to leave +PlayerRdy: lda #$08 ;set routine to be executed by game engine next frame + sta GameEngineSubroutine + lda #$01 ;set to face player to the right + sta PlayerFacingDir + lsr ;init A + sta AltEntranceControl ;init mode of entry + sta DisableCollisionDet ;init collision detection disable flag + sta JoypadOverride ;nullify controller override bits +ExitEntr: rts ;leave! + +;------------------------------------------------------------------------------------- +;$07 - used to hold upper limit of high byte when player falls down hole + +AutoControlPlayer: + sta SavedJoypadBits ;override controller bits with contents of A if executing here + +PlayerCtrlRoutine: + lda GameEngineSubroutine ;check task here + cmp #$0b ;if certain value is set, branch to skip controller bit loading + beq SizeChk + lda AreaType ;are we in a water type area? + bne SaveJoyp ;if not, branch + ldy Player_Y_HighPos + dey ;if not in vertical area between + bne DisJoyp ;status bar and bottom, branch + lda Player_Y_Position + cmp #$d0 ;if nearing the bottom of the screen or + bcc SaveJoyp ;not in the vertical area between status bar or bottom, +DisJoyp: lda #$00 ;disable controller bits + sta SavedJoypadBits +SaveJoyp: lda SavedJoypadBits ;otherwise store A and B buttons in $0a + and #%11000000 + sta A_B_Buttons + lda SavedJoypadBits ;store left and right buttons in $0c + and #%00000011 + sta Left_Right_Buttons + lda SavedJoypadBits ;store up and down buttons in $0b + and #%00001100 + sta Up_Down_Buttons + and #%00000100 ;check for pressing down + beq SizeChk ;if not, branch + lda Player_State ;check player's state + bne SizeChk ;if not on the ground, branch + ldy Left_Right_Buttons ;check left and right + beq SizeChk ;if neither pressed, branch + lda #$00 + sta Left_Right_Buttons ;if pressing down while on the ground, + sta Up_Down_Buttons ;nullify directional bits +SizeChk: jsr PlayerMovementSubs ;run movement subroutines + ldy #$01 ;is player small? + lda PlayerSize + bne ChkMoveDir + ldy #$00 ;check for if crouching + lda CrouchingFlag + beq ChkMoveDir ;if not, branch ahead + ldy #$02 ;if big and crouching, load y with 2 +ChkMoveDir: sty Player_BoundBoxCtrl ;set contents of Y as player's bounding box size control + lda #$01 ;set moving direction to right by default + ldy Player_X_Speed ;check player's horizontal speed + beq PlayerSubs ;if not moving at all horizontally, skip this part + bpl SetMoveDir ;if moving to the right, use default moving direction + asl ;otherwise change to move to the left +SetMoveDir: sta Player_MovingDir ;set moving direction +PlayerSubs: jsr ScrollHandler ;move the screen if necessary + jsr GetPlayerOffscreenBits ;get player's offscreen bits + jsr RelativePlayerPosition ;get coordinates relative to the screen + ldx #$00 ;set offset for player object + jsr BoundingBoxCore ;get player's bounding box coordinates + jsr PlayerBGCollision ;do collision detection and process + lda Player_Y_Position + cmp #$40 ;check to see if player is higher than 64th pixel + bcc PlayerHole ;if so, branch ahead + lda GameEngineSubroutine + cmp #$05 ;if running end-of-level routine, branch ahead + beq PlayerHole + cmp #$07 ;if running player entrance routine, branch ahead + beq PlayerHole + cmp #$04 ;if running routines $00-$03, branch ahead + bcc PlayerHole + lda Player_SprAttrib + and #%11011111 ;otherwise nullify player's + sta Player_SprAttrib ;background priority flag +PlayerHole: lda Player_Y_HighPos ;check player's vertical high byte + cmp #$02 ;for below the screen + bmi ExitCtrl ;branch to leave if not that far down + ldx #$01 + stx ScrollLock ;set scroll lock + ldy #$04 + sty $07 ;set value here + ldx #$00 ;use X as flag, and clear for cloud level + ldy GameTimerExpiredFlag ;check game timer expiration flag + bne HoleDie ;if set, branch + ldy CloudTypeOverride ;check for cloud type override + bne ChkHoleX ;skip to last part if found +HoleDie: inx ;set flag in X for player death + ldy GameEngineSubroutine + cpy #$0b ;check for some other routine running + beq ChkHoleX ;if so, branch ahead + ldy DeathMusicLoaded ;check value here + bne HoleBottom ;if already set, branch to next part + iny + sty EventMusicQueue ;otherwise play death music + sty DeathMusicLoaded ;and set value here +HoleBottom: ldy #$06 + sty $07 ;change value here +ChkHoleX: cmp $07 ;compare vertical high byte with value set here + bmi ExitCtrl ;if less, branch to leave + dex ;otherwise decrement flag in X + bmi CloudExit ;if flag was clear, branch to set modes and other values + ldy EventMusicBuffer ;check to see if music is still playing + bne ExitCtrl ;branch to leave if so + lda #$06 ;otherwise set to run lose life routine + sta GameEngineSubroutine ;on next frame +ExitCtrl: rts ;leave + +CloudExit: + lda #$00 + sta JoypadOverride ;clear controller override bits if any are set + jsr SetEntr ;do sub to set secondary mode + inc AltEntranceControl ;set mode of entry to 3 + rts + +;------------------------------------------------------------------------------------- + +Vine_AutoClimb: + lda Player_Y_HighPos ;check to see whether player reached position + bne AutoClimb ;above the status bar yet and if so, set modes + lda Player_Y_Position + cmp #$e4 + bcc SetEntr +AutoClimb: lda #%00001000 ;set controller bits override to up + sta JoypadOverride + ldy #$03 ;set player state to climbing + sty Player_State + jmp AutoControlPlayer +SetEntr: lda #$02 ;set starting position to override + sta AltEntranceControl + jmp ChgAreaMode ;set modes + +;------------------------------------------------------------------------------------- + +VerticalPipeEntry: + lda #$01 ;set 1 as movement amount + jsr MovePlayerYAxis ;do sub to move player downwards + jsr ScrollHandler ;do sub to scroll screen with saved force if necessary + ldy #$00 ;load default mode of entry + lda WarpZoneControl ;check warp zone control variable/flag + bne ChgAreaPipe ;if set, branch to use mode 0 + iny + lda AreaType ;check for castle level type + cmp #$03 + bne ChgAreaPipe ;if not castle type level, use mode 1 + iny + jmp ChgAreaPipe ;otherwise use mode 2 + +MovePlayerYAxis: + clc + adc Player_Y_Position ;add contents of A to player position + sta Player_Y_Position + rts + +;------------------------------------------------------------------------------------- + +SideExitPipeEntry: + jsr EnterSidePipe ;execute sub to move player to the right + ldy #$02 +ChgAreaPipe: dec ChangeAreaTimer ;decrement timer for change of area + bne ExitCAPipe + sty AltEntranceControl ;when timer expires set mode of alternate entry +ChgAreaMode: inc DisableScreenFlag ;set flag to disable screen output + lda #$00 + sta OperMode_Task ;set secondary mode of operation + sta Sprite0HitDetectFlag ;disable sprite 0 check +ExitCAPipe: rts ;leave + +EnterSidePipe: + lda #$08 ;set player's horizontal speed + sta Player_X_Speed + ldy #$01 ;set controller right button by default + lda Player_X_Position ;mask out higher nybble of player's + and #%00001111 ;horizontal position + bne RightPipe + sta Player_X_Speed ;if lower nybble = 0, set as horizontal speed + tay ;and nullify controller bit override here +RightPipe: tya ;use contents of Y to + jsr AutoControlPlayer ;execute player control routine with ctrl bits nulled + rts + +;------------------------------------------------------------------------------------- + +PlayerChangeSize: + lda TimerControl ;check master timer control + cmp #$f8 ;for specific moment in time + bne EndChgSize ;branch if before or after that point + jmp InitChangeSize ;otherwise run code to get growing/shrinking going +EndChgSize: cmp #$c4 ;check again for another specific moment + bne ExitChgSize ;and branch to leave if before or after that point + jsr DonePlayerTask ;otherwise do sub to init timer control and set routine +ExitChgSize: rts ;and then leave + +;------------------------------------------------------------------------------------- + +PlayerInjuryBlink: + lda TimerControl ;check master timer control + cmp #$f0 ;for specific moment in time + bcs ExitBlink ;branch if before that point + cmp #$c8 ;check again for another specific point + beq DonePlayerTask ;branch if at that point, and not before or after + jmp PlayerCtrlRoutine ;otherwise run player control routine +ExitBlink: bne ExitBoth ;do unconditional branch to leave + +InitChangeSize: + ldy PlayerChangeSizeFlag ;if growing/shrinking flag already set + bne ExitBoth ;then branch to leave + sty PlayerAnimCtrl ;otherwise initialize player's animation frame control + inc PlayerChangeSizeFlag ;set growing/shrinking flag + lda PlayerSize + eor #$01 ;invert player's size + sta PlayerSize +ExitBoth: rts ;leave + +;------------------------------------------------------------------------------------- +;$00 - used in CyclePlayerPalette to store current palette to cycle + +PlayerDeath: + lda TimerControl ;check master timer control + cmp #$f0 ;for specific moment in time + bcs ExitDeath ;branch to leave if before that point + jmp PlayerCtrlRoutine ;otherwise run player control routine + +DonePlayerTask: + lda #$00 + sta TimerControl ;initialize master timer control to continue timers + lda #$08 + sta GameEngineSubroutine ;set player control routine to run next frame + rts ;leave + +PlayerFireFlower: + lda TimerControl ;check master timer control + cmp #$c0 ;for specific moment in time + beq ResetPalFireFlower ;branch if at moment, not before or after + lda FrameCounter ;get frame counter + lsr + lsr ;divide by four to change every four frames + +CyclePlayerPalette: + and #$03 ;mask out all but d1-d0 (previously d3-d2) + sta $00 ;store result here to use as palette bits + lda Player_SprAttrib ;get player attributes + and #%11111100 ;save any other bits but palette bits + ora $00 ;add palette bits + sta Player_SprAttrib ;store as new player attributes + rts ;and leave + +ResetPalFireFlower: + jsr DonePlayerTask ;do sub to init timer control and run player control routine + +ResetPalStar: + lda Player_SprAttrib ;get player attributes + and #%11111100 ;mask out palette bits to force palette 0 + sta Player_SprAttrib ;store as new player attributes + rts ;and leave + +ExitDeath: + rts ;leave from death routine + +;------------------------------------------------------------------------------------- + +FlagpoleSlide: + lda Enemy_ID+5 ;check special use enemy slot + cmp #FlagpoleFlagObject ;for flagpole flag object + bne NoFPObj ;if not found, branch to something residual + lda FlagpoleSoundQueue ;load flagpole sound + sta Square1SoundQueue ;into square 1's sfx queue + lda #$00 + sta FlagpoleSoundQueue ;init flagpole sound queue + ldy Player_Y_Position + cpy #$9e ;check to see if player has slid down + bcs SlidePlayer ;far enough, and if so, branch with no controller bits set + lda #$04 ;otherwise force player to climb down (to slide) +SlidePlayer: jmp AutoControlPlayer ;jump to player control routine +NoFPObj: inc GameEngineSubroutine ;increment to next routine (this may + rts ;be residual code) + +;------------------------------------------------------------------------------------- + +Hidden1UpCoinAmts: + .db $15, $23, $16, $1b, $17, $18, $23, $63 + +PlayerEndLevel: + lda #$01 ;force player to walk to the right + jsr AutoControlPlayer + lda Player_Y_Position ;check player's vertical position + cmp #$ae + bcc ChkStop ;if player is not yet off the flagpole, skip this part + lda ScrollLock ;if scroll lock not set, branch ahead to next part + beq ChkStop ;because we only need to do this part once + lda #EndOfLevelMusic + sta EventMusicQueue ;load win level music in event music queue + lda #$00 + sta ScrollLock ;turn off scroll lock to skip this part later +ChkStop: lda Player_CollisionBits ;get player collision bits + lsr ;check for d0 set + bcs RdyNextA ;if d0 set, skip to next part + lda StarFlagTaskControl ;if star flag task control already set, + bne InCastle ;go ahead with the rest of the code + inc StarFlagTaskControl ;otherwise set task control now (this gets ball rolling!) +InCastle: lda #%00100000 ;set player's background priority bit to + sta Player_SprAttrib ;give illusion of being inside the castle +RdyNextA: lda StarFlagTaskControl + cmp #$05 ;if star flag task control not yet set + bne ExitNA ;beyond last valid task number, branch to leave + inc LevelNumber ;increment level number used for game logic + lda LevelNumber + cmp #$03 ;check to see if we have yet reached level -4 + bne NextArea ;and skip this last part here if not + ldy WorldNumber ;get world number as offset + lda CoinTallyFor1Ups ;check third area coin tally for bonus 1-ups + cmp Hidden1UpCoinAmts,y ;against minimum value, if player has not collected + bcc NextArea ;at least this number of coins, leave flag clear + inc Hidden1UpFlag ;otherwise set hidden 1-up box control flag +NextArea: inc AreaNumber ;increment area number used for address loader + jsr LoadAreaPointer ;get new level pointer + inc FetchNewGameTimerFlag ;set flag to load new game timer + jsr ChgAreaMode ;do sub to set secondary mode, disable screen and sprite 0 + sta HalfwayPage ;reset halfway page to 0 (beginning) + lda #Silence + sta EventMusicQueue ;silence music and leave +ExitNA: rts + +;------------------------------------------------------------------------------------- + +PlayerMovementSubs: + lda #$00 ;set A to init crouch flag by default + ldy PlayerSize ;is player small? + bne SetCrouch ;if so, branch + lda Player_State ;check state of player + bne ProcMove ;if not on the ground, branch + lda Up_Down_Buttons ;load controller bits for up and down + and #%00000100 ;single out bit for down button +SetCrouch: sta CrouchingFlag ;store value in crouch flag +ProcMove: jsr PlayerPhysicsSub ;run sub related to jumping and swimming + lda PlayerChangeSizeFlag ;if growing/shrinking flag set, + bne NoMoveSub ;branch to leave + lda Player_State + cmp #$03 ;get player state + beq MoveSubs ;if climbing, branch ahead, leave timer unset + ldy #$18 + sty ClimbSideTimer ;otherwise reset timer now +MoveSubs: jsr JumpEngine + + .dw OnGroundStateSub + .dw JumpSwimSub + .dw FallingSub + .dw ClimbingSub + +NoMoveSub: rts + +;------------------------------------------------------------------------------------- +;$00 - used by ClimbingSub to store high vertical adder + +OnGroundStateSub: + jsr GetPlayerAnimSpeed ;do a sub to set animation frame timing + lda Left_Right_Buttons + beq GndMove ;if left/right controller bits not set, skip instruction + sta PlayerFacingDir ;otherwise set new facing direction +GndMove: jsr ImposeFriction ;do a sub to impose friction on player's walk/run + jsr MovePlayerHorizontally ;do another sub to move player horizontally + sta Player_X_Scroll ;set returned value as player's movement speed for scroll + rts + +;-------------------------------- + +FallingSub: + lda VerticalForceDown + sta VerticalForce ;dump vertical movement force for falling into main one + jmp LRAir ;movement force, then skip ahead to process left/right movement + +;-------------------------------- + +JumpSwimSub: + ldy Player_Y_Speed ;if player's vertical speed zero + bpl DumpFall ;or moving downwards, branch to falling + lda A_B_Buttons + and #A_Button ;check to see if A button is being pressed + and PreviousA_B_Buttons ;and was pressed in previous frame + bne ProcSwim ;if so, branch elsewhere + lda JumpOrigin_Y_Position ;get vertical position player jumped from + sec + sbc Player_Y_Position ;subtract current from original vertical coordinate + cmp DiffToHaltJump ;compare to value set here to see if player is in mid-jump + bcc ProcSwim ;or just starting to jump, if just starting, skip ahead +DumpFall: lda VerticalForceDown ;otherwise dump falling into main fractional + sta VerticalForce +ProcSwim: lda SwimmingFlag ;if swimming flag not set, + beq LRAir ;branch ahead to last part + jsr GetPlayerAnimSpeed ;do a sub to get animation frame timing + lda Player_Y_Position + cmp #$14 ;check vertical position against preset value + bcs LRWater ;if not yet reached a certain position, branch ahead + lda #$18 + sta VerticalForce ;otherwise set fractional +LRWater: lda Left_Right_Buttons ;check left/right controller bits (check for swimming) + beq LRAir ;if not pressing any, skip + sta PlayerFacingDir ;otherwise set facing direction accordingly +LRAir: lda Left_Right_Buttons ;check left/right controller bits (check for jumping/falling) + beq JSMove ;if not pressing any, skip + jsr ImposeFriction ;otherwise process horizontal movement +JSMove: jsr MovePlayerHorizontally ;do a sub to move player horizontally + sta Player_X_Scroll ;set player's speed here, to be used for scroll later + lda GameEngineSubroutine + cmp #$0b ;check for specific routine selected + bne ExitMov1 ;branch if not set to run + lda #$28 + sta VerticalForce ;otherwise set fractional +ExitMov1: jmp MovePlayerVertically ;jump to move player vertically, then leave + +;-------------------------------- + +ClimbAdderLow: + .db $0e, $04, $fc, $f2 +ClimbAdderHigh: + .db $00, $00, $ff, $ff + +ClimbingSub: + lda Player_YMF_Dummy + clc ;add movement force to dummy variable + adc Player_Y_MoveForce ;save with carry + sta Player_YMF_Dummy + ldy #$00 ;set default adder here + lda Player_Y_Speed ;get player's vertical speed + bpl MoveOnVine ;if not moving upwards, branch + dey ;otherwise set adder to $ff +MoveOnVine: sty $00 ;store adder here + adc Player_Y_Position ;add carry to player's vertical position + sta Player_Y_Position ;and store to move player up or down + lda Player_Y_HighPos + adc $00 ;add carry to player's page location + sta Player_Y_HighPos ;and store + lda Left_Right_Buttons ;compare left/right controller bits + and Player_CollisionBits ;to collision flag + beq InitCSTimer ;if not set, skip to end + ldy ClimbSideTimer ;otherwise check timer + bne ExitCSub ;if timer not expired, branch to leave + ldy #$18 + sty ClimbSideTimer ;otherwise set timer now + ldx #$00 ;set default offset here + ldy PlayerFacingDir ;get facing direction + lsr ;move right button controller bit to carry + bcs ClimbFD ;if controller right pressed, branch ahead + inx + inx ;otherwise increment offset by 2 bytes +ClimbFD: dey ;check to see if facing right + beq CSetFDir ;if so, branch, do not increment + inx ;otherwise increment by 1 byte +CSetFDir: lda Player_X_Position + clc ;add or subtract from player's horizontal position + adc ClimbAdderLow,x ;using value here as adder and X as offset + sta Player_X_Position + lda Player_PageLoc ;add or subtract carry or borrow using value here + adc ClimbAdderHigh,x ;from the player's page location + sta Player_PageLoc + lda Left_Right_Buttons ;get left/right controller bits again + eor #%00000011 ;invert them and store them while player + sta PlayerFacingDir ;is on vine to face player in opposite direction +ExitCSub: rts ;then leave +InitCSTimer: sta ClimbSideTimer ;initialize timer here + rts + +;------------------------------------------------------------------------------------- +;$00 - used to store offset to friction data + +JumpMForceData: + .db $20, $20, $1e, $28, $28, $0d, $04 + +FallMForceData: + .db $70, $70, $60, $90, $90, $0a, $09 + +PlayerYSpdData: + .db $fc, $fc, $fc, $fb, $fb, $fe, $ff + +InitMForceData: + .db $00, $00, $00, $00, $00, $80, $00 + +MaxLeftXSpdData: + .db $d8, $e8, $f0 + +MaxRightXSpdData: + .db $28, $18, $10 + .db $0c ;used for pipe intros + +FrictionData: + .db $e4, $98, $d0 + +Climb_Y_SpeedData: + .db $00, $ff, $01 + +Climb_Y_MForceData: + .db $00, $20, $ff + +PlayerPhysicsSub: + lda Player_State ;check player state + cmp #$03 + bne CheckForJumping ;if not climbing, branch + ldy #$00 + lda Up_Down_Buttons ;get controller bits for up/down + and Player_CollisionBits ;check against player's collision detection bits + beq ProcClimb ;if not pressing up or down, branch + iny + and #%00001000 ;check for pressing up + bne ProcClimb + iny +ProcClimb: ldx Climb_Y_MForceData,y ;load value here + stx Player_Y_MoveForce ;store as vertical movement force + lda #$08 ;load default animation timing + ldx Climb_Y_SpeedData,y ;load some other value here + stx Player_Y_Speed ;store as vertical speed + bmi SetCAnim ;if climbing down, use default animation timing value + lsr ;otherwise divide timer setting by 2 +SetCAnim: sta PlayerAnimTimerSet ;store animation timer setting and leave + rts + +CheckForJumping: + lda JumpspringAnimCtrl ;if jumpspring animating, + bne NoJump ;skip ahead to something else + lda A_B_Buttons ;check for A button press + and #A_Button + beq NoJump ;if not, branch to something else + and PreviousA_B_Buttons ;if button not pressed in previous frame, branch + beq ProcJumping +NoJump: jmp X_Physics ;otherwise, jump to something else + +ProcJumping: + lda Player_State ;check player state + beq InitJS ;if on the ground, branch + lda SwimmingFlag ;if swimming flag not set, jump to do something else + beq NoJump ;to prevent midair jumping, otherwise continue + lda JumpSwimTimer ;if jump/swim timer nonzero, branch + bne InitJS + lda Player_Y_Speed ;check player's vertical speed + bpl InitJS ;if player's vertical speed motionless or down, branch + jmp X_Physics ;if timer at zero and player still rising, do not swim +InitJS: lda #$20 ;set jump/swim timer + sta JumpSwimTimer + ldy #$00 ;initialize vertical force and dummy variable + sty Player_YMF_Dummy + sty Player_Y_MoveForce + lda Player_Y_HighPos ;get vertical high and low bytes of jump origin + sta JumpOrigin_Y_HighPos ;and store them next to each other here + lda Player_Y_Position + sta JumpOrigin_Y_Position + lda #$01 ;set player state to jumping/swimming + sta Player_State + lda Player_XSpeedAbsolute ;check value related to walking/running speed + cmp #$09 + bcc ChkWtr ;branch if below certain values, increment Y + iny ;for each amount equal or exceeded + cmp #$10 + bcc ChkWtr + iny + cmp #$19 + bcc ChkWtr + iny + cmp #$1c + bcc ChkWtr ;note that for jumping, range is 0-4 for Y + iny +ChkWtr: lda #$01 ;set value here (apparently always set to 1) + sta DiffToHaltJump + lda SwimmingFlag ;if swimming flag disabled, branch + beq GetYPhy + ldy #$05 ;otherwise set Y to 5, range is 5-6 + lda Whirlpool_Flag ;if whirlpool flag not set, branch + beq GetYPhy + iny ;otherwise increment to 6 +GetYPhy: lda JumpMForceData,y ;store appropriate jump/swim + sta VerticalForce ;data here + lda FallMForceData,y + sta VerticalForceDown + lda InitMForceData,y + sta Player_Y_MoveForce + lda PlayerYSpdData,y + sta Player_Y_Speed + lda SwimmingFlag ;if swimming flag disabled, branch + beq PJumpSnd + lda #Sfx_EnemyStomp ;load swim/goomba stomp sound into + sta Square1SoundQueue ;square 1's sfx queue + lda Player_Y_Position + cmp #$14 ;check vertical low byte of player position + bcs X_Physics ;if below a certain point, branch + lda #$00 ;otherwise reset player's vertical speed + sta Player_Y_Speed ;and jump to something else to keep player + jmp X_Physics ;from swimming above water level +PJumpSnd: lda #Sfx_BigJump ;load big mario's jump sound by default + ldy PlayerSize ;is mario big? + beq SJumpSnd + lda #Sfx_SmallJump ;if not, load small mario's jump sound +SJumpSnd: sta Square1SoundQueue ;store appropriate jump sound in square 1 sfx queue +X_Physics: ldy #$00 + sty $00 ;init value here + lda Player_State ;if mario is on the ground, branch + beq ProcPRun + lda Player_XSpeedAbsolute ;check something that seems to be related + cmp #$19 ;to mario's speed + bcs GetXPhy ;if =>$19 branch here + bcc ChkRFast ;if not branch elsewhere +ProcPRun: iny ;if mario on the ground, increment Y + lda AreaType ;check area type + beq ChkRFast ;if water type, branch + dey ;decrement Y by default for non-water type area + lda Left_Right_Buttons ;get left/right controller bits + cmp Player_MovingDir ;check against moving direction + bne ChkRFast ;if controller bits <> moving direction, skip this part + lda A_B_Buttons ;check for b button pressed + and #B_Button + bne SetRTmr ;if pressed, skip ahead to set timer + lda RunningTimer ;check for running timer set + bne GetXPhy ;if set, branch +ChkRFast: iny ;if running timer not set or level type is water, + inc $00 ;increment Y again and temp variable in memory + lda RunningSpeed + bne FastXSp ;if running speed set here, branch + lda Player_XSpeedAbsolute + cmp #$21 ;otherwise check player's walking/running speed + bcc GetXPhy ;if less than a certain amount, branch ahead +FastXSp: inc $00 ;if running speed set or speed => $21 increment $00 + jmp GetXPhy ;and jump ahead +SetRTmr: lda #$0a ;if b button pressed, set running timer + sta RunningTimer +GetXPhy: lda MaxLeftXSpdData,y ;get maximum speed to the left + sta MaximumLeftSpeed + lda GameEngineSubroutine ;check for specific routine running + cmp #$07 ;(player entrance) + bne GetXPhy2 ;if not running, skip and use old value of Y + ldy #$03 ;otherwise set Y to 3 +GetXPhy2: lda MaxRightXSpdData,y ;get maximum speed to the right + sta MaximumRightSpeed + ldy $00 ;get other value in memory + lda FrictionData,y ;get value using value in memory as offset + sta FrictionAdderLow + lda #$00 + sta FrictionAdderHigh ;init something here + lda PlayerFacingDir + cmp Player_MovingDir ;check facing direction against moving direction + beq ExitPhy ;if the same, branch to leave + asl FrictionAdderLow ;otherwise multiply friction by 2 + rol FrictionAdderHigh ;then leave +ExitPhy: rts + +;------------------------------------------------------------------------------------- + +PlayerAnimTmrData: + .db $02, $04, $07 + +GetPlayerAnimSpeed: + ldy #$00 ;initialize offset in Y + lda Player_XSpeedAbsolute ;check player's walking/running speed + cmp #$1c ;against preset amount + bcs SetRunSpd ;if greater than a certain amount, branch ahead + iny ;otherwise increment Y + cmp #$0e ;compare against lower amount + bcs ChkSkid ;if greater than this but not greater than first, skip increment + iny ;otherwise increment Y again +ChkSkid: lda SavedJoypadBits ;get controller bits + and #%01111111 ;mask out A button + beq SetAnimSpd ;if no other buttons pressed, branch ahead of all this + and #$03 ;mask out all others except left and right + cmp Player_MovingDir ;check against moving direction + bne ProcSkid ;if left/right controller bits <> moving direction, branch + lda #$00 ;otherwise set zero value here +SetRunSpd: sta RunningSpeed ;store zero or running speed here + jmp SetAnimSpd +ProcSkid: lda Player_XSpeedAbsolute ;check player's walking/running speed + cmp #$0b ;against one last amount + bcs SetAnimSpd ;if greater than this amount, branch + lda PlayerFacingDir + sta Player_MovingDir ;otherwise use facing direction to set moving direction + lda #$00 + sta Player_X_Speed ;nullify player's horizontal speed + sta Player_X_MoveForce ;and dummy variable for player +SetAnimSpd: lda PlayerAnimTmrData,y ;get animation timer setting using Y as offset + sta PlayerAnimTimerSet + rts + +;------------------------------------------------------------------------------------- + +ImposeFriction: + and Player_CollisionBits ;perform AND between left/right controller bits and collision flag + cmp #$00 ;then compare to zero (this instruction is redundant) + bne JoypFrict ;if any bits set, branch to next part + lda Player_X_Speed + beq SetAbsSpd ;if player has no horizontal speed, branch ahead to last part + bpl RghtFrict ;if player moving to the right, branch to slow + bmi LeftFrict ;otherwise logic dictates player moving left, branch to slow +JoypFrict: lsr ;put right controller bit into carry + bcc RghtFrict ;if left button pressed, carry = 0, thus branch +LeftFrict: lda Player_X_MoveForce ;load value set here + clc + adc FrictionAdderLow ;add to it another value set here + sta Player_X_MoveForce ;store here + lda Player_X_Speed + adc FrictionAdderHigh ;add value plus carry to horizontal speed + sta Player_X_Speed ;set as new horizontal speed + cmp MaximumRightSpeed ;compare against maximum value for right movement + bmi XSpdSign ;if horizontal speed greater negatively, branch + lda MaximumRightSpeed ;otherwise set preset value as horizontal speed + sta Player_X_Speed ;thus slowing the player's left movement down + jmp SetAbsSpd ;skip to the end +RghtFrict: lda Player_X_MoveForce ;load value set here + sec + sbc FrictionAdderLow ;subtract from it another value set here + sta Player_X_MoveForce ;store here + lda Player_X_Speed + sbc FrictionAdderHigh ;subtract value plus borrow from horizontal speed + sta Player_X_Speed ;set as new horizontal speed + cmp MaximumLeftSpeed ;compare against maximum value for left movement + bpl XSpdSign ;if horizontal speed greater positively, branch + lda MaximumLeftSpeed ;otherwise set preset value as horizontal speed + sta Player_X_Speed ;thus slowing the player's right movement down +XSpdSign: cmp #$00 ;if player not moving or moving to the right, + bpl SetAbsSpd ;branch and leave horizontal speed value unmodified + eor #$ff + clc ;otherwise get two's compliment to get absolute + adc #$01 ;unsigned walking/running speed +SetAbsSpd: sta Player_XSpeedAbsolute ;store walking/running speed here and leave + rts + +;------------------------------------------------------------------------------------- +;$00 - used to store downward movement force in FireballObjCore +;$02 - used to store maximum vertical speed in FireballObjCore +;$07 - used to store pseudorandom bit in BubbleCheck + +ProcFireball_Bubble: + lda PlayerStatus ;check player's status + cmp #$02 + bcc ProcAirBubbles ;if not fiery, branch + lda A_B_Buttons + and #B_Button ;check for b button pressed + beq ProcFireballs ;branch if not pressed + and PreviousA_B_Buttons + bne ProcFireballs ;if button pressed in previous frame, branch + lda FireballCounter ;load fireball counter + and #%00000001 ;get LSB and use as offset for buffer + tax + lda Fireball_State,x ;load fireball state + bne ProcFireballs ;if not inactive, branch + ldy Player_Y_HighPos ;if player too high or too low, branch + dey + bne ProcFireballs + lda CrouchingFlag ;if player crouching, branch + bne ProcFireballs + lda Player_State ;if player's state = climbing, branch + cmp #$03 + beq ProcFireballs + lda #Sfx_Fireball ;play fireball sound effect + sta Square1SoundQueue + lda #$02 ;load state + sta Fireball_State,x + ldy PlayerAnimTimerSet ;copy animation frame timer setting + sty FireballThrowingTimer ;into fireball throwing timer + dey + sty PlayerAnimTimer ;decrement and store in player's animation timer + inc FireballCounter ;increment fireball counter + +ProcFireballs: + ldx #$00 + jsr FireballObjCore ;process first fireball object + ldx #$01 + jsr FireballObjCore ;process second fireball object, then do air bubbles + +ProcAirBubbles: + lda AreaType ;if not water type level, skip the rest of this + bne BublExit + ldx #$02 ;otherwise load counter and use as offset +BublLoop: stx ObjectOffset ;store offset + jsr BubbleCheck ;check timers and coordinates, create air bubble + jsr RelativeBubblePosition ;get relative coordinates + jsr GetBubbleOffscreenBits ;get offscreen information + jsr DrawBubble ;draw the air bubble + dex + bpl BublLoop ;do this until all three are handled +BublExit: rts ;then leave + +FireballXSpdData: + .db $40, $c0 + +FireballObjCore: + stx ObjectOffset ;store offset as current object + lda Fireball_State,x ;check for d7 = 1 + asl + bcs FireballExplosion ;if so, branch to get relative coordinates and draw explosion + ldy Fireball_State,x ;if fireball inactive, branch to leave + beq NoFBall + dey ;if fireball state set to 1, skip this part and just run it + beq RunFB + lda Player_X_Position ;get player's horizontal position + adc #$04 ;add four pixels and store as fireball's horizontal position + sta Fireball_X_Position,x + lda Player_PageLoc ;get player's page location + adc #$00 ;add carry and store as fireball's page location + sta Fireball_PageLoc,x + lda Player_Y_Position ;get player's vertical position and store + sta Fireball_Y_Position,x + lda #$01 ;set high byte of vertical position + sta Fireball_Y_HighPos,x + ldy PlayerFacingDir ;get player's facing direction + dey ;decrement to use as offset here + lda FireballXSpdData,y ;set horizontal speed of fireball accordingly + sta Fireball_X_Speed,x + lda #$04 ;set vertical speed of fireball + sta Fireball_Y_Speed,x + lda #$07 + sta Fireball_BoundBoxCtrl,x ;set bounding box size control for fireball + dec Fireball_State,x ;decrement state to 1 to skip this part from now on +RunFB: txa ;add 7 to offset to use + clc ;as fireball offset for next routines + adc #$07 + tax + lda #$50 ;set downward movement force here + sta $00 + lda #$03 ;set maximum speed here + sta $02 + lda #$00 + jsr ImposeGravity ;do sub here to impose gravity on fireball and move vertically + jsr MoveObjectHorizontally ;do another sub to move it horizontally + ldx ObjectOffset ;return fireball offset to X + jsr RelativeFireballPosition ;get relative coordinates + jsr GetFireballOffscreenBits ;get offscreen information + jsr GetFireballBoundBox ;get bounding box coordinates + jsr FireballBGCollision ;do fireball to background collision detection + lda FBall_OffscreenBits ;get fireball offscreen bits + and #%11001100 ;mask out certain bits + bne EraseFB ;if any bits still set, branch to kill fireball + jsr FireballEnemyCollision ;do fireball to enemy collision detection and deal with collisions + jmp DrawFireball ;draw fireball appropriately and leave +EraseFB: lda #$00 ;erase fireball state + sta Fireball_State,x +NoFBall: rts ;leave + +FireballExplosion: + jsr RelativeFireballPosition + jmp DrawExplosion_Fireball + +BubbleCheck: + lda PseudoRandomBitReg+1,x ;get part of LSFR + and #$01 + sta $07 ;store pseudorandom bit here + lda Bubble_Y_Position,x ;get vertical coordinate for air bubble + cmp #$f8 ;if offscreen coordinate not set, + bne MoveBubl ;branch to move air bubble + lda AirBubbleTimer ;if air bubble timer not expired, + bne ExitBubl ;branch to leave, otherwise create new air bubble + +SetupBubble: + ldy #$00 ;load default value here + lda PlayerFacingDir ;get player's facing direction + lsr ;move d0 to carry + bcc PosBubl ;branch to use default value if facing left + ldy #$08 ;otherwise load alternate value here +PosBubl: tya ;use value loaded as adder + adc Player_X_Position ;add to player's horizontal position + sta Bubble_X_Position,x ;save as horizontal position for airbubble + lda Player_PageLoc + adc #$00 ;add carry to player's page location + sta Bubble_PageLoc,x ;save as page location for airbubble + lda Player_Y_Position + clc ;add eight pixels to player's vertical position + adc #$08 + sta Bubble_Y_Position,x ;save as vertical position for air bubble + lda #$01 + sta Bubble_Y_HighPos,x ;set vertical high byte for air bubble + ldy $07 ;get pseudorandom bit, use as offset + lda BubbleTimerData,y ;get data for air bubble timer + sta AirBubbleTimer ;set air bubble timer +MoveBubl: ldy $07 ;get pseudorandom bit again, use as offset + lda Bubble_YMF_Dummy,x + sec ;subtract pseudorandom amount from dummy variable + sbc Bubble_MForceData,y + sta Bubble_YMF_Dummy,x ;save dummy variable + lda Bubble_Y_Position,x + sbc #$00 ;subtract borrow from airbubble's vertical coordinate + cmp #$20 ;if below the status bar, + bcs Y_Bubl ;branch to go ahead and use to move air bubble upwards + lda #$f8 ;otherwise set offscreen coordinate +Y_Bubl: sta Bubble_Y_Position,x ;store as new vertical coordinate for air bubble +ExitBubl: rts ;leave + +Bubble_MForceData: + .db $ff, $50 + +BubbleTimerData: + .db $40, $20 + +;------------------------------------------------------------------------------------- + +RunGameTimer: + lda OperMode ;get primary mode of operation + beq ExGTimer ;branch to leave if in title screen mode + lda GameEngineSubroutine + cmp #$08 ;if routine number less than eight running, + bcc ExGTimer ;branch to leave + cmp #$0b ;if running death routine, + beq ExGTimer ;branch to leave + lda Player_Y_HighPos + cmp #$02 ;if player below the screen, + bcs ExGTimer ;branch to leave regardless of level type + lda GameTimerCtrlTimer ;if game timer control not yet expired, + bne ExGTimer ;branch to leave + lda GameTimerDisplay + ora GameTimerDisplay+1 ;otherwise check game timer digits + ora GameTimerDisplay+2 + beq TimeUpOn ;if game timer digits at 000, branch to time-up code + ldy GameTimerDisplay ;otherwise check first digit + dey ;if first digit not on 1, + bne ResGTCtrl ;branch to reset game timer control + lda GameTimerDisplay+1 ;otherwise check second and third digits + ora GameTimerDisplay+2 + bne ResGTCtrl ;if timer not at 100, branch to reset game timer control + lda #TimeRunningOutMusic + sta EventMusicQueue ;otherwise load time running out music +ResGTCtrl: lda #$18 ;reset game timer control + sta GameTimerCtrlTimer + ldy #$23 ;set offset for last digit + lda #$ff ;set value to decrement game timer digit + sta DigitModifier+5 + jsr DigitsMathRoutine ;do sub to decrement game timer slowly + lda #$a4 ;set status nybbles to update game timer display + jmp PrintStatusBarNumbers ;do sub to update the display +TimeUpOn: sta PlayerStatus ;init player status (note A will always be zero here) + jsr ForceInjury ;do sub to kill the player (note player is small here) + inc GameTimerExpiredFlag ;set game timer expiration flag +ExGTimer: rts ;leave + +;------------------------------------------------------------------------------------- + +WarpZoneObject: + lda ScrollLock ;check for scroll lock flag + beq ExGTimer ;branch if not set to leave + lda Player_Y_Position ;check to see if player's vertical coordinate has + and Player_Y_HighPos ;same bits set as in vertical high byte (why?) + bne ExGTimer ;if so, branch to leave + sta ScrollLock ;otherwise nullify scroll lock flag + inc WarpZoneControl ;increment warp zone flag to make warp pipes for warp zone + jmp EraseEnemyObject ;kill this object + +;------------------------------------------------------------------------------------- +;$00 - used in WhirlpoolActivate to store whirlpool length / 2, page location of center of whirlpool +;and also to store movement force exerted on player +;$01 - used in ProcessWhirlpools to store page location of right extent of whirlpool +;and in WhirlpoolActivate to store center of whirlpool +;$02 - used in ProcessWhirlpools to store right extent of whirlpool and in +;WhirlpoolActivate to store maximum vertical speed + +ProcessWhirlpools: + lda AreaType ;check for water type level + bne ExitWh ;branch to leave if not found + sta Whirlpool_Flag ;otherwise initialize whirlpool flag + lda TimerControl ;if master timer control set, + bne ExitWh ;branch to leave + ldy #$04 ;otherwise start with last whirlpool data +WhLoop: lda Whirlpool_LeftExtent,y ;get left extent of whirlpool + clc + adc Whirlpool_Length,y ;add length of whirlpool + sta $02 ;store result as right extent here + lda Whirlpool_PageLoc,y ;get page location + beq NextWh ;if none or page 0, branch to get next data + adc #$00 ;add carry + sta $01 ;store result as page location of right extent here + lda Player_X_Position ;get player's horizontal position + sec + sbc Whirlpool_LeftExtent,y ;subtract left extent + lda Player_PageLoc ;get player's page location + sbc Whirlpool_PageLoc,y ;subtract borrow + bmi NextWh ;if player too far left, branch to get next data + lda $02 ;otherwise get right extent + sec + sbc Player_X_Position ;subtract player's horizontal coordinate + lda $01 ;get right extent's page location + sbc Player_PageLoc ;subtract borrow + bpl WhirlpoolActivate ;if player within right extent, branch to whirlpool code +NextWh: dey ;move onto next whirlpool data + bpl WhLoop ;do this until all whirlpools are checked +ExitWh: rts ;leave + +WhirlpoolActivate: + lda Whirlpool_Length,y ;get length of whirlpool + lsr ;divide by 2 + sta $00 ;save here + lda Whirlpool_LeftExtent,y ;get left extent of whirlpool + clc + adc $00 ;add length divided by 2 + sta $01 ;save as center of whirlpool + lda Whirlpool_PageLoc,y ;get page location + adc #$00 ;add carry + sta $00 ;save as page location of whirlpool center + lda FrameCounter ;get frame counter + lsr ;shift d0 into carry (to run on every other frame) + bcc WhPull ;if d0 not set, branch to last part of code + lda $01 ;get center + sec + sbc Player_X_Position ;subtract player's horizontal coordinate + lda $00 ;get page location of center + sbc Player_PageLoc ;subtract borrow + bpl LeftWh ;if player to the left of center, branch + lda Player_X_Position ;otherwise slowly pull player left, towards the center + sec + sbc #$01 ;subtract one pixel + sta Player_X_Position ;set player's new horizontal coordinate + lda Player_PageLoc + sbc #$00 ;subtract borrow + jmp SetPWh ;jump to set player's new page location +LeftWh: lda Player_CollisionBits ;get player's collision bits + lsr ;shift d0 into carry + bcc WhPull ;if d0 not set, branch + lda Player_X_Position ;otherwise slowly pull player right, towards the center + clc + adc #$01 ;add one pixel + sta Player_X_Position ;set player's new horizontal coordinate + lda Player_PageLoc + adc #$00 ;add carry +SetPWh: sta Player_PageLoc ;set player's new page location +WhPull: lda #$10 + sta $00 ;set vertical movement force + lda #$01 + sta Whirlpool_Flag ;set whirlpool flag to be used later + sta $02 ;also set maximum vertical speed + lsr + tax ;set X for player offset + jmp ImposeGravity ;jump to put whirlpool effect on player vertically, do not return + +;------------------------------------------------------------------------------------- + +FlagpoleScoreMods: + .db $05, $02, $08, $04, $01 + +FlagpoleScoreDigits: + .db $03, $03, $04, $04, $04 + +FlagpoleRoutine: + ldx #$05 ;set enemy object offset + stx ObjectOffset ;to special use slot + lda Enemy_ID,x + cmp #FlagpoleFlagObject ;if flagpole flag not found, + bne ExitFlagP ;branch to leave + lda GameEngineSubroutine + cmp #$04 ;if flagpole slide routine not running, + bne SkipScore ;branch to near the end of code + lda Player_State + cmp #$03 ;if player state not climbing, + bne SkipScore ;branch to near the end of code + lda Enemy_Y_Position,x ;check flagpole flag's vertical coordinate + cmp #$aa ;if flagpole flag down to a certain point, + bcs GiveFPScr ;branch to end the level + lda Player_Y_Position ;check player's vertical coordinate + cmp #$a2 ;if player down to a certain point, + bcs GiveFPScr ;branch to end the level + lda Enemy_YMF_Dummy,x + adc #$ff ;add movement amount to dummy variable + sta Enemy_YMF_Dummy,x ;save dummy variable + lda Enemy_Y_Position,x ;get flag's vertical coordinate + adc #$01 ;add 1 plus carry to move flag, and + sta Enemy_Y_Position,x ;store vertical coordinate + lda FlagpoleFNum_YMFDummy + sec ;subtract movement amount from dummy variable + sbc #$ff + sta FlagpoleFNum_YMFDummy ;save dummy variable + lda FlagpoleFNum_Y_Pos + sbc #$01 ;subtract one plus borrow to move floatey number, + sta FlagpoleFNum_Y_Pos ;and store vertical coordinate here +SkipScore: jmp FPGfx ;jump to skip ahead and draw flag and floatey number +GiveFPScr: ldy FlagpoleScore ;get score offset from earlier (when player touched flagpole) + lda FlagpoleScoreMods,y ;get amount to award player points + ldx FlagpoleScoreDigits,y ;get digit with which to award points + sta DigitModifier,x ;store in digit modifier + jsr AddToScore ;do sub to award player points depending on height of collision + lda #$05 + sta GameEngineSubroutine ;set to run end-of-level subroutine on next frame +FPGfx: jsr GetEnemyOffscreenBits ;get offscreen information + jsr RelativeEnemyPosition ;get relative coordinates + jsr FlagpoleGfxHandler ;draw flagpole flag and floatey number +ExitFlagP: rts + +;------------------------------------------------------------------------------------- + +Jumpspring_Y_PosData: + .db $08, $10, $08, $00 + +JumpspringHandler: + jsr GetEnemyOffscreenBits ;get offscreen information + lda TimerControl ;check master timer control + bne DrawJSpr ;branch to last section if set + lda JumpspringAnimCtrl ;check jumpspring frame control + beq DrawJSpr ;branch to last section if not set + tay + dey ;subtract one from frame control, + tya ;the only way a poor nmos 6502 can + and #%00000010 ;mask out all but d1, original value still in Y + bne DownJSpr ;if set, branch to move player up + inc Player_Y_Position + inc Player_Y_Position ;move player's vertical position down two pixels + jmp PosJSpr ;skip to next part +DownJSpr: dec Player_Y_Position ;move player's vertical position up two pixels + dec Player_Y_Position +PosJSpr: lda Jumpspring_FixedYPos,x ;get permanent vertical position + clc + adc Jumpspring_Y_PosData,y ;add value using frame control as offset + sta Enemy_Y_Position,x ;store as new vertical position + cpy #$01 ;check frame control offset (second frame is $00) + bcc BounceJS ;if offset not yet at third frame ($01), skip to next part + lda A_B_Buttons + and #A_Button ;check saved controller bits for A button press + beq BounceJS ;skip to next part if A not pressed + and PreviousA_B_Buttons ;check for A button pressed in previous frame + bne BounceJS ;skip to next part if so + lda #$f4 + sta JumpspringForce ;otherwise write new jumpspring force here +BounceJS: cpy #$03 ;check frame control offset again + bne DrawJSpr ;skip to last part if not yet at fifth frame ($03) + lda JumpspringForce + sta Player_Y_Speed ;store jumpspring force as player's new vertical speed + lda #$00 + sta JumpspringAnimCtrl ;initialize jumpspring frame control +DrawJSpr: jsr RelativeEnemyPosition ;get jumpspring's relative coordinates + jsr EnemyGfxHandler ;draw jumpspring + jsr OffscreenBoundsCheck ;check to see if we need to kill it + lda JumpspringAnimCtrl ;if frame control at zero, don't bother + beq ExJSpring ;trying to animate it, just leave + lda JumpspringTimer + bne ExJSpring ;if jumpspring timer not expired yet, leave + lda #$04 + sta JumpspringTimer ;otherwise initialize jumpspring timer + inc JumpspringAnimCtrl ;increment frame control to animate jumpspring +ExJSpring: rts ;leave + +;------------------------------------------------------------------------------------- + +Setup_Vine: + lda #VineObject ;load identifier for vine object + sta Enemy_ID,x ;store in buffer + lda #$01 + sta Enemy_Flag,x ;set flag for enemy object buffer + lda Block_PageLoc,y + sta Enemy_PageLoc,x ;copy page location from previous object + lda Block_X_Position,y + sta Enemy_X_Position,x ;copy horizontal coordinate from previous object + lda Block_Y_Position,y + sta Enemy_Y_Position,x ;copy vertical coordinate from previous object + ldy VineFlagOffset ;load vine flag/offset to next available vine slot + bne NextVO ;if set at all, don't bother to store vertical + sta VineStart_Y_Position ;otherwise store vertical coordinate here +NextVO: txa ;store object offset to next available vine slot + sta VineObjOffset,y ;using vine flag as offset + inc VineFlagOffset ;increment vine flag offset + lda #Sfx_GrowVine + sta Square2SoundQueue ;load vine grow sound + rts + +;------------------------------------------------------------------------------------- +;$06-$07 - used as address to block buffer data +;$02 - used as vertical high nybble of block buffer offset + +VineHeightData: + .db $30, $60 + +VineObjectHandler: + cpx #$05 ;check enemy offset for special use slot + bne ExitVH ;if not in last slot, branch to leave + ldy VineFlagOffset + dey ;decrement vine flag in Y, use as offset + lda VineHeight + cmp VineHeightData,y ;if vine has reached certain height, + beq RunVSubs ;branch ahead to skip this part + lda FrameCounter ;get frame counter + lsr ;shift d1 into carry + lsr + bcc RunVSubs ;if d1 not set (2 frames every 4) skip this part + lda Enemy_Y_Position+5 + sbc #$01 ;subtract vertical position of vine + sta Enemy_Y_Position+5 ;one pixel every frame it's time + inc VineHeight ;increment vine height +RunVSubs: lda VineHeight ;if vine still very small, + cmp #$08 ;branch to leave + bcc ExitVH + jsr RelativeEnemyPosition ;get relative coordinates of vine, + jsr GetEnemyOffscreenBits ;and any offscreen bits + ldy #$00 ;initialize offset used in draw vine sub +VDrawLoop: jsr DrawVine ;draw vine + iny ;increment offset + cpy VineFlagOffset ;if offset in Y and offset here + bne VDrawLoop ;do not yet match, loop back to draw more vine + lda Enemy_OffscreenBits + and #%00001100 ;mask offscreen bits + beq WrCMTile ;if none of the saved offscreen bits set, skip ahead + dey ;otherwise decrement Y to get proper offset again +KillVine: ldx VineObjOffset,y ;get enemy object offset for this vine object + jsr EraseEnemyObject ;kill this vine object + dey ;decrement Y + bpl KillVine ;if any vine objects left, loop back to kill it + sta VineFlagOffset ;initialize vine flag/offset + sta VineHeight ;initialize vine height +WrCMTile: lda VineHeight ;check vine height + cmp #$20 ;if vine small (less than 32 pixels tall) + bcc ExitVH ;then branch ahead to leave + ldx #$06 ;set offset in X to last enemy slot + lda #$01 ;set A to obtain horizontal in $04, but we don't care + ldy #$1b ;set Y to offset to get block at ($04, $10) of coordinates + jsr BlockBufferCollision ;do a sub to get block buffer address set, return contents + ldy $02 + cpy #$d0 ;if vertical high nybble offset beyond extent of + bcs ExitVH ;current block buffer, branch to leave, do not write + lda ($06),y ;otherwise check contents of block buffer at + bne ExitVH ;current offset, if not empty, branch to leave + lda #$26 + sta ($06),y ;otherwise, write climbing metatile to block buffer +ExitVH: ldx ObjectOffset ;get enemy object offset and leave + rts + +;------------------------------------------------------------------------------------- + +CannonBitmasks: + .db %00001111, %00000111 + +ProcessCannons: + lda AreaType ;get area type + beq ExCannon ;if water type area, branch to leave + ldx #$02 +ThreeSChk: stx ObjectOffset ;start at third enemy slot + lda Enemy_Flag,x ;check enemy buffer flag + bne Chk_BB ;if set, branch to check enemy + lda PseudoRandomBitReg+1,x ;otherwise get part of LSFR + ldy SecondaryHardMode ;get secondary hard mode flag, use as offset + and CannonBitmasks,y ;mask out bits of LSFR as decided by flag + cmp #$06 ;check to see if lower nybble is above certain value + bcs Chk_BB ;if so, branch to check enemy + tay ;transfer masked contents of LSFR to Y as pseudorandom offset + lda Cannon_PageLoc,y ;get page location + beq Chk_BB ;if not set or on page 0, branch to check enemy + lda Cannon_Timer,y ;get cannon timer + beq FireCannon ;if expired, branch to fire cannon + sbc #$00 ;otherwise subtract borrow (note carry will always be clear here) + sta Cannon_Timer,y ;to count timer down + jmp Chk_BB ;then jump ahead to check enemy + +FireCannon: + lda TimerControl ;if master timer control set, + bne Chk_BB ;branch to check enemy + lda #$0e ;otherwise we start creating one + sta Cannon_Timer,y ;first, reset cannon timer + lda Cannon_PageLoc,y ;get page location of cannon + sta Enemy_PageLoc,x ;save as page location of bullet bill + lda Cannon_X_Position,y ;get horizontal coordinate of cannon + sta Enemy_X_Position,x ;save as horizontal coordinate of bullet bill + lda Cannon_Y_Position,y ;get vertical coordinate of cannon + sec + sbc #$08 ;subtract eight pixels (because enemies are 24 pixels tall) + sta Enemy_Y_Position,x ;save as vertical coordinate of bullet bill + lda #$01 + sta Enemy_Y_HighPos,x ;set vertical high byte of bullet bill + sta Enemy_Flag,x ;set buffer flag + lsr ;shift right once to init A + sta Enemy_State,x ;then initialize enemy's state + lda #$09 + sta Enemy_BoundBoxCtrl,x ;set bounding box size control for bullet bill + lda #BulletBill_CannonVar + sta Enemy_ID,x ;load identifier for bullet bill (cannon variant) + jmp Next3Slt ;move onto next slot +Chk_BB: lda Enemy_ID,x ;check enemy identifier for bullet bill (cannon variant) + cmp #BulletBill_CannonVar + bne Next3Slt ;if not found, branch to get next slot + jsr OffscreenBoundsCheck ;otherwise, check to see if it went offscreen + lda Enemy_Flag,x ;check enemy buffer flag + beq Next3Slt ;if not set, branch to get next slot + jsr GetEnemyOffscreenBits ;otherwise, get offscreen information + jsr BulletBillHandler ;then do sub to handle bullet bill +Next3Slt: dex ;move onto next slot + bpl ThreeSChk ;do this until first three slots are checked +ExCannon: rts ;then leave + +;-------------------------------- + +BulletBillXSpdData: + .db $18, $e8 + +BulletBillHandler: + lda TimerControl ;if master timer control set, + bne RunBBSubs ;branch to run subroutines except movement sub + lda Enemy_State,x + bne ChkDSte ;if bullet bill's state set, branch to check defeated state + lda Enemy_OffscreenBits ;otherwise load offscreen bits + and #%00001100 ;mask out bits + cmp #%00001100 ;check to see if all bits are set + beq KillBB ;if so, branch to kill this object + ldy #$01 ;set to move right by default + jsr PlayerEnemyDiff ;get horizontal difference between player and bullet bill + bmi SetupBB ;if enemy to the left of player, branch + iny ;otherwise increment to move left +SetupBB: sty Enemy_MovingDir,x ;set bullet bill's moving direction + dey ;decrement to use as offset + lda BulletBillXSpdData,y ;get horizontal speed based on moving direction + sta Enemy_X_Speed,x ;and store it + lda $00 ;get horizontal difference + adc #$28 ;add 40 pixels + cmp #$50 ;if less than a certain amount, player is too close + bcc KillBB ;to cannon either on left or right side, thus branch + lda #$01 + sta Enemy_State,x ;otherwise set bullet bill's state + lda #$0a + sta EnemyFrameTimer,x ;set enemy frame timer + lda #Sfx_Blast + sta Square2SoundQueue ;play fireworks/gunfire sound +ChkDSte: lda Enemy_State,x ;check enemy state for d5 set + and #%00100000 + beq BBFly ;if not set, skip to move horizontally + jsr MoveD_EnemyVertically ;otherwise do sub to move bullet bill vertically +BBFly: jsr MoveEnemyHorizontally ;do sub to move bullet bill horizontally +RunBBSubs: jsr GetEnemyOffscreenBits ;get offscreen information + jsr RelativeEnemyPosition ;get relative coordinates + jsr GetEnemyBoundBox ;get bounding box coordinates + jsr PlayerEnemyCollision ;handle player to enemy collisions + jmp EnemyGfxHandler ;draw the bullet bill and leave +KillBB: jsr EraseEnemyObject ;kill bullet bill and leave + rts + +;------------------------------------------------------------------------------------- + +HammerEnemyOfsData: + .db $04, $04, $04, $05, $05, $05 + .db $06, $06, $06 + +HammerXSpdData: + .db $10, $f0 + +SpawnHammerObj: + lda PseudoRandomBitReg+1 ;get pseudorandom bits from + and #%00000111 ;second part of LSFR + bne SetMOfs ;if any bits are set, branch and use as offset + lda PseudoRandomBitReg+1 + and #%00001000 ;get d3 from same part of LSFR +SetMOfs: tay ;use either d3 or d2-d0 for offset here + lda Misc_State,y ;if any values loaded in + bne NoHammer ;$2a-$32 where offset is then leave with carry clear + ldx HammerEnemyOfsData,y ;get offset of enemy slot to check using Y as offset + lda Enemy_Flag,x ;check enemy buffer flag at offset + bne NoHammer ;if buffer flag set, branch to leave with carry clear + ldx ObjectOffset ;get original enemy object offset + txa + sta HammerEnemyOffset,y ;save here + lda #$90 + sta Misc_State,y ;save hammer's state here + lda #$07 + sta Misc_BoundBoxCtrl,y ;set something else entirely, here + sec ;return with carry set + rts +NoHammer: ldx ObjectOffset ;get original enemy object offset + clc ;return with carry clear + rts + +;-------------------------------- +;$00 - used to set downward force +;$01 - used to set upward force (residual) +;$02 - used to set maximum speed + +ProcHammerObj: + lda TimerControl ;if master timer control set + bne RunHSubs ;skip all of this code and go to last subs at the end + lda Misc_State,x ;otherwise get hammer's state + and #%01111111 ;mask out d7 + ldy HammerEnemyOffset,x ;get enemy object offset that spawned this hammer + cmp #$02 ;check hammer's state + beq SetHSpd ;if currently at 2, branch + bcs SetHPos ;if greater than 2, branch elsewhere + txa + clc ;add 13 bytes to use + adc #$0d ;proper misc object + tax ;return offset to X + lda #$10 + sta $00 ;set downward movement force + lda #$0f + sta $01 ;set upward movement force (not used) + lda #$04 + sta $02 ;set maximum vertical speed + lda #$00 ;set A to impose gravity on hammer + jsr ImposeGravity ;do sub to impose gravity on hammer and move vertically + jsr MoveObjectHorizontally ;do sub to move it horizontally + ldx ObjectOffset ;get original misc object offset + jmp RunAllH ;branch to essential subroutines +SetHSpd: lda #$fe + sta Misc_Y_Speed,x ;set hammer's vertical speed + lda Enemy_State,y ;get enemy object state + and #%11110111 ;mask out d3 + sta Enemy_State,y ;store new state + ldx Enemy_MovingDir,y ;get enemy's moving direction + dex ;decrement to use as offset + lda HammerXSpdData,x ;get proper speed to use based on moving direction + ldx ObjectOffset ;reobtain hammer's buffer offset + sta Misc_X_Speed,x ;set hammer's horizontal speed +SetHPos: dec Misc_State,x ;decrement hammer's state + lda Enemy_X_Position,y ;get enemy's horizontal position + clc + adc #$02 ;set position 2 pixels to the right + sta Misc_X_Position,x ;store as hammer's horizontal position + lda Enemy_PageLoc,y ;get enemy's page location + adc #$00 ;add carry + sta Misc_PageLoc,x ;store as hammer's page location + lda Enemy_Y_Position,y ;get enemy's vertical position + sec + sbc #$0a ;move position 10 pixels upward + sta Misc_Y_Position,x ;store as hammer's vertical position + lda #$01 + sta Misc_Y_HighPos,x ;set hammer's vertical high byte + bne RunHSubs ;unconditional branch to skip first routine +RunAllH: jsr PlayerHammerCollision ;handle collisions +RunHSubs: jsr GetMiscOffscreenBits ;get offscreen information + jsr RelativeMiscPosition ;get relative coordinates + jsr GetMiscBoundBox ;get bounding box coordinates + jsr DrawHammer ;draw the hammer + rts ;and we are done here + +;------------------------------------------------------------------------------------- +;$02 - used to store vertical high nybble offset from block buffer routine +;$06 - used to store low byte of block buffer address + +CoinBlock: + jsr FindEmptyMiscSlot ;set offset for empty or last misc object buffer slot + lda Block_PageLoc,x ;get page location of block object + sta Misc_PageLoc,y ;store as page location of misc object + lda Block_X_Position,x ;get horizontal coordinate of block object + ora #$05 ;add 5 pixels + sta Misc_X_Position,y ;store as horizontal coordinate of misc object + lda Block_Y_Position,x ;get vertical coordinate of block object + sbc #$10 ;subtract 16 pixels + sta Misc_Y_Position,y ;store as vertical coordinate of misc object + jmp JCoinC ;jump to rest of code as applies to this misc object + +SetupJumpCoin: + jsr FindEmptyMiscSlot ;set offset for empty or last misc object buffer slot + lda Block_PageLoc2,x ;get page location saved earlier + sta Misc_PageLoc,y ;and save as page location for misc object + lda $06 ;get low byte of block buffer offset + asl + asl ;multiply by 16 to use lower nybble + asl + asl + ora #$05 ;add five pixels + sta Misc_X_Position,y ;save as horizontal coordinate for misc object + lda $02 ;get vertical high nybble offset from earlier + adc #$20 ;add 32 pixels for the status bar + sta Misc_Y_Position,y ;store as vertical coordinate +JCoinC: lda #$fb + sta Misc_Y_Speed,y ;set vertical speed + lda #$01 + sta Misc_Y_HighPos,y ;set vertical high byte + sta Misc_State,y ;set state for misc object + sta Square2SoundQueue ;load coin grab sound + stx ObjectOffset ;store current control bit as misc object offset + jsr GiveOneCoin ;update coin tally on the screen and coin amount variable + inc CoinTallyFor1Ups ;increment coin tally used to activate 1-up block flag + rts + +FindEmptyMiscSlot: + ldy #$08 ;start at end of misc objects buffer +FMiscLoop: lda Misc_State,y ;get misc object state + beq UseMiscS ;branch if none found to use current offset + dey ;decrement offset + cpy #$05 ;do this for three slots + bne FMiscLoop ;do this until all slots are checked + ldy #$08 ;if no empty slots found, use last slot +UseMiscS: sty JumpCoinMiscOffset ;store offset of misc object buffer here (residual) + rts + +;------------------------------------------------------------------------------------- + +MiscObjectsCore: + ldx #$08 ;set at end of misc object buffer +MiscLoop: stx ObjectOffset ;store misc object offset here + lda Misc_State,x ;check misc object state + beq MiscLoopBack ;branch to check next slot + asl ;otherwise shift d7 into carry + bcc ProcJumpCoin ;if d7 not set, jumping coin, thus skip to rest of code here + jsr ProcHammerObj ;otherwise go to process hammer, + jmp MiscLoopBack ;then check next slot + +;-------------------------------- +;$00 - used to set downward force +;$01 - used to set upward force (residual) +;$02 - used to set maximum speed + +ProcJumpCoin: + ldy Misc_State,x ;check misc object state + dey ;decrement to see if it's set to 1 + beq JCoinRun ;if so, branch to handle jumping coin + inc Misc_State,x ;otherwise increment state to either start off or as timer + lda Misc_X_Position,x ;get horizontal coordinate for misc object + clc ;whether its jumping coin (state 0 only) or floatey number + adc ScrollAmount ;add current scroll speed + sta Misc_X_Position,x ;store as new horizontal coordinate + lda Misc_PageLoc,x ;get page location + adc #$00 ;add carry + sta Misc_PageLoc,x ;store as new page location + lda Misc_State,x + cmp #$30 ;check state of object for preset value + bne RunJCSubs ;if not yet reached, branch to subroutines + lda #$00 + sta Misc_State,x ;otherwise nullify object state + jmp MiscLoopBack ;and move onto next slot +JCoinRun: txa + clc ;add 13 bytes to offset for next subroutine + adc #$0d + tax + lda #$50 ;set downward movement amount + sta $00 + lda #$06 ;set maximum vertical speed + sta $02 + lsr ;divide by 2 and set + sta $01 ;as upward movement amount (apparently residual) + lda #$00 ;set A to impose gravity on jumping coin + jsr ImposeGravity ;do sub to move coin vertically and impose gravity on it + ldx ObjectOffset ;get original misc object offset + lda Misc_Y_Speed,x ;check vertical speed + cmp #$05 + bne RunJCSubs ;if not moving downward fast enough, keep state as-is + inc Misc_State,x ;otherwise increment state to change to floatey number +RunJCSubs: jsr RelativeMiscPosition ;get relative coordinates + jsr GetMiscOffscreenBits ;get offscreen information + jsr GetMiscBoundBox ;get bounding box coordinates (why?) + jsr JCoinGfxHandler ;draw the coin or floatey number + +MiscLoopBack: + dex ;decrement misc object offset + bpl MiscLoop ;loop back until all misc objects handled + rts ;then leave + +;------------------------------------------------------------------------------------- + +CoinTallyOffsets: + .db $17, $1d + +ScoreOffsets: + .db $0b, $11 + +StatusBarNybbles: + .db $02, $13 + +GiveOneCoin: + lda #$01 ;set digit modifier to add 1 coin + sta DigitModifier+5 ;to the current player's coin tally + ldx CurrentPlayer ;get current player on the screen + ldy CoinTallyOffsets,x ;get offset for player's coin tally + jsr DigitsMathRoutine ;update the coin tally + inc CoinTally ;increment onscreen player's coin amount + lda CoinTally + cmp #100 ;does player have 100 coins yet? + bne CoinPoints ;if not, skip all of this + lda #$00 + sta CoinTally ;otherwise, reinitialize coin amount + inc NumberofLives ;give the player an extra life + lda #Sfx_ExtraLife + sta Square2SoundQueue ;play 1-up sound + +CoinPoints: + lda #$02 ;set digit modifier to award + sta DigitModifier+4 ;200 points to the player + +AddToScore: + ldx CurrentPlayer ;get current player + ldy ScoreOffsets,x ;get offset for player's score + jsr DigitsMathRoutine ;update the score internally with value in digit modifier + +GetSBNybbles: + ldy CurrentPlayer ;get current player + lda StatusBarNybbles,y ;get nybbles based on player, use to update score and coins + +UpdateNumber: + jsr PrintStatusBarNumbers ;print status bar numbers based on nybbles, whatever they be + ldy VRAM_Buffer1_Offset + lda VRAM_Buffer1-6,y ;check highest digit of score + bne NoZSup ;if zero, overwrite with space tile for zero suppression + lda #$24 + sta VRAM_Buffer1-6,y +NoZSup: ldx ObjectOffset ;get enemy object buffer offset + rts + +;------------------------------------------------------------------------------------- + +SetupPowerUp: + lda #PowerUpObject ;load power-up identifier into + sta Enemy_ID+5 ;special use slot of enemy object buffer + lda Block_PageLoc,x ;store page location of block object + sta Enemy_PageLoc+5 ;as page location of power-up object + lda Block_X_Position,x ;store horizontal coordinate of block object + sta Enemy_X_Position+5 ;as horizontal coordinate of power-up object + lda #$01 + sta Enemy_Y_HighPos+5 ;set vertical high byte of power-up object + lda Block_Y_Position,x ;get vertical coordinate of block object + sec + sbc #$08 ;subtract 8 pixels + sta Enemy_Y_Position+5 ;and use as vertical coordinate of power-up object +PwrUpJmp: lda #$01 ;this is a residual jump point in enemy object jump table + sta Enemy_State+5 ;set power-up object's state + sta Enemy_Flag+5 ;set buffer flag + lda #$03 + sta Enemy_BoundBoxCtrl+5 ;set bounding box size control for power-up object + lda PowerUpType + cmp #$02 ;check currently loaded power-up type + bcs PutBehind ;if star or 1-up, branch ahead + lda PlayerStatus ;otherwise check player's current status + cmp #$02 + bcc StrType ;if player not fiery, use status as power-up type + lsr ;otherwise shift right to force fire flower type +StrType: sta PowerUpType ;store type here +PutBehind: lda #%00100000 + sta Enemy_SprAttrib+5 ;set background priority bit + lda #Sfx_GrowPowerUp + sta Square2SoundQueue ;load power-up reveal sound and leave + rts + +;------------------------------------------------------------------------------------- + +PowerUpObjHandler: + ldx #$05 ;set object offset for last slot in enemy object buffer + stx ObjectOffset + lda Enemy_State+5 ;check power-up object's state + beq ExitPUp ;if not set, branch to leave + asl ;shift to check if d7 was set in object state + bcc GrowThePowerUp ;if not set, branch ahead to skip this part + lda TimerControl ;if master timer control set, + bne RunPUSubs ;branch ahead to enemy object routines + lda PowerUpType ;check power-up type + beq ShroomM ;if normal mushroom, branch ahead to move it + cmp #$03 + beq ShroomM ;if 1-up mushroom, branch ahead to move it + cmp #$02 + bne RunPUSubs ;if not star, branch elsewhere to skip movement + jsr MoveJumpingEnemy ;otherwise impose gravity on star power-up and make it jump + jsr EnemyJump ;note that green paratroopa shares the same code here + jmp RunPUSubs ;then jump to other power-up subroutines +ShroomM: jsr MoveNormalEnemy ;do sub to make mushrooms move + jsr EnemyToBGCollisionDet ;deal with collisions + jmp RunPUSubs ;run the other subroutines + +GrowThePowerUp: + lda FrameCounter ;get frame counter + and #$03 ;mask out all but 2 LSB + bne ChkPUSte ;if any bits set here, branch + dec Enemy_Y_Position+5 ;otherwise decrement vertical coordinate slowly + lda Enemy_State+5 ;load power-up object state + inc Enemy_State+5 ;increment state for next frame (to make power-up rise) + cmp #$11 ;if power-up object state not yet past 16th pixel, + bcc ChkPUSte ;branch ahead to last part here + lda #$10 + sta Enemy_X_Speed,x ;otherwise set horizontal speed + lda #%10000000 + sta Enemy_State+5 ;and then set d7 in power-up object's state + asl ;shift once to init A + sta Enemy_SprAttrib+5 ;initialize background priority bit set here + rol ;rotate A to set right moving direction + sta Enemy_MovingDir,x ;set moving direction +ChkPUSte: lda Enemy_State+5 ;check power-up object's state + cmp #$06 ;for if power-up has risen enough + bcc ExitPUp ;if not, don't even bother running these routines +RunPUSubs: jsr RelativeEnemyPosition ;get coordinates relative to screen + jsr GetEnemyOffscreenBits ;get offscreen bits + jsr GetEnemyBoundBox ;get bounding box coordinates + jsr DrawPowerUp ;draw the power-up object + jsr PlayerEnemyCollision ;check for collision with player + jsr OffscreenBoundsCheck ;check to see if it went offscreen +ExitPUp: rts ;and we're done + +;------------------------------------------------------------------------------------- +;These apply to all routines in this section unless otherwise noted: +;$00 - used to store metatile from block buffer routine +;$02 - used to store vertical high nybble offset from block buffer routine +;$05 - used to store metatile stored in A at beginning of PlayerHeadCollision +;$06-$07 - used as block buffer address indirect + +BlockYPosAdderData: + .db $04, $12 + +PlayerHeadCollision: + pha ;store metatile number to stack + lda #$11 ;load unbreakable block object state by default + ldx SprDataOffset_Ctrl ;load offset control bit here + ldy PlayerSize ;check player's size + bne DBlockSte ;if small, branch + lda #$12 ;otherwise load breakable block object state +DBlockSte: sta Block_State,x ;store into block object buffer + jsr DestroyBlockMetatile ;store blank metatile in vram buffer to write to name table + ldx SprDataOffset_Ctrl ;load offset control bit + lda $02 ;get vertical high nybble offset used in block buffer routine + sta Block_Orig_YPos,x ;set as vertical coordinate for block object + tay + lda $06 ;get low byte of block buffer address used in same routine + sta Block_BBuf_Low,x ;save as offset here to be used later + lda ($06),y ;get contents of block buffer at old address at $06, $07 + jsr BlockBumpedChk ;do a sub to check which block player bumped head on + sta $00 ;store metatile here + ldy PlayerSize ;check player's size + bne ChkBrick ;if small, use metatile itself as contents of A + tya ;otherwise init A (note: big = 0) +ChkBrick: bcc PutMTileB ;if no match was found in previous sub, skip ahead + ldy #$11 ;otherwise load unbreakable state into block object buffer + sty Block_State,x ;note this applies to both player sizes + lda #$c4 ;load empty block metatile into A for now + ldy $00 ;get metatile from before + cpy #$58 ;is it brick with coins (with line)? + beq StartBTmr ;if so, branch + cpy #$5d ;is it brick with coins (without line)? + bne PutMTileB ;if not, branch ahead to store empty block metatile +StartBTmr: lda BrickCoinTimerFlag ;check brick coin timer flag + bne ContBTmr ;if set, timer expired or counting down, thus branch + lda #$0b + sta BrickCoinTimer ;if not set, set brick coin timer + inc BrickCoinTimerFlag ;and set flag linked to it +ContBTmr: lda BrickCoinTimer ;check brick coin timer + bne PutOldMT ;if not yet expired, branch to use current metatile + ldy #$c4 ;otherwise use empty block metatile +PutOldMT: tya ;put metatile into A +PutMTileB: sta Block_Metatile,x ;store whatever metatile be appropriate here + jsr InitBlock_XY_Pos ;get block object horizontal coordinates saved + ldy $02 ;get vertical high nybble offset + lda #$23 + sta ($06),y ;write blank metatile $23 to block buffer + lda #$10 + sta BlockBounceTimer ;set block bounce timer + pla ;pull original metatile from stack + sta $05 ;and save here + ldy #$00 ;set default offset + lda CrouchingFlag ;is player crouching? + bne SmallBP ;if so, branch to increment offset + lda PlayerSize ;is player big? + beq BigBP ;if so, branch to use default offset +SmallBP: iny ;increment for small or big and crouching +BigBP: lda Player_Y_Position ;get player's vertical coordinate + clc + adc BlockYPosAdderData,y ;add value determined by size + and #$f0 ;mask out low nybble to get 16-pixel correspondence + sta Block_Y_Position,x ;save as vertical coordinate for block object + ldy Block_State,x ;get block object state + cpy #$11 + beq Unbreak ;if set to value loaded for unbreakable, branch + jsr BrickShatter ;execute code for breakable brick + jmp InvOBit ;skip subroutine to do last part of code here +Unbreak: jsr BumpBlock ;execute code for unbreakable brick or question block +InvOBit: lda SprDataOffset_Ctrl ;invert control bit used by block objects + eor #$01 ;and floatey numbers + sta SprDataOffset_Ctrl + rts ;leave! + +;-------------------------------- + +InitBlock_XY_Pos: + lda Player_X_Position ;get player's horizontal coordinate + clc + adc #$08 ;add eight pixels + and #$f0 ;mask out low nybble to give 16-pixel correspondence + sta Block_X_Position,x ;save as horizontal coordinate for block object + lda Player_PageLoc + adc #$00 ;add carry to page location of player + sta Block_PageLoc,x ;save as page location of block object + sta Block_PageLoc2,x ;save elsewhere to be used later + lda Player_Y_HighPos + sta Block_Y_HighPos,x ;save vertical high byte of player into + rts ;vertical high byte of block object and leave + +;-------------------------------- + +BumpBlock: + jsr CheckTopOfBlock ;check to see if there's a coin directly above this block + lda #Sfx_Bump + sta Square1SoundQueue ;play bump sound + lda #$00 + sta Block_X_Speed,x ;initialize horizontal speed for block object + sta Block_Y_MoveForce,x ;init fractional movement force + sta Player_Y_Speed ;init player's vertical speed + lda #$fe + sta Block_Y_Speed,x ;set vertical speed for block object + lda $05 ;get original metatile from stack + jsr BlockBumpedChk ;do a sub to check which block player bumped head on + bcc ExitBlockChk ;if no match was found, branch to leave + tya ;move block number to A + cmp #$09 ;if block number was within 0-8 range, + bcc BlockCode ;branch to use current number + sbc #$05 ;otherwise subtract 5 for second set to get proper number +BlockCode: jsr JumpEngine ;run appropriate subroutine depending on block number + + .dw MushFlowerBlock + .dw CoinBlock + .dw CoinBlock + .dw ExtraLifeMushBlock + .dw MushFlowerBlock + .dw VineBlock + .dw StarBlock + .dw CoinBlock + .dw ExtraLifeMushBlock + +;-------------------------------- + +MushFlowerBlock: + lda #$00 ;load mushroom/fire flower into power-up type + .db $2c ;BIT instruction opcode + +StarBlock: + lda #$02 ;load star into power-up type + .db $2c ;BIT instruction opcode + +ExtraLifeMushBlock: + lda #$03 ;load 1-up mushroom into power-up type + sta $39 ;store correct power-up type + jmp SetupPowerUp + +VineBlock: + ldx #$05 ;load last slot for enemy object buffer + ldy SprDataOffset_Ctrl ;get control bit + jsr Setup_Vine ;set up vine object + +ExitBlockChk: + rts ;leave + +;-------------------------------- + +BrickQBlockMetatiles: + .db $c1, $c0, $5f, $60 ;used by question blocks + + ;these two sets are functionally identical, but look different + .db $55, $56, $57, $58, $59 ;used by ground level types + .db $5a, $5b, $5c, $5d, $5e ;used by other level types + +BlockBumpedChk: + ldy #$0d ;start at end of metatile data +BumpChkLoop: cmp BrickQBlockMetatiles,y ;check to see if current metatile matches + beq MatchBump ;metatile found in block buffer, branch if so + dey ;otherwise move onto next metatile + bpl BumpChkLoop ;do this until all metatiles are checked + clc ;if none match, return with carry clear +MatchBump: rts ;note carry is set if found match + +;-------------------------------- + +BrickShatter: + jsr CheckTopOfBlock ;check to see if there's a coin directly above this block + lda #Sfx_BrickShatter + sta Block_RepFlag,x ;set flag for block object to immediately replace metatile + sta NoiseSoundQueue ;load brick shatter sound + jsr SpawnBrickChunks ;create brick chunk objects + lda #$fe + sta Player_Y_Speed ;set vertical speed for player + lda #$05 + sta DigitModifier+5 ;set digit modifier to give player 50 points + jsr AddToScore ;do sub to update the score + ldx SprDataOffset_Ctrl ;load control bit and leave + rts + +;-------------------------------- + +CheckTopOfBlock: + ldx SprDataOffset_Ctrl ;load control bit + ldy $02 ;get vertical high nybble offset used in block buffer + beq TopEx ;branch to leave if set to zero, because we're at the top + tya ;otherwise set to A + sec + sbc #$10 ;subtract $10 to move up one row in the block buffer + sta $02 ;store as new vertical high nybble offset + tay + lda ($06),y ;get contents of block buffer in same column, one row up + cmp #$c2 ;is it a coin? (not underwater) + bne TopEx ;if not, branch to leave + lda #$00 + sta ($06),y ;otherwise put blank metatile where coin was + jsr RemoveCoin_Axe ;write blank metatile to vram buffer + ldx SprDataOffset_Ctrl ;get control bit + jsr SetupJumpCoin ;create jumping coin object and update coin variables +TopEx: rts ;leave! + +;-------------------------------- + +SpawnBrickChunks: + lda Block_X_Position,x ;set horizontal coordinate of block object + sta Block_Orig_XPos,x ;as original horizontal coordinate here + lda #$f0 + sta Block_X_Speed,x ;set horizontal speed for brick chunk objects + sta Block_X_Speed+2,x + lda #$fa + sta Block_Y_Speed,x ;set vertical speed for one + lda #$fc + sta Block_Y_Speed+2,x ;set lower vertical speed for the other + lda #$00 + sta Block_Y_MoveForce,x ;init fractional movement force for both + sta Block_Y_MoveForce+2,x + lda Block_PageLoc,x + sta Block_PageLoc+2,x ;copy page location + lda Block_X_Position,x + sta Block_X_Position+2,x ;copy horizontal coordinate + lda Block_Y_Position,x + clc ;add 8 pixels to vertical coordinate + adc #$08 ;and save as vertical coordinate for one of them + sta Block_Y_Position+2,x + lda #$fa + sta Block_Y_Speed,x ;set vertical speed...again??? (redundant) + rts + +;------------------------------------------------------------------------------------- + +BlockObjectsCore: + lda Block_State,x ;get state of block object + beq UpdSte ;if not set, branch to leave + and #$0f ;mask out high nybble + pha ;push to stack + tay ;put in Y for now + txa + clc + adc #$09 ;add 9 bytes to offset (note two block objects are created + tax ;when using brick chunks, but only one offset for both) + dey ;decrement Y to check for solid block state + beq BouncingBlockHandler ;branch if found, otherwise continue for brick chunks + jsr ImposeGravityBlock ;do sub to impose gravity on one block object object + jsr MoveObjectHorizontally ;do another sub to move horizontally + txa + clc ;move onto next block object + adc #$02 + tax + jsr ImposeGravityBlock ;do sub to impose gravity on other block object + jsr MoveObjectHorizontally ;do another sub to move horizontally + ldx ObjectOffset ;get block object offset used for both + jsr RelativeBlockPosition ;get relative coordinates + jsr GetBlockOffscreenBits ;get offscreen information + jsr DrawBrickChunks ;draw the brick chunks + pla ;get lower nybble of saved state + ldy Block_Y_HighPos,x ;check vertical high byte of block object + beq UpdSte ;if above the screen, branch to kill it + pha ;otherwise save state back into stack + lda #$f0 + cmp Block_Y_Position+2,x ;check to see if bottom block object went + bcs ChkTop ;to the bottom of the screen, and branch if not + sta Block_Y_Position+2,x ;otherwise set offscreen coordinate +ChkTop: lda Block_Y_Position,x ;get top block object's vertical coordinate + cmp #$f0 ;see if it went to the bottom of the screen + pla ;pull block object state from stack + bcc UpdSte ;if not, branch to save state + bcs KillBlock ;otherwise do unconditional branch to kill it + +BouncingBlockHandler: + jsr ImposeGravityBlock ;do sub to impose gravity on block object + ldx ObjectOffset ;get block object offset + jsr RelativeBlockPosition ;get relative coordinates + jsr GetBlockOffscreenBits ;get offscreen information + jsr DrawBlock ;draw the block + lda Block_Y_Position,x ;get vertical coordinate + and #$0f ;mask out high nybble + cmp #$05 ;check to see if low nybble wrapped around + pla ;pull state from stack + bcs UpdSte ;if still above amount, not time to kill block yet, thus branch + lda #$01 + sta Block_RepFlag,x ;otherwise set flag to replace metatile +KillBlock: lda #$00 ;if branched here, nullify object state +UpdSte: sta Block_State,x ;store contents of A in block object state + rts + +;------------------------------------------------------------------------------------- +;$02 - used to store offset to block buffer +;$06-$07 - used to store block buffer address + +BlockObjMT_Updater: + ldx #$01 ;set offset to start with second block object +UpdateLoop: stx ObjectOffset ;set offset here + lda VRAM_Buffer1 ;if vram buffer already being used here, + bne NextBUpd ;branch to move onto next block object + lda Block_RepFlag,x ;if flag for block object already clear, + beq NextBUpd ;branch to move onto next block object + lda Block_BBuf_Low,x ;get low byte of block buffer + sta $06 ;store into block buffer address + lda #$05 + sta $07 ;set high byte of block buffer address + lda Block_Orig_YPos,x ;get original vertical coordinate of block object + sta $02 ;store here and use as offset to block buffer + tay + lda Block_Metatile,x ;get metatile to be written + sta ($06),y ;write it to the block buffer + jsr ReplaceBlockMetatile ;do sub to replace metatile where block object is + lda #$00 + sta Block_RepFlag,x ;clear block object flag +NextBUpd: dex ;decrement block object offset + bpl UpdateLoop ;do this until both block objects are dealt with + rts ;then leave + +;------------------------------------------------------------------------------------- +;$00 - used to store high nybble of horizontal speed as adder +;$01 - used to store low nybble of horizontal speed +;$02 - used to store adder to page location + +MoveEnemyHorizontally: + inx ;increment offset for enemy offset + jsr MoveObjectHorizontally ;position object horizontally according to + ldx ObjectOffset ;counters, return with saved value in A, + rts ;put enemy offset back in X and leave + +MovePlayerHorizontally: + lda JumpspringAnimCtrl ;if jumpspring currently animating, + bne ExXMove ;branch to leave + tax ;otherwise set zero for offset to use player's stuff + +MoveObjectHorizontally: + lda SprObject_X_Speed,x ;get currently saved value (horizontal + asl ;speed, secondary counter, whatever) + asl ;and move low nybble to high + asl + asl + sta $01 ;store result here + lda SprObject_X_Speed,x ;get saved value again + lsr ;move high nybble to low + lsr + lsr + lsr + cmp #$08 ;if < 8, branch, do not change + bcc SaveXSpd + ora #%11110000 ;otherwise alter high nybble +SaveXSpd: sta $00 ;save result here + ldy #$00 ;load default Y value here + cmp #$00 ;if result positive, leave Y alone + bpl UseAdder + dey ;otherwise decrement Y +UseAdder: sty $02 ;save Y here + lda SprObject_X_MoveForce,x ;get whatever number's here + clc + adc $01 ;add low nybble moved to high + sta SprObject_X_MoveForce,x ;store result here + lda #$00 ;init A + rol ;rotate carry into d0 + pha ;push onto stack + ror ;rotate d0 back onto carry + lda SprObject_X_Position,x + adc $00 ;add carry plus saved value (high nybble moved to low + sta SprObject_X_Position,x ;plus $f0 if necessary) to object's horizontal position + lda SprObject_PageLoc,x + adc $02 ;add carry plus other saved value to the + sta SprObject_PageLoc,x ;object's page location and save + pla + clc ;pull old carry from stack and add + adc $00 ;to high nybble moved to low +ExXMove: rts ;and leave + +;------------------------------------------------------------------------------------- +;$00 - used for downward force +;$01 - used for upward force +;$02 - used for maximum vertical speed + +MovePlayerVertically: + ldx #$00 ;set X for player offset + lda TimerControl + bne NoJSChk ;if master timer control set, branch ahead + lda JumpspringAnimCtrl ;otherwise check to see if jumpspring is animating + bne ExXMove ;branch to leave if so +NoJSChk: lda VerticalForce ;dump vertical force + sta $00 + lda #$04 ;set maximum vertical speed here + jmp ImposeGravitySprObj ;then jump to move player vertically + +;-------------------------------- + +MoveD_EnemyVertically: + ldy #$3d ;set quick movement amount downwards + lda Enemy_State,x ;then check enemy state + cmp #$05 ;if not set to unique state for spiny's egg, go ahead + bne ContVMove ;and use, otherwise set different movement amount, continue on + +MoveFallingPlatform: + ldy #$20 ;set movement amount +ContVMove: jmp SetHiMax ;jump to skip the rest of this + +;-------------------------------- + +MoveRedPTroopaDown: + ldy #$00 ;set Y to move downwards + jmp MoveRedPTroopa ;skip to movement routine + +MoveRedPTroopaUp: + ldy #$01 ;set Y to move upwards + +MoveRedPTroopa: + inx ;increment X for enemy offset + lda #$03 + sta $00 ;set downward movement amount here + lda #$06 + sta $01 ;set upward movement amount here + lda #$02 + sta $02 ;set maximum speed here + tya ;set movement direction in A, and + jmp RedPTroopaGrav ;jump to move this thing + +;-------------------------------- + +MoveDropPlatform: + ldy #$7f ;set movement amount for drop platform + bne SetMdMax ;skip ahead of other value set here + +MoveEnemySlowVert: + ldy #$0f ;set movement amount for bowser/other objects +SetMdMax: lda #$02 ;set maximum speed in A + bne SetXMoveAmt ;unconditional branch + +;-------------------------------- + +MoveJ_EnemyVertically: + ldy #$1c ;set movement amount for podoboo/other objects +SetHiMax: lda #$03 ;set maximum speed in A +SetXMoveAmt: sty $00 ;set movement amount here + inx ;increment X for enemy offset + jsr ImposeGravitySprObj ;do a sub to move enemy object downwards + ldx ObjectOffset ;get enemy object buffer offset and leave + rts + +;-------------------------------- + +MaxSpdBlockData: + .db $06, $08 + +ResidualGravityCode: + ldy #$00 ;this part appears to be residual, + .db $2c ;no code branches or jumps to it... + +ImposeGravityBlock: + ldy #$01 ;set offset for maximum speed + lda #$50 ;set movement amount here + sta $00 + lda MaxSpdBlockData,y ;get maximum speed + +ImposeGravitySprObj: + sta $02 ;set maximum speed here + lda #$00 ;set value to move downwards + jmp ImposeGravity ;jump to the code that actually moves it + +;-------------------------------- + +MovePlatformDown: + lda #$00 ;save value to stack (if branching here, execute next + .db $2c ;part as BIT instruction) + +MovePlatformUp: + lda #$01 ;save value to stack + pha + ldy Enemy_ID,x ;get enemy object identifier + inx ;increment offset for enemy object + lda #$05 ;load default value here + cpy #$29 ;residual comparison, object #29 never executes + bne SetDplSpd ;this code, thus unconditional branch here + lda #$09 ;residual code +SetDplSpd: sta $00 ;save downward movement amount here + lda #$0a ;save upward movement amount here + sta $01 + lda #$03 ;save maximum vertical speed here + sta $02 + pla ;get value from stack + tay ;use as Y, then move onto code shared by red koopa + +RedPTroopaGrav: + jsr ImposeGravity ;do a sub to move object gradually + ldx ObjectOffset ;get enemy object offset and leave + rts + +;------------------------------------------------------------------------------------- +;$00 - used for downward force +;$01 - used for upward force +;$07 - used as adder for vertical position + +ImposeGravity: + pha ;push value to stack + lda SprObject_YMF_Dummy,x + clc ;add value in movement force to contents of dummy variable + adc SprObject_Y_MoveForce,x + sta SprObject_YMF_Dummy,x + ldy #$00 ;set Y to zero by default + lda SprObject_Y_Speed,x ;get current vertical speed + bpl AlterYP ;if currently moving downwards, do not decrement Y + dey ;otherwise decrement Y +AlterYP: sty $07 ;store Y here + adc SprObject_Y_Position,x ;add vertical position to vertical speed plus carry + sta SprObject_Y_Position,x ;store as new vertical position + lda SprObject_Y_HighPos,x + adc $07 ;add carry plus contents of $07 to vertical high byte + sta SprObject_Y_HighPos,x ;store as new vertical high byte + lda SprObject_Y_MoveForce,x + clc + adc $00 ;add downward movement amount to contents of $0433 + sta SprObject_Y_MoveForce,x + lda SprObject_Y_Speed,x ;add carry to vertical speed and store + adc #$00 + sta SprObject_Y_Speed,x + cmp $02 ;compare to maximum speed + bmi ChkUpM ;if less than preset value, skip this part + lda SprObject_Y_MoveForce,x + cmp #$80 ;if less positively than preset maximum, skip this part + bcc ChkUpM + lda $02 + sta SprObject_Y_Speed,x ;keep vertical speed within maximum value + lda #$00 + sta SprObject_Y_MoveForce,x ;clear fractional +ChkUpM: pla ;get value from stack + beq ExVMove ;if set to zero, branch to leave + lda $02 + eor #%11111111 ;otherwise get two's compliment of maximum speed + tay + iny + sty $07 ;store two's compliment here + lda SprObject_Y_MoveForce,x + sec ;subtract upward movement amount from contents + sbc $01 ;of movement force, note that $01 is twice as large as $00, + sta SprObject_Y_MoveForce,x ;thus it effectively undoes add we did earlier + lda SprObject_Y_Speed,x + sbc #$00 ;subtract borrow from vertical speed and store + sta SprObject_Y_Speed,x + cmp $07 ;compare vertical speed to two's compliment + bpl ExVMove ;if less negatively than preset maximum, skip this part + lda SprObject_Y_MoveForce,x + cmp #$80 ;check if fractional part is above certain amount, + bcs ExVMove ;and if so, branch to leave + lda $07 + sta SprObject_Y_Speed,x ;keep vertical speed within maximum value + lda #$ff + sta SprObject_Y_MoveForce,x ;clear fractional +ExVMove: rts ;leave! + +;------------------------------------------------------------------------------------- + +EnemiesAndLoopsCore: + lda Enemy_Flag,x ;check data here for MSB set + pha ;save in stack + asl + bcs ChkBowserF ;if MSB set in enemy flag, branch ahead of jumps + pla ;get from stack + beq ChkAreaTsk ;if data zero, branch + jmp RunEnemyObjectsCore ;otherwise, jump to run enemy subroutines +ChkAreaTsk: lda AreaParserTaskNum ;check number of tasks to perform + and #$07 + cmp #$07 ;if at a specific task, jump and leave + beq ExitELCore + jmp ProcLoopCommand ;otherwise, jump to process loop command/load enemies +ChkBowserF: pla ;get data from stack + and #%00001111 ;mask out high nybble + tay + lda Enemy_Flag,y ;use as pointer and load same place with different offset + bne ExitELCore + sta Enemy_Flag,x ;if second enemy flag not set, also clear first one +ExitELCore: rts + +;-------------------------------- + +;loop command data +LoopCmdWorldNumber: + .db $03, $03, $06, $06, $06, $06, $06, $06, $07, $07, $07 + +LoopCmdPageNumber: + .db $05, $09, $04, $05, $06, $08, $09, $0a, $06, $0b, $10 + +LoopCmdYPosition: + .db $40, $b0, $b0, $80, $40, $40, $80, $40, $f0, $f0, $f0 + +ExecGameLoopback: + lda Player_PageLoc ;send player back four pages + sec + sbc #$04 + sta Player_PageLoc + lda CurrentPageLoc ;send current page back four pages + sec + sbc #$04 + sta CurrentPageLoc + lda ScreenLeft_PageLoc ;subtract four from page location + sec ;of screen's left border + sbc #$04 + sta ScreenLeft_PageLoc + lda ScreenRight_PageLoc ;do the same for the page location + sec ;of screen's right border + sbc #$04 + sta ScreenRight_PageLoc + lda AreaObjectPageLoc ;subtract four from page control + sec ;for area objects + sbc #$04 + sta AreaObjectPageLoc + lda #$00 ;initialize page select for both + sta EnemyObjectPageSel ;area and enemy objects + sta AreaObjectPageSel + sta EnemyDataOffset ;initialize enemy object data offset + sta EnemyObjectPageLoc ;and enemy object page control + lda AreaDataOfsLoopback,y ;adjust area object offset based on + sta AreaDataOffset ;which loop command we encountered + rts + +ProcLoopCommand: + lda LoopCommand ;check if loop command was found + beq ChkEnemyFrenzy + lda CurrentColumnPos ;check to see if we're still on the first page + bne ChkEnemyFrenzy ;if not, do not loop yet + ldy #$0b ;start at the end of each set of loop data +FindLoop: dey + bmi ChkEnemyFrenzy ;if all data is checked and not match, do not loop + lda WorldNumber ;check to see if one of the world numbers + cmp LoopCmdWorldNumber,y ;matches our current world number + bne FindLoop + lda CurrentPageLoc ;check to see if one of the page numbers + cmp LoopCmdPageNumber,y ;matches the page we're currently on + bne FindLoop + lda Player_Y_Position ;check to see if the player is at the correct position + cmp LoopCmdYPosition,y ;if not, branch to check for world 7 + bne WrongChk + lda Player_State ;check to see if the player is + cmp #$00 ;on solid ground (i.e. not jumping or falling) + bne WrongChk ;if not, player fails to pass loop, and loopback + lda WorldNumber ;are we in world 7? (check performed on correct + cmp #World7 ;vertical position and on solid ground) + bne InitMLp ;if not, initialize flags used there, otherwise + inc MultiLoopCorrectCntr ;increment counter for correct progression +IncMLoop: inc MultiLoopPassCntr ;increment master multi-part counter + lda MultiLoopPassCntr ;have we done all three parts? + cmp #$03 + bne InitLCmd ;if not, skip this part + lda MultiLoopCorrectCntr ;if so, have we done them all correctly? + cmp #$03 + beq InitMLp ;if so, branch past unnecessary check here + bne DoLpBack ;unconditional branch if previous branch fails +WrongChk: lda WorldNumber ;are we in world 7? (check performed on + cmp #World7 ;incorrect vertical position or not on solid ground) + beq IncMLoop +DoLpBack: jsr ExecGameLoopback ;if player is not in right place, loop back + jsr KillAllEnemies +InitMLp: lda #$00 ;initialize counters used for multi-part loop commands + sta MultiLoopPassCntr + sta MultiLoopCorrectCntr +InitLCmd: lda #$00 ;initialize loop command flag + sta LoopCommand + +;-------------------------------- + +ChkEnemyFrenzy: + lda EnemyFrenzyQueue ;check for enemy object in frenzy queue + beq ProcessEnemyData ;if not, skip this part + sta Enemy_ID,x ;store as enemy object identifier here + lda #$01 + sta Enemy_Flag,x ;activate enemy object flag + lda #$00 + sta Enemy_State,x ;initialize state and frenzy queue + sta EnemyFrenzyQueue + jmp InitEnemyObject ;and then jump to deal with this enemy + +;-------------------------------- +;$06 - used to hold page location of extended right boundary +;$07 - used to hold high nybble of position of extended right boundary + +ProcessEnemyData: + ldy EnemyDataOffset ;get offset of enemy object data + lda (EnemyData),y ;load first byte + cmp #$ff ;check for EOD terminator + bne CheckEndofBuffer + jmp CheckFrenzyBuffer ;if found, jump to check frenzy buffer, otherwise + +CheckEndofBuffer: + and #%00001111 ;check for special row $0e + cmp #$0e + beq CheckRightBounds ;if found, branch, otherwise + cpx #$05 ;check for end of buffer + bcc CheckRightBounds ;if not at end of buffer, branch + iny + lda (EnemyData),y ;check for specific value here + and #%00111111 ;not sure what this was intended for, exactly + cmp #$2e ;this part is quite possibly residual code + beq CheckRightBounds ;but it has the effect of keeping enemies out of + rts ;the sixth slot + +CheckRightBounds: + lda ScreenRight_X_Pos ;add 48 to pixel coordinate of right boundary + clc + adc #$30 + and #%11110000 ;store high nybble + sta $07 + lda ScreenRight_PageLoc ;add carry to page location of right boundary + adc #$00 + sta $06 ;store page location + carry + ldy EnemyDataOffset + iny + lda (EnemyData),y ;if MSB of enemy object is clear, branch to check for row $0f + asl + bcc CheckPageCtrlRow + lda EnemyObjectPageSel ;if page select already set, do not set again + bne CheckPageCtrlRow + inc EnemyObjectPageSel ;otherwise, if MSB is set, set page select + inc EnemyObjectPageLoc ;and increment page control + +CheckPageCtrlRow: + dey + lda (EnemyData),y ;reread first byte + and #$0f + cmp #$0f ;check for special row $0f + bne PositionEnemyObj ;if not found, branch to position enemy object + lda EnemyObjectPageSel ;if page select set, + bne PositionEnemyObj ;branch without reading second byte + iny + lda (EnemyData),y ;otherwise, get second byte, mask out 2 MSB + and #%00111111 + sta EnemyObjectPageLoc ;store as page control for enemy object data + inc EnemyDataOffset ;increment enemy object data offset 2 bytes + inc EnemyDataOffset + inc EnemyObjectPageSel ;set page select for enemy object data and + jmp ProcLoopCommand ;jump back to process loop commands again + +PositionEnemyObj: + lda EnemyObjectPageLoc ;store page control as page location + sta Enemy_PageLoc,x ;for enemy object + lda (EnemyData),y ;get first byte of enemy object + and #%11110000 + sta Enemy_X_Position,x ;store column position + cmp ScreenRight_X_Pos ;check column position against right boundary + lda Enemy_PageLoc,x ;without subtracting, then subtract borrow + sbc ScreenRight_PageLoc ;from page location + bcs CheckRightExtBounds ;if enemy object beyond or at boundary, branch + lda (EnemyData),y + and #%00001111 ;check for special row $0e + cmp #$0e ;if found, jump elsewhere + beq ParseRow0e + jmp CheckThreeBytes ;if not found, unconditional jump + +CheckRightExtBounds: + lda $07 ;check right boundary + 48 against + cmp Enemy_X_Position,x ;column position without subtracting, + lda $06 ;then subtract borrow from page control temp + sbc Enemy_PageLoc,x ;plus carry + bcc CheckFrenzyBuffer ;if enemy object beyond extended boundary, branch + lda #$01 ;store value in vertical high byte + sta Enemy_Y_HighPos,x + lda (EnemyData),y ;get first byte again + asl ;multiply by four to get the vertical + asl ;coordinate + asl + asl + sta Enemy_Y_Position,x + cmp #$e0 ;do one last check for special row $0e + beq ParseRow0e ;(necessary if branched to $c1cb) + iny + lda (EnemyData),y ;get second byte of object + and #%01000000 ;check to see if hard mode bit is set + beq CheckForEnemyGroup ;if not, branch to check for group enemy objects + lda SecondaryHardMode ;if set, check to see if secondary hard mode flag + beq Inc2B ;is on, and if not, branch to skip this object completely + +CheckForEnemyGroup: + lda (EnemyData),y ;get second byte and mask out 2 MSB + and #%00111111 + cmp #$37 ;check for value below $37 + bcc BuzzyBeetleMutate + cmp #$3f ;if $37 or greater, check for value + bcc DoGroup ;below $3f, branch if below $3f + +BuzzyBeetleMutate: + cmp #Goomba ;if below $37, check for goomba + bne StrID ;value ($3f or more always fails) + ldy PrimaryHardMode ;check if primary hard mode flag is set + beq StrID ;and if so, change goomba to buzzy beetle + lda #BuzzyBeetle +StrID: sta Enemy_ID,x ;store enemy object number into buffer + lda #$01 + sta Enemy_Flag,x ;set flag for enemy in buffer + jsr InitEnemyObject + lda Enemy_Flag,x ;check to see if flag is set + bne Inc2B ;if not, leave, otherwise branch + rts + +CheckFrenzyBuffer: + lda EnemyFrenzyBuffer ;if enemy object stored in frenzy buffer + bne StrFre ;then branch ahead to store in enemy object buffer + lda VineFlagOffset ;otherwise check vine flag offset + cmp #$01 + bne ExEPar ;if other value <> 1, leave + lda #VineObject ;otherwise put vine in enemy identifier +StrFre: sta Enemy_ID,x ;store contents of frenzy buffer into enemy identifier value + +InitEnemyObject: + lda #$00 ;initialize enemy state + sta Enemy_State,x + jsr CheckpointEnemyID ;jump ahead to run jump engine and subroutines +ExEPar: rts ;then leave + +DoGroup: + jmp HandleGroupEnemies ;handle enemy group objects + +ParseRow0e: + iny ;increment Y to load third byte of object + iny + lda (EnemyData),y + lsr ;move 3 MSB to the bottom, effectively + lsr ;making %xxx00000 into %00000xxx + lsr + lsr + lsr + cmp WorldNumber ;is it the same world number as we're on? + bne NotUse ;if not, do not use (this allows multiple uses + dey ;of the same area, like the underground bonus areas) + lda (EnemyData),y ;otherwise, get second byte and use as offset + sta AreaPointer ;to addresses for level and enemy object data + iny + lda (EnemyData),y ;get third byte again, and this time mask out + and #%00011111 ;the 3 MSB from before, save as page number to be + sta EntrancePage ;used upon entry to area, if area is entered +NotUse: jmp Inc3B + +CheckThreeBytes: + ldy EnemyDataOffset ;load current offset for enemy object data + lda (EnemyData),y ;get first byte + and #%00001111 ;check for special row $0e + cmp #$0e + bne Inc2B +Inc3B: inc EnemyDataOffset ;if row = $0e, increment three bytes +Inc2B: inc EnemyDataOffset ;otherwise increment two bytes + inc EnemyDataOffset + lda #$00 ;init page select for enemy objects + sta EnemyObjectPageSel + ldx ObjectOffset ;reload current offset in enemy buffers + rts ;and leave + +CheckpointEnemyID: + lda Enemy_ID,x + cmp #$15 ;check enemy object identifier for $15 or greater + bcs InitEnemyRoutines ;and branch straight to the jump engine if found + tay ;save identifier in Y register for now + lda Enemy_Y_Position,x + adc #$08 ;add eight pixels to what will eventually be the + sta Enemy_Y_Position,x ;enemy object's vertical coordinate ($00-$14 only) + lda #$01 + sta EnemyOffscrBitsMasked,x ;set offscreen masked bit + tya ;get identifier back and use as offset for jump engine + +InitEnemyRoutines: + jsr JumpEngine + +;jump engine table for newly loaded enemy objects + + .dw InitNormalEnemy ;for objects $00-$0f + .dw InitNormalEnemy + .dw InitNormalEnemy + .dw InitRedKoopa + .dw NoInitCode + .dw InitHammerBro + .dw InitGoomba + .dw InitBloober + .dw InitBulletBill + .dw NoInitCode + .dw InitCheepCheep + .dw InitCheepCheep + .dw InitPodoboo + .dw InitPiranhaPlant + .dw InitJumpGPTroopa + .dw InitRedPTroopa + + .dw InitHorizFlySwimEnemy ;for objects $10-$1f + .dw InitLakitu + .dw InitEnemyFrenzy + .dw NoInitCode + .dw InitEnemyFrenzy + .dw InitEnemyFrenzy + .dw InitEnemyFrenzy + .dw InitEnemyFrenzy + .dw EndFrenzy + .dw NoInitCode + .dw NoInitCode + .dw InitShortFirebar + .dw InitShortFirebar + .dw InitShortFirebar + .dw InitShortFirebar + .dw InitLongFirebar + + .dw NoInitCode ;for objects $20-$2f + .dw NoInitCode + .dw NoInitCode + .dw NoInitCode + .dw InitBalPlatform + .dw InitVertPlatform + .dw LargeLiftUp + .dw LargeLiftDown + .dw InitHoriPlatform + .dw InitDropPlatform + .dw InitHoriPlatform + .dw PlatLiftUp + .dw PlatLiftDown + .dw InitBowser + .dw PwrUpJmp ;possibly dummy value + .dw Setup_Vine + + .dw NoInitCode ;for objects $30-$36 + .dw NoInitCode + .dw NoInitCode + .dw NoInitCode + .dw NoInitCode + .dw InitRetainerObj + .dw EndOfEnemyInitCode + +;------------------------------------------------------------------------------------- + +NoInitCode: + rts ;this executed when enemy object has no init code + +;-------------------------------- + +InitGoomba: + jsr InitNormalEnemy ;set appropriate horizontal speed + jmp SmallBBox ;set $09 as bounding box control, set other values + +;-------------------------------- + +InitPodoboo: + lda #$02 ;set enemy position to below + sta Enemy_Y_HighPos,x ;the bottom of the screen + sta Enemy_Y_Position,x + lsr + sta EnemyIntervalTimer,x ;set timer for enemy + lsr + sta Enemy_State,x ;initialize enemy state, then jump to use + jmp SmallBBox ;$09 as bounding box size and set other things + +;-------------------------------- + +InitRetainerObj: + lda #$b8 ;set fixed vertical position for + sta Enemy_Y_Position,x ;princess/mushroom retainer object + rts + +;-------------------------------- + +NormalXSpdData: + .db $f8, $f4 + +InitNormalEnemy: + ldy #$01 ;load offset of 1 by default + lda PrimaryHardMode ;check for primary hard mode flag set + bne GetESpd + dey ;if not set, decrement offset +GetESpd: lda NormalXSpdData,y ;get appropriate horizontal speed +SetESpd: sta Enemy_X_Speed,x ;store as speed for enemy object + jmp TallBBox ;branch to set bounding box control and other data + +;-------------------------------- + +InitRedKoopa: + jsr InitNormalEnemy ;load appropriate horizontal speed + lda #$01 ;set enemy state for red koopa troopa $03 + sta Enemy_State,x + rts + +;-------------------------------- + +HBroWalkingTimerData: + .db $80, $50 + +InitHammerBro: + lda #$00 ;init horizontal speed and timer used by hammer bro + sta HammerThrowingTimer,x ;apparently to time hammer throwing + sta Enemy_X_Speed,x + ldy SecondaryHardMode ;get secondary hard mode flag + lda HBroWalkingTimerData,y + sta EnemyIntervalTimer,x ;set value as delay for hammer bro to walk left + lda #$0b ;set specific value for bounding box size control + jmp SetBBox + +;-------------------------------- + +InitHorizFlySwimEnemy: + lda #$00 ;initialize horizontal speed + jmp SetESpd + +;-------------------------------- + +InitBloober: + lda #$00 ;initialize horizontal speed + sta BlooperMoveSpeed,x +SmallBBox: lda #$09 ;set specific bounding box size control + bne SetBBox ;unconditional branch + +;-------------------------------- + +InitRedPTroopa: + ldy #$30 ;load central position adder for 48 pixels down + lda Enemy_Y_Position,x ;set vertical coordinate into location to + sta RedPTroopaOrigXPos,x ;be used as original vertical coordinate + bpl GetCent ;if vertical coordinate < $80 + ldy #$e0 ;if => $80, load position adder for 32 pixels up +GetCent: tya ;send central position adder to A + adc Enemy_Y_Position,x ;add to current vertical coordinate + sta RedPTroopaCenterYPos,x ;store as central vertical coordinate +TallBBox: lda #$03 ;set specific bounding box size control +SetBBox: sta Enemy_BoundBoxCtrl,x ;set bounding box control here + lda #$02 ;set moving direction for left + sta Enemy_MovingDir,x +InitVStf: lda #$00 ;initialize vertical speed + sta Enemy_Y_Speed,x ;and movement force + sta Enemy_Y_MoveForce,x + rts + +;-------------------------------- + +InitBulletBill: + lda #$02 ;set moving direction for left + sta Enemy_MovingDir,x + lda #$09 ;set bounding box control for $09 + sta Enemy_BoundBoxCtrl,x + rts + +;-------------------------------- + +InitCheepCheep: + jsr SmallBBox ;set vertical bounding box, speed, init others + lda PseudoRandomBitReg,x ;check one portion of LSFR + and #%00010000 ;get d4 from it + sta CheepCheepMoveMFlag,x ;save as movement flag of some sort + lda Enemy_Y_Position,x + sta CheepCheepOrigYPos,x ;save original vertical coordinate here + rts + +;-------------------------------- + +InitLakitu: + lda EnemyFrenzyBuffer ;check to see if an enemy is already in + bne KillLakitu ;the frenzy buffer, and branch to kill lakitu if so + +SetupLakitu: + lda #$00 ;erase counter for lakitu's reappearance + sta LakituReappearTimer + jsr InitHorizFlySwimEnemy ;set $03 as bounding box, set other attributes + jmp TallBBox2 ;set $03 as bounding box again (not necessary) and leave + +KillLakitu: + jmp EraseEnemyObject + +;-------------------------------- +;$01-$03 - used to hold pseudorandom difference adjusters + +PRDiffAdjustData: + .db $26, $2c, $32, $38 + .db $20, $22, $24, $26 + .db $13, $14, $15, $16 + +LakituAndSpinyHandler: + lda FrenzyEnemyTimer ;if timer here not expired, leave + bne ExLSHand + cpx #$05 ;if we are on the special use slot, leave + bcs ExLSHand + lda #$80 ;set timer + sta FrenzyEnemyTimer + ldy #$04 ;start with the last enemy slot +ChkLak: lda Enemy_ID,y ;check all enemy slots to see + cmp #Lakitu ;if lakitu is on one of them + beq CreateSpiny ;if so, branch out of this loop + dey ;otherwise check another slot + bpl ChkLak ;loop until all slots are checked + inc LakituReappearTimer ;increment reappearance timer + lda LakituReappearTimer + cmp #$07 ;check to see if we're up to a certain value yet + bcc ExLSHand ;if not, leave + ldx #$04 ;start with the last enemy slot again +ChkNoEn: lda Enemy_Flag,x ;check enemy buffer flag for non-active enemy slot + beq CreateL ;branch out of loop if found + dex ;otherwise check next slot + bpl ChkNoEn ;branch until all slots are checked + bmi RetEOfs ;if no empty slots were found, branch to leave +CreateL: lda #$00 ;initialize enemy state + sta Enemy_State,x + lda #Lakitu ;create lakitu enemy object + sta Enemy_ID,x + jsr SetupLakitu ;do a sub to set up lakitu + lda #$20 + jsr PutAtRightExtent ;finish setting up lakitu +RetEOfs: ldx ObjectOffset ;get enemy object buffer offset again and leave +ExLSHand: rts + +;-------------------------------- + +CreateSpiny: + lda Player_Y_Position ;if player above a certain point, branch to leave + cmp #$2c + bcc ExLSHand + lda Enemy_State,y ;if lakitu is not in normal state, branch to leave + bne ExLSHand + lda Enemy_PageLoc,y ;store horizontal coordinates (high and low) of lakitu + sta Enemy_PageLoc,x ;into the coordinates of the spiny we're going to create + lda Enemy_X_Position,y + sta Enemy_X_Position,x + lda #$01 ;put spiny within vertical screen unit + sta Enemy_Y_HighPos,x + lda Enemy_Y_Position,y ;put spiny eight pixels above where lakitu is + sec + sbc #$08 + sta Enemy_Y_Position,x + lda PseudoRandomBitReg,x ;get 2 LSB of LSFR and save to Y + and #%00000011 + tay + ldx #$02 +DifLoop: lda PRDiffAdjustData,y ;get three values and save them + sta $01,x ;to $01-$03 + iny + iny ;increment Y four bytes for each value + iny + iny + dex ;decrement X for each one + bpl DifLoop ;loop until all three are written + ldx ObjectOffset ;get enemy object buffer offset + jsr PlayerLakituDiff ;move enemy, change direction, get value - difference + ldy Player_X_Speed ;check player's horizontal speed + cpy #$08 + bcs SetSpSpd ;if moving faster than a certain amount, branch elsewhere + tay ;otherwise save value in A to Y for now + lda PseudoRandomBitReg+1,x + and #%00000011 ;get one of the LSFR parts and save the 2 LSB + beq UsePosv ;branch if neither bits are set + tya + eor #%11111111 ;otherwise get two's compliment of Y + tay + iny +UsePosv: tya ;put value from A in Y back to A (they will be lost anyway) +SetSpSpd: jsr SmallBBox ;set bounding box control, init attributes, lose contents of A + ldy #$02 ;(putting this call elsewhere will preserve A) + sta Enemy_X_Speed,x ;set horizontal speed to zero because previous contents + cmp #$00 ;of A were lost...branch here will never be taken for + bmi SpinyRte ;the same reason + dey +SpinyRte: sty Enemy_MovingDir,x ;set moving direction to the right + lda #$fd + sta Enemy_Y_Speed,x ;set vertical speed to move upwards + lda #$01 + sta Enemy_Flag,x ;enable enemy object by setting flag + lda #$05 + sta Enemy_State,x ;put spiny in egg state and leave +ChpChpEx: rts + +;-------------------------------- + +FirebarSpinSpdData: + .db $28, $38, $28, $38, $28 + +FirebarSpinDirData: + .db $00, $00, $10, $10, $00 + +InitLongFirebar: + jsr DuplicateEnemyObj ;create enemy object for long firebar + +InitShortFirebar: + lda #$00 ;initialize low byte of spin state + sta FirebarSpinState_Low,x + lda Enemy_ID,x ;subtract $1b from enemy identifier + sec ;to get proper offset for firebar data + sbc #$1b + tay + lda FirebarSpinSpdData,y ;get spinning speed of firebar + sta FirebarSpinSpeed,x + lda FirebarSpinDirData,y ;get spinning direction of firebar + sta FirebarSpinDirection,x + lda Enemy_Y_Position,x + clc ;add four pixels to vertical coordinate + adc #$04 + sta Enemy_Y_Position,x + lda Enemy_X_Position,x + clc ;add four pixels to horizontal coordinate + adc #$04 + sta Enemy_X_Position,x + lda Enemy_PageLoc,x + adc #$00 ;add carry to page location + sta Enemy_PageLoc,x + jmp TallBBox2 ;set bounding box control (not used) and leave + +;-------------------------------- +;$00-$01 - used to hold pseudorandom bits + +FlyCCXPositionData: + .db $80, $30, $40, $80 + .db $30, $50, $50, $70 + .db $20, $40, $80, $a0 + .db $70, $40, $90, $68 + +FlyCCXSpeedData: + .db $0e, $05, $06, $0e + .db $1c, $20, $10, $0c + .db $1e, $22, $18, $14 + +FlyCCTimerData: + .db $10, $60, $20, $48 + +InitFlyingCheepCheep: + lda FrenzyEnemyTimer ;if timer here not expired yet, branch to leave + bne ChpChpEx + jsr SmallBBox ;jump to set bounding box size $09 and init other values + lda PseudoRandomBitReg+1,x + and #%00000011 ;set pseudorandom offset here + tay + lda FlyCCTimerData,y ;load timer with pseudorandom offset + sta FrenzyEnemyTimer + ldy #$03 ;load Y with default value + lda SecondaryHardMode + beq MaxCC ;if secondary hard mode flag not set, do not increment Y + iny ;otherwise, increment Y to allow as many as four onscreen +MaxCC: sty $00 ;store whatever pseudorandom bits are in Y + cpx $00 ;compare enemy object buffer offset with Y + bcs ChpChpEx ;if X => Y, branch to leave + lda PseudoRandomBitReg,x + and #%00000011 ;get last two bits of LSFR, first part + sta $00 ;and store in two places + sta $01 + lda #$fb ;set vertical speed for cheep-cheep + sta Enemy_Y_Speed,x + lda #$00 ;load default value + ldy Player_X_Speed ;check player's horizontal speed + beq GSeed ;if player not moving left or right, skip this part + lda #$04 + cpy #$19 ;if moving to the right but not very quickly, + bcc GSeed ;do not change A + asl ;otherwise, multiply A by 2 +GSeed: pha ;save to stack + clc + adc $00 ;add to last two bits of LSFR we saved earlier + sta $00 ;save it there + lda PseudoRandomBitReg+1,x + and #%00000011 ;if neither of the last two bits of second LSFR set, + beq RSeed ;skip this part and save contents of $00 + lda PseudoRandomBitReg+2,x + and #%00001111 ;otherwise overwrite with lower nybble of + sta $00 ;third LSFR part +RSeed: pla ;get value from stack we saved earlier + clc + adc $01 ;add to last two bits of LSFR we saved in other place + tay ;use as pseudorandom offset here + lda FlyCCXSpeedData,y ;get horizontal speed using pseudorandom offset + sta Enemy_X_Speed,x + lda #$01 ;set to move towards the right + sta Enemy_MovingDir,x + lda Player_X_Speed ;if player moving left or right, branch ahead of this part + bne D2XPos1 + ldy $00 ;get first LSFR or third LSFR lower nybble + tya ;and check for d1 set + and #%00000010 + beq D2XPos1 ;if d1 not set, branch + lda Enemy_X_Speed,x + eor #$ff ;if d1 set, change horizontal speed + clc ;into two's compliment, thus moving in the opposite + adc #$01 ;direction + sta Enemy_X_Speed,x + inc Enemy_MovingDir,x ;increment to move towards the left +D2XPos1: tya ;get first LSFR or third LSFR lower nybble again + and #%00000010 + beq D2XPos2 ;check for d1 set again, branch again if not set + lda Player_X_Position ;get player's horizontal position + clc + adc FlyCCXPositionData,y ;if d1 set, add value obtained from pseudorandom offset + sta Enemy_X_Position,x ;and save as enemy's horizontal position + lda Player_PageLoc ;get player's page location + adc #$00 ;add carry and jump past this part + jmp FinCCSt +D2XPos2: lda Player_X_Position ;get player's horizontal position + sec + sbc FlyCCXPositionData,y ;if d1 not set, subtract value obtained from pseudorandom + sta Enemy_X_Position,x ;offset and save as enemy's horizontal position + lda Player_PageLoc ;get player's page location + sbc #$00 ;subtract borrow +FinCCSt: sta Enemy_PageLoc,x ;save as enemy's page location + lda #$01 + sta Enemy_Flag,x ;set enemy's buffer flag + sta Enemy_Y_HighPos,x ;set enemy's high vertical byte + lda #$f8 + sta Enemy_Y_Position,x ;put enemy below the screen, and we are done + rts + +;-------------------------------- + +InitBowser: + jsr DuplicateEnemyObj ;jump to create another bowser object + stx BowserFront_Offset ;save offset of first here + lda #$00 + sta BowserBodyControls ;initialize bowser's body controls + sta BridgeCollapseOffset ;and bridge collapse offset + lda Enemy_X_Position,x + sta BowserOrigXPos ;store original horizontal position here + lda #$df + sta BowserFireBreathTimer ;store something here + sta Enemy_MovingDir,x ;and in moving direction + lda #$20 + sta BowserFeetCounter ;set bowser's feet timer and in enemy timer + sta EnemyFrameTimer,x + lda #$05 + sta BowserHitPoints ;give bowser 5 hit points + lsr + sta BowserMovementSpeed ;set default movement speed here + rts + +;-------------------------------- + +DuplicateEnemyObj: + ldy #$ff ;start at beginning of enemy slots +FSLoop: iny ;increment one slot + lda Enemy_Flag,y ;check enemy buffer flag for empty slot + bne FSLoop ;if set, branch and keep checking + sty DuplicateObj_Offset ;otherwise set offset here + txa ;transfer original enemy buffer offset + ora #%10000000 ;store with d7 set as flag in new enemy + sta Enemy_Flag,y ;slot as well as enemy offset + lda Enemy_PageLoc,x + sta Enemy_PageLoc,y ;copy page location and horizontal coordinates + lda Enemy_X_Position,x ;from original enemy to new enemy + sta Enemy_X_Position,y + lda #$01 + sta Enemy_Flag,x ;set flag as normal for original enemy + sta Enemy_Y_HighPos,y ;set high vertical byte for new enemy + lda Enemy_Y_Position,x + sta Enemy_Y_Position,y ;copy vertical coordinate from original to new +FlmEx: rts ;and then leave + +;-------------------------------- + +FlameYPosData: + .db $90, $80, $70, $90 + +FlameYMFAdderData: + .db $ff, $01 + +InitBowserFlame: + lda FrenzyEnemyTimer ;if timer not expired yet, branch to leave + bne FlmEx + sta Enemy_Y_MoveForce,x ;reset something here + lda NoiseSoundQueue + ora #Sfx_BowserFlame ;load bowser's flame sound into queue + sta NoiseSoundQueue + ldy BowserFront_Offset ;get bowser's buffer offset + lda Enemy_ID,y ;check for bowser + cmp #Bowser + beq SpawnFromMouth ;branch if found + jsr SetFlameTimer ;get timer data based on flame counter + clc + adc #$20 ;add 32 frames by default + ldy SecondaryHardMode + beq SetFrT ;if secondary mode flag not set, use as timer setting + sec + sbc #$10 ;otherwise subtract 16 frames for secondary hard mode +SetFrT: sta FrenzyEnemyTimer ;set timer accordingly + lda PseudoRandomBitReg,x + and #%00000011 ;get 2 LSB from first part of LSFR + sta BowserFlamePRandomOfs,x ;set here + tay ;use as offset + lda FlameYPosData,y ;load vertical position based on pseudorandom offset + +PutAtRightExtent: + sta Enemy_Y_Position,x ;set vertical position + lda ScreenRight_X_Pos + clc + adc #$20 ;place enemy 32 pixels beyond right side of screen + sta Enemy_X_Position,x + lda ScreenRight_PageLoc + adc #$00 ;add carry + sta Enemy_PageLoc,x + jmp FinishFlame ;skip this part to finish setting values + +SpawnFromMouth: + lda Enemy_X_Position,y ;get bowser's horizontal position + sec + sbc #$0e ;subtract 14 pixels + sta Enemy_X_Position,x ;save as flame's horizontal position + lda Enemy_PageLoc,y + sta Enemy_PageLoc,x ;copy page location from bowser to flame + lda Enemy_Y_Position,y + clc ;add 8 pixels to bowser's vertical position + adc #$08 + sta Enemy_Y_Position,x ;save as flame's vertical position + lda PseudoRandomBitReg,x + and #%00000011 ;get 2 LSB from first part of LSFR + sta Enemy_YMF_Dummy,x ;save here + tay ;use as offset + lda FlameYPosData,y ;get value here using bits as offset + ldy #$00 ;load default offset + cmp Enemy_Y_Position,x ;compare value to flame's current vertical position + bcc SetMF ;if less, do not increment offset + iny ;otherwise increment now +SetMF: lda FlameYMFAdderData,y ;get value here and save + sta Enemy_Y_MoveForce,x ;to vertical movement force + lda #$00 + sta EnemyFrenzyBuffer ;clear enemy frenzy buffer + +FinishFlame: + lda #$08 ;set $08 for bounding box control + sta Enemy_BoundBoxCtrl,x + lda #$01 ;set high byte of vertical and + sta Enemy_Y_HighPos,x ;enemy buffer flag + sta Enemy_Flag,x + lsr + sta Enemy_X_MoveForce,x ;initialize horizontal movement force, and + sta Enemy_State,x ;enemy state + rts + +;-------------------------------- + +FireworksXPosData: + .db $00, $30, $60, $60, $00, $20 + +FireworksYPosData: + .db $60, $40, $70, $40, $60, $30 + +InitFireworks: + lda FrenzyEnemyTimer ;if timer not expired yet, branch to leave + bne ExitFWk + lda #$20 ;otherwise reset timer + sta FrenzyEnemyTimer + dec FireworksCounter ;decrement for each explosion + ldy #$06 ;start at last slot +StarFChk: dey + lda Enemy_ID,y ;check for presence of star flag object + cmp #StarFlagObject ;if there isn't a star flag object, + bne StarFChk ;routine goes into infinite loop = crash + lda Enemy_X_Position,y + sec ;get horizontal coordinate of star flag object, then + sbc #$30 ;subtract 48 pixels from it and save to + pha ;the stack + lda Enemy_PageLoc,y + sbc #$00 ;subtract the carry from the page location + sta $00 ;of the star flag object + lda FireworksCounter ;get fireworks counter + clc + adc Enemy_State,y ;add state of star flag object (possibly not necessary) + tay ;use as offset + pla ;get saved horizontal coordinate of star flag - 48 pixels + clc + adc FireworksXPosData,y ;add number based on offset of fireworks counter + sta Enemy_X_Position,x ;store as the fireworks object horizontal coordinate + lda $00 + adc #$00 ;add carry and store as page location for + sta Enemy_PageLoc,x ;the fireworks object + lda FireworksYPosData,y ;get vertical position using same offset + sta Enemy_Y_Position,x ;and store as vertical coordinate for fireworks object + lda #$01 + sta Enemy_Y_HighPos,x ;store in vertical high byte + sta Enemy_Flag,x ;and activate enemy buffer flag + lsr + sta ExplosionGfxCounter,x ;initialize explosion counter + lda #$08 + sta ExplosionTimerCounter,x ;set explosion timing counter +ExitFWk: rts + +;-------------------------------- + +Bitmasks: + .db %00000001, %00000010, %00000100, %00001000, %00010000, %00100000, %01000000, %10000000 + +Enemy17YPosData: + .db $40, $30, $90, $50, $20, $60, $a0, $70 + +SwimCC_IDData: + .db $0a, $0b + +BulletBillCheepCheep: + lda FrenzyEnemyTimer ;if timer not expired yet, branch to leave + bne ExF17 + lda AreaType ;are we in a water-type level? + bne DoBulletBills ;if not, branch elsewhere + cpx #$03 ;are we past third enemy slot? + bcs ExF17 ;if so, branch to leave + ldy #$00 ;load default offset + lda PseudoRandomBitReg,x + cmp #$aa ;check first part of LSFR against preset value + bcc ChkW2 ;if less than preset, do not increment offset + iny ;otherwise increment +ChkW2: lda WorldNumber ;check world number + cmp #World2 + beq Get17ID ;if we're on world 2, do not increment offset + iny ;otherwise increment +Get17ID: tya + and #%00000001 ;mask out all but last bit of offset + tay + lda SwimCC_IDData,y ;load identifier for cheep-cheeps +Set17ID: sta Enemy_ID,x ;store whatever's in A as enemy identifier + lda BitMFilter + cmp #$ff ;if not all bits set, skip init part and compare bits + bne GetRBit + lda #$00 ;initialize vertical position filter + sta BitMFilter +GetRBit: lda PseudoRandomBitReg,x ;get first part of LSFR + and #%00000111 ;mask out all but 3 LSB +ChkRBit: tay ;use as offset + lda Bitmasks,y ;load bitmask + bit BitMFilter ;perform AND on filter without changing it + beq AddFBit + iny ;increment offset + tya + and #%00000111 ;mask out all but 3 LSB thus keeping it 0-7 + jmp ChkRBit ;do another check +AddFBit: ora BitMFilter ;add bit to already set bits in filter + sta BitMFilter ;and store + lda Enemy17YPosData,y ;load vertical position using offset + jsr PutAtRightExtent ;set vertical position and other values + sta Enemy_YMF_Dummy,x ;initialize dummy variable + lda #$20 ;set timer + sta FrenzyEnemyTimer + jmp CheckpointEnemyID ;process our new enemy object + +DoBulletBills: + ldy #$ff ;start at beginning of enemy slots +BB_SLoop: iny ;move onto the next slot + cpy #$05 ;branch to play sound if we've done all slots + bcs FireBulletBill + lda Enemy_Flag,y ;if enemy buffer flag not set, + beq BB_SLoop ;loop back and check another slot + lda Enemy_ID,y + cmp #BulletBill_FrenzyVar ;check enemy identifier for + bne BB_SLoop ;bullet bill object (frenzy variant) +ExF17: rts ;if found, leave + +FireBulletBill: + lda Square2SoundQueue + ora #Sfx_Blast ;play fireworks/gunfire sound + sta Square2SoundQueue + lda #BulletBill_FrenzyVar ;load identifier for bullet bill object + bne Set17ID ;unconditional branch + +;-------------------------------- +;$00 - used to store Y position of group enemies +;$01 - used to store enemy ID +;$02 - used to store page location of right side of screen +;$03 - used to store X position of right side of screen + +HandleGroupEnemies: + ldy #$00 ;load value for green koopa troopa + sec + sbc #$37 ;subtract $37 from second byte read + pha ;save result in stack for now + cmp #$04 ;was byte in $3b-$3e range? + bcs SnglID ;if so, branch + pha ;save another copy to stack + ldy #Goomba ;load value for goomba enemy + lda PrimaryHardMode ;if primary hard mode flag not set, + beq PullID ;branch, otherwise change to value + ldy #BuzzyBeetle ;for buzzy beetle +PullID: pla ;get second copy from stack +SnglID: sty $01 ;save enemy id here + ldy #$b0 ;load default y coordinate + and #$02 ;check to see if d1 was set + beq SetYGp ;if so, move y coordinate up, + ldy #$70 ;otherwise branch and use default +SetYGp: sty $00 ;save y coordinate here + lda ScreenRight_PageLoc ;get page number of right edge of screen + sta $02 ;save here + lda ScreenRight_X_Pos ;get pixel coordinate of right edge + sta $03 ;save here + ldy #$02 ;load two enemies by default + pla ;get first copy from stack + lsr ;check to see if d0 was set + bcc CntGrp ;if not, use default value + iny ;otherwise increment to three enemies +CntGrp: sty NumberofGroupEnemies ;save number of enemies here +GrLoop: ldx #$ff ;start at beginning of enemy buffers +GSltLp: inx ;increment and branch if past + cpx #$05 ;end of buffers + bcs NextED + lda Enemy_Flag,x ;check to see if enemy is already + bne GSltLp ;stored in buffer, and branch if so + lda $01 + sta Enemy_ID,x ;store enemy object identifier + lda $02 + sta Enemy_PageLoc,x ;store page location for enemy object + lda $03 + sta Enemy_X_Position,x ;store x coordinate for enemy object + clc + adc #$18 ;add 24 pixels for next enemy + sta $03 + lda $02 ;add carry to page location for + adc #$00 ;next enemy + sta $02 + lda $00 ;store y coordinate for enemy object + sta Enemy_Y_Position,x + lda #$01 ;activate flag for buffer, and + sta Enemy_Y_HighPos,x ;put enemy within the screen vertically + sta Enemy_Flag,x + jsr CheckpointEnemyID ;process each enemy object separately + dec NumberofGroupEnemies ;do this until we run out of enemy objects + bne GrLoop +NextED: jmp Inc2B ;jump to increment data offset and leave + +;-------------------------------- + +InitPiranhaPlant: + lda #$01 ;set initial speed + sta PiranhaPlant_Y_Speed,x + lsr + sta Enemy_State,x ;initialize enemy state and what would normally + sta PiranhaPlant_MoveFlag,x ;be used as vertical speed, but not in this case + lda Enemy_Y_Position,x + sta PiranhaPlantDownYPos,x ;save original vertical coordinate here + sec + sbc #$18 + sta PiranhaPlantUpYPos,x ;save original vertical coordinate - 24 pixels here + lda #$09 + jmp SetBBox2 ;set specific value for bounding box control + +;-------------------------------- + +InitEnemyFrenzy: + lda Enemy_ID,x ;load enemy identifier + sta EnemyFrenzyBuffer ;save in enemy frenzy buffer + sec + sbc #$12 ;subtract 12 and use as offset for jump engine + jsr JumpEngine + +;frenzy object jump table + .dw LakituAndSpinyHandler + .dw NoFrenzyCode + .dw InitFlyingCheepCheep + .dw InitBowserFlame + .dw InitFireworks + .dw BulletBillCheepCheep + +;-------------------------------- + +NoFrenzyCode: + rts + +;-------------------------------- + +EndFrenzy: + ldy #$05 ;start at last slot +LakituChk: lda Enemy_ID,y ;check enemy identifiers + cmp #Lakitu ;for lakitu + bne NextFSlot + lda #$01 ;if found, set state + sta Enemy_State,y +NextFSlot: dey ;move onto the next slot + bpl LakituChk ;do this until all slots are checked + lda #$00 + sta EnemyFrenzyBuffer ;empty enemy frenzy buffer + sta Enemy_Flag,x ;disable enemy buffer flag for this object + rts + +;-------------------------------- + +InitJumpGPTroopa: + lda #$02 ;set for movement to the left + sta Enemy_MovingDir,x + lda #$f8 ;set horizontal speed + sta Enemy_X_Speed,x +TallBBox2: lda #$03 ;set specific value for bounding box control +SetBBox2: sta Enemy_BoundBoxCtrl,x ;set bounding box control then leave + rts + +;-------------------------------- + +InitBalPlatform: + dec Enemy_Y_Position,x ;raise vertical position by two pixels + dec Enemy_Y_Position,x + ldy SecondaryHardMode ;if secondary hard mode flag not set, + bne AlignP ;branch ahead + ldy #$02 ;otherwise set value here + jsr PosPlatform ;do a sub to add or subtract pixels +AlignP: ldy #$ff ;set default value here for now + lda BalPlatformAlignment ;get current balance platform alignment + sta Enemy_State,x ;set platform alignment to object state here + bpl SetBPA ;if old alignment $ff, put $ff as alignment for negative + txa ;if old contents already $ff, put + tay ;object offset as alignment to make next positive +SetBPA: sty BalPlatformAlignment ;store whatever value's in Y here + lda #$00 + sta Enemy_MovingDir,x ;init moving direction + tay ;init Y + jsr PosPlatform ;do a sub to add 8 pixels, then run shared code here + +;-------------------------------- + +InitDropPlatform: + lda #$ff + sta PlatformCollisionFlag,x ;set some value here + jmp CommonPlatCode ;then jump ahead to execute more code + +;-------------------------------- + +InitHoriPlatform: + lda #$00 + sta XMoveSecondaryCounter,x ;init one of the moving counters + jmp CommonPlatCode ;jump ahead to execute more code + +;-------------------------------- + +InitVertPlatform: + ldy #$40 ;set default value here + lda Enemy_Y_Position,x ;check vertical position + bpl SetYO ;if above a certain point, skip this part + eor #$ff + clc ;otherwise get two's compliment + adc #$01 + ldy #$c0 ;get alternate value to add to vertical position +SetYO: sta YPlatformTopYPos,x ;save as top vertical position + tya + clc ;load value from earlier, add number of pixels + adc Enemy_Y_Position,x ;to vertical position + sta YPlatformCenterYPos,x ;save result as central vertical position + +;-------------------------------- + +CommonPlatCode: + jsr InitVStf ;do a sub to init certain other values +SPBBox: lda #$05 ;set default bounding box size control + ldy AreaType + cpy #$03 ;check for castle-type level + beq CasPBB ;use default value if found + ldy SecondaryHardMode ;otherwise check for secondary hard mode flag + bne CasPBB ;if set, use default value + lda #$06 ;use alternate value if not castle or secondary not set +CasPBB: sta Enemy_BoundBoxCtrl,x ;set bounding box size control here and leave + rts + +;-------------------------------- + +LargeLiftUp: + jsr PlatLiftUp ;execute code for platforms going up + jmp LargeLiftBBox ;overwrite bounding box for large platforms + +LargeLiftDown: + jsr PlatLiftDown ;execute code for platforms going down + +LargeLiftBBox: + jmp SPBBox ;jump to overwrite bounding box size control + +;-------------------------------- + +PlatLiftUp: + lda #$10 ;set movement amount here + sta Enemy_Y_MoveForce,x + lda #$ff ;set moving speed for platforms going up + sta Enemy_Y_Speed,x + jmp CommonSmallLift ;skip ahead to part we should be executing + +;-------------------------------- + +PlatLiftDown: + lda #$f0 ;set movement amount here + sta Enemy_Y_MoveForce,x + lda #$00 ;set moving speed for platforms going down + sta Enemy_Y_Speed,x + +;-------------------------------- + +CommonSmallLift: + ldy #$01 + jsr PosPlatform ;do a sub to add 12 pixels due to preset value + lda #$04 + sta Enemy_BoundBoxCtrl,x ;set bounding box control for small platforms + rts + +;-------------------------------- + +PlatPosDataLow: + .db $08,$0c,$f8 + +PlatPosDataHigh: + .db $00,$00,$ff + +PosPlatform: + lda Enemy_X_Position,x ;get horizontal coordinate + clc + adc PlatPosDataLow,y ;add or subtract pixels depending on offset + sta Enemy_X_Position,x ;store as new horizontal coordinate + lda Enemy_PageLoc,x + adc PlatPosDataHigh,y ;add or subtract page location depending on offset + sta Enemy_PageLoc,x ;store as new page location + rts ;and go back + +;-------------------------------- + +EndOfEnemyInitCode: + rts + +;------------------------------------------------------------------------------------- + +RunEnemyObjectsCore: + ldx ObjectOffset ;get offset for enemy object buffer + lda #$00 ;load value 0 for jump engine by default + ldy Enemy_ID,x + cpy #$15 ;if enemy object < $15, use default value + bcc JmpEO + tya ;otherwise subtract $14 from the value and use + sbc #$14 ;as value for jump engine +JmpEO: jsr JumpEngine + + .dw RunNormalEnemies ;for objects $00-$14 + + .dw RunBowserFlame ;for objects $15-$1f + .dw RunFireworks + .dw NoRunCode + .dw NoRunCode + .dw NoRunCode + .dw NoRunCode + .dw RunFirebarObj + .dw RunFirebarObj + .dw RunFirebarObj + .dw RunFirebarObj + .dw RunFirebarObj + + .dw RunFirebarObj ;for objects $20-$2f + .dw RunFirebarObj + .dw RunFirebarObj + .dw NoRunCode + .dw RunLargePlatform + .dw RunLargePlatform + .dw RunLargePlatform + .dw RunLargePlatform + .dw RunLargePlatform + .dw RunLargePlatform + .dw RunLargePlatform + .dw RunSmallPlatform + .dw RunSmallPlatform + .dw RunBowser + .dw PowerUpObjHandler + .dw VineObjectHandler + + .dw NoRunCode ;for objects $30-$35 + .dw RunStarFlagObj + .dw JumpspringHandler + .dw NoRunCode + .dw WarpZoneObject + .dw RunRetainerObj + +;-------------------------------- + +NoRunCode: + rts + +;-------------------------------- + +RunRetainerObj: + jsr GetEnemyOffscreenBits + jsr RelativeEnemyPosition + jmp EnemyGfxHandler + +;-------------------------------- + +RunNormalEnemies: + lda #$00 ;init sprite attributes + sta Enemy_SprAttrib,x + jsr GetEnemyOffscreenBits + jsr RelativeEnemyPosition + jsr EnemyGfxHandler + jsr GetEnemyBoundBox + jsr EnemyToBGCollisionDet + jsr EnemiesCollision + jsr PlayerEnemyCollision + ldy TimerControl ;if master timer control set, skip to last routine + bne SkipMove + jsr EnemyMovementSubs +SkipMove: jmp OffscreenBoundsCheck + +EnemyMovementSubs: + lda Enemy_ID,x + jsr JumpEngine + + .dw MoveNormalEnemy ;only objects $00-$14 use this table + .dw MoveNormalEnemy + .dw MoveNormalEnemy + .dw MoveNormalEnemy + .dw MoveNormalEnemy + .dw ProcHammerBro + .dw MoveNormalEnemy + .dw MoveBloober + .dw MoveBulletBill + .dw NoMoveCode + .dw MoveSwimmingCheepCheep + .dw MoveSwimmingCheepCheep + .dw MovePodoboo + .dw MovePiranhaPlant + .dw MoveJumpingEnemy + .dw ProcMoveRedPTroopa + .dw MoveFlyGreenPTroopa + .dw MoveLakitu + .dw MoveNormalEnemy + .dw NoMoveCode ;dummy + .dw MoveFlyingCheepCheep + +;-------------------------------- + +NoMoveCode: + rts + +;-------------------------------- + +RunBowserFlame: + jsr ProcBowserFlame + jsr GetEnemyOffscreenBits + jsr RelativeEnemyPosition + jsr GetEnemyBoundBox + jsr PlayerEnemyCollision + jmp OffscreenBoundsCheck + +;-------------------------------- + +RunFirebarObj: + jsr ProcFirebar + jmp OffscreenBoundsCheck + +;-------------------------------- + +RunSmallPlatform: + jsr GetEnemyOffscreenBits + jsr RelativeEnemyPosition + jsr SmallPlatformBoundBox + jsr SmallPlatformCollision + jsr RelativeEnemyPosition + jsr DrawSmallPlatform + jsr MoveSmallPlatform + jmp OffscreenBoundsCheck + +;-------------------------------- + +RunLargePlatform: + jsr GetEnemyOffscreenBits + jsr RelativeEnemyPosition + jsr LargePlatformBoundBox + jsr LargePlatformCollision + lda TimerControl ;if master timer control set, + bne SkipPT ;skip subroutine tree + jsr LargePlatformSubroutines +SkipPT: jsr RelativeEnemyPosition + jsr DrawLargePlatform + jmp OffscreenBoundsCheck + +;-------------------------------- + +LargePlatformSubroutines: + lda Enemy_ID,x ;subtract $24 to get proper offset for jump table + sec + sbc #$24 + jsr JumpEngine + + .dw BalancePlatform ;table used by objects $24-$2a + .dw YMovingPlatform + .dw MoveLargeLiftPlat + .dw MoveLargeLiftPlat + .dw XMovingPlatform + .dw DropPlatform + .dw RightPlatform + +;------------------------------------------------------------------------------------- + +EraseEnemyObject: + lda #$00 ;clear all enemy object variables + sta Enemy_Flag,x + sta Enemy_ID,x + sta Enemy_State,x + sta FloateyNum_Control,x + sta EnemyIntervalTimer,x + sta ShellChainCounter,x + sta Enemy_SprAttrib,x + sta EnemyFrameTimer,x + rts + +;------------------------------------------------------------------------------------- + +MovePodoboo: + lda EnemyIntervalTimer,x ;check enemy timer + bne PdbM ;branch to move enemy if not expired + jsr InitPodoboo ;otherwise set up podoboo again + lda PseudoRandomBitReg+1,x ;get part of LSFR + ora #%10000000 ;set d7 + sta Enemy_Y_MoveForce,x ;store as movement force + and #%00001111 ;mask out high nybble + ora #$06 ;set for at least six intervals + sta EnemyIntervalTimer,x ;store as new enemy timer + lda #$f9 + sta Enemy_Y_Speed,x ;set vertical speed to move podoboo upwards +PdbM: jmp MoveJ_EnemyVertically ;branch to impose gravity on podoboo + +;-------------------------------- +;$00 - used in HammerBroJumpCode as bitmask + +HammerThrowTmrData: + .db $30, $1c + +XSpeedAdderData: + .db $00, $e8, $00, $18 + +RevivedXSpeed: + .db $08, $f8, $0c, $f4 + +ProcHammerBro: + lda Enemy_State,x ;check hammer bro's enemy state for d5 set + and #%00100000 + beq ChkJH ;if not set, go ahead with code + jmp MoveDefeatedEnemy ;otherwise jump to something else +ChkJH: lda HammerBroJumpTimer,x ;check jump timer + beq HammerBroJumpCode ;if expired, branch to jump + dec HammerBroJumpTimer,x ;otherwise decrement jump timer + lda Enemy_OffscreenBits + and #%00001100 ;check offscreen bits + bne MoveHammerBroXDir ;if hammer bro a little offscreen, skip to movement code + lda HammerThrowingTimer,x ;check hammer throwing timer + bne DecHT ;if not expired, skip ahead, do not throw hammer + ldy SecondaryHardMode ;otherwise get secondary hard mode flag + lda HammerThrowTmrData,y ;get timer data using flag as offset + sta HammerThrowingTimer,x ;set as new timer + jsr SpawnHammerObj ;do a sub here to spawn hammer object + bcc DecHT ;if carry clear, hammer not spawned, skip to decrement timer + lda Enemy_State,x + ora #%00001000 ;set d3 in enemy state for hammer throw + sta Enemy_State,x + jmp MoveHammerBroXDir ;jump to move hammer bro +DecHT: dec HammerThrowingTimer,x ;decrement timer + jmp MoveHammerBroXDir ;jump to move hammer bro + +HammerBroJumpLData: + .db $20, $37 + +HammerBroJumpCode: + lda Enemy_State,x ;get hammer bro's enemy state + and #%00000111 ;mask out all but 3 LSB + cmp #$01 ;check for d0 set (for jumping) + beq MoveHammerBroXDir ;if set, branch ahead to moving code + lda #$00 ;load default value here + sta $00 ;save into temp variable for now + ldy #$fa ;set default vertical speed + lda Enemy_Y_Position,x ;check hammer bro's vertical coordinate + bmi SetHJ ;if on the bottom half of the screen, use current speed + ldy #$fd ;otherwise set alternate vertical speed + cmp #$70 ;check to see if hammer bro is above the middle of screen + inc $00 ;increment preset value to $01 + bcc SetHJ ;if above the middle of the screen, use current speed and $01 + dec $00 ;otherwise return value to $00 + lda PseudoRandomBitReg+1,x ;get part of LSFR, mask out all but LSB + and #$01 + bne SetHJ ;if d0 of LSFR set, branch and use current speed and $00 + ldy #$fa ;otherwise reset to default vertical speed +SetHJ: sty Enemy_Y_Speed,x ;set vertical speed for jumping + lda Enemy_State,x ;set d0 in enemy state for jumping + ora #$01 + sta Enemy_State,x + lda $00 ;load preset value here to use as bitmask + and PseudoRandomBitReg+2,x ;and do bit-wise comparison with part of LSFR + tay ;then use as offset + lda SecondaryHardMode ;check secondary hard mode flag + bne HJump + tay ;if secondary hard mode flag clear, set offset to 0 +HJump: lda HammerBroJumpLData,y ;get jump length timer data using offset from before + sta EnemyFrameTimer,x ;save in enemy timer + lda PseudoRandomBitReg+1,x + ora #%11000000 ;get contents of part of LSFR, set d7 and d6, then + sta HammerBroJumpTimer,x ;store in jump timer + +MoveHammerBroXDir: + ldy #$fc ;move hammer bro a little to the left + lda FrameCounter + and #%01000000 ;change hammer bro's direction every 64 frames + bne Shimmy + ldy #$04 ;if d6 set in counter, move him a little to the right +Shimmy: sty Enemy_X_Speed,x ;store horizontal speed + ldy #$01 ;set to face right by default + jsr PlayerEnemyDiff ;get horizontal difference between player and hammer bro + bmi SetShim ;if enemy to the left of player, skip this part + iny ;set to face left + lda EnemyIntervalTimer,x ;check walking timer + bne SetShim ;if not yet expired, skip to set moving direction + lda #$f8 + sta Enemy_X_Speed,x ;otherwise, make the hammer bro walk left towards player +SetShim: sty Enemy_MovingDir,x ;set moving direction + +MoveNormalEnemy: + ldy #$00 ;init Y to leave horizontal movement as-is + lda Enemy_State,x + and #%01000000 ;check enemy state for d6 set, if set skip + bne FallE ;to move enemy vertically, then horizontally if necessary + lda Enemy_State,x + asl ;check enemy state for d7 set + bcs SteadM ;if set, branch to move enemy horizontally + lda Enemy_State,x + and #%00100000 ;check enemy state for d5 set + bne MoveDefeatedEnemy ;if set, branch to move defeated enemy object + lda Enemy_State,x + and #%00000111 ;check d2-d0 of enemy state for any set bits + beq SteadM ;if enemy in normal state, branch to move enemy horizontally + cmp #$05 + beq FallE ;if enemy in state used by spiny's egg, go ahead here + cmp #$03 + bcs ReviveStunned ;if enemy in states $03 or $04, skip ahead to yet another part +FallE: jsr MoveD_EnemyVertically ;do a sub here to move enemy downwards + ldy #$00 + lda Enemy_State,x ;check for enemy state $02 + cmp #$02 + beq MEHor ;if found, branch to move enemy horizontally + and #%01000000 ;check for d6 set + beq SteadM ;if not set, branch to something else + lda Enemy_ID,x + cmp #PowerUpObject ;check for power-up object + beq SteadM + bne SlowM ;if any other object where d6 set, jump to set Y +MEHor: jmp MoveEnemyHorizontally ;jump here to move enemy horizontally for <> $2e and d6 set + +SlowM: ldy #$01 ;if branched here, increment Y to slow horizontal movement +SteadM: lda Enemy_X_Speed,x ;get current horizontal speed + pha ;save to stack + bpl AddHS ;if not moving or moving right, skip, leave Y alone + iny + iny ;otherwise increment Y to next data +AddHS: clc + adc XSpeedAdderData,y ;add value here to slow enemy down if necessary + sta Enemy_X_Speed,x ;save as horizontal speed temporarily + jsr MoveEnemyHorizontally ;then do a sub to move horizontally + pla + sta Enemy_X_Speed,x ;get old horizontal speed from stack and return to + rts ;original memory location, then leave + +ReviveStunned: + lda EnemyIntervalTimer,x ;if enemy timer not expired yet, + bne ChkKillGoomba ;skip ahead to something else + sta Enemy_State,x ;otherwise initialize enemy state to normal + lda FrameCounter + and #$01 ;get d0 of frame counter + tay ;use as Y and increment for movement direction + iny + sty Enemy_MovingDir,x ;store as pseudorandom movement direction + dey ;decrement for use as pointer + lda PrimaryHardMode ;check primary hard mode flag + beq SetRSpd ;if not set, use pointer as-is + iny + iny ;otherwise increment 2 bytes to next data +SetRSpd: lda RevivedXSpeed,y ;load and store new horizontal speed + sta Enemy_X_Speed,x ;and leave + rts + +MoveDefeatedEnemy: + jsr MoveD_EnemyVertically ;execute sub to move defeated enemy downwards + jmp MoveEnemyHorizontally ;now move defeated enemy horizontally + +ChkKillGoomba: + cmp #$0e ;check to see if enemy timer has reached + bne NKGmba ;a certain point, and branch to leave if not + lda Enemy_ID,x + cmp #Goomba ;check for goomba object + bne NKGmba ;branch if not found + jsr EraseEnemyObject ;otherwise, kill this goomba object +NKGmba: rts ;leave! + +;-------------------------------- + +MoveJumpingEnemy: + jsr MoveJ_EnemyVertically ;do a sub to impose gravity on green paratroopa + jmp MoveEnemyHorizontally ;jump to move enemy horizontally + +;-------------------------------- + +ProcMoveRedPTroopa: + lda Enemy_Y_Speed,x + ora Enemy_Y_MoveForce,x ;check for any vertical force or speed + bne MoveRedPTUpOrDown ;branch if any found + sta Enemy_YMF_Dummy,x ;initialize something here + lda Enemy_Y_Position,x ;check current vs. original vertical coordinate + cmp RedPTroopaOrigXPos,x + bcs MoveRedPTUpOrDown ;if current => original, skip ahead to more code + lda FrameCounter ;get frame counter + and #%00000111 ;mask out all but 3 LSB + bne NoIncPT ;if any bits set, branch to leave + inc Enemy_Y_Position,x ;otherwise increment red paratroopa's vertical position +NoIncPT: rts ;leave + +MoveRedPTUpOrDown: + lda Enemy_Y_Position,x ;check current vs. central vertical coordinate + cmp RedPTroopaCenterYPos,x + bcc MovPTDwn ;if current < central, jump to move downwards + jmp MoveRedPTroopaUp ;otherwise jump to move upwards +MovPTDwn: jmp MoveRedPTroopaDown ;move downwards + +;-------------------------------- +;$00 - used to store adder for movement, also used as adder for platform +;$01 - used to store maximum value for secondary counter + +MoveFlyGreenPTroopa: + jsr XMoveCntr_GreenPTroopa ;do sub to increment primary and secondary counters + jsr MoveWithXMCntrs ;do sub to move green paratroopa accordingly, and horizontally + ldy #$01 ;set Y to move green paratroopa down + lda FrameCounter + and #%00000011 ;check frame counter 2 LSB for any bits set + bne NoMGPT ;branch to leave if set to move up/down every fourth frame + lda FrameCounter + and #%01000000 ;check frame counter for d6 set + bne YSway ;branch to move green paratroopa down if set + ldy #$ff ;otherwise set Y to move green paratroopa up +YSway: sty $00 ;store adder here + lda Enemy_Y_Position,x + clc ;add or subtract from vertical position + adc $00 ;to give green paratroopa a wavy flight + sta Enemy_Y_Position,x +NoMGPT: rts ;leave! + +XMoveCntr_GreenPTroopa: + lda #$13 ;load preset maximum value for secondary counter + +XMoveCntr_Platform: + sta $01 ;store value here + lda FrameCounter + and #%00000011 ;branch to leave if not on + bne NoIncXM ;every fourth frame + ldy XMoveSecondaryCounter,x ;get secondary counter + lda XMovePrimaryCounter,x ;get primary counter + lsr + bcs DecSeXM ;if d0 of primary counter set, branch elsewhere + cpy $01 ;compare secondary counter to preset maximum value + beq IncPXM ;if equal, branch ahead of this part + inc XMoveSecondaryCounter,x ;increment secondary counter and leave +NoIncXM: rts +IncPXM: inc XMovePrimaryCounter,x ;increment primary counter and leave + rts +DecSeXM: tya ;put secondary counter in A + beq IncPXM ;if secondary counter at zero, branch back + dec XMoveSecondaryCounter,x ;otherwise decrement secondary counter and leave + rts + +MoveWithXMCntrs: + lda XMoveSecondaryCounter,x ;save secondary counter to stack + pha + ldy #$01 ;set value here by default + lda XMovePrimaryCounter,x + and #%00000010 ;if d1 of primary counter is + bne XMRight ;set, branch ahead of this part here + lda XMoveSecondaryCounter,x + eor #$ff ;otherwise change secondary + clc ;counter to two's compliment + adc #$01 + sta XMoveSecondaryCounter,x + ldy #$02 ;load alternate value here +XMRight: sty Enemy_MovingDir,x ;store as moving direction + jsr MoveEnemyHorizontally + sta $00 ;save value obtained from sub here + pla ;get secondary counter from stack + sta XMoveSecondaryCounter,x ;and return to original place + rts + +;-------------------------------- + +BlooberBitmasks: + .db %00111111, %00000011 + +MoveBloober: + lda Enemy_State,x + and #%00100000 ;check enemy state for d5 set + bne MoveDefeatedBloober ;branch if set to move defeated bloober + ldy SecondaryHardMode ;use secondary hard mode flag as offset + lda PseudoRandomBitReg+1,x ;get LSFR + and BlooberBitmasks,y ;mask out bits in LSFR using bitmask loaded with offset + bne BlooberSwim ;if any bits set, skip ahead to make swim + txa + lsr ;check to see if on second or fourth slot (1 or 3) + bcc FBLeft ;if not, branch to figure out moving direction + ldy Player_MovingDir ;otherwise, load player's moving direction and + bcs SBMDir ;do an unconditional branch to set +FBLeft: ldy #$02 ;set left moving direction by default + jsr PlayerEnemyDiff ;get horizontal difference between player and bloober + bpl SBMDir ;if enemy to the right of player, keep left + dey ;otherwise decrement to set right moving direction +SBMDir: sty Enemy_MovingDir,x ;set moving direction of bloober, then continue on here + +BlooberSwim: + jsr ProcSwimmingB ;execute sub to make bloober swim characteristically + lda Enemy_Y_Position,x ;get vertical coordinate + sec + sbc Enemy_Y_MoveForce,x ;subtract movement force + cmp #$20 ;check to see if position is above edge of status bar + bcc SwimX ;if so, don't do it + sta Enemy_Y_Position,x ;otherwise, set new vertical position, make bloober swim +SwimX: ldy Enemy_MovingDir,x ;check moving direction + dey + bne LeftSwim ;if moving to the left, branch to second part + lda Enemy_X_Position,x + clc ;add movement speed to horizontal coordinate + adc BlooperMoveSpeed,x + sta Enemy_X_Position,x ;store result as new horizontal coordinate + lda Enemy_PageLoc,x + adc #$00 ;add carry to page location + sta Enemy_PageLoc,x ;store as new page location and leave + rts + +LeftSwim: + lda Enemy_X_Position,x + sec ;subtract movement speed from horizontal coordinate + sbc BlooperMoveSpeed,x + sta Enemy_X_Position,x ;store result as new horizontal coordinate + lda Enemy_PageLoc,x + sbc #$00 ;subtract borrow from page location + sta Enemy_PageLoc,x ;store as new page location and leave + rts + +MoveDefeatedBloober: + jmp MoveEnemySlowVert ;jump to move defeated bloober downwards + +ProcSwimmingB: + lda BlooperMoveCounter,x ;get enemy's movement counter + and #%00000010 ;check for d1 set + bne ChkForFloatdown ;branch if set + lda FrameCounter + and #%00000111 ;get 3 LSB of frame counter + pha ;and save it to the stack + lda BlooperMoveCounter,x ;get enemy's movement counter + lsr ;check for d0 set + bcs SlowSwim ;branch if set + pla ;pull 3 LSB of frame counter from the stack + bne BSwimE ;branch to leave, execute code only every eighth frame + lda Enemy_Y_MoveForce,x + clc ;add to movement force to speed up swim + adc #$01 + sta Enemy_Y_MoveForce,x ;set movement force + sta BlooperMoveSpeed,x ;set as movement speed + cmp #$02 + bne BSwimE ;if certain horizontal speed, branch to leave + inc BlooperMoveCounter,x ;otherwise increment movement counter +BSwimE: rts + +SlowSwim: + pla ;pull 3 LSB of frame counter from the stack + bne NoSSw ;branch to leave, execute code only every eighth frame + lda Enemy_Y_MoveForce,x + sec ;subtract from movement force to slow swim + sbc #$01 + sta Enemy_Y_MoveForce,x ;set movement force + sta BlooperMoveSpeed,x ;set as movement speed + bne NoSSw ;if any speed, branch to leave + inc BlooperMoveCounter,x ;otherwise increment movement counter + lda #$02 + sta EnemyIntervalTimer,x ;set enemy's timer +NoSSw: rts ;leave + +ChkForFloatdown: + lda EnemyIntervalTimer,x ;get enemy timer + beq ChkNearPlayer ;branch if expired + +Floatdown: + lda FrameCounter ;get frame counter + lsr ;check for d0 set + bcs NoFD ;branch to leave on every other frame + inc Enemy_Y_Position,x ;otherwise increment vertical coordinate +NoFD: rts ;leave + +ChkNearPlayer: + lda Enemy_Y_Position,x ;get vertical coordinate + adc #$10 ;add sixteen pixels + cmp Player_Y_Position ;compare result with player's vertical coordinate + bcc Floatdown ;if modified vertical less than player's, branch + lda #$00 + sta BlooperMoveCounter,x ;otherwise nullify movement counter + rts + +;-------------------------------- + +MoveBulletBill: + lda Enemy_State,x ;check bullet bill's enemy object state for d5 set + and #%00100000 + beq NotDefB ;if not set, continue with movement code + jmp MoveJ_EnemyVertically ;otherwise jump to move defeated bullet bill downwards +NotDefB: lda #$e8 ;set bullet bill's horizontal speed + sta Enemy_X_Speed,x ;and move it accordingly (note: this bullet bill + jmp MoveEnemyHorizontally ;object occurs in frenzy object $17, not from cannons) + +;-------------------------------- +;$02 - used to hold preset values +;$03 - used to hold enemy state + +SwimCCXMoveData: + .db $40, $80 + .db $04, $04 ;residual data, not used + +MoveSwimmingCheepCheep: + lda Enemy_State,x ;check cheep-cheep's enemy object state + and #%00100000 ;for d5 set + beq CCSwim ;if not set, continue with movement code + jmp MoveEnemySlowVert ;otherwise jump to move defeated cheep-cheep downwards +CCSwim: sta $03 ;save enemy state in $03 + lda Enemy_ID,x ;get enemy identifier + sec + sbc #$0a ;subtract ten for cheep-cheep identifiers + tay ;use as offset + lda SwimCCXMoveData,y ;load value here + sta $02 + lda Enemy_X_MoveForce,x ;load horizontal force + sec + sbc $02 ;subtract preset value from horizontal force + sta Enemy_X_MoveForce,x ;store as new horizontal force + lda Enemy_X_Position,x ;get horizontal coordinate + sbc #$00 ;subtract borrow (thus moving it slowly) + sta Enemy_X_Position,x ;and save as new horizontal coordinate + lda Enemy_PageLoc,x + sbc #$00 ;subtract borrow again, this time from the + sta Enemy_PageLoc,x ;page location, then save + lda #$20 + sta $02 ;save new value here + cpx #$02 ;check enemy object offset + bcc ExSwCC ;if in first or second slot, branch to leave + lda CheepCheepMoveMFlag,x ;check movement flag + cmp #$10 ;if movement speed set to $00, + bcc CCSwimUpwards ;branch to move upwards + lda Enemy_YMF_Dummy,x + clc + adc $02 ;add preset value to dummy variable to get carry + sta Enemy_YMF_Dummy,x ;and save dummy + lda Enemy_Y_Position,x ;get vertical coordinate + adc $03 ;add carry to it plus enemy state to slowly move it downwards + sta Enemy_Y_Position,x ;save as new vertical coordinate + lda Enemy_Y_HighPos,x + adc #$00 ;add carry to page location and + jmp ChkSwimYPos ;jump to end of movement code + +CCSwimUpwards: + lda Enemy_YMF_Dummy,x + sec + sbc $02 ;subtract preset value to dummy variable to get borrow + sta Enemy_YMF_Dummy,x ;and save dummy + lda Enemy_Y_Position,x ;get vertical coordinate + sbc $03 ;subtract borrow to it plus enemy state to slowly move it upwards + sta Enemy_Y_Position,x ;save as new vertical coordinate + lda Enemy_Y_HighPos,x + sbc #$00 ;subtract borrow from page location + +ChkSwimYPos: + sta Enemy_Y_HighPos,x ;save new page location here + ldy #$00 ;load movement speed to upwards by default + lda Enemy_Y_Position,x ;get vertical coordinate + sec + sbc CheepCheepOrigYPos,x ;subtract original coordinate from current + bpl YPDiff ;if result positive, skip to next part + ldy #$10 ;otherwise load movement speed to downwards + eor #$ff + clc ;get two's compliment of result + adc #$01 ;to obtain total difference of original vs. current +YPDiff: cmp #$0f ;if difference between original vs. current vertical + bcc ExSwCC ;coordinates < 15 pixels, leave movement speed alone + tya + sta CheepCheepMoveMFlag,x ;otherwise change movement speed +ExSwCC: rts ;leave + +;-------------------------------- +;$00 - used as counter for firebar parts +;$01 - used for oscillated high byte of spin state or to hold horizontal adder +;$02 - used for oscillated high byte of spin state or to hold vertical adder +;$03 - used for mirror data +;$04 - used to store player's sprite 1 X coordinate +;$05 - used to evaluate mirror data +;$06 - used to store either screen X coordinate or sprite data offset +;$07 - used to store screen Y coordinate +;$ed - used to hold maximum length of firebar +;$ef - used to hold high byte of spinstate + +;horizontal adder is at first byte + high byte of spinstate, +;vertical adder is same + 8 bytes, two's compliment +;if greater than $08 for proper oscillation +FirebarPosLookupTbl: + .db $00, $01, $03, $04, $05, $06, $07, $07, $08 + .db $00, $03, $06, $09, $0b, $0d, $0e, $0f, $10 + .db $00, $04, $09, $0d, $10, $13, $16, $17, $18 + .db $00, $06, $0c, $12, $16, $1a, $1d, $1f, $20 + .db $00, $07, $0f, $16, $1c, $21, $25, $27, $28 + .db $00, $09, $12, $1b, $21, $27, $2c, $2f, $30 + .db $00, $0b, $15, $1f, $27, $2e, $33, $37, $38 + .db $00, $0c, $18, $24, $2d, $35, $3b, $3e, $40 + .db $00, $0e, $1b, $28, $32, $3b, $42, $46, $48 + .db $00, $0f, $1f, $2d, $38, $42, $4a, $4e, $50 + .db $00, $11, $22, $31, $3e, $49, $51, $56, $58 + +FirebarMirrorData: + .db $01, $03, $02, $00 + +FirebarTblOffsets: + .db $00, $09, $12, $1b, $24, $2d + .db $36, $3f, $48, $51, $5a, $63 + +FirebarYPos: + .db $0c, $18 + +ProcFirebar: + jsr GetEnemyOffscreenBits ;get offscreen information + lda Enemy_OffscreenBits ;check for d3 set + and #%00001000 ;if so, branch to leave + bne SkipFBar + lda TimerControl ;if master timer control set, branch + bne SusFbar ;ahead of this part + lda FirebarSpinSpeed,x ;load spinning speed of firebar + jsr FirebarSpin ;modify current spinstate + and #%00011111 ;mask out all but 5 LSB + sta FirebarSpinState_High,x ;and store as new high byte of spinstate +SusFbar: lda FirebarSpinState_High,x ;get high byte of spinstate + ldy Enemy_ID,x ;check enemy identifier + cpy #$1f + bcc SetupGFB ;if < $1f (long firebar), branch + cmp #$08 ;check high byte of spinstate + beq SkpFSte ;if eight, branch to change + cmp #$18 + bne SetupGFB ;if not at twenty-four branch to not change +SkpFSte: clc + adc #$01 ;add one to spinning thing to avoid horizontal state + sta FirebarSpinState_High,x +SetupGFB: sta $ef ;save high byte of spinning thing, modified or otherwise + jsr RelativeEnemyPosition ;get relative coordinates to screen + jsr GetFirebarPosition ;do a sub here (residual, too early to be used now) + ldy Enemy_SprDataOffset,x ;get OAM data offset + lda Enemy_Rel_YPos ;get relative vertical coordinate + sta Sprite_Y_Position,y ;store as Y in OAM data + sta $07 ;also save here + lda Enemy_Rel_XPos ;get relative horizontal coordinate + sta Sprite_X_Position,y ;store as X in OAM data + sta $06 ;also save here + lda #$01 + sta $00 ;set $01 value here (not necessary) + jsr FirebarCollision ;draw fireball part and do collision detection + ldy #$05 ;load value for short firebars by default + lda Enemy_ID,x + cmp #$1f ;are we doing a long firebar? + bcc SetMFbar ;no, branch then + ldy #$0b ;otherwise load value for long firebars +SetMFbar: sty $ed ;store maximum value for length of firebars + lda #$00 + sta $00 ;initialize counter here +DrawFbar: lda $ef ;load high byte of spinstate + jsr GetFirebarPosition ;get fireball position data depending on firebar part + jsr DrawFirebar_Collision ;position it properly, draw it and do collision detection + lda $00 ;check which firebar part + cmp #$04 + bne NextFbar + ldy DuplicateObj_Offset ;if we arrive at fifth firebar part, + lda Enemy_SprDataOffset,y ;get offset from long firebar and load OAM data offset + sta $06 ;using long firebar offset, then store as new one here +NextFbar: inc $00 ;move onto the next firebar part + lda $00 + cmp $ed ;if we end up at the maximum part, go on and leave + bcc DrawFbar ;otherwise go back and do another +SkipFBar: rts + +DrawFirebar_Collision: + lda $03 ;store mirror data elsewhere + sta $05 + ldy $06 ;load OAM data offset for firebar + lda $01 ;load horizontal adder we got from position loader + lsr $05 ;shift LSB of mirror data + bcs AddHA ;if carry was set, skip this part + eor #$ff + adc #$01 ;otherwise get two's compliment of horizontal adder +AddHA: clc ;add horizontal coordinate relative to screen to + adc Enemy_Rel_XPos ;horizontal adder, modified or otherwise + sta Sprite_X_Position,y ;store as X coordinate here + sta $06 ;store here for now, note offset is saved in Y still + cmp Enemy_Rel_XPos ;compare X coordinate of sprite to original X of firebar + bcs SubtR1 ;if sprite coordinate => original coordinate, branch + lda Enemy_Rel_XPos + sec ;otherwise subtract sprite X from the + sbc $06 ;original one and skip this part + jmp ChkFOfs +SubtR1: sec ;subtract original X from the + sbc Enemy_Rel_XPos ;current sprite X +ChkFOfs: cmp #$59 ;if difference of coordinates within a certain range, + bcc VAHandl ;continue by handling vertical adder + lda #$f8 ;otherwise, load offscreen Y coordinate + bne SetVFbr ;and unconditionally branch to move sprite offscreen +VAHandl: lda Enemy_Rel_YPos ;if vertical relative coordinate offscreen, + cmp #$f8 ;skip ahead of this part and write into sprite Y coordinate + beq SetVFbr + lda $02 ;load vertical adder we got from position loader + lsr $05 ;shift LSB of mirror data one more time + bcs AddVA ;if carry was set, skip this part + eor #$ff + adc #$01 ;otherwise get two's compliment of second part +AddVA: clc ;add vertical coordinate relative to screen to + adc Enemy_Rel_YPos ;the second data, modified or otherwise +SetVFbr: sta Sprite_Y_Position,y ;store as Y coordinate here + sta $07 ;also store here for now + +FirebarCollision: + jsr DrawFirebar ;run sub here to draw current tile of firebar + tya ;return OAM data offset and save + pha ;to the stack for now + lda StarInvincibleTimer ;if star mario invincibility timer + ora TimerControl ;or master timer controls set + bne NoColFB ;then skip all of this + sta $05 ;otherwise initialize counter + ldy Player_Y_HighPos + dey ;if player's vertical high byte offscreen, + bne NoColFB ;skip all of this + ldy Player_Y_Position ;get player's vertical position + lda PlayerSize ;get player's size + bne AdjSm ;if player small, branch to alter variables + lda CrouchingFlag + beq BigJp ;if player big and not crouching, jump ahead +AdjSm: inc $05 ;if small or big but crouching, execute this part + inc $05 ;first increment our counter twice (setting $02 as flag) + tya + clc ;then add 24 pixels to the player's + adc #$18 ;vertical coordinate + tay +BigJp: tya ;get vertical coordinate, altered or otherwise, from Y +FBCLoop: sec ;subtract vertical position of firebar + sbc $07 ;from the vertical coordinate of the player + bpl ChkVFBD ;if player lower on the screen than firebar, + eor #$ff ;skip two's compliment part + clc ;otherwise get two's compliment + adc #$01 +ChkVFBD: cmp #$08 ;if difference => 8 pixels, skip ahead of this part + bcs Chk2Ofs + lda $06 ;if firebar on far right on the screen, skip this, + cmp #$f0 ;because, really, what's the point? + bcs Chk2Ofs + lda Sprite_X_Position+4 ;get OAM X coordinate for sprite #1 + clc + adc #$04 ;add four pixels + sta $04 ;store here + sec ;subtract horizontal coordinate of firebar + sbc $06 ;from the X coordinate of player's sprite 1 + bpl ChkFBCl ;if modded X coordinate to the right of firebar + eor #$ff ;skip two's compliment part + clc ;otherwise get two's compliment + adc #$01 +ChkFBCl: cmp #$08 ;if difference < 8 pixels, collision, thus branch + bcc ChgSDir ;to process +Chk2Ofs: lda $05 ;if value of $02 was set earlier for whatever reason, + cmp #$02 ;branch to increment OAM offset and leave, no collision + beq NoColFB + ldy $05 ;otherwise get temp here and use as offset + lda Player_Y_Position + clc + adc FirebarYPos,y ;add value loaded with offset to player's vertical coordinate + inc $05 ;then increment temp and jump back + jmp FBCLoop +ChgSDir: ldx #$01 ;set movement direction by default + lda $04 ;if OAM X coordinate of player's sprite 1 + cmp $06 ;is greater than horizontal coordinate of firebar + bcs SetSDir ;then do not alter movement direction + inx ;otherwise increment it +SetSDir: stx Enemy_MovingDir ;store movement direction here + ldx #$00 + lda $00 ;save value written to $00 to stack + pha + jsr InjurePlayer ;perform sub to hurt or kill player + pla + sta $00 ;get value of $00 from stack +NoColFB: pla ;get OAM data offset + clc ;add four to it and save + adc #$04 + sta $06 + ldx ObjectOffset ;get enemy object buffer offset and leave + rts + +GetFirebarPosition: + pha ;save high byte of spinstate to the stack + and #%00001111 ;mask out low nybble + cmp #$09 + bcc GetHAdder ;if lower than $09, branch ahead + eor #%00001111 ;otherwise get two's compliment to oscillate + clc + adc #$01 +GetHAdder: sta $01 ;store result, modified or not, here + ldy $00 ;load number of firebar ball where we're at + lda FirebarTblOffsets,y ;load offset to firebar position data + clc + adc $01 ;add oscillated high byte of spinstate + tay ;to offset here and use as new offset + lda FirebarPosLookupTbl,y ;get data here and store as horizontal adder + sta $01 + pla ;pull whatever was in A from the stack + pha ;save it again because we still need it + clc + adc #$08 ;add eight this time, to get vertical adder + and #%00001111 ;mask out high nybble + cmp #$09 ;if lower than $09, branch ahead + bcc GetVAdder + eor #%00001111 ;otherwise get two's compliment + clc + adc #$01 +GetVAdder: sta $02 ;store result here + ldy $00 + lda FirebarTblOffsets,y ;load offset to firebar position data again + clc + adc $02 ;this time add value in $02 to offset here and use as offset + tay + lda FirebarPosLookupTbl,y ;get data here and store as vertica adder + sta $02 + pla ;pull out whatever was in A one last time + lsr ;divide by eight or shift three to the right + lsr + lsr + tay ;use as offset + lda FirebarMirrorData,y ;load mirroring data here + sta $03 ;store + rts + +;-------------------------------- + +PRandomSubtracter: + .db $f8, $a0, $70, $bd, $00 + +FlyCCBPriority: + .db $20, $20, $20, $00, $00 + +MoveFlyingCheepCheep: + lda Enemy_State,x ;check cheep-cheep's enemy state + and #%00100000 ;for d5 set + beq FlyCC ;branch to continue code if not set + lda #$00 + sta Enemy_SprAttrib,x ;otherwise clear sprite attributes + jmp MoveJ_EnemyVertically ;and jump to move defeated cheep-cheep downwards +FlyCC: jsr MoveEnemyHorizontally ;move cheep-cheep horizontally based on speed and force + ldy #$0d ;set vertical movement amount + lda #$05 ;set maximum speed + jsr SetXMoveAmt ;branch to impose gravity on flying cheep-cheep + lda Enemy_Y_MoveForce,x + lsr ;get vertical movement force and + lsr ;move high nybble to low + lsr + lsr + tay ;save as offset (note this tends to go into reach of code) + lda Enemy_Y_Position,x ;get vertical position + sec ;subtract pseudorandom value based on offset from position + sbc PRandomSubtracter,y + bpl AddCCF ;if result within top half of screen, skip this part + eor #$ff + clc ;otherwise get two's compliment + adc #$01 +AddCCF: cmp #$08 ;if result or two's compliment greater than eight, + bcs BPGet ;skip to the end without changing movement force + lda Enemy_Y_MoveForce,x + clc + adc #$10 ;otherwise add to it + sta Enemy_Y_MoveForce,x + lsr ;move high nybble to low again + lsr + lsr + lsr + tay +BPGet: lda FlyCCBPriority,y ;load bg priority data and store (this is very likely + sta Enemy_SprAttrib,x ;broken or residual code, value is overwritten before + rts ;drawing it next frame), then leave + +;-------------------------------- +;$00 - used to hold horizontal difference +;$01-$03 - used to hold difference adjusters + +LakituDiffAdj: + .db $15, $30, $40 + +MoveLakitu: + lda Enemy_State,x ;check lakitu's enemy state + and #%00100000 ;for d5 set + beq ChkLS ;if not set, continue with code + jmp MoveD_EnemyVertically ;otherwise jump to move defeated lakitu downwards +ChkLS: lda Enemy_State,x ;if lakitu's enemy state not set at all, + beq Fr12S ;go ahead and continue with code + lda #$00 + sta LakituMoveDirection,x ;otherwise initialize moving direction to move to left + sta EnemyFrenzyBuffer ;initialize frenzy buffer + lda #$10 + bne SetLSpd ;load horizontal speed and do unconditional branch +Fr12S: lda #Spiny + sta EnemyFrenzyBuffer ;set spiny identifier in frenzy buffer + ldy #$02 +LdLDa: lda LakituDiffAdj,y ;load values + sta $0001,y ;store in zero page + dey + bpl LdLDa ;do this until all values are stired + jsr PlayerLakituDiff ;execute sub to set speed and create spinys +SetLSpd: sta LakituMoveSpeed,x ;set movement speed returned from sub + ldy #$01 ;set moving direction to right by default + lda LakituMoveDirection,x + and #$01 ;get LSB of moving direction + bne SetLMov ;if set, branch to the end to use moving direction + lda LakituMoveSpeed,x + eor #$ff ;get two's compliment of moving speed + clc + adc #$01 + sta LakituMoveSpeed,x ;store as new moving speed + iny ;increment moving direction to left +SetLMov: sty Enemy_MovingDir,x ;store moving direction + jmp MoveEnemyHorizontally ;move lakitu horizontally + +PlayerLakituDiff: + ldy #$00 ;set Y for default value + jsr PlayerEnemyDiff ;get horizontal difference between enemy and player + bpl ChkLakDif ;branch if enemy is to the right of the player + iny ;increment Y for left of player + lda $00 + eor #$ff ;get two's compliment of low byte of horizontal difference + clc + adc #$01 ;store two's compliment as horizontal difference + sta $00 +ChkLakDif: lda $00 ;get low byte of horizontal difference + cmp #$3c ;if within a certain distance of player, branch + bcc ChkPSpeed + lda #$3c ;otherwise set maximum distance + sta $00 + lda Enemy_ID,x ;check if lakitu is in our current enemy slot + cmp #Lakitu + bne ChkPSpeed ;if not, branch elsewhere + tya ;compare contents of Y, now in A + cmp LakituMoveDirection,x ;to what is being used as horizontal movement direction + beq ChkPSpeed ;if moving toward the player, branch, do not alter + lda LakituMoveDirection,x ;if moving to the left beyond maximum distance, + beq SetLMovD ;branch and alter without delay + dec LakituMoveSpeed,x ;decrement horizontal speed + lda LakituMoveSpeed,x ;if horizontal speed not yet at zero, branch to leave + bne ExMoveLak +SetLMovD: tya ;set horizontal direction depending on horizontal + sta LakituMoveDirection,x ;difference between enemy and player if necessary +ChkPSpeed: lda $00 + and #%00111100 ;mask out all but four bits in the middle + lsr ;divide masked difference by four + lsr + sta $00 ;store as new value + ldy #$00 ;init offset + lda Player_X_Speed + beq SubDifAdj ;if player not moving horizontally, branch + lda ScrollAmount + beq SubDifAdj ;if scroll speed not set, branch to same place + iny ;otherwise increment offset + lda Player_X_Speed + cmp #$19 ;if player not running, branch + bcc ChkSpinyO + lda ScrollAmount + cmp #$02 ;if scroll speed below a certain amount, branch + bcc ChkSpinyO ;to same place + iny ;otherwise increment once more +ChkSpinyO: lda Enemy_ID,x ;check for spiny object + cmp #Spiny + bne ChkEmySpd ;branch if not found + lda Player_X_Speed ;if player not moving, skip this part + bne SubDifAdj +ChkEmySpd: lda Enemy_Y_Speed,x ;check vertical speed + bne SubDifAdj ;branch if nonzero + ldy #$00 ;otherwise reinit offset +SubDifAdj: lda $0001,y ;get one of three saved values from earlier + ldy $00 ;get saved horizontal difference +SPixelLak: sec ;subtract one for each pixel of horizontal difference + sbc #$01 ;from one of three saved values + dey + bpl SPixelLak ;branch until all pixels are subtracted, to adjust difference +ExMoveLak: rts ;leave!!! + +;------------------------------------------------------------------------------------- +;$04-$05 - used to store name table address in little endian order + +BridgeCollapseData: + .db $1a ;axe + .db $58 ;chain + .db $98, $96, $94, $92, $90, $8e, $8c ;bridge + .db $8a, $88, $86, $84, $82, $80 + +BridgeCollapse: + ldx BowserFront_Offset ;get enemy offset for bowser + lda Enemy_ID,x ;check enemy object identifier for bowser + cmp #Bowser ;if not found, branch ahead, + bne SetM2 ;metatile removal not necessary + stx ObjectOffset ;store as enemy offset here + lda Enemy_State,x ;if bowser in normal state, skip all of this + beq RemoveBridge + and #%01000000 ;if bowser's state has d6 clear, skip to silence music + beq SetM2 + lda Enemy_Y_Position,x ;check bowser's vertical coordinate + cmp #$e0 ;if bowser not yet low enough, skip this part ahead + bcc MoveD_Bowser +SetM2: lda #Silence ;silence music + sta EventMusicQueue + inc OperMode_Task ;move onto next secondary mode in autoctrl mode + jmp KillAllEnemies ;jump to empty all enemy slots and then leave + +MoveD_Bowser: + jsr MoveEnemySlowVert ;do a sub to move bowser downwards + jmp BowserGfxHandler ;jump to draw bowser's front and rear, then leave + +RemoveBridge: + dec BowserFeetCounter ;decrement timer to control bowser's feet + bne NoBFall ;if not expired, skip all of this + lda #$04 + sta BowserFeetCounter ;otherwise, set timer now + lda BowserBodyControls + eor #$01 ;invert bit to control bowser's feet + sta BowserBodyControls + lda #$22 ;put high byte of name table address here for now + sta $05 + ldy BridgeCollapseOffset ;get bridge collapse offset here + lda BridgeCollapseData,y ;load low byte of name table address and store here + sta $04 + ldy VRAM_Buffer1_Offset ;increment vram buffer offset + iny + ldx #$0c ;set offset for tile data for sub to draw blank metatile + jsr RemBridge ;do sub here to remove bowser's bridge metatiles + ldx ObjectOffset ;get enemy offset + jsr MoveVOffset ;set new vram buffer offset + lda #Sfx_Blast ;load the fireworks/gunfire sound into the square 2 sfx + sta Square2SoundQueue ;queue while at the same time loading the brick + lda #Sfx_BrickShatter ;shatter sound into the noise sfx queue thus + sta NoiseSoundQueue ;producing the unique sound of the bridge collapsing + inc BridgeCollapseOffset ;increment bridge collapse offset + lda BridgeCollapseOffset + cmp #$0f ;if bridge collapse offset has not yet reached + bne NoBFall ;the end, go ahead and skip this part + jsr InitVStf ;initialize whatever vertical speed bowser has + lda #%01000000 + sta Enemy_State,x ;set bowser's state to one of defeated states (d6 set) + lda #Sfx_BowserFall + sta Square2SoundQueue ;play bowser defeat sound +NoBFall: jmp BowserGfxHandler ;jump to code that draws bowser + +;-------------------------------- + +PRandomRange: + .db $21, $41, $11, $31 + +RunBowser: + lda Enemy_State,x ;if d5 in enemy state is not set + and #%00100000 ;then branch elsewhere to run bowser + beq BowserControl + lda Enemy_Y_Position,x ;otherwise check vertical position + cmp #$e0 ;if above a certain point, branch to move defeated bowser + bcc MoveD_Bowser ;otherwise proceed to KillAllEnemies + +KillAllEnemies: + ldx #$04 ;start with last enemy slot +KillLoop: jsr EraseEnemyObject ;branch to kill enemy objects + dex ;move onto next enemy slot + bpl KillLoop ;do this until all slots are emptied + sta EnemyFrenzyBuffer ;empty frenzy buffer + ldx ObjectOffset ;get enemy object offset and leave + rts + +BowserControl: + lda #$00 + sta EnemyFrenzyBuffer ;empty frenzy buffer + lda TimerControl ;if master timer control not set, + beq ChkMouth ;skip jump and execute code here + jmp SkipToFB ;otherwise, jump over a bunch of code +ChkMouth: lda BowserBodyControls ;check bowser's mouth + bpl FeetTmr ;if bit clear, go ahead with code here + jmp HammerChk ;otherwise skip a whole section starting here +FeetTmr: dec BowserFeetCounter ;decrement timer to control bowser's feet + bne ResetMDr ;if not expired, skip this part + lda #$20 ;otherwise, reset timer + sta BowserFeetCounter + lda BowserBodyControls ;and invert bit used + eor #%00000001 ;to control bowser's feet + sta BowserBodyControls +ResetMDr: lda FrameCounter ;check frame counter + and #%00001111 ;if not on every sixteenth frame, skip + bne B_FaceP ;ahead to continue code + lda #$02 ;otherwise reset moving/facing direction every + sta Enemy_MovingDir,x ;sixteen frames +B_FaceP: lda EnemyFrameTimer,x ;if timer set here expired, + beq GetPRCmp ;branch to next section + jsr PlayerEnemyDiff ;get horizontal difference between player and bowser, + bpl GetPRCmp ;and branch if bowser to the right of the player + lda #$01 + sta Enemy_MovingDir,x ;set bowser to move and face to the right + lda #$02 + sta BowserMovementSpeed ;set movement speed + lda #$20 + sta EnemyFrameTimer,x ;set timer here + sta BowserFireBreathTimer ;set timer used for bowser's flame + lda Enemy_X_Position,x + cmp #$c8 ;if bowser to the right past a certain point, + bcs HammerChk ;skip ahead to some other section +GetPRCmp: lda FrameCounter ;get frame counter + and #%00000011 + bne HammerChk ;execute this code every fourth frame, otherwise branch + lda Enemy_X_Position,x + cmp BowserOrigXPos ;if bowser not at original horizontal position, + bne GetDToO ;branch to skip this part + lda PseudoRandomBitReg,x + and #%00000011 ;get pseudorandom offset + tay + lda PRandomRange,y ;load value using pseudorandom offset + sta MaxRangeFromOrigin ;and store here +GetDToO: lda Enemy_X_Position,x + clc ;add movement speed to bowser's horizontal + adc BowserMovementSpeed ;coordinate and save as new horizontal position + sta Enemy_X_Position,x + ldy Enemy_MovingDir,x + cpy #$01 ;if bowser moving and facing to the right, skip ahead + beq HammerChk + ldy #$ff ;set default movement speed here (move left) + sec ;get difference of current vs. original + sbc BowserOrigXPos ;horizontal position + bpl CompDToO ;if current position to the right of original, skip ahead + eor #$ff + clc ;get two's compliment + adc #$01 + ldy #$01 ;set alternate movement speed here (move right) +CompDToO: cmp MaxRangeFromOrigin ;compare difference with pseudorandom value + bcc HammerChk ;if difference < pseudorandom value, leave speed alone + sty BowserMovementSpeed ;otherwise change bowser's movement speed +HammerChk: lda EnemyFrameTimer,x ;if timer set here not expired yet, skip ahead to + bne MakeBJump ;some other section of code + jsr MoveEnemySlowVert ;otherwise start by moving bowser downwards + lda WorldNumber ;check world number + cmp #World6 + bcc SetHmrTmr ;if world 1-5, skip this part (not time to throw hammers yet) + lda FrameCounter + and #%00000011 ;check to see if it's time to execute sub + bne SetHmrTmr ;if not, skip sub, otherwise + jsr SpawnHammerObj ;execute sub on every fourth frame to spawn misc object (hammer) +SetHmrTmr: lda Enemy_Y_Position,x ;get current vertical position + cmp #$80 ;if still above a certain point + bcc ChkFireB ;then skip to world number check for flames + lda PseudoRandomBitReg,x + and #%00000011 ;get pseudorandom offset + tay + lda PRandomRange,y ;get value using pseudorandom offset + sta EnemyFrameTimer,x ;set for timer here +SkipToFB: jmp ChkFireB ;jump to execute flames code +MakeBJump: cmp #$01 ;if timer not yet about to expire, + bne ChkFireB ;skip ahead to next part + dec Enemy_Y_Position,x ;otherwise decrement vertical coordinate + jsr InitVStf ;initialize movement amount + lda #$fe + sta Enemy_Y_Speed,x ;set vertical speed to move bowser upwards +ChkFireB: lda WorldNumber ;check world number here + cmp #World8 ;world 8? + beq SpawnFBr ;if so, execute this part here + cmp #World6 ;world 6-7? + bcs BowserGfxHandler ;if so, skip this part here +SpawnFBr: lda BowserFireBreathTimer ;check timer here + bne BowserGfxHandler ;if not expired yet, skip all of this + lda #$20 + sta BowserFireBreathTimer ;set timer here + lda BowserBodyControls + eor #%10000000 ;invert bowser's mouth bit to open + sta BowserBodyControls ;and close bowser's mouth + bmi ChkFireB ;if bowser's mouth open, loop back + jsr SetFlameTimer ;get timing for bowser's flame + ldy SecondaryHardMode + beq SetFBTmr ;if secondary hard mode flag not set, skip this + sec + sbc #$10 ;otherwise subtract from value in A +SetFBTmr: sta BowserFireBreathTimer ;set value as timer here + lda #BowserFlame ;put bowser's flame identifier + sta EnemyFrenzyBuffer ;in enemy frenzy buffer + +;-------------------------------- + +BowserGfxHandler: + jsr ProcessBowserHalf ;do a sub here to process bowser's front + ldy #$10 ;load default value here to position bowser's rear + lda Enemy_MovingDir,x ;check moving direction + lsr + bcc CopyFToR ;if moving left, use default + ldy #$f0 ;otherwise load alternate positioning value here +CopyFToR: tya ;move bowser's rear object position value to A + clc + adc Enemy_X_Position,x ;add to bowser's front object horizontal coordinate + ldy DuplicateObj_Offset ;get bowser's rear object offset + sta Enemy_X_Position,y ;store A as bowser's rear horizontal coordinate + lda Enemy_Y_Position,x + clc ;add eight pixels to bowser's front object + adc #$08 ;vertical coordinate and store as vertical coordinate + sta Enemy_Y_Position,y ;for bowser's rear + lda Enemy_State,x + sta Enemy_State,y ;copy enemy state directly from front to rear + lda Enemy_MovingDir,x + sta Enemy_MovingDir,y ;copy moving direction also + lda ObjectOffset ;save enemy object offset of front to stack + pha + ldx DuplicateObj_Offset ;put enemy object offset of rear as current + stx ObjectOffset + lda #Bowser ;set bowser's enemy identifier + sta Enemy_ID,x ;store in bowser's rear object + jsr ProcessBowserHalf ;do a sub here to process bowser's rear + pla + sta ObjectOffset ;get original enemy object offset + tax + lda #$00 ;nullify bowser's front/rear graphics flag + sta BowserGfxFlag +ExBGfxH: rts ;leave! + +ProcessBowserHalf: + inc BowserGfxFlag ;increment bowser's graphics flag, then run subroutines + jsr RunRetainerObj ;to get offscreen bits, relative position and draw bowser (finally!) + lda Enemy_State,x + bne ExBGfxH ;if either enemy object not in normal state, branch to leave + lda #$0a + sta Enemy_BoundBoxCtrl,x ;set bounding box size control + jsr GetEnemyBoundBox ;get bounding box coordinates + jmp PlayerEnemyCollision ;do player-to-enemy collision detection + +;------------------------------------------------------------------------------------- +;$00 - used to hold movement force and tile number +;$01 - used to hold sprite attribute data + +FlameTimerData: + .db $bf, $40, $bf, $bf, $bf, $40, $40, $bf + +SetFlameTimer: + ldy BowserFlameTimerCtrl ;load counter as offset + inc BowserFlameTimerCtrl ;increment + lda BowserFlameTimerCtrl ;mask out all but 3 LSB + and #%00000111 ;to keep in range of 0-7 + sta BowserFlameTimerCtrl + lda FlameTimerData,y ;load value to be used then leave +ExFl: rts + +ProcBowserFlame: + lda TimerControl ;if master timer control flag set, + bne SetGfxF ;skip all of this + lda #$40 ;load default movement force + ldy SecondaryHardMode + beq SFlmX ;if secondary hard mode flag not set, use default + lda #$60 ;otherwise load alternate movement force to go faster +SFlmX: sta $00 ;store value here + lda Enemy_X_MoveForce,x + sec ;subtract value from movement force + sbc $00 + sta Enemy_X_MoveForce,x ;save new value + lda Enemy_X_Position,x + sbc #$01 ;subtract one from horizontal position to move + sta Enemy_X_Position,x ;to the left + lda Enemy_PageLoc,x + sbc #$00 ;subtract borrow from page location + sta Enemy_PageLoc,x + ldy BowserFlamePRandomOfs,x ;get some value here and use as offset + lda Enemy_Y_Position,x ;load vertical coordinate + cmp FlameYPosData,y ;compare against coordinate data using $0417,x as offset + beq SetGfxF ;if equal, branch and do not modify coordinate + clc + adc Enemy_Y_MoveForce,x ;otherwise add value here to coordinate and store + sta Enemy_Y_Position,x ;as new vertical coordinate +SetGfxF: jsr RelativeEnemyPosition ;get new relative coordinates + lda Enemy_State,x ;if bowser's flame not in normal state, + bne ExFl ;branch to leave + lda #$51 ;otherwise, continue + sta $00 ;write first tile number + ldy #$02 ;load attributes without vertical flip by default + lda FrameCounter + and #%00000010 ;invert vertical flip bit every 2 frames + beq FlmeAt ;if d1 not set, write default value + ldy #$82 ;otherwise write value with vertical flip bit set +FlmeAt: sty $01 ;set bowser's flame sprite attributes here + ldy Enemy_SprDataOffset,x ;get OAM data offset + ldx #$00 + +DrawFlameLoop: + lda Enemy_Rel_YPos ;get Y relative coordinate of current enemy object + sta Sprite_Y_Position,y ;write into Y coordinate of OAM data + lda $00 + sta Sprite_Tilenumber,y ;write current tile number into OAM data + inc $00 ;increment tile number to draw more bowser's flame + lda $01 + sta Sprite_Attributes,y ;write saved attributes into OAM data + lda Enemy_Rel_XPos + sta Sprite_X_Position,y ;write X relative coordinate of current enemy object + clc + adc #$08 + sta Enemy_Rel_XPos ;then add eight to it and store + iny + iny + iny + iny ;increment Y four times to move onto the next OAM + inx ;move onto the next OAM, and branch if three + cpx #$03 ;have not yet been done + bcc DrawFlameLoop + ldx ObjectOffset ;reload original enemy offset + jsr GetEnemyOffscreenBits ;get offscreen information + ldy Enemy_SprDataOffset,x ;get OAM data offset + lda Enemy_OffscreenBits ;get enemy object offscreen bits + lsr ;move d0 to carry and result to stack + pha + bcc M3FOfs ;branch if carry not set + lda #$f8 ;otherwise move sprite offscreen, this part likely + sta Sprite_Y_Position+12,y ;residual since flame is only made of three sprites +M3FOfs: pla ;get bits from stack + lsr ;move d1 to carry and move bits back to stack + pha + bcc M2FOfs ;branch if carry not set again + lda #$f8 ;otherwise move third sprite offscreen + sta Sprite_Y_Position+8,y +M2FOfs: pla ;get bits from stack again + lsr ;move d2 to carry and move bits back to stack again + pha + bcc M1FOfs ;branch if carry not set yet again + lda #$f8 ;otherwise move second sprite offscreen + sta Sprite_Y_Position+4,y +M1FOfs: pla ;get bits from stack one last time + lsr ;move d3 to carry + bcc ExFlmeD ;branch if carry not set one last time + lda #$f8 + sta Sprite_Y_Position,y ;otherwise move first sprite offscreen +ExFlmeD: rts ;leave + +;-------------------------------- + +RunFireworks: + dec ExplosionTimerCounter,x ;decrement explosion timing counter here + bne SetupExpl ;if not expired, skip this part + lda #$08 + sta ExplosionTimerCounter,x ;reset counter + inc ExplosionGfxCounter,x ;increment explosion graphics counter + lda ExplosionGfxCounter,x + cmp #$03 ;check explosion graphics counter + bcs FireworksSoundScore ;if at a certain point, branch to kill this object +SetupExpl: jsr RelativeEnemyPosition ;get relative coordinates of explosion + lda Enemy_Rel_YPos ;copy relative coordinates + sta Fireball_Rel_YPos ;from the enemy object to the fireball object + lda Enemy_Rel_XPos ;first vertical, then horizontal + sta Fireball_Rel_XPos + ldy Enemy_SprDataOffset,x ;get OAM data offset + lda ExplosionGfxCounter,x ;get explosion graphics counter + jsr DrawExplosion_Fireworks ;do a sub to draw the explosion then leave + rts + +FireworksSoundScore: + lda #$00 ;disable enemy buffer flag + sta Enemy_Flag,x + lda #Sfx_Blast ;play fireworks/gunfire sound + sta Square2SoundQueue + lda #$05 ;set part of score modifier for 500 points + sta DigitModifier+4 + jmp EndAreaPoints ;jump to award points accordingly then leave + +;-------------------------------- + +StarFlagYPosAdder: + .db $00, $00, $08, $08 + +StarFlagXPosAdder: + .db $00, $08, $00, $08 + +StarFlagTileData: + .db $54, $55, $56, $57 + +RunStarFlagObj: + lda #$00 ;initialize enemy frenzy buffer + sta EnemyFrenzyBuffer + lda StarFlagTaskControl ;check star flag object task number here + cmp #$05 ;if greater than 5, branch to exit + bcs StarFlagExit + jsr JumpEngine ;otherwise jump to appropriate sub + + .dw StarFlagExit + .dw GameTimerFireworks + .dw AwardGameTimerPoints + .dw RaiseFlagSetoffFWorks + .dw DelayToAreaEnd + +GameTimerFireworks: + ldy #$05 ;set default state for star flag object + lda GameTimerDisplay+2 ;get game timer's last digit + cmp #$01 + beq SetFWC ;if last digit of game timer set to 1, skip ahead + ldy #$03 ;otherwise load new value for state + cmp #$03 + beq SetFWC ;if last digit of game timer set to 3, skip ahead + ldy #$00 ;otherwise load one more potential value for state + cmp #$06 + beq SetFWC ;if last digit of game timer set to 6, skip ahead + lda #$ff ;otherwise set value for no fireworks +SetFWC: sta FireworksCounter ;set fireworks counter here + sty Enemy_State,x ;set whatever state we have in star flag object + +IncrementSFTask1: + inc StarFlagTaskControl ;increment star flag object task number + +StarFlagExit: + rts ;leave + +AwardGameTimerPoints: + lda GameTimerDisplay ;check all game timer digits for any intervals left + ora GameTimerDisplay+1 + ora GameTimerDisplay+2 + beq IncrementSFTask1 ;if no time left on game timer at all, branch to next task + lda FrameCounter + and #%00000100 ;check frame counter for d2 set (skip ahead + beq NoTTick ;for four frames every four frames) branch if not set + lda #Sfx_TimerTick + sta Square2SoundQueue ;load timer tick sound +NoTTick: ldy #$23 ;set offset here to subtract from game timer's last digit + lda #$ff ;set adder here to $ff, or -1, to subtract one + sta DigitModifier+5 ;from the last digit of the game timer + jsr DigitsMathRoutine ;subtract digit + lda #$05 ;set now to add 50 points + sta DigitModifier+5 ;per game timer interval subtracted + +EndAreaPoints: + ldy #$0b ;load offset for mario's score by default + lda CurrentPlayer ;check player on the screen + beq ELPGive ;if mario, do not change + ldy #$11 ;otherwise load offset for luigi's score +ELPGive: jsr DigitsMathRoutine ;award 50 points per game timer interval + lda CurrentPlayer ;get player on the screen (or 500 points per + asl ;fireworks explosion if branched here from there) + asl ;shift to high nybble + asl + asl + ora #%00000100 ;add four to set nybble for game timer + jmp UpdateNumber ;jump to print the new score and game timer + +RaiseFlagSetoffFWorks: + lda Enemy_Y_Position,x ;check star flag's vertical position + cmp #$72 ;against preset value + bcc SetoffF ;if star flag higher vertically, branch to other code + dec Enemy_Y_Position,x ;otherwise, raise star flag by one pixel + jmp DrawStarFlag ;and skip this part here +SetoffF: lda FireworksCounter ;check fireworks counter + beq DrawFlagSetTimer ;if no fireworks left to go off, skip this part + bmi DrawFlagSetTimer ;if no fireworks set to go off, skip this part + lda #Fireworks + sta EnemyFrenzyBuffer ;otherwise set fireworks object in frenzy queue + +DrawStarFlag: + jsr RelativeEnemyPosition ;get relative coordinates of star flag + ldy Enemy_SprDataOffset,x ;get OAM data offset + ldx #$03 ;do four sprites +DSFLoop: lda Enemy_Rel_YPos ;get relative vertical coordinate + clc + adc StarFlagYPosAdder,x ;add Y coordinate adder data + sta Sprite_Y_Position,y ;store as Y coordinate + lda StarFlagTileData,x ;get tile number + sta Sprite_Tilenumber,y ;store as tile number + lda #$22 ;set palette and background priority bits + sta Sprite_Attributes,y ;store as attributes + lda Enemy_Rel_XPos ;get relative horizontal coordinate + clc + adc StarFlagXPosAdder,x ;add X coordinate adder data + sta Sprite_X_Position,y ;store as X coordinate + iny + iny ;increment OAM data offset four bytes + iny ;for next sprite + iny + dex ;move onto next sprite + bpl DSFLoop ;do this until all sprites are done + ldx ObjectOffset ;get enemy object offset and leave + rts + +DrawFlagSetTimer: + jsr DrawStarFlag ;do sub to draw star flag + lda #$06 + sta EnemyIntervalTimer,x ;set interval timer here + +IncrementSFTask2: + inc StarFlagTaskControl ;move onto next task + rts + +DelayToAreaEnd: + jsr DrawStarFlag ;do sub to draw star flag + lda EnemyIntervalTimer,x ;if interval timer set in previous task + bne StarFlagExit2 ;not yet expired, branch to leave + lda EventMusicBuffer ;if event music buffer empty, + beq IncrementSFTask2 ;branch to increment task + +StarFlagExit2: + rts ;otherwise leave + +;-------------------------------- +;$00 - used to store horizontal difference between player and piranha plant + +MovePiranhaPlant: + lda Enemy_State,x ;check enemy state + bne PutinPipe ;if set at all, branch to leave + lda EnemyFrameTimer,x ;check enemy's timer here + bne PutinPipe ;branch to end if not yet expired + lda PiranhaPlant_MoveFlag,x ;check movement flag + bne SetupToMovePPlant ;if moving, skip to part ahead + lda PiranhaPlant_Y_Speed,x ;if currently rising, branch + bmi ReversePlantSpeed ;to move enemy upwards out of pipe + jsr PlayerEnemyDiff ;get horizontal difference between player and + bpl ChkPlayerNearPipe ;piranha plant, and branch if enemy to right of player + lda $00 ;otherwise get saved horizontal difference + eor #$ff + clc ;and change to two's compliment + adc #$01 + sta $00 ;save as new horizontal difference + +ChkPlayerNearPipe: + lda $00 ;get saved horizontal difference + cmp #$21 + bcc PutinPipe ;if player within a certain distance, branch to leave + +ReversePlantSpeed: + lda PiranhaPlant_Y_Speed,x ;get vertical speed + eor #$ff + clc ;change to two's compliment + adc #$01 + sta PiranhaPlant_Y_Speed,x ;save as new vertical speed + inc PiranhaPlant_MoveFlag,x ;increment to set movement flag + +SetupToMovePPlant: + lda PiranhaPlantDownYPos,x ;get original vertical coordinate (lowest point) + ldy PiranhaPlant_Y_Speed,x ;get vertical speed + bpl RiseFallPiranhaPlant ;branch if moving downwards + lda PiranhaPlantUpYPos,x ;otherwise get other vertical coordinate (highest point) + +RiseFallPiranhaPlant: + sta $00 ;save vertical coordinate here + lda FrameCounter ;get frame counter + lsr + bcc PutinPipe ;branch to leave if d0 set (execute code every other frame) + lda TimerControl ;get master timer control + bne PutinPipe ;branch to leave if set (likely not necessary) + lda Enemy_Y_Position,x ;get current vertical coordinate + clc + adc PiranhaPlant_Y_Speed,x ;add vertical speed to move up or down + sta Enemy_Y_Position,x ;save as new vertical coordinate + cmp $00 ;compare against low or high coordinate + bne PutinPipe ;branch to leave if not yet reached + lda #$00 + sta PiranhaPlant_MoveFlag,x ;otherwise clear movement flag + lda #$40 + sta EnemyFrameTimer,x ;set timer to delay piranha plant movement + +PutinPipe: + lda #%00100000 ;set background priority bit in sprite + sta Enemy_SprAttrib,x ;attributes to give illusion of being inside pipe + rts ;then leave + +;------------------------------------------------------------------------------------- +;$07 - spinning speed + +FirebarSpin: + sta $07 ;save spinning speed here + lda FirebarSpinDirection,x ;check spinning direction + bne SpinCounterClockwise ;if moving counter-clockwise, branch to other part + ldy #$18 ;possibly residual ldy + lda FirebarSpinState_Low,x + clc ;add spinning speed to what would normally be + adc $07 ;the horizontal speed + sta FirebarSpinState_Low,x + lda FirebarSpinState_High,x ;add carry to what would normally be the vertical speed + adc #$00 + rts + +SpinCounterClockwise: + ldy #$08 ;possibly residual ldy + lda FirebarSpinState_Low,x + sec ;subtract spinning speed to what would normally be + sbc $07 ;the horizontal speed + sta FirebarSpinState_Low,x + lda FirebarSpinState_High,x ;add carry to what would normally be the vertical speed + sbc #$00 + rts + +;------------------------------------------------------------------------------------- +;$00 - used to hold collision flag, Y movement force + 5 or low byte of name table for rope +;$01 - used to hold high byte of name table for rope +;$02 - used to hold page location of rope + +BalancePlatform: + lda Enemy_Y_HighPos,x ;check high byte of vertical position + cmp #$03 + bne DoBPl + jmp EraseEnemyObject ;if far below screen, kill the object +DoBPl: lda Enemy_State,x ;get object's state (set to $ff or other platform offset) + bpl CheckBalPlatform ;if doing other balance platform, branch to leave + rts + +CheckBalPlatform: + tay ;save offset from state as Y + lda PlatformCollisionFlag,x ;get collision flag of platform + sta $00 ;store here + lda Enemy_MovingDir,x ;get moving direction + beq ChkForFall + jmp PlatformFall ;if set, jump here + +ChkForFall: + lda #$2d ;check if platform is above a certain point + cmp Enemy_Y_Position,x + bcc ChkOtherForFall ;if not, branch elsewhere + cpy $00 ;if collision flag is set to same value as + beq MakePlatformFall ;enemy state, branch to make platforms fall + clc + adc #$02 ;otherwise add 2 pixels to vertical position + sta Enemy_Y_Position,x ;of current platform and branch elsewhere + jmp StopPlatforms ;to make platforms stop + +MakePlatformFall: + jmp InitPlatformFall ;make platforms fall + +ChkOtherForFall: + cmp Enemy_Y_Position,y ;check if other platform is above a certain point + bcc ChkToMoveBalPlat ;if not, branch elsewhere + cpx $00 ;if collision flag is set to same value as + beq MakePlatformFall ;enemy state, branch to make platforms fall + clc + adc #$02 ;otherwise add 2 pixels to vertical position + sta Enemy_Y_Position,y ;of other platform and branch elsewhere + jmp StopPlatforms ;jump to stop movement and do not return + +ChkToMoveBalPlat: + lda Enemy_Y_Position,x ;save vertical position to stack + pha + lda PlatformCollisionFlag,x ;get collision flag + bpl ColFlg ;branch if collision + lda Enemy_Y_MoveForce,x + clc ;add $05 to contents of moveforce, whatever they be + adc #$05 + sta $00 ;store here + lda Enemy_Y_Speed,x + adc #$00 ;add carry to vertical speed + bmi PlatDn ;branch if moving downwards + bne PlatUp ;branch elsewhere if moving upwards + lda $00 + cmp #$0b ;check if there's still a little force left + bcc PlatSt ;if not enough, branch to stop movement + bcs PlatUp ;otherwise keep branch to move upwards +ColFlg: cmp ObjectOffset ;if collision flag matches + beq PlatDn ;current enemy object offset, branch +PlatUp: jsr MovePlatformUp ;do a sub to move upwards + jmp DoOtherPlatform ;jump ahead to remaining code +PlatSt: jsr StopPlatforms ;do a sub to stop movement + jmp DoOtherPlatform ;jump ahead to remaining code +PlatDn: jsr MovePlatformDown ;do a sub to move downwards + +DoOtherPlatform: + ldy Enemy_State,x ;get offset of other platform + pla ;get old vertical coordinate from stack + sec + sbc Enemy_Y_Position,x ;get difference of old vs. new coordinate + clc + adc Enemy_Y_Position,y ;add difference to vertical coordinate of other + sta Enemy_Y_Position,y ;platform to move it in the opposite direction + lda PlatformCollisionFlag,x ;if no collision, skip this part here + bmi DrawEraseRope + tax ;put offset which collision occurred here + jsr PositionPlayerOnVPlat ;and use it to position player accordingly + +DrawEraseRope: + ldy ObjectOffset ;get enemy object offset + lda Enemy_Y_Speed,y ;check to see if current platform is + ora Enemy_Y_MoveForce,y ;moving at all + beq ExitRp ;if not, skip all of this and branch to leave + ldx VRAM_Buffer1_Offset ;get vram buffer offset + cpx #$20 ;if offset beyond a certain point, go ahead + bcs ExitRp ;and skip this, branch to leave + lda Enemy_Y_Speed,y + pha ;save two copies of vertical speed to stack + pha + jsr SetupPlatformRope ;do a sub to figure out where to put new bg tiles + lda $01 ;write name table address to vram buffer + sta VRAM_Buffer1,x ;first the high byte, then the low + lda $00 + sta VRAM_Buffer1+1,x + lda #$02 ;set length for 2 bytes + sta VRAM_Buffer1+2,x + lda Enemy_Y_Speed,y ;if platform moving upwards, branch + bmi EraseR1 ;to do something else + lda #$a2 + sta VRAM_Buffer1+3,x ;otherwise put tile numbers for left + lda #$a3 ;and right sides of rope in vram buffer + sta VRAM_Buffer1+4,x + jmp OtherRope ;jump to skip this part +EraseR1: lda #$24 ;put blank tiles in vram buffer + sta VRAM_Buffer1+3,x ;to erase rope + sta VRAM_Buffer1+4,x + +OtherRope: + lda Enemy_State,y ;get offset of other platform from state + tay ;use as Y here + pla ;pull second copy of vertical speed from stack + eor #$ff ;invert bits to reverse speed + jsr SetupPlatformRope ;do sub again to figure out where to put bg tiles + lda $01 ;write name table address to vram buffer + sta VRAM_Buffer1+5,x ;this time we're doing putting tiles for + lda $00 ;the other platform + sta VRAM_Buffer1+6,x + lda #$02 + sta VRAM_Buffer1+7,x ;set length again for 2 bytes + pla ;pull first copy of vertical speed from stack + bpl EraseR2 ;if moving upwards (note inversion earlier), skip this + lda #$a2 + sta VRAM_Buffer1+8,x ;otherwise put tile numbers for left + lda #$a3 ;and right sides of rope in vram + sta VRAM_Buffer1+9,x ;transfer buffer + jmp EndRp ;jump to skip this part +EraseR2: lda #$24 ;put blank tiles in vram buffer + sta VRAM_Buffer1+8,x ;to erase rope + sta VRAM_Buffer1+9,x +EndRp: lda #$00 ;put null terminator at the end + sta VRAM_Buffer1+10,x + lda VRAM_Buffer1_Offset ;add ten bytes to the vram buffer offset + clc ;and store + adc #10 + sta VRAM_Buffer1_Offset +ExitRp: ldx ObjectOffset ;get enemy object buffer offset and leave + rts + +SetupPlatformRope: + pha ;save second/third copy to stack + lda Enemy_X_Position,y ;get horizontal coordinate + clc + adc #$08 ;add eight pixels + ldx SecondaryHardMode ;if secondary hard mode flag set, + bne GetLRp ;use coordinate as-is + clc + adc #$10 ;otherwise add sixteen more pixels +GetLRp: pha ;save modified horizontal coordinate to stack + lda Enemy_PageLoc,y + adc #$00 ;add carry to page location + sta $02 ;and save here + pla ;pull modified horizontal coordinate + and #%11110000 ;from the stack, mask out low nybble + lsr ;and shift three bits to the right + lsr + lsr + sta $00 ;store result here as part of name table low byte + ldx Enemy_Y_Position,y ;get vertical coordinate + pla ;get second/third copy of vertical speed from stack + bpl GetHRp ;skip this part if moving downwards or not at all + txa + clc + adc #$08 ;add eight to vertical coordinate and + tax ;save as X +GetHRp: txa ;move vertical coordinate to A + ldx VRAM_Buffer1_Offset ;get vram buffer offset + asl + rol ;rotate d7 to d0 and d6 into carry + pha ;save modified vertical coordinate to stack + rol ;rotate carry to d0, thus d7 and d6 are at 2 LSB + and #%00000011 ;mask out all bits but d7 and d6, then set + ora #%00100000 ;d5 to get appropriate high byte of name table + sta $01 ;address, then store + lda $02 ;get saved page location from earlier + and #$01 ;mask out all but LSB + asl + asl ;shift twice to the left and save with the + ora $01 ;rest of the bits of the high byte, to get + sta $01 ;the proper name table and the right place on it + pla ;get modified vertical coordinate from stack + and #%11100000 ;mask out low nybble and LSB of high nybble + clc + adc $00 ;add to horizontal part saved here + sta $00 ;save as name table low byte + lda Enemy_Y_Position,y + cmp #$e8 ;if vertical position not below the + bcc ExPRp ;bottom of the screen, we're done, branch to leave + lda $00 + and #%10111111 ;mask out d6 of low byte of name table address + sta $00 +ExPRp: rts ;leave! + +InitPlatformFall: + tya ;move offset of other platform from Y to X + tax + jsr GetEnemyOffscreenBits ;get offscreen bits + lda #$06 + jsr SetupFloateyNumber ;award 1000 points to player + lda Player_Rel_XPos + sta FloateyNum_X_Pos,x ;put floatey number coordinates where player is + lda Player_Y_Position + sta FloateyNum_Y_Pos,x + lda #$01 ;set moving direction as flag for + sta Enemy_MovingDir,x ;falling platforms + +StopPlatforms: + jsr InitVStf ;initialize vertical speed and low byte + sta Enemy_Y_Speed,y ;for both platforms and leave + sta Enemy_Y_MoveForce,y + rts + +PlatformFall: + tya ;save offset for other platform to stack + pha + jsr MoveFallingPlatform ;make current platform fall + pla + tax ;pull offset from stack and save to X + jsr MoveFallingPlatform ;make other platform fall + ldx ObjectOffset + lda PlatformCollisionFlag,x ;if player not standing on either platform, + bmi ExPF ;skip this part + tax ;transfer collision flag offset as offset to X + jsr PositionPlayerOnVPlat ;and position player appropriately +ExPF: ldx ObjectOffset ;get enemy object buffer offset and leave + rts + +;-------------------------------- + +YMovingPlatform: + lda Enemy_Y_Speed,x ;if platform moving up or down, skip ahead to + ora Enemy_Y_MoveForce,x ;check on other position + bne ChkYCenterPos + sta Enemy_YMF_Dummy,x ;initialize dummy variable + lda Enemy_Y_Position,x + cmp YPlatformTopYPos,x ;if current vertical position => top position, branch + bcs ChkYCenterPos ;ahead of all this + lda FrameCounter + and #%00000111 ;check for every eighth frame + bne SkipIY + inc Enemy_Y_Position,x ;increase vertical position every eighth frame +SkipIY: jmp ChkYPCollision ;skip ahead to last part + +ChkYCenterPos: + lda Enemy_Y_Position,x ;if current vertical position < central position, branch + cmp YPlatformCenterYPos,x ;to slow ascent/move downwards + bcc YMDown + jsr MovePlatformUp ;otherwise start slowing descent/moving upwards + jmp ChkYPCollision +YMDown: jsr MovePlatformDown ;start slowing ascent/moving downwards + +ChkYPCollision: + lda PlatformCollisionFlag,x ;if collision flag not set here, branch + bmi ExYPl ;to leave + jsr PositionPlayerOnVPlat ;otherwise position player appropriately +ExYPl: rts ;leave + +;-------------------------------- +;$00 - used as adder to position player hotizontally + +XMovingPlatform: + lda #$0e ;load preset maximum value for secondary counter + jsr XMoveCntr_Platform ;do a sub to increment counters for movement + jsr MoveWithXMCntrs ;do a sub to move platform accordingly, and return value + lda PlatformCollisionFlag,x ;if no collision with player, + bmi ExXMP ;branch ahead to leave + +PositionPlayerOnHPlat: + lda Player_X_Position + clc ;add saved value from second subroutine to + adc $00 ;current player's position to position + sta Player_X_Position ;player accordingly in horizontal position + lda Player_PageLoc ;get player's page location + ldy $00 ;check to see if saved value here is positive or negative + bmi PPHSubt ;if negative, branch to subtract + adc #$00 ;otherwise add carry to page location + jmp SetPVar ;jump to skip subtraction +PPHSubt: sbc #$00 ;subtract borrow from page location +SetPVar: sta Player_PageLoc ;save result to player's page location + sty Platform_X_Scroll ;put saved value from second sub here to be used later + jsr PositionPlayerOnVPlat ;position player vertically and appropriately +ExXMP: rts ;and we are done here + +;-------------------------------- + +DropPlatform: + lda PlatformCollisionFlag,x ;if no collision between platform and player + bmi ExDPl ;occurred, just leave without moving anything + jsr MoveDropPlatform ;otherwise do a sub to move platform down very quickly + jsr PositionPlayerOnVPlat ;do a sub to position player appropriately +ExDPl: rts ;leave + +;-------------------------------- +;$00 - residual value from sub + +RightPlatform: + jsr MoveEnemyHorizontally ;move platform with current horizontal speed, if any + sta $00 ;store saved value here (residual code) + lda PlatformCollisionFlag,x ;check collision flag, if no collision between player + bmi ExRPl ;and platform, branch ahead, leave speed unaltered + lda #$10 + sta Enemy_X_Speed,x ;otherwise set new speed (gets moving if motionless) + jsr PositionPlayerOnHPlat ;use saved value from earlier sub to position player +ExRPl: rts ;then leave + +;-------------------------------- + +MoveLargeLiftPlat: + jsr MoveLiftPlatforms ;execute common to all large and small lift platforms + jmp ChkYPCollision ;branch to position player correctly + +MoveSmallPlatform: + jsr MoveLiftPlatforms ;execute common to all large and small lift platforms + jmp ChkSmallPlatCollision ;branch to position player correctly + +MoveLiftPlatforms: + lda TimerControl ;if master timer control set, skip all of this + bne ExLiftP ;and branch to leave + lda Enemy_YMF_Dummy,x + clc ;add contents of movement amount to whatever's here + adc Enemy_Y_MoveForce,x + sta Enemy_YMF_Dummy,x + lda Enemy_Y_Position,x ;add whatever vertical speed is set to current + adc Enemy_Y_Speed,x ;vertical position plus carry to move up or down + sta Enemy_Y_Position,x ;and then leave + rts + +ChkSmallPlatCollision: + lda PlatformCollisionFlag,x ;get bounding box counter saved in collision flag + beq ExLiftP ;if none found, leave player position alone + jsr PositionPlayerOnS_Plat ;use to position player correctly +ExLiftP: rts ;then leave + +;------------------------------------------------------------------------------------- +;$00 - page location of extended left boundary +;$01 - extended left boundary position +;$02 - page location of extended right boundary +;$03 - extended right boundary position + +OffscreenBoundsCheck: + lda Enemy_ID,x ;check for cheep-cheep object + cmp #FlyingCheepCheep ;branch to leave if found + beq ExScrnBd + lda ScreenLeft_X_Pos ;get horizontal coordinate for left side of screen + ldy Enemy_ID,x + cpy #HammerBro ;check for hammer bro object + beq LimitB + cpy #PiranhaPlant ;check for piranha plant object + bne ExtendLB ;these two will be erased sooner than others if too far left +LimitB: adc #$38 ;add 56 pixels to coordinate if hammer bro or piranha plant +ExtendLB: sbc #$48 ;subtract 72 pixels regardless of enemy object + sta $01 ;store result here + lda ScreenLeft_PageLoc + sbc #$00 ;subtract borrow from page location of left side + sta $00 ;store result here + lda ScreenRight_X_Pos ;add 72 pixels to the right side horizontal coordinate + adc #$48 + sta $03 ;store result here + lda ScreenRight_PageLoc + adc #$00 ;then add the carry to the page location + sta $02 ;and store result here + lda Enemy_X_Position,x ;compare horizontal coordinate of the enemy object + cmp $01 ;to modified horizontal left edge coordinate to get carry + lda Enemy_PageLoc,x + sbc $00 ;then subtract it from the page coordinate of the enemy object + bmi TooFar ;if enemy object is too far left, branch to erase it + lda Enemy_X_Position,x ;compare horizontal coordinate of the enemy object + cmp $03 ;to modified horizontal right edge coordinate to get carry + lda Enemy_PageLoc,x + sbc $02 ;then subtract it from the page coordinate of the enemy object + bmi ExScrnBd ;if enemy object is on the screen, leave, do not erase enemy + lda Enemy_State,x ;if at this point, enemy is offscreen to the right, so check + cmp #HammerBro ;if in state used by spiny's egg, do not erase + beq ExScrnBd + cpy #PiranhaPlant ;if piranha plant, do not erase + beq ExScrnBd + cpy #FlagpoleFlagObject ;if flagpole flag, do not erase + beq ExScrnBd + cpy #StarFlagObject ;if star flag, do not erase + beq ExScrnBd + cpy #JumpspringObject ;if jumpspring, do not erase + beq ExScrnBd ;erase all others too far to the right +TooFar: jsr EraseEnemyObject ;erase object if necessary +ExScrnBd: rts ;leave + +;------------------------------------------------------------------------------------- + +;some unused space + .db $ff, $ff, $ff + +;------------------------------------------------------------------------------------- +;$01 - enemy buffer offset + +FireballEnemyCollision: + lda Fireball_State,x ;check to see if fireball state is set at all + beq ExitFBallEnemy ;branch to leave if not + asl + bcs ExitFBallEnemy ;branch to leave also if d7 in state is set + lda FrameCounter + lsr ;get LSB of frame counter + bcs ExitFBallEnemy ;branch to leave if set (do routine every other frame) + txa + asl ;multiply fireball offset by four + asl + clc + adc #$1c ;then add $1c or 28 bytes to it + tay ;to use fireball's bounding box coordinates + ldx #$04 + +FireballEnemyCDLoop: + stx $01 ;store enemy object offset here + tya + pha ;push fireball offset to the stack + lda Enemy_State,x + and #%00100000 ;check to see if d5 is set in enemy state + bne NoFToECol ;if so, skip to next enemy slot + lda Enemy_Flag,x ;check to see if buffer flag is set + beq NoFToECol ;if not, skip to next enemy slot + lda Enemy_ID,x ;check enemy identifier + cmp #$24 + bcc GoombaDie ;if < $24, branch to check further + cmp #$2b + bcc NoFToECol ;if in range $24-$2a, skip to next enemy slot +GoombaDie: cmp #Goomba ;check for goomba identifier + bne NotGoomba ;if not found, continue with code + lda Enemy_State,x ;otherwise check for defeated state + cmp #$02 ;if stomped or otherwise defeated, + bcs NoFToECol ;skip to next enemy slot +NotGoomba: lda EnemyOffscrBitsMasked,x ;if any masked offscreen bits set, + bne NoFToECol ;skip to next enemy slot + txa + asl ;otherwise multiply enemy offset by four + asl + clc + adc #$04 ;add 4 bytes to it + tax ;to use enemy's bounding box coordinates + jsr SprObjectCollisionCore ;do fireball-to-enemy collision detection + ldx ObjectOffset ;return fireball's original offset + bcc NoFToECol ;if carry clear, no collision, thus do next enemy slot + lda #%10000000 + sta Fireball_State,x ;set d7 in enemy state + ldx $01 ;get enemy offset + jsr HandleEnemyFBallCol ;jump to handle fireball to enemy collision +NoFToECol: pla ;pull fireball offset from stack + tay ;put it in Y + ldx $01 ;get enemy object offset + dex ;decrement it + bpl FireballEnemyCDLoop ;loop back until collision detection done on all enemies + +ExitFBallEnemy: + ldx ObjectOffset ;get original fireball offset and leave + rts + +BowserIdentities: + .db Goomba, GreenKoopa, BuzzyBeetle, Spiny, Lakitu, Bloober, HammerBro, Bowser + +HandleEnemyFBallCol: + jsr RelativeEnemyPosition ;get relative coordinate of enemy + ldx $01 ;get current enemy object offset + lda Enemy_Flag,x ;check buffer flag for d7 set + bpl ChkBuzzyBeetle ;branch if not set to continue + and #%00001111 ;otherwise mask out high nybble and + tax ;use low nybble as enemy offset + lda Enemy_ID,x + cmp #Bowser ;check enemy identifier for bowser + beq HurtBowser ;branch if found + ldx $01 ;otherwise retrieve current enemy offset + +ChkBuzzyBeetle: + lda Enemy_ID,x + cmp #BuzzyBeetle ;check for buzzy beetle + beq ExHCF ;branch if found to leave (buzzy beetles fireproof) + cmp #Bowser ;check for bowser one more time (necessary if d7 of flag was clear) + bne ChkOtherEnemies ;if not found, branch to check other enemies + +HurtBowser: + dec BowserHitPoints ;decrement bowser's hit points + bne ExHCF ;if bowser still has hit points, branch to leave + jsr InitVStf ;otherwise do sub to init vertical speed and movement force + sta Enemy_X_Speed,x ;initialize horizontal speed + sta EnemyFrenzyBuffer ;init enemy frenzy buffer + lda #$fe + sta Enemy_Y_Speed,x ;set vertical speed to make defeated bowser jump a little + ldy WorldNumber ;use world number as offset + lda BowserIdentities,y ;get enemy identifier to replace bowser with + sta Enemy_ID,x ;set as new enemy identifier + lda #$20 ;set A to use starting value for state + cpy #$03 ;check to see if using offset of 3 or more + bcs SetDBSte ;branch if so + ora #$03 ;otherwise add 3 to enemy state +SetDBSte: sta Enemy_State,x ;set defeated enemy state + lda #Sfx_BowserFall + sta Square2SoundQueue ;load bowser defeat sound + ldx $01 ;get enemy offset + lda #$09 ;award 5000 points to player for defeating bowser + bne EnemySmackScore ;unconditional branch to award points + +ChkOtherEnemies: + cmp #BulletBill_FrenzyVar + beq ExHCF ;branch to leave if bullet bill (frenzy variant) + cmp #Podoboo + beq ExHCF ;branch to leave if podoboo + cmp #$15 + bcs ExHCF ;branch to leave if identifier => $15 + +ShellOrBlockDefeat: + lda Enemy_ID,x ;check for piranha plant + cmp #PiranhaPlant + bne StnE ;branch if not found + lda Enemy_Y_Position,x + adc #$18 ;add 24 pixels to enemy object's vertical position + sta Enemy_Y_Position,x +StnE: jsr ChkToStunEnemies ;do yet another sub + lda Enemy_State,x + and #%00011111 ;mask out 2 MSB of enemy object's state + ora #%00100000 ;set d5 to defeat enemy and save as new state + sta Enemy_State,x + lda #$02 ;award 200 points by default + ldy Enemy_ID,x ;check for hammer bro + cpy #HammerBro + bne GoombaPoints ;branch if not found + lda #$06 ;award 1000 points for hammer bro + +GoombaPoints: + cpy #Goomba ;check for goomba + bne EnemySmackScore ;branch if not found + lda #$01 ;award 100 points for goomba + +EnemySmackScore: + jsr SetupFloateyNumber ;update necessary score variables + lda #Sfx_EnemySmack ;play smack enemy sound + sta Square1SoundQueue +ExHCF: rts ;and now let's leave + +;------------------------------------------------------------------------------------- + +PlayerHammerCollision: + lda FrameCounter ;get frame counter + lsr ;shift d0 into carry + bcc ExPHC ;branch to leave if d0 not set to execute every other frame + lda TimerControl ;if either master timer control + ora Misc_OffscreenBits ;or any offscreen bits for hammer are set, + bne ExPHC ;branch to leave + txa + asl ;multiply misc object offset by four + asl + clc + adc #$24 ;add 36 or $24 bytes to get proper offset + tay ;for misc object bounding box coordinates + jsr PlayerCollisionCore ;do player-to-hammer collision detection + ldx ObjectOffset ;get misc object offset + bcc ClHCol ;if no collision, then branch + lda Misc_Collision_Flag,x ;otherwise read collision flag + bne ExPHC ;if collision flag already set, branch to leave + lda #$01 + sta Misc_Collision_Flag,x ;otherwise set collision flag now + lda Misc_X_Speed,x + eor #$ff ;get two's compliment of + clc ;hammer's horizontal speed + adc #$01 + sta Misc_X_Speed,x ;set to send hammer flying the opposite direction + lda StarInvincibleTimer ;if star mario invincibility timer set, + bne ExPHC ;branch to leave + jmp InjurePlayer ;otherwise jump to hurt player, do not return +ClHCol: lda #$00 ;clear collision flag + sta Misc_Collision_Flag,x +ExPHC: rts + +;------------------------------------------------------------------------------------- + +HandlePowerUpCollision: + jsr EraseEnemyObject ;erase the power-up object + lda #$06 + jsr SetupFloateyNumber ;award 1000 points to player by default + lda #Sfx_PowerUpGrab + sta Square2SoundQueue ;play the power-up sound + lda PowerUpType ;check power-up type + cmp #$02 + bcc Shroom_Flower_PUp ;if mushroom or fire flower, branch + cmp #$03 + beq SetFor1Up ;if 1-up mushroom, branch + lda #$23 ;otherwise set star mario invincibility + sta StarInvincibleTimer ;timer, and load the star mario music + lda #StarPowerMusic ;into the area music queue, then leave + sta AreaMusicQueue + rts + +Shroom_Flower_PUp: + lda PlayerStatus ;if player status = small, branch + beq UpToSuper + cmp #$01 ;if player status not super, leave + bne NoPUp + ldx ObjectOffset ;get enemy offset, not necessary + lda #$02 ;set player status to fiery + sta PlayerStatus + jsr GetPlayerColors ;run sub to change colors of player + ldx ObjectOffset ;get enemy offset again, and again not necessary + lda #$0c ;set value to be used by subroutine tree (fiery) + jmp UpToFiery ;jump to set values accordingly + +SetFor1Up: + lda #$0b ;change 1000 points into 1-up instead + sta FloateyNum_Control,x ;and then leave + rts + +UpToSuper: + lda #$01 ;set player status to super + sta PlayerStatus + lda #$09 ;set value to be used by subroutine tree (super) + +UpToFiery: + ldy #$00 ;set value to be used as new player state + jsr SetPRout ;set values to stop certain things in motion +NoPUp: rts + +;-------------------------------- + +ResidualXSpdData: + .db $18, $e8 + +KickedShellXSpdData: + .db $30, $d0 + +DemotedKoopaXSpdData: + .db $08, $f8 + +PlayerEnemyCollision: + lda FrameCounter ;check counter for d0 set + lsr + bcs NoPUp ;if set, branch to leave + jsr CheckPlayerVertical ;if player object is completely offscreen or + bcs NoPECol ;if down past 224th pixel row, branch to leave + lda EnemyOffscrBitsMasked,x ;if current enemy is offscreen by any amount, + bne NoPECol ;go ahead and branch to leave + lda GameEngineSubroutine + cmp #$08 ;if not set to run player control routine + bne NoPECol ;on next frame, branch to leave + lda Enemy_State,x + and #%00100000 ;if enemy state has d5 set, branch to leave + bne NoPECol + jsr GetEnemyBoundBoxOfs ;get bounding box offset for current enemy object + jsr PlayerCollisionCore ;do collision detection on player vs. enemy + ldx ObjectOffset ;get enemy object buffer offset + bcs CheckForPUpCollision ;if collision, branch past this part here + lda Enemy_CollisionBits,x + and #%11111110 ;otherwise, clear d0 of current enemy object's + sta Enemy_CollisionBits,x ;collision bit +NoPECol: rts + +CheckForPUpCollision: + ldy Enemy_ID,x + cpy #PowerUpObject ;check for power-up object + bne EColl ;if not found, branch to next part + jmp HandlePowerUpCollision ;otherwise, unconditional jump backwards +EColl: lda StarInvincibleTimer ;if star mario invincibility timer expired, + beq HandlePECollisions ;perform task here, otherwise kill enemy like + jmp ShellOrBlockDefeat ;hit with a shell, or from beneath + +KickedShellPtsData: + .db $0a, $06, $04 + +HandlePECollisions: + lda Enemy_CollisionBits,x ;check enemy collision bits for d0 set + and #%00000001 ;or for being offscreen at all + ora EnemyOffscrBitsMasked,x + bne ExPEC ;branch to leave if either is true + lda #$01 + ora Enemy_CollisionBits,x ;otherwise set d0 now + sta Enemy_CollisionBits,x + cpy #Spiny ;branch if spiny + beq ChkForPlayerInjury + cpy #PiranhaPlant ;branch if piranha plant + beq InjurePlayer + cpy #Podoboo ;branch if podoboo + beq InjurePlayer + cpy #BulletBill_CannonVar ;branch if bullet bill + beq ChkForPlayerInjury + cpy #$15 ;branch if object => $15 + bcs InjurePlayer + lda AreaType ;branch if water type level + beq InjurePlayer + lda Enemy_State,x ;branch if d7 of enemy state was set + asl + bcs ChkForPlayerInjury + lda Enemy_State,x ;mask out all but 3 LSB of enemy state + and #%00000111 + cmp #$02 ;branch if enemy is in normal or falling state + bcc ChkForPlayerInjury + lda Enemy_ID,x ;branch to leave if goomba in defeated state + cmp #Goomba + beq ExPEC + lda #Sfx_EnemySmack ;play smack enemy sound + sta Square1SoundQueue + lda Enemy_State,x ;set d7 in enemy state, thus become moving shell + ora #%10000000 + sta Enemy_State,x + jsr EnemyFacePlayer ;set moving direction and get offset + lda KickedShellXSpdData,y ;load and set horizontal speed data with offset + sta Enemy_X_Speed,x + lda #$03 ;add three to whatever the stomp counter contains + clc ;to give points for kicking the shell + adc StompChainCounter + ldy EnemyIntervalTimer,x ;check shell enemy's timer + cpy #$03 ;if above a certain point, branch using the points + bcs KSPts ;data obtained from the stomp counter + 3 + lda KickedShellPtsData,y ;otherwise, set points based on proximity to timer expiration +KSPts: jsr SetupFloateyNumber ;set values for floatey number now +ExPEC: rts ;leave!!! + +ChkForPlayerInjury: + lda Player_Y_Speed ;check player's vertical speed + bmi ChkInj ;perform procedure below if player moving upwards + bne EnemyStomped ;or not at all, and branch elsewhere if moving downwards +ChkInj: lda Enemy_ID,x ;branch if enemy object < $07 + cmp #Bloober + bcc ChkETmrs + lda Player_Y_Position ;add 12 pixels to player's vertical position + clc + adc #$0c + cmp Enemy_Y_Position,x ;compare modified player's position to enemy's position + bcc EnemyStomped ;branch if this player's position above (less than) enemy's +ChkETmrs: lda StompTimer ;check stomp timer + bne EnemyStomped ;branch if set + lda InjuryTimer ;check to see if injured invincibility timer still + bne ExInjColRoutines ;counting down, and branch elsewhere to leave if so + lda Player_Rel_XPos + cmp Enemy_Rel_XPos ;if player's relative position to the left of enemy's + bcc TInjE ;relative position, branch here + jmp ChkEnemyFaceRight ;otherwise do a jump here +TInjE: lda Enemy_MovingDir,x ;if enemy moving towards the left, + cmp #$01 ;branch, otherwise do a jump here + bne InjurePlayer ;to turn the enemy around + jmp LInj + +InjurePlayer: + lda InjuryTimer ;check again to see if injured invincibility timer is + bne ExInjColRoutines ;at zero, and branch to leave if so + +ForceInjury: + ldx PlayerStatus ;check player's status + beq KillPlayer ;branch if small + sta PlayerStatus ;otherwise set player's status to small + lda #$08 + sta InjuryTimer ;set injured invincibility timer + asl + sta Square1SoundQueue ;play pipedown/injury sound + jsr GetPlayerColors ;change player's palette if necessary + lda #$0a ;set subroutine to run on next frame +SetKRout: ldy #$01 ;set new player state +SetPRout: sta GameEngineSubroutine ;load new value to run subroutine on next frame + sty Player_State ;store new player state + ldy #$ff + sty TimerControl ;set master timer control flag to halt timers + iny + sty ScrollAmount ;initialize scroll speed + +ExInjColRoutines: + ldx ObjectOffset ;get enemy offset and leave + rts + +KillPlayer: + stx Player_X_Speed ;halt player's horizontal movement by initializing speed + inx + stx EventMusicQueue ;set event music queue to death music + lda #$fc + sta Player_Y_Speed ;set new vertical speed + lda #$0b ;set subroutine to run on next frame + bne SetKRout ;branch to set player's state and other things + +StompedEnemyPtsData: + .db $02, $06, $05, $06 + +EnemyStomped: + lda Enemy_ID,x ;check for spiny, branch to hurt player + cmp #Spiny ;if found + beq InjurePlayer + lda #Sfx_EnemyStomp ;otherwise play stomp/swim sound + sta Square1SoundQueue + lda Enemy_ID,x + ldy #$00 ;initialize points data offset for stomped enemies + cmp #FlyingCheepCheep ;branch for cheep-cheep + beq EnemyStompedPts + cmp #BulletBill_FrenzyVar ;branch for either bullet bill object + beq EnemyStompedPts + cmp #BulletBill_CannonVar + beq EnemyStompedPts + cmp #Podoboo ;branch for podoboo (this branch is logically impossible + beq EnemyStompedPts ;for cpu to take due to earlier checking of podoboo) + iny ;increment points data offset + cmp #HammerBro ;branch for hammer bro + beq EnemyStompedPts + iny ;increment points data offset + cmp #Lakitu ;branch for lakitu + beq EnemyStompedPts + iny ;increment points data offset + cmp #Bloober ;branch if NOT bloober + bne ChkForDemoteKoopa + +EnemyStompedPts: + lda StompedEnemyPtsData,y ;load points data using offset in Y + jsr SetupFloateyNumber ;run sub to set floatey number controls + lda Enemy_MovingDir,x + pha ;save enemy movement direction to stack + jsr SetStun ;run sub to kill enemy + pla + sta Enemy_MovingDir,x ;return enemy movement direction from stack + lda #%00100000 + sta Enemy_State,x ;set d5 in enemy state + jsr InitVStf ;nullify vertical speed, physics-related thing, + sta Enemy_X_Speed,x ;and horizontal speed + lda #$fd ;set player's vertical speed, to give bounce + sta Player_Y_Speed + rts + +ChkForDemoteKoopa: + cmp #$09 ;branch elsewhere if enemy object < $09 + bcc HandleStompedShellE + and #%00000001 ;demote koopa paratroopas to ordinary troopas + sta Enemy_ID,x + ldy #$00 ;return enemy to normal state + sty Enemy_State,x + lda #$03 ;award 400 points to the player + jsr SetupFloateyNumber + jsr InitVStf ;nullify physics-related thing and vertical speed + jsr EnemyFacePlayer ;turn enemy around if necessary + lda DemotedKoopaXSpdData,y + sta Enemy_X_Speed,x ;set appropriate moving speed based on direction + jmp SBnce ;then move onto something else + +RevivalRateData: + .db $10, $0b + +HandleStompedShellE: + lda #$04 ;set defeated state for enemy + sta Enemy_State,x + inc StompChainCounter ;increment the stomp counter + lda StompChainCounter ;add whatever is in the stomp counter + clc ;to whatever is in the stomp timer + adc StompTimer + jsr SetupFloateyNumber ;award points accordingly + inc StompTimer ;increment stomp timer of some sort + ldy PrimaryHardMode ;check primary hard mode flag + lda RevivalRateData,y ;load timer setting according to flag + sta EnemyIntervalTimer,x ;set as enemy timer to revive stomped enemy +SBnce: lda #$fc ;set player's vertical speed for bounce + sta Player_Y_Speed ;and then leave!!! + rts + +ChkEnemyFaceRight: + lda Enemy_MovingDir,x ;check to see if enemy is moving to the right + cmp #$01 + bne LInj ;if not, branch + jmp InjurePlayer ;otherwise go back to hurt player +LInj: jsr EnemyTurnAround ;turn the enemy around, if necessary + jmp InjurePlayer ;go back to hurt player + + +EnemyFacePlayer: + ldy #$01 ;set to move right by default + jsr PlayerEnemyDiff ;get horizontal difference between player and enemy + bpl SFcRt ;if enemy is to the right of player, do not increment + iny ;otherwise, increment to set to move to the left +SFcRt: sty Enemy_MovingDir,x ;set moving direction here + dey ;then decrement to use as a proper offset + rts + +SetupFloateyNumber: + sta FloateyNum_Control,x ;set number of points control for floatey numbers + lda #$30 + sta FloateyNum_Timer,x ;set timer for floatey numbers + lda Enemy_Y_Position,x + sta FloateyNum_Y_Pos,x ;set vertical coordinate + lda Enemy_Rel_XPos + sta FloateyNum_X_Pos,x ;set horizontal coordinate and leave +ExSFN: rts + +;------------------------------------------------------------------------------------- +;$01 - used to hold enemy offset for second enemy + +SetBitsMask: + .db %10000000, %01000000, %00100000, %00010000, %00001000, %00000100, %00000010 + +ClearBitsMask: + .db %01111111, %10111111, %11011111, %11101111, %11110111, %11111011, %11111101 + +EnemiesCollision: + lda FrameCounter ;check counter for d0 set + lsr + bcc ExSFN ;if d0 not set, leave + lda AreaType + beq ExSFN ;if water area type, leave + lda Enemy_ID,x + cmp #$15 ;if enemy object => $15, branch to leave + bcs ExitECRoutine + cmp #Lakitu ;if lakitu, branch to leave + beq ExitECRoutine + cmp #PiranhaPlant ;if piranha plant, branch to leave + beq ExitECRoutine + lda EnemyOffscrBitsMasked,x ;if masked offscreen bits nonzero, branch to leave + bne ExitECRoutine + jsr GetEnemyBoundBoxOfs ;otherwise, do sub, get appropriate bounding box offset for + dex ;first enemy we're going to compare, then decrement for second + bmi ExitECRoutine ;branch to leave if there are no other enemies +ECLoop: stx $01 ;save enemy object buffer offset for second enemy here + tya ;save first enemy's bounding box offset to stack + pha + lda Enemy_Flag,x ;check enemy object enable flag + beq ReadyNextEnemy ;branch if flag not set + lda Enemy_ID,x + cmp #$15 ;check for enemy object => $15 + bcs ReadyNextEnemy ;branch if true + cmp #Lakitu + beq ReadyNextEnemy ;branch if enemy object is lakitu + cmp #PiranhaPlant + beq ReadyNextEnemy ;branch if enemy object is piranha plant + lda EnemyOffscrBitsMasked,x + bne ReadyNextEnemy ;branch if masked offscreen bits set + txa ;get second enemy object's bounding box offset + asl ;multiply by four, then add four + asl + clc + adc #$04 + tax ;use as new contents of X + jsr SprObjectCollisionCore ;do collision detection using the two enemies here + ldx ObjectOffset ;use first enemy offset for X + ldy $01 ;use second enemy offset for Y + bcc NoEnemyCollision ;if carry clear, no collision, branch ahead of this + lda Enemy_State,x + ora Enemy_State,y ;check both enemy states for d7 set + and #%10000000 + bne YesEC ;branch if at least one of them is set + lda Enemy_CollisionBits,y ;load first enemy's collision-related bits + and SetBitsMask,x ;check to see if bit connected to second enemy is + bne ReadyNextEnemy ;already set, and move onto next enemy slot if set + lda Enemy_CollisionBits,y + ora SetBitsMask,x ;if the bit is not set, set it now + sta Enemy_CollisionBits,y +YesEC: jsr ProcEnemyCollisions ;react according to the nature of collision + jmp ReadyNextEnemy ;move onto next enemy slot + +NoEnemyCollision: + lda Enemy_CollisionBits,y ;load first enemy's collision-related bits + and ClearBitsMask,x ;clear bit connected to second enemy + sta Enemy_CollisionBits,y ;then move onto next enemy slot + +ReadyNextEnemy: + pla ;get first enemy's bounding box offset from the stack + tay ;use as Y again + ldx $01 ;get and decrement second enemy's object buffer offset + dex + bpl ECLoop ;loop until all enemy slots have been checked + +ExitECRoutine: + ldx ObjectOffset ;get enemy object buffer offset + rts ;leave + +ProcEnemyCollisions: + lda Enemy_State,y ;check both enemy states for d5 set + ora Enemy_State,x + and #%00100000 ;if d5 is set in either state, or both, branch + bne ExitProcessEColl ;to leave and do nothing else at this point + lda Enemy_State,x + cmp #$06 ;if second enemy state < $06, branch elsewhere + bcc ProcSecondEnemyColl + lda Enemy_ID,x ;check second enemy identifier for hammer bro + cmp #HammerBro ;if hammer bro found in alt state, branch to leave + beq ExitProcessEColl + lda Enemy_State,y ;check first enemy state for d7 set + asl + bcc ShellCollisions ;branch if d7 is clear + lda #$06 + jsr SetupFloateyNumber ;award 1000 points for killing enemy + jsr ShellOrBlockDefeat ;then kill enemy, then load + ldy $01 ;original offset of second enemy + +ShellCollisions: + tya ;move Y to X + tax + jsr ShellOrBlockDefeat ;kill second enemy + ldx ObjectOffset + lda ShellChainCounter,x ;get chain counter for shell + clc + adc #$04 ;add four to get appropriate point offset + ldx $01 + jsr SetupFloateyNumber ;award appropriate number of points for second enemy + ldx ObjectOffset ;load original offset of first enemy + inc ShellChainCounter,x ;increment chain counter for additional enemies + +ExitProcessEColl: + rts ;leave!!! + +ProcSecondEnemyColl: + lda Enemy_State,y ;if first enemy state < $06, branch elsewhere + cmp #$06 + bcc MoveEOfs + lda Enemy_ID,y ;check first enemy identifier for hammer bro + cmp #HammerBro ;if hammer bro found in alt state, branch to leave + beq ExitProcessEColl + jsr ShellOrBlockDefeat ;otherwise, kill first enemy + ldy $01 + lda ShellChainCounter,y ;get chain counter for shell + clc + adc #$04 ;add four to get appropriate point offset + ldx ObjectOffset + jsr SetupFloateyNumber ;award appropriate number of points for first enemy + ldx $01 ;load original offset of second enemy + inc ShellChainCounter,x ;increment chain counter for additional enemies + rts ;leave!!! + +MoveEOfs: + tya ;move Y ($01) to X + tax + jsr EnemyTurnAround ;do the sub here using value from $01 + ldx ObjectOffset ;then do it again using value from $08 + +EnemyTurnAround: + lda Enemy_ID,x ;check for specific enemies + cmp #PiranhaPlant + beq ExTA ;if piranha plant, leave + cmp #Lakitu + beq ExTA ;if lakitu, leave + cmp #HammerBro + beq ExTA ;if hammer bro, leave + cmp #Spiny + beq RXSpd ;if spiny, turn it around + cmp #GreenParatroopaJump + beq RXSpd ;if green paratroopa, turn it around + cmp #$07 + bcs ExTA ;if any OTHER enemy object => $07, leave +RXSpd: lda Enemy_X_Speed,x ;load horizontal speed + eor #$ff ;get two's compliment for horizontal speed + tay + iny + sty Enemy_X_Speed,x ;store as new horizontal speed + lda Enemy_MovingDir,x + eor #%00000011 ;invert moving direction and store, then leave + sta Enemy_MovingDir,x ;thus effectively turning the enemy around +ExTA: rts ;leave!!! + +;------------------------------------------------------------------------------------- +;$00 - vertical position of platform + +LargePlatformCollision: + lda #$ff ;save value here + sta PlatformCollisionFlag,x + lda TimerControl ;check master timer control + bne ExLPC ;if set, branch to leave + lda Enemy_State,x ;if d7 set in object state, + bmi ExLPC ;branch to leave + lda Enemy_ID,x + cmp #$24 ;check enemy object identifier for + bne ChkForPlayerC_LargeP ;balance platform, branch if not found + lda Enemy_State,x + tax ;set state as enemy offset here + jsr ChkForPlayerC_LargeP ;perform code with state offset, then original offset, in X + +ChkForPlayerC_LargeP: + jsr CheckPlayerVertical ;figure out if player is below a certain point + bcs ExLPC ;or offscreen, branch to leave if true + txa + jsr GetEnemyBoundBoxOfsArg ;get bounding box offset in Y + lda Enemy_Y_Position,x ;store vertical coordinate in + sta $00 ;temp variable for now + txa ;send offset we're on to the stack + pha + jsr PlayerCollisionCore ;do player-to-platform collision detection + pla ;retrieve offset from the stack + tax + bcc ExLPC ;if no collision, branch to leave + jsr ProcLPlatCollisions ;otherwise collision, perform sub +ExLPC: ldx ObjectOffset ;get enemy object buffer offset and leave + rts + +;-------------------------------- +;$00 - counter for bounding boxes + +SmallPlatformCollision: + lda TimerControl ;if master timer control set, + bne ExSPC ;branch to leave + sta PlatformCollisionFlag,x ;otherwise initialize collision flag + jsr CheckPlayerVertical ;do a sub to see if player is below a certain point + bcs ExSPC ;or entirely offscreen, and branch to leave if true + lda #$02 + sta $00 ;load counter here for 2 bounding boxes + +ChkSmallPlatLoop: + ldx ObjectOffset ;get enemy object offset + jsr GetEnemyBoundBoxOfs ;get bounding box offset in Y + and #%00000010 ;if d1 of offscreen lower nybble bits was set + bne ExSPC ;then branch to leave + lda BoundingBox_UL_YPos,y ;check top of platform's bounding box for being + cmp #$20 ;above a specific point + bcc MoveBoundBox ;if so, branch, don't do collision detection + jsr PlayerCollisionCore ;otherwise, perform player-to-platform collision detection + bcs ProcSPlatCollisions ;skip ahead if collision + +MoveBoundBox: + lda BoundingBox_UL_YPos,y ;move bounding box vertical coordinates + clc ;128 pixels downwards + adc #$80 + sta BoundingBox_UL_YPos,y + lda BoundingBox_DR_YPos,y + clc + adc #$80 + sta BoundingBox_DR_YPos,y + dec $00 ;decrement counter we set earlier + bne ChkSmallPlatLoop ;loop back until both bounding boxes are checked +ExSPC: ldx ObjectOffset ;get enemy object buffer offset, then leave + rts + +;-------------------------------- + +ProcSPlatCollisions: + ldx ObjectOffset ;return enemy object buffer offset to X, then continue + +ProcLPlatCollisions: + lda BoundingBox_DR_YPos,y ;get difference by subtracting the top + sec ;of the player's bounding box from the bottom + sbc BoundingBox_UL_YPos ;of the platform's bounding box + cmp #$04 ;if difference too large or negative, + bcs ChkForTopCollision ;branch, do not alter vertical speed of player + lda Player_Y_Speed ;check to see if player's vertical speed is moving down + bpl ChkForTopCollision ;if so, don't mess with it + lda #$01 ;otherwise, set vertical + sta Player_Y_Speed ;speed of player to kill jump + +ChkForTopCollision: + lda BoundingBox_DR_YPos ;get difference by subtracting the top + sec ;of the platform's bounding box from the bottom + sbc BoundingBox_UL_YPos,y ;of the player's bounding box + cmp #$06 + bcs PlatformSideCollisions ;if difference not close enough, skip all of this + lda Player_Y_Speed + bmi PlatformSideCollisions ;if player's vertical speed moving upwards, skip this + lda $00 ;get saved bounding box counter from earlier + ldy Enemy_ID,x + cpy #$2b ;if either of the two small platform objects are found, + beq SetCollisionFlag ;regardless of which one, branch to use bounding box counter + cpy #$2c ;as contents of collision flag + beq SetCollisionFlag + txa ;otherwise use enemy object buffer offset + +SetCollisionFlag: + ldx ObjectOffset ;get enemy object buffer offset + sta PlatformCollisionFlag,x ;save either bounding box counter or enemy offset here + lda #$00 + sta Player_State ;set player state to normal then leave + rts + +PlatformSideCollisions: + lda #$01 ;set value here to indicate possible horizontal + sta $00 ;collision on left side of platform + lda BoundingBox_DR_XPos ;get difference by subtracting platform's left edge + sec ;from player's right edge + sbc BoundingBox_UL_XPos,y + cmp #$08 ;if difference close enough, skip all of this + bcc SideC + inc $00 ;otherwise increment value set here for right side collision + lda BoundingBox_DR_XPos,y ;get difference by subtracting player's left edge + clc ;from platform's right edge + sbc BoundingBox_UL_XPos + cmp #$09 ;if difference not close enough, skip subroutine + bcs NoSideC ;and instead branch to leave (no collision) +SideC: jsr ImpedePlayerMove ;deal with horizontal collision +NoSideC: ldx ObjectOffset ;return with enemy object buffer offset + rts + +;------------------------------------------------------------------------------------- + +PlayerPosSPlatData: + .db $80, $00 + +PositionPlayerOnS_Plat: + tay ;use bounding box counter saved in collision flag + lda Enemy_Y_Position,x ;for offset + clc ;add positioning data using offset to the vertical + adc PlayerPosSPlatData-1,y ;coordinate + .db $2c ;BIT instruction opcode + +PositionPlayerOnVPlat: + lda Enemy_Y_Position,x ;get vertical coordinate + ldy GameEngineSubroutine + cpy #$0b ;if certain routine being executed on this frame, + beq ExPlPos ;skip all of this + ldy Enemy_Y_HighPos,x + cpy #$01 ;if vertical high byte offscreen, skip this + bne ExPlPos + sec ;subtract 32 pixels from vertical coordinate + sbc #$20 ;for the player object's height + sta Player_Y_Position ;save as player's new vertical coordinate + tya + sbc #$00 ;subtract borrow and store as player's + sta Player_Y_HighPos ;new vertical high byte + lda #$00 + sta Player_Y_Speed ;initialize vertical speed and low byte of force + sta Player_Y_MoveForce ;and then leave +ExPlPos: rts + +;------------------------------------------------------------------------------------- + +CheckPlayerVertical: + lda Player_OffscreenBits ;if player object is completely offscreen + cmp #$f0 ;vertically, leave this routine + bcs ExCPV + ldy Player_Y_HighPos ;if player high vertical byte is not + dey ;within the screen, leave this routine + bne ExCPV + lda Player_Y_Position ;if on the screen, check to see how far down + cmp #$d0 ;the player is vertically +ExCPV: rts + +;------------------------------------------------------------------------------------- + +GetEnemyBoundBoxOfs: + lda ObjectOffset ;get enemy object buffer offset + +GetEnemyBoundBoxOfsArg: + asl ;multiply A by four, then add four + asl ;to skip player's bounding box + clc + adc #$04 + tay ;send to Y + lda Enemy_OffscreenBits ;get offscreen bits for enemy object + and #%00001111 ;save low nybble + cmp #%00001111 ;check for all bits set + rts + +;------------------------------------------------------------------------------------- +;$00-$01 - used to hold many values, essentially temp variables +;$04 - holds lower nybble of vertical coordinate from block buffer routine +;$eb - used to hold block buffer adder + +PlayerBGUpperExtent: + .db $20, $10 + +PlayerBGCollision: + lda DisableCollisionDet ;if collision detection disabled flag set, + bne ExPBGCol ;branch to leave + lda GameEngineSubroutine + cmp #$0b ;if running routine #11 or $0b + beq ExPBGCol ;branch to leave + cmp #$04 + bcc ExPBGCol ;if running routines $00-$03 branch to leave + lda #$01 ;load default player state for swimming + ldy SwimmingFlag ;if swimming flag set, + bne SetPSte ;branch ahead to set default state + lda Player_State ;if player in normal state, + beq SetFallS ;branch to set default state for falling + cmp #$03 + bne ChkOnScr ;if in any other state besides climbing, skip to next part +SetFallS: lda #$02 ;load default player state for falling +SetPSte: sta Player_State ;set whatever player state is appropriate +ChkOnScr: lda Player_Y_HighPos + cmp #$01 ;check player's vertical high byte for still on the screen + bne ExPBGCol ;branch to leave if not + lda #$ff + sta Player_CollisionBits ;initialize player's collision flag + lda Player_Y_Position + cmp #$cf ;check player's vertical coordinate + bcc ChkCollSize ;if not too close to the bottom of screen, continue +ExPBGCol: rts ;otherwise leave + +ChkCollSize: + ldy #$02 ;load default offset + lda CrouchingFlag + bne GBBAdr ;if player crouching, skip ahead + lda PlayerSize + bne GBBAdr ;if player small, skip ahead + dey ;otherwise decrement offset for big player not crouching + lda SwimmingFlag + bne GBBAdr ;if swimming flag set, skip ahead + dey ;otherwise decrement offset +GBBAdr: lda BlockBufferAdderData,y ;get value using offset + sta $eb ;store value here + tay ;put value into Y, as offset for block buffer routine + ldx PlayerSize ;get player's size as offset + lda CrouchingFlag + beq HeadChk ;if player not crouching, branch ahead + inx ;otherwise increment size as offset +HeadChk: lda Player_Y_Position ;get player's vertical coordinate + cmp PlayerBGUpperExtent,x ;compare with upper extent value based on offset + bcc DoFootCheck ;if player is too high, skip this part + jsr BlockBufferColli_Head ;do player-to-bg collision detection on top of + beq DoFootCheck ;player, and branch if nothing above player's head + jsr CheckForCoinMTiles ;check to see if player touched coin with their head + bcs AwardTouchedCoin ;if so, branch to some other part of code + ldy Player_Y_Speed ;check player's vertical speed + bpl DoFootCheck ;if player not moving upwards, branch elsewhere + ldy $04 ;check lower nybble of vertical coordinate returned + cpy #$04 ;from collision detection routine + bcc DoFootCheck ;if low nybble < 4, branch + jsr CheckForSolidMTiles ;check to see what player's head bumped on + bcs SolidOrClimb ;if player collided with solid metatile, branch + ldy AreaType ;otherwise check area type + beq NYSpd ;if water level, branch ahead + ldy BlockBounceTimer ;if block bounce timer not expired, + bne NYSpd ;branch ahead, do not process collision + jsr PlayerHeadCollision ;otherwise do a sub to process collision + jmp DoFootCheck ;jump ahead to skip these other parts here + +SolidOrClimb: + cmp #$26 ;if climbing metatile, + beq NYSpd ;branch ahead and do not play sound + lda #Sfx_Bump + sta Square1SoundQueue ;otherwise load bump sound +NYSpd: lda #$01 ;set player's vertical speed to nullify + sta Player_Y_Speed ;jump or swim + +DoFootCheck: + ldy $eb ;get block buffer adder offset + lda Player_Y_Position + cmp #$cf ;check to see how low player is + bcs DoPlayerSideCheck ;if player is too far down on screen, skip all of this + jsr BlockBufferColli_Feet ;do player-to-bg collision detection on bottom left of player + jsr CheckForCoinMTiles ;check to see if player touched coin with their left foot + bcs AwardTouchedCoin ;if so, branch to some other part of code + pha ;save bottom left metatile to stack + jsr BlockBufferColli_Feet ;do player-to-bg collision detection on bottom right of player + sta $00 ;save bottom right metatile here + pla + sta $01 ;pull bottom left metatile and save here + bne ChkFootMTile ;if anything here, skip this part + lda $00 ;otherwise check for anything in bottom right metatile + beq DoPlayerSideCheck ;and skip ahead if not + jsr CheckForCoinMTiles ;check to see if player touched coin with their right foot + bcc ChkFootMTile ;if not, skip unconditional jump and continue code + +AwardTouchedCoin: + jmp HandleCoinMetatile ;follow the code to erase coin and award to player 1 coin + +ChkFootMTile: + jsr CheckForClimbMTiles ;check to see if player landed on climbable metatiles + bcs DoPlayerSideCheck ;if so, branch + ldy Player_Y_Speed ;check player's vertical speed + bmi DoPlayerSideCheck ;if player moving upwards, branch + cmp #$c5 + bne ContChk ;if player did not touch axe, skip ahead + jmp HandleAxeMetatile ;otherwise jump to set modes of operation +ContChk: jsr ChkInvisibleMTiles ;do sub to check for hidden coin or 1-up blocks + beq DoPlayerSideCheck ;if either found, branch + ldy JumpspringAnimCtrl ;if jumpspring animating right now, + bne InitSteP ;branch ahead + ldy $04 ;check lower nybble of vertical coordinate returned + cpy #$05 ;from collision detection routine + bcc LandPlyr ;if lower nybble < 5, branch + lda Player_MovingDir + sta $00 ;use player's moving direction as temp variable + jmp ImpedePlayerMove ;jump to impede player's movement in that direction +LandPlyr: jsr ChkForLandJumpSpring ;do sub to check for jumpspring metatiles and deal with it + lda #$f0 + and Player_Y_Position ;mask out lower nybble of player's vertical position + sta Player_Y_Position ;and store as new vertical position to land player properly + jsr HandlePipeEntry ;do sub to process potential pipe entry + lda #$00 + sta Player_Y_Speed ;initialize vertical speed and fractional + sta Player_Y_MoveForce ;movement force to stop player's vertical movement + sta StompChainCounter ;initialize enemy stomp counter +InitSteP: lda #$00 + sta Player_State ;set player's state to normal + +DoPlayerSideCheck: + ldy $eb ;get block buffer adder offset + iny + iny ;increment offset 2 bytes to use adders for side collisions + lda #$02 ;set value here to be used as counter + sta $00 + +SideCheckLoop: + iny ;move onto the next one + sty $eb ;store it + lda Player_Y_Position + cmp #$20 ;check player's vertical position + bcc BHalf ;if player is in status bar area, branch ahead to skip this part + cmp #$e4 + bcs ExSCH ;branch to leave if player is too far down + jsr BlockBufferColli_Side ;do player-to-bg collision detection on one half of player + beq BHalf ;branch ahead if nothing found + cmp #$1c ;otherwise check for pipe metatiles + beq BHalf ;if collided with sideways pipe (top), branch ahead + cmp #$6b + beq BHalf ;if collided with water pipe (top), branch ahead + jsr CheckForClimbMTiles ;do sub to see if player bumped into anything climbable + bcc CheckSideMTiles ;if not, branch to alternate section of code +BHalf: ldy $eb ;load block adder offset + iny ;increment it + lda Player_Y_Position ;get player's vertical position + cmp #$08 + bcc ExSCH ;if too high, branch to leave + cmp #$d0 + bcs ExSCH ;if too low, branch to leave + jsr BlockBufferColli_Side ;do player-to-bg collision detection on other half of player + bne CheckSideMTiles ;if something found, branch + dec $00 ;otherwise decrement counter + bne SideCheckLoop ;run code until both sides of player are checked +ExSCH: rts ;leave + +CheckSideMTiles: + jsr ChkInvisibleMTiles ;check for hidden or coin 1-up blocks + beq ExCSM ;branch to leave if either found + jsr CheckForClimbMTiles ;check for climbable metatiles + bcc ContSChk ;if not found, skip and continue with code + jmp HandleClimbing ;otherwise jump to handle climbing +ContSChk: jsr CheckForCoinMTiles ;check to see if player touched coin + bcs HandleCoinMetatile ;if so, execute code to erase coin and award to player 1 coin + jsr ChkJumpspringMetatiles ;check for jumpspring metatiles + bcc ChkPBtm ;if not found, branch ahead to continue cude + lda JumpspringAnimCtrl ;otherwise check jumpspring animation control + bne ExCSM ;branch to leave if set + jmp StopPlayerMove ;otherwise jump to impede player's movement +ChkPBtm: ldy Player_State ;get player's state + cpy #$00 ;check for player's state set to normal + bne StopPlayerMove ;if not, branch to impede player's movement + ldy PlayerFacingDir ;get player's facing direction + dey + bne StopPlayerMove ;if facing left, branch to impede movement + cmp #$6c ;otherwise check for pipe metatiles + beq PipeDwnS ;if collided with sideways pipe (bottom), branch + cmp #$1f ;if collided with water pipe (bottom), continue + bne StopPlayerMove ;otherwise branch to impede player's movement +PipeDwnS: lda Player_SprAttrib ;check player's attributes + bne PlyrPipe ;if already set, branch, do not play sound again + ldy #Sfx_PipeDown_Injury + sty Square1SoundQueue ;otherwise load pipedown/injury sound +PlyrPipe: ora #%00100000 + sta Player_SprAttrib ;set background priority bit in player attributes + lda Player_X_Position + and #%00001111 ;get lower nybble of player's horizontal coordinate + beq ChkGERtn ;if at zero, branch ahead to skip this part + ldy #$00 ;set default offset for timer setting data + lda ScreenLeft_PageLoc ;load page location for left side of screen + beq SetCATmr ;if at page zero, use default offset + iny ;otherwise increment offset +SetCATmr: lda AreaChangeTimerData,y ;set timer for change of area as appropriate + sta ChangeAreaTimer +ChkGERtn: lda GameEngineSubroutine ;get number of game engine routine running + cmp #$07 + beq ExCSM ;if running player entrance routine or + cmp #$08 ;player control routine, go ahead and branch to leave + bne ExCSM + lda #$02 + sta GameEngineSubroutine ;otherwise set sideways pipe entry routine to run + rts ;and leave + +;-------------------------------- +;$02 - high nybble of vertical coordinate from block buffer +;$04 - low nybble of horizontal coordinate from block buffer +;$06-$07 - block buffer address + +StopPlayerMove: + jsr ImpedePlayerMove ;stop player's movement +ExCSM: rts ;leave + +AreaChangeTimerData: + .db $a0, $34 + +HandleCoinMetatile: + jsr ErACM ;do sub to erase coin metatile from block buffer + inc CoinTallyFor1Ups ;increment coin tally used for 1-up blocks + jmp GiveOneCoin ;update coin amount and tally on the screen + +HandleAxeMetatile: + lda #$00 + sta OperMode_Task ;reset secondary mode + lda #$02 + sta OperMode ;set primary mode to autoctrl mode + lda #$18 + sta Player_X_Speed ;set horizontal speed and continue to erase axe metatile +ErACM: ldy $02 ;load vertical high nybble offset for block buffer + lda #$00 ;load blank metatile + sta ($06),y ;store to remove old contents from block buffer + jmp RemoveCoin_Axe ;update the screen accordingly + +;-------------------------------- +;$02 - high nybble of vertical coordinate from block buffer +;$04 - low nybble of horizontal coordinate from block buffer +;$06-$07 - block buffer address + +ClimbXPosAdder: + .db $f9, $07 + +ClimbPLocAdder: + .db $ff, $00 + +FlagpoleYPosData: + .db $18, $22, $50, $68, $90 + +HandleClimbing: + ldy $04 ;check low nybble of horizontal coordinate returned from + cpy #$06 ;collision detection routine against certain values, this + bcc ExHC ;makes actual physical part of vine or flagpole thinner + cpy #$0a ;than 16 pixels + bcc ChkForFlagpole +ExHC: rts ;leave if too far left or too far right + +ChkForFlagpole: + cmp #$24 ;check climbing metatiles + beq FlagpoleCollision ;branch if flagpole ball found + cmp #$25 + bne VineCollision ;branch to alternate code if flagpole shaft not found + +FlagpoleCollision: + lda GameEngineSubroutine + cmp #$05 ;check for end-of-level routine running + beq PutPlayerOnVine ;if running, branch to end of climbing code + lda #$01 + sta PlayerFacingDir ;set player's facing direction to right + inc ScrollLock ;set scroll lock flag + lda GameEngineSubroutine + cmp #$04 ;check for flagpole slide routine running + beq RunFR ;if running, branch to end of flagpole code here + lda #BulletBill_CannonVar ;load identifier for bullet bills (cannon variant) + jsr KillEnemies ;get rid of them + lda #Silence + sta EventMusicQueue ;silence music + lsr + sta FlagpoleSoundQueue ;load flagpole sound into flagpole sound queue + ldx #$04 ;start at end of vertical coordinate data + lda Player_Y_Position + sta FlagpoleCollisionYPos ;store player's vertical coordinate here to be used later + +ChkFlagpoleYPosLoop: + cmp FlagpoleYPosData,x ;compare with current vertical coordinate data + bcs MtchF ;if player's => current, branch to use current offset + dex ;otherwise decrement offset to use + bne ChkFlagpoleYPosLoop ;do this until all data is checked (use last one if all checked) +MtchF: stx FlagpoleScore ;store offset here to be used later +RunFR: lda #$04 + sta GameEngineSubroutine ;set value to run flagpole slide routine + jmp PutPlayerOnVine ;jump to end of climbing code + +VineCollision: + cmp #$26 ;check for climbing metatile used on vines + bne PutPlayerOnVine + lda Player_Y_Position ;check player's vertical coordinate + cmp #$20 ;for being in status bar area + bcs PutPlayerOnVine ;branch if not that far up + lda #$01 + sta GameEngineSubroutine ;otherwise set to run autoclimb routine next frame + +PutPlayerOnVine: + lda #$03 ;set player state to climbing + sta Player_State + lda #$00 ;nullify player's horizontal speed + sta Player_X_Speed ;and fractional horizontal movement force + sta Player_X_MoveForce + lda Player_X_Position ;get player's horizontal coordinate + sec + sbc ScreenLeft_X_Pos ;subtract from left side horizontal coordinate + cmp #$10 + bcs SetVXPl ;if 16 or more pixels difference, do not alter facing direction + lda #$02 + sta PlayerFacingDir ;otherwise force player to face left +SetVXPl: ldy PlayerFacingDir ;get current facing direction, use as offset + lda $06 ;get low byte of block buffer address + asl + asl ;move low nybble to high + asl + asl + clc + adc ClimbXPosAdder-1,y ;add pixels depending on facing direction + sta Player_X_Position ;store as player's horizontal coordinate + lda $06 ;get low byte of block buffer address again + bne ExPVne ;if not zero, branch + lda ScreenRight_PageLoc ;load page location of right side of screen + clc + adc ClimbPLocAdder-1,y ;add depending on facing location + sta Player_PageLoc ;store as player's page location +ExPVne: rts ;finally, we're done! + +;-------------------------------- + +ChkInvisibleMTiles: + cmp #$5f ;check for hidden coin block + beq ExCInvT ;branch to leave if found + cmp #$60 ;check for hidden 1-up block +ExCInvT: rts ;leave with zero flag set if either found + +;-------------------------------- +;$00-$01 - used to hold bottom right and bottom left metatiles (in that order) +;$00 - used as flag by ImpedePlayerMove to restrict specific movement + +ChkForLandJumpSpring: + jsr ChkJumpspringMetatiles ;do sub to check if player landed on jumpspring + bcc ExCJSp ;if carry not set, jumpspring not found, therefore leave + lda #$70 + sta VerticalForce ;otherwise set vertical movement force for player + lda #$f9 + sta JumpspringForce ;set default jumpspring force + lda #$03 + sta JumpspringTimer ;set jumpspring timer to be used later + lsr + sta JumpspringAnimCtrl ;set jumpspring animation control to start animating +ExCJSp: rts ;and leave + +ChkJumpspringMetatiles: + cmp #$67 ;check for top jumpspring metatile + beq JSFnd ;branch to set carry if found + cmp #$68 ;check for bottom jumpspring metatile + clc ;clear carry flag + bne NoJSFnd ;branch to use cleared carry if not found +JSFnd: sec ;set carry if found +NoJSFnd: rts ;leave + +HandlePipeEntry: + lda Up_Down_Buttons ;check saved controller bits from earlier + and #%00000100 ;for pressing down + beq ExPipeE ;if not pressing down, branch to leave + lda $00 + cmp #$11 ;check right foot metatile for warp pipe right metatile + bne ExPipeE ;branch to leave if not found + lda $01 + cmp #$10 ;check left foot metatile for warp pipe left metatile + bne ExPipeE ;branch to leave if not found + lda #$30 + sta ChangeAreaTimer ;set timer for change of area + lda #$03 + sta GameEngineSubroutine ;set to run vertical pipe entry routine on next frame + lda #Sfx_PipeDown_Injury + sta Square1SoundQueue ;load pipedown/injury sound + lda #%00100000 + sta Player_SprAttrib ;set background priority bit in player's attributes + lda WarpZoneControl ;check warp zone control + beq ExPipeE ;branch to leave if none found + and #%00000011 ;mask out all but 2 LSB + asl + asl ;multiply by four + tax ;save as offset to warp zone numbers (starts at left pipe) + lda Player_X_Position ;get player's horizontal position + cmp #$60 + bcc GetWNum ;if player at left, not near middle, use offset and skip ahead + inx ;otherwise increment for middle pipe + cmp #$a0 + bcc GetWNum ;if player at middle, but not too far right, use offset and skip + inx ;otherwise increment for last pipe +GetWNum: ldy WarpZoneNumbers,x ;get warp zone numbers + dey ;decrement for use as world number + sty WorldNumber ;store as world number and offset + ldx WorldAddrOffsets,y ;get offset to where this world's area offsets are + lda AreaAddrOffsets,x ;get area offset based on world offset + sta AreaPointer ;store area offset here to be used to change areas + lda #Silence + sta EventMusicQueue ;silence music + lda #$00 + sta EntrancePage ;initialize starting page number + sta AreaNumber ;initialize area number used for area address offset + sta LevelNumber ;initialize level number used for world display + sta AltEntranceControl ;initialize mode of entry + inc Hidden1UpFlag ;set flag for hidden 1-up blocks + inc FetchNewGameTimerFlag ;set flag to load new game timer +ExPipeE: rts ;leave!!! + +ImpedePlayerMove: + lda #$00 ;initialize value here + ldy Player_X_Speed ;get player's horizontal speed + ldx $00 ;check value set earlier for + dex ;left side collision + bne RImpd ;if right side collision, skip this part + inx ;return value to X + cpy #$00 ;if player moving to the left, + bmi ExIPM ;branch to invert bit and leave + lda #$ff ;otherwise load A with value to be used later + jmp NXSpd ;and jump to affect movement +RImpd: ldx #$02 ;return $02 to X + cpy #$01 ;if player moving to the right, + bpl ExIPM ;branch to invert bit and leave + lda #$01 ;otherwise load A with value to be used here +NXSpd: ldy #$10 + sty SideCollisionTimer ;set timer of some sort + ldy #$00 + sty Player_X_Speed ;nullify player's horizontal speed + cmp #$00 ;if value set in A not set to $ff, + bpl PlatF ;branch ahead, do not decrement Y + dey ;otherwise decrement Y now +PlatF: sty $00 ;store Y as high bits of horizontal adder + clc + adc Player_X_Position ;add contents of A to player's horizontal + sta Player_X_Position ;position to move player left or right + lda Player_PageLoc + adc $00 ;add high bits and carry to + sta Player_PageLoc ;page location if necessary +ExIPM: txa ;invert contents of X + eor #$ff + and Player_CollisionBits ;mask out bit that was set here + sta Player_CollisionBits ;store to clear bit + rts + +;-------------------------------- + +SolidMTileUpperExt: + .db $10, $61, $88, $c4 + +CheckForSolidMTiles: + jsr GetMTileAttrib ;find appropriate offset based on metatile's 2 MSB + cmp SolidMTileUpperExt,x ;compare current metatile with solid metatiles + rts + +ClimbMTileUpperExt: + .db $24, $6d, $8a, $c6 + +CheckForClimbMTiles: + jsr GetMTileAttrib ;find appropriate offset based on metatile's 2 MSB + cmp ClimbMTileUpperExt,x ;compare current metatile with climbable metatiles + rts + +CheckForCoinMTiles: + cmp #$c2 ;check for regular coin + beq CoinSd ;branch if found + cmp #$c3 ;check for underwater coin + beq CoinSd ;branch if found + clc ;otherwise clear carry and leave + rts +CoinSd: lda #Sfx_CoinGrab + sta Square2SoundQueue ;load coin grab sound and leave + rts + +GetMTileAttrib: + tay ;save metatile value into Y + and #%11000000 ;mask out all but 2 MSB + asl + rol ;shift and rotate d7-d6 to d1-d0 + rol + tax ;use as offset for metatile data + tya ;get original metatile value back +ExEBG: rts ;leave + +;------------------------------------------------------------------------------------- +;$06-$07 - address from block buffer routine + +EnemyBGCStateData: + .db $01, $01, $02, $02, $02, $05 + +EnemyBGCXSpdData: + .db $10, $f0 + +EnemyToBGCollisionDet: + lda Enemy_State,x ;check enemy state for d6 set + and #%00100000 + bne ExEBG ;if set, branch to leave + jsr SubtEnemyYPos ;otherwise, do a subroutine here + bcc ExEBG ;if enemy vertical coord + 62 < 68, branch to leave + ldy Enemy_ID,x + cpy #Spiny ;if enemy object is not spiny, branch elsewhere + bne DoIDCheckBGColl + lda Enemy_Y_Position,x + cmp #$25 ;if enemy vertical coordinate < 36 branch to leave + bcc ExEBG + +DoIDCheckBGColl: + cpy #GreenParatroopaJump ;check for some other enemy object + bne HBChk ;branch if not found + jmp EnemyJump ;otherwise jump elsewhere +HBChk: cpy #HammerBro ;check for hammer bro + bne CInvu ;branch if not found + jmp HammerBroBGColl ;otherwise jump elsewhere +CInvu: cpy #Spiny ;if enemy object is spiny, branch + beq YesIn + cpy #PowerUpObject ;if special power-up object, branch + beq YesIn + cpy #$07 ;if enemy object =>$07, branch to leave + bcs ExEBGChk +YesIn: jsr ChkUnderEnemy ;if enemy object < $07, or = $12 or $2e, do this sub + bne HandleEToBGCollision ;if block underneath enemy, branch + +NoEToBGCollision: + jmp ChkForRedKoopa ;otherwise skip and do something else + +;-------------------------------- +;$02 - vertical coordinate from block buffer routine + +HandleEToBGCollision: + jsr ChkForNonSolids ;if something is underneath enemy, find out what + beq NoEToBGCollision ;if blank $26, coins, or hidden blocks, jump, enemy falls through + cmp #$23 + bne LandEnemyProperly ;check for blank metatile $23 and branch if not found + ldy $02 ;get vertical coordinate used to find block + lda #$00 ;store default blank metatile in that spot so we won't + sta ($06),y ;trigger this routine accidentally again + lda Enemy_ID,x + cmp #$15 ;if enemy object => $15, branch ahead + bcs ChkToStunEnemies + cmp #Goomba ;if enemy object not goomba, branch ahead of this routine + bne GiveOEPoints + jsr KillEnemyAboveBlock ;if enemy object IS goomba, do this sub + +GiveOEPoints: + lda #$01 ;award 100 points for hitting block beneath enemy + jsr SetupFloateyNumber + +ChkToStunEnemies: + cmp #$09 ;perform many comparisons on enemy object identifier + bcc SetStun + cmp #$11 ;if the enemy object identifier is equal to the values + bcs SetStun ;$09, $0e, $0f or $10, it will be modified, and not + cmp #$0a ;modified if not any of those values, note that piranha plant will + bcc Demote ;always fail this test because A will still have vertical + cmp #PiranhaPlant ;coordinate from previous addition, also these comparisons + bcc SetStun ;are only necessary if branching from $d7a1 +Demote: and #%00000001 ;erase all but LSB, essentially turning enemy object + sta Enemy_ID,x ;into green or red koopa troopa to demote them +SetStun: lda Enemy_State,x ;load enemy state + and #%11110000 ;save high nybble + ora #%00000010 + sta Enemy_State,x ;set d1 of enemy state + dec Enemy_Y_Position,x + dec Enemy_Y_Position,x ;subtract two pixels from enemy's vertical position + lda Enemy_ID,x + cmp #Bloober ;check for bloober object + beq SetWYSpd + lda #$fd ;set default vertical speed + ldy AreaType + bne SetNotW ;if area type not water, set as speed, otherwise +SetWYSpd: lda #$ff ;change the vertical speed +SetNotW: sta Enemy_Y_Speed,x ;set vertical speed now + ldy #$01 + jsr PlayerEnemyDiff ;get horizontal difference between player and enemy object + bpl ChkBBill ;branch if enemy is to the right of player + iny ;increment Y if not +ChkBBill: lda Enemy_ID,x + cmp #BulletBill_CannonVar ;check for bullet bill (cannon variant) + beq NoCDirF + cmp #BulletBill_FrenzyVar ;check for bullet bill (frenzy variant) + beq NoCDirF ;branch if either found, direction does not change + sty Enemy_MovingDir,x ;store as moving direction +NoCDirF: dey ;decrement and use as offset + lda EnemyBGCXSpdData,y ;get proper horizontal speed + sta Enemy_X_Speed,x ;and store, then leave +ExEBGChk: rts + +;-------------------------------- +;$04 - low nybble of vertical coordinate from block buffer routine + +LandEnemyProperly: + lda $04 ;check lower nybble of vertical coordinate saved earlier + sec + sbc #$08 ;subtract eight pixels + cmp #$05 ;used to determine whether enemy landed from falling + bcs ChkForRedKoopa ;branch if lower nybble in range of $0d-$0f before subtract + lda Enemy_State,x + and #%01000000 ;branch if d6 in enemy state is set + bne LandEnemyInitState + lda Enemy_State,x + asl ;branch if d7 in enemy state is not set + bcc ChkLandedEnemyState +SChkA: jmp DoEnemySideCheck ;if lower nybble < $0d, d7 set but d6 not set, jump here + +ChkLandedEnemyState: + lda Enemy_State,x ;if enemy in normal state, branch back to jump here + beq SChkA + cmp #$05 ;if in state used by spiny's egg + beq ProcEnemyDirection ;then branch elsewhere + cmp #$03 ;if already in state used by koopas and buzzy beetles + bcs ExSteChk ;or in higher numbered state, branch to leave + lda Enemy_State,x ;load enemy state again (why?) + cmp #$02 ;if not in $02 state (used by koopas and buzzy beetles) + bne ProcEnemyDirection ;then branch elsewhere + lda #$10 ;load default timer here + ldy Enemy_ID,x ;check enemy identifier for spiny + cpy #Spiny + bne SetForStn ;branch if not found + lda #$00 ;set timer for $00 if spiny +SetForStn: sta EnemyIntervalTimer,x ;set timer here + lda #$03 ;set state here, apparently used to render + sta Enemy_State,x ;upside-down koopas and buzzy beetles + jsr EnemyLanding ;then land it properly +ExSteChk: rts ;then leave + +ProcEnemyDirection: + lda Enemy_ID,x ;check enemy identifier for goomba + cmp #Goomba ;branch if found + beq LandEnemyInitState + cmp #Spiny ;check for spiny + bne InvtD ;branch if not found + lda #$01 + sta Enemy_MovingDir,x ;send enemy moving to the right by default + lda #$08 + sta Enemy_X_Speed,x ;set horizontal speed accordingly + lda FrameCounter + and #%00000111 ;if timed appropriately, spiny will skip over + beq LandEnemyInitState ;trying to face the player +InvtD: ldy #$01 ;load 1 for enemy to face the left (inverted here) + jsr PlayerEnemyDiff ;get horizontal difference between player and enemy + bpl CNwCDir ;if enemy to the right of player, branch + iny ;if to the left, increment by one for enemy to face right (inverted) +CNwCDir: tya + cmp Enemy_MovingDir,x ;compare direction in A with current direction in memory + bne LandEnemyInitState + jsr ChkForBump_HammerBroJ ;if equal, not facing in correct dir, do sub to turn around + +LandEnemyInitState: + jsr EnemyLanding ;land enemy properly + lda Enemy_State,x + and #%10000000 ;if d7 of enemy state is set, branch + bne NMovShellFallBit + lda #$00 ;otherwise initialize enemy state and leave + sta Enemy_State,x ;note this will also turn spiny's egg into spiny + rts + +NMovShellFallBit: + lda Enemy_State,x ;nullify d6 of enemy state, save other bits + and #%10111111 ;and store, then leave + sta Enemy_State,x + rts + +;-------------------------------- + +ChkForRedKoopa: + lda Enemy_ID,x ;check for red koopa troopa $03 + cmp #RedKoopa + bne Chk2MSBSt ;branch if not found + lda Enemy_State,x + beq ChkForBump_HammerBroJ ;if enemy found and in normal state, branch +Chk2MSBSt: lda Enemy_State,x ;save enemy state into Y + tay + asl ;check for d7 set + bcc GetSteFromD ;branch if not set + lda Enemy_State,x + ora #%01000000 ;set d6 + jmp SetD6Ste ;jump ahead of this part +GetSteFromD: lda EnemyBGCStateData,y ;load new enemy state with old as offset +SetD6Ste: sta Enemy_State,x ;set as new state + +;-------------------------------- +;$00 - used to store bitmask (not used but initialized here) +;$eb - used in DoEnemySideCheck as counter and to compare moving directions + +DoEnemySideCheck: + lda Enemy_Y_Position,x ;if enemy within status bar, branch to leave + cmp #$20 ;because there's nothing there that impedes movement + bcc ExESdeC + ldy #$16 ;start by finding block to the left of enemy ($00,$14) + lda #$02 ;set value here in what is also used as + sta $eb ;OAM data offset +SdeCLoop: lda $eb ;check value + cmp Enemy_MovingDir,x ;compare value against moving direction + bne NextSdeC ;branch if different and do not seek block there + lda #$01 ;set flag in A for save horizontal coordinate + jsr BlockBufferChk_Enemy ;find block to left or right of enemy object + beq NextSdeC ;if nothing found, branch + jsr ChkForNonSolids ;check for non-solid blocks + bne ChkForBump_HammerBroJ ;branch if not found +NextSdeC: dec $eb ;move to the next direction + iny + cpy #$18 ;increment Y, loop only if Y < $18, thus we check + bcc SdeCLoop ;enemy ($00, $14) and ($10, $14) pixel coordinates +ExESdeC: rts + +ChkForBump_HammerBroJ: + cpx #$05 ;check if we're on the special use slot + beq NoBump ;and if so, branch ahead and do not play sound + lda Enemy_State,x ;if enemy state d7 not set, branch + asl ;ahead and do not play sound + bcc NoBump + lda #Sfx_Bump ;otherwise, play bump sound + sta Square1SoundQueue ;sound will never be played if branching from ChkForRedKoopa +NoBump: lda Enemy_ID,x ;check for hammer bro + cmp #$05 + bne InvEnemyDir ;branch if not found + lda #$00 + sta $00 ;initialize value here for bitmask + ldy #$fa ;load default vertical speed for jumping + jmp SetHJ ;jump to code that makes hammer bro jump + +InvEnemyDir: + jmp RXSpd ;jump to turn the enemy around + +;-------------------------------- +;$00 - used to hold horizontal difference between player and enemy + +PlayerEnemyDiff: + lda Enemy_X_Position,x ;get distance between enemy object's + sec ;horizontal coordinate and the player's + sbc Player_X_Position ;horizontal coordinate + sta $00 ;and store here + lda Enemy_PageLoc,x + sbc Player_PageLoc ;subtract borrow, then leave + rts + +;-------------------------------- + +EnemyLanding: + jsr InitVStf ;do something here to vertical speed and something else + lda Enemy_Y_Position,x + and #%11110000 ;save high nybble of vertical coordinate, and + ora #%00001000 ;set d3, then store, probably used to set enemy object + sta Enemy_Y_Position,x ;neatly on whatever it's landing on + rts + +SubtEnemyYPos: + lda Enemy_Y_Position,x ;add 62 pixels to enemy object's + clc ;vertical coordinate + adc #$3e + cmp #$44 ;compare against a certain range + rts ;and leave with flags set for conditional branch + +EnemyJump: + jsr SubtEnemyYPos ;do a sub here + bcc DoSide ;if enemy vertical coord + 62 < 68, branch to leave + lda Enemy_Y_Speed,x + clc ;add two to vertical speed + adc #$02 + cmp #$03 ;if green paratroopa not falling, branch ahead + bcc DoSide + jsr ChkUnderEnemy ;otherwise, check to see if green paratroopa is + beq DoSide ;standing on anything, then branch to same place if not + jsr ChkForNonSolids ;check for non-solid blocks + beq DoSide ;branch if found + jsr EnemyLanding ;change vertical coordinate and speed + lda #$fd + sta Enemy_Y_Speed,x ;make the paratroopa jump again +DoSide: jmp DoEnemySideCheck ;check for horizontal blockage, then leave + +;-------------------------------- + +HammerBroBGColl: + jsr ChkUnderEnemy ;check to see if hammer bro is standing on anything + beq NoUnderHammerBro + cmp #$23 ;check for blank metatile $23 and branch if not found + bne UnderHammerBro + +KillEnemyAboveBlock: + jsr ShellOrBlockDefeat ;do this sub to kill enemy + lda #$fc ;alter vertical speed of enemy and leave + sta Enemy_Y_Speed,x + rts + +UnderHammerBro: + lda EnemyFrameTimer,x ;check timer used by hammer bro + bne NoUnderHammerBro ;branch if not expired + lda Enemy_State,x + and #%10001000 ;save d7 and d3 from enemy state, nullify other bits + sta Enemy_State,x ;and store + jsr EnemyLanding ;modify vertical coordinate, speed and something else + jmp DoEnemySideCheck ;then check for horizontal blockage and leave + +NoUnderHammerBro: + lda Enemy_State,x ;if hammer bro is not standing on anything, set d0 + ora #$01 ;in the enemy state to indicate jumping or falling, then leave + sta Enemy_State,x + rts + +ChkUnderEnemy: + lda #$00 ;set flag in A for save vertical coordinate + ldy #$15 ;set Y to check the bottom middle (8,18) of enemy object + jmp BlockBufferChk_Enemy ;hop to it! + +ChkForNonSolids: + cmp #$26 ;blank metatile used for vines? + beq NSFnd + cmp #$c2 ;regular coin? + beq NSFnd + cmp #$c3 ;underwater coin? + beq NSFnd + cmp #$5f ;hidden coin block? + beq NSFnd + cmp #$60 ;hidden 1-up block? +NSFnd: rts + +;------------------------------------------------------------------------------------- + +FireballBGCollision: + lda Fireball_Y_Position,x ;check fireball's vertical coordinate + cmp #$18 + bcc ClearBounceFlag ;if within the status bar area of the screen, branch ahead + jsr BlockBufferChk_FBall ;do fireball to background collision detection on bottom of it + beq ClearBounceFlag ;if nothing underneath fireball, branch + jsr ChkForNonSolids ;check for non-solid metatiles + beq ClearBounceFlag ;branch if any found + lda Fireball_Y_Speed,x ;if fireball's vertical speed set to move upwards, + bmi InitFireballExplode ;branch to set exploding bit in fireball's state + lda FireballBouncingFlag,x ;if bouncing flag already set, + bne InitFireballExplode ;branch to set exploding bit in fireball's state + lda #$fd + sta Fireball_Y_Speed,x ;otherwise set vertical speed to move upwards (give it bounce) + lda #$01 + sta FireballBouncingFlag,x ;set bouncing flag + lda Fireball_Y_Position,x + and #$f8 ;modify vertical coordinate to land it properly + sta Fireball_Y_Position,x ;store as new vertical coordinate + rts ;leave + +ClearBounceFlag: + lda #$00 + sta FireballBouncingFlag,x ;clear bouncing flag by default + rts ;leave + +InitFireballExplode: + lda #$80 + sta Fireball_State,x ;set exploding flag in fireball's state + lda #Sfx_Bump + sta Square1SoundQueue ;load bump sound + rts ;leave + +;------------------------------------------------------------------------------------- +;$00 - used to hold one of bitmasks, or offset +;$01 - used for relative X coordinate, also used to store middle screen page location +;$02 - used for relative Y coordinate, also used to store middle screen coordinate + +;this data added to relative coordinates of sprite objects +;stored in order: left edge, top edge, right edge, bottom edge +BoundBoxCtrlData: + .db $02, $08, $0e, $20 + .db $03, $14, $0d, $20 + .db $02, $14, $0e, $20 + .db $02, $09, $0e, $15 + .db $00, $00, $18, $06 + .db $00, $00, $20, $0d + .db $00, $00, $30, $0d + .db $00, $00, $08, $08 + .db $06, $04, $0a, $08 + .db $03, $0e, $0d, $14 + .db $00, $02, $10, $15 + .db $04, $04, $0c, $1c + +GetFireballBoundBox: + txa ;add seven bytes to offset + clc ;to use in routines as offset for fireball + adc #$07 + tax + ldy #$02 ;set offset for relative coordinates + bne FBallB ;unconditional branch + +GetMiscBoundBox: + txa ;add nine bytes to offset + clc ;to use in routines as offset for misc object + adc #$09 + tax + ldy #$06 ;set offset for relative coordinates +FBallB: jsr BoundingBoxCore ;get bounding box coordinates + jmp CheckRightScreenBBox ;jump to handle any offscreen coordinates + +GetEnemyBoundBox: + ldy #$48 ;store bitmask here for now + sty $00 + ldy #$44 ;store another bitmask here for now and jump + jmp GetMaskedOffScrBits + +SmallPlatformBoundBox: + ldy #$08 ;store bitmask here for now + sty $00 + ldy #$04 ;store another bitmask here for now + +GetMaskedOffScrBits: + lda Enemy_X_Position,x ;get enemy object position relative + sec ;to the left side of the screen + sbc ScreenLeft_X_Pos + sta $01 ;store here + lda Enemy_PageLoc,x ;subtract borrow from current page location + sbc ScreenLeft_PageLoc ;of left side + bmi CMBits ;if enemy object is beyond left edge, branch + ora $01 + beq CMBits ;if precisely at the left edge, branch + ldy $00 ;if to the right of left edge, use value in $00 for A +CMBits: tya ;otherwise use contents of Y + and Enemy_OffscreenBits ;preserve bitwise whatever's in here + sta EnemyOffscrBitsMasked,x ;save masked offscreen bits here + bne MoveBoundBoxOffscreen ;if anything set here, branch + jmp SetupEOffsetFBBox ;otherwise, do something else + +LargePlatformBoundBox: + inx ;increment X to get the proper offset + jsr GetXOffscreenBits ;then jump directly to the sub for horizontal offscreen bits + dex ;decrement to return to original offset + cmp #$fe ;if completely offscreen, branch to put entire bounding + bcs MoveBoundBoxOffscreen ;box offscreen, otherwise start getting coordinates + +SetupEOffsetFBBox: + txa ;add 1 to offset to properly address + clc ;the enemy object memory locations + adc #$01 + tax + ldy #$01 ;load 1 as offset here, same reason + jsr BoundingBoxCore ;do a sub to get the coordinates of the bounding box + jmp CheckRightScreenBBox ;jump to handle offscreen coordinates of bounding box + +MoveBoundBoxOffscreen: + txa ;multiply offset by 4 + asl + asl + tay ;use as offset here + lda #$ff + sta EnemyBoundingBoxCoord,y ;load value into four locations here and leave + sta EnemyBoundingBoxCoord+1,y + sta EnemyBoundingBoxCoord+2,y + sta EnemyBoundingBoxCoord+3,y + rts + +BoundingBoxCore: + stx $00 ;save offset here + lda SprObject_Rel_YPos,y ;store object coordinates relative to screen + sta $02 ;vertically and horizontally, respectively + lda SprObject_Rel_XPos,y + sta $01 + txa ;multiply offset by four and save to stack + asl + asl + pha + tay ;use as offset for Y, X is left alone + lda SprObj_BoundBoxCtrl,x ;load value here to be used as offset for X + asl ;multiply that by four and use as X + asl + tax + lda $01 ;add the first number in the bounding box data to the + clc ;relative horizontal coordinate using enemy object offset + adc BoundBoxCtrlData,x ;and store somewhere using same offset * 4 + sta BoundingBox_UL_Corner,y ;store here + lda $01 + clc + adc BoundBoxCtrlData+2,x ;add the third number in the bounding box data to the + sta BoundingBox_LR_Corner,y ;relative horizontal coordinate and store + inx ;increment both offsets + iny + lda $02 ;add the second number to the relative vertical coordinate + clc ;using incremented offset and store using the other + adc BoundBoxCtrlData,x ;incremented offset + sta BoundingBox_UL_Corner,y + lda $02 + clc + adc BoundBoxCtrlData+2,x ;add the fourth number to the relative vertical coordinate + sta BoundingBox_LR_Corner,y ;and store + pla ;get original offset loaded into $00 * y from stack + tay ;use as Y + ldx $00 ;get original offset and use as X again + rts + +CheckRightScreenBBox: + lda ScreenLeft_X_Pos ;add 128 pixels to left side of screen + clc ;and store as horizontal coordinate of middle + adc #$80 + sta $02 + lda ScreenLeft_PageLoc ;add carry to page location of left side of screen + adc #$00 ;and store as page location of middle + sta $01 + lda SprObject_X_Position,x ;get horizontal coordinate + cmp $02 ;compare against middle horizontal coordinate + lda SprObject_PageLoc,x ;get page location + sbc $01 ;subtract from middle page location + bcc CheckLeftScreenBBox ;if object is on the left side of the screen, branch + lda BoundingBox_DR_XPos,y ;check right-side edge of bounding box for offscreen + bmi NoOfs ;coordinates, branch if still on the screen + lda #$ff ;load offscreen value here to use on one or both horizontal sides + ldx BoundingBox_UL_XPos,y ;check left-side edge of bounding box for offscreen + bmi SORte ;coordinates, and branch if still on the screen + sta BoundingBox_UL_XPos,y ;store offscreen value for left side +SORte: sta BoundingBox_DR_XPos,y ;store offscreen value for right side +NoOfs: ldx ObjectOffset ;get object offset and leave + rts + +CheckLeftScreenBBox: + lda BoundingBox_UL_XPos,y ;check left-side edge of bounding box for offscreen + bpl NoOfs2 ;coordinates, and branch if still on the screen + cmp #$a0 ;check to see if left-side edge is in the middle of the + bcc NoOfs2 ;screen or really offscreen, and branch if still on + lda #$00 + ldx BoundingBox_DR_XPos,y ;check right-side edge of bounding box for offscreen + bpl SOLft ;coordinates, branch if still onscreen + sta BoundingBox_DR_XPos,y ;store offscreen value for right side +SOLft: sta BoundingBox_UL_XPos,y ;store offscreen value for left side +NoOfs2: ldx ObjectOffset ;get object offset and leave + rts + +;------------------------------------------------------------------------------------- +;$06 - second object's offset +;$07 - counter + +PlayerCollisionCore: + ldx #$00 ;initialize X to use player's bounding box for comparison + +SprObjectCollisionCore: + sty $06 ;save contents of Y here + lda #$01 + sta $07 ;save value 1 here as counter, compare horizontal coordinates first + +CollisionCoreLoop: + lda BoundingBox_UL_Corner,y ;compare left/top coordinates + cmp BoundingBox_UL_Corner,x ;of first and second objects' bounding boxes + bcs FirstBoxGreater ;if first left/top => second, branch + cmp BoundingBox_LR_Corner,x ;otherwise compare to right/bottom of second + bcc SecondBoxVerticalChk ;if first left/top < second right/bottom, branch elsewhere + beq CollisionFound ;if somehow equal, collision, thus branch + lda BoundingBox_LR_Corner,y ;if somehow greater, check to see if bottom of + cmp BoundingBox_UL_Corner,y ;first object's bounding box is greater than its top + bcc CollisionFound ;if somehow less, vertical wrap collision, thus branch + cmp BoundingBox_UL_Corner,x ;otherwise compare bottom of first bounding box to the top + bcs CollisionFound ;of second box, and if equal or greater, collision, thus branch + ldy $06 ;otherwise return with carry clear and Y = $0006 + rts ;note horizontal wrapping never occurs + +SecondBoxVerticalChk: + lda BoundingBox_LR_Corner,x ;check to see if the vertical bottom of the box + cmp BoundingBox_UL_Corner,x ;is greater than the vertical top + bcc CollisionFound ;if somehow less, vertical wrap collision, thus branch + lda BoundingBox_LR_Corner,y ;otherwise compare horizontal right or vertical bottom + cmp BoundingBox_UL_Corner,x ;of first box with horizontal left or vertical top of second box + bcs CollisionFound ;if equal or greater, collision, thus branch + ldy $06 ;otherwise return with carry clear and Y = $0006 + rts + +FirstBoxGreater: + cmp BoundingBox_UL_Corner,x ;compare first and second box horizontal left/vertical top again + beq CollisionFound ;if first coordinate = second, collision, thus branch + cmp BoundingBox_LR_Corner,x ;if not, compare with second object right or bottom edge + bcc CollisionFound ;if left/top of first less than or equal to right/bottom of second + beq CollisionFound ;then collision, thus branch + cmp BoundingBox_LR_Corner,y ;otherwise check to see if top of first box is greater than bottom + bcc NoCollisionFound ;if less than or equal, no collision, branch to end + beq NoCollisionFound + lda BoundingBox_LR_Corner,y ;otherwise compare bottom of first to top of second + cmp BoundingBox_UL_Corner,x ;if bottom of first is greater than top of second, vertical wrap + bcs CollisionFound ;collision, and branch, otherwise, proceed onwards here + +NoCollisionFound: + clc ;clear carry, then load value set earlier, then leave + ldy $06 ;like previous ones, if horizontal coordinates do not collide, we do + rts ;not bother checking vertical ones, because what's the point? + +CollisionFound: + inx ;increment offsets on both objects to check + iny ;the vertical coordinates + dec $07 ;decrement counter to reflect this + bpl CollisionCoreLoop ;if counter not expired, branch to loop + sec ;otherwise we already did both sets, therefore collision, so set carry + ldy $06 ;load original value set here earlier, then leave + rts + +;------------------------------------------------------------------------------------- +;$02 - modified y coordinate +;$03 - stores metatile involved in block buffer collisions +;$04 - comes in with offset to block buffer adder data, goes out with low nybble x/y coordinate +;$05 - modified x coordinate +;$06-$07 - block buffer address + +BlockBufferChk_Enemy: + pha ;save contents of A to stack + txa + clc ;add 1 to X to run sub with enemy offset in mind + adc #$01 + tax + pla ;pull A from stack and jump elsewhere + jmp BBChk_E + +ResidualMiscObjectCode: + txa + clc ;supposedly used once to set offset for + adc #$0d ;miscellaneous objects + tax + ldy #$1b ;supposedly used once to set offset for block buffer data + jmp ResJmpM ;probably used in early stages to do misc to bg collision detection + +BlockBufferChk_FBall: + ldy #$1a ;set offset for block buffer adder data + txa + clc + adc #$07 ;add seven bytes to use + tax +ResJmpM: lda #$00 ;set A to return vertical coordinate +BBChk_E: jsr BlockBufferCollision ;do collision detection subroutine for sprite object + ldx ObjectOffset ;get object offset + cmp #$00 ;check to see if object bumped into anything + rts + +BlockBufferAdderData: + .db $00, $07, $0e + +BlockBuffer_X_Adder: + .db $08, $03, $0c, $02, $02, $0d, $0d, $08 + .db $03, $0c, $02, $02, $0d, $0d, $08, $03 + .db $0c, $02, $02, $0d, $0d, $08, $00, $10 + .db $04, $14, $04, $04 + +BlockBuffer_Y_Adder: + .db $04, $20, $20, $08, $18, $08, $18, $02 + .db $20, $20, $08, $18, $08, $18, $12, $20 + .db $20, $18, $18, $18, $18, $18, $14, $14 + .db $06, $06, $08, $10 + +BlockBufferColli_Feet: + iny ;if branched here, increment to next set of adders + +BlockBufferColli_Head: + lda #$00 ;set flag to return vertical coordinate + .db $2c ;BIT instruction opcode + +BlockBufferColli_Side: + lda #$01 ;set flag to return horizontal coordinate + ldx #$00 ;set offset for player object + +BlockBufferCollision: + pha ;save contents of A to stack + sty $04 ;save contents of Y here + lda BlockBuffer_X_Adder,y ;add horizontal coordinate + clc ;of object to value obtained using Y as offset + adc SprObject_X_Position,x + sta $05 ;store here + lda SprObject_PageLoc,x + adc #$00 ;add carry to page location + and #$01 ;get LSB, mask out all other bits + lsr ;move to carry + ora $05 ;get stored value + ror ;rotate carry to MSB of A + lsr ;and effectively move high nybble to + lsr ;lower, LSB which became MSB will be + lsr ;d4 at this point + jsr GetBlockBufferAddr ;get address of block buffer into $06, $07 + ldy $04 ;get old contents of Y + lda SprObject_Y_Position,x ;get vertical coordinate of object + clc + adc BlockBuffer_Y_Adder,y ;add it to value obtained using Y as offset + and #%11110000 ;mask out low nybble + sec + sbc #$20 ;subtract 32 pixels for the status bar + sta $02 ;store result here + tay ;use as offset for block buffer + lda ($06),y ;check current content of block buffer + sta $03 ;and store here + ldy $04 ;get old contents of Y again + pla ;pull A from stack + bne RetXC ;if A = 1, branch + lda SprObject_Y_Position,x ;if A = 0, load vertical coordinate + jmp RetYC ;and jump +RetXC: lda SprObject_X_Position,x ;otherwise load horizontal coordinate +RetYC: and #%00001111 ;and mask out high nybble + sta $04 ;store masked out result here + lda $03 ;get saved content of block buffer + rts ;and leave + +;------------------------------------------------------------------------------------- + +;unused byte + .db $ff + +;------------------------------------------------------------------------------------- +;$00 - offset to vine Y coordinate adder +;$02 - offset to sprite data + +VineYPosAdder: + .db $00, $30 + +DrawVine: + sty $00 ;save offset here + lda Enemy_Rel_YPos ;get relative vertical coordinate + clc + adc VineYPosAdder,y ;add value using offset in Y to get value + ldx VineObjOffset,y ;get offset to vine + ldy Enemy_SprDataOffset,x ;get sprite data offset + sty $02 ;store sprite data offset here + jsr SixSpriteStacker ;stack six sprites on top of each other vertically + lda Enemy_Rel_XPos ;get relative horizontal coordinate + sta Sprite_X_Position,y ;store in first, third and fifth sprites + sta Sprite_X_Position+8,y + sta Sprite_X_Position+16,y + clc + adc #$06 ;add six pixels to second, fourth and sixth sprites + sta Sprite_X_Position+4,y ;to give characteristic staggered vine shape to + sta Sprite_X_Position+12,y ;our vertical stack of sprites + sta Sprite_X_Position+20,y + lda #%00100001 ;set bg priority and palette attribute bits + sta Sprite_Attributes,y ;set in first, third and fifth sprites + sta Sprite_Attributes+8,y + sta Sprite_Attributes+16,y + ora #%01000000 ;additionally, set horizontal flip bit + sta Sprite_Attributes+4,y ;for second, fourth and sixth sprites + sta Sprite_Attributes+12,y + sta Sprite_Attributes+20,y + ldx #$05 ;set tiles for six sprites +VineTL: lda #$e1 ;set tile number for sprite + sta Sprite_Tilenumber,y + iny ;move offset to next sprite data + iny + iny + iny + dex ;move onto next sprite + bpl VineTL ;loop until all sprites are done + ldy $02 ;get original offset + lda $00 ;get offset to vine adding data + bne SkpVTop ;if offset not zero, skip this part + lda #$e0 + sta Sprite_Tilenumber,y ;set other tile number for top of vine +SkpVTop: ldx #$00 ;start with the first sprite again +ChkFTop: lda VineStart_Y_Position ;get original starting vertical coordinate + sec + sbc Sprite_Y_Position,y ;subtract top-most sprite's Y coordinate + cmp #$64 ;if two coordinates are less than 100/$64 pixels + bcc NextVSp ;apart, skip this to leave sprite alone + lda #$f8 + sta Sprite_Y_Position,y ;otherwise move sprite offscreen +NextVSp: iny ;move offset to next OAM data + iny + iny + iny + inx ;move onto next sprite + cpx #$06 ;do this until all sprites are checked + bne ChkFTop + ldy $00 ;return offset set earlier + rts + +SixSpriteStacker: + ldx #$06 ;do six sprites +StkLp: sta Sprite_Data,y ;store X or Y coordinate into OAM data + clc + adc #$08 ;add eight pixels + iny + iny ;move offset four bytes forward + iny + iny + dex ;do another sprite + bne StkLp ;do this until all sprites are done + ldy $02 ;get saved OAM data offset and leave + rts + +;------------------------------------------------------------------------------------- + +FirstSprXPos: + .db $04, $00, $04, $00 + +FirstSprYPos: + .db $00, $04, $00, $04 + +SecondSprXPos: + .db $00, $08, $00, $08 + +SecondSprYPos: + .db $08, $00, $08, $00 + +FirstSprTilenum: + .db $80, $82, $81, $83 + +SecondSprTilenum: + .db $81, $83, $80, $82 + +HammerSprAttrib: + .db $03, $03, $c3, $c3 + +DrawHammer: + ldy Misc_SprDataOffset,x ;get misc object OAM data offset + lda TimerControl + bne ForceHPose ;if master timer control set, skip this part + lda Misc_State,x ;otherwise get hammer's state + and #%01111111 ;mask out d7 + cmp #$01 ;check to see if set to 1 yet + beq GetHPose ;if so, branch +ForceHPose: ldx #$00 ;reset offset here + beq RenderH ;do unconditional branch to rendering part +GetHPose: lda FrameCounter ;get frame counter + lsr ;move d3-d2 to d1-d0 + lsr + and #%00000011 ;mask out all but d1-d0 (changes every four frames) + tax ;use as timing offset +RenderH: lda Misc_Rel_YPos ;get relative vertical coordinate + clc + adc FirstSprYPos,x ;add first sprite vertical adder based on offset + sta Sprite_Y_Position,y ;store as sprite Y coordinate for first sprite + clc + adc SecondSprYPos,x ;add second sprite vertical adder based on offset + sta Sprite_Y_Position+4,y ;store as sprite Y coordinate for second sprite + lda Misc_Rel_XPos ;get relative horizontal coordinate + clc + adc FirstSprXPos,x ;add first sprite horizontal adder based on offset + sta Sprite_X_Position,y ;store as sprite X coordinate for first sprite + clc + adc SecondSprXPos,x ;add second sprite horizontal adder based on offset + sta Sprite_X_Position+4,y ;store as sprite X coordinate for second sprite + lda FirstSprTilenum,x + sta Sprite_Tilenumber,y ;get and store tile number of first sprite + lda SecondSprTilenum,x + sta Sprite_Tilenumber+4,y ;get and store tile number of second sprite + lda HammerSprAttrib,x + sta Sprite_Attributes,y ;get and store attribute bytes for both + sta Sprite_Attributes+4,y ;note in this case they use the same data + ldx ObjectOffset ;get misc object offset + lda Misc_OffscreenBits + and #%11111100 ;check offscreen bits + beq NoHOffscr ;if all bits clear, leave object alone + lda #$00 + sta Misc_State,x ;otherwise nullify misc object state + lda #$f8 + jsr DumpTwoSpr ;do sub to move hammer sprites offscreen +NoHOffscr: rts ;leave + +;------------------------------------------------------------------------------------- +;$00-$01 - used to hold tile numbers ($01 addressed in draw floatey number part) +;$02 - used to hold Y coordinate for floatey number +;$03 - residual byte used for flip (but value set here affects nothing) +;$04 - attribute byte for floatey number +;$05 - used as X coordinate for floatey number + +FlagpoleScoreNumTiles: + .db $f9, $50 + .db $f7, $50 + .db $fa, $fb + .db $f8, $fb + .db $f6, $fb + +FlagpoleGfxHandler: + ldy Enemy_SprDataOffset,x ;get sprite data offset for flagpole flag + lda Enemy_Rel_XPos ;get relative horizontal coordinate + sta Sprite_X_Position,y ;store as X coordinate for first sprite + clc + adc #$08 ;add eight pixels and store + sta Sprite_X_Position+4,y ;as X coordinate for second and third sprites + sta Sprite_X_Position+8,y + clc + adc #$0c ;add twelve more pixels and + sta $05 ;store here to be used later by floatey number + lda Enemy_Y_Position,x ;get vertical coordinate + jsr DumpTwoSpr ;and do sub to dump into first and second sprites + adc #$08 ;add eight pixels + sta Sprite_Y_Position+8,y ;and store into third sprite + lda FlagpoleFNum_Y_Pos ;get vertical coordinate for floatey number + sta $02 ;store it here + lda #$01 + sta $03 ;set value for flip which will not be used, and + sta $04 ;attribute byte for floatey number + sta Sprite_Attributes,y ;set attribute bytes for all three sprites + sta Sprite_Attributes+4,y + sta Sprite_Attributes+8,y + lda #$7e + sta Sprite_Tilenumber,y ;put triangle shaped tile + sta Sprite_Tilenumber+8,y ;into first and third sprites + lda #$7f + sta Sprite_Tilenumber+4,y ;put skull tile into second sprite + lda FlagpoleCollisionYPos ;get vertical coordinate at time of collision + beq ChkFlagOffscreen ;if zero, branch ahead + tya + clc ;add 12 bytes to sprite data offset + adc #$0c + tay ;put back in Y + lda FlagpoleScore ;get offset used to award points for touching flagpole + asl ;multiply by 2 to get proper offset here + tax + lda FlagpoleScoreNumTiles,x ;get appropriate tile data + sta $00 + lda FlagpoleScoreNumTiles+1,x + jsr DrawOneSpriteRow ;use it to render floatey number + +ChkFlagOffscreen: + ldx ObjectOffset ;get object offset for flag + ldy Enemy_SprDataOffset,x ;get OAM data offset + lda Enemy_OffscreenBits ;get offscreen bits + and #%00001110 ;mask out all but d3-d1 + beq ExitDumpSpr ;if none of these bits set, branch to leave + +;------------------------------------------------------------------------------------- + +MoveSixSpritesOffscreen: + lda #$f8 ;set offscreen coordinate if jumping here + +DumpSixSpr: + sta Sprite_Data+20,y ;dump A contents + sta Sprite_Data+16,y ;into third row sprites + +DumpFourSpr: + sta Sprite_Data+12,y ;into second row sprites + +DumpThreeSpr: + sta Sprite_Data+8,y + +DumpTwoSpr: + sta Sprite_Data+4,y ;and into first row sprites + sta Sprite_Data,y + +ExitDumpSpr: + rts + +;------------------------------------------------------------------------------------- + +DrawLargePlatform: + ldy Enemy_SprDataOffset,x ;get OAM data offset + sty $02 ;store here + iny ;add 3 to it for offset + iny ;to X coordinate + iny + lda Enemy_Rel_XPos ;get horizontal relative coordinate + jsr SixSpriteStacker ;store X coordinates using A as base, stack horizontally + ldx ObjectOffset + lda Enemy_Y_Position,x ;get vertical coordinate + jsr DumpFourSpr ;dump into first four sprites as Y coordinate + ldy AreaType + cpy #$03 ;check for castle-type level + beq ShrinkPlatform + ldy SecondaryHardMode ;check for secondary hard mode flag set + beq SetLast2Platform ;branch if not set elsewhere + +ShrinkPlatform: + lda #$f8 ;load offscreen coordinate if flag set or castle-type level + +SetLast2Platform: + ldy Enemy_SprDataOffset,x ;get OAM data offset + sta Sprite_Y_Position+16,y ;store vertical coordinate or offscreen + sta Sprite_Y_Position+20,y ;coordinate into last two sprites as Y coordinate + lda #$5b ;load default tile for platform (girder) + ldx CloudTypeOverride + beq SetPlatformTilenum ;if cloud level override flag not set, use + lda #$75 ;otherwise load other tile for platform (puff) + +SetPlatformTilenum: + ldx ObjectOffset ;get enemy object buffer offset + iny ;increment Y for tile offset + jsr DumpSixSpr ;dump tile number into all six sprites + lda #$02 ;set palette controls + iny ;increment Y for sprite attributes + jsr DumpSixSpr ;dump attributes into all six sprites + inx ;increment X for enemy objects + jsr GetXOffscreenBits ;get offscreen bits again + dex + ldy Enemy_SprDataOffset,x ;get OAM data offset + asl ;rotate d7 into carry, save remaining + pha ;bits to the stack + bcc SChk2 + lda #$f8 ;if d7 was set, move first sprite offscreen + sta Sprite_Y_Position,y +SChk2: pla ;get bits from stack + asl ;rotate d6 into carry + pha ;save to stack + bcc SChk3 + lda #$f8 ;if d6 was set, move second sprite offscreen + sta Sprite_Y_Position+4,y +SChk3: pla ;get bits from stack + asl ;rotate d5 into carry + pha ;save to stack + bcc SChk4 + lda #$f8 ;if d5 was set, move third sprite offscreen + sta Sprite_Y_Position+8,y +SChk4: pla ;get bits from stack + asl ;rotate d4 into carry + pha ;save to stack + bcc SChk5 + lda #$f8 ;if d4 was set, move fourth sprite offscreen + sta Sprite_Y_Position+12,y +SChk5: pla ;get bits from stack + asl ;rotate d3 into carry + pha ;save to stack + bcc SChk6 + lda #$f8 ;if d3 was set, move fifth sprite offscreen + sta Sprite_Y_Position+16,y +SChk6: pla ;get bits from stack + asl ;rotate d2 into carry + bcc SLChk ;save to stack + lda #$f8 + sta Sprite_Y_Position+20,y ;if d2 was set, move sixth sprite offscreen +SLChk: lda Enemy_OffscreenBits ;check d7 of offscreen bits + asl ;and if d7 is not set, skip sub + bcc ExDLPl + jsr MoveSixSpritesOffscreen ;otherwise branch to move all sprites offscreen +ExDLPl: rts + +;------------------------------------------------------------------------------------- + +DrawFloateyNumber_Coin: + lda FrameCounter ;get frame counter + lsr ;divide by 2 + bcs NotRsNum ;branch if d0 not set to raise number every other frame + dec Misc_Y_Position,x ;otherwise, decrement vertical coordinate +NotRsNum: lda Misc_Y_Position,x ;get vertical coordinate + jsr DumpTwoSpr ;dump into both sprites + lda Misc_Rel_XPos ;get relative horizontal coordinate + sta Sprite_X_Position,y ;store as X coordinate for first sprite + clc + adc #$08 ;add eight pixels + sta Sprite_X_Position+4,y ;store as X coordinate for second sprite + lda #$02 + sta Sprite_Attributes,y ;store attribute byte in both sprites + sta Sprite_Attributes+4,y + lda #$f7 + sta Sprite_Tilenumber,y ;put tile numbers into both sprites + lda #$fb ;that resemble "200" + sta Sprite_Tilenumber+4,y + jmp ExJCGfx ;then jump to leave (why not an rts here instead?) + +JumpingCoinTiles: + .db $60, $61, $62, $63 + +JCoinGfxHandler: + ldy Misc_SprDataOffset,x ;get coin/floatey number's OAM data offset + lda Misc_State,x ;get state of misc object + cmp #$02 ;if 2 or greater, + bcs DrawFloateyNumber_Coin ;branch to draw floatey number + lda Misc_Y_Position,x ;store vertical coordinate as + sta Sprite_Y_Position,y ;Y coordinate for first sprite + clc + adc #$08 ;add eight pixels + sta Sprite_Y_Position+4,y ;store as Y coordinate for second sprite + lda Misc_Rel_XPos ;get relative horizontal coordinate + sta Sprite_X_Position,y + sta Sprite_X_Position+4,y ;store as X coordinate for first and second sprites + lda FrameCounter ;get frame counter + lsr ;divide by 2 to alter every other frame + and #%00000011 ;mask out d2-d1 + tax ;use as graphical offset + lda JumpingCoinTiles,x ;load tile number + iny ;increment OAM data offset to write tile numbers + jsr DumpTwoSpr ;do sub to dump tile number into both sprites + dey ;decrement to get old offset + lda #$02 + sta Sprite_Attributes,y ;set attribute byte in first sprite + lda #$82 + sta Sprite_Attributes+4,y ;set attribute byte with vertical flip in second sprite + ldx ObjectOffset ;get misc object offset +ExJCGfx: rts ;leave + +;------------------------------------------------------------------------------------- +;$00-$01 - used to hold tiles for drawing the power-up, $00 also used to hold power-up type +;$02 - used to hold bottom row Y position +;$03 - used to hold flip control (not used here) +;$04 - used to hold sprite attributes +;$05 - used to hold X position +;$07 - counter + +;tiles arranged in top left, right, bottom left, right order +PowerUpGfxTable: + .db $76, $77, $78, $79 ;regular mushroom + .db $d6, $d6, $d9, $d9 ;fire flower + .db $8d, $8d, $e4, $e4 ;star + .db $76, $77, $78, $79 ;1-up mushroom + +PowerUpAttributes: + .db $02, $01, $02, $01 + +DrawPowerUp: + ldy Enemy_SprDataOffset+5 ;get power-up's sprite data offset + lda Enemy_Rel_YPos ;get relative vertical coordinate + clc + adc #$08 ;add eight pixels + sta $02 ;store result here + lda Enemy_Rel_XPos ;get relative horizontal coordinate + sta $05 ;store here + ldx PowerUpType ;get power-up type + lda PowerUpAttributes,x ;get attribute data for power-up type + ora Enemy_SprAttrib+5 ;add background priority bit if set + sta $04 ;store attributes here + txa + pha ;save power-up type to the stack + asl + asl ;multiply by four to get proper offset + tax ;use as X + lda #$01 + sta $07 ;set counter here to draw two rows of sprite object + sta $03 ;init d1 of flip control + +PUpDrawLoop: + lda PowerUpGfxTable,x ;load left tile of power-up object + sta $00 + lda PowerUpGfxTable+1,x ;load right tile + jsr DrawOneSpriteRow ;branch to draw one row of our power-up object + dec $07 ;decrement counter + bpl PUpDrawLoop ;branch until two rows are drawn + ldy Enemy_SprDataOffset+5 ;get sprite data offset again + pla ;pull saved power-up type from the stack + beq PUpOfs ;if regular mushroom, branch, do not change colors or flip + cmp #$03 + beq PUpOfs ;if 1-up mushroom, branch, do not change colors or flip + sta $00 ;store power-up type here now + lda FrameCounter ;get frame counter + lsr ;divide by 2 to change colors every two frames + and #%00000011 ;mask out all but d1 and d0 (previously d2 and d1) + ora Enemy_SprAttrib+5 ;add background priority bit if any set + sta Sprite_Attributes,y ;set as new palette bits for top left and + sta Sprite_Attributes+4,y ;top right sprites for fire flower and star + ldx $00 + dex ;check power-up type for fire flower + beq FlipPUpRightSide ;if found, skip this part + sta Sprite_Attributes+8,y ;otherwise set new palette bits for bottom left + sta Sprite_Attributes+12,y ;and bottom right sprites as well for star only + +FlipPUpRightSide: + lda Sprite_Attributes+4,y + ora #%01000000 ;set horizontal flip bit for top right sprite + sta Sprite_Attributes+4,y + lda Sprite_Attributes+12,y + ora #%01000000 ;set horizontal flip bit for bottom right sprite + sta Sprite_Attributes+12,y ;note these are only done for fire flower and star power-ups +PUpOfs: jmp SprObjectOffscrChk ;jump to check to see if power-up is offscreen at all, then leave + +;------------------------------------------------------------------------------------- +;$00-$01 - used in DrawEnemyObjRow to hold sprite tile numbers +;$02 - used to store Y position +;$03 - used to store moving direction, used to flip enemies horizontally +;$04 - used to store enemy's sprite attributes +;$05 - used to store X position +;$eb - used to hold sprite data offset +;$ec - used to hold either altered enemy state or special value used in gfx handler as condition +;$ed - used to hold enemy state from buffer +;$ef - used to hold enemy code used in gfx handler (may or may not resemble Enemy_ID values) + +;tiles arranged in top left, right, middle left, right, bottom left, right order +EnemyGraphicsTable: + .db $fc, $fc, $aa, $ab, $ac, $ad ;buzzy beetle frame 1 + .db $fc, $fc, $ae, $af, $b0, $b1 ; frame 2 + .db $fc, $a5, $a6, $a7, $a8, $a9 ;koopa troopa frame 1 + .db $fc, $a0, $a1, $a2, $a3, $a4 ; frame 2 + .db $69, $a5, $6a, $a7, $a8, $a9 ;koopa paratroopa frame 1 + .db $6b, $a0, $6c, $a2, $a3, $a4 ; frame 2 + .db $fc, $fc, $96, $97, $98, $99 ;spiny frame 1 + .db $fc, $fc, $9a, $9b, $9c, $9d ; frame 2 + .db $fc, $fc, $8f, $8e, $8e, $8f ;spiny's egg frame 1 + .db $fc, $fc, $95, $94, $94, $95 ; frame 2 + .db $fc, $fc, $dc, $dc, $df, $df ;bloober frame 1 + .db $dc, $dc, $dd, $dd, $de, $de ; frame 2 + .db $fc, $fc, $b2, $b3, $b4, $b5 ;cheep-cheep frame 1 + .db $fc, $fc, $b6, $b3, $b7, $b5 ; frame 2 + .db $fc, $fc, $70, $71, $72, $73 ;goomba + .db $fc, $fc, $6e, $6e, $6f, $6f ;koopa shell frame 1 (upside-down) + .db $fc, $fc, $6d, $6d, $6f, $6f ; frame 2 + .db $fc, $fc, $6f, $6f, $6e, $6e ;koopa shell frame 1 (rightsideup) + .db $fc, $fc, $6f, $6f, $6d, $6d ; frame 2 + .db $fc, $fc, $f4, $f4, $f5, $f5 ;buzzy beetle shell frame 1 (rightsideup) + .db $fc, $fc, $f4, $f4, $f5, $f5 ; frame 2 + .db $fc, $fc, $f5, $f5, $f4, $f4 ;buzzy beetle shell frame 1 (upside-down) + .db $fc, $fc, $f5, $f5, $f4, $f4 ; frame 2 + .db $fc, $fc, $fc, $fc, $ef, $ef ;defeated goomba + .db $b9, $b8, $bb, $ba, $bc, $bc ;lakitu frame 1 + .db $fc, $fc, $bd, $bd, $bc, $bc ; frame 2 + .db $7a, $7b, $da, $db, $d8, $d8 ;princess + .db $cd, $cd, $ce, $ce, $cf, $cf ;mushroom retainer + .db $7d, $7c, $d1, $8c, $d3, $d2 ;hammer bro frame 1 + .db $7d, $7c, $89, $88, $8b, $8a ; frame 2 + .db $d5, $d4, $e3, $e2, $d3, $d2 ; frame 3 + .db $d5, $d4, $e3, $e2, $8b, $8a ; frame 4 + .db $e5, $e5, $e6, $e6, $eb, $eb ;piranha plant frame 1 + .db $ec, $ec, $ed, $ed, $ee, $ee ; frame 2 + .db $fc, $fc, $d0, $d0, $d7, $d7 ;podoboo + .db $bf, $be, $c1, $c0, $c2, $fc ;bowser front frame 1 + .db $c4, $c3, $c6, $c5, $c8, $c7 ;bowser rear frame 1 + .db $bf, $be, $ca, $c9, $c2, $fc ; front frame 2 + .db $c4, $c3, $c6, $c5, $cc, $cb ; rear frame 2 + .db $fc, $fc, $e8, $e7, $ea, $e9 ;bullet bill + .db $f2, $f2, $f3, $f3, $f2, $f2 ;jumpspring frame 1 + .db $f1, $f1, $f1, $f1, $fc, $fc ; frame 2 + .db $f0, $f0, $fc, $fc, $fc, $fc ; frame 3 + +EnemyGfxTableOffsets: + .db $0c, $0c, $00, $0c, $0c, $a8, $54, $3c + .db $ea, $18, $48, $48, $cc, $c0, $18, $18 + .db $18, $90, $24, $ff, $48, $9c, $d2, $d8 + .db $f0, $f6, $fc + +EnemyAttributeData: + .db $01, $02, $03, $02, $01, $01, $03, $03 + .db $03, $01, $01, $02, $02, $21, $01, $02 + .db $01, $01, $02, $ff, $02, $02, $01, $01 + .db $02, $02, $02 + +EnemyAnimTimingBMask: + .db $08, $18 + +JumpspringFrameOffsets: + .db $18, $19, $1a, $19, $18 + +EnemyGfxHandler: + lda Enemy_Y_Position,x ;get enemy object vertical position + sta $02 + lda Enemy_Rel_XPos ;get enemy object horizontal position + sta $05 ;relative to screen + ldy Enemy_SprDataOffset,x + sty $eb ;get sprite data offset + lda #$00 + sta VerticalFlipFlag ;initialize vertical flip flag by default + lda Enemy_MovingDir,x + sta $03 ;get enemy object moving direction + lda Enemy_SprAttrib,x + sta $04 ;get enemy object sprite attributes + lda Enemy_ID,x + cmp #PiranhaPlant ;is enemy object piranha plant? + bne CheckForRetainerObj ;if not, branch + ldy PiranhaPlant_Y_Speed,x + bmi CheckForRetainerObj ;if piranha plant moving upwards, branch + ldy EnemyFrameTimer,x + beq CheckForRetainerObj ;if timer for movement expired, branch + rts ;if all conditions fail, leave + +CheckForRetainerObj: + lda Enemy_State,x ;store enemy state + sta $ed + and #%00011111 ;nullify all but 5 LSB and use as Y + tay + lda Enemy_ID,x ;check for mushroom retainer/princess object + cmp #RetainerObject + bne CheckForBulletBillCV ;if not found, branch + ldy #$00 ;if found, nullify saved state in Y + lda #$01 ;set value that will not be used + sta $03 + lda #$15 ;set value $15 as code for mushroom retainer/princess object + +CheckForBulletBillCV: + cmp #BulletBill_CannonVar ;otherwise check for bullet bill object + bne CheckForJumpspring ;if not found, branch again + dec $02 ;decrement saved vertical position + lda #$03 + ldy EnemyFrameTimer,x ;get timer for enemy object + beq SBBAt ;if expired, do not set priority bit + ora #%00100000 ;otherwise do so +SBBAt: sta $04 ;set new sprite attributes + ldy #$00 ;nullify saved enemy state both in Y and in + sty $ed ;memory location here + lda #$08 ;set specific value to unconditionally branch once + +CheckForJumpspring: + cmp #JumpspringObject ;check for jumpspring object + bne CheckForPodoboo + ldy #$03 ;set enemy state -2 MSB here for jumpspring object + ldx JumpspringAnimCtrl ;get current frame number for jumpspring object + lda JumpspringFrameOffsets,x ;load data using frame number as offset + +CheckForPodoboo: + sta $ef ;store saved enemy object value here + sty $ec ;and Y here (enemy state -2 MSB if not changed) + ldx ObjectOffset ;get enemy object offset + cmp #$0c ;check for podoboo object + bne CheckBowserGfxFlag ;branch if not found + lda Enemy_Y_Speed,x ;if moving upwards, branch + bmi CheckBowserGfxFlag + inc VerticalFlipFlag ;otherwise, set flag for vertical flip + +CheckBowserGfxFlag: + lda BowserGfxFlag ;if not drawing bowser at all, skip to something else + beq CheckForGoomba + ldy #$16 ;if set to 1, draw bowser's front + cmp #$01 + beq SBwsrGfxOfs + iny ;otherwise draw bowser's rear +SBwsrGfxOfs: sty $ef + +CheckForGoomba: + ldy $ef ;check value for goomba object + cpy #Goomba + bne CheckBowserFront ;branch if not found + lda Enemy_State,x + cmp #$02 ;check for defeated state + bcc GmbaAnim ;if not defeated, go ahead and animate + ldx #$04 ;if defeated, write new value here + stx $ec +GmbaAnim: and #%00100000 ;check for d5 set in enemy object state + ora TimerControl ;or timer disable flag set + bne CheckBowserFront ;if either condition true, do not animate goomba + lda FrameCounter + and #%00001000 ;check for every eighth frame + bne CheckBowserFront + lda $03 + eor #%00000011 ;invert bits to flip horizontally every eight frames + sta $03 ;leave alone otherwise + +CheckBowserFront: + lda EnemyAttributeData,y ;load sprite attribute using enemy object + ora $04 ;as offset, and add to bits already loaded + sta $04 + lda EnemyGfxTableOffsets,y ;load value based on enemy object as offset + tax ;save as X + ldy $ec ;get previously saved value + lda BowserGfxFlag + beq CheckForSpiny ;if not drawing bowser object at all, skip all of this + cmp #$01 + bne CheckBowserRear ;if not drawing front part, branch to draw the rear part + lda BowserBodyControls ;check bowser's body control bits + bpl ChkFrontSte ;branch if d7 not set (control's bowser's mouth) + ldx #$de ;otherwise load offset for second frame +ChkFrontSte: lda $ed ;check saved enemy state + and #%00100000 ;if bowser not defeated, do not set flag + beq DrawBowser + +FlipBowserOver: + stx VerticalFlipFlag ;set vertical flip flag to nonzero + +DrawBowser: + jmp DrawEnemyObject ;draw bowser's graphics now + +CheckBowserRear: + lda BowserBodyControls ;check bowser's body control bits + and #$01 + beq ChkRearSte ;branch if d0 not set (control's bowser's feet) + ldx #$e4 ;otherwise load offset for second frame +ChkRearSte: lda $ed ;check saved enemy state + and #%00100000 ;if bowser not defeated, do not set flag + beq DrawBowser + lda $02 ;subtract 16 pixels from + sec ;saved vertical coordinate + sbc #$10 + sta $02 + jmp FlipBowserOver ;jump to set vertical flip flag + +CheckForSpiny: + cpx #$24 ;check if value loaded is for spiny + bne CheckForLakitu ;if not found, branch + cpy #$05 ;if enemy state set to $05, do this, + bne NotEgg ;otherwise branch + ldx #$30 ;set to spiny egg offset + lda #$02 + sta $03 ;set enemy direction to reverse sprites horizontally + lda #$05 + sta $ec ;set enemy state +NotEgg: jmp CheckForHammerBro ;skip a big chunk of this if we found spiny but not in egg + +CheckForLakitu: + cpx #$90 ;check value for lakitu's offset loaded + bne CheckUpsideDownShell ;branch if not loaded + lda $ed + and #%00100000 ;check for d5 set in enemy state + bne NoLAFr ;branch if set + lda FrenzyEnemyTimer + cmp #$10 ;check timer to see if we've reached a certain range + bcs NoLAFr ;branch if not + ldx #$96 ;if d6 not set and timer in range, load alt frame for lakitu +NoLAFr: jmp CheckDefeatedState ;skip this next part if we found lakitu but alt frame not needed + +CheckUpsideDownShell: + lda $ef ;check for enemy object => $04 + cmp #$04 + bcs CheckRightSideUpShell ;branch if true + cpy #$02 + bcc CheckRightSideUpShell ;branch if enemy state < $02 + ldx #$5a ;set for upside-down koopa shell by default + ldy $ef + cpy #BuzzyBeetle ;check for buzzy beetle object + bne CheckRightSideUpShell + ldx #$7e ;set for upside-down buzzy beetle shell if found + inc $02 ;increment vertical position by one pixel + +CheckRightSideUpShell: + lda $ec ;check for value set here + cmp #$04 ;if enemy state < $02, do not change to shell, if + bne CheckForHammerBro ;enemy state => $02 but not = $04, leave shell upside-down + ldx #$72 ;set right-side up buzzy beetle shell by default + inc $02 ;increment saved vertical position by one pixel + ldy $ef + cpy #BuzzyBeetle ;check for buzzy beetle object + beq CheckForDefdGoomba ;branch if found + ldx #$66 ;change to right-side up koopa shell if not found + inc $02 ;and increment saved vertical position again + +CheckForDefdGoomba: + cpy #Goomba ;check for goomba object (necessary if previously + bne CheckForHammerBro ;failed buzzy beetle object test) + ldx #$54 ;load for regular goomba + lda $ed ;note that this only gets performed if enemy state => $02 + and #%00100000 ;check saved enemy state for d5 set + bne CheckForHammerBro ;branch if set + ldx #$8a ;load offset for defeated goomba + dec $02 ;set different value and decrement saved vertical position + +CheckForHammerBro: + ldy ObjectOffset + lda $ef ;check for hammer bro object + cmp #HammerBro + bne CheckForBloober ;branch if not found + lda $ed + beq CheckToAnimateEnemy ;branch if not in normal enemy state + and #%00001000 + beq CheckDefeatedState ;if d3 not set, branch further away + ldx #$b4 ;otherwise load offset for different frame + bne CheckToAnimateEnemy ;unconditional branch + +CheckForBloober: + cpx #$48 ;check for cheep-cheep offset loaded + beq CheckToAnimateEnemy ;branch if found + lda EnemyIntervalTimer,y + cmp #$05 + bcs CheckDefeatedState ;branch if some timer is above a certain point + cpx #$3c ;check for bloober offset loaded + bne CheckToAnimateEnemy ;branch if not found this time + cmp #$01 + beq CheckDefeatedState ;branch if timer is set to certain point + inc $02 ;increment saved vertical coordinate three pixels + inc $02 + inc $02 + jmp CheckAnimationStop ;and do something else + +CheckToAnimateEnemy: + lda $ef ;check for specific enemy objects + cmp #Goomba + beq CheckDefeatedState ;branch if goomba + cmp #$08 + beq CheckDefeatedState ;branch if bullet bill (note both variants use $08 here) + cmp #Podoboo + beq CheckDefeatedState ;branch if podoboo + cmp #$18 ;branch if => $18 + bcs CheckDefeatedState + ldy #$00 + cmp #$15 ;check for mushroom retainer/princess object + bne CheckForSecondFrame ;which uses different code here, branch if not found + iny ;residual instruction + lda WorldNumber ;are we on world 8? + cmp #World8 + bcs CheckDefeatedState ;if so, leave the offset alone (use princess) + ldx #$a2 ;otherwise, set for mushroom retainer object instead + lda #$03 ;set alternate state here + sta $ec + bne CheckDefeatedState ;unconditional branch + +CheckForSecondFrame: + lda FrameCounter ;load frame counter + and EnemyAnimTimingBMask,y ;mask it (partly residual, one byte not ever used) + bne CheckDefeatedState ;branch if timing is off + +CheckAnimationStop: + lda $ed ;check saved enemy state + and #%10100000 ;for d7 or d5, or check for timers stopped + ora TimerControl + bne CheckDefeatedState ;if either condition true, branch + txa + clc + adc #$06 ;add $06 to current enemy offset + tax ;to animate various enemy objects + +CheckDefeatedState: + lda $ed ;check saved enemy state + and #%00100000 ;for d5 set + beq DrawEnemyObject ;branch if not set + lda $ef + cmp #$04 ;check for saved enemy object => $04 + bcc DrawEnemyObject ;branch if less + ldy #$01 + sty VerticalFlipFlag ;set vertical flip flag + dey + sty $ec ;init saved value here + +DrawEnemyObject: + ldy $eb ;load sprite data offset + jsr DrawEnemyObjRow ;draw six tiles of data + jsr DrawEnemyObjRow ;into sprite data + jsr DrawEnemyObjRow + ldx ObjectOffset ;get enemy object offset + ldy Enemy_SprDataOffset,x ;get sprite data offset + lda $ef + cmp #$08 ;get saved enemy object and check + bne CheckForVerticalFlip ;for bullet bill, branch if not found + +SkipToOffScrChk: + jmp SprObjectOffscrChk ;jump if found + +CheckForVerticalFlip: + lda VerticalFlipFlag ;check if vertical flip flag is set here + beq CheckForESymmetry ;branch if not + lda Sprite_Attributes,y ;get attributes of first sprite we dealt with + ora #%10000000 ;set bit for vertical flip + iny + iny ;increment two bytes so that we store the vertical flip + jsr DumpSixSpr ;in attribute bytes of enemy obj sprite data + dey + dey ;now go back to the Y coordinate offset + tya + tax ;give offset to X + lda $ef + cmp #HammerBro ;check saved enemy object for hammer bro + beq FlipEnemyVertically + cmp #Lakitu ;check saved enemy object for lakitu + beq FlipEnemyVertically ;branch for hammer bro or lakitu + cmp #$15 + bcs FlipEnemyVertically ;also branch if enemy object => $15 + txa + clc + adc #$08 ;if not selected objects or => $15, set + tax ;offset in X for next row + +FlipEnemyVertically: + lda Sprite_Tilenumber,x ;load first or second row tiles + pha ;and save tiles to the stack + lda Sprite_Tilenumber+4,x + pha + lda Sprite_Tilenumber+16,y ;exchange third row tiles + sta Sprite_Tilenumber,x ;with first or second row tiles + lda Sprite_Tilenumber+20,y + sta Sprite_Tilenumber+4,x + pla ;pull first or second row tiles from stack + sta Sprite_Tilenumber+20,y ;and save in third row + pla + sta Sprite_Tilenumber+16,y + +CheckForESymmetry: + lda BowserGfxFlag ;are we drawing bowser at all? + bne SkipToOffScrChk ;branch if so + lda $ef + ldx $ec ;get alternate enemy state + cmp #$05 ;check for hammer bro object + bne ContES + jmp SprObjectOffscrChk ;jump if found +ContES: cmp #Bloober ;check for bloober object + beq MirrorEnemyGfx + cmp #PiranhaPlant ;check for piranha plant object + beq MirrorEnemyGfx + cmp #Podoboo ;check for podoboo object + beq MirrorEnemyGfx ;branch if either of three are found + cmp #Spiny ;check for spiny object + bne ESRtnr ;branch closer if not found + cpx #$05 ;check spiny's state + bne CheckToMirrorLakitu ;branch if not an egg, otherwise +ESRtnr: cmp #$15 ;check for princess/mushroom retainer object + bne SpnySC + lda #$42 ;set horizontal flip on bottom right sprite + sta Sprite_Attributes+20,y ;note that palette bits were already set earlier +SpnySC: cpx #$02 ;if alternate enemy state set to 1 or 0, branch + bcc CheckToMirrorLakitu + +MirrorEnemyGfx: + lda BowserGfxFlag ;if enemy object is bowser, skip all of this + bne CheckToMirrorLakitu + lda Sprite_Attributes,y ;load attribute bits of first sprite + and #%10100011 + sta Sprite_Attributes,y ;save vertical flip, priority, and palette bits + sta Sprite_Attributes+8,y ;in left sprite column of enemy object OAM data + sta Sprite_Attributes+16,y + ora #%01000000 ;set horizontal flip + cpx #$05 ;check for state used by spiny's egg + bne EggExc ;if alternate state not set to $05, branch + ora #%10000000 ;otherwise set vertical flip +EggExc: sta Sprite_Attributes+4,y ;set bits of right sprite column + sta Sprite_Attributes+12,y ;of enemy object sprite data + sta Sprite_Attributes+20,y + cpx #$04 ;check alternate enemy state + bne CheckToMirrorLakitu ;branch if not $04 + lda Sprite_Attributes+8,y ;get second row left sprite attributes + ora #%10000000 + sta Sprite_Attributes+8,y ;store bits with vertical flip in + sta Sprite_Attributes+16,y ;second and third row left sprites + ora #%01000000 + sta Sprite_Attributes+12,y ;store with horizontal and vertical flip in + sta Sprite_Attributes+20,y ;second and third row right sprites + +CheckToMirrorLakitu: + lda $ef ;check for lakitu enemy object + cmp #Lakitu + bne CheckToMirrorJSpring ;branch if not found + lda VerticalFlipFlag + bne NVFLak ;branch if vertical flip flag set + lda Sprite_Attributes+16,y ;save vertical flip and palette bits + and #%10000001 ;in third row left sprite + sta Sprite_Attributes+16,y + lda Sprite_Attributes+20,y ;set horizontal flip and palette bits + ora #%01000001 ;in third row right sprite + sta Sprite_Attributes+20,y + ldx FrenzyEnemyTimer ;check timer + cpx #$10 + bcs SprObjectOffscrChk ;branch if timer has not reached a certain range + sta Sprite_Attributes+12,y ;otherwise set same for second row right sprite + and #%10000001 + sta Sprite_Attributes+8,y ;preserve vertical flip and palette bits for left sprite + bcc SprObjectOffscrChk ;unconditional branch +NVFLak: lda Sprite_Attributes,y ;get first row left sprite attributes + and #%10000001 + sta Sprite_Attributes,y ;save vertical flip and palette bits + lda Sprite_Attributes+4,y ;get first row right sprite attributes + ora #%01000001 ;set horizontal flip and palette bits + sta Sprite_Attributes+4,y ;note that vertical flip is left as-is + +CheckToMirrorJSpring: + lda $ef ;check for jumpspring object (any frame) + cmp #$18 + bcc SprObjectOffscrChk ;branch if not jumpspring object at all + lda #$82 + sta Sprite_Attributes+8,y ;set vertical flip and palette bits of + sta Sprite_Attributes+16,y ;second and third row left sprites + ora #%01000000 + sta Sprite_Attributes+12,y ;set, in addition to those, horizontal flip + sta Sprite_Attributes+20,y ;for second and third row right sprites + +SprObjectOffscrChk: + ldx ObjectOffset ;get enemy buffer offset + lda Enemy_OffscreenBits ;check offscreen information + lsr + lsr ;shift three times to the right + lsr ;which puts d2 into carry + pha ;save to stack + bcc LcChk ;branch if not set + lda #$04 ;set for right column sprites + jsr MoveESprColOffscreen ;and move them offscreen +LcChk: pla ;get from stack + lsr ;move d3 to carry + pha ;save to stack + bcc Row3C ;branch if not set + lda #$00 ;set for left column sprites, + jsr MoveESprColOffscreen ;move them offscreen +Row3C: pla ;get from stack again + lsr ;move d5 to carry this time + lsr + pha ;save to stack again + bcc Row23C ;branch if carry not set + lda #$10 ;set for third row of sprites + jsr MoveESprRowOffscreen ;and move them offscreen +Row23C: pla ;get from stack + lsr ;move d6 into carry + pha ;save to stack + bcc AllRowC + lda #$08 ;set for second and third rows + jsr MoveESprRowOffscreen ;move them offscreen +AllRowC: pla ;get from stack once more + lsr ;move d7 into carry + bcc ExEGHandler + jsr MoveESprRowOffscreen ;move all sprites offscreen (A should be 0 by now) + lda Enemy_ID,x + cmp #Podoboo ;check enemy identifier for podoboo + beq ExEGHandler ;skip this part if found, we do not want to erase podoboo! + lda Enemy_Y_HighPos,x ;check high byte of vertical position + cmp #$02 ;if not yet past the bottom of the screen, branch + bne ExEGHandler + jsr EraseEnemyObject ;what it says + +ExEGHandler: + rts + +DrawEnemyObjRow: + lda EnemyGraphicsTable,x ;load two tiles of enemy graphics + sta $00 + lda EnemyGraphicsTable+1,x + +DrawOneSpriteRow: + sta $01 + jmp DrawSpriteObject ;draw them + +MoveESprRowOffscreen: + clc ;add A to enemy object OAM data offset + adc Enemy_SprDataOffset,x + tay ;use as offset + lda #$f8 + jmp DumpTwoSpr ;move first row of sprites offscreen + +MoveESprColOffscreen: + clc ;add A to enemy object OAM data offset + adc Enemy_SprDataOffset,x + tay ;use as offset + jsr MoveColOffscreen ;move first and second row sprites in column offscreen + sta Sprite_Data+16,y ;move third row sprite in column offscreen + rts + +;------------------------------------------------------------------------------------- +;$00-$01 - tile numbers +;$02 - relative Y position +;$03 - horizontal flip flag (not used here) +;$04 - attributes +;$05 - relative X position + +DefaultBlockObjTiles: + .db $85, $85, $86, $86 ;brick w/ line (these are sprite tiles, not BG!) + +DrawBlock: + lda Block_Rel_YPos ;get relative vertical coordinate of block object + sta $02 ;store here + lda Block_Rel_XPos ;get relative horizontal coordinate of block object + sta $05 ;store here + lda #$03 + sta $04 ;set attribute byte here + lsr + sta $03 ;set horizontal flip bit here (will not be used) + ldy Block_SprDataOffset,x ;get sprite data offset + ldx #$00 ;reset X for use as offset to tile data +DBlkLoop: lda DefaultBlockObjTiles,x ;get left tile number + sta $00 ;set here + lda DefaultBlockObjTiles+1,x ;get right tile number + jsr DrawOneSpriteRow ;do sub to write tile numbers to first row of sprites + cpx #$04 ;check incremented offset + bne DBlkLoop ;and loop back until all four sprites are done + ldx ObjectOffset ;get block object offset + ldy Block_SprDataOffset,x ;get sprite data offset + lda AreaType + cmp #$01 ;check for ground level type area + beq ChkRep ;if found, branch to next part + lda #$86 + sta Sprite_Tilenumber,y ;otherwise remove brick tiles with lines + sta Sprite_Tilenumber+4,y ;and replace then with lineless brick tiles +ChkRep: lda Block_Metatile,x ;check replacement metatile + cmp #$c4 ;if not used block metatile, then + bne BlkOffscr ;branch ahead to use current graphics + lda #$87 ;set A for used block tile + iny ;increment Y to write to tile bytes + jsr DumpFourSpr ;do sub to dump into all four sprites + dey ;return Y to original offset + lda #$03 ;set palette bits + ldx AreaType + dex ;check for ground level type area again + beq SetBFlip ;if found, use current palette bits + lsr ;otherwise set to $01 +SetBFlip: ldx ObjectOffset ;put block object offset back in X + sta Sprite_Attributes,y ;store attribute byte as-is in first sprite + ora #%01000000 + sta Sprite_Attributes+4,y ;set horizontal flip bit for second sprite + ora #%10000000 + sta Sprite_Attributes+12,y ;set both flip bits for fourth sprite + and #%10000011 + sta Sprite_Attributes+8,y ;set vertical flip bit for third sprite +BlkOffscr: lda Block_OffscreenBits ;get offscreen bits for block object + pha ;save to stack + and #%00000100 ;check to see if d2 in offscreen bits are set + beq PullOfsB ;if not set, branch, otherwise move sprites offscreen + lda #$f8 ;move offscreen two OAMs + sta Sprite_Y_Position+4,y ;on the right side + sta Sprite_Y_Position+12,y +PullOfsB: pla ;pull offscreen bits from stack +ChkLeftCo: and #%00001000 ;check to see if d3 in offscreen bits are set + beq ExDBlk ;if not set, branch, otherwise move sprites offscreen + +MoveColOffscreen: + lda #$f8 ;move offscreen two OAMs + sta Sprite_Y_Position,y ;on the left side (or two rows of enemy on either side + sta Sprite_Y_Position+8,y ;if branched here from enemy graphics handler) +ExDBlk: rts + +;------------------------------------------------------------------------------------- +;$00 - used to hold palette bits for attribute byte or relative X position + +DrawBrickChunks: + lda #$02 ;set palette bits here + sta $00 + lda #$75 ;set tile number for ball (something residual, likely) + ldy GameEngineSubroutine + cpy #$05 ;if end-of-level routine running, + beq DChunks ;use palette and tile number assigned + lda #$03 ;otherwise set different palette bits + sta $00 + lda #$84 ;and set tile number for brick chunks +DChunks: ldy Block_SprDataOffset,x ;get OAM data offset + iny ;increment to start with tile bytes in OAM + jsr DumpFourSpr ;do sub to dump tile number into all four sprites + lda FrameCounter ;get frame counter + asl + asl + asl ;move low nybble to high + asl + and #$c0 ;get what was originally d3-d2 of low nybble + ora $00 ;add palette bits + iny ;increment offset for attribute bytes + jsr DumpFourSpr ;do sub to dump attribute data into all four sprites + dey + dey ;decrement offset to Y coordinate + lda Block_Rel_YPos ;get first block object's relative vertical coordinate + jsr DumpTwoSpr ;do sub to dump current Y coordinate into two sprites + lda Block_Rel_XPos ;get first block object's relative horizontal coordinate + sta Sprite_X_Position,y ;save into X coordinate of first sprite + lda Block_Orig_XPos,x ;get original horizontal coordinate + sec + sbc ScreenLeft_X_Pos ;subtract coordinate of left side from original coordinate + sta $00 ;store result as relative horizontal coordinate of original + sec + sbc Block_Rel_XPos ;get difference of relative positions of original - current + adc $00 ;add original relative position to result + adc #$06 ;plus 6 pixels to position second brick chunk correctly + sta Sprite_X_Position+4,y ;save into X coordinate of second sprite + lda Block_Rel_YPos+1 ;get second block object's relative vertical coordinate + sta Sprite_Y_Position+8,y + sta Sprite_Y_Position+12,y ;dump into Y coordinates of third and fourth sprites + lda Block_Rel_XPos+1 ;get second block object's relative horizontal coordinate + sta Sprite_X_Position+8,y ;save into X coordinate of third sprite + lda $00 ;use original relative horizontal position + sec + sbc Block_Rel_XPos+1 ;get difference of relative positions of original - current + adc $00 ;add original relative position to result + adc #$06 ;plus 6 pixels to position fourth brick chunk correctly + sta Sprite_X_Position+12,y ;save into X coordinate of fourth sprite + lda Block_OffscreenBits ;get offscreen bits for block object + jsr ChkLeftCo ;do sub to move left half of sprites offscreen if necessary + lda Block_OffscreenBits ;get offscreen bits again + asl ;shift d7 into carry + bcc ChnkOfs ;if d7 not set, branch to last part + lda #$f8 + jsr DumpTwoSpr ;otherwise move top sprites offscreen +ChnkOfs: lda $00 ;if relative position on left side of screen, + bpl ExBCDr ;go ahead and leave + lda Sprite_X_Position,y ;otherwise compare left-side X coordinate + cmp Sprite_X_Position+4,y ;to right-side X coordinate + bcc ExBCDr ;branch to leave if less + lda #$f8 ;otherwise move right half of sprites offscreen + sta Sprite_Y_Position+4,y + sta Sprite_Y_Position+12,y +ExBCDr: rts ;leave + +;------------------------------------------------------------------------------------- + +DrawFireball: + ldy FBall_SprDataOffset,x ;get fireball's sprite data offset + lda Fireball_Rel_YPos ;get relative vertical coordinate + sta Sprite_Y_Position,y ;store as sprite Y coordinate + lda Fireball_Rel_XPos ;get relative horizontal coordinate + sta Sprite_X_Position,y ;store as sprite X coordinate, then do shared code + +DrawFirebar: + lda FrameCounter ;get frame counter + lsr ;divide by four + lsr + pha ;save result to stack + and #$01 ;mask out all but last bit + eor #$64 ;set either tile $64 or $65 as fireball tile + sta Sprite_Tilenumber,y ;thus tile changes every four frames + pla ;get from stack + lsr ;divide by four again + lsr + lda #$02 ;load value $02 to set palette in attrib byte + bcc FireA ;if last bit shifted out was not set, skip this + ora #%11000000 ;otherwise flip both ways every eight frames +FireA: sta Sprite_Attributes,y ;store attribute byte and leave + rts + +;------------------------------------------------------------------------------------- + +ExplosionTiles: + .db $68, $67, $66 + +DrawExplosion_Fireball: + ldy Alt_SprDataOffset,x ;get OAM data offset of alternate sort for fireball's explosion + lda Fireball_State,x ;load fireball state + inc Fireball_State,x ;increment state for next frame + lsr ;divide by 2 + and #%00000111 ;mask out all but d3-d1 + cmp #$03 ;check to see if time to kill fireball + bcs KillFireBall ;branch if so, otherwise continue to draw explosion + +DrawExplosion_Fireworks: + tax ;use whatever's in A for offset + lda ExplosionTiles,x ;get tile number using offset + iny ;increment Y (contains sprite data offset) + jsr DumpFourSpr ;and dump into tile number part of sprite data + dey ;decrement Y so we have the proper offset again + ldx ObjectOffset ;return enemy object buffer offset to X + lda Fireball_Rel_YPos ;get relative vertical coordinate + sec ;subtract four pixels vertically + sbc #$04 ;for first and third sprites + sta Sprite_Y_Position,y + sta Sprite_Y_Position+8,y + clc ;add eight pixels vertically + adc #$08 ;for second and fourth sprites + sta Sprite_Y_Position+4,y + sta Sprite_Y_Position+12,y + lda Fireball_Rel_XPos ;get relative horizontal coordinate + sec ;subtract four pixels horizontally + sbc #$04 ;for first and second sprites + sta Sprite_X_Position,y + sta Sprite_X_Position+4,y + clc ;add eight pixels horizontally + adc #$08 ;for third and fourth sprites + sta Sprite_X_Position+8,y + sta Sprite_X_Position+12,y + lda #$02 ;set palette attributes for all sprites, but + sta Sprite_Attributes,y ;set no flip at all for first sprite + lda #$82 + sta Sprite_Attributes+4,y ;set vertical flip for second sprite + lda #$42 + sta Sprite_Attributes+8,y ;set horizontal flip for third sprite + lda #$c2 + sta Sprite_Attributes+12,y ;set both flips for fourth sprite + rts ;we are done + +KillFireBall: + lda #$00 ;clear fireball state to kill it + sta Fireball_State,x + rts + +;------------------------------------------------------------------------------------- + +DrawSmallPlatform: + ldy Enemy_SprDataOffset,x ;get OAM data offset + lda #$5b ;load tile number for small platforms + iny ;increment offset for tile numbers + jsr DumpSixSpr ;dump tile number into all six sprites + iny ;increment offset for attributes + lda #$02 ;load palette controls + jsr DumpSixSpr ;dump attributes into all six sprites + dey ;decrement for original offset + dey + lda Enemy_Rel_XPos ;get relative horizontal coordinate + sta Sprite_X_Position,y + sta Sprite_X_Position+12,y ;dump as X coordinate into first and fourth sprites + clc + adc #$08 ;add eight pixels + sta Sprite_X_Position+4,y ;dump into second and fifth sprites + sta Sprite_X_Position+16,y + clc + adc #$08 ;add eight more pixels + sta Sprite_X_Position+8,y ;dump into third and sixth sprites + sta Sprite_X_Position+20,y + lda Enemy_Y_Position,x ;get vertical coordinate + tax + pha ;save to stack + cpx #$20 ;if vertical coordinate below status bar, + bcs TopSP ;do not mess with it + lda #$f8 ;otherwise move first three sprites offscreen +TopSP: jsr DumpThreeSpr ;dump vertical coordinate into Y coordinates + pla ;pull from stack + clc + adc #$80 ;add 128 pixels + tax + cpx #$20 ;if below status bar (taking wrap into account) + bcs BotSP ;then do not change altered coordinate + lda #$f8 ;otherwise move last three sprites offscreen +BotSP: sta Sprite_Y_Position+12,y ;dump vertical coordinate + 128 pixels + sta Sprite_Y_Position+16,y ;into Y coordinates + sta Sprite_Y_Position+20,y + lda Enemy_OffscreenBits ;get offscreen bits + pha ;save to stack + and #%00001000 ;check d3 + beq SOfs + lda #$f8 ;if d3 was set, move first and + sta Sprite_Y_Position,y ;fourth sprites offscreen + sta Sprite_Y_Position+12,y +SOfs: pla ;move out and back into stack + pha + and #%00000100 ;check d2 + beq SOfs2 + lda #$f8 ;if d2 was set, move second and + sta Sprite_Y_Position+4,y ;fifth sprites offscreen + sta Sprite_Y_Position+16,y +SOfs2: pla ;get from stack + and #%00000010 ;check d1 + beq ExSPl + lda #$f8 ;if d1 was set, move third and + sta Sprite_Y_Position+8,y ;sixth sprites offscreen + sta Sprite_Y_Position+20,y +ExSPl: ldx ObjectOffset ;get enemy object offset and leave + rts + +;------------------------------------------------------------------------------------- + +DrawBubble: + ldy Player_Y_HighPos ;if player's vertical high position + dey ;not within screen, skip all of this + bne ExDBub + lda Bubble_OffscreenBits ;check air bubble's offscreen bits + and #%00001000 + bne ExDBub ;if bit set, branch to leave + ldy Bubble_SprDataOffset,x ;get air bubble's OAM data offset + lda Bubble_Rel_XPos ;get relative horizontal coordinate + sta Sprite_X_Position,y ;store as X coordinate here + lda Bubble_Rel_YPos ;get relative vertical coordinate + sta Sprite_Y_Position,y ;store as Y coordinate here + lda #$74 + sta Sprite_Tilenumber,y ;put air bubble tile into OAM data + lda #$02 + sta Sprite_Attributes,y ;set attribute byte +ExDBub: rts ;leave + +;------------------------------------------------------------------------------------- +;$00 - used to store player's vertical offscreen bits + +PlayerGfxTblOffsets: + .db $20, $28, $c8, $18, $00, $40, $50, $58 + .db $80, $88, $b8, $78, $60, $a0, $b0, $b8 + +;tiles arranged in order, 2 tiles per row, top to bottom + +PlayerGraphicsTable: +;big player table + .db $00, $01, $02, $03, $04, $05, $06, $07 ;walking frame 1 + .db $08, $09, $0a, $0b, $0c, $0d, $0e, $0f ; frame 2 + .db $10, $11, $12, $13, $14, $15, $16, $17 ; frame 3 + .db $18, $19, $1a, $1b, $1c, $1d, $1e, $1f ;skidding + .db $20, $21, $22, $23, $24, $25, $26, $27 ;jumping + .db $08, $09, $28, $29, $2a, $2b, $2c, $2d ;swimming frame 1 + .db $08, $09, $0a, $0b, $0c, $30, $2c, $2d ; frame 2 + .db $08, $09, $0a, $0b, $2e, $2f, $2c, $2d ; frame 3 + .db $08, $09, $28, $29, $2a, $2b, $5c, $5d ;climbing frame 1 + .db $08, $09, $0a, $0b, $0c, $0d, $5e, $5f ; frame 2 + .db $fc, $fc, $08, $09, $58, $59, $5a, $5a ;crouching + .db $08, $09, $28, $29, $2a, $2b, $0e, $0f ;fireball throwing + +;small player table + .db $fc, $fc, $fc, $fc, $32, $33, $34, $35 ;walking frame 1 + .db $fc, $fc, $fc, $fc, $36, $37, $38, $39 ; frame 2 + .db $fc, $fc, $fc, $fc, $3a, $37, $3b, $3c ; frame 3 + .db $fc, $fc, $fc, $fc, $3d, $3e, $3f, $40 ;skidding + .db $fc, $fc, $fc, $fc, $32, $41, $42, $43 ;jumping + .db $fc, $fc, $fc, $fc, $32, $33, $44, $45 ;swimming frame 1 + .db $fc, $fc, $fc, $fc, $32, $33, $44, $47 ; frame 2 + .db $fc, $fc, $fc, $fc, $32, $33, $48, $49 ; frame 3 + .db $fc, $fc, $fc, $fc, $32, $33, $90, $91 ;climbing frame 1 + .db $fc, $fc, $fc, $fc, $3a, $37, $92, $93 ; frame 2 + .db $fc, $fc, $fc, $fc, $9e, $9e, $9f, $9f ;killed + +;used by both player sizes + .db $fc, $fc, $fc, $fc, $3a, $37, $4f, $4f ;small player standing + .db $fc, $fc, $00, $01, $4c, $4d, $4e, $4e ;intermediate grow frame + .db $00, $01, $4c, $4d, $4a, $4a, $4b, $4b ;big player standing + +SwimKickTileNum: + .db $31, $46 + +PlayerGfxHandler: + lda InjuryTimer ;if player's injured invincibility timer + beq CntPl ;not set, skip checkpoint and continue code + lda FrameCounter + lsr ;otherwise check frame counter and branch + bcs ExPGH ;to leave on every other frame (when d0 is set) +CntPl: lda GameEngineSubroutine ;if executing specific game engine routine, + cmp #$0b ;branch ahead to some other part + beq PlayerKilled + lda PlayerChangeSizeFlag ;if grow/shrink flag set + bne DoChangeSize ;then branch to some other code + ldy SwimmingFlag ;if swimming flag set, branch to + beq FindPlayerAction ;different part, do not return + lda Player_State + cmp #$00 ;if player status normal, + beq FindPlayerAction ;branch and do not return + jsr FindPlayerAction ;otherwise jump and return + lda FrameCounter + and #%00000100 ;check frame counter for d2 set (8 frames every + bne ExPGH ;eighth frame), and branch if set to leave + tax ;initialize X to zero + ldy Player_SprDataOffset ;get player sprite data offset + lda PlayerFacingDir ;get player's facing direction + lsr + bcs SwimKT ;if player facing to the right, use current offset + iny + iny ;otherwise move to next OAM data + iny + iny +SwimKT: lda PlayerSize ;check player's size + beq BigKTS ;if big, use first tile + lda Sprite_Tilenumber+24,y ;check tile number of seventh/eighth sprite + cmp SwimTileRepOffset ;against tile number in player graphics table + beq ExPGH ;if spr7/spr8 tile number = value, branch to leave + inx ;otherwise increment X for second tile +BigKTS: lda SwimKickTileNum,x ;overwrite tile number in sprite 7/8 + sta Sprite_Tilenumber+24,y ;to animate player's feet when swimming +ExPGH: rts ;then leave + +FindPlayerAction: + jsr ProcessPlayerAction ;find proper offset to graphics table by player's actions + jmp PlayerGfxProcessing ;draw player, then process for fireball throwing + +DoChangeSize: + jsr HandleChangeSize ;find proper offset to graphics table for grow/shrink + jmp PlayerGfxProcessing ;draw player, then process for fireball throwing + +PlayerKilled: + ldy #$0e ;load offset for player killed + lda PlayerGfxTblOffsets,y ;get offset to graphics table + +PlayerGfxProcessing: + sta PlayerGfxOffset ;store offset to graphics table here + lda #$04 + jsr RenderPlayerSub ;draw player based on offset loaded + jsr ChkForPlayerAttrib ;set horizontal flip bits as necessary + lda FireballThrowingTimer + beq PlayerOffscreenChk ;if fireball throw timer not set, skip to the end + ldy #$00 ;set value to initialize by default + lda PlayerAnimTimer ;get animation frame timer + cmp FireballThrowingTimer ;compare to fireball throw timer + sty FireballThrowingTimer ;initialize fireball throw timer + bcs PlayerOffscreenChk ;if animation frame timer => fireball throw timer skip to end + sta FireballThrowingTimer ;otherwise store animation timer into fireball throw timer + ldy #$07 ;load offset for throwing + lda PlayerGfxTblOffsets,y ;get offset to graphics table + sta PlayerGfxOffset ;store it for use later + ldy #$04 ;set to update four sprite rows by default + lda Player_X_Speed + ora Left_Right_Buttons ;check for horizontal speed or left/right button press + beq SUpdR ;if no speed or button press, branch using set value in Y + dey ;otherwise set to update only three sprite rows +SUpdR: tya ;save in A for use + jsr RenderPlayerSub ;in sub, draw player object again + +PlayerOffscreenChk: + lda Player_OffscreenBits ;get player's offscreen bits + lsr + lsr ;move vertical bits to low nybble + lsr + lsr + sta $00 ;store here + ldx #$03 ;check all four rows of player sprites + lda Player_SprDataOffset ;get player's sprite data offset + clc + adc #$18 ;add 24 bytes to start at bottom row + tay ;set as offset here +PROfsLoop: lda #$f8 ;load offscreen Y coordinate just in case + lsr $00 ;shift bit into carry + bcc NPROffscr ;if bit not set, skip, do not move sprites + jsr DumpTwoSpr ;otherwise dump offscreen Y coordinate into sprite data +NPROffscr: tya + sec ;subtract eight bytes to do + sbc #$08 ;next row up + tay + dex ;decrement row counter + bpl PROfsLoop ;do this until all sprite rows are checked + rts ;then we are done! + +;------------------------------------------------------------------------------------- + +IntermediatePlayerData: + .db $58, $01, $00, $60, $ff, $04 + +DrawPlayer_Intermediate: + ldx #$05 ;store data into zero page memory +PIntLoop: lda IntermediatePlayerData,x ;load data to display player as he always + sta $02,x ;appears on world/lives display + dex + bpl PIntLoop ;do this until all data is loaded + ldx #$b8 ;load offset for small standing + ldy #$04 ;load sprite data offset + jsr DrawPlayerLoop ;draw player accordingly + lda Sprite_Attributes+36 ;get empty sprite attributes + ora #%01000000 ;set horizontal flip bit for bottom-right sprite + sta Sprite_Attributes+32 ;store and leave + rts + +;------------------------------------------------------------------------------------- +;$00-$01 - used to hold tile numbers, $00 also used to hold upper extent of animation frames +;$02 - vertical position +;$03 - facing direction, used as horizontal flip control +;$04 - attributes +;$05 - horizontal position +;$07 - number of rows to draw +;these also used in IntermediatePlayerData + +RenderPlayerSub: + sta $07 ;store number of rows of sprites to draw + lda Player_Rel_XPos + sta Player_Pos_ForScroll ;store player's relative horizontal position + sta $05 ;store it here also + lda Player_Rel_YPos + sta $02 ;store player's vertical position + lda PlayerFacingDir + sta $03 ;store player's facing direction + lda Player_SprAttrib + sta $04 ;store player's sprite attributes + ldx PlayerGfxOffset ;load graphics table offset + ldy Player_SprDataOffset ;get player's sprite data offset + +DrawPlayerLoop: + lda PlayerGraphicsTable,x ;load player's left side + sta $00 + lda PlayerGraphicsTable+1,x ;now load right side + jsr DrawOneSpriteRow + dec $07 ;decrement rows of sprites to draw + bne DrawPlayerLoop ;do this until all rows are drawn + rts + +ProcessPlayerAction: + lda Player_State ;get player's state + cmp #$03 + beq ActionClimbing ;if climbing, branch here + cmp #$02 + beq ActionFalling ;if falling, branch here + cmp #$01 + bne ProcOnGroundActs ;if not jumping, branch here + lda SwimmingFlag + bne ActionSwimming ;if swimming flag set, branch elsewhere + ldy #$06 ;load offset for crouching + lda CrouchingFlag ;get crouching flag + bne NonAnimatedActs ;if set, branch to get offset for graphics table + ldy #$00 ;otherwise load offset for jumping + jmp NonAnimatedActs ;go to get offset to graphics table + +ProcOnGroundActs: + ldy #$06 ;load offset for crouching + lda CrouchingFlag ;get crouching flag + bne NonAnimatedActs ;if set, branch to get offset for graphics table + ldy #$02 ;load offset for standing + lda Player_X_Speed ;check player's horizontal speed + ora Left_Right_Buttons ;and left/right controller bits + beq NonAnimatedActs ;if no speed or buttons pressed, use standing offset + lda Player_XSpeedAbsolute ;load walking/running speed + cmp #$09 + bcc ActionWalkRun ;if less than a certain amount, branch, too slow to skid + lda Player_MovingDir ;otherwise check to see if moving direction + and PlayerFacingDir ;and facing direction are the same + bne ActionWalkRun ;if moving direction = facing direction, branch, don't skid + iny ;otherwise increment to skid offset ($03) + +NonAnimatedActs: + jsr GetGfxOffsetAdder ;do a sub here to get offset adder for graphics table + lda #$00 + sta PlayerAnimCtrl ;initialize animation frame control + lda PlayerGfxTblOffsets,y ;load offset to graphics table using size as offset + rts + +ActionFalling: + ldy #$04 ;load offset for walking/running + jsr GetGfxOffsetAdder ;get offset to graphics table + jmp GetCurrentAnimOffset ;execute instructions for falling state + +ActionWalkRun: + ldy #$04 ;load offset for walking/running + jsr GetGfxOffsetAdder ;get offset to graphics table + jmp FourFrameExtent ;execute instructions for normal state + +ActionClimbing: + ldy #$05 ;load offset for climbing + lda Player_Y_Speed ;check player's vertical speed + beq NonAnimatedActs ;if no speed, branch, use offset as-is + jsr GetGfxOffsetAdder ;otherwise get offset for graphics table + jmp ThreeFrameExtent ;then skip ahead to more code + +ActionSwimming: + ldy #$01 ;load offset for swimming + jsr GetGfxOffsetAdder + lda JumpSwimTimer ;check jump/swim timer + ora PlayerAnimCtrl ;and animation frame control + bne FourFrameExtent ;if any one of these set, branch ahead + lda A_B_Buttons + asl ;check for A button pressed + bcs FourFrameExtent ;branch to same place if A button pressed + +GetCurrentAnimOffset: + lda PlayerAnimCtrl ;get animation frame control + jmp GetOffsetFromAnimCtrl ;jump to get proper offset to graphics table + +FourFrameExtent: + lda #$03 ;load upper extent for frame control + jmp AnimationControl ;jump to get offset and animate player object + +ThreeFrameExtent: + lda #$02 ;load upper extent for frame control for climbing + +AnimationControl: + sta $00 ;store upper extent here + jsr GetCurrentAnimOffset ;get proper offset to graphics table + pha ;save offset to stack + lda PlayerAnimTimer ;load animation frame timer + bne ExAnimC ;branch if not expired + lda PlayerAnimTimerSet ;get animation frame timer amount + sta PlayerAnimTimer ;and set timer accordingly + lda PlayerAnimCtrl + clc ;add one to animation frame control + adc #$01 + cmp $00 ;compare to upper extent + bcc SetAnimC ;if frame control + 1 < upper extent, use as next + lda #$00 ;otherwise initialize frame control +SetAnimC: sta PlayerAnimCtrl ;store as new animation frame control +ExAnimC: pla ;get offset to graphics table from stack and leave + rts + +GetGfxOffsetAdder: + lda PlayerSize ;get player's size + beq SzOfs ;if player big, use current offset as-is + tya ;for big player + clc ;otherwise add eight bytes to offset + adc #$08 ;for small player + tay +SzOfs: rts ;go back + +ChangeSizeOffsetAdder: + .db $00, $01, $00, $01, $00, $01, $02, $00, $01, $02 + .db $02, $00, $02, $00, $02, $00, $02, $00, $02, $00 + +HandleChangeSize: + ldy PlayerAnimCtrl ;get animation frame control + lda FrameCounter + and #%00000011 ;get frame counter and execute this code every + bne GorSLog ;fourth frame, otherwise branch ahead + iny ;increment frame control + cpy #$0a ;check for preset upper extent + bcc CSzNext ;if not there yet, skip ahead to use + ldy #$00 ;otherwise initialize both grow/shrink flag + sty PlayerChangeSizeFlag ;and animation frame control +CSzNext: sty PlayerAnimCtrl ;store proper frame control +GorSLog: lda PlayerSize ;get player's size + bne ShrinkPlayer ;if player small, skip ahead to next part + lda ChangeSizeOffsetAdder,y ;get offset adder based on frame control as offset + ldy #$0f ;load offset for player growing + +GetOffsetFromAnimCtrl: + asl ;multiply animation frame control + asl ;by eight to get proper amount + asl ;to add to our offset + adc PlayerGfxTblOffsets,y ;add to offset to graphics table + rts ;and return with result in A + +ShrinkPlayer: + tya ;add ten bytes to frame control as offset + clc + adc #$0a ;this thing apparently uses two of the swimming frames + tax ;to draw the player shrinking + ldy #$09 ;load offset for small player swimming + lda ChangeSizeOffsetAdder,x ;get what would normally be offset adder + bne ShrPlF ;and branch to use offset if nonzero + ldy #$01 ;otherwise load offset for big player swimming +ShrPlF: lda PlayerGfxTblOffsets,y ;get offset to graphics table based on offset loaded + rts ;and leave + +ChkForPlayerAttrib: + ldy Player_SprDataOffset ;get sprite data offset + lda GameEngineSubroutine + cmp #$0b ;if executing specific game engine routine, + beq KilledAtt ;branch to change third and fourth row OAM attributes + lda PlayerGfxOffset ;get graphics table offset + cmp #$50 + beq C_S_IGAtt ;if crouch offset, either standing offset, + cmp #$b8 ;or intermediate growing offset, + beq C_S_IGAtt ;go ahead and execute code to change + cmp #$c0 ;fourth row OAM attributes only + beq C_S_IGAtt + cmp #$c8 + bne ExPlyrAt ;if none of these, branch to leave +KilledAtt: lda Sprite_Attributes+16,y + and #%00111111 ;mask out horizontal and vertical flip bits + sta Sprite_Attributes+16,y ;for third row sprites and save + lda Sprite_Attributes+20,y + and #%00111111 + ora #%01000000 ;set horizontal flip bit for second + sta Sprite_Attributes+20,y ;sprite in the third row +C_S_IGAtt: lda Sprite_Attributes+24,y + and #%00111111 ;mask out horizontal and vertical flip bits + sta Sprite_Attributes+24,y ;for fourth row sprites and save + lda Sprite_Attributes+28,y + and #%00111111 + ora #%01000000 ;set horizontal flip bit for second + sta Sprite_Attributes+28,y ;sprite in the fourth row +ExPlyrAt: rts ;leave + +;------------------------------------------------------------------------------------- +;$00 - used in adding to get proper offset + +RelativePlayerPosition: + ldx #$00 ;set offsets for relative cooordinates + ldy #$00 ;routine to correspond to player object + jmp RelWOfs ;get the coordinates + +RelativeBubblePosition: + ldy #$01 ;set for air bubble offsets + jsr GetProperObjOffset ;modify X to get proper air bubble offset + ldy #$03 + jmp RelWOfs ;get the coordinates + +RelativeFireballPosition: + ldy #$00 ;set for fireball offsets + jsr GetProperObjOffset ;modify X to get proper fireball offset + ldy #$02 +RelWOfs: jsr GetObjRelativePosition ;get the coordinates + ldx ObjectOffset ;return original offset + rts ;leave + +RelativeMiscPosition: + ldy #$02 ;set for misc object offsets + jsr GetProperObjOffset ;modify X to get proper misc object offset + ldy #$06 + jmp RelWOfs ;get the coordinates + +RelativeEnemyPosition: + lda #$01 ;get coordinates of enemy object + ldy #$01 ;relative to the screen + jmp VariableObjOfsRelPos + +RelativeBlockPosition: + lda #$09 ;get coordinates of one block object + ldy #$04 ;relative to the screen + jsr VariableObjOfsRelPos + inx ;adjust offset for other block object if any + inx + lda #$09 + iny ;adjust other and get coordinates for other one + +VariableObjOfsRelPos: + stx $00 ;store value to add to A here + clc + adc $00 ;add A to value stored + tax ;use as enemy offset + jsr GetObjRelativePosition + ldx ObjectOffset ;reload old object offset and leave + rts + +GetObjRelativePosition: + lda SprObject_Y_Position,x ;load vertical coordinate low + sta SprObject_Rel_YPos,y ;store here + lda SprObject_X_Position,x ;load horizontal coordinate + sec ;subtract left edge coordinate + sbc ScreenLeft_X_Pos + sta SprObject_Rel_XPos,y ;store result here + rts + +;------------------------------------------------------------------------------------- +;$00 - used as temp variable to hold offscreen bits + +GetPlayerOffscreenBits: + ldx #$00 ;set offsets for player-specific variables + ldy #$00 ;and get offscreen information about player + jmp GetOffScreenBitsSet + +GetFireballOffscreenBits: + ldy #$00 ;set for fireball offsets + jsr GetProperObjOffset ;modify X to get proper fireball offset + ldy #$02 ;set other offset for fireball's offscreen bits + jmp GetOffScreenBitsSet ;and get offscreen information about fireball + +GetBubbleOffscreenBits: + ldy #$01 ;set for air bubble offsets + jsr GetProperObjOffset ;modify X to get proper air bubble offset + ldy #$03 ;set other offset for airbubble's offscreen bits + jmp GetOffScreenBitsSet ;and get offscreen information about air bubble + +GetMiscOffscreenBits: + ldy #$02 ;set for misc object offsets + jsr GetProperObjOffset ;modify X to get proper misc object offset + ldy #$06 ;set other offset for misc object's offscreen bits + jmp GetOffScreenBitsSet ;and get offscreen information about misc object + +ObjOffsetData: + .db $07, $16, $0d + +GetProperObjOffset: + txa ;move offset to A + clc + adc ObjOffsetData,y ;add amount of bytes to offset depending on setting in Y + tax ;put back in X and leave + rts + +GetEnemyOffscreenBits: + lda #$01 ;set A to add 1 byte in order to get enemy offset + ldy #$01 ;set Y to put offscreen bits in Enemy_OffscreenBits + jmp SetOffscrBitsOffset + +GetBlockOffscreenBits: + lda #$09 ;set A to add 9 bytes in order to get block obj offset + ldy #$04 ;set Y to put offscreen bits in Block_OffscreenBits + +SetOffscrBitsOffset: + stx $00 + clc ;add contents of X to A to get + adc $00 ;appropriate offset, then give back to X + tax + +GetOffScreenBitsSet: + tya ;save offscreen bits offset to stack for now + pha + jsr RunOffscrBitsSubs + asl ;move low nybble to high nybble + asl + asl + asl + ora $00 ;mask together with previously saved low nybble + sta $00 ;store both here + pla ;get offscreen bits offset from stack + tay + lda $00 ;get value here and store elsewhere + sta SprObject_OffscrBits,y + ldx ObjectOffset + rts + +RunOffscrBitsSubs: + jsr GetXOffscreenBits ;do subroutine here + lsr ;move high nybble to low + lsr + lsr + lsr + sta $00 ;store here + jmp GetYOffscreenBits + +;-------------------------------- +;(these apply to these three subsections) +;$04 - used to store offset to sprite object data +;$05 - used as adder in DividePDiff +;$06 - used to store constant used to compare to pixel difference in $07 +;$07 - used to store pixel difference between X positions of object and screen edges + +XOffscreenBitsData: + .db $7f, $3f, $1f, $0f, $07, $03, $01, $00 + .db $80, $c0, $e0, $f0, $f8, $fc, $fe, $ff + +DefaultXOnscreenOfs: + .db $07, $0f, $07 + +GetXOffscreenBits: + stx $04 ;save position in buffer to here + ldy #$01 ;start with right side of screen +XOfsLoop: lda ScreenEdge_X_Pos,y ;get pixel coordinate of edge + sec ;get difference between pixel coordinate of edge + sbc SprObject_X_Position,x ;and pixel coordinate of object position + sta $07 ;store here + lda ScreenEdge_PageLoc,y ;get page location of edge + sbc SprObject_PageLoc,x ;subtract page location of object position from it + ldx DefaultXOnscreenOfs,y ;load offset value here + cmp #$00 + bmi XLdBData ;if beyond right edge or in front of left edge, branch + ldx DefaultXOnscreenOfs+1,y ;if not, load alternate offset value here + cmp #$01 + bpl XLdBData ;if one page or more to the left of either edge, branch + lda #$38 ;if no branching, load value here and store + sta $06 + lda #$08 ;load some other value and execute subroutine + jsr DividePDiff +XLdBData: lda XOffscreenBitsData,x ;get bits here + ldx $04 ;reobtain position in buffer + cmp #$00 ;if bits not zero, branch to leave + bne ExXOfsBS + dey ;otherwise, do left side of screen now + bpl XOfsLoop ;branch if not already done with left side +ExXOfsBS: rts + +;-------------------------------- + +YOffscreenBitsData: + .db $00, $08, $0c, $0e + .db $0f, $07, $03, $01 + .db $00 + +DefaultYOnscreenOfs: + .db $04, $00, $04 + +HighPosUnitData: + .db $ff, $00 + +GetYOffscreenBits: + stx $04 ;save position in buffer to here + ldy #$01 ;start with top of screen +YOfsLoop: lda HighPosUnitData,y ;load coordinate for edge of vertical unit + sec + sbc SprObject_Y_Position,x ;subtract from vertical coordinate of object + sta $07 ;store here + lda #$01 ;subtract one from vertical high byte of object + sbc SprObject_Y_HighPos,x + ldx DefaultYOnscreenOfs,y ;load offset value here + cmp #$00 + bmi YLdBData ;if under top of the screen or beyond bottom, branch + ldx DefaultYOnscreenOfs+1,y ;if not, load alternate offset value here + cmp #$01 + bpl YLdBData ;if one vertical unit or more above the screen, branch + lda #$20 ;if no branching, load value here and store + sta $06 + lda #$04 ;load some other value and execute subroutine + jsr DividePDiff +YLdBData: lda YOffscreenBitsData,x ;get offscreen data bits using offset + ldx $04 ;reobtain position in buffer + cmp #$00 + bne ExYOfsBS ;if bits not zero, branch to leave + dey ;otherwise, do bottom of the screen now + bpl YOfsLoop +ExYOfsBS: rts + +;-------------------------------- + +DividePDiff: + sta $05 ;store current value in A here + lda $07 ;get pixel difference + cmp $06 ;compare to preset value + bcs ExDivPD ;if pixel difference >= preset value, branch + lsr ;divide by eight to get tile difference + lsr + lsr + and #$07 ;mask out all but 3 LSB + cpy #$01 ;right side of the screen or top? + bcs SetOscrO ;if so, branch, use difference / 8 as offset + adc $05 ;if not, add value to difference / 8 +SetOscrO: tax ;use as offset +ExDivPD: rts ;leave + +;------------------------------------------------------------------------------------- +;$00-$01 - tile numbers +;$02 - Y coordinate +;$03 - flip control +;$04 - sprite attributes +;$05 - X coordinate + +DrawSpriteObject: + lda $03 ;get saved flip control bits + lsr + lsr ;move d1 into carry + lda $00 + bcc NoHFlip ;if d1 not set, branch + sta Sprite_Tilenumber+4,y ;store first tile into second sprite + lda $01 ;and second into first sprite + sta Sprite_Tilenumber,y + lda #$40 ;activate horizontal flip OAM attribute + bne SetHFAt ;and unconditionally branch +NoHFlip: sta Sprite_Tilenumber,y ;store first tile into first sprite + lda $01 ;and second into second sprite + sta Sprite_Tilenumber+4,y + lda #$00 ;clear bit for horizontal flip +SetHFAt: ora $04 ;add other OAM attributes if necessary + sta Sprite_Attributes,y ;store sprite attributes + sta Sprite_Attributes+4,y + lda $02 ;now the y coordinates + sta Sprite_Y_Position,y ;note because they are + sta Sprite_Y_Position+4,y ;side by side, they are the same + lda $05 + sta Sprite_X_Position,y ;store x coordinate, then + clc ;add 8 pixels and store another to + adc #$08 ;put them side by side + sta Sprite_X_Position+4,y + lda $02 ;add eight pixels to the next y + clc ;coordinate + adc #$08 + sta $02 + tya ;add eight to the offset in Y to + clc ;move to the next two sprites + adc #$08 + tay + inx ;increment offset to return it to the + inx ;routine that called this subroutine + rts + +;------------------------------------------------------------------------------------- + +;unused space + .db $ff, $ff, $ff, $ff, $ff, $ff + +;------------------------------------------------------------------------------------- + +SoundEngine: + lda OperMode ;are we in title screen mode? + bne SndOn + sta SND_MASTERCTRL_REG ;if so, disable sound and leave + rts +SndOn: lda #$ff + sta JOYPAD_PORT2 ;disable irqs and set frame counter mode??? + lda #$0f + sta SND_MASTERCTRL_REG ;enable first four channels + lda PauseModeFlag ;is sound already in pause mode? + bne InPause + lda PauseSoundQueue ;if not, check pause sfx queue + cmp #$01 + bne RunSoundSubroutines ;if queue is empty, skip pause mode routine +InPause: lda PauseSoundBuffer ;check pause sfx buffer + bne ContPau + lda PauseSoundQueue ;check pause queue + beq SkipSoundSubroutines + sta PauseSoundBuffer ;if queue full, store in buffer and activate + sta PauseModeFlag ;pause mode to interrupt game sounds + lda #$00 ;disable sound and clear sfx buffers + sta SND_MASTERCTRL_REG + sta Square1SoundBuffer + sta Square2SoundBuffer + sta NoiseSoundBuffer + lda #$0f + sta SND_MASTERCTRL_REG ;enable sound again + lda #$2a ;store length of sound in pause counter + sta Squ1_SfxLenCounter +PTone1F: lda #$44 ;play first tone + bne PTRegC ;unconditional branch +ContPau: lda Squ1_SfxLenCounter ;check pause length left + cmp #$24 ;time to play second? + beq PTone2F + cmp #$1e ;time to play first again? + beq PTone1F + cmp #$18 ;time to play second again? + bne DecPauC ;only load regs during times, otherwise skip +PTone2F: lda #$64 ;store reg contents and play the pause sfx +PTRegC: ldx #$84 + ldy #$7f + jsr PlaySqu1Sfx +DecPauC: dec Squ1_SfxLenCounter ;decrement pause sfx counter + bne SkipSoundSubroutines + lda #$00 ;disable sound if in pause mode and + sta SND_MASTERCTRL_REG ;not currently playing the pause sfx + lda PauseSoundBuffer ;if no longer playing pause sfx, check to see + cmp #$02 ;if we need to be playing sound again + bne SkipPIn + lda #$00 ;clear pause mode to allow game sounds again + sta PauseModeFlag +SkipPIn: lda #$00 ;clear pause sfx buffer + sta PauseSoundBuffer + beq SkipSoundSubroutines + +RunSoundSubroutines: + jsr Square1SfxHandler ;play sfx on square channel 1 + jsr Square2SfxHandler ; '' '' '' square channel 2 + jsr NoiseSfxHandler ; '' '' '' noise channel + jsr MusicHandler ;play music on all channels + lda #$00 ;clear the music queues + sta AreaMusicQueue + sta EventMusicQueue + +SkipSoundSubroutines: + lda #$00 ;clear the sound effects queues + sta Square1SoundQueue + sta Square2SoundQueue + sta NoiseSoundQueue + sta PauseSoundQueue + ldy DAC_Counter ;load some sort of counter + lda AreaMusicBuffer + and #%00000011 ;check for specific music + beq NoIncDAC + inc DAC_Counter ;increment and check counter + cpy #$30 + bcc StrWave ;if not there yet, just store it +NoIncDAC: tya + beq StrWave ;if we are at zero, do not decrement + dec DAC_Counter ;decrement counter +StrWave: sty SND_DELTA_REG+1 ;store into DMC load register (??) + rts ;we are done here + +;-------------------------------- + +Dump_Squ1_Regs: + sty SND_SQUARE1_REG+1 ;dump the contents of X and Y into square 1's control regs + stx SND_SQUARE1_REG + rts + +PlaySqu1Sfx: + jsr Dump_Squ1_Regs ;do sub to set ctrl regs for square 1, then set frequency regs + +SetFreq_Squ1: + ldx #$00 ;set frequency reg offset for square 1 sound channel + +Dump_Freq_Regs: + tay + lda FreqRegLookupTbl+1,y ;use previous contents of A for sound reg offset + beq NoTone ;if zero, then do not load + sta SND_REGISTER+2,x ;first byte goes into LSB of frequency divider + lda FreqRegLookupTbl,y ;second byte goes into 3 MSB plus extra bit for + ora #%00001000 ;length counter + sta SND_REGISTER+3,x +NoTone: rts + +Dump_Sq2_Regs: + stx SND_SQUARE2_REG ;dump the contents of X and Y into square 2's control regs + sty SND_SQUARE2_REG+1 + rts + +PlaySqu2Sfx: + jsr Dump_Sq2_Regs ;do sub to set ctrl regs for square 2, then set frequency regs + +SetFreq_Squ2: + ldx #$04 ;set frequency reg offset for square 2 sound channel + bne Dump_Freq_Regs ;unconditional branch + +SetFreq_Tri: + ldx #$08 ;set frequency reg offset for triangle sound channel + bne Dump_Freq_Regs ;unconditional branch + +;-------------------------------- + +SwimStompEnvelopeData: + .db $9f, $9b, $98, $96, $95, $94, $92, $90 + .db $90, $9a, $97, $95, $93, $92 + +PlayFlagpoleSlide: + lda #$40 ;store length of flagpole sound + sta Squ1_SfxLenCounter + lda #$62 ;load part of reg contents for flagpole sound + jsr SetFreq_Squ1 + ldx #$99 ;now load the rest + bne FPS2nd + +PlaySmallJump: + lda #$26 ;branch here for small mario jumping sound + bne JumpRegContents + +PlayBigJump: + lda #$18 ;branch here for big mario jumping sound + +JumpRegContents: + ldx #$82 ;note that small and big jump borrow each others' reg contents + ldy #$a7 ;anyway, this loads the first part of mario's jumping sound + jsr PlaySqu1Sfx + lda #$28 ;store length of sfx for both jumping sounds + sta Squ1_SfxLenCounter ;then continue on here + +ContinueSndJump: + lda Squ1_SfxLenCounter ;jumping sounds seem to be composed of three parts + cmp #$25 ;check for time to play second part yet + bne N2Prt + ldx #$5f ;load second part + ldy #$f6 + bne DmpJpFPS ;unconditional branch +N2Prt: cmp #$20 ;check for third part + bne DecJpFPS + ldx #$48 ;load third part +FPS2nd: ldy #$bc ;the flagpole slide sound shares part of third part +DmpJpFPS: jsr Dump_Squ1_Regs + bne DecJpFPS ;unconditional branch outta here + +PlayFireballThrow: + lda #$05 + ldy #$99 ;load reg contents for fireball throw sound + bne Fthrow ;unconditional branch + +PlayBump: + lda #$0a ;load length of sfx and reg contents for bump sound + ldy #$93 +Fthrow: ldx #$9e ;the fireball sound shares reg contents with the bump sound + sta Squ1_SfxLenCounter + lda #$0c ;load offset for bump sound + jsr PlaySqu1Sfx + +ContinueBumpThrow: + lda Squ1_SfxLenCounter ;check for second part of bump sound + cmp #$06 + bne DecJpFPS + lda #$bb ;load second part directly + sta SND_SQUARE1_REG+1 +DecJpFPS: bne BranchToDecLength1 ;unconditional branch + + +Square1SfxHandler: + ldy Square1SoundQueue ;check for sfx in queue + beq CheckSfx1Buffer + sty Square1SoundBuffer ;if found, put in buffer + bmi PlaySmallJump ;small jump + lsr Square1SoundQueue + bcs PlayBigJump ;big jump + lsr Square1SoundQueue + bcs PlayBump ;bump + lsr Square1SoundQueue + bcs PlaySwimStomp ;swim/stomp + lsr Square1SoundQueue + bcs PlaySmackEnemy ;smack enemy + lsr Square1SoundQueue + bcs PlayPipeDownInj ;pipedown/injury + lsr Square1SoundQueue + bcs PlayFireballThrow ;fireball throw + lsr Square1SoundQueue + bcs PlayFlagpoleSlide ;slide flagpole + +CheckSfx1Buffer: + lda Square1SoundBuffer ;check for sfx in buffer + beq ExS1H ;if not found, exit sub + bmi ContinueSndJump ;small mario jump + lsr + bcs ContinueSndJump ;big mario jump + lsr + bcs ContinueBumpThrow ;bump + lsr + bcs ContinueSwimStomp ;swim/stomp + lsr + bcs ContinueSmackEnemy ;smack enemy + lsr + bcs ContinuePipeDownInj ;pipedown/injury + lsr + bcs ContinueBumpThrow ;fireball throw + lsr + bcs DecrementSfx1Length ;slide flagpole +ExS1H: rts + +PlaySwimStomp: + lda #$0e ;store length of swim/stomp sound + sta Squ1_SfxLenCounter + ldy #$9c ;store reg contents for swim/stomp sound + ldx #$9e + lda #$26 + jsr PlaySqu1Sfx + +ContinueSwimStomp: + ldy Squ1_SfxLenCounter ;look up reg contents in data section based on + lda SwimStompEnvelopeData-1,y ;length of sound left, used to control sound's + sta SND_SQUARE1_REG ;envelope + cpy #$06 + bne BranchToDecLength1 + lda #$9e ;when the length counts down to a certain point, put this + sta SND_SQUARE1_REG+2 ;directly into the LSB of square 1's frequency divider + +BranchToDecLength1: + bne DecrementSfx1Length ;unconditional branch (regardless of how we got here) + +PlaySmackEnemy: + lda #$0e ;store length of smack enemy sound + ldy #$cb + ldx #$9f + sta Squ1_SfxLenCounter + lda #$28 ;store reg contents for smack enemy sound + jsr PlaySqu1Sfx + bne DecrementSfx1Length ;unconditional branch + +ContinueSmackEnemy: + ldy Squ1_SfxLenCounter ;check about halfway through + cpy #$08 + bne SmSpc + lda #$a0 ;if we're at the about-halfway point, make the second tone + sta SND_SQUARE1_REG+2 ;in the smack enemy sound + lda #$9f + bne SmTick +SmSpc: lda #$90 ;this creates spaces in the sound, giving it its distinct noise +SmTick: sta SND_SQUARE1_REG + +DecrementSfx1Length: + dec Squ1_SfxLenCounter ;decrement length of sfx + bne ExSfx1 + +StopSquare1Sfx: + ldx #$00 ;if end of sfx reached, clear buffer + stx $f1 ;and stop making the sfx + ldx #$0e + stx SND_MASTERCTRL_REG + ldx #$0f + stx SND_MASTERCTRL_REG +ExSfx1: rts + +PlayPipeDownInj: + lda #$2f ;load length of pipedown sound + sta Squ1_SfxLenCounter + +ContinuePipeDownInj: + lda Squ1_SfxLenCounter ;some bitwise logic, forces the regs + lsr ;to be written to only during six specific times + bcs NoPDwnL ;during which d3 must be set and d1-0 must be clear + lsr + bcs NoPDwnL + and #%00000010 + beq NoPDwnL + ldy #$91 ;and this is where it actually gets written in + ldx #$9a + lda #$44 + jsr PlaySqu1Sfx +NoPDwnL: jmp DecrementSfx1Length + +;-------------------------------- + +ExtraLifeFreqData: + .db $58, $02, $54, $56, $4e, $44 + +PowerUpGrabFreqData: + .db $4c, $52, $4c, $48, $3e, $36, $3e, $36, $30 + .db $28, $4a, $50, $4a, $64, $3c, $32, $3c, $32 + .db $2c, $24, $3a, $64, $3a, $34, $2c, $22, $2c + +;residual frequency data + .db $22, $1c, $14 + +PUp_VGrow_FreqData: + .db $14, $04, $22, $24, $16, $04, $24, $26 ;used by both + .db $18, $04, $26, $28, $1a, $04, $28, $2a + .db $1c, $04, $2a, $2c, $1e, $04, $2c, $2e ;used by vinegrow + .db $20, $04, $2e, $30, $22, $04, $30, $32 + +PlayCoinGrab: + lda #$35 ;load length of coin grab sound + ldx #$8d ;and part of reg contents + bne CGrab_TTickRegL + +PlayTimerTick: + lda #$06 ;load length of timer tick sound + ldx #$98 ;and part of reg contents + +CGrab_TTickRegL: + sta Squ2_SfxLenCounter + ldy #$7f ;load the rest of reg contents + lda #$42 ;of coin grab and timer tick sound + jsr PlaySqu2Sfx + +ContinueCGrabTTick: + lda Squ2_SfxLenCounter ;check for time to play second tone yet + cmp #$30 ;timer tick sound also executes this, not sure why + bne N2Tone + lda #$54 ;if so, load the tone directly into the reg + sta SND_SQUARE2_REG+2 +N2Tone: bne DecrementSfx2Length + +PlayBlast: + lda #$20 ;load length of fireworks/gunfire sound + sta Squ2_SfxLenCounter + ldy #$94 ;load reg contents of fireworks/gunfire sound + lda #$5e + bne SBlasJ + +ContinueBlast: + lda Squ2_SfxLenCounter ;check for time to play second part + cmp #$18 + bne DecrementSfx2Length + ldy #$93 ;load second part reg contents then + lda #$18 +SBlasJ: bne BlstSJp ;unconditional branch to load rest of reg contents + +PlayPowerUpGrab: + lda #$36 ;load length of power-up grab sound + sta Squ2_SfxLenCounter + +ContinuePowerUpGrab: + lda Squ2_SfxLenCounter ;load frequency reg based on length left over + lsr ;divide by 2 + bcs DecrementSfx2Length ;alter frequency every other frame + tay + lda PowerUpGrabFreqData-1,y ;use length left over / 2 for frequency offset + ldx #$5d ;store reg contents of power-up grab sound + ldy #$7f + +LoadSqu2Regs: + jsr PlaySqu2Sfx + +DecrementSfx2Length: + dec Squ2_SfxLenCounter ;decrement length of sfx + bne ExSfx2 + +EmptySfx2Buffer: + ldx #$00 ;initialize square 2's sound effects buffer + stx Square2SoundBuffer + +StopSquare2Sfx: + ldx #$0d ;stop playing the sfx + stx SND_MASTERCTRL_REG + ldx #$0f + stx SND_MASTERCTRL_REG +ExSfx2: rts + +Square2SfxHandler: + lda Square2SoundBuffer ;special handling for the 1-up sound to keep it + and #Sfx_ExtraLife ;from being interrupted by other sounds on square 2 + bne ContinueExtraLife + ldy Square2SoundQueue ;check for sfx in queue + beq CheckSfx2Buffer + sty Square2SoundBuffer ;if found, put in buffer and check for the following + bmi PlayBowserFall ;bowser fall + lsr Square2SoundQueue + bcs PlayCoinGrab ;coin grab + lsr Square2SoundQueue + bcs PlayGrowPowerUp ;power-up reveal + lsr Square2SoundQueue + bcs PlayGrowVine ;vine grow + lsr Square2SoundQueue + bcs PlayBlast ;fireworks/gunfire + lsr Square2SoundQueue + bcs PlayTimerTick ;timer tick + lsr Square2SoundQueue + bcs PlayPowerUpGrab ;power-up grab + lsr Square2SoundQueue + bcs PlayExtraLife ;1-up + +CheckSfx2Buffer: + lda Square2SoundBuffer ;check for sfx in buffer + beq ExS2H ;if not found, exit sub + bmi ContinueBowserFall ;bowser fall + lsr + bcs Cont_CGrab_TTick ;coin grab + lsr + bcs ContinueGrowItems ;power-up reveal + lsr + bcs ContinueGrowItems ;vine grow + lsr + bcs ContinueBlast ;fireworks/gunfire + lsr + bcs Cont_CGrab_TTick ;timer tick + lsr + bcs ContinuePowerUpGrab ;power-up grab + lsr + bcs ContinueExtraLife ;1-up +ExS2H: rts + +Cont_CGrab_TTick: + jmp ContinueCGrabTTick + +JumpToDecLength2: + jmp DecrementSfx2Length + +PlayBowserFall: + lda #$38 ;load length of bowser defeat sound + sta Squ2_SfxLenCounter + ldy #$c4 ;load contents of reg for bowser defeat sound + lda #$18 +BlstSJp: bne PBFRegs + +ContinueBowserFall: + lda Squ2_SfxLenCounter ;check for almost near the end + cmp #$08 + bne DecrementSfx2Length + ldy #$a4 ;if so, load the rest of reg contents for bowser defeat sound + lda #$5a +PBFRegs: ldx #$9f ;the fireworks/gunfire sound shares part of reg contents here +EL_LRegs: bne LoadSqu2Regs ;this is an unconditional branch outta here + +PlayExtraLife: + lda #$30 ;load length of 1-up sound + sta Squ2_SfxLenCounter + +ContinueExtraLife: + lda Squ2_SfxLenCounter + ldx #$03 ;load new tones only every eight frames +DivLLoop: lsr + bcs JumpToDecLength2 ;if any bits set here, branch to dec the length + dex + bne DivLLoop ;do this until all bits checked, if none set, continue + tay + lda ExtraLifeFreqData-1,y ;load our reg contents + ldx #$82 + ldy #$7f + bne EL_LRegs ;unconditional branch + +PlayGrowPowerUp: + lda #$10 ;load length of power-up reveal sound + bne GrowItemRegs + +PlayGrowVine: + lda #$20 ;load length of vine grow sound + +GrowItemRegs: + sta Squ2_SfxLenCounter + lda #$7f ;load contents of reg for both sounds directly + sta SND_SQUARE2_REG+1 + lda #$00 ;start secondary counter for both sounds + sta Sfx_SecondaryCounter + +ContinueGrowItems: + inc Sfx_SecondaryCounter ;increment secondary counter for both sounds + lda Sfx_SecondaryCounter ;this sound doesn't decrement the usual counter + lsr ;divide by 2 to get the offset + tay + cpy Squ2_SfxLenCounter ;have we reached the end yet? + beq StopGrowItems ;if so, branch to jump, and stop playing sounds + lda #$9d ;load contents of other reg directly + sta SND_SQUARE2_REG + lda PUp_VGrow_FreqData,y ;use secondary counter / 2 as offset for frequency regs + jsr SetFreq_Squ2 + rts + +StopGrowItems: + jmp EmptySfx2Buffer ;branch to stop playing sounds + +;-------------------------------- + +BrickShatterFreqData: + .db $01, $0e, $0e, $0d, $0b, $06, $0c, $0f + .db $0a, $09, $03, $0d, $08, $0d, $06, $0c + +PlayBrickShatter: + lda #$20 ;load length of brick shatter sound + sta Noise_SfxLenCounter + +ContinueBrickShatter: + lda Noise_SfxLenCounter + lsr ;divide by 2 and check for bit set to use offset + bcc DecrementSfx3Length + tay + ldx BrickShatterFreqData,y ;load reg contents of brick shatter sound + lda BrickShatterEnvData,y + +PlayNoiseSfx: + sta SND_NOISE_REG ;play the sfx + stx SND_NOISE_REG+2 + lda #$18 + sta SND_NOISE_REG+3 + +DecrementSfx3Length: + dec Noise_SfxLenCounter ;decrement length of sfx + bne ExSfx3 + lda #$f0 ;if done, stop playing the sfx + sta SND_NOISE_REG + lda #$00 + sta NoiseSoundBuffer +ExSfx3: rts + +NoiseSfxHandler: + ldy NoiseSoundQueue ;check for sfx in queue + beq CheckNoiseBuffer + sty NoiseSoundBuffer ;if found, put in buffer + lsr NoiseSoundQueue + bcs PlayBrickShatter ;brick shatter + lsr NoiseSoundQueue + bcs PlayBowserFlame ;bowser flame + +CheckNoiseBuffer: + lda NoiseSoundBuffer ;check for sfx in buffer + beq ExNH ;if not found, exit sub + lsr + bcs ContinueBrickShatter ;brick shatter + lsr + bcs ContinueBowserFlame ;bowser flame +ExNH: rts + +PlayBowserFlame: + lda #$40 ;load length of bowser flame sound + sta Noise_SfxLenCounter + +ContinueBowserFlame: + lda Noise_SfxLenCounter + lsr + tay + ldx #$0f ;load reg contents of bowser flame sound + lda BowserFlameEnvData-1,y + bne PlayNoiseSfx ;unconditional branch here + +;-------------------------------- + +ContinueMusic: + jmp HandleSquare2Music ;if we have music, start with square 2 channel + +MusicHandler: + lda EventMusicQueue ;check event music queue + bne LoadEventMusic + lda AreaMusicQueue ;check area music queue + bne LoadAreaMusic + lda EventMusicBuffer ;check both buffers + ora AreaMusicBuffer + bne ContinueMusic + rts ;no music, then leave + +LoadEventMusic: + sta EventMusicBuffer ;copy event music queue contents to buffer + cmp #DeathMusic ;is it death music? + bne NoStopSfx ;if not, jump elsewhere + jsr StopSquare1Sfx ;stop sfx in square 1 and 2 + jsr StopSquare2Sfx ;but clear only square 1's sfx buffer +NoStopSfx: ldx AreaMusicBuffer + stx AreaMusicBuffer_Alt ;save current area music buffer to be re-obtained later + ldy #$00 + sty NoteLengthTblAdder ;default value for additional length byte offset + sty AreaMusicBuffer ;clear area music buffer + cmp #TimeRunningOutMusic ;is it time running out music? + bne FindEventMusicHeader + ldx #$08 ;load offset to be added to length byte of header + stx NoteLengthTblAdder + bne FindEventMusicHeader ;unconditional branch + +LoadAreaMusic: + cmp #$04 ;is it underground music? + bne NoStop1 ;no, do not stop square 1 sfx + jsr StopSquare1Sfx +NoStop1: ldy #$10 ;start counter used only by ground level music +GMLoopB: sty GroundMusicHeaderOfs + +HandleAreaMusicLoopB: + ldy #$00 ;clear event music buffer + sty EventMusicBuffer + sta AreaMusicBuffer ;copy area music queue contents to buffer + cmp #$01 ;is it ground level music? + bne FindAreaMusicHeader + inc GroundMusicHeaderOfs ;increment but only if playing ground level music + ldy GroundMusicHeaderOfs ;is it time to loopback ground level music? + cpy #$32 + bne LoadHeader ;branch ahead with alternate offset + ldy #$11 + bne GMLoopB ;unconditional branch + +FindAreaMusicHeader: + ldy #$08 ;load Y for offset of area music + sty MusicOffset_Square2 ;residual instruction here + +FindEventMusicHeader: + iny ;increment Y pointer based on previously loaded queue contents + lsr ;bit shift and increment until we find a set bit for music + bcc FindEventMusicHeader + +LoadHeader: + lda MusicHeaderOffsetData,y ;load offset for header + tay + lda MusicHeaderData,y ;now load the header + sta NoteLenLookupTblOfs + lda MusicHeaderData+1,y + sta MusicDataLow + lda MusicHeaderData+2,y + sta MusicDataHigh + lda MusicHeaderData+3,y + sta MusicOffset_Triangle + lda MusicHeaderData+4,y + sta MusicOffset_Square1 + lda MusicHeaderData+5,y + sta MusicOffset_Noise + sta NoiseDataLoopbackOfs + lda #$01 ;initialize music note counters + sta Squ2_NoteLenCounter + sta Squ1_NoteLenCounter + sta Tri_NoteLenCounter + sta Noise_BeatLenCounter + lda #$00 ;initialize music data offset for square 2 + sta MusicOffset_Square2 + sta AltRegContentFlag ;initialize alternate control reg data used by square 1 + lda #$0b ;disable triangle channel and reenable it + sta SND_MASTERCTRL_REG + lda #$0f + sta SND_MASTERCTRL_REG + +HandleSquare2Music: + dec Squ2_NoteLenCounter ;decrement square 2 note length + bne MiscSqu2MusicTasks ;is it time for more data? if not, branch to end tasks + ldy MusicOffset_Square2 ;increment square 2 music offset and fetch data + inc MusicOffset_Square2 + lda (MusicData),y + beq EndOfMusicData ;if zero, the data is a null terminator + bpl Squ2NoteHandler ;if non-negative, data is a note + bne Squ2LengthHandler ;otherwise it is length data + +EndOfMusicData: + lda EventMusicBuffer ;check secondary buffer for time running out music + cmp #TimeRunningOutMusic + bne NotTRO + lda AreaMusicBuffer_Alt ;load previously saved contents of primary buffer + bne MusicLoopBack ;and start playing the song again if there is one +NotTRO: and #VictoryMusic ;check for victory music (the only secondary that loops) + bne VictoryMLoopBack + lda AreaMusicBuffer ;check primary buffer for any music except pipe intro + and #%01011111 + bne MusicLoopBack ;if any area music except pipe intro, music loops + lda #$00 ;clear primary and secondary buffers and initialize + sta AreaMusicBuffer ;control regs of square and triangle channels + sta EventMusicBuffer + sta SND_TRIANGLE_REG + lda #$90 + sta SND_SQUARE1_REG + sta SND_SQUARE2_REG + rts + +MusicLoopBack: + jmp HandleAreaMusicLoopB + +VictoryMLoopBack: + jmp LoadEventMusic + +Squ2LengthHandler: + jsr ProcessLengthData ;store length of note + sta Squ2_NoteLenBuffer + ldy MusicOffset_Square2 ;fetch another byte (MUST NOT BE LENGTH BYTE!) + inc MusicOffset_Square2 + lda (MusicData),y + +Squ2NoteHandler: + ldx Square2SoundBuffer ;is there a sound playing on this channel? + bne SkipFqL1 + jsr SetFreq_Squ2 ;no, then play the note + beq Rest ;check to see if note is rest + jsr LoadControlRegs ;if not, load control regs for square 2 +Rest: sta Squ2_EnvelopeDataCtrl ;save contents of A + jsr Dump_Sq2_Regs ;dump X and Y into square 2 control regs +SkipFqL1: lda Squ2_NoteLenBuffer ;save length in square 2 note counter + sta Squ2_NoteLenCounter + +MiscSqu2MusicTasks: + lda Square2SoundBuffer ;is there a sound playing on square 2? + bne HandleSquare1Music + lda EventMusicBuffer ;check for death music or d4 set on secondary buffer + and #%10010001 ;note that regs for death music or d4 are loaded by default + bne HandleSquare1Music + ldy Squ2_EnvelopeDataCtrl ;check for contents saved from LoadControlRegs + beq NoDecEnv1 + dec Squ2_EnvelopeDataCtrl ;decrement unless already zero +NoDecEnv1: jsr LoadEnvelopeData ;do a load of envelope data to replace default + sta SND_SQUARE2_REG ;based on offset set by first load unless playing + ldx #$7f ;death music or d4 set on secondary buffer + stx SND_SQUARE2_REG+1 + +HandleSquare1Music: + ldy MusicOffset_Square1 ;is there a nonzero offset here? + beq HandleTriangleMusic ;if not, skip ahead to the triangle channel + dec Squ1_NoteLenCounter ;decrement square 1 note length + bne MiscSqu1MusicTasks ;is it time for more data? + +FetchSqu1MusicData: + ldy MusicOffset_Square1 ;increment square 1 music offset and fetch data + inc MusicOffset_Square1 + lda (MusicData),y + bne Squ1NoteHandler ;if nonzero, then skip this part + lda #$83 + sta SND_SQUARE1_REG ;store some data into control regs for square 1 + lda #$94 ;and fetch another byte of data, used to give + sta SND_SQUARE1_REG+1 ;death music its unique sound + sta AltRegContentFlag + bne FetchSqu1MusicData ;unconditional branch + +Squ1NoteHandler: + jsr AlternateLengthHandler + sta Squ1_NoteLenCounter ;save contents of A in square 1 note counter + ldy Square1SoundBuffer ;is there a sound playing on square 1? + bne HandleTriangleMusic + txa + and #%00111110 ;change saved data to appropriate note format + jsr SetFreq_Squ1 ;play the note + beq SkipCtrlL + jsr LoadControlRegs +SkipCtrlL: sta Squ1_EnvelopeDataCtrl ;save envelope offset + jsr Dump_Squ1_Regs + +MiscSqu1MusicTasks: + lda Square1SoundBuffer ;is there a sound playing on square 1? + bne HandleTriangleMusic + lda EventMusicBuffer ;check for death music or d4 set on secondary buffer + and #%10010001 + bne DeathMAltReg + ldy Squ1_EnvelopeDataCtrl ;check saved envelope offset + beq NoDecEnv2 + dec Squ1_EnvelopeDataCtrl ;decrement unless already zero +NoDecEnv2: jsr LoadEnvelopeData ;do a load of envelope data + sta SND_SQUARE1_REG ;based on offset set by first load +DeathMAltReg: lda AltRegContentFlag ;check for alternate control reg data + bne DoAltLoad + lda #$7f ;load this value if zero, the alternate value +DoAltLoad: sta SND_SQUARE1_REG+1 ;if nonzero, and let's move on + +HandleTriangleMusic: + lda MusicOffset_Triangle + dec Tri_NoteLenCounter ;decrement triangle note length + bne HandleNoiseMusic ;is it time for more data? + ldy MusicOffset_Triangle ;increment triangle music offset and fetch data + inc MusicOffset_Triangle + lda (MusicData),y + beq LoadTriCtrlReg ;if zero, skip all this and move on to noise + bpl TriNoteHandler ;if non-negative, data is note + jsr ProcessLengthData ;otherwise, it is length data + sta Tri_NoteLenBuffer ;save contents of A + lda #$1f + sta SND_TRIANGLE_REG ;load some default data for triangle control reg + ldy MusicOffset_Triangle ;fetch another byte + inc MusicOffset_Triangle + lda (MusicData),y + beq LoadTriCtrlReg ;check once more for nonzero data + +TriNoteHandler: + jsr SetFreq_Tri + ldx Tri_NoteLenBuffer ;save length in triangle note counter + stx Tri_NoteLenCounter + lda EventMusicBuffer + and #%01101110 ;check for death music or d4 set on secondary buffer + bne NotDOrD4 ;if playing any other secondary, skip primary buffer check + lda AreaMusicBuffer ;check primary buffer for water or castle level music + and #%00001010 + beq HandleNoiseMusic ;if playing any other primary, or death or d4, go on to noise routine +NotDOrD4: txa ;if playing water or castle music or any secondary + cmp #$12 ;besides death music or d4 set, check length of note + bcs LongN + lda EventMusicBuffer ;check for win castle music again if not playing a long note + and #EndOfCastleMusic + beq MediN + lda #$0f ;load value $0f if playing the win castle music and playing a short + bne LoadTriCtrlReg ;note, load value $1f if playing water or castle level music or any +MediN: lda #$1f ;secondary besides death and d4 except win castle or win castle and playing + bne LoadTriCtrlReg ;a short note, and load value $ff if playing a long note on water, castle +LongN: lda #$ff ;or any secondary (including win castle) except death and d4 + +LoadTriCtrlReg: + sta SND_TRIANGLE_REG ;save final contents of A into control reg for triangle + +HandleNoiseMusic: + lda AreaMusicBuffer ;check if playing underground or castle music + and #%11110011 + beq ExitMusicHandler ;if so, skip the noise routine + dec Noise_BeatLenCounter ;decrement noise beat length + bne ExitMusicHandler ;is it time for more data? + +FetchNoiseBeatData: + ldy MusicOffset_Noise ;increment noise beat offset and fetch data + inc MusicOffset_Noise + lda (MusicData),y ;get noise beat data, if nonzero, branch to handle + bne NoiseBeatHandler + lda NoiseDataLoopbackOfs ;if data is zero, reload original noise beat offset + sta MusicOffset_Noise ;and loopback next time around + bne FetchNoiseBeatData ;unconditional branch + +NoiseBeatHandler: + jsr AlternateLengthHandler + sta Noise_BeatLenCounter ;store length in noise beat counter + txa + and #%00111110 ;reload data and erase length bits + beq SilentBeat ;if no beat data, silence + cmp #$30 ;check the beat data and play the appropriate + beq LongBeat ;noise accordingly + cmp #$20 + beq StrongBeat + and #%00010000 + beq SilentBeat + lda #$1c ;short beat data + ldx #$03 + ldy #$18 + bne PlayBeat + +StrongBeat: + lda #$1c ;strong beat data + ldx #$0c + ldy #$18 + bne PlayBeat + +LongBeat: + lda #$1c ;long beat data + ldx #$03 + ldy #$58 + bne PlayBeat + +SilentBeat: + lda #$10 ;silence + +PlayBeat: + sta SND_NOISE_REG ;load beat data into noise regs + stx SND_NOISE_REG+2 + sty SND_NOISE_REG+3 + +ExitMusicHandler: + rts + +AlternateLengthHandler: + tax ;save a copy of original byte into X + ror ;save LSB from original byte into carry + txa ;reload original byte and rotate three times + rol ;turning xx00000x into 00000xxx, with the + rol ;bit in carry as the MSB here + rol + +ProcessLengthData: + and #%00000111 ;clear all but the three LSBs + clc + adc NoteLenLookupTblOfs ;add offset loaded from first header byte + adc NoteLengthTblAdder ;add extra if time running out music + tay + lda MusicLengthLookupTbl,y ;load length + rts + +LoadControlRegs: + lda EventMusicBuffer ;check secondary buffer for win castle music + and #EndOfCastleMusic + beq NotECstlM + lda #$04 ;this value is only used for win castle music + bne AllMus ;unconditional branch +NotECstlM: lda AreaMusicBuffer + and #%01111101 ;check primary buffer for water music + beq WaterMus + lda #$08 ;this is the default value for all other music + bne AllMus +WaterMus: lda #$28 ;this value is used for water music and all other event music +AllMus: ldx #$82 ;load contents of other sound regs for square 2 + ldy #$7f + rts + +LoadEnvelopeData: + lda EventMusicBuffer ;check secondary buffer for win castle music + and #EndOfCastleMusic + beq LoadUsualEnvData + lda EndOfCastleMusicEnvData,y ;load data from offset for win castle music + rts + +LoadUsualEnvData: + lda AreaMusicBuffer ;check primary buffer for water music + and #%01111101 + beq LoadWaterEventMusEnvData + lda AreaMusicEnvData,y ;load default data from offset for all other music + rts + +LoadWaterEventMusEnvData: + lda WaterEventMusEnvData,y ;load data from offset for water music and all other event music + rts + +;-------------------------------- + +;music header offsets + +MusicHeaderData: + .db DeathMusHdr-MHD ;event music + .db GameOverMusHdr-MHD + .db VictoryMusHdr-MHD + .db WinCastleMusHdr-MHD + .db GameOverMusHdr-MHD + .db EndOfLevelMusHdr-MHD + .db TimeRunningOutHdr-MHD + .db SilenceHdr-MHD + + .db GroundLevelPart1Hdr-MHD ;area music + .db WaterMusHdr-MHD + .db UndergroundMusHdr-MHD + .db CastleMusHdr-MHD + .db Star_CloudHdr-MHD + .db GroundLevelLeadInHdr-MHD + .db Star_CloudHdr-MHD + .db SilenceHdr-MHD + + .db GroundLevelLeadInHdr-MHD ;ground level music layout + .db GroundLevelPart1Hdr-MHD, GroundLevelPart1Hdr-MHD + .db GroundLevelPart2AHdr-MHD, GroundLevelPart2BHdr-MHD, GroundLevelPart2AHdr-MHD, GroundLevelPart2CHdr-MHD + .db GroundLevelPart2AHdr-MHD, GroundLevelPart2BHdr-MHD, GroundLevelPart2AHdr-MHD, GroundLevelPart2CHdr-MHD + .db GroundLevelPart3AHdr-MHD, GroundLevelPart3BHdr-MHD, GroundLevelPart3AHdr-MHD, GroundLevelLeadInHdr-MHD + .db GroundLevelPart1Hdr-MHD, GroundLevelPart1Hdr-MHD + .db GroundLevelPart4AHdr-MHD, GroundLevelPart4BHdr-MHD, GroundLevelPart4AHdr-MHD, GroundLevelPart4CHdr-MHD + .db GroundLevelPart4AHdr-MHD, GroundLevelPart4BHdr-MHD, GroundLevelPart4AHdr-MHD, GroundLevelPart4CHdr-MHD + .db GroundLevelPart3AHdr-MHD, GroundLevelPart3BHdr-MHD, GroundLevelPart3AHdr-MHD, GroundLevelLeadInHdr-MHD + .db GroundLevelPart4AHdr-MHD, GroundLevelPart4BHdr-MHD, GroundLevelPart4AHdr-MHD, GroundLevelPart4CHdr-MHD + +;music headers +;header format is as follows: +;1 byte - length byte offset +;2 bytes - music data address +;1 byte - triangle data offset +;1 byte - square 1 data offset +;1 byte - noise data offset (not used by secondary music) + +TimeRunningOutHdr: .db $08, TimeRunOutMusData, $27, $18 +Star_CloudHdr: .db $20, Star_CloudMData, $2e, $1a, $40 +EndOfLevelMusHdr: .db $20, WinLevelMusData, $3d, $21 +ResidualHeaderData: .db $20, $c4, $fc, $3f, $1d +UndergroundMusHdr: .db $18, UndergroundMusData, $00, $00 +SilenceHdr: .db $08, SilenceData, $00 +CastleMusHdr: .db $00, CastleMusData, $93, $62 +VictoryMusHdr: .db $10, VictoryMusData, $24, $14 +GameOverMusHdr: .db $18, GameOverMusData, $1e, $14 +WaterMusHdr: .db $08, WaterMusData, $a0, $70, $68 +WinCastleMusHdr: .db $08, EndOfCastleMusData, $4c, $24 +GroundLevelPart1Hdr: .db $18, GroundM_P1Data, $2d, $1c, $b8 +GroundLevelPart2AHdr: .db $18, GroundM_P2AData, $20, $12, $70 +GroundLevelPart2BHdr: .db $18, GroundM_P2BData, $1b, $10, $44 +GroundLevelPart2CHdr: .db $18, GroundM_P2CData, $11, $0a, $1c +GroundLevelPart3AHdr: .db $18, GroundM_P3AData, $2d, $10, $58 +GroundLevelPart3BHdr: .db $18, GroundM_P3BData, $14, $0d, $3f +GroundLevelLeadInHdr: .db $18, GroundMLdInData, $15, $0d, $21 +GroundLevelPart4AHdr: .db $18, GroundM_P4AData, $18, $10, $7a +GroundLevelPart4BHdr: .db $18, GroundM_P4BData, $19, $0f, $54 +GroundLevelPart4CHdr: .db $18, GroundM_P4CData, $1e, $12, $2b +DeathMusHdr: .db $18, DeathMusData, $1e, $0f, $2d + +;-------------------------------- + +;MUSIC DATA +;square 2/triangle format +;d7 - length byte flag (0-note, 1-length) +;if d7 is set to 0 and d6-d0 is nonzero: +;d6-d0 - note offset in frequency look-up table (must be even) +;if d7 is set to 1: +;d6-d3 - unused +;d2-d0 - length offset in length look-up table +;value of $00 in square 2 data is used as null terminator, affects all sound channels +;value of $00 in triangle data causes routine to skip note + +;square 1 format +;d7-d6, d0 - length offset in length look-up table (bit order is d0,d7,d6) +;d5-d1 - note offset in frequency look-up table +;value of $00 in square 1 data is flag alternate control reg data to be loaded + +;noise format +;d7-d6, d0 - length offset in length look-up table (bit order is d0,d7,d6) +;d5-d4 - beat type (0 - rest, 1 - short, 2 - strong, 3 - long) +;d3-d1 - unused +;value of $00 in noise data is used as null terminator, affects only noise + +;all music data is organized into sections (unless otherwise stated): +;square 2, square 1, triangle, noise + +Star_CloudMData: + .db $84, $2c, $2c, $2c, $82, $04, $2c, $04, $85, $2c, $84, $2c, $2c + .db $2a, $2a, $2a, $82, $04, $2a, $04, $85, $2a, $84, $2a, $2a, $00 + + .db $1f, $1f, $1f, $98, $1f, $1f, $98, $9e, $98, $1f + .db $1d, $1d, $1d, $94, $1d, $1d, $94, $9c, $94, $1d + + .db $86, $18, $85, $26, $30, $84, $04, $26, $30 + .db $86, $14, $85, $22, $2c, $84, $04, $22, $2c + + .db $21, $d0, $c4, $d0, $31, $d0, $c4, $d0, $00 + +GroundM_P1Data: + .db $85, $2c, $22, $1c, $84, $26, $2a, $82, $28, $26, $04 + .db $87, $22, $34, $3a, $82, $40, $04, $36, $84, $3a, $34 + .db $82, $2c, $30, $85, $2a + +SilenceData: + .db $00 + + .db $5d, $55, $4d, $15, $19, $96, $15, $d5, $e3, $eb + .db $2d, $a6, $2b, $27, $9c, $9e, $59 + + .db $85, $22, $1c, $14, $84, $1e, $22, $82, $20, $1e, $04, $87 + .db $1c, $2c, $34, $82, $36, $04, $30, $34, $04, $2c, $04, $26 + .db $2a, $85, $22 + +GroundM_P2AData: + .db $84, $04, $82, $3a, $38, $36, $32, $04, $34 + .db $04, $24, $26, $2c, $04, $26, $2c, $30, $00 + + .db $05, $b4, $b2, $b0, $2b, $ac, $84 + .db $9c, $9e, $a2, $84, $94, $9c, $9e + + .db $85, $14, $22, $84, $2c, $85, $1e + .db $82, $2c, $84, $2c, $1e + +GroundM_P2BData: + .db $84, $04, $82, $3a, $38, $36, $32, $04, $34 + .db $04, $64, $04, $64, $86, $64, $00 + + .db $05, $b4, $b2, $b0, $2b, $ac, $84 + .db $37, $b6, $b6, $45 + + .db $85, $14, $1c, $82, $22, $84, $2c + .db $4e, $82, $4e, $84, $4e, $22 + +GroundM_P2CData: + .db $84, $04, $85, $32, $85, $30, $86, $2c, $04, $00 + + .db $05, $a4, $05, $9e, $05, $9d, $85 + + .db $84, $14, $85, $24, $28, $2c, $82 + .db $22, $84, $22, $14 + + .db $21, $d0, $c4, $d0, $31, $d0, $c4, $d0, $00 + +GroundM_P3AData: + .db $82, $2c, $84, $2c, $2c, $82, $2c, $30 + .db $04, $34, $2c, $04, $26, $86, $22, $00 + + .db $a4, $25, $25, $a4, $29, $a2, $1d, $9c, $95 + +GroundM_P3BData: + .db $82, $2c, $2c, $04, $2c, $04, $2c, $30, $85, $34, $04, $04, $00 + + .db $a4, $25, $25, $a4, $a8, $63, $04 + +;triangle data used by both sections of third part + .db $85, $0e, $1a, $84, $24, $85, $22, $14, $84, $0c + +GroundMLdInData: + .db $82, $34, $84, $34, $34, $82, $2c, $84, $34, $86, $3a, $04, $00 + + .db $a0, $21, $21, $a0, $21, $2b, $05, $a3 + + .db $82, $18, $84, $18, $18, $82, $18, $18, $04, $86, $3a, $22 + +;noise data used by lead-in and third part sections + .db $31, $90, $31, $90, $31, $71, $31, $90, $90, $90, $00 + +GroundM_P4AData: + .db $82, $34, $84, $2c, $85, $22, $84, $24 + .db $82, $26, $36, $04, $36, $86, $26, $00 + + .db $ac, $27, $5d, $1d, $9e, $2d, $ac, $9f + + .db $85, $14, $82, $20, $84, $22, $2c + .db $1e, $1e, $82, $2c, $2c, $1e, $04 + +GroundM_P4BData: + .db $87, $2a, $40, $40, $40, $3a, $36 + .db $82, $34, $2c, $04, $26, $86, $22, $00 + + .db $e3, $f7, $f7, $f7, $f5, $f1, $ac, $27, $9e, $9d + + .db $85, $18, $82, $1e, $84, $22, $2a + .db $22, $22, $82, $2c, $2c, $22, $04 + +DeathMusData: + .db $86, $04 ;death music share data with fourth part c of ground level music + +GroundM_P4CData: + .db $82, $2a, $36, $04, $36, $87, $36, $34, $30, $86, $2c, $04, $00 + + .db $00, $68, $6a, $6c, $45 ;death music only + + .db $a2, $31, $b0, $f1, $ed, $eb, $a2, $1d, $9c, $95 + + .db $86, $04 ;death music only + + .db $85, $22, $82, $22, $87, $22, $26, $2a, $84, $2c, $22, $86, $14 + +;noise data used by fourth part sections + .db $51, $90, $31, $11, $00 + +CastleMusData: + .db $80, $22, $28, $22, $26, $22, $24, $22, $26 + .db $22, $28, $22, $2a, $22, $28, $22, $26 + .db $22, $28, $22, $26, $22, $24, $22, $26 + .db $22, $28, $22, $2a, $22, $28, $22, $26 + .db $20, $26, $20, $24, $20, $26, $20, $28 + .db $20, $26, $20, $28, $20, $26, $20, $24 + .db $20, $26, $20, $24, $20, $26, $20, $28 + .db $20, $26, $20, $28, $20, $26, $20, $24 + .db $28, $30, $28, $32, $28, $30, $28, $2e + .db $28, $30, $28, $2e, $28, $2c, $28, $2e + .db $28, $30, $28, $32, $28, $30, $28, $2e + .db $28, $30, $28, $2e, $28, $2c, $28, $2e, $00 + + .db $04, $70, $6e, $6c, $6e, $70, $72, $70, $6e + .db $70, $6e, $6c, $6e, $70, $72, $70, $6e + .db $6e, $6c, $6e, $70, $6e, $70, $6e, $6c + .db $6e, $6c, $6e, $70, $6e, $70, $6e, $6c + .db $76, $78, $76, $74, $76, $74, $72, $74 + .db $76, $78, $76, $74, $76, $74, $72, $74 + + .db $84, $1a, $83, $18, $20, $84, $1e, $83, $1c, $28 + .db $26, $1c, $1a, $1c + +GameOverMusData: + .db $82, $2c, $04, $04, $22, $04, $04, $84, $1c, $87 + .db $26, $2a, $26, $84, $24, $28, $24, $80, $22, $00 + + .db $9c, $05, $94, $05, $0d, $9f, $1e, $9c, $98, $9d + + .db $82, $22, $04, $04, $1c, $04, $04, $84, $14 + .db $86, $1e, $80, $16, $80, $14 + +TimeRunOutMusData: + .db $81, $1c, $30, $04, $30, $30, $04, $1e, $32, $04, $32, $32 + .db $04, $20, $34, $04, $34, $34, $04, $36, $04, $84, $36, $00 + + .db $46, $a4, $64, $a4, $48, $a6, $66, $a6, $4a, $a8, $68, $a8 + .db $6a, $44, $2b + + .db $81, $2a, $42, $04, $42, $42, $04, $2c, $64, $04, $64, $64 + .db $04, $2e, $46, $04, $46, $46, $04, $22, $04, $84, $22 + +WinLevelMusData: + .db $87, $04, $06, $0c, $14, $1c, $22, $86, $2c, $22 + .db $87, $04, $60, $0e, $14, $1a, $24, $86, $2c, $24 + .db $87, $04, $08, $10, $18, $1e, $28, $86, $30, $30 + .db $80, $64, $00 + + .db $cd, $d5, $dd, $e3, $ed, $f5, $bb, $b5, $cf, $d5 + .db $db, $e5, $ed, $f3, $bd, $b3, $d1, $d9, $df, $e9 + .db $f1, $f7, $bf, $ff, $ff, $ff, $34 + .db $00 ;unused byte + + .db $86, $04, $87, $14, $1c, $22, $86, $34, $84, $2c + .db $04, $04, $04, $87, $14, $1a, $24, $86, $32, $84 + .db $2c, $04, $86, $04, $87, $18, $1e, $28, $86, $36 + .db $87, $30, $30, $30, $80, $2c + +;square 2 and triangle use the same data, square 1 is unused +UndergroundMusData: + .db $82, $14, $2c, $62, $26, $10, $28, $80, $04 + .db $82, $14, $2c, $62, $26, $10, $28, $80, $04 + .db $82, $08, $1e, $5e, $18, $60, $1a, $80, $04 + .db $82, $08, $1e, $5e, $18, $60, $1a, $86, $04 + .db $83, $1a, $18, $16, $84, $14, $1a, $18, $0e, $0c + .db $16, $83, $14, $20, $1e, $1c, $28, $26, $87 + .db $24, $1a, $12, $10, $62, $0e, $80, $04, $04 + .db $00 + +;noise data directly follows square 2 here unlike in other songs +WaterMusData: + .db $82, $18, $1c, $20, $22, $26, $28 + .db $81, $2a, $2a, $2a, $04, $2a, $04, $83, $2a, $82, $22 + .db $86, $34, $32, $34, $81, $04, $22, $26, $2a, $2c, $30 + .db $86, $34, $83, $32, $82, $36, $84, $34, $85, $04, $81, $22 + .db $86, $30, $2e, $30, $81, $04, $22, $26, $2a, $2c, $2e + .db $86, $30, $83, $22, $82, $36, $84, $34, $85, $04, $81, $22 + .db $86, $3a, $3a, $3a, $82, $3a, $81, $40, $82, $04, $81, $3a + .db $86, $36, $36, $36, $82, $36, $81, $3a, $82, $04, $81, $36 + .db $86, $34, $82, $26, $2a, $36 + .db $81, $34, $34, $85, $34, $81, $2a, $86, $2c, $00 + + .db $84, $90, $b0, $84, $50, $50, $b0, $00 + + .db $98, $96, $94, $92, $94, $96, $58, $58, $58, $44 + .db $5c, $44, $9f, $a3, $a1, $a3, $85, $a3, $e0, $a6 + .db $23, $c4, $9f, $9d, $9f, $85, $9f, $d2, $a6, $23 + .db $c4, $b5, $b1, $af, $85, $b1, $af, $ad, $85, $95 + .db $9e, $a2, $aa, $6a, $6a, $6b, $5e, $9d + + .db $84, $04, $04, $82, $22, $86, $22 + .db $82, $14, $22, $2c, $12, $22, $2a, $14, $22, $2c + .db $1c, $22, $2c, $14, $22, $2c, $12, $22, $2a, $14 + .db $22, $2c, $1c, $22, $2c, $18, $22, $2a, $16, $20 + .db $28, $18, $22, $2a, $12, $22, $2a, $18, $22, $2a + .db $12, $22, $2a, $14, $22, $2c, $0c, $22, $2c, $14, $22, $34, $12 + .db $22, $30, $10, $22, $2e, $16, $22, $34, $18, $26 + .db $36, $16, $26, $36, $14, $26, $36, $12, $22, $36 + .db $5c, $22, $34, $0c, $22, $22, $81, $1e, $1e, $85, $1e + .db $81, $12, $86, $14 + +EndOfCastleMusData: + .db $81, $2c, $22, $1c, $2c, $22, $1c, $85, $2c, $04 + .db $81, $2e, $24, $1e, $2e, $24, $1e, $85, $2e, $04 + .db $81, $32, $28, $22, $32, $28, $22, $85, $32 + .db $87, $36, $36, $36, $84, $3a, $00 + + .db $5c, $54, $4c, $5c, $54, $4c + .db $5c, $1c, $1c, $5c, $5c, $5c, $5c + .db $5e, $56, $4e, $5e, $56, $4e + .db $5e, $1e, $1e, $5e, $5e, $5e, $5e + .db $62, $5a, $50, $62, $5a, $50 + .db $62, $22, $22, $62, $e7, $e7, $e7, $2b + + .db $86, $14, $81, $14, $80, $14, $14, $81, $14, $14, $14, $14 + .db $86, $16, $81, $16, $80, $16, $16, $81, $16, $16, $16, $16 + .db $81, $28, $22, $1a, $28, $22, $1a, $28, $80, $28, $28 + .db $81, $28, $87, $2c, $2c, $2c, $84, $30 + +VictoryMusData: + .db $83, $04, $84, $0c, $83, $62, $10, $84, $12 + .db $83, $1c, $22, $1e, $22, $26, $18, $1e, $04, $1c, $00 + + .db $e3, $e1, $e3, $1d, $de, $e0, $23 + .db $ec, $75, $74, $f0, $f4, $f6, $ea, $31, $2d + + .db $83, $12, $14, $04, $18, $1a, $1c, $14 + .db $26, $22, $1e, $1c, $18, $1e, $22, $0c, $14 + +;unused space + .db $ff, $ff, $ff + +FreqRegLookupTbl: + .db $00, $88, $00, $2f, $00, $00 + .db $02, $a6, $02, $80, $02, $5c, $02, $3a + .db $02, $1a, $01, $df, $01, $c4, $01, $ab + .db $01, $93, $01, $7c, $01, $67, $01, $53 + .db $01, $40, $01, $2e, $01, $1d, $01, $0d + .db $00, $fe, $00, $ef, $00, $e2, $00, $d5 + .db $00, $c9, $00, $be, $00, $b3, $00, $a9 + .db $00, $a0, $00, $97, $00, $8e, $00, $86 + .db $00, $77, $00, $7e, $00, $71, $00, $54 + .db $00, $64, $00, $5f, $00, $59, $00, $50 + .db $00, $47, $00, $43, $00, $3b, $00, $35 + .db $00, $2a, $00, $23, $04, $75, $03, $57 + .db $02, $f9, $02, $cf, $01, $fc, $00, $6a + +MusicLengthLookupTbl: + .db $05, $0a, $14, $28, $50, $1e, $3c, $02 + .db $04, $08, $10, $20, $40, $18, $30, $0c + .db $03, $06, $0c, $18, $30, $12, $24, $08 + .db $36, $03, $09, $06, $12, $1b, $24, $0c + .db $24, $02, $06, $04, $0c, $12, $18, $08 + .db $12, $01, $03, $02, $06, $09, $0c, $04 + +EndOfCastleMusicEnvData: + .db $98, $99, $9a, $9b + +AreaMusicEnvData: + .db $90, $94, $94, $95, $95, $96, $97, $98 + +WaterEventMusEnvData: + .db $90, $91, $92, $92, $93, $93, $93, $94 + .db $94, $94, $94, $94, $94, $95, $95, $95 + .db $95, $95, $95, $96, $96, $96, $96, $96 + .db $96, $96, $96, $96, $96, $96, $96, $96 + .db $96, $96, $96, $96, $95, $95, $94, $93 + +BowserFlameEnvData: + .db $15, $16, $16, $17, $17, $18, $19, $19 + .db $1a, $1a, $1c, $1d, $1d, $1e, $1e, $1f + .db $1f, $1f, $1f, $1e, $1d, $1c, $1e, $1f + .db $1f, $1e, $1d, $1c, $1a, $18, $16, $14 + +BrickShatterEnvData: + .db $15, $16, $16, $17, $17, $18, $19, $19 + .db $1a, $1a, $1c, $1d, $1d, $1e, $1e, $1f + +;------------------------------------------------------------------------------------- +;INTERRUPT VECTORS + + .dw NonMaskableInterrupt + .dw Start + .dw $fff0 ;unused