PRGROM
by asmagicianmaks
Game: SMB
Description:
!SRAM = $7007E6 UploadPRG: LDA !carl_initialized BEQ + if !sa1 STZ $3010 else STZ $0D10 endif RTL + REP #$20 LDA $7FC700 ;check for CHR module CMP #$ADDE BNE + LDA $7FC702 CMP #$EFBE BEQ ++ RTL ++ SEP #$20 LDA #$80 ;disable display STA !carl_initialized STA $2100 STZ $420C STZ $4200 TXA ;unload all modules besides this and CHR LSR #3 STA $00 LDA $7FC707 STA $01 LDX #$0C LDA #$00 - CPX $00 BEQ + CPX $01 BEQ + STA.l !SRAM,x + DEX BPL - LDA $7FC706 STA $F2 STA $F5 REP #$30 LDA $7FC704 STA $F0 SEP #$20 LDA #$11 ;sprites and layer 1 main and sub screen STA $212C STA $212D LDA #$04 ;overscan STA $2133 LDA #$23 ;64x64 tilemap at $2000 STA $2107 STZ $2101 ;8x8 and 16x16 sprites, sprites tiles at $0000 LDA #$01 STA $210B ;layer 1 tiles at $1000 STZ $2106 ;no mosaic LDA $F2 STA $8C LDA.b #$7F STA $02 REP #$20 STZ $00 LDA [$F0] PHK PEA .decomp-1 PEA $804C JML $00B8DA|!bank .decomp ;AXY 8 bit LDA #$80 STA $2115 LDX #$00 REP #$30 STZ $2116 --- LDY #$0007 - LDA.l $7F0000,x STA $2118 INX INX DEY BPL - LDY #$0007 -- STZ $2118 DEY BPL -- CPX #$2000 BCC --- LDA #$F000 ;clear OAM RAM LDX #$01FE - STA $0800,x DEX DEX BPL - LDX #$001E ;additional OAM RAM - STZ $0A00,x DEX DEX BPL - LDA.l $0E8000 ;AMK detection CMP #$4140 BNE + LDA.l $0E8002 CMP #$4B4D BNE + SEP #$20 LDA #$FF STA $2142 STA $1E00|!addr BRA .skipspc + LDY #$0002 LDA [$F0],y STA $00 SEP #$30 LDA $F2 STA $02 PHK PEA .spc-1 PEA $804C LDA #$FF STA $2141 JML $0080F7|!bank .spc LDX #$03 - STZ $1DF9|!addr,x STZ $1DFD|!addr,x DEX BPL - .skipspc REP #$30 LDA #$0000 TCD SEP #$30 JML PRGStart ;Addresses that have to be set for NMI ;$xx10 has to be not 0 ;$xx40 ;$xx41 ;$xx42 ;$xx43 ;$xx44 ;$0D9B has to be $02 ;$0D9F ;$0DAE ;$13C6 ;$1DF9 if not using AMK ;$1DFA ;$1DFB ;$1DFC ;$1DFF ;if !AMK ; !1DFA = $ff ; !Sfx_SmallJump = $2B ; !Sfx_BigJump = $2B ;!Silence = $FF ;!StarPowerMusic = $05 ;!PipeIntroMusic = $08 ;!CloudMusic = $06 ;!CastleMusic = $0A ;!UndergroundMusic = $0A ;!WaterMusic = $0A ;!GroundMusic = $0A ;!TimeRunningOutMusic = $80 ;!EndOfLevelMusic = $04 ;!AltGameOverMusic = $02 ;!EndOfCastleMusic = $03 ;!VictoryMusic = $07 ;!GameOverMusic = $02 ;!DeathMusic = $01 ; ;endif ;Based on SMBDIS.ASM - A COMPREHENSIVE SUPER MARIO BROS. DISASSEMBLY by doppelganger (doppelheathen@gmail.com) ;------------------------------------------------------------------------------------- ;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 = $0800 ;originally $0200 !Sprite_Y_Position = $0801 !Sprite_Tilenumber = $0802 !Sprite_Attributes = $0803 !Sprite_X_Position = $0800 !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 = $0200 !VRAM_Buffer1 = $0201 !VRAM_Buffer2_Offset = $0240 !VRAM_Buffer2 = $0241 !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 !PauseSoundQueue = $fa !AreaMusicQueue = $fb !EventMusicQueue = $fc !1DF9 = $ff !1DFC = $fe !1DFA = $fd !DrumrollBuffer = $f3 !AreaMusicBuffer = $f4 !EventMusicBuffer = $07b1 !PauseSoundBuffer = $07b2 ;------------------------------------------------------------------------------------- ;CONSTANTS ;sound effects constants ;$1DF9 !Sfx_Flagpole = $05 !Sfx_PipeDown_Injury = $04 !Sfx_EnemySmack = $03 !Sfx_EnemyStomp = $02 !Sfx_Bump = $01 !Sfx_PowerUpGrab = $0A !Sfx_Swim = $0E ;$1DFC !Sfx_Fireball = $06 !Sfx_BowserFall = $20 !Sfx_ExtraLife = $05 !Sfx_Drumroll = $11 !Sfx_Drumrolled = $12 !Sfx_Blast = $09 !Sfx_GrowVine = $03 !Sfx_GrowPowerUp = $02 !Sfx_CoinGrab = $01 !Sfx_BowserFlame = $17 !Sfx_BrickShatter = $07 !Sfx_Bridge = $16 ;$1DFA !Sfx_SmallJump = $01 !Sfx_BigJump = $01 ;music constants !Silence = $0F !StarPowerMusic = $0D !PipeIntroMusic = $15 !CloudMusic = $12 !CastleMusic = $08 !UndergroundMusic = $06 !WaterMusic = $03 !GroundMusic = $02 !TimeRunningOutMusic = $80 !EndOfLevelMusic = $0C !AltGameOverMusic = $0A !EndOfCastleMusic = $0B !VictoryMusic = $1C !GameOverMusic = $0A !DeathMusic = $09 ;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 !SoundMemory = $07b0 !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 PRGStart: REP #$10 LDX #$01FF TXS PHK PLB SEP #$10 ldy.b #!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 #$0a ;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.b #!WarmBootOffset ;if passed both, load warm boot pointer COLDBOOT: jsr INITIALIZEMEMORY ;clear memory using pointer in Y sta !OperMode ;reset primary mode of operation lda #$a5 ;set warm boot flag sta !WarmBootValidation sta !PseudoRandomBitReg ;set seed for pseudorandom register ;lda.b #%00001111 ;sta !SND_MASTERCTRL_REG ;enable all sound channels except dmc LDA #$80 STA $2100 jsr MOVEALLSPRITESOFFSCREEN jsr INITIALIZENAMETABLES ;initialize both name tables inc !DisableScreenFlag ;set flag to disable screen output lda !Mirror_PPU_CTRL_REG1 ora.b #%10000000 ;enable NMIs jsr WRITEPPUREG1 JMP SKIPMAINOPER ;------------------------------------------------------------------------------------- ;$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 db TITLESCREEN VRAM_ADDRTABLE_HIGH: db !VRAM_Buffer1>>8, WATERPALETTEDATA>>8, GROUNDPALETTEDATA>>8 db UNDERGROUNDPALETTEDATA>>8, CASTLEPALETTEDATA>>8, !VRAM_Buffer1_Offset>>8 db !VRAM_Buffer2>>8, !VRAM_Buffer2>>8, BOWSERPALETTEDATA>>8 db DAYSNOWPALETTEDATA>>8, NIGHTSNOWPALETTEDATA>>8, MUSHROOMPALETTEDATA>>8 db MARIOTHANKSMESSAGE>>8, LUIGITHANKSMESSAGE>>8, MUSHROOMRETAINERSAVED>>8 db PRINCESSSAVED1>>8, PRINCESSSAVED2>>8, WORLDSELECTMESSAGE1>>8 db WORLDSELECTMESSAGE2>>8 db TITLESCREEN>>8 VRAM_BUFFER_OFFSET: db !VRAM_Buffer1_Offset, !VRAM_Buffer2_Offset NONMASKABLEINTERRUPT: lda !Mirror_PPU_CTRL_REG1 ;disable NMIs in mirror reg and.b #%01111111 ;save all other bits sta !Mirror_PPU_CTRL_REG1 lda !Mirror_PPU_CTRL_REG2 ;disable OAM and background display by default and.b #%11100110 ldy !DisableScreenFlag ;get screen disable flag bne SCREENOFF ;if set, used bits as-is ora.b #%00011110 SCREENOFF: sta !Mirror_PPU_CTRL_REG2 ;save bits for later but not in register at the moment LDA #$80 STA $2100 STZ $210D ;reset layer 1 pos STZ $210D STZ $210E STZ $210E STZ $4300 ;do OAM DMA REP #$20 STZ $2102 LDA #$0004 STA $4301 LDA #$0008 STA $4303 LDA #$0220 STA $4305 LDY #$01 STY $420B SEP #$20 ;sta !PPU_SPR_ADDR ;reset spr-ram address register ;lda #$02 ;perform spr-ram DMA access on $0200-$02ff ;sta !SPR_DMA ldx !VRAM_Buffer_AddrCtrl ;load control for pointer to buffer contents CPX #$13 BNE + JSR TITLESCREENPROP + lda VRAM_ADDRTABLE_LOW,x ;set indirect at $00 to pointer sta $00 lda VRAM_ADDRTABLE_HIGH,x sta $01 jsr UPDATESCREEN ;update screen with buffer contents ldy #$00 ldx !VRAM_Buffer_AddrCtrl ;check for usage of $0341 cpx #$06 bne INITBUFFER iny ;get offset based on usage INITBUFFER: ldx VRAM_BUFFER_OFFSET,y lda #$00 ;clear buffer header at last location sta !VRAM_Buffer1_Offset,x sta !VRAM_Buffer1,x sta !VRAM_Buffer_AddrCtrl ;reinit address control to $0301 LDA !Sprite0HitDetectFlag BNE + LDX !HorizontalScroll LDA !Mirror_PPU_CTRL_REG1 AND #$01 STX $210D STA $210D + lda !Mirror_PPU_CTRL_REG2 ;copy mirror of $2001 to register LDY #$80 AND.b #%00011000 BEQ + LDY #$0F + STY $2100 JSR SPCComm ;jsr SOUNDENGINE ;play sound jsr READJOYPADS ;read joypads jsr PAUSEROUTINE ;handle pause LDA !SavedJoypad1Bits CMP #$30 BNE + LDA $1E00|!addr BNE .amk LDA #$12 STA $2140 .amk LDA #$80 STA $2100 JMP PRGStart + jsr UPDATETOPSCORE lda !GamePauseStatus ;check for pause status lsr bcs PAUSESKIP lda !TimerControl ;if master timer control not set, decrement beq DECTIMERS ;all frame and interval timers dec !TimerControl bne NODECTIMERS DECTIMERS: ldx #$14 ;load end offset for end of frame timers dec !IntervalTimerControl ;decrement interval timer control, bpl DECTIMERSLOOP ;if not expired, only frame timers will decrement lda #$14 sta !IntervalTimerControl ;if control for interval timers expired, ldx #$23 ;interval timers will decrement along with frame timers DECTIMERSLOOP: lda !Timers,x ;check current timer beq SKIPEXPTIMER ;if current timer expired, branch to skip, dec !Timers,x ;otherwise decrement the current timer SKIPEXPTIMER: dex ;move onto next timer bpl DECTIMERSLOOP ;do this until all timers are dealt with NODECTIMERS: inc !FrameCounter ;increment frame counter PAUSESKIP: ldx #$00 ldy #$07 lda !PseudoRandomBitReg ;get first memory location of LSFR bytes and.b #%00000010 ;mask out all but d1 sta $00 ;save here lda !PseudoRandomBitReg+1 ;get second memory location and.b #%00000010 ;mask out all but d1 eor $00 ;perform exclusive-OR on d1 from first and second bytes clc ;if neither or both are set, carry will be clear beq ROTPRANDOMBIT sec ;if one or the other is set, carry will be set ROTPRANDOMBIT: ror !PseudoRandomBitReg,x ;rotate carry into d7, and rotate last bit into carry inx ;increment to next byte dey ;decrement for loop bne ROTPRANDOMBIT lda !Sprite0HitDetectFlag ;check for flag here BEQ SKIPSPRITE0 - BIT $4212 BMI - lda !GamePauseStatus ;if in pause mode, do not bother with sprites at all lsr bcs SPRITE0HIT jsr MOVESPRITESOFFSCREEN jsr SPRITESHUFFLER SPRITE0HIT: LDX !HorizontalScroll LDA !Mirror_PPU_CTRL_REG1 AND #$01 - LDY $2137 LDY $213D CPY #$1F BCC - - BIT $4212 BVC - NOP STX $210D STA $210D SKIPSPRITE0: lda !GamePauseStatus ;if in pause mode, do not perform operation mode stuff lsr bcs SKIPMAINOPER jsr OPERMODEEXECUTIONTREE ;otherwise do one of many, many possible subroutines SKIPMAINOPER: PHD ;run vanilla NMI and CARL REP #$20 if !sa1 LDA #$3000 else LDA #$0D00 endif TCD STZ $41 STZ $43 SEP #$20 LDA #$20 STA $40 if !sa1 STZ $317F endif STZ $13C6|!addr STZ $0D9F|!addr LDA #$02 STA $10 STA $0D9B|!addr LDA #$80 STA $0DAE|!addr STA $4200 BRA + - WAI + if !sa1 LDA $3010 else LDA $0D10 endif BNE - STZ $4200 PLD JMP NONMASKABLEINTERRUPT ;------------------------------------------------------------------------------------- PAUSEROUTINE: lda !OperMode ;are we in victory mode? cmp #!VictoryModeValue ;if so, go ahead beq CHKPAUSETIMER cmp #!GameModeValue ;are we in game mode? bne EXITPAUSE ;if not, leave lda !OperMode_Task ;if we are in game mode, are we running game engine? cmp #$03 bne EXITPAUSE ;if not, leave CHKPAUSETIMER: lda !GamePauseTimer ;check if pause timer is still counting down beq CHKSTART dec !GamePauseTimer ;if so, decrement and leave rts CHKSTART: lda !SavedJoypad1Bits ;check to see if START is pressed and.b #!Start_Button ;on controller 1 beq CLRPAUSETIMER lda !GamePauseStatus ;check to see if timer flag is set and.b #%10000000 ;and if so, do not reset timer bne EXITPAUSE lda #$2b ;set pause timer sta !GamePauseTimer lda !GamePauseStatus tay iny ;set pause sfx queue for next pause mode sty !PauseSoundQueue eor.b #%00000001 ;invert d0 and set d7 ora.b #%10000000 bne SETPAUSE ;unconditional branch CLRPAUSETIMER: lda !GamePauseStatus ;clear timer flag if timer is at zero and START button and.b #%01111111 ;is not pressed SETPAUSE: sta !GamePauseStatus EXITPAUSE: rts ;------------------------------------------------------------------------------------- ;$00 - used for preset value SPRITESHUFFLER: ldy !AreaType ;load level type, likely residual code lda #$28 ;load preset value which will put it at sta $00 ;sprite #10 ldx #$0e ;START at the end of OAM data offsets SHUFFLELOOP: lda !SprDataOffset,x ;check for offset value against cmp $00 ;the preset value bcc NEXTSPROFFSET ;if less, skip this part ldy !SprShuffleAmtOffset ;get current offset to preset value we want to add clc adc !SprShuffleAmt,y ;get shuffle amount, add to current sprite offset bcc STRSPROFFSET ;if not exceeded $ff, skip second add clc adc $00 ;otherwise add preset value $28 to offset STRSPROFFSET: sta !SprDataOffset,x ;store new offset here or old one if branched to here NEXTSPROFFSET: dex ;move backwards to next one bpl SHUFFLELOOP ldx !SprShuffleAmtOffset ;load offset inx cpx #$03 ;check if offset + 1 goes to 3 bne SETAMTOFFSET ;if offset + 1 not 3, store ldx #$00 ;otherwise, init to 0 SETAMTOFFSET: stx !SprShuffleAmtOffset ldx #$08 ;load offsets for values and storage ldy #$02 SETMISCOFFSET: lda !SprDataOffset+5,y ;load one of three OAM data offsets sta !Misc_SprDataOffset-2,x ;store first one unmodified, but clc ;add eight to the second and eight adc #$08 ;more to the third one sta !Misc_SprDataOffset-1,x ;note that due to the way X is set up, clc ;this code loads into the misc sprite offsets adc #$08 sta !Misc_SprDataOffset,x dex dex dex dey bpl SETMISCOFFSET ;do this until all misc spr offsets are loaded rts ;------------------------------------------------------------------------------------- OPERMODEEXECUTIONTREE: lda !OperMode ;this is the heart of the entire program, jsr JUMPENGINE ;most of what goes on starts here dw TITLESCREENMODE dw GAMEMODE dw VICTORYMODE dw GAMEOVERMODE ;------------------------------------------------------------------------------------- MOVEALLSPRITESOFFSCREEN: ldy #$00 ;this routine moves all sprites off the screen db $2c ;this is a BIT opcode that skips over the next 2 bytes MOVESPRITESOFFSCREEN: ldy #$04 ;this routine moves all but sprite 0 lda #$f0 ;off the screen SPRINITLOOP: sta !Sprite_Y_Position,y ;write 248 into OAM data's Y coordinate iny ;which will move it off the screen iny iny iny bne SPRINITLOOP rts ;------------------------------------------------------------------------------------- TITLESCREENMODE: lda !OperMode_Task jsr JUMPENGINE dw INITIALIZEGAME dw SCREENROUTINES dw PRIMARYGAMESETUP dw GAMEMENUROUTINE ;------------------------------------------------------------------------------------- WSELECTBUFFERTEMPLATE: db $04,$20,$73,$01,$00,$00 GAMEMENUROUTINE: ldy #$00 lda !SavedJoypad1Bits ;check to see if either player pressed ora !SavedJoypad2Bits ;only the START button (either joypad) cmp.b #!Start_Button beq STARTGAME cmp.b #!A_Button+!Start_Button ;check to see if A + START was pressed bne CHKSELECT ;if not, branch to check select button STARTGAME: jmp CHKCONTINUE ;if either START or A + START, execute here CHKSELECT: cmp.b #!Select_Button ;check to see if the select button was pressed beq SELECTBLOGIC ;if so, branch reset demo timer ldx !DemoTimer ;otherwise check demo timer bne CHKWORLDSEL ;if demo timer not expired, branch to check world selection sta !SelectTimer ;set controller bits here if running demo jsr DEMOENGINE ;run through the demo actions bcs RESETTITLE ;if carry flag set, demo over, thus branch jmp RUNDEMO ;otherwise, run game engine for demo CHKWORLDSEL: ldx !WorldSelectEnableFlag ;check to see if world selection has been enabled beq NULLJOYPAD cmp.b #!B_Button ;if so, check to see if the B button was pressed bne NULLJOYPAD iny ;if so, increment Y and execute same code as select SELECTBLOGIC: lda !DemoTimer ;if select or B pressed, check demo timer one last time beq RESETTITLE ;if demo timer expired, branch to reset title screen mode lda #$18 ;otherwise reset demo timer sta !DemoTimer lda !SelectTimer ;check select/B button timer bne NULLJOYPAD ;if not expired, branch lda #$10 ;otherwise reset select button timer sta !SelectTimer cpy #$01 ;was the B button pressed earlier? if so, branch beq INCWORLDSEL ;note this will not be run if world selection is disabled lda !NumberOfPlayers ;if no, must have been the select button, therefore eor.b #%00000001 ;change number of players and draw icon accordingly sta !NumberOfPlayers jsr DRAWMUSHROOMICON jmp NULLJOYPAD INCWORLDSEL: ldx !WorldSelectNumber ;increment world select number inx txa and.b #%00000111 ;mask out higher bits sta !WorldSelectNumber ;store as current world select number jsr GOCONTINUE UPDATESHROOM: lda WSELECTBUFFERTEMPLATE,x ;write template for world select in vram buffer sta !VRAM_Buffer1-1,x ;do this until all bytes are written inx cpx #$06 bmi UPDATESHROOM ldy !WorldNumber ;get world number from variable and increment for iny ;proper display, and put in blank byte before sty !VRAM_Buffer1+3 ;null terminator NULLJOYPAD: lda #$00 ;clear joypad bits for player 1 sta !SavedJoypad1Bits RUNDEMO: jsr GAMECOREROUTINE ;run game engine lda !GameEngineSubroutine ;check to see if we're running lose life routine cmp #$06 bne EXITMENU ;if not, do not do all the resetting below RESETTITLE: lda #$00 ;reset game modes, disable sta !OperMode ;sprite 0 check and disable sta !OperMode_Task ;screen output sta !Sprite0HitDetectFlag inc !DisableScreenFlag rts CHKCONTINUE: ldy !DemoTimer ;if timer for demo has expired, reset modes beq RESETTITLE asl ;check to see if A button was also pushed bcc STARTWORLD1 ;if not, don't load continue function's world number lda !ContinueWorld ;load previously saved world number for secret jsr GOCONTINUE ;continue function when pressing A + START STARTWORLD1: jsr LOADAREAPOINTER inc !Hidden1UpFlag ;set 1-up box flag for both players inc !OffScr_Hidden1UpFlag inc !FetchNewGameTimerFlag ;set fetch new game timer flag inc !OperMode ;set next game mode lda !WorldSelectEnableFlag ;if world select flag is on, then primary sta !PrimaryHardMode ;hard mode must be on as well lda #$00 sta !OperMode_Task ;set game mode here, and clear demo timer sta !DemoTimer ldx #$17 lda #$00 INITSCORES: sta !ScoreAndCoinDisplay,x ;clear player scores and coin displays dex bpl INITSCORES EXITMENU: rts GOCONTINUE: sta !WorldNumber ;START both players at the first area sta !OffScr_WorldNumber ;of the previously saved world number ldx #$00 ;note that on power-up using this function stx !AreaNumber ;will make no difference stx !OffScr_AreaNumber rts ;------------------------------------------------------------------------------------- MUSHROOMICONDATA: db $07,$22,$49,$83,$ce,$24,$24,$00 DRAWMUSHROOMICON: ldy #$07 ;read eight bytes to be read by transfer routine ICONDATAREAD: lda MUSHROOMICONDATA,y ;note that the default position is set for a sta !VRAM_Buffer1-1,y ;1-player game dey bpl ICONDATAREAD lda !NumberOfPlayers ;check number of players beq EXITICON ;if set to 1-player game, we're done lda #$24 ;otherwise, load blank tile in 1-player position sta !VRAM_Buffer1+3 lda #$ce ;then load shroom icon tile in 2-player position sta !VRAM_Buffer1+5 EXITICON: rts ;------------------------------------------------------------------------------------- DEMOACTIONDATA: db $01,$80,$02,$81,$41,$80,$01 db $42,$c2,$02,$80,$41,$c1,$41,$c1 db $01,$c1,$01,$02,$80,$00 DEMOTIMINGDATA: db $9b,$10,$18,$05,$2c,$20,$24 db $15,$5a,$10,$20,$28,$30,$20,$10 db $80,$20,$30,$30,$01,$ff,$00 DEMOENGINE: ldx !DemoAction ;load current demo action lda !DemoActionTimer ;load current action timer bne DOACTION ;if timer still counting down, skip inx inc !DemoAction ;if expired, increment action, X, and sec ;set carry by default for demo over lda DEMOTIMINGDATA-1,x ;get next timer sta !DemoActionTimer ;store as current timer beq DEMOOVER ;if timer already at zero, skip DOACTION: lda DEMOACTIONDATA-1,x ;get and perform action (current or next) sta !SavedJoypad1Bits dec !DemoActionTimer ;decrement action timer clc ;clear carry if demo still going DEMOOVER: rts ;------------------------------------------------------------------------------------- VICTORYMODE: jsr VICTORYMODESUBROUTINES ;run victory mode subroutines lda !OperMode_Task ;get current task of victory mode beq AUTOPLAYER ;if on bridge collapse, skip enemy processing ldx #$00 stx !ObjectOffset ;otherwise reset enemy object offset jsr ENEMIESANDLOOPSCORE ;and run enemy code AUTOPLAYER: jsr RELATIVEPLAYERPOSITION ;get player's relative coordinates jmp PLAYERGFXHANDLER ;draw the player, then leave VICTORYMODESUBROUTINES: lda !OperMode_Task jsr JUMPENGINE dw BRIDGECOLLAPSE dw SETUPVICTORYMODE dw PLAYERVICTORYWALK dw PRINTVICTORYMESSAGES dw PLAYERENDWORLD ;------------------------------------------------------------------------------------- SETUPVICTORYMODE: ldx !ScreenRight_PageLoc ;get page location of right side of screen inx ;increment to next page stx !DestinationPageLoc ;store here lda #!EndOfCastleMusic sta !EventMusicQueue ;play win castle music jmp INCMODETASK_B ;jump to set next major task in victory mode ;------------------------------------------------------------------------------------- PLAYERVICTORYWALK: ldy #$00 ;set value here to not walk player by default sty !VictoryWalkControl lda !Player_PageLoc ;get player's page location cmp !DestinationPageLoc ;compare with destination page location bne PERFORMWALK ;if page locations don't match, branch lda !Player_X_Position ;otherwise get player's horizontal position cmp #$60 ;compare with preset horizontal position bcs DONTWALK ;if still on other page, branch ahead PERFORMWALK: inc !VictoryWalkControl ;otherwise increment value and Y iny ;note Y will be used to walk the player DONTWALK: tya ;put contents of Y in A and jsr AUTOCONTROLPLAYER ;use A to move player to the right or not lda !ScreenLeft_PageLoc ;check page location of left side of screen cmp !DestinationPageLoc ;against set value here beq EXITVWALK ;branch if equal to change modes if necessary lda !ScrollFractional clc ;do fixed point math on fractional part of scroll adc #$80 sta !ScrollFractional ;save fractional movement amount lda #$01 ;set 1 pixel per frame adc #$00 ;add carry from previous addition tay ;use as scroll amount jsr SCROLLSCREEN ;do sub to scroll the screen jsr UPDSCROLLVAR ;do another sub to update screen and scroll variables inc !VictoryWalkControl ;increment value to stay in this routine EXITVWALK: lda !VictoryWalkControl ;load value set here beq INCMODETASK_A ;if zero, branch to change modes rts ;otherwise leave ;------------------------------------------------------------------------------------- PRINTVICTORYMESSAGES: lda !SecondaryMsgCounter ;load secondary message counter bne INCMSGCOUNTER ;if set, branch to increment message counters lda !PrimaryMsgCounter ;otherwise load primary message counter beq THANKPLAYER ;if set to zero, branch to print first message cmp #$09 ;if at 9 or above, branch elsewhere (this comparison bcs INCMSGCOUNTER ;is residual code, counter never reaches 9) ldy !WorldNumber ;check world number cpy #!World8 bne MRETAINERMSG ;if not at world 8, skip to next part cmp #$03 ;check primary message counter again bcc INCMSGCOUNTER ;if not at 3 yet (world 8 only), branch to increment sbc #$01 ;otherwise subtract one jmp THANKPLAYER ;and skip to next part MRETAINERMSG: cmp #$02 ;check primary message counter bcc INCMSGCOUNTER ;if not at 2 yet (world 1-7 only), branch THANKPLAYER: tay ;put primary message counter into Y bne SECONDPARTMSG ;if counter nonzero, skip this part, do not print first message lda !CurrentPlayer ;otherwise get player currently on the screen beq EVALFORMUSIC ;if mario, branch iny ;otherwise increment Y once for luigi and bne EVALFORMUSIC ;do an unconditional branch to the same place SECONDPARTMSG: iny ;increment Y to do world 8's message lda !WorldNumber cmp #!World8 ;check world number beq EVALFORMUSIC ;if at world 8, branch to next part dey ;otherwise decrement Y for world 1-7's message cpy #$04 ;if counter at 4 (world 1-7 only) bcs SETENDTIMER ;branch to set victory end timer cpy #$03 ;if counter at 3 (world 1-7 only) bcs INCMSGCOUNTER ;branch to keep counting EVALFORMUSIC: cpy #$03 ;if counter not yet at 3 (world 8 only), branch bne PRINTMSG ;to print message only (note world 1-7 will only lda #!VictoryMusic ;reach this code if counter = 0, and will always branch) sta !EventMusicQueue ;otherwise load victory music first (world 8 only) PRINTMSG: tya ;put primary message counter in A clc ;add $0c or 12 to counter thus giving an appropriate value, adc #$0c ;($0c-$0d = first), ($0e = world 1-7's), ($0f-$12 = world 8's) sta !VRAM_Buffer_AddrCtrl ;write message counter to vram address controller INCMSGCOUNTER: lda !SecondaryMsgCounter clc adc #$04 ;add four to secondary message counter sta !SecondaryMsgCounter lda !PrimaryMsgCounter adc #$00 ;add carry to primary message counter sta !PrimaryMsgCounter cmp #$07 ;check primary counter one more time SETENDTIMER: bcc EXITMSGS ;if not reached value yet, branch to leave lda #$06 sta !WorldEndTimer ;otherwise set world end timer INCMODETASK_A: inc !OperMode_Task ;move onto next task in mode EXITMSGS: rts ;leave ;------------------------------------------------------------------------------------- PLAYERENDWORLD: lda !WorldEndTimer ;check to see if world end timer expired bne ENDEXITONE ;branch to leave if not ldy !WorldNumber ;check world number cpy #!World8 ;if on world 8, player is done with game, bcs ENDCHKBBUTTON ;thus branch to read controller lda #$00 sta !AreaNumber ;otherwise initialize area number used as offset sta !LevelNumber ;and level number control to START at area 1 sta !OperMode_Task ;initialize secondary mode of operation inc !WorldNumber ;increment world number to move onto the next world jsr LOADAREAPOINTER ;get area address offset for the next area inc !FetchNewGameTimerFlag ;set flag to load game timer from header lda #!GameModeValue sta !OperMode ;set mode of operation to game mode ENDEXITONE: rts ;and leave ENDCHKBBUTTON: lda !SavedJoypad1Bits ora !SavedJoypad2Bits ;check to see if B button was pressed on and.b #!B_Button ;either controller beq ENDEXITTWO ;branch to leave if not lda #$01 ;otherwise set world selection flag sta !WorldSelectEnableFlag lda #$ff ;remove onscreen player's lives sta !NumberofLives jsr TERMINATEGAME ;do sub to continue other player or end game ENDEXITTWO: rts ;leave ;------------------------------------------------------------------------------------- ;data is used as tiles for numbers ;that appear when you defeat enemies FLOATEYNUMTILEDATA: db $ff,$ff ;dummy db $f6,$fb ; "100" db $f7,$fb ; "200" db $f8,$fb ; "400" db $f9,$fb ; "500" db $fa,$fb ; "800" db $f6,$50 ; "1000" db $f7,$50 ; "2000" db $f8,$50 ; "4000" db $f9,$50 ; "5000" db $fa,$50 ; "8000" db $fd,$fe ; "1-UP" ;high nybble is digit number, low nybble is number to ;add to the digit of the player's score SCOREUPDATEDATA: db $ff ;dummy db $41,$42,$44,$45,$48 db $31,$32,$34,$35,$38,$00 FLOATEYNUMBERSROUTINE: lda !FloateyNum_Control,x ;load control for floatey number beq ENDEXITONE ;if zero, branch to leave cmp #$0b ;if less than $0b, branch bcc CHKNUMTIMER lda #$0b ;otherwise set to $0b, thus keeping sta !FloateyNum_Control,x ;it in range CHKNUMTIMER: tay ;use as Y lda !FloateyNum_Timer,x ;check value here bne DECNUMTIMER ;if nonzero, branch ahead sta !FloateyNum_Control,x ;initialize floatey number control and leave rts DECNUMTIMER: dec !FloateyNum_Timer,x ;decrement value here cmp #$2b ;if not reached a certain point, branch bne CHKTALLENEMY cpy #$0b ;check offset for $0b bne LOADNUMTILES ;branch ahead if not found inc !NumberofLives ;give player one extra life (1-up) lda #!Sfx_ExtraLife sta !1DFC ;and play the 1-up sound LOADNUMTILES: lda SCOREUPDATEDATA,y ;load point value here lsr ;move high nybble to low lsr lsr lsr tax ;use as X offset, essentially the digit lda SCOREUPDATEDATA,y ;load again and this time and.b #%00001111 ;mask out the high nybble sta !DigitModifier,x ;store as amount to add to the digit jsr ADDTOSCORE ;update the score accordingly CHKTALLENEMY: ldy !Enemy_SprDataOffset,x ;get OAM data offset for enemy object lda !Enemy_ID,x ;get enemy object identifier cmp #!Spiny beq FLOATEYPART ;branch if spiny cmp #!PiranhaPlant beq FLOATEYPART ;branch if piranha plant cmp #!HammerBro beq GETALTOFFSET ;branch elsewhere if hammer bro cmp #!GreyCheepCheep beq FLOATEYPART ;branch if cheep-cheep of either color cmp #!RedCheepCheep beq FLOATEYPART cmp #!TallEnemy bcs GETALTOFFSET ;branch elsewhere if enemy object => $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 #$24 ;$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: ;TODO lda !OperMode ;are we in title screen mode? bne INCMODETASK_B ;if not, exit LDA #$13 jmp SETVRAMADDR_B ;increment task and exit ;------------------------------------------------------------------------------------- CLEARBUFFERSDRAWICON: lda !OperMode ;check game mode bne INCMODETASK_B ;if not title screen mode, leave ldx #$00 ;otherwise, clear buffer space TSCRCLEAR: sta !VRAM_Buffer1-1,x ;sta !VRAM_Buffer1-1+$100,x ;STA $0300,x STA !SprObject_X_MoveForce,x dex bne TSCRCLEAR jsr DRAWMUSHROOMICON ;draw player select icon INCSUBTASK: inc !ScreenRoutineTask ;move onto next task rts ;------------------------------------------------------------------------------------- WRITETOPSCORE: lda #$fa ;run display routine to display top score on title jsr UPDATENUMBER INCMODETASK_B: inc !OperMode_Task ;move onto next mode rts ;------------------------------------------------------------------------------------- GAMETEXT: TOPSTATUSBARLINE: db $20,$43,$05,$16,$0a,$1b,$12,$18 ; "MARIO" db $20,$52,$0b,$20,$18,$1b,$15,$0d ; "WORLD TIME" db $24,$24,$1d,$12,$16,$0e db $20,$68,$05,$00,$24,$24,$2e,$29 ; score trailing digit and coin display ;db $23,$c0,$7f,$aa ; attribute table data, clears name table 0 to palette 2 ;db $23,$c2,$01,$ea ; attribute table data, used for coin icon in status bar db $20,$6B,$C1,$0C db $ff ; end of data block WORLDLIVESDISPLAY: db $21,$cd,$07,$24,$24 ; cross with spaces used on db $29,$24,$24,$24,$24 ; lives display db $21,$4b,$09,$20,$18 ; "WORLD - " used on lives display db $1b,$15,$0d,$24,$24,$28,$24 db $22,$0c,$47,$24 ; possibly used to clear time up db $21,$d1,$C1,$0C ; attribute table data for crown if more than 9 lives db $ff TWOPLAYERTIMEUP: db $21,$cd,$05,$16,$0a,$1b,$12,$18 ; "MARIO" ONEPLAYERTIMEUP: db $22,$0c,$07,$1d,$12,$16,$0e,$24,$1e,$19 ; "TIME UP" db $ff TWOPLAYERGAMEOVER: db $21,$cd,$05,$16,$0a,$1b,$12,$18 ; "MARIO" ONEPLAYERGAMEOVER: db $22,$0b,$09,$10,$0a,$16,$0e,$24 ; "GAME OVER" db $18,$1f,$0e,$1b db $ff WARPZONEWELCOME: ;db $25,$84,$15,$20,$0e,$15,$0c,$18,$16 ; "WELCOME TO WARP ZONE!" ;db $0e,$24,$1d,$18,$24,$20,$0a,$1b,$19 ;db $24,$23,$18,$17,$0e,$2b ;db $26,$25,$01,$24 ; placeholder for left pipe ;db $26,$2d,$01,$24 ; placeholder for middle pipe ;db $26,$35,$01,$24 ; placeholder for right pipe ;db $27,$d9,$46,$aa ; attribute data ;db $27,$e1,$45,$aa db $25,$84,$15,$20,$0e,$15,$0c,$18,$16 ; "WELCOME TO WARP ZONE!" db $0e,$24,$1d,$18,$24,$20,$0a,$1b,$19 db $24,$23,$18,$17,$0e,$2b db $26,$25,$C2,$24,$08 ; placeholder for left pipe db $26,$2d,$C2,$24,$08 ; placeholder for middle pipe db $26,$35,$C2,$24,$08 ; placeholder for right pipe db $25,$84,$D5,$08,$08,$08,$08,$08,$08 db $08,$08,$08,$08,$08,$08,$08,$08,$08 db $08,$08,$08,$08,$08,$08 db $ff LUIGINAME: db $15,$1e,$12,$10,$12 ; "LUIGI", no address or length WARPZONENUMBERS: db $04,$03,$02,$00 ; warp zone numbers, note spaces on middle db $24,$05,$24,$00 ; zone, partly responsible for db $08,$07,$06,$00 ; the minus world GAMETEXTOFFSETS: db TOPSTATUSBARLINE-GAMETEXT, TOPSTATUSBARLINE-GAMETEXT db WORLDLIVESDISPLAY-GAMETEXT, WORLDLIVESDISPLAY-GAMETEXT db TWOPLAYERTIMEUP-GAMETEXT, ONEPLAYERTIMEUP-GAMETEXT db TWOPLAYERGAMEOVER-GAMETEXT, ONEPLAYERGAMEOVER-GAMETEXT db WARPZONEWELCOME-GAMETEXT, WARPZONEWELCOME-GAMETEXT WRITEGAMETEXT: pha ;save text number to stack asl tay ;multiply by 2 and use as offset cpy #$04 ;if set to do top status bar or world/lives display, bcc LDGAMETEXT ;branch to use current offset as-is cpy #$08 ;if set to do time-up or game over, bcc CHK2PLAYERS ;branch to check players ldy #$08 ;otherwise warp zone, therefore set offset CHK2PLAYERS: lda !NumberOfPlayers ;check for number of players bne LDGAMETEXT ;if there are two, use current offset to also print name iny ;otherwise increment offset by one to not print name LDGAMETEXT: ldx GAMETEXTOFFSETS,y ;get offset to message we want to print ldy #$00 GAMETEXTLOOP: lda GAMETEXT,x ;load message data cmp #$ff ;check for terminator beq ENDGAMETEXT ;branch to end text if found sta !VRAM_Buffer1,y ;otherwise write data to buffer inx ;and increment increment iny bne GAMETEXTLOOP ;do this for 256 bytes if no terminator found ENDGAMETEXT: lda #$00 ;put null terminator at end sta !VRAM_Buffer1,y pla ;pull original text number from stack tax cmp #$04 ;are we printing warp zone? bcs PRINTWARPZONENUMBERS dex ;are we printing the world/lives display? bne CHECKPLAYERNAME ;if not, branch to check player's name lda !NumberofLives ;otherwise, check number of lives clc ;and increment by one for display adc #$01 cmp #10 ;more than 9 lives? bcc PUTLIVES sbc #10 ;if so, subtract 10 and put a crown tile ldy #$9f ;next to the difference...strange things happen if sty !VRAM_Buffer1+7 ;the number of lives exceeds 19 PUTLIVES: sta !VRAM_Buffer1+8 ldy !WorldNumber ;write world and level numbers (incremented for display) iny ;to the buffer in the spaces surrounding the dash sty !VRAM_Buffer1+19 ldy !LevelNumber iny sty !VRAM_Buffer1+21 ;we're done here rts CHECKPLAYERNAME: lda !NumberOfPlayers ;check number of players beq EXITCHKNAME ;if only 1 player, leave lda !CurrentPlayer ;load current player dex ;check to see if current message number is for time up bne CHKLUIGI ldy !OperMode ;check for game over mode cpy #!GameOverModeValue beq CHKLUIGI eor.b #%00000001 ;if not, must be time up, invert d0 to do other player CHKLUIGI: lsr bcc EXITCHKNAME ;if mario is current player, do not change the name ldy #$04 NAMELOOP: lda LUIGINAME,y ;otherwise, replace "MARIO" with "LUIGI" sta !VRAM_Buffer1+3,y dey bpl NAMELOOP ;do this until each letter is replaced EXITCHKNAME: rts PRINTWARPZONENUMBERS: sbc #$04 ;subtract 4 and then shift to the left asl ;twice to get proper warp zone number asl ;offset tax ldy #$00 WARPNUMLOOP: lda WARPZONENUMBERS,x ;print warp zone numbers into the sta !VRAM_Buffer1+27,y ;placeholders from earlier inx iny ;put a number in every fourth space iny iny iny INY CPY #$0F bcc WARPNUMLOOP LDA #$2F ;load new buffer pointer at end of message jmp SETVRAMOFFSET ;------------------------------------------------------------------------------------- RESETSPRITESANDSCREENTIMER: lda !ScreenTimer ;check if screen timer has expired bne NORESET ;if not, branch to leave jsr MOVEALLSPRITESOFFSCREEN ;otherwise reset sprites now RESETSCREENTIMER: lda #$07 ;reset timer again sta !ScreenTimer inc !ScreenRoutineTask ;move onto next task NORESET: rts ;------------------------------------------------------------------------------------- ;$00 - temp vram buffer offset ;$01 - temp metatile buffer offset ;$02 - temp metatile graphics table offset ;$03 - used to store attribute bits ;$04 - used to determine attribute table row ;$05 - used to determine attribute table column ;$06 - metatile graphics table address low ;$07 - metatile graphics table address high RENDERAREAGRAPHICS: lda !CurrentColumnPos ;store LSB of where we're at and #$01 sta $05 ldy !VRAM_Buffer2_Offset ;store vram buffer offset sty $00 lda !CurrentNTAddr_Low ;get current name table address we're supposed to render sta !VRAM_Buffer2+1,y lda !CurrentNTAddr_High sta !VRAM_Buffer2,y LDA #$F4 ;lda #$9a ;store length byte of 26 here with d7 set sta !VRAM_Buffer2+2,y ;to increment by 32 (in columns) lda #$00 ;init attribute row sta $04 tax DRAWMTLOOP: stx $01 ;store init value of 0 or incremented offset for buffer lda !MetatileBuffer,x ;get first metatile number, and mask out all but 2 MSB and.b #%11000000 ASL ;note that metatile format is: ROL ;%xx000000 - attribute table bits, ROL ;%00xxxxxx - metatile number STA $03 ;store attribute table bits here tay ;rotate bits to d1-d0 and use as offset here lda METATILEGRAPHICS_LOW,y ;get address to graphics table from here sta $06 lda METATILEGRAPHICS_HIGH,y sta $07 lda !MetatileBuffer,x ;get metatile number again asl ;multiply by 4 and use as tile offset asl sta $02 lda !AreaParserTaskNum ;get current task number for level processing and and.b #%00000001 ;mask out all but LSB, then invert LSB, multiply by 2 eor.b #%00000001 ;to get the correct column position in the metatile, asl ;then add to the tile offset so we can draw either side adc $02 ;of the metatiles tay ldx $00 ;use vram buffer offset from before as X lda ($06),y sta !VRAM_Buffer2+3,x ;get first tile number (top left or top right) and store iny lda ($06),y ;now get the second (bottom left or bottom right) and store STA !VRAM_Buffer2+5,x LDA $03 ASL ASL STA !VRAM_Buffer2+4,x STA !VRAM_Buffer2+6,x ; ldy $04 ;get current attribute row ; lda $05 ;get LSB of current column where we're at, and ; bne RIGHTCHECK ;branch if set (clear = left attrib, set = right) ; lda $01 ;get current row we're rendering ; lsr ;branch if LSB set (clear = top left, set = bottom left) ; bcs LLEFT ; rol $03 ;rotate attribute bits 3 to the left ; rol $03 ;thus in d1-d0, for upper left square ; rol $03 ; jmp SETATTRIB ;RIGHTCHECK: lda $01 ;get LSB of current row we're rendering ; lsr ;branch if set (clear = top right, set = bottom right) ; bcs NEXTMTROW ; lsr $03 ;shift attribute bits 4 to the right ; lsr $03 ;thus in d3-d2, for upper right square ; lsr $03 ; lsr $03 ; jmp SETATTRIB ;LLEFT: lsr $03 ;shift attribute bits 2 to the right ; lsr $03 ;thus in d5-d4 for lower left square ;NEXTMTROW: inc $04 ;move onto next attribute row ;SETATTRIB: lda !AttributeBuffer,y ;get previously saved bits from before ; ora $03 ;if any, and put new bits, if any, onto ; sta !AttributeBuffer,y ;the old, and store inc $00 ;increment vram buffer offset by 2 inc $00 INC $00 INC $00 ldx $01 ;get current gfx buffer row, and check for inx ;the bottom of the screen cpx #$0d bcc DRAWMTLOOP ;if not there yet, loop back ldy $00 ;get current vram buffer offset, increment by 3 iny ;(for name table address and length bytes) iny iny lda #$00 sta !VRAM_Buffer2,y ;put null terminator at end of data for name table sty !VRAM_Buffer2_Offset ;store new buffer offset inc !CurrentNTAddr_Low ;increment name table address low lda !CurrentNTAddr_Low ;check current low byte and.b #%00011111 ;if no wraparound, just skip this part bne EXITDRAWM lda #$80 ;if wraparound occurs, make sure low byte stays sta !CurrentNTAddr_Low ;just under the status bar lda !CurrentNTAddr_High ;and then invert d2 of the name table address high eor.b #%00000100 ;to move onto the next appropriate name table sta !CurrentNTAddr_High EXITDRAWM: jmp SETVRAMCTRL ;jump to set buffer to $0341 and leave ;------------------------------------------------------------------------------------- ;$00 - temp attribute table address high (big endian order this time!) ;$01 - temp attribute table address low RENDERATTRIBUTETABLES: lda !CurrentNTAddr_Low ;get low byte of next name table address and.b #%00011111 ;to be written to, mask out all but 5 LSB, sec ;subtract four sbc #$04 and.b #%00011111 ;mask out bits again and store sta $01 lda !CurrentNTAddr_High ;get high byte and branch if borrow not set bcs SETATHIGH eor.b #%00000100 ;otherwise invert d2 SETATHIGH: and.b #%00000100 ;mask out all other bits ora #$23 ;add $2300 to the high byte and store sta $00 lda $01 ;get low byte - 4, divide by 4, add offset for lsr ;attribute table and store lsr adc #$c0 ;we should now have the appropriate block of sta $01 ;attribute table in our temp address ldx #$00 ldy !VRAM_Buffer2_Offset ;get buffer offset ATTRIBLOOP: lda $00 sta !VRAM_Buffer2,y ;store high byte of attribute table address lda $01 clc ;get low byte, add 8 because we want to START adc #$08 ;below the status bar, and store sta !VRAM_Buffer2+1,y sta $01 ;also store in temp again lda !AttributeBuffer,x ;fetch current attribute table byte and store sta !VRAM_Buffer2+3,y ;in the buffer lda #$01 sta !VRAM_Buffer2+2,y ;store length of 1 in buffer lsr sta !AttributeBuffer,x ;clear current byte in attribute buffer iny ;increment buffer offset by 4 bytes iny iny iny inx ;increment attribute offset and check to see cpx #$07 ;if we're at the end yet bcc ATTRIBLOOP sta !VRAM_Buffer2,y ;put null terminator at the end sty !VRAM_Buffer2_Offset ;store offset in case we want to do any more SETVRAMCTRL: lda #$06 sta !VRAM_Buffer_AddrCtrl ;set buffer to $0341 and leave rts ;------------------------------------------------------------------------------------- ;$00 - used as temporary counter in COLORROTATION COLORROTATEPALETTE: db $27,$27,$27,$17,$07,$17 BLANKPALETTE: db $3f,$0c,$04,$ff,$ff,$ff,$ff,$00 ;used based on area type PALETTE3DATA: db $0f,$07,$12,$0f db $0f,$07,$17,$0f db $0f,$07,$17,$1c db $0f,$07,$17,$00 COLORROTATION: lda !FrameCounter ;get frame counter and #$07 ;mask out all but three LSB bne EXITCOLORROT ;branch if not set to zero to do this every eighth frame ldx !VRAM_Buffer1_Offset ;check vram buffer offset cpx #$31 bcs EXITCOLORROT ;if offset over 48 bytes, branch to leave tay ;otherwise use frame counter's 3 LSB as offset here GETBLANKPAL: lda BLANKPALETTE,y ;get blank palette for palette 3 sta !VRAM_Buffer1,x ;store it in the vram buffer inx ;increment offsets iny cpy #$08 bcc GETBLANKPAL ;do this until all bytes are copied ldx !VRAM_Buffer1_Offset ;get current vram buffer offset lda #$03 sta $00 ;set counter here lda !AreaType ;get area type asl ;multiply by 4 to get proper offset asl tay ;save as offset here GETAREAPAL: lda PALETTE3DATA,y ;fetch palette to be written based on area type sta !VRAM_Buffer1+3,x ;store it to overwrite blank palette in vram buffer iny inx dec $00 ;decrement counter bpl GETAREAPAL ;do this until the palette is all copied ldx !VRAM_Buffer1_Offset ;get current vram buffer offset ldy !ColorRotateOffset ;get color cycling offset lda COLORROTATEPALETTE,y sta !VRAM_Buffer1+4,x ;get and store current color in second slot of palette lda !VRAM_Buffer1_Offset clc ;add seven bytes to vram buffer offset adc #$07 sta !VRAM_Buffer1_Offset inc !ColorRotateOffset ;increment color cycling offset lda !ColorRotateOffset cmp #$06 ;check to see if it's still in range bcc EXITCOLORROT ;if so, branch to leave lda #$00 sta !ColorRotateOffset ;otherwise, init to keep it in range EXITCOLORROT: rts ;leave ;------------------------------------------------------------------------------------- ;$00 - temp store for offset control bit ;$01 - temp vram buffer offset ;$02 - temp store for vertical high nybble in block buffer routine ;$03 - temp adder for high byte of name table address ;$04, $05 - name table address low/high ;$06, $07 - block buffer address low/high BLOCKGFXDATA: db $45,$45,$47,$47 db $47,$47,$47,$47 db $57,$58,$59,$5a db $24,$24,$24,$24 db $26,$26,$26,$26 REMOVECOIN_AXE: ldy #$41 ;set low byte so offset points to $0341 lda #$03 ;load offset for default blank metatile ldx !AreaType ;check area type bne WRITEBLANKMT ;if not water type, use offset lda #$04 ;otherwise load offset for blank metatile used in water WRITEBLANKMT: jsr PUTBLOCKMETATILE ;do a sub to write blank metatile to vram buffer lda #$06 sta !VRAM_Buffer_AddrCtrl ;set vram address controller to $0341 and leave rts REPLACEBLOCKMETATILE: jsr WRITEBLOCKMETATILE ;write metatile to vram buffer to replace block object inc !Block_ResidualCounter ;increment unused counter (residual code) dec !Block_RepFlag,x ;decrement flag (residual code) rts ;leave DESTROYBLOCKMETATILE: lda #$00 ;force blank metatile if branched/jumped to this point WRITEBLOCKMETATILE: ldy #$03 ;load offset for blank metatile cmp #$00 ;check contents of A for blank metatile beq USEBOFFSET ;branch if found (unconditional if branched from 8a6b) ldy #$00 ;load offset for brick metatile w/ line cmp #$58 beq USEBOFFSET ;use offset if metatile is brick with coins (w/ line) cmp #$51 beq USEBOFFSET ;use offset if metatile is breakable brick w/ line iny ;increment offset for brick metatile w/o line cmp #$5d beq USEBOFFSET ;use offset if metatile is brick with coins (w/o line) cmp #$52 beq USEBOFFSET ;use offset if metatile is breakable brick w/o line iny ;if any other metatile, increment offset for empty block USEBOFFSET: tya ;put Y in A ldy !VRAM_Buffer1_Offset ;get vram buffer offset iny ;move onto next byte jsr PUTBLOCKMETATILE ;get appropriate block data and write to vram buffer MOVEVOFFSET: dey ;decrement vram buffer offset tya ;add 10 bytes to it clc adc #10 jmp SETVRAMOFFSET ;branch to store as new vram buffer offset PUTBLOCKMETATILE: stx $00 ;store control bit from !SprDataOffset_Ctrl sty $01 ;store vram buffer offset for next byte asl asl ;multiply A by four and use as X tax ldy #$20 ;load high byte for name table 0 lda $06 ;get low byte of block buffer pointer cmp #$d0 ;check to see if we're on odd-page block buffer bcc SAVEHADDER ;if not, use current high byte ldy #$24 ;otherwise load high byte for name table 1 SAVEHADDER: sty $03 ;save high byte here and #$0f ;mask out high nybble of block buffer pointer asl ;multiply by 2 to get appropriate name table low byte sta $04 ;and then store it here lda #$00 sta $05 ;initialize temp high byte lda $02 ;get vertical high nybble offset used in block buffer routine clc adc #$20 ;add 32 pixels for the status bar asl rol $05 ;shift and rotate d7 onto d0 and d6 into carry asl rol $05 ;shift and rotate d6 onto d0 and d5 into carry adc $04 ;add low byte of name table and carry to vertical high nybble sta $04 ;and store here lda $05 ;get whatever was in d7 and d6 of vertical high nybble adc #$00 ;add carry clc adc $03 ;then add high byte of name table sta $05 ;store here ldy $01 ;get vram buffer offset to be used REMBRIDGE: lda BLOCKGFXDATA,x ;write top left and top right sta !VRAM_Buffer1+2,y ;tile numbers into first spot lda BLOCKGFXDATA+1,x sta !VRAM_Buffer1+3,y lda BLOCKGFXDATA+2,x ;write bottom left and bottom sta !VRAM_Buffer1+7,y ;right tiles numbers into lda BLOCKGFXDATA+3,x ;second spot sta !VRAM_Buffer1+8,y lda $04 sta !VRAM_Buffer1,y ;write low byte of name table clc ;into first slot as read adc #$20 ;add 32 bytes to value sta !VRAM_Buffer1+5,y ;write low byte of name table lda $05 ;plus 32 bytes into second slot sta !VRAM_Buffer1-1,y ;write high byte of name sta !VRAM_Buffer1+4,y ;table address to both slots lda #$02 sta !VRAM_Buffer1+1,y ;put length of 2 in sta !VRAM_Buffer1+6,y ;both slots lda #$00 sta !VRAM_Buffer1+9,y ;put null terminator at end ldx $00 ;get offset control bit here rts ;and leave ;------------------------------------------------------------------------------------- ;METATILE GRAPHICS TABLE METATILEGRAPHICS_LOW: db PALETTE0_MTILES, PALETTE1_MTILES, PALETTE2_MTILES, PALETTE3_MTILES METATILEGRAPHICS_HIGH: db PALETTE0_MTILES>>8, PALETTE1_MTILES>>8, PALETTE2_MTILES>>8, PALETTE3_MTILES>>8 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,$01,$03 db $30,$00,$10 db $00 NIGHTSNOWPALETTEDATA: db $3f,$00,$04 db $0f,$30,$00,$10 db $00 MUSHROOMPALETTEDATA: db $3f,$01,$03 db $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 !Mirror_PPU_CTRL_REG1 ora.b #%00010000 and.b #%11110000 STA !Mirror_PPU_CTRL_REG1 LDA #$80 STA $2115 REP #$30 LDA #$2000 STA $2116 LDA #$0824 LDX #$0800 - STA $2118 DEX BPL - SEP #$30 TXA STZ !VRAM_Buffer1_Offset ;init vram buffer 1 offset STZ !VRAM_Buffer1 ;init vram buffer 1 STZ !HorizontalScroll ;reset scroll variables STZ !VerticalScroll RTS ;------------------------------------------------------------------------------------- ;$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.b #%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.b #%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 RGBLow: db $8C,$A0,$62,$47,$2C,$2E,$2E,$6B,$A6,$C1,$E0,$E0,$E0,$00,$00,$00 db $B5,$81,$48,$0F,$D4,$D8,$F7,$33,$6D,$A6,$C0,$E0,$C0,$00,$00,$00 db $FF,$CB,$91,$59,$1E,$1F,$3F,$7D,$B7,$F0,$0A,$27,$07,$29,$00,$00 db $FF,$77,$7A,$5D,$3F,$3F,$3F,$5F,$7C,$99,$B7,$B5,$95,$D6,$00,$00 RGBHigh: db $31,$34,$3C,$3C,$30,$18,$00,$00,$00,$00,$00,$08,$20,$00,$00,$00 db $56,$59,$69,$65,$54,$38,$18,$01,$01,$01,$01,$1D,$3D,$00,$00,$00 db $7F,$7E,$7E,$7E,$7E,$62,$3E,$26,$16,$16,$2B,$47,$67,$25,$00,$00 db $7F,$7F,$7F,$7F,$7F,$73,$67,$5B,$57,$57,$5F,$6B,$77,$5A,$00,$00 HandleRGBBG: INY LDA ($00),y DEY TAX LDA RGBHigh,x XBA LDA RGBLow,x REP #$20 ASL #3 SEP #$21 ROR #3 XBA ORA #$40 STA $2132 LDA RGBHigh,x LSR A SEC : ROR STA $2132 XBA STA $2132 RTS HandleRGB: INY LDA ($00),y TAX ASL ASL BIT.b #%01000000 BEQ + ORA #$80 + AND.b #%10110000 PHA TXA AND.b #%00000011 ORA $01,s STA $2121 STA $01,s INY BIT.b #%00110011 BNE + JSR HandleRGBBG + LDA ($00),y - PHA INY LDA ($00),y TAX LDA RGBLow,x STA $2122 LDA RGBHigh,x STA $2122 PLA DEC BEQ + BIT.b #%00000011 BNE - TAX LDA $01,s CLC ADC #$10 BIT.b #%01000000 BEQ ++ PHX JSR HandleRGBBG PLX LDA #$80 ++ STA $2121 STA $01,s TXA BRA - + PLA BRA ++++ WRITEBUFFERTOSCREEN: CMP #$3F BEQ HandleRGB STA $2117 iny lda ($00),y ;load next byte (second) STA $2116 iny lda ($00),y ;load next byte (third) asl ;shift to left and save in stack LDX #$00 BCC + INX ASL BCC ++ LDX #$81 LSR LSR BIT #$01 BEQ +++ DEX SEC +++ STX $2115 TAX - BCS +++ INY LDA ($00),y STA $2118 DEX +++ INY LDA ($00),y STA $2119 DEX BNE - BRA ++++ + ASL ++ STX $2115 bcc GETLENGTH ;if d6 of third byte was clear, do not repeat byte ora.b #%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 $2118 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 UPDATESCREEN: 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 rts TITLESCREENPROP: PHX LDA #$80 STA $2115 REP #$20 LDY #$04 LDA #$2085 -- STA $2116 LDX #$15 - STY $2119 DEX BPL - CLC ADC #$0020 CMP #$2205 BNE -- LDA #$2249 -- STA $2116 LDY #$04 STY $2119 LDY #$08 LDX #$0D - STY $2119 DEX BPL - CLC ADC #$0040 CMP #$22C9 BNE -- LDA #$22EC STA $2116 LDX #$0A - STY $2119 DEX BPL - SEP #$20 PLX 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.b #%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 db $58,$18,$FF,$06 ;------------------------------------------------------------------------------------- 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.b #%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 STA $0300,y 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.b #%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.b #!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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%01000000 bne ALTER2 ;branch if d6 is set pla pha ;pull and push offset to copy to A and.b #%00001111 ;mask out high nybble and store as sta !TerrainControl ;new terrain height type bits pla and.b #%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.b #%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.b #%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.w !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.b #%00001111 sta $07 ;save row location iny lda (!AreaData),y ;get next byte, save lower nybble (length or height) and.b #%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 db !Block_Buffer_1>>8, !Block_Buffer_2>>8 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.b #%00001111 ;pull from stack, mask out high nybble clc adc BLOCKBUFFERADDR,y ;add to low byte sta $06 ;store here and leave rts ;------------------------------------------------------------------------------------- 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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%00001111 ;mask out all but lower nybble sta !TerrainControl pla ;pull and push byte to copy it to A pha and.b #%00110000 ;save 2 MSB for background scenery type lsr lsr ;shift bits to LSBs lsr lsr sta !BackgroundScenery ;save as background scenery pla and.b #%11000000 clc rol ;rotate bits over to LSBs rol rol cmp.b #%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 ENEMYDATAADDRHIGH: db E_CASTLEAREA1>>8, E_CASTLEAREA2>>8, E_CASTLEAREA3>>8, E_CASTLEAREA4>>8, E_CASTLEAREA5>>8, E_CASTLEAREA6>>8 db E_GROUNDAREA1>>8, E_GROUNDAREA2>>8, E_GROUNDAREA3>>8, E_GROUNDAREA4>>8, E_GROUNDAREA5>>8, E_GROUNDAREA6>>8 db E_GROUNDAREA7>>8, E_GROUNDAREA8>>8, E_GROUNDAREA9>>8, E_GROUNDAREA10>>8, E_GROUNDAREA11>>8, E_GROUNDAREA12>>8 db E_GROUNDAREA13>>8, E_GROUNDAREA14>>8, E_GROUNDAREA15>>8, E_GROUNDAREA16>>8, E_GROUNDAREA17>>8, E_GROUNDAREA18>>8 db E_GROUNDAREA19>>8, E_GROUNDAREA20>>8, E_GROUNDAREA21>>8, E_GROUNDAREA22>>8, E_UNDERGROUNDAREA1>>8 db E_UNDERGROUNDAREA2>>8, E_UNDERGROUNDAREA3>>8, E_WATERAREA1>>8, E_WATERAREA2>>8, E_WATERAREA3>>8 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 AREADATAADDRHIGH: db L_WATERAREA1>>8, L_WATERAREA2>>8, L_WATERAREA3>>8, L_GROUNDAREA1>>8, L_GROUNDAREA2>>8, L_GROUNDAREA3>>8 db L_GROUNDAREA4>>8, L_GROUNDAREA5>>8, L_GROUNDAREA6>>8, L_GROUNDAREA7>>8, L_GROUNDAREA8>>8, L_GROUNDAREA9>>8 db L_GROUNDAREA10>>8, L_GROUNDAREA11>>8, L_GROUNDAREA12>>8, L_GROUNDAREA13>>8, L_GROUNDAREA14>>8, L_GROUNDAREA15>>8 db L_GROUNDAREA16>>8, L_GROUNDAREA17>>8, L_GROUNDAREA18>>8, L_GROUNDAREA19>>8, L_GROUNDAREA20>>8, L_GROUNDAREA21>>8 db L_GROUNDAREA22>>8, L_UNDERGROUNDAREA1>>8, L_UNDERGROUNDAREA2>>8, L_UNDERGROUNDAREA3>>8, L_CASTLEAREA1>>8 db L_CASTLEAREA2>>8, L_CASTLEAREA3>>8, L_CASTLEAREA4>>8, L_CASTLEAREA5>>8, L_CASTLEAREA6>>8 ;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 ;------------------------------------------------------------------------------------- ;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.b #%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.b #%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.b #%11000000 sta !A_B_Buttons lda !SavedJoypadBits ;store left and right buttons in $0c and.b #%00000011 sta !Left_Right_Buttons lda !SavedJoypadBits ;store up and down buttons in $0b and.b #%00001100 sta !Up_Down_Buttons and.b #%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.b #%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 LDY #!DeathMusic 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.b #%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.b #%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 #$06 ;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.b #%11111000 ;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.b #%11111000 ;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 ;TODO lda !FlagpoleSoundQueue ;load flagpole sound sta !1DF9 ;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.b #%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.b #%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.b #!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.b #%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.b #%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.b #!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_Swim ;load swim/goomba stomp sound into sta !1DF9 ;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 !1DFA ;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 #!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.b #%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 #!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.b #%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 !1DFC 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.b #%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.b #%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.b #!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.w !Block_PageLoc,y sta !Enemy_PageLoc,x ;copy page location from previous object lda.w !Block_X_Position,y sta !Enemy_X_Position,x ;copy horizontal coordinate from previous object lda.w !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 !1DFC ;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.b #%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.b #%00001100 ;mask out bits cmp.b #%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 !1DFC ;play fireworks/gunfire sound CHKDSTE: lda !Enemy_State,x ;check enemy state for d5 set and.b #%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.b #%00000111 ;second part of LSFR bne SETMOFS ;if any bits are set, branch and use as offset lda !PseudoRandomBitReg+1 and.b #%00001000 ;get d3 from same part of LSFR SETMOFS: tay ;use either d3 or d2-d0 for offset here lda.w !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.w !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.b #%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.w !Enemy_State,y ;get enemy object state and.b #%11110111 ;mask out d3 sta.w !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.w !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.w !Enemy_PageLoc,y ;get enemy's page location adc #$00 ;add carry sta !Misc_PageLoc,x ;store as hammer's page location lda.w !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.w !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.w !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.w !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.w !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.w !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.w !Misc_Y_Position,y ;store as vertical coordinate JCOINC: lda #$fb sta.w !Misc_Y_Speed,y ;set vertical speed lda #$01 sta.w !Misc_Y_HighPos,y ;set vertical high byte sta.w !Misc_State,y ;set state for misc object LDA #!Sfx_CoinGrab sta !1DFC ;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.w !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 !1DFC ;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.b #%00100000 sta !Enemy_SprAttrib+5 ;set background priority bit lda #!Sfx_GrowPowerUp sta !1DFC ;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.b #%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 !1DF9 ;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 !1DFC ;load brick shatter sound LDA #$01 sta !Block_RepFlag,x ;set flag for block object to immediately replace metatile 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.b #%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.b #%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.b #%00001111 ;mask out high nybble tay lda.w !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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.w !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.w !Enemy_State,y ;if lakitu is not in normal state, branch to leave bne EXLSHAND lda.w !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.w !Enemy_X_Position,y sta !Enemy_X_Position,x lda #$01 ;put spiny within vertical screen unit sta !Enemy_Y_HighPos,x lda.w !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.b #%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.b #%00000011 ;get one of the LSFR parts and save the 2 LSB beq USEPOSV ;branch if neither bits are set tya eor.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.w !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.b #%10000000 ;store with d7 set as flag in new enemy sta.w !Enemy_Flag,y ;slot as well as enemy offset lda !Enemy_PageLoc,x sta.w !Enemy_PageLoc,y ;copy page location and horizontal coordinates lda !Enemy_X_Position,x ;from original enemy to new enemy sta.w !Enemy_X_Position,y lda #$01 sta !Enemy_Flag,x ;set flag as normal for original enemy sta.w !Enemy_Y_HighPos,y ;set high vertical byte for new enemy lda !Enemy_Y_Position,x sta.w !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 LDA #!Sfx_BowserFlame ;load bowser's flame sound into queue STA !1DFC ldy !BowserFront_Offset ;get bowser's buffer offset lda.w !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.b #%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.w !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.w !Enemy_PageLoc,y sta !Enemy_PageLoc,x ;copy page location from bowser to flame lda.w !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.b #%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.w !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.w !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.w !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.w !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.b #%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.b #%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.b #%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.w !Enemy_Flag,y ;if enemy buffer flag not set, beq BB_SLOOP ;loop back and check another slot lda.w !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 LDA #!Sfx_Blast ;play fireworks/gunfire sound sta !1DFC 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.w !Enemy_ID,y ;check enemy identifiers cmp #!Lakitu ;for lakitu bne NEXTFSLOT lda #$01 ;if found, set state sta.w !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.b #%10000000 ;set d7 sta !Enemy_Y_MoveForce,x ;store as movement force and.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%00100000 ;check enemy state for d5 set bne MOVEDEFEATEDENEMY ;if set, branch to move defeated enemy object lda !Enemy_State,x and.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%00000010 ;check for d1 set bne CHKFORFLOATDOWN ;branch if set lda !FrameCounter and.b #%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.b #%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.b #%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.b #%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.b #%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.b #%00001111 ;mask out low nybble cmp #$09 bcc GETHADDER ;if lower than $09, branch ahead eor.b #%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.b #%00001111 ;mask out high nybble cmp #$09 ;if lower than $09, branch ahead bcc GETVADDER eor.b #%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: ;read as part of the previous table and unused for gfx purposes db $20,$20,$20,$00,$00 MOVEFLYINGCHEEPCHEEP: lda !Enemy_State,x ;check cheep-cheep's enemy state and.b #%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.b #%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.b #%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.b #%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_Bridge ;load the fireworks/gunfire sound into the square 2 sfx sta !1DFC ;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.b #%01000000 sta !Enemy_State,x ;set bowser's state to one of defeated states (d6 set) lda #!Sfx_BowserFall sta !1DFC ;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.b #%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.b #%00000001 ;to control bowser's feet sta !BowserBodyControls RESETMDR: lda !FrameCounter ;check frame counter and.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.w !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.w !Enemy_Y_Position,y ;for bowser's rear lda !Enemy_State,x sta.w !Enemy_State,y ;copy enemy state directly from front to rear lda !Enemy_MovingDir,x sta.w !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.b #%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 #$24 ;load attributes without vertical flip by default lda !FrameCounter and.b #%00000010 ;invert vertical flip bit every 2 frames beq FLMEAT ;if d1 not set, write default value ldy #$A4 ;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 !1DFC 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 BNE + STZ !DrumrollBuffer LDA #!Sfx_Drumrolled STA !1DFC BRA INCREMENTSFTASK1 ;if no time left on game timer at all, branch to next task + LDA !DrumrollBuffer BNE NOTTICK INC !DrumrollBuffer LDA #!Sfx_Drumroll STA !1DFC ;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.b #%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 #$04 ;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.b #%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.w !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.w !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.w !Enemy_Y_Position,y ;add difference to vertical coordinate of other sta.w !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.w !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.w !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.w !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.w !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.w !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.w !Enemy_PageLoc,y adc #$00 ;add carry to page location sta $02 ;and save here pla ;pull modified horizontal coordinate and.b #%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.b #%00000011 ;mask out all bits but d7 and d6, then set ora.b #%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.b #%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.w !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.b #%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.w !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.b #%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 ;------------------------------------------------------------------------------------- ;$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.b #%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.b #%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.b #%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 !1DFC ;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.b #%00011111 ;mask out 2 MSB of enemy object's state ora.b #%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 !1DF9 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 !1DF9 ;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.b #%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.b #%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.b #%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.b #%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 !1DF9 lda !Enemy_State,x ;set d7 in enemy state, thus become moving shell ora.b #%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 LDA #!Sfx_PipeDown_Injury sta !1DF9 ;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 LDX #!DeathMusic 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 !1DF9 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.b #%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.b #%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.w !Enemy_State,y ;check both enemy states for d7 set and.b #%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.w !Enemy_State,y ;check both enemy states for d5 set ora !Enemy_State,x and.b #%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.w !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.w !Enemy_State,y ;if first enemy state < $06, branch elsewhere cmp #$06 bcc MOVEEOFS lda.w !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.b #%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.b #%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.b #%00001111 ;save low nybble cmp.b #%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 !1DF9 ;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 !1DF9 ;otherwise load pipedown/injury sound PLYRPIPE: ora.b #%00100000 sta !Player_SprAttrib ;set background priority bit in player attributes lda !Player_X_Position and.b #%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 LDA #!Sfx_Flagpole 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.b #%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 !1DF9 ;load pipedown/injury sound lda.b #%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.b #%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 !1DFC ;load coin grab sound and leave rts GETMTILEATTRIB: tay ;save metatile value into Y and.b #%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.b #%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.b #%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.b #%11110000 ;save high nybble ora.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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 !1DF9 ;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.b #%11110000 ;save high nybble of vertical coordinate, and ora.b #%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.b #%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 !1DF9 ;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.b #%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.b #%00001111 ;and mask out high nybble sta $04 ;store masked out result here lda $03 ;get saved content of block buffer rts ;and leave ;------------------------------------------------------------------------------------- ;$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 INY 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.b #%00000010 ;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.b #%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 $26,$26,$E6,$E6 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.b #%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.b #%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.b #%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 #$22;#$01 STZ $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.b #%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_Y_Position+20,y ;dump A contents sta !Sprite_Y_Position+16,y ;into third row sprites DUMPFOURSPR: sta !Sprite_Y_Position+12,y ;into second row sprites DUMPTHREESPR: sta !Sprite_Y_Position+8,y DUMPTWOSPR: sta !Sprite_Y_Position+4,y ;and into first row sprites sta !Sprite_Y_Position,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 #$24 ;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 #$24 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.b #%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 #$24 sta !Sprite_Attributes,y ;set attribute byte in first sprite lda #$A4 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 $04,$02,$04,$02 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 EOR #$20 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.b #%00000110 ;mask out all but d1 and d0 (previously d2 and d1) ora !Enemy_SprAttrib+5 ;add background priority bit if any set EOR #$20 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.b #%01000000 ;set horizontal flip bit for top right sprite sta !Sprite_Attributes+4,y lda !Sprite_Attributes+12,y ora.b #%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 $02,$04,$06,$04,$02,$02,$06,$06 db $06,$02,$02,$04,$04,$22,$02,$04 db $02,$02,$04,$ff,$04,$04,$02,$02 db $04,$04,$04 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.b #%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 #$06 ldy !EnemyFrameTimer,x ;get timer for enemy object beq SBBAT ;if expired, do not set priority bit ORA #$20 ;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.b #%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.b #%00001000 ;check for every eighth frame bne CHECKBOWSERFRONT lda $03 eor.b #%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 EOR #$20 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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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.b #%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 #$64 ;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.b #%10100110 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.b #%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.b #%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.b #%10000000 sta !Sprite_Attributes+8,y ;store bits with vertical flip in sta !Sprite_Attributes+16,y ;second and third row left sprites ora.b #%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.b #%10100010 ;in third row left sprite sta !Sprite_Attributes+16,y lda !Sprite_Attributes+20,y ;set horizontal flip and palette bits ora.b #%01100010 ;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.b #%10100010 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.b #%10100010 sta !Sprite_Attributes,y ;save vertical flip and palette bits lda !Sprite_Attributes+4,y ;get first row right sprite attributes ora.b #%01100010 ;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 #$A4 sta !Sprite_Attributes+8,y ;set vertical flip and palette bits of sta !Sprite_Attributes+16,y ;second and third row left sprites ora.b #%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_Y_Position+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 #$26 sta $04 ;set attribute byte here LDA #$01 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 #$26 ;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 LDA #$22 SETBFLIP: ldx !ObjectOffset ;put block object offset back in X sta !Sprite_Attributes,y ;store attribute byte as-is in first sprite ora.b #%01000000 sta !Sprite_Attributes+4,y ;set horizontal flip bit for second sprite ora.b #%10000000 sta !Sprite_Attributes+12,y ;set both flip bits for fourth sprite and.b #%10100110 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.b #%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.b #%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 #$24 ;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 #$26 ;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 #$24 ;load value $02 to set palette in attrib byte bcc FIREA ;if last bit shifted out was not set, skip this ora.b #%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.b #%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 #$24 ;set palette attributes for all sprites, but sta !Sprite_Attributes,y ;set no flip at all for first sprite lda #$A4 sta !Sprite_Attributes+4,y ;set vertical flip for second sprite lda #$64 sta !Sprite_Attributes+8,y ;set horizontal flip for third sprite lda #$E4 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 #$24 ;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.b #%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.b #%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.b #%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.b #%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 #$24 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.b #%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 PLAYERGRAPHICSTABLE+$9E ;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,$20,$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+28 ;get empty sprite attributes ora.b #%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 EOR #$20 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.b #%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.b #%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.b #%00111111 ora.b #%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.b #%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.b #%00111111 ora.b #%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 SPCComm: LDA !OperMode BEQ .skip LDA !PauseSoundQueue BEQ + CLC ADC #$10 LDY $1E00|!addr BNE .amk0 STA $2140 .amk0 STZ !PauseSoundQueue BRA .end + LDA $1E00|!addr BNE .amk LDA !1DF9 STA $2140 LDA !1DFA STA $2141 LDA !1DFC STA $2143 .amk LDA !EventMusicBuffer BEQ + DEC !EventMusicBuffer + LDA !EventMusicQueue BEQ ++ BPL +++ LDA $1E00|!addr BNE .amk2 LDA #$FF STA $2140 .amk2 BRA ++++ +++ LDY #$00 CMP #!DeathMusic BEQ +++ CMP #!EndOfLevelMusic BNE +++++ +++ LDY #$B4 STY !EventMusicBuffer +++++ LDY $1E00|!addr BNE .amk3 STA $2142 .amk3 ++++ STZ !EventMusicQueue BRA +++ ++ LDA !AreaMusicQueue BEQ .end LDY $1E00|!addr BNE .amk4 STA $2142 .amk4 STA !AreaMusicBuffer +++ STZ !AreaMusicQueue .end STZ !1DF9 STZ !1DFA STZ !1DFC .skip RTS TITLESCREEN: db $20,$A6,$54,$26 db $20,$C6,$54,$26 db $20,$E6,$54,$26 db $21,$06,$54,$26 db $20,$85,$01,$44 db $20,$86,$54,$48 db $20,$9A,$01,$49 db $20,$A5,$89,$46 ;! db $46,$46,$46,$46,$46,$46,$46,$46 db $20,$BA,$89,$4A ;! db $4A,$4A,$4A,$4A,$4A,$4A,$4A,$4A db $20,$A6,$0A,$D0,$D1,$D8,$D8,$DE db $D1,$D0,$DA,$DE,$D1 db $20,$C6,$0A,$D2,$D3,$DB,$DB,$DB db $D9,$DB,$DC,$DB,$DF db $20,$E6,$0A,$D4,$D5,$D4,$D9,$DB db $E2,$D4,$DA,$DB,$E0 db $21,$06,$0A,$D6,$D7,$D6,$D7,$E1 db $26,$D6,$DD,$E1,$E1 db $21,$26,$14,$D0,$E8,$D1,$D0,$D1 db $DE,$D1,$D8,$D0,$D1,$26,$DE,$D1 db $DE,$D1,$D0,$D1,$D0,$D1,$26 db $21,$46,$14,$DB,$42,$42,$DB,$42 db $DB,$42,$DB,$DB,$42,$26,$DB,$42 db $DB,$42,$DB,$42,$DB,$42,$26 db $21,$66,$46,$DB db $21,$6C,$0E,$DF,$DB,$DB,$DB,$26 db $DB,$DF,$DB,$DF,$DB,$DB,$E4,$E5 db $26 db $21,$86,$14,$DB,$DB,$DB,$DE,$43 db $DB,$E0,$DB,$DB,$DB,$26,$DB,$E3 db $DB,$E0,$DB,$DB,$E6,$E3,$26 db $21,$A6,$14,$DB,$DB,$DB,$DB,$42 db $DB,$DB,$DB,$D4,$D9,$26,$DB,$D9 db $DB,$DB,$D4,$D9,$D4,$D9,$E7 db $21,$C5,$16,$5F,$95,$95,$95,$95 db $95,$95,$95,$95,$97,$98,$78,$95 db $96,$95,$95,$97,$98,$97,$98,$95 db $7A db $21,$ED,$0E,$CF,$01,$09,$08,$05 db $24,$17,$12,$17,$1D,$0E,$17,$0D db $18 db $22,$4B,$0D,$01,$24,$19,$15,$0A db $22,$0E,$1B,$24,$10,$0A,$16,$0E db $22,$8B,$0D,$02,$24,$19,$15,$0A db $22,$0E,$1B,$24,$10,$0A,$16,$0E db $22,$EC,$04,$1D,$18,$19,$28 db $22,$F6,$01,$00 ;db $23,$C9,$56,$55 ;db $23,$E2,$04,$99,$AA,$AA,$AA ;db $23,$EA,$04,$99,$AA,$AA,$AA db $00back to listings