;============================================================================ ; Faxanadu (U).nes ; ; PRG15_MIRROR ($c000 - $ffff) ;============================================================================
.segment "PRG15_MIRROR" .ORG $c000
;============================================================================ ; Write attribute data for the HUD to the PPU. ; ; The attribute data will be written from ; HUD_ATTRIBUTE_DATA_BY_INDEX based on the ; UI_AttributeDataIndex set when loading the ; screen. ; ; INPUTS: ; UI_AttributeDataIndex: ; The index into the lookup table for the current ; screen. ; ; HUD_ATTRIBUTE_DATA_BY_INDEX: ; The lookup table of attribute data to write. ; ; OUTPUTS: ; PPUADDR: ; The updated PPU address. ; ; PPUDATA: ; The written data. ; ; CALLS: ; UI_DrawHUD ; ; XREFS: ; Screen_SetupNew ;============================================================================
[c000]UI_SetHUDPPUAttributes:; [$c000]
; ; Set the PPUADDR to 0x23C0, the top row where the status ; bar resides. ;
[c000]LDA #$23 [c002]STA a:PPUADDR; Set upper PPUADDR as 0x23. [c005]LDA #$c0 [c007]STA a:PPUADDR; Set upper PPUADDR as 0xC0. [c00a]LDX a:UI_AttributeDataIndex; X = HUD attribute data index, computed when setting up the screen. [c00d]LDA HUD_ATTRIBUTE_DATA_BY_INDEX,X; Load the value for the attribute data.
; ; Write 8 bytes based on data from the lookup table ; HUD_ATTRIBUTE_DATA_BY_INDEX at index ; UI_AttributeDataIndex. ;
[c010]LDX #$08; X = 8 (loop counter). [c012]@_writeLoop:; [$c012] [c012]STA a:PPUDATA; Write to the PPU. [c015]DEX; X-- [c016]BNE @_writeLoop; If X > 0, loop.
; ; Draw the HUD. ;
[c018]JMP UI_DrawHUD; Jump to draw the HUD.
;============================================================================ ; Attribute data used for the HUD. ; ; These are indexed by values from lookup table ; PALETTE_INDEX_TO_UI_BG_ATTRIBUTE_INDEX (stored in ; UI_AttributeDataIndex. ; ; Only values 0, 1, 2, and 3 are used. ; ; The rest seem to be unused, but many end up styled to ; better match regions of the game. ; ; XREFS: ; UI_SetHUDPPUAttributes ;============================================================================
; ; XREFS: ; UI_SetHUDPPUAttributes ;
[c01b]HUD_ATTRIBUTE_DATA_BY_INDEX:; [$c01b] [c01b].byte $00; [0]: Dartmoor, Castle of Fraternal, King Grieve's Room [c01c].byte $55; [1]: Start Screen [c01d].byte $aa; [2]: Most exterior areas. [c01e].byte $ff; [3]: Most interior areas. [c01f].byte $41; [4]: Here and below are unused. [c020].byte $20; [5]: [c021].byte $04; [6]: [c022].byte $07; [7]: [c023].byte $08; [8]: [c024].byte $09; [9]: [c025].byte $0a; [10]: [c026].byte $61; [11]: [c027].byte $20; [12]: [c028].byte $04; [13]: [c029].byte $0b; [14]: [c02a].byte $0c; [15]: [c02b].byte $0d; [16]: [c02c].byte $0e; [17]: [c02d].byte $56; [18]: [c02e].byte $20; [19]: [c02f].byte $03; [20]: [c030].byte $0f; [21]: [c031].byte $10; [22]: [c032].byte $11; [23]:
;============================================================================ ; DEADCODE ;============================================================================
[c033]DEADCODE_FUN_PRG15_MIRROR__c033:; [$c033] [c033]LDY #$00 [c035]LDA (Temp_Addr_L),Y [c037]STA PPU_TargetAddr [c039]INY [c03a]LDA (Temp_Addr_L),Y [c03c]STA PPU_TargetAddr_U [c03e]INY [c03f]LDA (Temp_Addr_L),Y [c041]PHA [c042]JSR PPUBuffer_QueueCommandOrLength [c045]PLA [c046]STA Temp_00 [c048]LDY #$03 [c04a]@LAB_PRG15_MIRROR__c04a:; [$c04a] [c04a]LDA (Temp_Addr_L),Y [c04c]STA PPUBuffer,X [c04f]INX [c050]INY [c051]DEC Temp_00 [c053]BNE @LAB_PRG15_MIRROR__c04a [c055]STX PPUBuffer_WriteOffset [c057]RTS
;============================================================================ ; Draw the player's state on the HUD UI. ; ; INPUTS: ; Player_MP: ; The player's current MP. ; ; OUTPUTS: ; None ; ; CALLS: ; UI_DrawTimeValue ; UI_DrawGoldValue ; UI_DrawPlayerExperience ; UI_DrawPlayerHP ; Player_SetMP ; PPUBuffer_DrawAll ; ; XREFS: ; UI_SetHUDPPUAttributes ;============================================================================
[c058]UI_DrawHUD:; [$c058] [c058]LDA #$00 [c05a]JSR UI_DrawTimeValue; Draw the time value. [c05d]JSR UI_DrawGoldValue; Draw the gold value. [c060]JSR PPUBuffer_DrawAll; Flush to the PPU. [c063]JSR UI_DrawPlayerExperience; Draw the player experience value. [c066]JSR PPUBuffer_DrawAll; Flush to the PPU. [c069]JSR PPUBuffer_DrawAll; Flush to the PPU. [c06c]JSR UI_DrawPlayerHP; Draw the player's HP bar. [c06f]JSR PPUBuffer_DrawAll; Flush to the PPU. [c072]LDA a:Player_MP; Load the player's MP. [c075]JSR Player_SetMP; Set it and draw. [c078]JMP PPUBuffer_DrawAll; Flush to the PPU.
;============================================================================ ; Increase the player's HP. ; ; This will be capped to 80HP. ; ; INPUTS: ; A: ; The number of health points to add. ; ; Player_HP_U: ; The upper byte of player health to add to. ; ; OUTPUTS: ; Player_HP_U: ; The new upper byte of player health. ; ; CALLS: ; UI_DrawPlayerHPValue ; ; XREFS: ; Player_HandleTouchBread ; Player_FillHPAndMP ; Player_UseRedPotion ;============================================================================
[c07b]Player_AddHP:; [$c07b]
; ; Update the player's HP with the provided value. ;
[c07b]CLC; Clear carry so it's not added. [c07c]ADC a:Player_HP_U; Add the provided HP to the player's health. [c07f]STA a:Player_HP_U; Store as the new health.
; ; Check against the cap. ;
[c082]CMP #$50; Cap at 80 HP. [c084]BCC @_drawHP; Jump if it's under. [c086]LDA #$50; Else, cap it to 80HP. [c088]STA a:Player_HP_U; Store as the new health. [c08b]@_drawHP:; [$c08b] [c08b]JMP UI_DrawPlayerHPValue
;============================================================================ ; Decrease the player's HP. ; ; If the HP hits <= 0, and an elixir is in the inventory, it will be used. ; If it hits <= 0 and there's no elixir, the player will die. ; ; INPUTS ; Player_HP_U: ; Player_HP_L: ; The player's current health. ; ; Arg_PlayerHealthDelta_U: ; Arg_PlayerHealthDelta_L: ; The health to add. ; ; SpecialItems: ; The player's current special items. ; ; OUTPUTS: ; Player_HP_U: ; Player_HP_L: ; The new player health. ; ; PlayerIsDead: ; Set to 1 if the player dies. ; ; CALLS: ; UI_DrawPlayerHP ; Player_UseElixir ; ; XREFS: ; Player_ApplyDamage ; Player_HandleHitByMagic ; SpriteBehavior_FlashScreenHitPlayer ;============================================================================
[c08e]Player_ReduceHP:; [$c08e]
; ; Reduce the lower byte of player health. ;
[c08e]LDA a:Player_HP_L; Load the lower byte of the player's health. [c091]SEC [c092]SBC a:Arg_PlayerHealthDelta_L; Subtract the specified lower byte of health. [c095]STA a:Player_HP_L; Store it as the new player health.
; ; Reduce the upper byte of player health. ;
[c098]LDA a:Player_HP_U; Load the upper byte of the player's health. [c09b]SBC a:Arg_PlayerHealthDelta_U; Subtract the specified upper byte of health. [c09e]STA a:Player_HP_U; Store it as the new player health. [c0a1]BCS @_isStillAlive; If there's still health left, the player is still alive.
; ; The player is out of health. They may die. ;
[c0a3]LDA #$00 [c0a5]STA a:Player_HP_U; Set the player's upper byte of health to 0. [c0a8]JSR UI_DrawPlayerHP; Draw the health.
; ; Check if the player has an elixir before killing them. ;
[c0ab]LDA a:SpecialItems; Load the player's special items. [c0ae]AND #$08; Check if the player has an elixir. [c0b0]BEQ @_killPlayer; If not, kill the player.
; ; The player has an elixir. Fill up their health instead ; of killing the player. ;
[c0b2]JMP Player_UseElixir; Use the Elixir.
; ; Mark the player as dead. ;
[c0b5]@_killPlayer:; [$c0b5] [c0b5]LDA #$01; Mark the player as dead. [c0b7]STA a:PlayerIsDead; Store that.
; ; Draw the player's health. ;
[c0ba]@_isStillAlive:; [$c0ba] [c0ba]JMP UI_DrawPlayerHP; Draw player health.
;============================================================================ ; Draw the player's health. ; ; INPUTS: ; Player_HP_U: ; The full value of the player's health. ; ; OUTPUTS: ; None ; ; CALLS: ; UI_DrawPlayerHPValue ; ; XREFS: ; Player_ReduceHP ; Player_UseHourGlass ; UI_DrawHUD ;============================================================================
[c0bd]UI_DrawPlayerHP:; [$c0bd] [c0bd]LDA a:Player_HP_U; Load the player's current health. [c0c0]JMP UI_DrawPlayerHPValue; Draw it.
;============================================================================ ; Decrement the MP for a spell. ; ; This will look at the selected magic type and deduct the ; cost from the player's total MP if there's enough to cast. ; ; INPUTS: ; Player_MP: ; The player's current MP. ; ; SelectedMagic: ; The selected magic. ; ; MAGIC_COSTS: ; The table of magic costs per spell. ; ; OUTPUTS: ; C: ; 0 = Cost deducted for the spell. ; 1 = Not enough MP for the spell. ; ; Player_MP: ; The new MP amount. ; ; CALLS: ; Player_SetMP ; ; XREFS: ; Player_CastMagic ;============================================================================
[c0c3]Player_ReduceMP:; [$c0c3] [c0c3]LDX a:SelectedMagic; Load the selected magic spell. [c0c6]LDA a:Player_MP; Load the player's total MP. [c0c9]SEC [c0ca]SBC MAGIC_COSTS,X; Look up the cost of this spell. [c0cd]BCC @_notEnoughMP; If there's not enough MP for the spell, then jump.
; ; The player has enough MP for the spell. ;
[c0cf]STA a:Player_MP; Else, store the reduced MP. [c0d2]JSR Player_SetMP; Set that on the player and draw. [c0d5]CLC; Clear the carry flag to indicate a success. [c0d6]RTS
; ; The player does not have enough mana. ;
[c0d7]@_notEnoughMP:; [$c0d7] [c0d7]SEC; Set the carry flag to 1 to indicate not enough MP. [c0d8]RTS
;============================================================================ ; Add points to the player's MP. ; ; This will be capped to 80 points. ; ; INPUTS: ; A: ; The MP to add. ; ; Player_MP: ; The player's current MP. ; ; OUTPUTS: ; Player_MP: ; The new MP. ; ; CALLS: ; Player_SetMP ; ; XREFS: ; Player_FillHPAndMP ;============================================================================
[c0d9]Player_AddMP:; [$c0d9]
; ; Add the provided MP to the player's MP. ;
[c0d9]CLC; Clear carry so it's not added. [c0da]ADC a:Player_MP; Add to the player's MP. [c0dd]STA a:Player_MP; Store it. [c0e0]CMP #$50; Check if it's > 80 points. [c0e2]BCC @_drawMP; If we're under the cap, return.
; ; Cap the MP to the total allowed amount. ;
[c0e4]LDA #$50; Cap to 80 points.
; ; Store it. ;
[c0e6]STA a:Player_MP [c0e9]@_drawMP:; [$c0e9] [c0e9]JMP Player_SetMP; Set the player's mana points and draw.
;============================================================================ ; NOTE: This is used exclusively by the Unknown 29 sprite behavior. ; ; XREFS: ; SpriteBehavior_Unknown_29_SomeSetup ;============================================================================
[c0ec]Player_Something_ChangeHP:; [$c0ec] [c0ec]TXA [c0ed]PHA [c0ee]LDA #$00 [c0f0]STA a:Temp1_SomethingChangedHP [c0f3]STA a:Temp2_SomethingChangedHP [c0f6]LDX #$10 [c0f8]ROL a:Arg_PlayerHealthDelta_L [c0fb]ROL a:Arg_PlayerHealthDelta_U [c0fe]@LAB_PRG15_MIRROR__c0fe:; [$c0fe] [c0fe]ROL a:Temp1_SomethingChangedHP [c101]ROL a:Temp2_SomethingChangedHP [c104]LDA a:Temp1_SomethingChangedHP [c107]CMP a:SpriteBehaviorUnknown20_SomethingXOrY [c10a]LDA a:Temp2_SomethingChangedHP [c10d]SBC a:BYTE_04bf [c110]BCC @LAB_PRG15_MIRROR__c124 [c112]LDA a:Temp1_SomethingChangedHP [c115]SBC a:SpriteBehaviorUnknown20_SomethingXOrY [c118]STA a:Temp1_SomethingChangedHP [c11b]LDA a:Temp2_SomethingChangedHP [c11e]SBC a:BYTE_04bf [c121]STA a:Temp2_SomethingChangedHP [c124]@LAB_PRG15_MIRROR__c124:; [$c124] [c124]ROL a:Arg_PlayerHealthDelta_L [c127]ROL a:Arg_PlayerHealthDelta_U [c12a]DEX [c12b]BNE @LAB_PRG15_MIRROR__c0fe [c12d]PLA [c12e]TAX [c12f]RTS
;============================================================================ ; Clear sprite state. ; ; This will clear out all the current sprites and the ; selected weapon, and reset the screen color mode. ; ; INPUTS: ; PPU_Mask: ; The current screen color mode. ; ; OUTPUTS: ; CurrentSprites_Entities: ; The cleared sprite entities. ; ; CurrentSprites_HP: ; The cleared sprite HPs. ; ; CurrentSprites_HitCounter: ; The cleared sprite hit counters. ; ; CurrentSprites_Behaviors: ; The cleared sprite subtypes. ; ; PPU_Mask: ; The new color mode with greyscale removed. ; ; IScript_PortraitID: ; Set to 0xFF. ; ; CALLS: ; CurrentSprite_ResetPPUTileOffset ; ; XREFS: ; EndGame_Begin ; Game_MainLoop ; Player_HandleDeath ; Screen_SetupSprites ;============================================================================
[c130]Screen_ClearSprites:; [$c130] [c130]LDX #$07; X = 7 (loop counter -- looping 8 times) [c132]@_clearSpritesLoop:; [$c132] [c132]LDA #$ff [c134]STA CurrentSprites_Entities,X; Set sprite to 0xFF (cleared). [c137]LDA #$00 [c139]STA CurrentSprites_HP,X; Set sprite hit points to 0. [c13c]STA CurrentSprites_HitCounter,X; Set sprite hit counter to 0. [c13f]STA CurrentSprites_Behaviors,X; Set sprite subtype to 0. [c142]DEX; X-- [c143]BPL @_clearSpritesLoop; If we're not done, loop.
; ; We're out of the loop. Reset the PPU draw offset and restore ; screen state. ;
[c145]JSR CurrentSprite_ResetPPUTileOffset; Reset the PPU offset. [c148]LDA #$ff [c14a]STA a:IScript_PortraitID; Clear any portrait shown on screen. [c14d]LDA PPU_Mask; Load the current screen color mode. [c14f]AND #$fe; Set to color mode (clear bit 1) [c151]STA PPU_Mask; Store the new color mode. [c153]RTS
;============================================================================ ; Load extra information about the contents of the screen. ; ; This may include data such as sprite values, NPC IScript ; entrypoints, and special screen events (such as boss kill ; triggers). ; ; INPUTS: ; Area_CurrentArea: ; The area being loaded. ; ; Area_CurrentScreen: ; The screen being loaded within the area. ; ; AREA_SPRITE_ADDRESSES: ; The table of areas to screen lists. ; ; OUTPUTS: ; Sprites_ReadInfoAddr: ; The starting read address for the screen info. ; ; CurrentScreen_SpecialEventID: ; The screen's loaded special event ID (0xFF if ; there is none). ; ; Screen_ExtraInfoAddr: ; Temp_Addr_L: ; Used internally and clobbered. ; ; CALLS: ; Screen_LoadSpecialEventID ; ; XREFS: ; Screen_LoadSpriteInfo ;============================================================================
[c154]Screen_LoadAllScreenInfo:; [$c154]
; ; Set the address of the sprites-for-screen lookup table ; for the area. ;
[c154]LDA Area_CurrentArea; A = Current area index. [c156]ASL A; Convert to a word offset for the lookup table.
; ; Set the starting address of the screen data. ;
[c157]TAY; Y = A [c158]LDA AREA_SPRITE_ADDRESSES,Y; Load the upper byte of the address. [c15b]STA Temp_Addr_L; Store for reading. [c15d]LDA AREA_SPRITE_ADDRESSES+1,Y; Load the lower byte of thea ddress. [c160]STA Temp_Addr_U; Store it.
; ; Check the first byte of the address. If 0xFF, ; nothing will be loaded. ;
[c162]LDY #$01; Y = 1 [c164]LDA (Temp_Addr_L),Y; Load the address for the screens list. [c166]CMP #$ff; Is it 0xFF? [c168]BEQ RETURN_C1B3; If so, there's nothing to load. Return.
; ; Set the start address for a screen's sprite and metadata ; information. ;
[c16a]LDA Area_CurrentScreen; A = Current screen index. [c16c]ASL A; Convert to a word offset for the lookup table. [c16d]TAY; Y = A
; ; Read the address for the screen information. ;
[c16e]LDA (Temp_Addr_L),Y; Load the lower byte of the screen address. [c170]STA Sprites_ReadInfoAddr; Store it as the lower byte of the of the sprites read address. [c172]INY; Y++ [c173]LDA (Temp_Addr_L),Y; Load the upper byte. [c175]STA Sprites_ReadInfoAddr_U; And store it.
; ; Find the end of the sprites list for the screen. ; ; Sprites are in 2-byte pairs of (Entity ID, Sprite Value). ; This will skip those bytes until the end of the list is ; hit. ;
[c177]LDY #$00; Y = 0 [c179]@_readLoop:; [$c179] [c179]LDA (Sprites_ReadInfoAddr),Y; Load the next byte from the screen data. [c17b]CMP #$ff; Is it 0xFF? [c17d]BNE @_prepareNextLoopIter; If not, jump to prepare for the next loop
; ; The sprite information is 0xFF, so we're done looping. ; Read the screen information. ;
[c17f]INY; Y++ [c180]TYA; A = Y [c181]CLC [c182]ADC Sprites_ReadInfoAddr; Increment the read address by 2 bytes. [c184]STA Screen_ExtraInfoAddr [c186]LDA Sprites_ReadInfoAddr_U; Load the upper byte. [c188]ADC #$00; Add carry, if lower byte overflowed. [c18a]STA Screen_ExtraInfoAddr_U; Store as the new upper byte. [c18c]JMP Screen_LoadSpecialEventID; Read the extra screen information.
; ; Increment by 2 (the sprite entity and value). ;
[c18f]@_prepareNextLoopIter:; [$c18f] [c18f]INY; Y++ [c190]INY; Y++ [c191]BNE @_readLoop; If Y > 0, loop.
; ; v-- Fall through --v ;
;============================================================================ ; Clear the special event ID for the current screen. ; ; This disables any custom logic specific to certain screens. ; ; INPUTS: ; None. ; ; OUTPUTS: ; CurrentScreen_SpecialEventID: ; Set to 0xFF (unset). ; ; XREFS: ; Screen_LoadSpecialEventID ;============================================================================
[c193]Screen_SetNoSpecialEventID:; [$c193]
; ; No special event information was found for the screen. ; Clear that state (set to 0xFF). ;
[c193]LDA #$ff [c195]STA a:CurrentScreen_SpecialEventID; Clear the special event ID (0xFF). [c198]RTS
;============================================================================ ; Load extra information about the current screen. ; ; Screens may contain a special event ID, which adds custom ; logic to perform on each tick on a screen. This is used ; for three things: ; ; 1. Managing the pushable block status on the path to ; Mascon. ; 2. Boss battle logic. ; 3. Final boss logic. ; ; INPUTS: ; Screen_ExtraInfoAddr: ; Screen_ExtraInfoAddr+1: ; The address containing the extra screen ; information. ; ; OUTPUTS: ; CurrentScreen_SpecialEventID: ; The special event ID loaded from the screen info, ; or 0xFF (unset) if not found. ; ; XREFS: ; Screen_LoadAllScreenInfo ;============================================================================
[c199]Screen_LoadSpecialEventID:; [$c199]
; ; Begin scanning for a 0xFF in the screen information. This ; will indicate the start of extra information for the screen ; (special screen event states, IScripts entrypoint references). ;
[c199]LDY #$00; Y = 0 (loop counter) [c19b]@_scanLoop:; [$c19b] [c19b]LDA (Screen_ExtraInfoAddr),Y; Load the next byte in the screen info data. [c19d]CMP #$ff; Is it 0xFF? [c19f]BNE @_noFFMarker; If so, we found the start of the extra data to load. Jump.
; ; We found the start of the extra info for the screen. We're ; looking for 0x80, which indicates special event code will ; run on this screen every tick. The type of event will be ; indicated in the following byte. ; ; An example of this would be running code when a boss is ; defeated, producing an item. ;
[c1a1]INY; Y++ [c1a2]LDA (Screen_ExtraInfoAddr),Y; Load the next byte to see what we're working with. [c1a4]CMP #$80; Is it 0x80? [c1a6]BNE Screen_SetNoSpecialEventID; If not, then we won't be running special event code on this screen.
; ; This screen runs special event code. Load it. ;
[c1a8]INY; Y++ [c1a9]LDA (Screen_ExtraInfoAddr),Y; Load the next byte containing the special event ID. [c1ab]STA a:CurrentScreen_SpecialEventID; Store it while on the screen. [c1ae]RTS [c1af]@_noFFMarker:; [$c1af] [c1af]INY; Y++
; ; If we hit 256 loops, we're done. ;
[c1b0]BNE @_scanLoop; If we haven't wrapped, then loop. [c1b2]RTS; Else, we're done.
; ; XREFS: ; Screen_LoadAllScreenInfo ;
[c1b3]RETURN_C1B3:; [$c1b3] [c1b3]RTS
;============================================================================ ; Load information on a sprite from the screens list. ; ; This will read the next sprite entity ID in the screen ; information, followed by the block X/Y position where ; the sprite should be placed. ; ; INPUTS: ; CurrentROMBank: ; The current ROM bank. ; ; Sprites_ReadInfoAddr: ; Sprites_ReadInfoAddr+1: ; The address in the screens table to read from. ; ; OUTPUTS: ; CurrentSprite_Entity: ; The loaded sprite entity ID. ; ; CurrentSprite_XPos: ; The loaded block X position of the sprite. ; ; CurrentSprite_YPos: ; The loaded block Y position of the sprite. ; ; Sprites_ReadInfoAddr: ; Sprites_ReadInfoAddr+1: ; The updated address in the screens table to read ; from. ; ; CALLS: ; Screen_LoadAllScreenInfo ; Sprites_PopulateNextAvailableSprite ; MMC1_UpdateROMBank ; MMC1_UpdatePRGBankToStackA ; ; XREFS: ; Game_MainLoop ; Screen_SetupSprites ;============================================================================
[c1b4]Screen_LoadSpriteInfo:; [$c1b4]
; ; Switch to ROM bank 11, where sprite information lives. ;
[c1b4]LDA a:CurrentROMBank; Load the current ROM bank. [c1b7]PHA; Push it to the stack. [c1b8]LDX #$0b [c1ba]JSR MMC1_UpdateROMBank; Switch to bank 11.
; ; Load all the sprites for this screen. ;
[c1bd]JSR Screen_LoadAllScreenInfo; Load the sprite information for the screen.
; ; Switch bank to our previous bank. ;
[c1c0]PLA; Pop A (our saved bank). [c1c1]TAX; X = A [c1c2]JSR MMC1_UpdateROMBank; And switch back to it.
; ; Begin our loop for reading all sprites for the screen. ;
[c1c5]@_readSpriteLoop:; [$c1c5] [c1c5]LDY #$00; Y = 0 (info read offset)
; ; Switch back to ROM bank 11 (sprite info). ;
[c1c7]LDA a:CurrentROMBank; A = current ROM bank [c1ca]PHA; Push to the stack. [c1cb]LDX #$0b [c1cd]JSR MMC1_UpdateROMBank; Switch to bank 11.
; ; Read information on the next sprite on the screen. ;
[c1d0]LDA (Sprites_ReadInfoAddr),Y; Read the current byte of the screen information. [c1d2]CMP #$ff; Is it 0xFF (our end-of-sprites list)? [c1d4]BEQ @_restoreBankAndReturn; If it is, we're done. Restore our bank and return.
; ; Store the sprite's entity ID. ;
[c1d6]STA a:CurrentSprite_Entity; Store this byte as the sprite entity ID.
; ; Load the sprite's Y block position. ; ; This will be the upper nibble of the byte following the ; sprite entity ID. ;
[c1d9]INY; Y++ [c1da]LDA (Sprites_ReadInfoAddr),Y; Load the next byte. [c1dc]AND #$f0; Take only the upper nibble. [c1de]STA a:CurrentSprite_YPos; Store it as the sprite's starting Y position.
; ; Load the sprite's X block position. ; ; This will be the lower nibble, which will then be placed ; in the upper nibble. ;
[c1e1]LDA (Sprites_ReadInfoAddr),Y; Load the same byte. [c1e3]ASL A; Shift the X coordinate from the lower nibble to the upper nibble. [c1e4]ASL A [c1e5]ASL A [c1e6]ASL A [c1e7]STA a:CurrentSprite_XPos; Store as the sprite's X position.
; ; Restore our bank. ;
[c1ea]PLA; Pull A (the saved bank) from the stack. [c1eb]TAX; X = A [c1ec]JSR MMC1_UpdateROMBank; And switch back to it.
; ; Finish processing this sprite. We'll add to the next ; available slot and fill in any information needed based ; on what we loaded. ;
[c1ef]JSR Sprites_PopulateNextAvailableSprite; Add this sprite to the next available slot.
; ; Increment the read address for the next sprite in the ; screen data. ;
[c1f2]LDA Sprites_ReadInfoAddr; A = lower byte of the screen information address [c1f4]CLC [c1f5]ADC #$02; Increment by 2 (sprite entity ID and position). [c1f7]STA Sprites_ReadInfoAddr; Store as the new lower byte of the address. [c1f9]LDA Sprites_ReadInfoAddr_U; A = upper byte of the screen information address [c1fb]ADC #$00; Add carry if the lower byte overflowed. [c1fd]STA Sprites_ReadInfoAddr_U; Store it as the new upper byte of the address. [c1ff]JMP @_readSpriteLoop; Loop to read the next sprite. [c202]@_restoreBankAndReturn:; [$c202] [c202]JMP MMC1_UpdatePRGBankToStackA
;============================================================================ ; Populate the loaded sprite in the next available slot. ; ; This will set the default state of the sprite, the ; sprite's HP, hitbox type, subtype, and behavior ; addresses. ; ; If the sprite can't fit on the screen, it will not be ; loaded. ; ; INPUTS: ; CurrentSprite_Entity: ; The loaded sprite entity ID. ; ; CurrentSprite_XPos: ; The loaded block X position. ; ; CurrentSprite_YPos: ; The loaded block Y position. ; ; OUTPUTS: ; CurrentSprites_Entities: ; The updated sprite entities. ; ; CurrentSprites_Flags: ; The updated sprite flags. ; ; CurrentSprites_Phases: ; The updated sprite phases. ; ; CurrentSprites_HitByMagicBehavior: ; The updated behaviors when hit by magic. ; ; CurrentSprites_XPos_Full: ; The updated sprite X positions. ; ; CurrentSprites_YPos: ; The updated sprite Y positions. ; ; CurrentSprites_HitBoxTypes: ; The updated sprite hit box types. ; ; CurrentSprites_HP: ; The updated sprite HPs. ; ; CurrentSprites_BehaviorAddrs_L: ; The updated sprite behavior address lower bytes. ; ; CurrentSprites_BehaviorAddrs_U: ; The updated sprite behavior address upper bytes. ; ; CurrentSprites_Behaviors: ; The updated sprite subtypes. ; ; CALLS: ; Sprites_LoadSpriteValue ; ; XREFS: ; Screen_LoadSpriteInfo ;============================================================================
[c205]Sprites_PopulateNextAvailableSprite:; [$c205]
; ; Prepare the sprite loop. ;
[c205]LDX #$07; 7 = loop counter [c207]@_loop:; [$c207] [c207]STX a:CurrentSpriteIndex; Set it [c20a]LDA CurrentSprites_Entities,X; Load the sprite entity at this index. [c20d]CMP #$ff; Is it 0xFF (unset)? [c20f]BNE @_prepareNextIter; If not, jump to prepare for next loop.
; ; This sprite index is unset. Populate it. ;
[c211]LDA #$00; 0 = unset [c213]STA CurrentSprites_Phases,X; Clear the sprite phase. [c216]STA CurrentSprites_Flags,X; Clear the sprite flags. [c219]LDA #$ff; 0xFF = unset [c21b]STA CurrentSprites_HitByMagicBehavior,X; Clear the hit by magic behavior.
; ; Set the sprite's starting X/Y position. ;
[c21e]LDA a:CurrentSprite_XPos; Load the current sprite X position to set. [c221]STA CurrentSprites_XPos_Full,X; Set for the sprite index. [c223]LDA a:CurrentSprite_YPos; Load the Y position. [c226]STA CurrentSprites_YPos,X; Set for the sprite index. [c228]LDA a:CurrentSprite_Entity; Load the sprite entity. [c22b]STA CurrentSprites_Entities,X; Set it. [c22e]TAY; Y = A (entity) [c22f]LDA SPRITE_ENTITIES_HITBOX_TYPES,Y; Load the hitbox type for the entity. [c232]STA CurrentSprites_HitBoxTypes,X; Set it. [c235]LDA SPRITE_ENTITIES_HP,Y; Load the HP for the entity. [c238]STA CurrentSprites_HP,X; Set it.
; ; Prepare the behavior scripts for the entity. ;
[c23b]TYA; A = Y (entity) [c23c]ASL A; Convert to a word boundary. [c23d]TAY; Y = A [c23e]LDA SPRITE_BSCRIPTS,Y; Load the lower byte of the behavior script for the entity. [c241]STA CurrentSprites_BehaviorAddrs_L,X; Set the lower byte of the address. [c244]LDA SPRITE_BSCRIPTS+1,Y; Load the upper byte. [c247]STA CurrentSprites_BehaviorAddrs_U,X; Set the upper byte. [c24a]LDA #$ff; A = 0xFF (unset) [c24c]STA CurrentSprites_Behaviors,X; Clear the current behavior state. [c24f]JMP Sprites_LoadSpriteValue; Load the value for the sprite and return. [c252]@_prepareNextIter:; [$c252] [c252]LDX a:CurrentSpriteIndex; X = current sprite index. [c255]DEX; X-- [c256]BPL @_loop; If >= 0, loop. [c258]RTS
;============================================================================ ; Banks storing the images for a range of sprites. ; ; XREFS: ; Sprites_StoreBankForCurrentSprite ;============================================================================
; ; XREFS: ; Sprites_StoreBankForCurrentSprite ;
[c259]SPRITE_IMAGE_BANKS:; [$c259] [c259].byte $06; [0]: Bank for sprites 0-54
; ; XREFS: ; Sprites_StoreBankForCurrentSprite ;
[c25a]SPRITE_IMAGE_BANKS_1_:; [$c25a] [c25a].byte $07; [1]: Bank for sprites 55-100
;============================================================================ ; Load the value for a sprite from the screen's sprite info table. ; ; This loads the value information associated with a sprite ; on the screen. This may represent a message ID or some ; other value, depending on the sprite and situation. ; ; INPUTS: ; X: ; The sprite index to load the value for, ; ; CurrentROMBank: ; The currently-loaded ROM bank. ; ; Screen_ExtraInfoAddr: ; Screen_ExtraInfoAddr+1: ; The current address for the extra sprite values ; on the screen. ; ; OUTPUTS: ; CurrentSprites_Values: ; The updated sprite values. ; ; Screen_ExtraInfoAddr: ; Screen_ExtraInfoAddr+1: ; The incremented address for the extra sprite ; values on the screen. ; ; CALLS: ; MMC1_UpdateROMBank ; MMC1_UpdatePRGBankToStackA ; ; XREFS: ; Sprites_PopulateNextAvailableSprite ;============================================================================
[c25b]Sprites_LoadSpriteValue:; [$c25b] [c25b]TXA; A = X (sprite index) [c25c]TAY; Y = A
; ; Save the current ROM bank and switch to bank 11. ;
[c25d]LDA a:CurrentROMBank; Get the current ROM bank. [c260]PHA; Push to the stack. [c261]LDX #$0b; X = 11 (bank) [c263]JSR MMC1_UpdateROMBank; Switch to bank 11.
; ; Restore our sprite index to X so it's not clobbered ; when this returns. ;
[c266]TYA; A = Y [c267]TAX; X = A
; ; Load the extra information for this sprite and store it ; in the sprite list. ;
[c268]LDY #$00; Y = 0 [c26a]LDA (Screen_ExtraInfoAddr),Y; A = Extra information for the sprite. [c26c]STA CurrentSprites_Values,X; Store in CurrentSprites_Values. [c26f]CMP #$ff; Is it 0xFF (unset)? [c271]BEQ @_restoreBankAndReturn; If so, we're done. Jump.
; ; Increment the position in the info data. ;
[c273]INC Screen_ExtraInfoAddr; Increment the lower byte of the offset in the extra info data. [c275]BNE @_restoreBankAndReturn; If this didn't wrap to 0, we're done. Jump. [c277]INC Screen_ExtraInfoAddr_U; Else, increment the upper byte as well. [c279]@_restoreBankAndReturn:; [$c279] [c279]JMP MMC1_UpdatePRGBankToStackA; Restore the bank saved to the stack.
;============================================================================ ; Determine and store the bank for images for the current sprite. ; ; This will determine which bank would store images for the ; currently-processed sprite. ; ; If the sprite entity ID is 0-54, this will be bank 6. ; ; If the sprite entity ID is 55-100, this will be bank 7. ; ; INPUTS: ; CurrentSprite_Entity: ; The entity ID for the currently-processed sprite. ; ; SPRITE_IMAGE_BANKS: ; The lookup table for sprite entity IDs to banks. ; ; OUTPUTS: ; CurrentSprite_TilesBank: ; The bank for the images for this sprite. ; ; XREFS: ; GameLoop_LoadSpriteImages ;============================================================================
[c27c]Sprites_StoreBankForCurrentSprite:; [$c27c] [c27c]LDY #$00; Y = 0 [c27e]LDA a:CurrentSprite_Entity; A = current sprite entity [c281]CMP #$37; Is it < sprite 55 (Unused Child sprite)? [c283]BCC @_loadBank; If yes, then load bank from table at index 0. [c285]INY; Else, load bank from table at index 1.
; ; Load the image for the bank. ;
[c286]@_loadBank:; [$c286] [c286]LDA SPRITE_IMAGE_BANKS,Y; Load the address for the sprite entity. [c289]STA a:CurrentSprite_TilesBank; Store as the bank for this sprite. [c28c]@_return:; [$c28c] [c28c]RTS
;============================================================================ ; Load images for all sprites on the scren. ; ; This will loop through each sprite and load the images, ; drawing them in the PPU buffer. ; ; If any sprites become off-screen (vertically), they'll be ; removed. ; ; As a special case, if the sprite is the boss Pakukame, a ; Lilith will be spawned. ; ; INPUTS: ; CurrentSpriteIndex: ; The current sprite index at the start of this ; call. ; ; CurrentSprites_Entities: ; The list of sprite entities for all sprites on ; screen. ; ; OUTPUTS: ; CurrentSprites_Entities: ; Updated if sprites are removed or added. ; ; CurrentSprites_PPUOffsets: ; PPU tile offsets for sprite data. ; ; CurrentSprite_Entity: ; Clobbered. ; ; CALLS: ; CurrentSprite_ResetPPUTileOffset ; PPUBuffer_WaitUntilClear ; Sprites_LoadImageForCurrentSprite ; Sprites_StoreBankForCurrentSprite ; ; XREFS: ; Game_MainLoop ; Game_SetupAndLoadOutsideArea ; Game_SetupEnterBuilding ; Game_SetupEnterScreen ; Game_SetupExitBuilding ; Game_SetupNewArea ; Game_Start ; IScripts_ClearPortraitImage ;============================================================================
[c28d]GameLoop_LoadSpriteImages:; [$c28d]
; ; Save state and prepare for image loading. ;
[c28d]LDA a:CurrentSpriteIndex; A = current sprite index [c290]PHA; Push it to the stack. [c291]JSR CurrentSprite_ResetPPUTileOffset; Reset the PPU drawing coordinates.
; ; Begin our loop for all 7 sprite slots. ;
[c294]LDX #$07; X = 7 (sprite index counter)
; ; Set this as the currently-processed sprite, and update ; the entity. ; ; We'll only process if the entity is not unset. ;
[c296]@_loop:; [$c296] [c296]STX a:CurrentSpriteIndex; Store it as the currently-processed sprite index. [c299]LDA CurrentSprites_Entities,X; Load the associated sprite entity ID. [c29c]CMP #$ff; Is it 0xFF (unset)? [c29e]BEQ @_nextLoop; If so, jump and prepare for the next loop. [c2a0]STA a:CurrentSprite_Entity; Store this as the entity at this index.
; ; Load the image for this sprite. ;
[c2a3]JSR Sprites_StoreBankForCurrentSprite; Store the image bank for the current sprite. [c2a6]JSR Sprites_LoadImageForCurrentSprite; Load the sprite's image from the bank.
; ; Special-case the Pakukame boss. If that was placed, then ; replace it with a Lilith instead. They spawn Liliths. ;
[c2a9]LDA a:CurrentSprite_Entity; Load the current entity. [c2ac]CMP #$30; Is it the Pakukame boss? [c2ae]BNE @_loadSpriteInfo; If not, jump.
; ; This is the Pakukame. Swap in a Lilith and load the image. ; ; This will then restore the entity back to Pakukame. ; Effectively, each Lilith on screen seems to be backed by ; a Pakukame. ;
[c2b0]LDA a:CurrentSprite_Entity; Load the Pakukame entity ID [c2b3]PHA; Push to the stack. [c2b4]LDA CurrentSprite_StartPPUTileOffset; Load the sprite's starting PPU tile offset. [c2b6]PHA; Push it to the stack. [c2b7]LDA #$09; A = 9 (Lilith) [c2b9]STA a:CurrentSprite_Entity; Store as the new entity. [c2bc]JSR Sprites_StoreBankForCurrentSprite; Store the bank for Lilith. [c2bf]JSR Sprites_LoadImageForCurrentSprite; Load Lilth's image from the bank. [c2c2]PLA; Pop the PPU offset row. [c2c3]STA CurrentSprite_StartPPUTileOffset; Store it again. [c2c5]PLA; Pop the current sprite entity. [c2c6]STA a:CurrentSprite_Entity; Store it again.
; ; Store the starting PPU tile offset for the sprite. ; ; This will be stored in this sprite's slot in the current ; sprites information. ;
[c2c9]@_loadSpriteInfo:; [$c2c9] [c2c9]LDX a:CurrentSpriteIndex; X = current sprite index [c2cc]LDA CurrentSprite_StartPPUTileOffset; A = Start PPU tile offset [c2ce]STA CurrentSprites_PPUOffsets,X; Set this in the PPU tile offsets for this sprite.
; ; Check if the sprite is on-screen or off-screen. ; ; If the Y position is off the screen (row 30 or higher), ; then the sprite will be unset. ;
[c2d1]LDA CurrentSprites_YPos,X; Load the Y position for this sprite. [c2d3]CMP #$f0; Is it less than 0xF0? [c2d5]BCC @_nextLoop; If so, loop.
; ; It's off-screen. Unset it. ;
[c2d7]LDA #$ff; Else, it's off the screen. [c2d9]STA CurrentSprites_Entities,X; Unset the entity.
; ; Done with any loading required for the sprite. Prepare for ; the next loop, if needed. ;
[c2dc]@_nextLoop:; [$c2dc] [c2dc]LDX a:CurrentSpriteIndex; X = current sprite index (loop counter). [c2df]DEX; X-- [c2e0]BPL @_loop; If X >= 0, loop.
; ; We're done. Restore the original current sprite index and ; wait until everything's drawn to the screen. ;
[c2e2]PLA; Pull A (current sprite index) from the stack. [c2e3]STA a:CurrentSpriteIndex; Store that back as the current sprite index. [c2e6]JMP PPUBuffer_WaitUntilClear; Wait until everything's drawn to the screen.
;============================================================================ ; Update a magic spell and run a magic-dependent handler. ; ; This is called for every tick of a magic spell. It's put ; on the stack by CastMagic_RunSpellHandler. ; ; This will update the visibility of the magic, set ; X/Y positions factoring in the screen scroll, and ; then set some state that ultimately is not used by ; the production build of Faxanadu. ; ; It will then call a magic-dependent update handler. ; ; INPUTS: ; CastMagic_Type: ; The magic type that was cast. ; ; CastMagic_XPos_Full: ; The X position of the magic. ; ; CastMagic_YPos_Full: ; The Y position of the magic. ; ; Screen_Maybe_ScrollXCounter: ; Player_Something_ScrollPosY: ; Referenced but ultimately unused. ; ; OUTPUTS: ; Arg_DrawSprite_PosX: ; Arg_DrawSprite_PosY: ; X and Y coordinates for the magic, used ; for appearance management. ; ; Unused_Sprite_ScrollPosX: ; Unused_Sprite_ScrollPosY: ; Set but unused. ; ; CALLS: ; CastMagic_CalculateVisibility ;============================================================================
[c2e9]CastMagic_RunUpdateSpellHandler:; [$c2e9]
; ; These two lines are deadcode. It might be the developers ; attempting to hard-code Deluge and Thunder during testing? ; Each gets overriden immediately. ;
[c2e9]LDA #$00; A = 0 (deadcode) [c2eb]LDA #$01; A = 1 (deadcode) [c2ed]LDA a:CastMagic_Type; A = Cast magic type
; ; If 0xFF, return. ;
[c2f0]BMI @_return
; ; This was an active, cast magic spell. Set state. ;
[c2f2]JSR CastMagic_CalculateVisibility; Calculate visibility for the magic spell based on foreground elements.
; ; Manage the X position of the spell. ;
[c2f5]LDA a:CastMagic_XPos_Full; Load the magic's X position. [c2f8]STA Arg_DrawSprite_PosX; Set as an argument for future appearance updates.
; ; Set some unused state not used in the finished game. ;
[c2fa]LDA Screen_Maybe_ScrollXCounter [c2fc]STA Unused_Sprite_ScrollPosX
; ; Manage the Y position of the spell. ;
[c2fe]LDA a:CastMagic_YPos_Full [c301]STA Arg_DrawSprite_PosY; Set as an argument for future appearance updates.
; ; Set more unused state not used in the finished game. ;
[c303]LDA Player_Something_ScrollPosY [c305]STA Unused_Sprite_ScrollPosY
; ; Set the finish handler for this type as the next function ; to run. ;
[c307]LDA a:CastMagic_Type; Load the magic type. [c30a]ASL A; Convert to a word boundary for the lookup table. [c30b]TAY; Y = A [c30c]LDA DAT_bb28,Y; Load the lower byte of the finish handler. [c30f]PHA; Push to the stack. [c310]LDA DAT_bb27,Y; Load the upper byte. [c313]PHA; Push. [c314]@_return:; [$c314] [c314]RTS; Return and then call the function.
;============================================================================ ; Calculate the visibility of the cast magic. ; ; This will check the cast magic against any foreground ; blocks, checking if it's fully visible, partially ; obscured, or fully obscured. ; ; INPUTS: ; CastMagic_XPos_Full: ; The X pixel position of the magic. ; ; CastMagic_YPos_Full: ; The Y pixel position of the magic. ; ; OUTPUTS: ; Temp_MovingSpriteVisibility: ; The visibility state of the cast magic. ; ; MovingSpriteVisibility: ; The visibility state of the cast magic. ; ; Arg_PixelPosX: ; Arg_PixelPosX: ; Clobbered. ; ; CALLS: ; Area_ConvertPixelsToBlockPos ; ScreenBuffer_LoadBlockProperty ; ; XREFS: ; CastMagic_RunUpdateSpellHandler ;============================================================================
[c315]CastMagic_CalculateVisibility:; [$c315]
; ; Set the default to visible. ;
[c315]LDA #$00 [c317]STA Temp_MovingSpriteVisibility; Set the default visibility to visible.
; ; Convert the cast magic's position to block positions. ;
[c319]LDA a:CastMagic_YPos_Full; Load the magic's Y position. [c31c]STA Arg_PixelPosY; Set it as an argument for getting the block property. [c31e]LDA a:CastMagic_XPos_Full; Load the magic's X position. [c321]CLC [c322]ADC #$04; Add 4 to that to better check block alignment. [c324]STA Arg_PixelPosX; Set it as an argument. [c326]JSR Area_ConvertPixelsToBlockPos; Convert to a block position. [c329]JSR ScreenBuffer_LoadBlockProperty; Get the block property for this position.
; ; Check if a block overlaps the left. ;
[c32c]CMP #$04; Is this block type 4? [c32e]BEQ @_leftIsObscured; If so, the left is obscured. [c330]CMP #$0d; Else, is this block type 13? [c332]BEQ @_leftIsObscured; If so, the left is obscured. [c334]CMP #$09; Else, is this block type 9? [c336]BNE @_checkRightObscured; If not, the left is not obscured. Start checking the right.
; ; This is a foreground layer block. The trailing part ; is obscured. ;
[c338]@_leftIsObscured:; [$c338] [c338]LDA Temp_MovingSpriteVisibility; Load the cast magic's visibility state. [c33a]ORA #$01; Mark the left-hand side as obscured. [c33c]STA Temp_MovingSpriteVisibility; Store it.
; ; Now check the right-hand side of the magic. ;
[c33e]@_checkRightObscured:; [$c33e] [c33e]LDA Arg_PixelPosX; Load the pixel X position. [c340]CLC [c341]ADC #$08; Add 8 to it, to better check the right side. [c343]STA Arg_PixelPosX; Store as the new pixel argument. [c345]JSR Area_ConvertPixelsToBlockPos; Convert to a block position. [c348]JSR ScreenBuffer_LoadBlockProperty; Load the block property.
; ; Check if a block overlaps the right. ;
[c34b]CMP #$04; Is this block 4? [c34d]BNE @_checkNeedToFlipBits; If not, it's not obscured. [c34f]CMP #$04; Again, is this block 4? [c351]BEQ @_rightIsObscured; If so, the right is obscured. [c353]CMP #$0d; Else, is this block 13? [c355]BEQ @_rightIsObscured; If so, the right is obscured. [c357]CMP #$09; Else, is this block 9? [c359]BNE @_checkNeedToFlipBits; If not, it's not obscured. Move to the next step.
; ; The trailing side of the magic is obscured. ;
[c35b]@_rightIsObscured:; [$c35b] [c35b]LDA Temp_MovingSpriteVisibility; Load the cast magic's visibility state. [c35d]ORA #$02; Mark the right-hand side as obscured. [c35f]STA Temp_MovingSpriteVisibility; Store it.
; ; Check if the magic is partially-obscured (just left or ; right). If so, make sure the flips are flipped for the ; facing direction. ;
[c361]@_checkNeedToFlipBits:; [$c361] [c361]LDX a:CurrentSpriteIndex; X = current sprite index. [c364]LDA a:CastMagic_Flags; A = cast magic flags. [c367]AND #$40; Is it facing right? [c369]BEQ @_facingRight; If so, there's nothing to do. We're done. [c36b]LDA Temp_MovingSpriteVisibility; A = cast magic visibility [c36d]BEQ @_storeVisibility; If the block is fully visible, jump. [c36f]CMP #$03; Else, is it full obscured? [c371]BEQ @_storeVisibility; If yes, jump.
; ; Swap the bits to face the other direction. ;
[c373]EOR #$03; Swap the visibility of leading and trailing flags. [c375]JMP @_storeVisibility; Jump to store. [c378]@_facingRight:; [$c378] [c378]LDA Temp_MovingSpriteVisibility; Load the previously-calculated visibility from above. [c37a]@_storeVisibility:; [$c37a] [c37a]STA MovingSpriteVisibility; Store the new visibility state. [c37c]RTS
;============================================================================ ; Set the appearance of the magic sprite. ; ; This will take in an offset into the magic's list of ; frames and set that as the new appearance. ; ; INPUTS: ; A: ; The offset into the list of frames. ; ; CastMagic_Type: ; The type of cast magic. ; ; SPRITE_MAGIC_IMAGE_ADDRS_U: ; The lookup table of upper addresses of magic ; sprite images. ; ; CALLS: ; Sprite_SetAppearanceAddrFromOffset ; ; XREFS: ; CastMagic_FinishHandler_Death ; CastMagic_FinishHandler_Deluge ; CastMagic_FinishHandler_DelugeOrDeathAfterHit ; CastMagic_FinishHandler_Fire ; CastMagic_FinishHandler_HitWallEffect ; CastMagic_FinishHandler_Thunder ; CastMagic_FinishHandler_Tilte ; CastMagic_FinishHandler_TilteAfterFirstHit ;============================================================================
[c37d]CastMagic_SetAppearance:; [$c37d] [c37d]LDY a:CastMagic_Type; Y = cast magic type [c380]CLC [c381]ADC SPRITE_MAGIC_IMAGE_ADDRS_U,Y; Add the value from the lookup table. [c384]JMP Sprite_SetAppearanceAddrFromOffset; Set the sprite appearance.
;============================================================================ ; Table of upper address bytes for magic sprite images. ; ; XREFS: ; CastMagic_SetAppearance ;============================================================================
; ; XREFS: ; CastMagic_SetAppearance ;
[c387]SPRITE_MAGIC_IMAGE_ADDRS_U:; [$c387] [c387].byte $95; [0]: Deluge [c388].byte $99; [1]: Thunder [c389].byte $9b; [2]: Fire [c38a].byte $9d; [3]: Death [c38b].byte $a1; [4]: Tilte [c38c].byte $a5; [5]: UNUSED: Deluge after first hit [c38d].byte $99; [6]: Thunder after first hit [c38e].byte $9b; [7]: Fire after first hit [c38f].byte $a5; [8]: UNUSED: Death after first hit [c390].byte $a5; [9]: UNUSED [c391].byte $a5; [10]: UNUSED: Hit wall effect [c392].byte $a5; [11]: Tilte after first hit
;============================================================================ ; Set whether a cast magic's sprite is flipped based on the cast direction. ; ; INPUTS: ; CastMagic_Flags: ; The cast magic's flags. ; ; OUTPUTS: ; CurrentSprite_FlipMask: ; The sprite's flip state. ; ; XREFS: ; CastMagic_FinishHandler_Death ; CastMagic_FinishHandler_Deluge ; CastMagic_FinishHandler_DelugeOrDeathAfterHit ; CastMagic_FinishHandler_Fire ; CastMagic_FinishHandler_Thunder ; CastMagic_FinishHandler_Tilte ;============================================================================
[c393]CastMagic_UpdateSpriteDirection:; [$c393] [c393]LDA a:CastMagic_Flags; Load the cast magic's flags. [c396]AND #$40; Take only the facing direction bit. [c398]STA CurrentSprite_FlipMask; Set as the sprite's flip mask. [c39a]RTS
;============================================================================ ; Finish updating the Deluge spell. ; ; This will maintain the sprite's direction and update ; the appearance. ; ; INPUTS: ; InterruptCounter: ; The current interrupt counter. ; ; OUTPUTS: ; None. ; ; CALLS: ; CastMagic_UpdateSpriteDirection ; CastMagic_SetAppearance ; ; XREFS: ; CAST_MAGIC_UPDATE_FINISH_HANDLERS [$PRG14::bb27] ;============================================================================
[c39b]CastMagic_FinishHandler_Deluge:; [$c39b] [c39b]JSR CastMagic_UpdateSpriteDirection; Set the sprite's direction. [c39e]LDA InterruptCounter; Load the interrupt counter. [c3a0]LSR A; Shift the interrupt counter to generate an appearance index. [c3a1]LSR A [c3a2]AND #$03; Keep the 2 right-most bits (effectively, bits 3 and 4 of the interrupt counter). [c3a4]JMP CastMagic_SetAppearance; Set that as the appearance index.
;============================================================================ ; Finish updating the Thunder spell. ; ; This will maintain the sprite's direction and update ; the appearance. ; ; It will update the appearance for 2 frames at a time, ; and wait 2 frames in-between. ; ; INPUTS: ; InterruptCounter: ; The current interrupt counter. ; ; OUTPUTS: ; None. ; ; CALLS: ; CastMagic_UpdateSpriteDirection ; CastMagic_SetAppearance ; ; XREFS: ; CAST_MAGIC_UPDATE_FINISH_HANDLERS [$PRG14::bb29] ; CAST_MAGIC_UPDATE_FINISH_HANDLERS [$PRG14::bb33] ;============================================================================
[c3a7]CastMagic_FinishHandler_Thunder:; [$c3a7] [c3a7]JSR CastMagic_UpdateSpriteDirection; Set the sprite's direction. [c3aa]LDA InterruptCounter; Load the interrupt counter. [c3ac]LSR A; Shift the interrupt counter to generate a value to check against. [c3ad]LSR A [c3ae]BCS @_return; If this should not update the appearance, return. [c3b0]AND #$01; Keep the right-most bit (effectively, bit 2 of the interrupt counter). [c3b2]JMP CastMagic_SetAppearance; Set that as the appearance index. [c3b5]@_return:; [$c3b5] [c3b5]RTS
;============================================================================ ; Finish updating the Fire spell. ; ; This will maintain the sprite's direction and update ; the appearance. ; ; It will run for 2 frames, skip 2 frames, run for 2 frames, ; etc. ; ; INPUTS: ; InterruptCounter: ; The current interrupt counter. ; ; OUTPUTS: ; None. ; ; CALLS: ; CastMagic_UpdateSpriteDirection ; CastMagic_SetAppearance ; ; XREFS: ; CAST_MAGIC_UPDATE_FINISH_HANDLERS [$PRG14::bb2b] ; CAST_MAGIC_UPDATE_FINISH_HANDLERS [$PRG14::bb35] ;============================================================================
[c3b6]CastMagic_FinishHandler_Fire:; [$c3b6] [c3b6]LDA InterruptCounter; Load the interrupt counter. [c3b8]AND #$02; Is bit 1 set? [c3ba]BNE @_updateFire; If so, jump to update. [c3bc]RTS [c3bd]@_updateFire:; [$c3bd] [c3bd]JSR CastMagic_UpdateSpriteDirection; Set the sprite's direction. [c3c0]LDA InterruptCounter; Load the interrupt counter. [c3c2]LSR A; Shift the interrupt counter to generate an appearance index. [c3c3]LSR A [c3c4]AND #$01; Keep the right-most bit (effectively, bit 2 of the interrupt counter). [c3c6]JMP CastMagic_SetAppearance; Set that as the appearance index.
;============================================================================ ; Finish updating the Death spell. ; ; This will maintain the sprite's direction and update ; the appearance. ; ; INPUTS: ; InterruptCounter: ; The current interrupt counter. ; ; OUTPUTS: ; None. ; ; CALLS: ; CastMagic_UpdateSpriteDirection ; CastMagic_SetAppearance ; ; XREFS: ; CAST_MAGIC_UPDATE_FINISH_HANDLERS [$PRG14::bb2d] ;============================================================================
[c3c9]CastMagic_FinishHandler_Death:; [$c3c9] [c3c9]JSR CastMagic_UpdateSpriteDirection; Update the sprite's direction. [c3cc]LDA InterruptCounter; Load the interrupt counter. [c3ce]LSR A; Shift the interrupt counter to generate an appearance index. [c3cf]LSR A [c3d0]LSR A [c3d1]AND #$03; Keep the 2 right-most bits (effectively, bits 3 and 4 of the interrupt counter). [c3d3]JMP CastMagic_SetAppearance; Set that as the appearance index.
;============================================================================ ; Finish updating the Tilte spell. ; ; This will set the appearance to 0, 1, 2, or 3, ; based on the current phase of the magic spell and ; the interrupt counter. ; ; The appearance will be: ; ; * 0: Magic phase is even; ; Interrupt counter has bit 3 set. ; ; * 1: Magic phase is even; ; Interrupt counter does not have bit 3 set. ; ; * 2: Magic phase is odd; ; Interrupt counter has bit 1 and 2 unset. ; ; * 3: Magic phase is odd; ; Interrupt counter has bit 1 unset and 2 set. ; ; INPUTS: ; CastMagic_Phase: ; The phase of the magic spell. ; ; InterruptCounter: ; The current interrupt counter. ; ; OUTPUTS: ; None. ; ; CALLS: ; CastMagic_SetAppearance ; ; XREFS: ; CAST_MAGIC_UPDATE_FINISH_HANDLERS [$PRG14::bb2f] ;============================================================================
[c3d6]CastMagic_FinishHandler_Tilte:; [$c3d6] [c3d6]JSR CastMagic_UpdateSpriteDirection; Update the sprite's direction.
; ; Check if the first bit of the magic's phase is 0 or 1. ;
[c3d9]LDA a:CastMagic_Phase; Load the magic's phase. [c3dc]LSR A; Shift bit 0 into Carry. [c3dd]BCS @_phaseIs1; If it's set, jump.
; ; The magic phase is 0. Update the appearance. ; ; The appearance value will be 1 if the interrupt counter ; does not have bit 3 set. ;
[c3df]LDY #$00; Y = 0 [c3e1]LDA InterruptCounter; A = interrupt counter [c3e3]AND #$08; Keep bit 3. [c3e5]BEQ @_setAppearance1; If 0, jump to use 0 as the appearance value. [c3e7]INY; Y++ (appearance value == 1) [c3e8]@_setAppearance1:; [$c3e8] [c3e8]TYA; A = 0 or 1 (appearance value) [c3e9]JMP CastMagic_SetAppearance; Set the appearance of the magic.
; ; The magic phase is 1. Update the appearance if the ; interrupt counter has bit 1 unset. ;
[c3ec]@_phaseIs1:; [$c3ec] [c3ec]LDA InterruptCounter; Load the interrupt counter. [c3ee]LSR A; Divide by 2. [c3ef]LSR A; And place in Carry. [c3f0]BCC @_setAppearance2; If 0, jump to update appearance. [c3f2]RTS
; ; The appearance will be updated based on the interrupt ; counter. It will be 3 if bit 2 is unset, or 4 if set. ;
[c3f3]@_setAppearance2:; [$c3f3] [c3f3]AND #$01; Keep bit 0. [c3f5]CLC [c3f6]ADC #$02; Add 2. [c3f8]JMP CastMagic_SetAppearance; Set the appearance of the magic.
;============================================================================ ; UNUSED: Finish handling updates to Deluge and Death spells that have ; collided at least once. ; ; This would maintain the sprite's direction and appearance. ; ; It's never actually run, due to both these spells clearing ; immediately when colliding. ; ; INPUTS: ; None. ; ; OUTPUTS: ; None. ; ; CALLS: ; CastMagic_SetAppearance ; CastMagic_UpdateSpriteDirection ; ; XREFS: ; CAST_MAGIC_UPDATE_FINISH_HANDLERS [$PRG14::bb31] ; CAST_MAGIC_UPDATE_FINISH_HANDLERS [$PRG14::bb37] ;============================================================================
[c3fb]CastMagic_FinishHandler_DelugeOrDeathAfterHit:; [$c3fb] [c3fb]JSR CastMagic_UpdateSpriteDirection; Update the sprite direction. [c3fe]LDA #$00 [c400]JMP CastMagic_SetAppearance; Set the appearance to 0.
;============================================================================ ; UNUSED: Add a Magic Hit Wall effect. ; ; This was meant to be used by the Deluge and Fire magic. ; Upon hitting a wall, it would briefly spawn two explosion ; sprites (using the diagonal Tilte sprite) up against the ; collided block, before disappearing. ; ; This may have been wired off because there appears to be ; a race condition in the Fire spell, where it may not ; always show. ; ; This can be reactivated using the following Game Genie ; codes: ; ; ZAVLOUNN ; ZEUUSUNN ; ; INPUTS: ; CastMagic_Unused_HitWallDeltaPosY: ; The delta Y position for the hit wall sprite. ; ; CastMagic_YPos_Full: ; The Y position of the magic sprite. ; ; OUTPUTS: ; CurrentSprite_FlipMask: ; Set to 0. ; ; Arg_DrawSprite_PosY: ; Temp_00: ; Clobbered. ; ; CALLS: ; CastMagic_SetAppearance ; ; XREFS: ; CAST_MAGIC_UPDATE_FINISH_HANDLERS [$PRG14::bb3b] ;============================================================================
[c403]CastMagic_FinishHandler_HitWallEffect:; [$c403]
; ; Set the default flip mask of the sprite to 0 (face right). ;
[c403]LDA #$00 [c405]STA CurrentSprite_FlipMask; Set flip mask to face right.
; ; Calculate a multiplier used to position the explosion ; sprites. ; ; This will be the delta Y position * 2. ;
[c407]LDA a:CastMagic_Unused_HitWallDeltaPosY; A = Delta Y position. [c40a]ASL A; A *= 2 [c40b]STA Temp_00; Save it temporarily.
; ; Position and draw the top explosion sprite. ; ; This will be MagicY - (DeltaY * 2). ;
[c40d]LDA a:CastMagic_YPos_Full; A = Magic Y position. [c410]SEC [c411]SBC Temp_00; A -= Effect delta Y position. [c413]STA Arg_DrawSprite_PosY; Set as the new sprite's Y position. [c415]LDA #$02 [c417]JSR CastMagic_SetAppearance; Draw the top explosion sprite at that position.
; ; Position and draw the bottom explosion sprite. ; ; This will be MagicY + 16 + (DeltaY * 2). ;
[c41a]LDA a:CastMagic_Unused_HitWallDeltaPosY; A = Delta Y position. [c41d]ASL A; A *= 2 [c41e]CLC [c41f]ADC #$10; A += 16 [c421]CLC [c422]ADC a:CastMagic_YPos_Full; A += Magic Y position. [c425]STA Arg_DrawSprite_PosY; Save it as the new sprite's Y position. [c427]LDA #$03 [c429]JMP CastMagic_SetAppearance; Draw the bottom explosion sprite at that position.
;============================================================================ ; TODO: Document CastMagic_FinishHandler_TilteAfterFirstHit ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; CAST_MAGIC_UPDATE_FINISH_HANDLERS [$PRG14::bb3d] ;============================================================================
[c42c]CastMagic_FinishHandler_TilteAfterFirstHit:; [$c42c] [c42c]LDA #$00 [c42e]STA CurrentSprite_FlipMask [c430]LDX #$03 [c432]@_loop:; [$c432] [c432]LDA a:CastMagic_Counter [c435]ASL A [c436]PHA [c437]STA Temp_00 [c439]EOR MAGICHITHANDLER_c42c_ARRAY1,X [c43c]BPL @LAB_PRG15_MIRROR__c442 [c43e]STA Temp_00 [c440]INC Temp_00 [c442]@LAB_PRG15_MIRROR__c442:; [$c442] [c442]LDA a:CastMagic_XPos_Full [c445]CLC [c446]ADC Temp_00 [c448]STA Arg_DrawSprite_PosX [c44a]PLA [c44b]STA Temp_00 [c44d]EOR $c470,X [c450]BPL @LAB_PRG15_MIRROR__c456 [c452]STA Temp_00 [c454]INC Temp_00 [c456]@LAB_PRG15_MIRROR__c456:; [$c456] [c456]LDA a:CastMagic_YPos_Full [c459]CLC [c45a]ADC Temp_00 [c45c]STA Arg_DrawSprite_PosY [c45e]TXA [c45f]PHA [c460]LDA $c474,X [c463]JSR CastMagic_SetAppearance [c466]PLA [c467]TAX [c468]DEX [c469]BPL @_loop [c46b]RTS [c46c]MAGICHITHANDLER_c42c_ARRAY1:; [$c46c] [c46c].byte $ff; [0]: [c46d].byte $00; [1]:
; ; XREFS: ; CastMagic_FinishHandler_TilteAfterFirstHit ;
[c46e]MAGICHITHANDLER_c42c_ARRAY1_2_:; [$c46e] [c46e].byte $ff; [2]:
; ; XREFS: ; CastMagic_FinishHandler_TilteAfterFirstHit ;
[c46f]MAGICHITHANDLER_c42c_ARRAY1_3_:; [$c46f] [c46f].byte $00; [3]: [c470].byte $ff; [0]: [c471].byte $ff; [1]: [c472].byte $00; [2]:
; ; XREFS: ; CastMagic_FinishHandler_TilteAfterFirstHit ;
[c473]BYTE_ARRAY_PRG15_MIRROR__c470_3_:; [$c473] [c473].byte $00; [3]: [c474].byte $00; [0]: [c475].byte $02; [1]: [c476].byte $01; [2]:
; ; XREFS: ; CastMagic_FinishHandler_TilteAfterFirstHit ;
[c477]BYTE_ARRAY_PRG15_MIRROR__c474_3_:; [$c477] [c477].byte $03; [3]:
;============================================================================ ; Check whether the user wants to use the selected item. ; ; This will check if the player has both initiated an action ; to use the item and is in a position to use one. ; ; If these conditions are met, the action handler for the ; item will be invoked. ; ; INPUTS: ; Player_Flags: ; The player's current flags. ; ; Joy1_ButtonMask: ; The currently-held buttons. ; ; Joy1_ChangedButtonMask: ; The newly-pressed buttons. ; ; SelectedItem: ; The selected item. ; ; OUTPUTS: ; None ; ; XREFS: ; EndGame_MainLoop ; Game_MainLoop ;============================================================================
[c478]GameLoop_CheckUseCurrentItem:; [$c478] [c478]LDA Player_Flags; Load the player's flags.
; ; Check if the player is in a state where they'd be allowed ; to use an item. ;
[c47a]AND #$05 [c47c]BNE GameLoop_UseItem_Return
; ; Check if the Down button has been pressed. If not, return. ;
[c47e]LDA Joy1_ButtonMask; Load the button mask. [c480]AND #$04; Check if the Down button is pressed. [c482]BEQ GameLoop_UseItem_Return; If not, return.
; ; Check if the B button has been pressed. If not, return. ;
[c484]LDA a:Joy1_ChangedButtonMask; Load the changed button mask. [c487]AND #$40; Check if the B button is pressed. [c489]BEQ GameLoop_UseItem_Return; If not, return.
; ; Down-B was pressed. The item can be used, if a valid one is ; selected. Figure out if there's a valid one first... ;
[c48b]LDA a:SelectedItem; Load the selected item. [c48e]ASL A; Multiply by 2 to get a word boundary for address lookup. [c48f]TAY; Y = A [c490]CPY #$22; Is it a valid item? [c492]BCS GameLoop_UseItem_Return; If not, return.
; ; Look up this item in the jump table and run it. ;
[c494]LDA USE_ITEM_JUMP_TABLE+1,Y; Load the lower byte of the item handler address. [c497]PHA; Push it. [c498]LDA USE_ITEM_JUMP_TABLE,Y; Load the upper byte. [c49b]PHA; Push it.
; ; XREFS: ; GameLoop_CheckUseCurrentItem ; USE_ITEM_JUMP_TABLE [$PRG15_MIRROR::c49d] ; USE_ITEM_JUMP_TABLE [$PRG15_MIRROR::c49f] ; USE_ITEM_JUMP_TABLE [$PRG15_MIRROR::c4a1] ; USE_ITEM_JUMP_TABLE [$PRG15_MIRROR::c4a3] ; USE_ITEM_JUMP_TABLE [$PRG15_MIRROR::c4a5] ; USE_ITEM_JUMP_TABLE [$PRG15_MIRROR::c4a7] ; USE_ITEM_JUMP_TABLE [$PRG15_MIRROR::c4a9] ; USE_ITEM_JUMP_TABLE [$PRG15_MIRROR::c4ab] ; USE_ITEM_JUMP_TABLE [$PRG15_MIRROR::c4ad] ; USE_ITEM_JUMP_TABLE [$PRG15_MIRROR::c4b1] ; USE_ITEM_JUMP_TABLE [$PRG15_MIRROR::c4b3] ; USE_ITEM_JUMP_TABLE [$PRG15_MIRROR::c4b5] ; USE_ITEM_JUMP_TABLE [$PRG15_MIRROR::c4b9] ;
[c49c]GameLoop_UseItem_Return:; [$c49c] [c49c]RTS
;============================================================================ ; Handler functions for using specific items. ; ; XREFS: ; GameLoop_CheckUseCurrentItem ;============================================================================
; ; XREFS: ; GameLoop_CheckUseCurrentItem ;
[c49d]USE_ITEM_JUMP_TABLE:; [$c49d] [c49d].word GameLoop_UseItem_Return-1; [0]: Ring of Elf [c49f].word GameLoop_UseItem_Return-1; [1]: Ring of Ruby [c4a1].word GameLoop_UseItem_Return-1; [2]: Ring of Dworf [c4a3].word GameLoop_UseItem_Return-1; [3]: Demon's Ring [c4a5].word GameLoop_UseItem_Return-1; [4]: "A" Key [c4a7].word GameLoop_UseItem_Return-1; [5]: "K" Key [c4a9].word GameLoop_UseItem_Return-1; [6]: "Q" Key [c4ab].word GameLoop_UseItem_Return-1; [7]: "J" Key [c4ad].word GameLoop_UseItem_Return-1; [8]: "Jo" Key [c4af].word Player_UseMattock-1; [9]: Mattock [c4b1].word GameLoop_UseItem_Return-1; [10]: Magical Rod [c4b3].word GameLoop_UseItem_Return-1; [11]: Crystal [c4b5].word GameLoop_UseItem_Return-1; [12]: Lamp [c4b7].word Player_UseHourGlass-1; [13]: Hour Glass [c4b9].word GameLoop_UseItem_Return-1; [14]: Book [c4bb].word Player_UseWingBoots-1; [15]: Wing Boots [c4bd].word Player_UseRedPotion-1; [16]: Red Potion
;============================================================================ ; Clear the selected item. ; ; INPUTS: ; None ; ; OUTPUTS: ; SelectedItem: ; The cleared item selection. ; ; CALLS: ; UI_ClearSelectedItemPic ; ; XREFS: ; Player_UseHourGlass ; Player_UseMattock ; Player_UseRedPotion ; Player_UseWingBoots ;============================================================================
[c4bf]Player_ClearSelectedItem:; [$c4bf] [c4bf]PHA; Push A to the stack. [c4c0]LDA #$ff [c4c2]STA a:SelectedItem; Set selected item to 0xFF (unset). [c4c5]JSR UI_ClearSelectedItemPic; Update the UI for the cleared image. [c4c8]PLA; Pop A from the stack. [c4c9]RTS
;============================================================================ ; Use the player's Elixir. ; ; This will remove the Elixir from the inventory and update the player's ; health. ; ; This then falls through to Player_FillHPAndMP. ; ; INPUTS: ; SpecialItems: ; The special items bitmask to update. ; ; Player_HP_U: ; The player's HP. ; ; Player_MP: ; The player's MP. ; ; OUTPUTS: ; SpecialItems: ; The new special items bitmask with the ; Elixir removed. ; ; Player_HP_U: ; The player's updated HP. ; ; Player_MP: ; The player's updated MP. ; ; CALLS: ; Player_AddHP ; Player_AddMP ; MMC1_LoadBankAndJump ; Sound_PlayEffect ; WaitForNextFrame ; Sprites_FlipRanges ; ; XREFS: ; Player_ReduceHP ;============================================================================
[c4ca]Player_UseElixir:; [$c4ca] [c4ca]LDA a:SpecialItems; Load the special items. [c4cd]AND #$f7; Clear the elixir bit. [c4cf]STA a:SpecialItems; Store it.
; ; Run the "Use Elixir" IScript. ; ; IScript 0x85 via jump to IScripts_Begin. ;
[c4d2]LDA #$85; 0x85 == Use Elixir. [c4d4]JSR MMC1_LoadBankAndJump; Run IScript: [c4d7].byte BANK_12_LOGIC; Bank = 12 [c4d8].word IScripts_Begin-1; Address = IScripts_Begin
;============================================================================ ; Progressively fill the player's HP and MP. ; ; This will add 4HP and 4MP at a time, playing a sound ; effect for each segment. This appears to the player as ; the HP and MP bars filling up. ; ; INPUTS: ; SpecialItems: ; The special items bitmask to update. ; ; Player_HP_U: ; The player's HP. ; ; Player_MP: ; The player's MP. ; ; OUTPUTS: ; SpecialItems: ; The new special items bitmask with the ; Elixir removed. ; ; Player_HP_U: ; The player's updated HP. ; ; Player_MP: ; The player's updated MP. ; ; CALLS: ; Player_AddHP ; Player_AddMP ; MMC1_LoadBankAndJump ; Sound_PlayEffect ; WaitForNextFrame ; Sprites_FlipRanges ; ; XREFS: ; Player_FillHPAndMP ;============================================================================
[c4da]Player_FillHPAndMP:; [$c4da]
; ; Progressively fill the HP, 4 units at a time. ; ; Start by playing the sound. ;
[c4da]LDA #$13; Set sound 0x13 (fill HP/MP bar). [c4dc]JSR Sound_PlayEffect; Play it.
; ; Add the HP. ;
[c4df]LDA #$04; Set the HP to 4 units. [c4e1]JSR Player_AddHP; Add it to the player. [c4e4]JSR Game_DrawScreenInFrozenState; Draw the screen, and prevent updating until done.
; ; Wait 4 frames of updates before filling up the next units ; of HP, to help animate the bar. ;
[c4e7]JSR WaitForNextFrame; Wait a bit. [c4ea]JSR Sprites_FlipRanges [c4ed]JSR WaitForNextFrame [c4f0]JSR Sprites_FlipRanges [c4f3]JSR WaitForNextFrame [c4f6]JSR Sprites_FlipRanges [c4f9]JSR WaitForNextFrame [c4fc]JSR Sprites_FlipRanges
; ; Check if we're done filling up HP. ;
[c4ff]LDA a:Player_HP_U; Load the player's HP. [c502]CMP #$50; Is it below the cap of 80HP? [c504]BCC Player_FillHPAndMP; If so, loop.
; ; Progressively fill the MP, 4 units at a time. ; ; Start by playing the sound. ;
[c506]@_fillMPLoop:; [$c506] [c506]LDA #$13; Set sound 0x13 (fill HP/MP bar). [c508]JSR Sound_PlayEffect; Play it.
; ; Add the MP. ;
[c50b]LDA #$04; Set the MP to 4 units. [c50d]JSR Player_AddMP; Add it to the player. [c510]JSR Game_DrawScreenInFrozenState; Draw the screen but keep all sprites frozen.
; ; Wait 4 frames of updates before filling up the next units ; of MP, to help animate the bar. ;
[c513]JSR WaitForNextFrame; Wait a bit. [c516]JSR Sprites_FlipRanges [c519]JSR WaitForNextFrame [c51c]JSR Sprites_FlipRanges [c51f]JSR WaitForNextFrame [c522]JSR Sprites_FlipRanges [c525]JSR WaitForNextFrame [c528]JSR Sprites_FlipRanges
; ; Check if we're done filling up MP. ;
[c52b]LDA a:Player_MP; Load the player's MP. [c52e]CMP #$50; Is it below the cap of 80MP? [c530]BCC @_fillMPLoop; If so, loop. [c532]RTS
;============================================================================ ; Use the Red Potion item. ; ; This will play the Red Potion message and a sound ; effect, and remove the item from the inventory. ; ; Health will be progressively filled. ; ; INPUTS: ; Player_HP_U: ; The player's HP. ; ; OUTPUTS: ; Player_HP_U: ; The player's updated HP. ; ; CALLS: ; Game_DrawScreenInFrozenState ; MMC1_LoadBankAndJump ; Player_ClearSelectedItem ; Player_AddHP ; Sound_PlayEffect ; Sprites_FlipRanges ; WaitForNextFrame ; ; XREFS: ; USE_ITEM_JUMP_TABLE [$PRG15_MIRROR::c4bd] ;============================================================================
[c533]Player_UseRedPotion:; [$c533]
; ; Run the "Used Red Potion" IScript. ; ; IScript 0x80 via jump to IScripts_Begin. ;
[c533]LDA #$80; 0x80 == Use Red Potion IScript. [c535]JSR MMC1_LoadBankAndJump; Run IScript: [c538].byte BANK_12_LOGIC; Bank = 12 [c539].word IScripts_Begin-1; Address = IScripts_Begin [c53b]@_afterFarJump:; [$c53b] [c53b]JSR Player_ClearSelectedItem; Clear the selected item.
; ; Play the initial sound effect. ;
[c53e]LDA #$1a; 0x1A == Initial item used sound effect. [c540]JSR Sound_PlayEffect; Play it.
; ; Set up the HP fill loop. This will fill up to 80. ;
[c543]LDX #$50; X = Maximum HP / our loop counter. [c545]@_loop:; [$c545] [c545]TXA; A = X [c546]PHA; Push it to the stack.
; ; Play the unit fill sound effect. ;
[c547]LDA #$13; 0x13 = Unit of HP fill sound effect. [c549]JSR Sound_PlayEffect; Play the sound.
; ; Add 2HP. ;
[c54c]LDA #$02 [c54e]JSR Player_AddHP; Add the 2HP to the player. [c551]JSR Game_DrawScreenInFrozenState
; ; Wait 4 frames worth of updates before filling the next 2HP. ;
[c554]JSR WaitForNextFrame [c557]JSR Sprites_FlipRanges [c55a]JSR WaitForNextFrame [c55d]JSR Sprites_FlipRanges [c560]JSR WaitForNextFrame [c563]JSR Sprites_FlipRanges [c566]JSR WaitForNextFrame [c569]JSR Sprites_FlipRanges
; ; Check if we're done. ;
[c56c]PLA; Pop the max HP. [c56d]TAX; X = A [c56e]LDA a:Player_HP_U; Load the player's current HP. [c571]CMP #$50; Is the player at max HP? [c573]BCS @_return; If so, return. [c575]DEX; X-- [c576]BNE @_loop; If X > 0, loop. [c578]@_return:; [$c578] [c578]RTS
;============================================================================ ; Use the Wing Boots item. ; ; This will play the Wing Boots message and a sound ; effect, and remove the item from the inventory. ; ; The duration of the Wing Boots will depend on the ; player's title: ; ; * 40 seconds: Novice, Aspirant, Battler, Fighter ; * 30 seconds: Adept, Chevalier, Veteran, Warrior ; * 20 seconds: Swordman, Hero, Soldier, Myrmidon ; * 10 seconds: Champion, Superhero, Paladin, Lor ; ; INPUTS: ; PlayerTitle: ; The player's current title. ; ; TITLE_TO_WINGBOOTS_DURATION: ; The table of player titles to Wingboots duration. ; ; OUTPUTS: ; DurationWingBoots: ; The starting number of seconds remaining for the ; Wing Boots. ; ; CALLS: ; Player_ClearSelectedItem: ; Sound_PlayEffect ; MMC1_LoadBankAndJump ; UI_DrawTimeValue ; ; XREFS: ; USE_ITEM_JUMP_TABLE [$PRG15_MIRROR::c4bb] ;============================================================================
[c579]Player_UseWingBoots:; [$c579]
; ; Run the "Wing Boots Used" IScript. ; ; IScript 0x83 via jump to IScripts_Begin. ;
[c579]LDA #$83; 0x83 == Wing Boots used IScript. [c57b]JSR MMC1_LoadBankAndJump; Run IScript: [c57e].byte BANK_12_LOGIC; Bank = 12 [c57f].word IScripts_Begin-1; Address = IScripts_Begin
; ; Remove the item. ;
[c581]@_afterFarJump:; [$c581] [c581]JSR Player_ClearSelectedItem; Clear the selected item.
; ; Play a sound effect for the item usage. ;
[c584]LDA #$1a; Set the sound to play to 0x1A (special item sound). [c586]JSR Sound_PlayEffect; Play the sound.
; ; Calculate the index into the duration table based on ; the player's title. ;
[c589]LDA a:PlayerTitle; Load the player's title. [c58c]LSR A; Divide by 4. [c58d]LSR A [c58e]TAX; X = A (index) [c58f]LDA TITLE_TO_WINGBOOTS_DURATION,X; Look up in the duration table. [c592]STA a:DurationWingBoots; Set that as the starting duration. [c595]JSR UI_DrawTimeValue; Draw the time on the HUD. [c598]RTS
;============================================================================ ; Wing Boots durations by player title. ; ; The duration decreases every 4 player titles. ; ; XREFS: ; Player_UseWingBoots ;============================================================================
; ; XREFS: ; Player_UseWingBoots ;
[c599]TITLE_TO_WINGBOOTS_DURATION:; [$c599] [c599].byte $28; [0]: 40 seconds: Novice, Aspirant, Battler, Fighter [c59a].byte $1e; [1]: 30 seconds: Adept, Chevalier, Veteran, Warrior [c59b].byte $14; [2]: 20 seconds: Swordman, Hero, Soldier, Myrmidon [c59c].byte $0a; [3]: 10 seconds: Champion, Superhero, Paladin, Lord
;============================================================================ ; Decrement the Wing Boot's duration. ; ; This will decrement and then draw the time in the HUD. ; ; INPUTS: ; DurationWingBoots: ; The remaining duration for the Wing Boots. ; ; InterruptCounter: ; The current interrupt counter. ; ; OUTPUTS: ; DurationWingBoots: ; The new duration for the Wing Boots. ; ; CALLS: ; MMC1_LoadBankAndJump ; UI_DrawTimeValue ; ; XREFS: ; GameLoop_CountdownItems ;============================================================================
[c59d]Game_DecWingBootsDuration:; [$c59d]
; ; Check if there's any time remaining on the Wing Boots. ;
[c59d]LDA a:DurationWingBoots; Load the remaining time on the Wing Boots. [c5a0]BMI @_return; If the counter is unset (0xFF), return.
; ; Update the player's status to mark Wing Boots as on. ;
[c5a2]LDA Player_StatusFlag; Load the player's status flags. [c5a4]ORA #$80; Enable the Wing Boots bit. [c5a6]STA Player_StatusFlag; Store it.
; ; Update at most once every second. ;
[c5a8]LDA InterruptCounter; Load the interrupt counter. [c5aa]AND #$3f; Check if we're at the 1 second mark. [c5ac]BNE @_return; If not, return.
; ; Reduce time on the Wing Boots by a second. ;
[c5ae]DEC a:DurationWingBoots; Decrement the remaining time for the Wing Boots. [c5b1]LDA a:DurationWingBoots; Load that duration [c5b4]BMI @_wingBootsDone; If it wrapped from 0 to 0xFF, it's used up. Jump. [c5b6]JMP UI_DrawTimeValue; Else, draw the time remaining.
; ; The Wing Boots ran out. Remove the flag from the player's ; status. ;
[c5b9]@_wingBootsDone:; [$c5b9] [c5b9]LDA Player_StatusFlag; Load the player's status flags. [c5bb]AND #$7f; Clear the Wing Boots flag. [c5bd]STA Player_StatusFlag; Store it.
; ; Run "Wing Boots are gone" IScript. ; ; IScript 0x96 via jump to IScripts_Begin. ;
[c5bf]LDA #$96; 0x96 == Wing Boots are gone IScript. [c5c1]JSR MMC1_LoadBankAndJump; Run the IScript: [c5c4].byte BANK_12_LOGIC; Bank = 12 [c5c5].word IScripts_Begin-1; Address = IScripts_Begin
; ; It will resume here. ;
[c5c7]@_return:; [$c5c7] [c5c7]RTS
;============================================================================ ; Use the Hour Glass. ; ; This will invoke the IScript for the Hour Glass's ; message, play a sound, and begin the Hour Glass ; effect. ; ; The player's health will be reduced by half in the ; process. ; ; INPUTS: ; Player_HP_U: ; Player_HP_L: ; The player's current health. ; ; OUTPUTS: ; Player_HP_U: ; Player_HP_L: ; The player's new health, reduced by half. ; ; DurationHourGlass: ; The duration of the Hour Glass. ; ; Music_Current: ; The music to play while the Hour Glass is ; active. ; ; CALLS: ; MMC1_LoadBankAndJump ; Player_ClearSelectedItem ; Sound_PlayEffect ; UI_DrawPlayerHP ; ; XREFS: ; USE_ITEM_JUMP_TABLE [$PRG15_MIRROR::c4b7] ;============================================================================
[c5c8]Player_UseHourGlass:; [$c5c8]
; ; Run the "Hour Glass Used" IScript. ; ; IScript 0x82 via jump to IScripts_Begin. ;
[c5c8]LDA #$82; Set the IScript to run to 0x82. [c5ca]JSR MMC1_LoadBankAndJump; Run IScript: [c5cd].byte BANK_12_LOGIC; Bank = 12 [c5ce].word IScripts_Begin-1; Address = IScripts_Begin
; ; Remove the item. ;
[c5d0]@_afterFarJump:; [$c5d0] [c5d0]JSR Player_ClearSelectedItem; Clear the selected item.
; ; Play a sound effect for the item usage. ;
[c5d3]LDA #$1a; Set the sound to play to 0x1A (special item sound). [c5d5]JSR Sound_PlayEffect; Play the sound.
; ; Reduce the player's health by half. This is the cost of ; the Hour Glass. ;
[c5d8]LSR a:Player_HP_U; Divide the upper value of the player's HP by half. [c5db]ROR a:Player_HP_L; Divide the lower byte of the player's HP by half, and add carry from the upper. [c5de]JSR UI_DrawPlayerHP; Draw the new HP.
; ; Set the duration of the Hour Glass to 15 seconds. ;
[c5e1]LDA #$0f; Set the duration to 15 seconds. [c5e3]STA a:DurationHourGlass
; ; Change the music. ;
[c5e6]LDA #$0b; Set the new music to play. [c5e8]STA Music_Current; And play it. [c5ea]RTS
;============================================================================ ; Decrement the Hour Glass's duration. ; ; INPUTS: ; DurationHourGlass: ; The remaining duration for the Hour Glass. ; ; InterruptCounter: ; The current interrupt counter. ; ; OUTPUTS: ; DurationHourGlass: ; The new duration for the Hour Glass.. ; ; Music_Current: ; The restored music for the area. ; ; CALLS: ; MMC1_LoadBankAndJump ; ; XREFS: ; GameLoop_CountdownItems ;============================================================================
[c5eb]Game_DecHourGlassDuration:; [$c5eb]
; ; Check if there's any time remaining on the Hour Glass. ;
[c5eb]LDA a:DurationHourGlass; Load the remaining time on the Hour Glass. [c5ee]BMI @_return; If the counter is unset (0xFF), return.
; ; Update at most once every second. ;
[c5f0]LDA InterruptCounter; Load the interrupt counter. [c5f2]AND #$3f; Check if we're at the 1 second mark. [c5f4]BNE @_return; If not, return.
; ; Reduce time on the Hour Glass by a second. ;
[c5f6]DEC a:DurationHourGlass; Decrement the remaining time for the Hour Glass. [c5f9]BPL @_return; If it wrapped from 0 to 0xFF, it's used up. Jump.
; ; Run "Hour Glass is gone" IScript. ; ; IScript 0x97 via jump to IScripts_Begin. ;
[c5fb]LDA #$97; 0x97 == Hour Glass is gone IScript. [c5fd]JSR MMC1_LoadBankAndJump; Run the IScript: [c600].byte BANK_12_LOGIC; Bank = 12 [c601].word IScripts_Begin-1; Address = IScripts_Begin
; ; Load the area's default music. ;
[c603]@_afterFarJump:; [$c603] [c603]LDA a:Areas_DefaultMusic; Load the music for the area. [c606]STA Music_Current; Store it. [c608]@_return:; [$c608] [c608]RTS
;============================================================================ ; Add 100XP to the player's experience. ; ; INPUTS: ; None ; ; OUTPUTS: ; Temp_Int24: ; Temp_Int24+1: ; Clobbered. ; ; CALLS: ; Player_UpdateExperience ; ; XREFS: ; Player_PickUpGlove ; Player_PickUpOintment ;============================================================================
[c609]Player_Add100XP:; [$c609] [c609]LDA #$64; A = 100 (lower byte of XP) [c60b]STA a:Temp_Int24; Store it as an argument. [c60e]LDA #$00; A = 0 (upper byte of XP). [c610]STA a:Temp_Int24_M; Store it as an argument. [c613]JMP Player_UpdateExperience; Update the player's experience using those values.
;============================================================================ ; Use the Mattock. ; ; This will check if the Mattock can be used at the current ; player position. If so, it will invoke the IScript for the ; Mattock's message, play a sound, and update the level state. ; ; INPUTS: ; Area_CurrentArea: ; The current area. ; ; Player_PosX_Block: ; The player's current X position. ; ; Player_PosY: ; The player's current Y position. ; ; Player_Flags: ; The player's flags. ; ; USE_MATTOCK_BLOCK_DISTANCES: ; TODO ; ; OUTPUTS: ; Arg_PixelPosX: ; Arg_PixelPosY: ; Clobbered. ; ; CALLS: ; MMC1_LoadBankAndJump ; Player_ClearSelectedItem ; Sound_PlayEffect ; WaitForNextFrame ; Area_SetBlocks ; ; XREFS: ; USE_ITEM_JUMP_TABLE [$PRG15_MIRROR::c4af] ;============================================================================
[c616]Player_UseMattock:; [$c616]
; ; Check if the block check position will be in bounds. ; ; The offset will be dependent on the facing direction. ; ; If facing left, this will check the block immediately ; to the left (0xFF -- wrapping around the screen). ; ; If facing right, this will check block + 16 (within or ; at the end of the blocks to clear). ;
[c616]LDY #$00; Lookup table index = 0 (default) [c618]LDA Player_Flags; Load the player's flags. [c61a]AND #$40; Is the player facing right? [c61c]BEQ @LAB_PRG15_MIRROR__c61f; If not, jump.
; ; The player is facing right. Set our offset into the block ; distances table to 1. ;
[c61e]INY; Lookup table index = 1 [c61f]@LAB_PRG15_MIRROR__c61f:; [$c61f] [c61f]LDA Player_PosX_Block; Get the player's X position. [c621]CLC [c622]ADC USE_MATTOCK_BLOCK_DISTANCES,Y; Add the block distance for the facing direction. [c625]CMP #$f0; Is the player too near to the right edge of the screen? [c627]BCS @_return; If so, return.
; ; Begin fetching the block at this offset. ; ; Same logic as above for block calculation. ;
[c629]STA Arg_PixelPosX; Store the calculated position as the X argument. [c62b]LDA Player_PosY; Load the player's current Y position. [c62d]STA Arg_PixelPosY; Store that as the Y argument. [c62f]JSR Area_ConvertPixelsToBlockPos; Convert those pixel positions to block positions.
; ; Check if the screen buffer contains the blocks to clear. ; If not, there's nothing to do. ; ; Each block is composed of 4 tiles, which are represented ; in the lookup table. This only needs to check for the ; presence of one of those blocks. ;
[c632]LDA Area_CurrentArea; Load the current area. [c634]ASL A; Convert to a 4 byte index. [c635]ASL A [c636]TAY; Y = index [c637]LDA ScreenBuffer,X; Load the value from the screen buffer. [c63a]CMP USE_MATTOCK_BLOCK_TRANSITIONS,Y; Is this block already cleared by the Mattock? [c63d]BEQ @_useMattock; If not, use the Mattock to clear them. [c63f]RTS; Else, return.
; ; The Mattock will be used. Store the offsets into the ; screen buffer and array on the stack for later. ;
[c640]@_useMattock:; [$c640] [c640]TXA; A = X [c641]PHA; Push to the stack. [c642]TYA; A = Y [c643]PHA; Push to the stack.
; ; Run the "Used Mattock" IScript. ; ; IScript 0x81 via jump to IScripts_Begin. ;
[c644]LDA #$81 [c646]JSR MMC1_LoadBankAndJump; Run IScript: [c649].byte BANK_12_LOGIC; Bank = 12 [c64a].word IScripts_Begin-1; Address = IScripts_Begin
; ; Remove the Mattock. ;
[c64c]@_afterFarJump:; [$c64c] [c64c]JSR Player_ClearSelectedItem; Clear the selected item.
; ; Play the sound effect for the Mattock usage. ;
[c64f]LDA #$1a; 0x1A == Special item sound effect. [c651]JSR Sound_PlayEffect; Play it.
; ; Prepare for the loop. ; ; This loop will handle animating the destruction/crumbling ; of blocks. This uses 4 frames of animation, all defined in ; USE_MATTOCK_BLOCK_TRANSITIONS. ;
[c654]PLA; Pull A from the stack. [c655]TAY; Y = A [c656]PLA; Pull A from the stack. [c657]TAX; X = A
; ; Only update every 4 frames, to get a quick animation effect. ;
[c658]@_loop:; [$c658] [c658]JSR WaitForNextFrame; Wait for 4 frames. [c65b]JSR WaitForNextFrame [c65e]JSR WaitForNextFrame [c661]JSR WaitForNextFrame
; ; Update the screen to place the next block in the sequence. ;
[c664]TYA; A = Y [c665]PHA; Push to the stack. [c666]TXA; A = X [c667]PHA; Push to the stack. [c668]LDA USE_MATTOCK_BLOCK_TRANSITIONS,Y; Load the block for this position. [c66b]STA ScreenBuffer,X; And store it in the screen buffer. [c66e]JSR Area_SetBlocks; Update the level data.
; ; Update to the next block. ;
[c671]TXA; A = X [c672]CLC [c673]ADC #$10; A += 32 [c675]TAX; X = A [c676]LDA a:Arg_BlockAttributesIndex; Load a value. [c679]STA ScreenBuffer,X; And write it to the screen buffer. [c67c]JSR Area_SetBlocks; Upate the level data.
; ; Prepare for the next loop iteration. ;
[c67f]PLA; Pull A from the stack. [c680]TAX; X = A [c681]PLA; Pull A from the stack. [c682]TAY; Y = A [c683]INY; Y++ [c684]TYA; A = Y [c685]AND #$03; Have we finished updating the blocks? [c687]BEQ @_return; If so, return. [c689]JMP @_loop; Else, loop. [c68c]@_return:; [$c68c] [c68c]RTS
;============================================================================ ; Block distances for checking if a player is on-screen. ; ; This is used when using the Mattock. ; ; XREFS: ; Player_UseMattock ;============================================================================
; ; XREFS: ; Player_UseMattock ;
[c68d]USE_MATTOCK_BLOCK_DISTANCES:; [$c68d] [c68d].byte $ff; [0]:
; ; XREFS: ; Player_UseMattock ;
[c68e]USE_MATTOCK_BLOCK_DISTANCES_1_:; [$c68e] [c68e].byte $10; [1]:
;============================================================================ ; New block IDs to place when clearing blocks. ; ; This is divided into regions, each with 4 blocks. ; Those 4 blocks are an animation transition between ; the placed blocks (index 0 within a group) and ; cleared blocks (index 3). ; ; XREFS: ; Player_UseMattock ;============================================================================
; ; XREFS: ; Player_UseMattock ;
[c68f]USE_MATTOCK_BLOCK_TRANSITIONS:; [$c68f] [c68f].byte $00; [0]: Eolis [c690].byte $00; [1]: [c691].byte $00; [2]: [c692].byte $00; [3]: [c693].byte $63; [4]: Apolune [c694].byte $85; [5]: [c695].byte $86; [6]: [c696].byte $42; [7]: [c697].byte $00; [8]: Forepaw [c698].byte $00; [9]: [c699].byte $00; [10]: [c69a].byte $00; [11]: [c69b].byte $00; [12]: Mascon [c69c].byte $00; [13]: [c69d].byte $00; [14]: [c69e].byte $00; [15]: [c69f].byte $00; [16]: Victim [c6a0].byte $00; [17]: [c6a1].byte $00; [18]: [c6a2].byte $00; [19]: [c6a3].byte $00; [20]: Conflate [c6a4].byte $00; [21]: [c6a5].byte $00; [22]: [c6a6].byte $00; [23]: [c6a7].byte $00; [24]: Daybreak [c6a8].byte $00; [25]: [c6a9].byte $00; [26]: [c6aa].byte $00; [27]: [c6ab].byte $00; [28]: Evil Fortress [c6ac].byte $00; [29]: [c6ad].byte $00; [30]: [c6ae].byte $00; [31]:
;============================================================================ ; Clear timers on all temporary status items. ; ; This applies to the Glove, Ointment, Wing Boots, and ; Hour Glass. ; ; INPUTS: ; None ; ; OUTPUTS: ; DurationGlove: ; DurationHourGlass: ; DurationOintment: ; DurationWingBoots: ; All set to 0xFF (not active). ; ; XREFS: ; Game_Start ; Player_Spawn ;============================================================================
[c6af]Game_ClearTimedItems:; [$c6af] [c6af]LDA #$ff [c6b1]STA a:DurationGlove; Clear the Glove duration. [c6b4]STA a:DurationOintment; Clear the Ointment duration. [c6b7]STA a:DurationWingBoots; Clear the Wing Boots duration. [c6ba]STA a:DurationHourGlass; Clear the Hour Glass duration. [c6bd]RTS
;============================================================================ ; Handle picking up the Hour Glass. ; ; This will invoke the IScript, play the sound, and add ; the Hour Glass to the inventory. ; ; INPUTS: ; None ; ; OUTPUTS: ; None ; ; CALLS: ; MMC1_LoadBankAndJump ; Sound_PlayEffect ; Player_PickUpItem ; ; XREFS: ; Player_PickUp ;============================================================================
[c6be]Player_PickUpHourGlass:; [$c6be]
; ; Run the "Got Hour Glass" IScript. ; ; IScript 0x8A via jump to IScripts_Begin. ;
[c6be]LDA #$8a; 0x8A == Hour Glass picked up IScript. [c6c0]JSR MMC1_LoadBankAndJump; Run IScript: [c6c3].byte BANK_12_LOGIC; Bank = 12 [c6c4].word IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c6c6]@_afterFarJump:; [$c6c6] [c6c6]LDA #$08; 0x08 == Item picked up sound. [c6c8]JSR Sound_PlayEffect; Play it.
; ; Pick up the item. ;
[c6cb]LDA #$0d; 0xD == Hour Glass. [c6cd]JMP Player_PickUpItem; Pick it up.
;============================================================================ ; Handle picking up the Wing Boots for the quest. ; ; This will invoke the IScript, play the sound, and add ; the Wing Boots to the inventory. ; ; INPUTS: ; Quests: ; The player's current completed quests. ; ; OUTPUTS: ; Quests: ; The updated set of completed quests. ; ; CALLS: ; MMC1_LoadBankAndJump ; Player_PickUpItem ; Sound_PlayEffect ; ; XREFS: ; Player_PickUp ;============================================================================
[c6d0]Player_PickUpWingBootsWithQuest:; [$c6d0] [c6d0]LDA a:Quests; Load the completed quests. [c6d3]ORA #$08; Mark this quest as completed. [c6d5]STA a:Quests; Store it back out.
; ; v-- Fall through --v ;
;============================================================================ ; Handle picking up the Wing Boots. ; ; This will invoke the IScript, play the sound, and add ; the Wing Boots to the inventory. ; ; INPUTS: ; None ; ; OUTPUTS: ; None ; ; CALLS: ; MMC1_LoadBankAndJump ; Player_PickUpItem ; Sound_PlayEffect ; ; XREFS: ; Player_PickUp ;============================================================================
[c6d8]Player_PickUpWingBoots:; [$c6d8]
; ; Run the "Got Wing Boots" IScript. ; ; IScript 0x89 via jump to IScripts_Begin. ;
[c6d8]LDA #$89; 0x89 == Wing boots picked up IScript. [c6da]JSR MMC1_LoadBankAndJump; Run IScript: [c6dd].byte BANK_12_LOGIC; Bank = 12 [c6de].word IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c6e0]@_afterFarJump:; [$c6e0] [c6e0]LDA #$08; 0x8 == Item pick-up sound. [c6e2]JSR Sound_PlayEffect; Play it.
; ; Pick up the item. ;
[c6e5]LDA #$0f; 0xF == Wing Boots. [c6e7]JMP Player_PickUpItem; Pick it up.
;============================================================================ ; Pick up the Battle Suit and add to the inventory. ; ; This will invoke the IScript, play the sound, and then ; attempt to cap the armor insert position and add the item ; there. ; ; BUGS: ; This has a bug where it checks the caps against the ; WEAPON slots, not the ARMOR slots! ; ; Player_PickUpDragonSlayer has the inverse ; bug. ; ; INPUTS: ; NumberOfWeapons: ; The number of weapons the player is carrying. ; ; OUTPUTS: ; ArmorInventory: ; The updated armor inventory. ; ; NumberOfArmors: ; The number of armors in the inventory. ; ; See CALLS. ; ; CALLS: ; MMC1_LoadBankAndJump ; Sound_PlayEffect ; ; XREFS: ; Player_PickUp ;============================================================================
[c6ea]Player_PickUpBattleSuit:; [$c6ea]
; ; Run the "Got Battle Suit" IScript. ; ; IScript 0x8B via jump to IScripts_Begin. ;
[c6ea]LDA #$8b; 0x8B == Battle Suit picked up IScript. [c6ec]JSR MMC1_LoadBankAndJump; Run IScript: [c6ef].byte BANK_12_LOGIC; Bank = 12 [c6f0].word IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c6f2]@_afterFarJump:; [$c6f2] [c6f2]LDA #$08; 0x08 == Item picked up sound. [c6f4]JSR Sound_PlayEffect; Play it.
; ; Cap the number of WEAPONS in the inventory. ; ; Yes, weapons. This *should* be armor. This code is swapped ; with the code that equips the Dragon Slayer. ;
[c6f7]LDX a:NumberOfWeapons; X = Number of weapons. [c6fa]CPX #$04; If < 4 [c6fc]BCC @_addItem; Then, add it to the latest slot. [c6fe]LDX #$03; Else, cap X to 3.
; ; Add the Battle Suit to the inventory. ;
[c700]@_addItem:; [$c700] [c700]LDA #$03; 0x03 == Battle Suit [c702]STA ArmorInventory,X; Set in the inventory slot
; ; Increase the number of armors collected. ;
[c705]INX; X++ [c706]STX a:NumberOfArmors; Store as the number of armors. [c709]RTS
;============================================================================ ; Pick up the Battle Helmet and add to the inventory. ; ; This will invoke the IScript, play the sound, and then ; cap the shield insertion point and add the item there. ; ; Keep in mind, the Battle Helmet is considered a shield in ; this game. ; ; INPUTS: ; NumberOfShields: ; The number of shields the player is carrying. ; ; OUTPUTS: ; ShieldInventory: ; The updated armor inventory. ; ; NumberOfShields: ; The number of armors in the inventory. ; ; CALLS: ; MMC1_LoadBankAndJump ; Sound_PlayEffect ; ; XREFS: ; Player_PickUp ;============================================================================
[c70a]Player_PickUpBattleHelmet:; [$c70a]
; ; Run the "Got Battle Helmet" IScript. ; ; IScript 0x8C via jump to IScripts_Begin. ;
[c70a]LDA #$8c; 0x8C == Battle Helmet picked up IScript. [c70c]JSR MMC1_LoadBankAndJump; Run it. [c70f].byte BANK_12_LOGIC; Bank = 12 [c710].word IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c712]@_afterFarJump:; [$c712] [c712]LDA #$08; 0x08 == Item picked up sound. [c714]JSR Sound_PlayEffect; Play it.
; ; Cap the number of shields in the inventory. ;
[c717]LDX a:NumberOfShields; X = Number of shields. [c71a]CPX #$04; If X < 4: [c71c]BCC @_addItem; Then, add to the latest slot. [c71e]LDX #$03; Else, cap X to 3.
; ; Add the Battle Helmet to the inventory. ;
[c720]@_addItem:; [$c720] [c720]LDA #$03; 0x03 == Battle Helmet. [c722]STA ShieldInventory,X; Set in the inventory slot.
; ; Increase the number of shields collected. ;
[c725]INX; X++ [c726]STX a:NumberOfShields; Store as the number of shields. [c729]RTS
;============================================================================ ; Pick up the Dragon Slayer and add to the inventory. ; ; This will invoke the IScript, play the sound, and then ; attempt to cap the armor insert position and add the item ; there. ; ; BUGS: ; This has a bug where it checks the caps against the ; ARMOR slots, not the WEAPON slots! ; ; Player_PickUpBattleSuit has the inverse ; bug. ; ; INPUTS: ; NumberOfArmors: ; The number of armors the player is carrying. ; ; OUTPUTS: ; WeaponInventory: ; The updated weapon inventory. ; ; NumberOfWeapons: ; The number of weapons in the inventory. ; ; CALLS: ; MMC1_LoadBankAndJump ; Sound_PlayEffect ; ; XREFS: ; Player_PickUp ;============================================================================
[c72a]Player_PickUpDragonSlayer:; [$c72a]
; ; Run the "Got Dragon Slayer" IScript. ; ; IScript 0x8D via jump to IScripts_Begin. ;
[c72a]LDA #$8d; 0x8D == Dragon Slayer picked up IScript. [c72c]JSR MMC1_LoadBankAndJump; Run it. [c72f].byte BANK_12_LOGIC; Bank = 12 [c730].word IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c732]@_afterFarJump:; [$c732] [c732]LDA #$08; 0x08 == Item picked up sound. [c734]JSR Sound_PlayEffect; Play it.
; ; Cap the number of ARMORS in the inventory. ; ; Yes, armors. This *should* be weapons. This code is swapped ; with the code that equips the Battle Suit. ;
[c737]LDX a:NumberOfArmors; X = number of armors. [c73a]CPX #$04; If < 4 [c73c]BCC @_addItem; Then, add it to the latest slot. [c73e]LDX #$03; Else, cap X to 3.
; ; Add the Dragon Slayer to the inventory. ;
[c740]@_addItem:; [$c740] [c740]LDA #$03; 0x03 == Dragon Slayer [c742]STA WeaponInventory,X; Set in the inventory slot.
; ; Increase the number of weapons collected. ;
[c745]INX; X++ [c746]STX a:NumberOfWeapons; Store as the number of weapons. [c749]RTS
;============================================================================ ; Handle picking up the Mattock for the quest. ; ; This will invoke the IScript, play the sound, and set ; the Mattock bit in the quests and in the inventory. ; ; INPUTS: ; Quests: ; The current completed quests. ; ; OUTPUTS: ; Quests: ; The updated completed quests. ; ; CALLS: ; MMC1_LoadBankAndJump ; Player_PickUpItem ; Sound_PlayEffect ; ; XREFS: ; Player_PickUp ;============================================================================
[c74a]Player_PickUpMattockWithQuest:; [$c74a] [c74a]LDA a:Quests; Load the quests bitmask. [c74d]ORA #$10; Bit 0x10 == Mattock [c74f]STA a:Quests; Save it.
; ; v-- Fall through to the next function. --v ;
;============================================================================ ; Handle picking up the Mattock. ; ; This will invoke the IScript, play the sound, and add ; the Mattock to the inventory. ; ; INPUTS: ; None ; ; OUTPUTS: ; None ; ; CALLS: ; MMC1_LoadBankAndJump ; Player_PickUpItem ; Sound_PlayEffect ; ; XREFS: ; Player_PickUp ;============================================================================
[c752]Player_PickUpMattock:; [$c752]
; ; Run the "Got Mattock" IScript. ; ; IScript 0x88 via jump to IScripts_Begin. ;
[c752]LDA #$88; 0x88 == Mattock picked up IScript. [c754]JSR MMC1_LoadBankAndJump; Run IScript: [c757].byte BANK_12_LOGIC; Bank = 12 [c758].word IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c75a]@_afterFarJump:; [$c75a] [c75a]LDA #$08; 0x08 == Item picked up sound. [c75c]JSR Sound_PlayEffect; Play it.
; ; Load the Mattock into the inventory. ;
[c75f]LDA #$09; 0x09 == Mattock [c761]JMP Player_PickUpItem; Pick up the Mattock.
;============================================================================ ; Handle picking up an item. ; ; INPUTS: ; A: ; The sprite entity that was interacted with. ; ; This will determine the proper handler for the ; item pick-up. ; ; OUTPUTS: ; See CALLS. ; ; CALLS: ; Player_PickUpBattleHelmet ; Player_PickUpBattleSuit ; Player_PickUpBlackOnyx ; Player_PickUpDragonSlayer ; Player_PickUpElixir ; PRG15::87cf ; Player_PickUpHourGlass ; Player_PickUpMagicalRod ; Player_PickUpMattock ; Player_PickUpMattockWithQuest ; Player_PickUpOintment ; Player_PickUpPendant ; Player_PickUpPoison ; Player_PickUpRedPotion ; Player_PickUpWingBoots ; Player_PickUpWingBootsWithQuest ; ; XREFS: ; Player_HandleTouchItem ;============================================================================
[c764]Player_PickUp:; [$c764]
; ; Check if this is the Mattock. ;
[c764]CMP #$50; 0x50 == Mattock [c766]BEQ Player_PickUpMattock; If 0x50, this is the Mattock. Pick it up.
; ; Check if this is the Magical Rod. ;
[c768]CMP #$57; 0x57 == Magical Rod [c76a]BNE @_checkBattleSuit [c76c]JMP Player_PickUpMagicalRod; This is the Magical Rod. Pick it up.
; ; Check if this is the battle suit. ;
[c76f]@_checkBattleSuit:; [$c76f] [c76f]CMP #$58; 0x58 == Battle Suit [c771]BNE @_checkBattleHelmet [c773]JMP Player_PickUpBattleSuit; This is the Battle Suit. Pick it up.
; ; Check if this is the battle helmet. ;
[c776]@_checkBattleHelmet:; [$c776] [c776]CMP #$59; 0x59 == Battle Helmet [c778]BEQ Player_PickUpBattleHelmet; This is the Battle Helmet. Pick it up.
; ; Check if this is the Dragon Slayer. ;
[c77a]CMP #$5a; 0x5A == Dragon Slayer [c77c]BEQ Player_PickUpDragonSlayer; This is the Dragon Slayer. Pick it up.
; ; Check if this is the Mattock. ;
[c77e]CMP #$5b; 0x5B == Mattock [c780]BEQ Player_PickUpMattockWithQuest; This is the Mattock. Pick it up.
; ; Check if this is the Wing Boots. ;
[c782]CMP #$55; 0x55 == Wing Boots [c784]BNE @_checkWingBootsQuest8 [c786]JMP Player_PickUpWingBoots; This is the Wing Boots. Pick it up.
; ; Check if this is the Wing Boots (with Quest 0x08). ;
[c789]@_checkWingBootsQuest8:; [$c789] [c789]CMP #$5c; 0x5C == Wing Boots for Quest 8 [c78b]BNE @_checkHourGlass [c78d]JMP Player_PickUpWingBootsWithQuest; This is the Wing Boots for Quest 8. Pick it up.
; ; Check if this is the Hour Glass. ;
[c790]@_checkHourGlass:; [$c790] [c790]CMP #$56; 0x56 == Hour Glass [c792]BNE @_checkRedPotion [c794]JMP Player_PickUpHourGlass; This is the Hour Glass. Pick it up.
; ; Check if this is the Red Potion. ;
[c797]@_checkRedPotion:; [$c797] [c797]CMP #$5d; 0x5D == Red Potion (1) [c799]BNE @_checkPoison [c79b]JMP Player_PickUpRedPotion; This is the Red Potion (1). Pick it up.
; ; Check if this is the Poison. ;
[c79e]@_checkPoison:; [$c79e] [c79e]CMP #$5e; 0x5E == Poison [c7a0]BNE @_checkGlove1 [c7a2]JMP Player_PickUpPoison; This is the Poison. Pick it up. [c7a5]@_checkGlove1:; [$c7a5] [c7a5]CMP #$5f; 0x5F == Glove [c7a7]BEQ Player_PickUpGlove; This is the Glove. Pick it up.
; ; Check if this is the Ointment. ;
[c7a9]CMP #$60; 0x60 == Ointment [c7ab]BNE @_checkGlove2 [c7ad]JMP Player_PickUpOintment; This is the Ointment. Pick it up. [c7b0]@_checkGlove2:; [$c7b0] [c7b0]SEC [c7b1]SBC #$48 [c7b3]TAY [c7b4]BEQ Player_PickUpGlove; This is the Glove. Pick it up.
; ; Check if this is the Black Onyx. ;
[c7b6]DEY; 0x49 == Black Onyx [c7b7]BEQ Player_PickUpBlackOnyx; If 0x49, this is the Black Onyx. Pick it up.
; ; Check if this is the Pendant. ;
[c7b9]DEY; 0x4A == Pendant [c7ba]BEQ Player_PickUpPendant; If 0x4A, this is the pendant. Pick it up.
; ; Check if this is the Red Potion (another variant). ;
[c7bc]DEY; 0x4B == Red Potion (2) [c7bd]BEQ Player_PickUpRedPotion; If 0x4B, this is the Red Potion (2). Pick it up.
; ; Check if this is the Poison. ;
[c7bf]DEY; 0x4C == Poison [c7c0]BEQ Player_PickUpPoison; If 0x4C, this is the Poison. Pick it up.
; ; Check if this is the Elixir. ;
[c7c2]DEY; 0x4D == Elixir [c7c3]BNE @_checkOintment [c7c5]JMP Player_PickUpElixir; This is the Elixir. Pick it up.
; ; Check if this is the Ointment. ;
[c7c8]@_checkOintment:; [$c7c8] [c7c8]DEY; 0x4E == Ointment [c7c9]BNE @_exit [c7cb]JMP Player_PickUpOintment; This is the Ointment. Pick it up. [c7ce]@_exit:; [$c7ce] [c7ce]RTS
;============================================================================ ; Handle picking up the Glove. ; ; This will invoke the IScript, play the sound, and enable ; the Glove state. ; ; Picking up the glove awards 100XP. ; ; INPUTS: ; None ; ; OUTPUTS: ; None ; ; CALLS: ; MMC1_LoadBankAndJump ; Sound_PlayEffect ; Player_Add100XP ; ; XREFS: ; Player_PickUp ;============================================================================
[c7cf]Player_PickUpGlove:; [$c7cf]
; ; Run the "Got Glove" IScript. ; ; IScript 0x92 via jump to IScripts_Begin. ;
[c7cf]LDA #$92; 0x92 == Glove picked up IScript. [c7d1]JSR MMC1_LoadBankAndJump; Run IScript: [c7d4].byte BANK_12_LOGIC; Bank = 12 [c7d5].word IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c7d7]@_afterFarJump:; [$c7d7] [c7d7]LDA #$08; 0x08 == Item picked up sound. [c7d9]JSR Sound_PlayEffect; Play it.
; ; Activate the Glove by setting its duration, and then ; add 100XP to the player. ;
[c7dc]LDA #$14; A = 20 seconds. [c7de]STA a:DurationGlove; Set as the Glove duration. [c7e1]JMP Player_Add100XP; Give the player 100XP.
;============================================================================ ; Handle picking up the Black Onyx. ; ; This will invoke the IScript, play the sound, and add ; the Black Onyx to the special items inventory. ; ; INPUTS: ; SpecialItems: ; The player's special items. ; ; OUTPUTS: ; SpecialItems: ; The updated special items. ; ; CALLS: ; MMC1_LoadBankAndJump ; Sound_PlayEffect ; ; XREFS: ; Player_PickUp ;============================================================================
[c7e4]Player_PickUpBlackOnyx:; [$c7e4]
; ; Run the "Got Black Onyx" IScript. ; ; IScript 0x8E via jump to IScripts_Begin. ;
[c7e4]LDA #$8e; 0x8E == Black Onyx picked up IScript. [c7e6]JSR MMC1_LoadBankAndJump; Run IScript: [c7e9].byte BANK_12_LOGIC; Bank = 12 [c7ea].word IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c7ec]@_afterFarJump:; [$c7ec] [c7ec]LDA #$08; 0x08 == Item picked up sound. [c7ee]JSR Sound_PlayEffect; Play it.
; ; Add the item to the player's special items list. ;
[c7f1]LDA a:SpecialItems; Load the current special items bitmask. [c7f4]ORA #$01; Bit 0x01 == Black Onyx. [c7f6]STA a:SpecialItems; Save it back out. [c7f9]RTS
;============================================================================ ; Handle picking up the Pendant. ; ; This will invoke the IScript, play the sound, and add ; the Pendant to the special items inventory. ; ; INPUTS: ; SpecialItems: ; The player's special items. ; ; OUTPUTS: ; SpecialItems: ; The updated special items. ; ; CALLS: ; MMC1_LoadBankAndJump ; Sound_PlayEffect ; ; XREFS: ; Player_PickUp ;============================================================================
[c7fa]Player_PickUpPendant:; [$c7fa]
; ; Run the "Got Glove" IScript. ; ; IScript 0x8F via jump to IScripts_Begin. ;
[c7fa]LDA #$8f; 0x8F = Pendant picked up IScript. [c7fc]JSR MMC1_LoadBankAndJump; Run IScript: [c7ff].byte BANK_12_LOGIC; Bank = 12 [c800].word IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c802]@_afterFarJump:; [$c802] [c802]LDA #$08; 0x08 = Item picked up sound. [c804]JSR Sound_PlayEffect; Play it.
; ; Add the item to the player's special items list. ;
[c807]LDA a:SpecialItems; Load the curent special items bitmask. [c80a]ORA #$02; Bit 0x02 == Pendant. [c80c]STA a:SpecialItems; Save it back out. [c80f]RTS
;============================================================================ ; Handle picking up the Magical Rod. ; ; This will invoke the IScript, play the sound, and add ; the Magical Rod to the special items inventory. ; ; INPUTS: ; SpecialItems: ; The player's special items. ; ; OUTPUTS: ; SpecialItems: ; The updated special items. ; ; CALLS: ; MMC1_LoadBankAndJump ; Sound_PlayEffect ; ; XREFS: ; Player_PickUp ;============================================================================
[c810]Player_PickUpMagicalRod:; [$c810]
; ; Run the "Got Magical Rod" IScript. ; ; IScript 0x90 via jump to IScripts_Begin. ;
[c810]LDA #$90; 0x90 == Magical Rod picked up IScript. [c812]JSR MMC1_LoadBankAndJump; Run IScript: [c815].byte BANK_12_LOGIC; Bank = 12 [c816].word IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c818]@_afterFarJump:; [$c818] [c818]LDA #$08; 0x08 == Item picked up sound. [c81a]JSR Sound_PlayEffect; Play it.
; ; Add the Magical Rod to the inventory. ;
[c81d]LDA a:SpecialItems; Load the current special items bitmask. [c820]ORA #$04; Bit 0x04 == Magical Rod. [c822]STA a:SpecialItems; Store it. [c825]RTS
;============================================================================ ; Handle picking up the Red Potion. ; ; This will invoke the IScript, play the sound, and add ; the Red Potion to the inventory. ; ; INPUTS: ; None ; ; OUTPUTS: ; X: ; Set to the current sprite index. ; ; CALLS: ; MMC1_LoadBankAndJump ; Sound_PlayEffect ; Player_PickUpItem ; ; XREFS: ; Player_PickUp ;============================================================================
[c826]Player_PickUpRedPotion:; [$c826]
; ; Run the "Got Red Potion" IScript. ; ; IScript 0x87 via jump to IScripts_Begin. ;
[c826]LDA #$87; 0x87 == Red Potion picked up IScript. [c828]JSR MMC1_LoadBankAndJump; Run IScript: [c82b].byte BANK_12_LOGIC; Bank = 12 [c82c].word IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c82e]@_afterFarJump:; [$c82e] [c82e]LDA #$08; 0x08 == Item picked up sound. [c830]JSR Sound_PlayEffect; Play it.
; ; Add the Red Potion to the inventory. ;
[c833]LDA #$10; 0x10 == Red Potion. [c835]JSR Player_PickUpItem; Pick up the item. [c838]LDX a:CurrentSpriteIndex; X = current sprite index (restored?) [c83b]RTS
;============================================================================ ; Handle picking up the Poison. ; ; This will invoke the IScript, play the sound, and decrease ; the player health. ; ; INPUTS: ; None ; ; OUTPUTS: ; X: ; Set to the current sprite index. ; ; CALLS: ; MMC1_LoadBankAndJump ; Sound_PlayEffect ; Player_ReduceHP ; ; XREFS: ; Player_PickUp ;============================================================================
[c83c]Player_PickUpPoison:; [$c83c]
; ; Run the "Got Poison" IScript. ; ; IScript 0x91 via jump to IScripts_Begin. ;
[c83c]LDA #$91; 0x91 = Poison picked up IScript. [c83e]JSR MMC1_LoadBankAndJump; Run IScript: [c841].byte BANK_12_LOGIC; Bank = 12 [c842].word IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for the player taking damage. ;
[c844]@_afterFarJump:; [$c844] [c844]LDA #$04; 0x084 = Player took damage sound. [c846]JSR Sound_PlayEffect; Play it.
; ; Set temporary invincibility frames for the player. ;
[c849]LDA #$3c; A = 60 iframes [c84b]STA Player_InvincibilityPhase; Set it as the starting invincibility phase.
; ; Mark the player as hit. ;
[c84d]LDA Player_StatusFlag; Load the player's status flags. [c84f]ORA #$02; Bit 0x02 == Player was hit. [c851]STA Player_StatusFlag; Save it.
; ; Remove 16 points from the player's health. ;
[c853]LDA #$00; A = 0 [c855]STA a:Arg_PlayerHealthDelta_L; Store as the lower byte of HP to remove. [c858]LDA #$10; A = 16 [c85a]STA a:Arg_PlayerHealthDelta_U; Store as the upper byte of HP to remove. [c85d]JSR Player_ReduceHP; Reduce the HP by that amount. [c860]LDX a:CurrentSpriteIndex; X = current sprite index (restored?) [c863]RTS
;============================================================================ ; Handle picking up the Elixir. ; ; This will invoke the IScript, play the sound, and add ; the Elixir to the special items inventory. ; ; INPUTS: ; None ; ; OUTPUTS: ; X: ; Set to the current sprite index. ; ; CALLS: ; MMC1_LoadBankAndJump ; Sound_PlayEffect ; ; XREFS: ; Player_PickUp ;============================================================================
[c864]Player_PickUpElixir:; [$c864]
; ; Run the "Got Elixir" IScript. ; ; IScript 0x86 via jump to IScripts_Begin. ;
[c864]LDA #$86; 0x86 == Elixir picked up IScript. [c866]JSR MMC1_LoadBankAndJump; Run IScript: [c869].byte BANK_12_LOGIC; Bank = 12 [c86a].word IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c86c]@_afterFarJump:; [$c86c] [c86c]LDA #$08; 0x08 == Item picked up sound. [c86e]JSR Sound_PlayEffect; Play it.
; ; Add the Elixir to the inventory. ;
[c871]LDA a:SpecialItems; Load the current special items bitmask. [c874]ORA #$08; Bit 0x08 == Elixir. [c876]STA a:SpecialItems; Save it back out. [c879]RTS
;============================================================================ ; Handle picking up the Ointment. ; ; This will invoke the IScript, play the sound, and set ; the Ointment state. ; ; INPUTS: ; None ; ; OUTPUTS: ; None ; ; CALLS: ; MMC1_LoadBankAndJump ; Sound_PlayEffect ; Player_Add100XP ; ; XREFS: ; Player_PickUp ;============================================================================
[c87a]Player_PickUpOintment:; [$c87a]
; ; Run "Got Ointment" IScript. ; ; IScript 0x94 via jump to IScripts_Begin. ;
[c87a]LDA #$94; 0x94 == Ointment picked up IScript. [c87c]JSR MMC1_LoadBankAndJump; Run IScript: [c87f].byte BANK_12_LOGIC; Bank = 12 [c880].word IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c882]@_afterFarJump:; [$c882] [c882]LDA #$08; 0x08 == Item picked up sound. [c884]JSR Sound_PlayEffect; Play it.
; ; Activate the Ointment by setting its duration, and then ; add 100XP to the player. ; ; Duration is initially set to a base of 30, but will be ; modified by level separately. ;
[c887]LDA #$1e; A = 30 seconds. [c889]STA a:DurationOintment; Set as the Ointment duration. [c88c]JMP Player_Add100XP; Give the player 100XP.
;============================================================================ ; Count down all the player's special items. ; ; This will count down the Hour Glass, Wing Boots, ; Glove, and Ointment durations. ; ; INPUTS: ; None ; ; OUTPUTS: ; None ; ; CALLS: ; Game_DecHourGlassDuration ; Game_DecWingBootsDuration ; Game_DecGloveDuration ; Game_DecOintmentDuration ; ; XREFS: ; EndGame_MainLoop ; Game_MainLoop ;============================================================================
[c88f]GameLoop_CountdownItems:; [$c88f] [c88f]JSR Game_DecHourGlassDuration [c892]JSR Game_DecWingBootsDuration [c895]JSR Game_DecGloveDuration [c898]JMP Game_DecOintmentDuration
;============================================================================ ; Decrement the Glove's duration. ; ; INPUTS: ; DurationGlove: ; The remaining duration for the glove. ; ; InterruptCounter: ; The current interrupt counter. ; ; OUTPUTS: ; DurationGlove: ; The new duration for the glove. ; ; CALLS: ; MMC1_LoadBankAndJump ; ; XREFS: ; GameLoop_CountdownItems ;============================================================================
[c89b]Game_DecGloveDuration:; [$c89b]
; ; Check if the Glove is active. ;
[c89b]LDA a:DurationGlove; Load the Glove's duration. [c89e]BMI @_return; If it's 0xFF (not activated), return.
; ; Check if a second has passed. ;
[c8a0]LDA InterruptCounter; Load the interrupt counter. [c8a2]AND #$3f; Check if it's an even second boundary. [c8a4]BNE @_return; If not, return.
; ; Decrement and check if the Glove is still active. ;
[c8a6]DEC a:DurationGlove; Decrement the Glove's duration by 1 second. [c8a9]BPL @_return; If it's >= 0, it's still active. Return.
; ; Countdown finished. ; ; Run "Glove is gone" IScript. ; ; IScript 0x93 via jump to IScripts_Begin. ;
[c8ab]LDA #$93; 0x93 == Glove is gone IScript. [c8ad]JSR MMC1_LoadBankAndJump; Run the IScript: [c8b0].byte BANK_12_LOGIC; Bank = 12 [c8b1].word IScripts_Begin-1; Address = IScripts_Begin [c8b3]@_return:; [$c8b3] [c8b3]RTS
;============================================================================ ; Decrement the Ointment's duration. ; ; INPUTS: ; DurationOintment: ; The remaining duration for the Ointment. ; ; InterruptCounter: ; The current interrupt counter. ; ; OUTPUTS: ; DurationOintment: ; The new duration for the Ointment. ; ; CALLS: ; MMC1_LoadBankAndJump ; ; XREFS: ; GameLoop_CountdownItems ;============================================================================
[c8b4]Game_DecOintmentDuration:; [$c8b4]
; ; Check if the Ointment is active. ;
[c8b4]LDA a:DurationOintment; Load the Ointment's duration. [c8b7]BMI @_return; If it's 0xFF (not activated), return.
; ; Check if a second has passed. ;
[c8b9]LDA InterruptCounter; Load the interrupt counter. [c8bb]AND #$3f; Check if it's an even second boundary. [c8bd]BNE @_return; If not, return.
; ; Decrement and check if the Ointment is still active. ;
[c8bf]DEC a:DurationOintment; Decrement the Ointment's duration by 1 second. [c8c2]BPL @_return; If it's >= 0, it's still active. Return.
; ; Countdown finished. ; ; Run "Ointment is gone" IScript. ; ; IScript 0x95 via jump to IScripts_Begin. ;
[c8c4]LDA #$95; 0x95 == Ointment is gone IScript. [c8c6]JSR MMC1_LoadBankAndJump; Run the IScript: [c8c9].byte BANK_12_LOGIC; Bank = 12 [c8ca].word IScripts_Begin-1; Address = IScripts_Begin [c8cc]@_return:; [$c8cc] [c8cc]RTS
;============================================================================ ; Pick up a standard item and place it in the inventory. ; ; This will be placed in the last available inventory slot. ; If the inventory is full, this does nothing. ; ; INPUTS: ; A: ; The ID of the item to place in the inventory. ; ; NumberOfItems: ; The number of items the player has. ; ; OUTPUTS: ; ItemInventory: ; The updated item inventory. ; ; NumberOfItems: ; The new number of items the player has. ; ; XREFS: ; Player_PickUpHourGlass ; Player_PickUpMattock ; Player_PickUpRedPotion ; Player_PickUpWingBoots ;============================================================================
[c8cd]Player_PickUpItem:; [$c8cd]
; ; Check first if the inventory is full. ;
[c8cd]LDX a:NumberOfItems; Load the number of items in the inventory. [c8d0]CPX #$08; Is there room? [c8d2]BCS @_return; If full, exit.
; ; There's room. Set the item in the inventory and increment ; the item counter. ;
[c8d4]STA ItemInventory,X; Store the item in the next available slot. [c8d7]INX; Set the next available slot. [c8d8]STX a:NumberOfItems [c8db]@_return:; [$c8db] [c8db]RTS
;============================================================================ ; Clear the selected item picture from the HUD. ; ; This will loop through the 4 tiles of the selected item ; attribute area in the HUD, setting all to 0. ; ; INPUTS: ; None ; ; OUTPUTS: ; PPUBuffer_WriteOffset: ; New upper bounds of the PPU buffer. ; ; PPU_TargetAddr: ; PPU_TargetAddr+1: ; Clobbered. ; ; CALLS: ; PPUBuffer_QueueCommandOrLength ; ; XREFS: ; Game_UnlockDoorWithUsableItem ; Player_ClearSelectedItem ;============================================================================
[c8dc]UI_ClearSelectedItemPic:; [$c8dc]
; ; Set the draw position to 0x13C0 (top-left of selected ; item tiles). ;
[c8dc]LDA #$13; A = 0x13 [c8de]STA a:PPU_TargetAddr_U; Set as upper byte. [c8e1]LDA #$c0; A = 0xC0 [c8e3]STA a:PPU_TargetAddr; Set as lower byte.
; ; Begin the draw loop. This will draw 4 tiles of the sprite. ; All 4 tiles making up the sprite are adjacent within this ; tile attribute. ;
[c8e6]LDX #$04; X = 4 (counter). [c8e8]@_vertLoop:; [$c8e8] [c8e8]TXA; A = X [c8e9]PHA; Push A to the stack.
; ; Set the draw length as 16 bytes (this tile). ;
[c8ea]LDA #$10; A = 16 [c8ec]JSR PPUBuffer_QueueCommandOrLength; Write as the length. [c8ef]LDY #$10; Y = 16 [c8f1]LDA #$00; A = 0 [c8f3]@_horizLoop:; [$c8f3] [c8f3]STA PPUBuffer,X; Write a 0 to the PPU buffer. [c8f6]INX; X++ [c8f7]DEY; Y-- [c8f8]BNE @_horizLoop; If not 0, loop.
; ; This row is finished. Prepare to draw the next (if any). ;
[c8fa]STX PPUBuffer_WriteOffset; Store X as the new upper bounds. [c8fc]LDA a:PPU_TargetAddr; A = target address to draw to. [c8ff]CLC [c900]ADC #$10; Increment by 16 (next tile). [c902]STA a:PPU_TargetAddr; Store as the new lower byte of the target address. [c905]LDA a:PPU_TargetAddr_U; Load the upper byte. [c908]ADC #$00; Add the carry flag, if any. [c90a]STA a:PPU_TargetAddr_U; And save it. [c90d]PLA; Pull A from the stack (full 0..4 loop counter). [c90e]TAX; X = A [c90f]DEX; X-- [c910]BNE @_vertLoop; If > 0, loop. [c912]RTS
;============================================================================ ; Main entry point for the game. ; ; This sets up the PPU, clears out the PPU buffer, ; initializes the MMC1 chip, the screen, the music, ; and then sets up the start splash screen. ; ; INPUTS: ; {@symbl PPUSTATUS}: ; Read to ensure writes occur in the right order. ; ; OUTPUTS: ; Temp_0200: ; PPUBuffer: ; ScreenBuffer: ; SPRITE_0_RANGE_1_START: ; CurrentSprites_InternalBehaviorStates: ; FirstColumnInRightScreen: ; Cleared. ; ; PPUMASK: ; Cleared. ; ; PPUCTRL: ; Set to the initial flags for writing. ; ; CALLS: ; Game_InitMMCAndBank ; Game_InitScreenAndMusic ; Game_InitStateForStartScreen ; ; XREFS: ; IScriptAction_FinishGame ;============================================================================
[c913]Game_Init:; [$c913] [c913]SEI [c914]CLD [c915]LDX #$ff; X = 0xFF [c917]TXS; Push to stack.
; ; Set the PPU settings. ; ; For PPUMASK: ; ; * Color ; * Show background and sprites in leftmost 8 pixels ; ; ; For PPUCTRL: ; ; * Base nametable address = 0x2000 ; * VRAM address increment per CPU read/write: add 1, going across ; * Sprite pattern table address for 8x8 sprites: 0x1000 ; * Background pattern table address: 0x1000 ; * Sprite size: 8x8 pixels ; * PPU master/slave select: Read backdrop from EXT pins ; * Vblank NMI disabled ;
[c918]LDA #$00; A = 0 [c91a]STA a:PPUMASK; Set as the PPU mask. [c91d]LDA #$18; A = 0x18 [c91f]STA a:PPUCTRL; Set as the PPU control.
; ; Read the PPUSTATUS register several times before writing. ; ; NESdev says this is common to ensure writes occur in the ; correct order. ;
[c922]LDA a:PPUSTATUS; Read PPU status. [c925]@_readPPUStatus1:; [$c925] [c925]LDA a:PPUSTATUS; Read PPU status. [c928]BPL @_readPPUStatus1; If > -1, loop. [c92a]@_readPPUStatus2:; [$c92a] [c92a]LDA a:PPUSTATUS; Read PPU status. [c92d]BPL @_readPPUStatus2; If > -1, loop.
; ; Set PPUMASK to 0 to allow transferring large amounts of ; Data to VRAM. ;
[c92f]LDA #$00; A = 0 [c931]STA a:PPUMASK; Set as the PPU mask. [c934]LDX #$ff; X = 0xFF [c936]TXS; Transfer to stack.
; ; Clear RAM, setting all ranges of bytes used for state to 0. ;
[c937]LDX #$00; X = 0 (loop counter) [c939]@_loop:; [$c939] [c939]CPX #$fc; Is X >= 252? [c93b]BCS @_clearState; If so, jump. [c93d]STA Temp_00,X; Clear memory at address X. [c93f]@_clearState:; [$c93f] [c93f]STA Temp_0200,X; Clear memory at $0200 + X [c942]STA CurrentSprites_InternalBehaviorStates_4_,X; Clear memory at $0300 + X [c945]STA FirstColumnInRightScreen_10_,X; Clear memory at $0400 + X [c948]STA PPUBuffer,X; Clear memory at $0500 + X [c94b]STA ScreenBuffer,X; Clear memory at $0600 + X [c94e]STA SPRITE_0_RANGE_1_START,X; Clear memory at $0700 + X [c951]INX; X++ [c952]BNE @_loop; If X != 0, loop. [c954]JSR Game_InitMMCAndBank; Initialize the MMC1 chip and bank. [c957]JSR Game_InitScreenAndMusic; Initialize the screen and music. [c95a]JMP Game_InitStateForStartScreen; Initialize the start screen.
; ; Check whether the OAM needs to be reset. ; ; ; XREFS: ; OnInterrupt ;
[c95d]OnInterrupt__updatePPUOAMAndAudio:; [$c95d] [c95d]LDA Game_NeedOAMReset; Check the reset flag. [c95f]BEQ OnInterrupt__updatePPUAndAudio; If 0, don't reset. Jump to PPU/audio management. [c961]LDA #$00 [c963]STA a:OAMADDR; Reset OAMADDR to 0. [c966]STA Game_NeedOAMReset; Clear the reset flag. [c968]LDA #$07 [c96a]STA a:OAMDMA; Initialize the OAM, writing from $0700.
; ; Update the PPU. ; ; ; XREFS: ; OnInterrupt ;
[c96d]OnInterrupt__updatePPUAndAudio:; [$c96d] [c96d]JSR PPU_HandleOnInterrupt; Run PPU on-interrupt handlers.
; ; Update the audio so music keeps playing. ; ; ; XREFS: ; OnInterrupt ;
[c970]OnInterrupt__updateAudio:; [$c970] [c970]INC InterruptCounter; Increment the interrupt counter. [c972]LDA a:CurrentROMBank; Load the current ROM bank. [c975]PHA; Push it to the stack. [c976]LDX #$05 [c978]JSR MMC1_EnsurePRG; Switch to bank 5. [c97b]JSR thunk_Music_HandleOnInterrupt; Run audio interrupt handlers. [c97e]JSR thunk_SoundEffects_HandleOnInterrupt; Run sound playback interrupt handlers. [c981]PLA; Pop the bank from the stack. [c982]TAX [c983]JSR MMC1_EnsurePRG; Switch back to the bank. [c986]JMP OnInterrupt__popAndReturn; Prepare to exit the interrupt handler.
; ; Main interrupt handling code won't be run. Update the ; paused interrupt counter state and clear the PPU mask ; to enable transfering large amounts of data to VRAM. ; ; ; XREFS: ; OnInterrupt ;
[c989]OnInterrupt__noProcessInterrupts:; [$c989] [c989]LDX PauseInterruptCounter; Load the interrupt counter. [c98b]CPX #$02; Is it < 2? [c98d]BCS @_clearPPUMask; If so, jump. [c98f]INC PauseInterruptCounter; Increment the counter. [c991]@_clearPPUMask:; [$c991] [c991]LDA #$00 [c993]STA a:PPUMASK; Clear PPUMASK. [c996]JMP OnInterrupt__updateAudio; Update the audio.
;============================================================================ ; Main interrupt handler for the game. ; ; This controls PPU interactions, audio playback, and ; controller input. Normally, these will run every ; two frames. ; ; Processing of these can be enabled or disabled ; by other code as needed by other code by setting ; Game_InterruptHandlersEnabled and ; Game_NeedOAMReset. ; ; Even if main input handlers are disabled, this will ; generally write to the PPU and play audio (so music ; doesn't skip). ; ; INPUTS: ; Game_InterruptHandlersEnabled: ; Whether to run game state interrupt handlers. ; ; PauseInterruptCounter: ; A counter indicating if interrupt handlers have ; been paused (range 0..2). ; ; Game_InterruptsHandledLatch: ; The current frame toggle. Game state interrupt ; handlers will only be run if 0. ; ; InterruptCounter: ; The current interrupt counter to increment. ; ; Game_NeedOAMReset: ; If > 0, OAM will be reset. ; ; OUTPUTS: ; PPUMASK: ; May be cleared. ; ; Game_InterruptsHandledLatch: ; May be set to 1 to skip the next frame. ; ; InterruptCounter: ; The updated interrupt counter. ; ; PauseInterruptCounter: ; Incremented if interrupt handlers are paused ; (capped to 2). ; ; Game_NeedOAMReset: ; OAMADDR ; May be set to 0. ; ; OAMDMA: ; May be set to 7. ; ; CALLS: ; MMC1_EnsurePRG ; Music_HandleOnInterrupt ; Input_HandleOnInterrupt ; PPU_HandleOnInterrupt ; SoundEffects_HandleOnInterrupt ;============================================================================
[c999]OnInterrupt:; [$c999]
; ; Push all registers to the stack. ;
[c999]PHA; Push A to the stack. [c99a]TXA; Set A = X. [c99b]PHA; Push A (X) to the stack. [c99c]TYA; Set A = Y. [c99d]PHA; Push A (Y) to the stack.
; ; Check what will be handled this interrupt. ;
[c99e]LDA Game_InterruptHandlersEnabled; Check whether interrupt handling code should be run. [c9a0]BEQ OnInterrupt__noProcessInterrupts; If not, jump.
; ; Interrupts may be run. See if it's time to run them. ;
[c9a2]LDA Game_InterruptsHandledLatch; Check if we're on a frame where we should run interrupts. [c9a4]BNE OnInterrupt__updatePPUOAMAndAudio; Jump to update the PPU, OAM, and Audio.
; ; Ready to handle routine operations. ;
[c9a6]INC Game_InterruptsHandledLatch; Flip the toggle.
; ; Reset the OAM. ; ; This will write $00 to OAMADDR and then use OAMDMA. ;
[c9a8]LDA #$00 [c9aa]STA a:OAMADDR; Clear OAMADDR. [c9ad]STA Game_NeedOAMReset; Turn off the reset flag. [c9af]LDA #$07 [c9b1]STA a:OAMDMA; Initialize the OAM, writing from SPRITE_0_RANGE_1_START ($0700).
; ; Update the PPU state. ;
[c9b4]JSR PPU_HandleOnInterrupt
; ; Switch to bank 5 (audio logic) and run audio updates. ;
[c9b7]LDA a:CurrentROMBank; Load the current bank. [c9ba]PHA; Push it to the stack. [c9bb]LDX #$05 [c9bd]JSR MMC1_EnsurePRG; Switch to bank 5. [c9c0]JSR thunk_Music_HandleOnInterrupt; Run audio interrupt handling code. [c9c3]JSR thunk_SoundEffects_HandleOnInterrupt; Run sound playback interrupt handling code. [c9c6]PLA; Pop the previous bank from the stack. [c9c7]TAX [c9c8]JSR MMC1_EnsurePRG; Switch back to it.
; ; Handle input management and final state. ;
[c9cb]JSR Input_HandleOnInterrupt; Run input interrupt handlers. [c9ce]INC InterruptCounter; Increment the interrupt counter.
; ; Restore all registers from the stack. ; ; ; XREFS: ; OnInterrupt ;
[c9d0]OnInterrupt__popAndReturn:; [$c9d0] [c9d0]PLA; Pull A from the stack. [c9d1]TAY; Set Y = A [c9d2]PLA; Pull A from the stack. [c9d3]TAX; Set X = A [c9d4]PLA; Pull A from the satck [c9d5]OnHardwareInterrupt:; [$c9d5] [c9d5]RTI; Return from interrupt
;============================================================================ ; Update the PPU on interrupt. ; ; This will run any screen scroll handlers, ensure the right ; PPU flags pertaining to scroll, and then set the new scroll ; screen and offsets. ; ; INPUTS: ; PPU_Mask: ; The screen mask to set. ; ; PPU_ControlFlags: ; The saved PPU control flags. ; ; Sprites_Sprite0Mode: ; The current sprite 0 placement mode for the ; screen, dictating the flags to set. ; ; PPU_ScrollScreen: ; The scroll screen to set. ; ; PPU_ScrollX: ; The horizontal scroll offset to set. ; ; OUTPUTS: ; PPUCTRL: ; PPUMASK: ; PPUSCROLL: ; The updated PPU state. ; ; CALLS: ; PPUBuffer_Draw ; Screen_RunWriteScrollDataHandler ; ; XREFS: ; OnInterrupt ;============================================================================
[c9d6]PPU_HandleOnInterrupt:; [$c9d6]
; ; Handle any scrolling-related screen updates and flush ; the PPU buffer to the screen. ;
[c9d6]JSR Screen_RunWriteScrollDataHandler; Run the handler for any screen scrolling. [c9d9]JSR PPUBuffer_Draw; Flush the PPU buffer to the screen. [c9dc]LDA PPU_Mask; Load the stored PPU mask. [c9de]STA a:PPUMASK; And set it on the PPU. [c9e1]LDA Sprites_Sprite0Mode; Load the sprite 0 mode. [c9e3]BMI @_setScroll; If 0xFF (no sprite 0), jump. [c9e5]LDA PPU_ForceLowerPatternTables; Load the Force Lower Pattern Tables setting. [c9e7]BEQ @_clearNametable2400; If not forcing, then jump.
; ; Clear the following flags: ; ; * Nametable 2400 ; * Sprite pattern 1000 ; * BGTable Addr 1000 ;
[c9e9]LDA PPU_ControlFlags; Load the saved PPU control flags. [c9eb]AND #$e6; Clear flags. [c9ed]STA a:PPUCTRL; Set it on the PPU. [c9f0]JMP @_resetScroll; Jump to update scroll positions.
; ; Clear the Nametable 2400 flag. ;
[c9f3]@_clearNametable2400:; [$c9f3] [c9f3]LDA PPU_ControlFlags [c9f5]AND #$fe [c9f7]STA a:PPUCTRL
; ; Reset the screen scroll. ;
[c9fa]@_resetScroll:; [$c9fa] [c9fa]LDA #$00 [c9fc]STA a:PPUSCROLL; Clear horizontal screen scroll. [c9ff]STA a:PPUSCROLL; Clear vertical screen scroll.
; ; Wait until sprite 0 isn't hit in the sprite update. ;
[ca02]@_waitSprite0NotHitLoop:; [$ca02] [ca02]BIT a:PPUSTATUS; Test the Sprite 0 Hit flag. [ca05]BVS @_waitSprite0NotHitLoop; If set, loop.
; ; Now wait until it is hit. ;
[ca07]@_waitSprite0Hit:; [$ca07] [ca07]BIT a:PPUSTATUS; Test the Sprite 0 Hit flag. [ca0a]BVC @_waitSprite0Hit; if not set, loop.
; ; Loop 160 times, giving the screen time to update. ;
[ca0c]LDX #$a0; X = 160 (loop counter). [ca0e]@_loop160Times:; [$ca0e] [ca0e]DEX; X-- [ca0f]BNE @_loop160Times; If > 0, loop.
; ; Set the scroll state for the PPU. ; ; This will set the nametable for the scroll screen and ; set the horizontal scroll offset. Vertical will stay 0. ;
[ca11]@_setScroll:; [$ca11] [ca11]LDA PPU_ScrollScreen; Load the current scroll screen. [ca13]AND #$01; Convert to an even/odd value (0 or 1) to select the nametable. [ca15]ORA PPU_ControlFlags; OR with the PPU controls. [ca17]STA a:PPUCTRL; Store as the new PPU control flags. [ca1a]LDA PPU_ScrollX; Load the calculated scroll X. [ca1c]STA a:PPUSCROLL; Write as the horizontal scroll offset. [ca1f]LDA #$00; Hard-code scroll Y as 0. [ca21]STA a:PPUSCROLL; Write as the vertical scroll offset. [ca24]RTS
;============================================================================ ; Wait for the next frame. ; ; This will wait for the next frame, looping until ; the frame change occurs. ; ; INPUTS: ; Game_InterruptsHandledLatch: ; The frame toggle flag. ; ; OUTPUTS: ; None ; ; XREFS: ; IScripts_UpdatePortraitAnimation ; PasswordScreen_HandleWrongPasswordAndWaitForInput ; PasswordScreen_WaitForInput ; PlayerMenu_HandleInventoryMenuInput ; PlayerMenu_Show ; PlayerMenu_ShowStatusMenu ; PlayerMenu_ShowSubmenu ; SplashAnimation_RunIntro ; SplashAnimation_RunOutro ; EndGame_Begin ; EndGame_MainLoop ; GameLoop_CheckPauseGame ; Game_MainLoop ; Game_SetupAndLoadOutsideArea ; Game_SetupEnterBuilding ; Game_SetupEnterScreen ; Game_SetupExitBuilding ; Game_SetupNewArea ; Game_ShowStartScreen ; Game_Start ; Player_FillHPAndMP ; Player_HandleDeath ; Player_UseMattock ; Player_UseRedPotion ;============================================================================
[ca25]WaitForNextFrame:; [$ca25] [ca25]LDA #$00; A = 0 [ca27]STA Game_InterruptsHandledLatch; Set as the frame toggle state. [ca29]@_loop:; [$ca29] [ca29]LDA Game_InterruptsHandledLatch; Load the frame toggle state from the interrupt handler. [ca2b]BEQ @_loop; If it's 0, loop. [ca2d]RTS; Else, return.
;============================================================================ ; Wait for the next interrupt. ; ; This will loop until the interrupt counter has changed. ; ; INPUTS: ; InterruptCounter: ; The interrupt counter. ; ; OUTTPUTS: ; None ; ; XREFS: ; Game_DropLadderToMascon ; Game_OpenPathToMascon ; Player_HandleDeath ; Screen_FadeToBlack ;============================================================================
[ca2e]WaitForInterrupt:; [$ca2e] [ca2e]LDA InterruptCounter; Load the interrupt count. [ca30]@_loop:; [$ca30] [ca30]CMP InterruptCounter; Has the interrupt counter changed? [ca32]BEQ @_loop; If not, loop. [ca34]RTS
;============================================================================ ; Handle input management on interrupt. ; ; This will read from the controller ports, storing ; the current button bitmasks and determining which buttons ; have been held down and which have changed since the last ; time the interrupt handler was run. ; ; Controller 2 is read for the purpose of the debug level ; switch code at Debug_ChooseArea. ; ; INPUTS: ; JOY1: ; JOY2: ; The controller inputs. ; ; Joy1_ButtonMask: ; The previous controller 1 button mask. ; ; OUTPUTS: ; Joy1_ButtonMask: ; The new controller 1 button mask. ; ; Joy1_ChangedButtonMask: ; The buttons that changed since the last interrupt. ; ; Joy1_PrevButtonMask: ; The buttons held down since last interrupt. ; ; Joy2_ButtonMask: ; The new controller 2 button mask. ; ; XREFS: ; OnInterrupt ;============================================================================
[ca35]Input_HandleOnInterrupt:; [$ca35]
; ; Store the current button mask as the new previous button mask. ;
[ca35]LDA Joy1_ButtonMask; A = Stored controller 1 button mask [ca37]STA Joy1_PrevButtonMask; Store as the previous button mask.
; ; Prepare to read the controller state. ;
[ca39]LDA #$01; A = 1 [ca3b]STA a:JOY1; Trigger loading controller 1 bits. [ca3e]LDA #$00 [ca40]STA a:JOY1; Prepare for read. [ca43]STA Joy1_ButtonMask; Reset controller 1 button mask to 0. [ca45]STA Joy2_ButtonMask; Reset controller 2 button mask to 0.
; ; Read the controller 1 state into the button mask. ;
[ca47]LDX #$08; X = 8 (loop counter) [ca49]@_loop1:; [$ca49] [ca49]LDA a:JOY1; A = Current controller 1 state. [ca4c]AND #$03; Take the first two bits. [ca4e]LSR A; Shift right 1. Bit 0 goes into Carry. [ca4f]ROL Joy1_ButtonMask; Rotate the target bitmask left, moving Carry into bit 0. [ca51]ORA Joy1_ButtonMask; OR to the generated bitmask. [ca53]STA Joy1_ButtonMask; Store as the new button mask. [ca55]DEX; X-- [ca56]BNE @_loop1; If > 0, loop.
; ; Read the controller 2 state into the button mask. ; ; This is only used for the debug level skip code. ;
[ca58]LDX #$08; X = 8 (loop counter). [ca5a]@_loop2:; [$ca5a] [ca5a]LDA a:JOY2; A = Current controller 2 state. [ca5d]AND #$01; Take the first bit. [ca5f]LSR A; Shift right 1. Bit 0 goes into Carry. [ca60]ROL Joy2_ButtonMask; Rotate the target bitmask left, moving Carry into bit 0. [ca62]DEX; X-- [ca63]BNE @_loop2; If > 0, loop.
; ; Compute the changed button mask between the last read and now. ;
[ca65]LDA Joy1_ButtonMask; Load the controller 1 button mask. [ca67]EOR Joy1_PrevButtonMask; XOR with the previously-changed, yielding the button differences. [ca69]AND Joy1_ButtonMask; AND with the new button mask. [ca6b]STA Joy1_ChangedButtonMask; And store as the changed button mask. [ca6d]RTS
;============================================================================ ; Return a pseudo-random value. ; ; Every time this is called, a new pseudo-random value will ; be returned. ; ; The set of random values is the first 256 bytes of bank ; 14. If any buttons are pressed, the button bitmask will ; affect the random value (the result value and button ; bitmask is XOR'd). ; ; INPUTS: ; Random_Offset: ; The current offset into the bank. ; ; Joy1_ButtonMask: ; The controller 1 button mask. ; ; OUTPUTS: ; A: ; The pseudo-random value. ; ; Random_Offset: ; The incremented offset (which will wrap from 0xFF ; to 0x00). ; ; XREFS: ; BScript_Action_RandomlyFlipXDirection ; BScript_Action_RandomlyFlipYDirection ;============================================================================
[ca6e]GetRandom:; [$ca6e] [ca6e]LDX Random_Offset; Load the current offset into the bank. [ca70]LDA Sprites_UpdateAll,X; Load the byte value at that offset. [ca73]EOR Joy1_ButtonMask; XOR with the controler 1 button mask. [ca75]INC Random_Offset; Increment the offset. [ca77]RTS
;============================================================================ ; Initialize all the game state. ; ; This is called early at startup to set initial state ; for the game, including the PPU, scroll positions, ; PPU attribute tables, sound, sound effects, pause flag, ; and more. ; ; INPUTS: ; None ; ; OUTPUTS: ; PPU_ControlFlags: ; The updated PPU control flags. ; ; PPU_ScrollX: ; The scroll X position (set to 0). ; ; PPU_ScrollY: ; The scroll Y position (set to 0). ; ; PPU_ScrollScreen: ; The new scroll screen (set to 0). ; ; PPU_Mask: ; The new screen color mode. ; ; Game_PausedState: ; Set to 0 (false). ; ; Maybe_Game_Ready: ; Set to 0 (false). ; ; PPU_ForceLowerPatternTables: ; Set to 0 (false). ; ; Game_InterruptHandlersEnabled: ; Set to 0 (false). ; ; Game_InterruptsHandledLatch: ; Set to 0 (false). ; ; CALLS: ; MMC1_UpdateROMBank ; PPU_InitAttributeAndNameTables ; PPU_InitVBlank ; Screen_ResetSpritesForNonGame ; thunk_SoundEffects_Init ; thunk_Audio_InitPlayingState ; ; XREFS: ; Game_Init ;============================================================================
[ca78]Game_InitScreenAndMusic:; [$ca78]
; ; Initialize PPU settings. ;
[ca78]LDA #$10 [ca7a]STA PPU_ControlFlags; Set PPU control flags to BGTable and Address $1000. [ca7c]LDA #$00 [ca7e]STA PPU_ScrollX; Clear scroll X. [ca80]STA PPU_ScrollScreen; Clear scroll screen. [ca82]STA PPU_ScrollY; Clear scroll Y. [ca84]LDA #$1e [ca86]STA PPU_Mask; Set screen color mode to enable sprites, BG, and showing left-most 8 pixels. [ca88]JSR Screen_ResetSpritesForNonGame; Reset sprites for a non-game screen. [ca8b]JSR PPU_InitAttributeAndNameTables; Initialize attributes and nametables.
; ; Stop any interrupt handling for the game. ;
[ca8e]LDA #$00 [ca90]STA Game_InterruptHandlersEnabled; Disable interrupt handlers. [ca92]STA a:Maybe_Game_Ready; Disable the game ready state.
; ; Switch to bank 5 and set up sound. ;
[ca95]LDA a:CurrentROMBank; Load the current ROM bank. [ca98]PHA; Push it to the stack. [ca99]LDX #$05; Bank 5 = Sound. [ca9b]JSR MMC1_UpdateROMBank; Switch to it. [ca9e]JSR thunk_Audio_InitPlayingState; Initialize the sound play state. [caa1]JSR thunk_SoundEffects_Init; Initialize sound. [caa4]PLA; Pop the previous bank from the stack. [caa5]TAX; X = previous bank. [caa6]JSR MMC1_UpdateROMBank; And switch back to it.
; ; Re-enable the game. ;
[caa9]LDA #$00 [caab]STA Game_InterruptsHandledLatch; Disable the interrupt handlers latch. [caad]STA a:Game_PausedState; Disable the pause state. [cab0]STA PPU_ForceLowerPatternTables; Disable forcing lower pattern tables. [cab2]JMP PPU_InitVBlank; Initialize VBlank.
;============================================================================ ; Set the PPU address to the specified value. ; ; INPUTS: ; A: ; The upper byte of the address. ; ; X: ; The lower byte of the address. ; ; OUTPUTS: ; PPUADDR: ; The updated address. ; ; XREFS: ; PPU_InitAttributeAndNameTables ;============================================================================
[cab5]PPU_SetAddr:; [$cab5] [cab5]STA a:PPUADDR; Set the upper byte. [cab8]STX a:PPUADDR; Set the lower byte. [cabb]RTS
;============================================================================ ; TODO: Document PPU_InitAttributeAndNameTables ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Game_InitScreenAndMusic ;============================================================================
[cabc]PPU_InitAttributeAndNameTables:; [$cabc] [cabc]LDA #$20 [cabe]STA Temp_PPU_NameTableValue
; ; Set address 0x2000: Name table 0. ; ; Write 32 to 8 bytes. ;
[cac0]LDA #$20 [cac2]LDX #$00 [cac4]JSR PPU_SetAddr [cac7]LDY #$08 [cac9]LDX #$00 [cacb]LDA Temp_PPU_NameTableValue [cacd]JSR PPU_FillGrid
; ; Set address 0x23C0: Attribute table 0. ; ; Write 64 entries of byte 0x55 to the table. This is color bit 01 ; for each quadrant of each sprite. ;
[cad0]LDA #$23 [cad2]LDX #$c0 [cad4]JSR PPU_SetAddr [cad7]LDX #$40 [cad9]LDY #$01 [cadb]LDA #$55 [cadd]JSR PPU_FillGrid
; ; Set address 0x27C0: Attribute table 1. ; ; Write 64 entries here with the same information. ;
[cae0]LDA #$27 [cae2]LDX #$c0 [cae4]JSR PPU_SetAddr [cae7]LDX #$40 [cae9]LDY #$01 [caeb]LDA #$55
; ; v-- Fall through --v ;
;============================================================================ ; Fills a grid in the PPU with a value. ; ; This writes a value to the PPU up to X times, and then ; again 256 * Y times. ; ; This seems a bit strange, but in practice it's never ; really used this way. Always 256 columns for every row, ; or only ever one row. ; ; INPUTS: ; A: ; The value to write. ; ; X: ; Initial number of columns for the first row ; (256 columns per row after). ; ; Y: ; The number of rows. ; ; OUTPUTS: ; PPUDATA: ; Updated with the new written data. ; ; XREFS: ; PPU_FillGrid ; PPU_InitAttributeAndNameTables ;============================================================================
[caed]PPU_FillGrid:; [$caed] [caed]STA a:PPUDATA; Write the value to PPUDATA. [caf0]DEX; X-- [caf1]BNE PPU_FillGrid; If X > 0, loop.
; ; An inner loop was finished. Decrement and begin again. ; ; It appears it should write 256 more times on this iteration. ;
[caf3]DEY; Y-- [caf4]BNE PPU_FillGrid; If Y > 0, loop [caf6]RTS
;============================================================================ ; Wait until all buffered data to the PPU is flushed. ; ; INPUTS: ; PPUBuffer_WriteOffset: ; The upper bounds of the PPU buffer to write. ; ; PPUBuffer_ReadOffset: ; The current offset within the PPU buffer to write. ; ; OUTPUTS: ; None ; ; XREFS: ; PasswordScreen_Show ; SplashAnimation_DrawScenery ; StartScreen_Draw ; Game_InitStateForSpawn ; Game_MainLoop ; Game_SetupAndLoadOutsideArea ; Game_SetupEnterBuilding ; Game_SetupEnterScreen ; Game_SetupExitBuilding ; Game_SetupNewArea ; Game_Start ; PPU_WaitUntilFlushed ;============================================================================
[caf7]PPU_WaitUntilFlushed:; [$caf7]
; ; Wait until the PPU buffer has been emptied (flushed to the ; PPU). ;
[caf7]LDA PPUBuffer_WriteOffset; Load the upper bounds of the PPU buffer. [caf9]CMP PPUBuffer_ReadOffset; Has the read offset hit the upper bounds? [cafb]BNE PPU_WaitUntilFlushed; If not, loop until it's cleared.
; ; Pause interrupt handling for 2 interrupts. ;
[cafd]LDA #$00 [caff]STA PauseInterruptCounter; Reset the paused interrupt counter. [cb01]STA Game_InterruptHandlersEnabled; Disable processing of interrupts. [cb03]STA PPU_ForceLowerPatternTables
; ; Wait for 2 interrupts (at least). ;
[cb05]@_waitForInterruptLoop:; [$cb05] [cb05]LDA PauseInterruptCounter; Load the paused interrupt counter. [cb07]CMP #$02; Has it reached 2 interrupts? [cb09]BCC @_waitForInterruptLoop; If not, loop. [cb0b]RTS
;============================================================================ ; DEADCODE ; ; XREFS: ; DEADCODE_FUN_PRG15_MIRROR__cb0c ;============================================================================
[cb0c]DEADCODE_FUN_PRG15_MIRROR__cb0c:; [$cb0c] [cb0c]LDA a:PPUSTATUS [cb0f]BMI DEADCODE_FUN_PRG15_MIRROR__cb0c [cb11]@LAB_PRG15_MIRROR__cb11:; [$cb11] [cb11]LDA a:PPUSTATUS [cb14]BPL @LAB_PRG15_MIRROR__cb11 [cb16]RTS
;============================================================================ ; Reset the screen for game play mode. ; ; This is called to reset the sprites state and enable ; interrupt handlers for game play screens. ; ; This is called when entering/exiting buildings or ; switching areas. ; ; INPUTS: ; None. ; ; OUTPUTS: ; Game_InterruptHandlersEnabled: ; Set to 1 (true). ; ; CALLS: ; Screen_ResetSpritesForGamePlay ; ; XREFS: ; Game_MainLoop ; Game_SetupAndLoadOutsideArea ; Game_SetupEnterBuilding ; Game_SetupEnterScreen ; Game_SetupExitBuilding ; Game_SetupNewArea ; Game_Start ;============================================================================
[cb17]Screen_ResetForGamePlay:; [$cb17] [cb17]JSR Screen_ResetSpritesForGamePlay; Reset the sprites for game play mode. [cb1a]LDA #$01 [cb1c]STA Game_InterruptHandlersEnabled; Enable interrupt handlers. [cb1e]RTS
;============================================================================ ; DEADCODE ;============================================================================
[cb1f]DEADCODE_FUN_PRG15_MIRROR__cb1f:; [$cb1f] [cb1f]JSR DEADCODE_Sprites_ResetForDefaultsMode1 [cb22]LDA #$01 [cb24]STA Game_InterruptHandlersEnabled [cb26]RTS
;============================================================================ ; Reset the screen for non-game mode. ; ; This is called to reset the sprites state and enable ; interrupt handlers for non-game screens. That includes: ; ; * The title screen. ; * The password screen. ; * Intro animation. ; * Outro (end game) animation. ; ; INPUTS: ; None. ; ; OUTPUTS: ; Game_InterruptHandlersEnabled: ; Set to 1 (true). ; ; CALLS: ; Screen_ResetSpritesForNonGame ; ; XREFS: ; PasswordScreen_Show ; SplashAnimation_RunIntro ; SplashAnimation_RunOutro ; StartScreen_Draw ;============================================================================
[cb27]Screen_ResetForNonGame:; [$cb27] [cb27]JSR Screen_ResetSpritesForNonGame; Reset the sprites for a non-game mode. [cb2a]LDA #$01 [cb2c]STA Game_InterruptHandlersEnabled; Enable interrupt handlers. [cb2e]RTS
;============================================================================ ; Initialize VBlank for the PPU. ; ; INPUTS: ; PPU_ControlFlags: ; The current PPU control flags. ; ; OUTPUTS: ; PPU_ControlFlags: ; The updated PPU control flags. ; ; XREFS: ; Game_InitScreenAndMusic ;============================================================================
[cb2f]PPU_InitVBlank:; [$cb2f]
; ; Enable VBlank NMI. ;
[cb2f]LDA PPU_ControlFlags; Load the PPU control flags. [cb31]ORA #$80; Enable the VBlank bit. [cb33]BNE @_savePPUCtrl; If not 0, jump. (DEADCODE: It will never be 0, so always jump).
; ; DEADCODE: Disable VBlank NMI. ; ; We should never actually end up here, since the ; value we compare against 0 above is always at least ; 0x80. ;
[cb35]LDA PPU_ControlFlags [cb37]AND #$7f
; ; Write our copy of the bitmask and set on the PPU. ;
[cb39]@_savePPUCtrl:; [$cb39] [cb39]STA PPU_ControlFlags; Store the saved PPU control flags. [cb3b]STA a:PPUCTRL; Store the flags in the PPU. [cb3e]RTS
;============================================================================ ; DEADCODE ; ; XREFS: ; DEADCODE_FUN_PRG15_MIRROR__cb1f ;============================================================================
[cb3f]DEADCODE_Sprites_ResetForDefaultsMode1:; [$cb3f] [cb3f]LDA #$01 [cb41]STA Sprites_Sprite0Mode [cb43]STA PPU_ForceLowerPatternTables [cb45]BNE Sprites_Reset
; ; v-- Fall through --v ;
;============================================================================ ; Reset sprite content for a gameplay screen. ; ; This will set sprite 0 to X=8, Y=23 (where the "P" ; symbol is placed for health), disable forcing ; lower pattern tables, and then reset the sprites. ; ; INPUTS: ; None. ; ; OUTPUTS: ; Sprites_Sprite0Mode: ; Set to mode 0 (X=8, Y=23). ; ; PPU_ForceLowerPatternTables: ; Set to 0 (false). ; ; CALLS: ; Sprites_Reset ; ; XREFS: ; IScripts_UpdatePortraitAnimation ; PlayerMenu_HandleInventoryMenuInput ; PlayerMenu_Show ; PlayerMenu_ShowStatusMenu ; PlayerMenu_ShowSubmenu ; EndGame_Begin ; EndGame_MainLoop ; Game_MainLoop ; Game_WaitForOAMReset ; Player_HandleDeath ; Screen_ResetForGamePlay ;============================================================================
[cb47]Screen_ResetSpritesForGamePlay:; [$cb47] [cb47]LDA #$00 [cb49]STA Sprites_Sprite0Mode; Set sprite 0 to X=8, Y=23. [cb4b]STA PPU_ForceLowerPatternTables; Disable forcing lower pattern tables. [cb4d]BEQ Sprites_Reset; Reset sprites. This will always jump.
;============================================================================ ; Reset sprite content for a non-gameplay screen. ; ; This will remove sprite 0 and clear out the sprite slots. ; ; INPUTS: ; None. ; ; OUTPUTS: ; A: ; The start of the slot range for new sprites. ; ; Sprites_Sprite0Mode: ; Set to 0xFF (no sprite 0). ; ; Sprites_PPUOffset: ; Sprites_Unused_0034: ; Sprites_Unused_0035: ; Sprites_Unused_0037: ; Sprites_Unused_0038: ; Sprites_SlotsFull: ; Cleared. ; ; SPRITE_0_RANGE_1_START: ; The cleared sprite information. ; ; Sprites_StartSlotRange: ; The start of the slot range for sprites. This will ; be 0 or 32. ; ; CALLS: ; Sprites_Reset ; ; XREFS: ; PasswordScreen_HandleWrongPasswordAndWaitForInput ; PasswordScreen_WaitForInput ; SplashAnimation_RunIntro ; SplashAnimation_RunOutro ; Game_InitScreenAndMusic ; Game_ShowStartScreen ; Screen_ResetForNonGame ;============================================================================
[cb4f]Screen_ResetSpritesForNonGame:; [$cb4f] [cb4f]LDA #$ff; A = 0xFF (no sprite 0) [cb51]STA Sprites_Sprite0Mode; Store it.
; ; v-- Fall through --v ;
;============================================================================ ; Reset the state for the sprites on screen. ; ; This will clear out the sprite slots, fill out all ; available sprite information in the OAM with default ; values, and optionally add a sprite 0. ; ; Sprite 0 will be added if Sprites_Sprite0Mode ; is 0 or 1. This is used for Sprite 0 hits (see ; https://www.nesdev.org/wiki/PPU_OAM#Sprite_0_hits). ; ; In practice, only mode 0 is used. 1 seems to be a ; hold-over from an earlier design. ; ; INPUTS: ; Sprites_Sprite0Mode: ; The mode used for sprite 0 defaults. ; ; SPRITES_START_XY: ; The X, Y positions for sprite 0, based on the ; defaults mode. ; ; OUTPUTS: ; SPRITE_0_RANGE_1_START: ; The populated sprite information. ; ; Sprites_StartSlotRange: ; The start of the slot range for sprites. This will ; be 0 or 32. ; ; Sprites_PPUOffset: ; Sprites_SlotsFull: ; Sprites_Unused_0034: ; Sprites_Unused_0035: ; Sprites_Unused_0037: ; Sprites_Unused_0038: ; Cleared to 0. ; ; XREFS: ; DEADCODE_Sprites_ResetForDefaultsMode1 ; Screen_ResetSpritesForGamePlay ;============================================================================
[cb53]Sprites_Reset:; [$cb53]
; ; Reset state. ; ; Some of this seem to be remnants from an older design. ;
[cb53]LDY #$00; Y = 0 [cb55]STY Sprites_PPUOffset; Sprites_PPUOffset = 0 [cb57]STY Sprites_Unused_0034; Sprites_Unused_0034 = 0 [cb59]STY Sprites_Unused_0035; Sprites_Unused_0035 = 0 [cb5b]STY Sprites_Unused_0038; Sprites_Unused_0038 = 0 [cb5d]STY Sprites_Unused_0037; Sprites_Unused_0037 = 0 [cb5f]STY Sprites_SlotsFull; Set sprite slots to not be full. [cb61]STY Sprites_SpriteSlot; Set the starting sprite slot to 0. [cb63]LDA Sprites_Sprite0Mode; Load our byte from before.
; ; Check whether sprite 0 should be hard-coded. ;
[cb65]BMI @_beginFillSpriteSlots; If 0xFF, then jump. [cb67]INC Sprites_SpriteSlot; Increment the sprite slot (start new sprites at sprite 1). [cb69]TAY; Y = A (index)
; ; Set the sprite 0 to: ; ; X = 8 if Sprites_Sprite0Mode is 0 ; 0 if Sprites_Sprite0Mode is 1 ; Y = 23 if Sprites_Sprite0Mode is 0 ; 72 if Sprites_Sprite0Mode is 1 ; Tile ID = 127 ; Attributes = Palette 3, background priority ; ; NOTE: Sprites_Sprite0Mode is never 1 in the shipped game. ; ; When 0, this will be the location of the "P" symbol for the ; HP bar. ;
[cb6a]LDA SPRITES_START_XY,Y; Load the Y for the sprite. [cb6d]STA a:SPRITE_0_RANGE_1_START; Store for sprite 0's Y. [cb70]LDA #$7f; 0x7F == tile ID [cb72]STA a:SPRITE0_TILE; Store for sprite 0's tile ID. [cb75]LDA #$23; 0x23 == Palette index 3, behind background [cb77]STA a:SPRITE0_ATTRS; Store for sprite 0's attributes. [cb7a]LDA $cb98,Y; Load the X for the sprite. [cb7d]STA a:SPRITE0_X; Store for sprite 0's X. [cb80]LDY #$04; Y = 4 (loop counter).
; ; Begin filling all sprite slots with a Y = 240 (off-screen). ;
[cb82]@_beginFillSpriteSlots:; [$cb82] [cb82]LDA #$f0; A = 0xF0 (default Y for unused slots). [cb84]@_fillSpriteSlotsLoop:; [$cb84] [cb84]STA SPRITE_0_RANGE_1_START,Y; Set it for this sprite slot's Y. [cb87]INY; Increment slot byte by 4. [cb88]INY [cb89]INY [cb8a]INY [cb8b]BNE @_fillSpriteSlotsLoop; If not 0, loop.
; ; Flip the start of the sprite slot range between 0 and 32. ;
[cb8d]LDA Sprites_StartSlotRange; Load the frame flags. [cb8f]AND #$80; Cap to 0 or 32. [cb91]EOR #$80; Flip it between 0 and 32. [cb93]STA Sprites_StartSlotRange; Store it. [cb95]RTS
;============================================================================ ; Start X/Y locations for the default sprite 0. ;============================================================================
[cb96]SPRITES_START_XY:; [$cb96] [cb96].byte $17; [0]: SPRITE_0_RANGE_1_START when Sprites_Sprite0Mode == 0 [cb97].byte $48; [1]: SPRITE_0_RANGE_1_START when Sprites_Sprite0Mode == 0xFF [cb98].byte $08; [2]: SPRITE0_X when Sprites_Sprite0Mode == 0 [cb99].byte $00; [3]: SPRITE0_X when Sprites_Sprite0Mode == 0xFF
;============================================================================ ; Request a reset of the OAM and wait for the next interrupt. ; ; INPUTS: ; InterruptCounter: ; The current interrupt counter. ; ; OUTPUTS: ; Game_NeedOAMReset: ; Set to 1 to request a reset. ; ; CALLS: ; Screen_ResetSpritesForGamePlay ; ; XREFS: ; IScripts_ClearPortraitImage ; IScripts_LoadPortraitTiles ;============================================================================
[cb9a]Game_WaitForOAMReset:; [$cb9a] [cb9a]JSR Screen_ResetSpritesForGamePlay [cb9d]LDA #$01 [cb9f]STA Game_NeedOAMReset [cba1]LDA InterruptCounter [cba3]@_waitForInterruptLoop:; [$cba3] [cba3]CMP InterruptCounter [cba5]BEQ @_waitForInterruptLoop [cba7]RTS
;============================================================================ ; Flip sprite data between slot ranges. ; ; This updates the OAM data for the sprites, copying between ; ranges 0-31 and 32-63. Only data in sprites 1 onward are ; copied. Sprite 0 is left alone. ; ; INPUTS: ; SPRITE_0_RANGE_1_START: ; The start of the sprite data. ; ; OUTPUTS: ; SPRITE_0_RANGE_1_START: ; The start of the updated sprite data. ; ; XREFS: ; GameLoop_CheckPauseGame ; Player_FillHPAndMP ; Player_UseRedPotion ;============================================================================
[cba8]Sprites_FlipRanges:; [$cba8] [cba8]LDX #$04; X = 4 (sprite data offset) [cbaa]LDY #$84; Y = 0x84 (start of loop counter) [cbac]@_loop:; [$cbac] [cbac]LDA SPRITE_0_RANGE_1_START,X; Load the sprite Y from this offset in range 1. [cbaf]PHA; Push it to the stack. [cbb0]LDA SPRITE_0_RANGE_1_START,Y; Load the sprite Y from this offset in range 2. [cbb3]STA SPRITE_0_RANGE_1_START,X; Copy to range 1. [cbb6]PLA; Pop the previous Y from the stack. [cbb7]STA SPRITE_0_RANGE_1_START,Y; Copy it to range 2. [cbba]INX; X++ (sprite data offset) [cbbb]INY; Y++ (loop counter) [cbbc]BNE @_loop; If not wrapped to 0, loop. [cbbe]RTS
;============================================================================ ; Initialize the MMC1 chip and switch to bank 14. ; ; INPUTS: ; None. ; ; OUTPUTS: ; MMC1_ShiftSync: ; Set to 0. ; ; CurrentROMBank: ; Set to bank 14. ; ; SavedPRGBank: ; Set to bank 14. ; ; CALLS: ; MMC1_Init ; MMC1_UpdateROMBank ; ; XREFS: ; Game_Init ;============================================================================
[cbbf]Game_InitMMCAndBank:; [$cbbf] [cbbf]LDA #$00 [cbc1]STA MMC1_ShiftSync [cbc3]JSR MMC1_Init; Initialize the MMC1 chipset. [cbc6]LDX #$0e [cbc8]STX a:CurrentROMBank; Set the previous and current ROM banks to 14. [cbcb]STX SavedPRGBank [cbcd]JMP MMC1_UpdateROMBank; Switch to the bank.
;============================================================================ ; Initialize the MMC1 chip. ; ; INPUTS: ; None. ; ; OUTPUTS: ; None. ; ; XREFS: ; Game_InitMMCAndBank ;============================================================================
[cbd0]MMC1_Init:; [$cbd0] [cbd0]LDA #$ff [cbd2]STA a:MMC1_SERIAL [cbd5]LDA #$0e; Horizontal Mirroring Regular Mirroring Swap ROM bank at 0x8000 Swap 8K of VROM at PPU 0x0000 Don't reset [cbd7]STA a:$9fff [cbda]LSR A [cbdb]STA a:$9fff [cbde]LSR A [cbdf]STA a:$9fff [cbe2]LSR A [cbe3]STA a:$9fff [cbe6]LSR A [cbe7]STA a:$9fff; VROM_SIZE_SELECT [cbea]LDA #$00; Select VROM bank at 0x0000 Switch 4KB only Don't reset [cbec]STA a:$bfff [cbef]LSR A [cbf0]STA a:$bfff [cbf3]LSR A [cbf4]STA a:$bfff [cbf7]LSR A [cbf8]STA a:$bfff [cbfb]LSR A [cbfc]STA a:$bfff; VROM_PAGE_SELECT_1 [cbff]LDA #$00; Select VROM bank at 0x1000 Switch 4KB only Don't reset [cc01]STA a:$dfff [cc04]LSR A [cc05]STA a:$dfff [cc08]LSR A [cc09]STA a:$dfff [cc0c]LSR A [cc0d]STA a:$dfff [cc10]LSR A [cc11]STA a:$dfff [cc14]RTS
;============================================================================ ; Save the current ROM bank and switch to a new one. ; ; INPUTS: ; X: ; The new bank to switch to. ; ; OUTPUTS: ; CurrentROMBank: ; The new ROM bank. ; ; SavedPRGBank: ; The previously-saved ROM bank. ; ; MMC1_ShiftSync: ; Flag used to manage/reinitialize MMC1 state. ; ; XREFS: ; Area_LoadScrollDataRight ; Game_EnterAreaHandler ; Game_EnterBuilding ; Game_ExitBuilding ; Game_LoadCurrentArea ; Game_LoadFirstLevel ;============================================================================
[cc15]MMC1_SaveROMBankAndUpdateTo:; [$cc15] [cc15]LDA a:CurrentROMBank; Get the currently-loaded ROM bank [cc18]STA SavedPRGBank
; ; v-- Fall through --v ;
;============================================================================ ; Switch the active ROM bank. ; ; INPUTS: ; X: ; The new bank to switch to. ; ; OUTPUTS: ; CurrentROMBank: ; The new ROM bank. ; ; MMC1_ShiftSync: ; Flag used to manage/reinitialize MMC1 state. ; ; XREFS: ; Area_LoadBlockProperties ; Area_LoadScrollDataRight ; Area_LoadTiles ; Area_SetBlocks ; Area_SetStateFromDoorDestination ; CurrentSprite_LoadTilesInfo ; EndGame_MainLoop ; Game_DrawScreenInFrozenState ; Game_InitMMCAndBank ; Game_InitScreenAndMusic ; Game_LoadAreaTable ; Game_MainLoop ; IScripts_DrawPortraitTileToPPU ; IScripts_LoadPortraitTilesAddress ; MMC1_LoadBankAndJump ; MMC1_RestorePrevROMBank ; MMC1_UpdatePRGBankToStackA ; Messages_Load ; PPU_LoadGlyphsForStrings ; PPU_WriteTilesFromCHRRAM ; Player_HandleDeath ; Player_LoadArmorSpriteTilesAddr ; Player_LoadArmorTile ; Player_LoadShieldSpriteTileAddrs ; Player_LoadShieldTile ; Player_LoadWeaponSpriteTileAddrs ; Player_LoadWeaponTile ; Screen_HandleScrollDown ; Screen_HandleScrollLeft ; Screen_HandleScrollRight ; Screen_HandleScrollUp ; Screen_LoadSpriteInfo ; Screen_LoadUIPalette ; Screen_SetFadeOutPalette ; Screen_SetPaletteData ; Sprite_DrawPortraitPartAppearance ; Sprite_Draw_Finish ; Sprite_SetAppearanceAddrFromOffset ; Sprite_SetPlayerAppearanceAddr ; Sprites_LoadCommon ; Sprites_LoadImageForCurrentSprite ; Sprites_LoadSpriteValue ; TextBox_GetBackingAttributeData ; TextBox_LoadAndShowMessage ; TextBox_LoadItemSourceTiles ; TextBox_ShowNextChar ; TextBox_WriteChar ; Textbox_Maybe_GetAreaBehindTextbox ; UI_DrawSelectedItem ;============================================================================
[cc1a]MMC1_UpdateROMBank:; [$cc1a] [cc1a]STX a:CurrentROMBank; Set the bank we're switching to. [cc1d]@_setupMMC1:; [$cc1d] [cc1d]LDA #$01 [cc1f]STA MMC1_ShiftSync
; ; Write 5 LSBs of A to $FFFF, one bit per write (MMC1 serial protocol): ;
[cc21]TXA; ROM_PAGE_SELECT parameter in X [cc22]STA a:MMC1_SERIAL [cc25]LSR A [cc26]STA a:MMC1_SERIAL [cc29]LSR A [cc2a]STA a:MMC1_SERIAL [cc2d]LSR A [cc2e]STA a:MMC1_SERIAL [cc31]LSR A [cc32]STA a:MMC1_SERIAL
; ; Check the MMC1 synchronization state. ; ; If 0, this will re-initialize the MMC1 and switch banks. ; ; If 1, the interrupt handler is already switching banks. ; This can then return. ;
[cc35]LDA MMC1_ShiftSync [cc37]CMP #$01 [cc39]BEQ @_finish
; ; Slow path. Re-initialize the MMC1. ;
[cc3b]LDA #$ff [cc3d]STA a:MMC1_SERIAL; Reset the MMC1 shifter/controler. [cc40]LDA #$0e; Horizontal Mirroring Regular Mirroring Swap ROM bank at 0x8000 Swap 8K of VROM at PPU 0x000 Don't reset
; ; Set the CHR banking mode. ;
[cc42]STA a:$9fff [cc45]LSR A [cc46]STA a:$9fff [cc49]LSR A [cc4a]STA a:$9fff [cc4d]LSR A [cc4e]STA a:$9fff [cc51]LSR A [cc52]STA a:$9fff
; ; Set CHR bank 0 = 0. ;
[cc55]LDA #$00; Select VROM bank at 0x000 Switch 4K only Don't reset [cc57]STA a:$bfff [cc5a]LSR A [cc5b]STA a:$bfff [cc5e]LSR A [cc5f]STA a:$bfff [cc62]LSR A [cc63]STA a:$bfff [cc66]LSR A [cc67]STA a:$bfff
; ; Set CHR bank 1 = 0. ;
[cc6a]LDA #$00; Select VROM bank at 0x1000 Switch 4K only Don't reset [cc6c]STA a:$dfff [cc6f]LSR A [cc70]STA a:$dfff [cc73]LSR A [cc74]STA a:$dfff [cc77]LSR A [cc78]STA a:$dfff [cc7b]LSR A [cc7c]STA a:$dfff [cc7f]JMP @_setupMMC1 [cc82]@_finish:; [$cc82] [cc82]DEC MMC1_ShiftSync [cc84]RTS
;============================================================================ ; Watchdog handler to ensure the correct ROM bank is set. ; ; This is called by the interrupt handler to keep the MMC1 ; in a known configuration and keep the desired ROM bank ; set. ; ; If other code is in the middle of switching ROM banks, ; this will notice and perform a full MMC1 re-initialization ; before setting the bank. ; ; INPUTS: ; X: ; The ROM bank to ensure is set. ; ; MMC1_ShiftSync: ; Flag indicating if a ROM bank is currently being ; set. ; ; OUTPUTS: ; CurrentROMBank: ; The new ROM bank. ; ; MMC1_ShiftSync: ; Flag used to manage/reinitialize MMC1 state. ; ; XREFS: ; OnInterrupt ;============================================================================
[cc85]MMC1_EnsurePRG:; [$cc85] [cc85]STX a:CurrentROMBank
; ; Check if any code was in the process of switching banks. ; ; If 0, we can exit. ; ; If 1, MMC1_UpdateROMBank was in the process of ; switching banks. ;
[cc88]LDA MMC1_ShiftSync [cc8a]BEQ @_fastPath
; ; The sync state was set, so something is switching banks. ; Increment the sync state and reset the MMC1 chip. ;
[cc8c]INC MMC1_ShiftSync
; ; Reset MMC1. ;
[cc8e]LDA #$ff [cc90]STA a:MMC1_SERIAL [cc93]LDA #$0e; Horizontal Mirroring Regular Mirroring Swap ROM bank at 0x8000 Swap 8K of VROM at PPU 0x000 Don't reset [cc95]@_setMMC1State:; [$cc95] [cc95]STA a:$9fff [cc98]LSR A [cc99]STA a:$9fff [cc9c]LSR A [cc9d]STA a:$9fff [cca0]LSR A [cca1]STA a:$9fff [cca4]LSR A [cca5]STA a:$9fff
; ; Set CHR bank 0 = 0 ;
[cca8]LDA #$00 [ccaa]STA a:$bfff [ccad]LSR A [ccae]STA a:$bfff [ccb1]LSR A [ccb2]STA a:$bfff [ccb5]LSR A [ccb6]STA a:$bfff [ccb9]LSR A [ccba]STA a:$bfff
; ; Set CHR bank 1 = 0. ;
[ccbd]LDA #$00 [ccbf]STA a:$dfff [ccc2]LSR A [ccc3]STA a:$dfff [ccc6]LSR A [ccc7]STA a:$dfff [ccca]LSR A [cccb]STA a:$dfff [ccce]LSR A [cccf]STA a:$dfff
; ; Set the bank. ;
[ccd2]@_fastPath:; [$ccd2] [ccd2]TXA [ccd3]STA a:MMC1_SERIAL [ccd6]LSR A [ccd7]STA a:MMC1_SERIAL [ccda]LSR A [ccdb]STA a:MMC1_SERIAL [ccde]LSR A [ccdf]STA a:MMC1_SERIAL [cce2]LSR A [cce3]STA a:MMC1_SERIAL [cce6]RTS
;============================================================================ ; Restore the previously-saved ROM bank. ; ; This will switch to the bank stored in ; SavedPRGBank. ; ; INPUTS: ; SavedPRGBank: ; The saved ROM bank to switch back to. ; ; OUTPUTS: ; None. ; ; CALLS: ; MMC1_UpdateROMBank ; ; XREFS: ; Area_LoadScrollDataRight ; Game_EnterAreaHandler ; Game_EnterBuilding ; Game_ExitBuilding ; Game_LoadCurrentArea ; Game_LoadFirstLevel ;============================================================================
[cce7]MMC1_RestorePrevROMBank:; [$cce7] [cce7]LDX SavedPRGBank [cce9]JMP MMC1_UpdateROMBank
;============================================================================ ; TODO: Document PPUBuffer_DrawCommand_RemoveVerticalLines ; ; INPUTS: ; None. ; ; OUTPUTS: ; A ; ; XREFS: ; PPUBUFFER_DRAW_COMMANDS ; [$PRG15_MIRROR::cfc8] ;============================================================================
[ccec]PPUBuffer_DrawCommand_RemoveVerticalLines:; [$ccec]
; ; Read the first byte from the buffer as the phase. ;
[ccec]LDX PPUBuffer_ReadOffset; Load our current PPU buffer offset into X. [ccee]LDA PPUBuffer,X; Load the first address byte from the buffer into A. [ccf1]INX; Increment buffer offset (X = X + 1). [ccf2]STA Temp_08; Store the upper byte (A) into a temporary variable.
; ; Read the upper address from the buffer and set in PPUADDR. ;
[ccf4]LDA PPUBuffer,X; Load the next byte from the buffer into A. [ccf7]INX; Increment buffer offset. [ccf8]TAY; Y = lower byte. [ccf9]STY a:PPUADDR; Write the upper byte to PPUADDR.
; ; Read the lower address from the buffer and set in PPUADDR. ;
[ccfc]LDA PPUBuffer,X [ccff]INX [cd00]STX PPUBuffer_ReadOffset; Set the new offset. [cd02]TAX [cd03]STX a:PPUADDR; Write the lower byte to PPUADDR.
; ; Begin preparing for the loop. There will be 16 iterations. ;
[cd06]LDA #$10; Set to 16 iterations in Temp_06. [cd08]STA Temp_06 [cd0a]LDA a:PPUDATA; Read a byte from PPUDATA. [cd0d]@_loop:; [$cd0d] [cd0d]STX Temp_07; Store X (offset) into our temp state. [cd0f]LDA a:PPUDATA; Read the next byte of data.
; ; Set the address again. The PPUDATA read advanced the offset. ;
[cd12]STY a:PPUADDR; Set upper byte of PPU address to Y. [cd15]STX a:PPUADDR; Set lower byte of PPU address to X.
; ; Add the phase byte and loop index together and get the three ; least-significant bits. This will be our index into the lookup ; table. ;
[cd18]PHA [cd19]LDA Temp_06; Load the loop index. [cd1b]CLC [cd1c]ADC Temp_08; Add the phase byte to it. [cd1e]AND #$07; Retain only the 3 least-significant bits. [cd20]TAX; Store in X. [cd21]PLA
; ; Take the PPU data we loaded before and AND the value in the ; lookup table. This will be the replacement for the data we ; loaded. ;
[cd22]AND PPUBUFFER_DRAWCOMMAND_0xFA_MASKS,X [cd25]STA a:PPUDATA; Set as the new PPU data.
; ; Load the offset into the buffer and increment. ;
[cd28]LDX Temp_07; Load the saved offset. [cd2a]INX; Increment by 1.
; ; Prepare either for the next loop or for termination. ;
[cd2b]LDA a:PPUDATA; Load our next PPU data byte for the next loop. [cd2e]DEC Temp_06; Decrement our loop index by 1. [cd30]BNE @_loop; If not 0, loop. [cd32]RTS
; ; XREFS: ; PPUBuffer_DrawCommand_RemoveVerticalLines ;
[cd33]PPUBUFFER_DRAWCOMMAND_0xFA_MASKS:; [$cd33] [cd33].byte $fe; [0]: [cd34].byte $df; [1]: [cd35].byte $f7; [2]: [cd36].byte $fd; [3]: [cd37].byte $bf; [4]: [cd38].byte $ef; [5]: [cd39].byte $7f; [6]: [cd3a].byte $fb; [7]:
;============================================================================ ; Rotate 16 bytes of tiles right by 1 pixel. ; ; This PPUBuffer draw command will read the upper and ; lower bytes of a PPU address and then iterate over the ; next 16 bytes at that address, rotating all data right ; by 1 pixel. ; ; INPUTS: ; {@sybmol PPUBuffer}: ; The PPU buffer to read from. ; ; PPUBuffer_ReadOffset: ; The offset within the PPU buffer to read from. ; ; PPUADDR: ; The PPU address to read and write. ; ; PPUDATA: ; The PPU data to read and write. ; ; OUTPUTS: ; PPUBuffer_ReadOffset: ; The updated offset within the PPU buffer. ; ; PPUADDR: ; The written PPU address. ; ; PPUDATA: ; The written PPU data. ; ; TEMP VARIABLES: ; Temp_06 ; ; XREFS: ; PPUBUFFER_DRAW_COMMANDS ; [$PRG15_MIRROR::cfc4] ;============================================================================
[cd3b]PPUBuffer_DrawCommand_RotateTilesRight1Pixel:; [$cd3b]
; ; Read the first two bytes from the offset and set as ; the PPUADDR. ;
[cd3b]LDX PPUBuffer_ReadOffset; Load our current PPU buffer offset. [cd3d]LDA PPUBuffer,X; Load the first address byte from the buffer. [cd40]INX [cd41]TAY; Y = byte 1 [cd42]STY a:PPUADDR; Set as the upper byte of the PPU address. [cd45]LDA PPUBuffer,X; Load the next address byte. [cd48]INX [cd49]STX PPUBuffer_ReadOffset; Update the offset to skip these bytes. [cd4b]TAX [cd4c]STX a:PPUADDR; Set as the lower byte of the PPU address.
; ; Begin preparing for drawing. ; ; We'll draw 16 bytes from the buffer. ;
[cd4f]LDA #$10; Set our loop for 16 bytes. [cd51]STA Temp_06 [cd53]LDA a:PPUDATA; Load byte 1 from the PPU. [cd56]@_loop:; [$cd56] [cd56]LDA a:PPUDATA; Load a byte from the PPU.
; ; Set the address again. The PPUDATA read advanced the offset. ;
[cd59]STY a:PPUADDR; Set upper byte of PPU address to Y. [cd5c]STX a:PPUADDR; Set lower byte of PPU address to X.
; ; Rotate the tile data from this address right by 1 pixel. ;
[cd5f]PHA [cd60]LSR A; Rotate the pixel data right by 1. [cd61]PLA [cd62]ROR A; OR it to the data rotated left by 7. [cd63]STA a:PPUDATA; Store the rotated data. [cd66]INX; Increment the lower byte of the PPU address. [cd67]LDA a:PPUDATA
; ; Check if we're at the end of the loop. ; ; If we've processed 16 entries, we're done. ;
[cd6a]DEC Temp_06; Decrement our loop counter. [cd6c]BNE @_loop; If it's > 0, loop. [cd6e]RTS
;============================================================================ ; Reset the PPU tile offset for loading sprite tiles. ; ; This will reset to a value of $0900. ; ; INPUTS: ; None ; ; OUTPUTS: ; CurrentSprite_TargetPPUTileAddr: ; CurrentSprite_TargetPPUTileAddr+1: ; The updated offset ($0900). ; ; XREFS: ; GameLoop_LoadSpriteImages ; Screen_ClearSprites ;============================================================================
[cd6f]CurrentSprite_ResetPPUTileOffset:; [$cd6f] [cd6f]LDA #$09 [cd71]STA CurrentSprite_TargetPPUTileAddr_U; Set upper to 0x09. [cd73]LDA #$00 [cd75]STA CurrentSprite_TargetPPUTileAddr; Set lower to 0x00. [cd77]RTS
;============================================================================ ; Look up tile information needed to draw a sprite. ; ; This will take the sprite entity ID and look up the starting ; address for tile data and the number of tiles to draw. ; ; INPUTS: ; CurrentROMBank: ; The current ROM bank to switch back to after load. ; ; CurrentSprite_Entity: ; The current sprite entity being loaded. ; ; CurrentSprite_TilesBank: ; The tiles bank for the current sprite. ; ; SPRITES_PPU_TILE_COUNTS: ; The lookup table of entity ID to tile count. ; ; OUTPUTS: ; CurrentSprite_LoadTileAddr+1: ; CurrentSprite_LoadTileAddr: ; The address for the tiles to load. ; ; CurrentSprite_LoadTileCount: ; The number of tiles to load. ; ; Temp_Addr_U: ; Temp_Addr_L: ; Clobbered. ; ; CALLS: ; MMC1_UpdateROMBank ; ; XREFS: ; Sprites_LoadImageForCurrentSprite ;============================================================================
[cd78]CurrentSprite_LoadTilesInfo:; [$cd78]
; ; Save the current ROM bank to the stack. ;
[cd78]LDA a:CurrentROMBank; Load the current bank. [cd7b]PHA; Push to the stack.
; ; Switch to the bank for the current sprite's images. ;
[cd7c]LDX a:CurrentSprite_TilesBank; Load the bank where the images for the current sprite are found [cd7f]JSR MMC1_UpdateROMBank
; ; Read the first byte of the ROM bank and store as the start of ; the lower byte of the sprite images address. ;
[cd82]LDA a:ROMBankStart; Read byte from $8000. [cd85]STA Temp_Addr_L; Store as the lower byte of the address.
; ; Read the second byte as the upper address, and add 0x80 to it. ;
[cd87]LDA a:ROMBankStart+1; Read byte from $8001. [cd8a]CLC [cd8b]ADC #$80; Add 0x80. [cd8d]STA Temp_Addr_U; Store as the upper byte of the address.
; ; Compute the starting index for the sprite based on the ; entity ID. ; ; Entities 0-55 are in the first sprite bank. 56+ are in ; the second. ;
[cd8f]LDA a:CurrentSprite_Entity; Load the current sprite ID [cd92]CMP #$37; Check if we're on the next bank. [cd94]BCC @_loadImages [cd96]SBC #$37; We are, so subtract 55 for the new sprite index.
; ; Compute the address for the sprite image. ;
[cd98]@_loadImages:; [$cd98] [cd98]ASL A; Convert the sprite ID to a word boundary. [cd99]TAY; Y = A [cd9a]LDA (Temp_Addr_L),Y; Get the pointer to the sprite data from the bank address computed above. [cd9c]STA CurrentSprite_LoadTileAddr; A = Lower byte of the image data address. [cd9e]INY [cd9f]LDA (Temp_Addr_L),Y; A = Upper byte of the address. [cda1]CLC [cda2]ADC #$80; Add 0x80 to the upper address. [cda4]STA CurrentSprite_LoadTileAddr_U; Upper byte of pointer to the sprite bitmap
; ; Get the PPU tile numbers for the sprite entity and store ; it for lookup. ;
[cda6]LDA a:CurrentSprite_Entity; Load the current sprite ID [cda9]TAY [cdaa]LDA SPRITES_PPU_TILE_COUNTS,Y; Get the number of tiles needed [cdad]STA CurrentSprite_LoadTileCount; Store that for the render
; ; Restore the previous bank. ;
[cdaf]PLA; Pop the previous bank. [cdb0]TAX; Transfer to X. [cdb1]JSR MMC1_UpdateROMBank; Update to the bank. [cdb4]RTS
;============================================================================ ; Load all tiles required for a sprite into the PPU. ; ; This will load information on the tiles needed for the ; current sprite entity, and will then iterate through each ; tile, writing it to the PPU buffer. ; ; INPUTS: ; CurrentSprite_TargetPPUTileAddr+1: ; CurrentSprite_TargetPPUTileAddr: ; The target PPU address to begin writing to. ; ; OUTPUTS: ; CurrentSprite_TargetPPUTileAddr+1: ; CurrentSprite_TargetPPUTileAddr: ; The new target PPU address for the next sprite. ; ; PPUBuffer: ; The PPU buffer populated with new tile data. ; ; PPUBuffer_WriteOffset: ; The new PPU buffer write offset. ; ; CurrentSprite_LoadTileAddr+1: ; CurrentSprite_LoadTileAddr: ; CurrentSprite_LoadTileCount: ; PPU_TargetAddr+1: ; PPU_TargetAddr: ; Clobbered. ; ; CALLS: ; CurrentSprite_LoadTilesInfo ; MMC1_UpdateROMBank ; ; XREFS: ; GameLoop_LoadSpriteImages ;============================================================================
[cdb5]Sprites_LoadImageForCurrentSprite:; [$cdb5]
; ; Calculate the starting PPU tile offset for this sprite. ; ; This is equivalent to: ; ; // Lower byte ; Temp_00 = ; (byte)(CurrentSprite_TargetPPUTileAddr << 4); ; ; // Upper byte ; CurrentSprite_StartPPUTileOffset = (byte)( ; (CurrentSprite_TargetPPUTileAddr+1 << 4) | ; (CurrentSprite_TargetPPUTileAddr >> 4) ; ); ;
[cdb5]LDA CurrentSprite_TargetPPUTileAddr; Load the lower byte of the current PPU tile address to read from. [cdb7]STA Temp_00; Store it temporarily. [cdb9]LDA CurrentSprite_TargetPPUTileAddr_U; Load the upper byte of the PPU tile address. [cdbb]ASL Temp_00; Multiply by 16. [cdbd]ROL A [cdbe]ASL Temp_00 [cdc0]ROL A [cdc1]ASL Temp_00 [cdc3]ROL A [cdc4]ASL Temp_00 [cdc6]ROL A; A = Lower byte of the offset. [cdc7]STA CurrentSprite_StartPPUTileOffset; Store the upper byte as the starting offset.
; ; Load the tile information for the sprite, and proceed to ; load into the PPU if the sprite entity has tiles. ;
[cdc9]JSR CurrentSprite_LoadTilesInfo; Look up the sprite data pointer for this. [cdcc]LDA CurrentSprite_LoadTileCount; A = Tile count. [cdce]BNE @_loadSpriteToPPUBuffer; If count > 0, jump to load tile data. [cdd0]RTS
; ; Switch to the bank containing the tiles to load. ;
[cdd1]@_loadSpriteToPPUBuffer:; [$cdd1] [cdd1]LDA a:CurrentROMBank; Load the current ROM bank. [cdd4]PHA; Push it to the stack. [cdd5]LDX a:CurrentSprite_TilesBank; X = Bank for the tiles. [cdd8]JSR MMC1_UpdateROMBank; Switch to that bank.
; ; Set the PPU address to load tiles into. ;
[cddb]LDA CurrentSprite_TargetPPUTileAddr_U; Load the upper byte of the target PPU address. [cddd]STA PPU_TargetAddr_U; Set it. [cddf]LDA CurrentSprite_TargetPPUTileAddr; Load the lower byte. [cde1]STA PPU_TargetAddr; Set it.
; ; Queue loading 16 bytes for the current tile. ;
[cde3]LDA #$10; A = 16 (bytes) [cde5]JSR PPUBuffer_QueueCommandOrLength; Queue as the length, and set X as the write index.
; ; Copy the sprite data for this tile to the PPU buffer. ;
[cde8]LDY #$00; Y = 0 (loop counter) [cdea]@_copySpriteImage:; [$cdea] [cdea]LDA (CurrentSprite_LoadTileAddr),Y; Load a byte from the tile. [cdec]STA PPUBuffer,X; Write to the PPU buffer at X. [cdef]INX; X++ (write offset) [cdf0]INY; Y++ (loop counter) [cdf1]CPY #$10; Is X < 16? [cdf3]BCC @_copySpriteImage; If so, loop.
; ; 16 bytes of sprite data was written to the PPU buffer. ; Update the new write position for the buffer. ;
[cdf5]STX PPUBuffer_WriteOffset; Store the resulting PPU buffer write offset.
; ; Restore the previous bank. ;
[cdf7]PLA; Pull the previous bank from the stack. [cdf8]TAX; Set to X. [cdf9]JSR MMC1_UpdateROMBank; And switch to it.
; ; Update the source tile address to advance to the next tile. ;
[cdfc]LDA CurrentSprite_LoadTileAddr; Load the lower byte of the tile address. [cdfe]CLC [cdff]ADC #$10; Add 16 (the bytes we just read). [ce01]STA CurrentSprite_LoadTileAddr; And store it. [ce03]LDA CurrentSprite_LoadTileAddr_U; Load the upper byte. [ce05]ADC #$00; Add Carry, if the lower byte wrapped. [ce07]STA CurrentSprite_LoadTileAddr_U; And store it.
; ; Update the target PPU tile address to advance to the next ; tile. ;
[ce09]LDA CurrentSprite_TargetPPUTileAddr; Load the lower byte of the target address. [ce0b]CLC [ce0c]ADC #$10; Add 16. [ce0e]STA CurrentSprite_TargetPPUTileAddr; And store it. [ce10]LDA CurrentSprite_TargetPPUTileAddr_U; Load the upper byte. [ce12]ADC #$00; Add Carry, if the lower byte wrapped. [ce14]STA CurrentSprite_TargetPPUTileAddr_U; And store it.
; ; Decrement the tile count, and loop if there are tiles ; remaining. ;
[ce16]DEC CurrentSprite_LoadTileCount; Decrement the remaining tile count. [ce18]BNE @_loadSpriteToPPUBuffer; If > 0, loop. [ce1a]RTS
;============================================================================ ; The number of PPU tiles each sprite needs. ; ; XREFS: ; CurrentSprite_LoadTilesInfo ;============================================================================
; ; XREFS: ; CurrentSprite_LoadTilesInfo ;
[ce1b]SPRITES_PPU_TILE_COUNTS:; [$ce1b] [ce1b].byte $01; [0]: [ce1c].byte $01; [1]: Dropped: Bread [ce1d].byte $01; [2]: Dropped: Coin [ce1e].byte $01; [3]: Enemy: ? [ce1f].byte $10; [4]: Enemy: Raiden [ce20].byte $10; [5]: Enemy: Necron Aides [ce21].byte $10; [6]: Enemy: Zombie [ce22].byte $08; [7]: Enemy: Hornet [ce23].byte $06; [8]: Enemy: Bihoruda [ce24].byte $06; [9]: Enemy: Lilith [ce25].byte $07; [10]: Magic: ? [ce26].byte $06; [11]: Enemy: Yuinaru [ce27].byte $0c; [12]: Enemy: Snowman [ce28].byte $10; [13]: Enemy: Nash [ce29].byte $10; [14]: Enemy: Fire Giant [ce2a].byte $12; [15]: Enemy: Ishiisu [ce2b].byte $0d; [16]: Enemy: Execution Hood [ce2c].byte $26; [17]: Boss: Rokusutahn [ce2d].byte $10; [18]: Boss: unused (round body of snake boss) [ce2e].byte $00; [19]: Effect: Enemy death [ce2f].byte $00; [20]: Effect: Lightning ball [ce30].byte $16; [21]: Enemy: Charron [ce31].byte $17; [22]: Enemy: ? (Unused) [ce32].byte $10; [23]: Enemy: Geributa [ce33].byte $0e; [24]: Enemy: Sugata [ce34].byte $12; [25]: Enemy: Grimlock [ce35].byte $0c; [26]: Enemy: Giant Bees [ce36].byte $0e; [27]: Enemy: Myconid [ce37].byte $10; [28]: Enemy: Naga [ce38].byte $10; [29]: Enemy: Skeleton Knight (unused) [ce39].byte $12; [30]: Enemy: Giant Strider [ce3a].byte $12; [31]: Enemy: Sir Gawaine [ce3b].byte $1f; [32]: Enemy: Maskman [ce3c].byte $16; [33]: Enemy: Wolfman [ce3d].byte $0f; [34]: Enemy: Yareeka [ce3e].byte $10; [35]: Enemy: Magman [ce3f].byte $13; [36]: Enemy: Curly-tailed guy with spear (unused) [ce40].byte $10; [37]: Enemy: ? (unused) [ce41].byte $11; [38]: Enemy: Ikeda [ce42].byte $10; [39]: Enemy: Muppet guy (unused) [ce43].byte $10; [40]: Enemy: Lamprey [ce44].byte $10; [41]: Enemy: ? (unused) [ce45].byte $13; [42]: Enemy: Monodron [ce46].byte $0c; [43]: Enemy: Winged skeleton (unused) [ce47].byte $12; [44]: Enemy: Tamazutsu [ce48].byte $3e; [45]: Boss: Ripasheiku [ce49].byte $33; [46]: Boss: Zoradohna [ce4a].byte $1c; [47]: Boss: Borabohra [ce4b].byte $0e; [48]: Boss: Pakukame [ce4c].byte $25; [49]: Boss: Zorugeriru [ce4d].byte $54; [50]: Boss: King Grieve [ce4e].byte $69; [51]: Boss: Shadow Eura [ce4f].byte $10; [52]: NPC: Walking Man 1 [ce50].byte $10; [53]: NPC: Blue lady (unused) [ce51].byte $09; [54]: NPC: Child (unused) [ce52].byte $08; [55]: NPC: Armor Salesman [ce53].byte $0b; [56]: NPC: Martial Artist [ce54].byte $0b; [57]: NPC: Priest [ce55].byte $14; [58]: NPC: King [ce56].byte $0c; [59]: NPC: Magic Teacher [ce57].byte $08; [60]: NPC: Key Salesman [ce58].byte $0a; [61]: NPC: Smoking Man [ce59].byte $0e; [62]: NPC: Man in Chair [ce5a].byte $0a; [63]: NPC: Sitting Man [ce5b].byte $0d; [64]: NPC: Meat Salesman [ce5c].byte $10; [65]: NPC: Lady in Blue Dress with Cup [ce5d].byte $10; [66]: NPC: Guard [ce5e].byte $0b; [67]: NPC: Doctor [ce5f].byte $0e; [68]: NPC: Walking Woman 1 [ce60].byte $0d; [69]: NPC: Walking Woman 2 [ce61].byte $09; [70]: Enemy: Eyeball (unused) [ce62].byte $08; [71]: Enemy: Zozura [ce63].byte $02; [72]: Item: Glove [ce64].byte $02; [73]: Item: Black Onyx [ce65].byte $04; [74]: Item: Pendant [ce66].byte $02; [75]: Item: Red Potion [ce67].byte $02; [76]: Item: Poison [ce68].byte $04; [77]: Item: Elixir [ce69].byte $02; [78]: Item: Ointment [ce6a].byte $00; [79]: Trigger: Intro [ce6b].byte $02; [80]: Item: Mattock [ce6c].byte $00; [81]: Magic: ? [ce6d].byte $0c; [82]: Effect: Fountain [ce6e].byte $00; [83]: Magic: ? [ce6f].byte $00; [84]: Magic: Enemy Fireball [ce70].byte $04; [85]: Item: Wing Boots [ce71].byte $02; [86]: Item: Hour Glass [ce72].byte $04; [87]: Item: Magical Rod [ce73].byte $02; [88]: Item: Battle Suit [ce74].byte $04; [89]: Item: Battle Helmet [ce75].byte $04; [90]: Item: Dragon Slayer [ce76].byte $02; [91]: Item: Mattock [ce77].byte $04; [92]: Item: Wing Boots (from quest) [ce78].byte $02; [93]: Item: Red Potion [ce79].byte $02; [94]: Item: Poison [ce7a].byte $02; [95]: Item: Glove [ce7b].byte $02; [96]: Item: Ointment [ce7c].byte $0c; [97]: Effect: Spring of Trunk [ce7d].byte $0c; [98]: Effect: Spring of Sky [ce7e].byte $0c; [99]: Effect: Spring of Tower [ce7f].byte $00; [100]: Effect: Boss Death
;============================================================================ ; Load common sprites from bank 8. ; ; This will load 80 sprites (5 passes of 16 sprites at ; 1,280 bytes total) from bank 8 into the PPU. ; ; The sprites consist of the coins, bread, damage effects, ; magic, HUD and textbox symbols, and the hand cursor. ; ; INPUTS: ; CurrentROMBank: ; The current ROM bank. ; ; OUTPUTS: ; PPUADDR: ; PPUDATA: ; Written sprite data. ; ; Temp_00: ; Temp_Addr_L: ; Temp_Addr_U: ; Clobbered. ; ; CALLS: ; MMC1_UpdateROMBank ; ; XREFS: ; Game_InitStateForSpawn ; Screen_SetupNew ;============================================================================
[ce80]Sprites_LoadCommon:; [$ce80]
; ; Save the current ROM bank and switch to bank 8. ;
[ce80]LDA a:CurrentROMBank; Load the current ROM bank. [ce83]PHA; Push it to the stack. [ce84]LDX #$08; Bank 8 == sprite data. [ce86]JSR MMC1_UpdateROMBank; Switch to the bank.
; ; Set the start address for the sprites to load. ;
[ce89]LDA a:TILES_COMMON_START_REF; Load the lower byte of the address of the first sprite (a coin). [ce8c]STA Temp_Addr_L; Store it. [ce8e]LDA a:TILES_COMMON_START_REF+1; Load the upper byte. [ce91]CLC; Clear Carry. [ce92]ADC #$80; Add 0x80 to the upper byte. [ce94]STA Temp_Addr_U; And store as the upper byte of the address.
; ; Set the sprite tile load count to 5 passes of 256 bytes, ; yielding 80 sprites. ;
[ce96]LDA #$05; 5 = number of passes of 256 bytes of sprite tiles to load. [ce98]STA Temp_00; Store it for the loop.
; ; Set the PPU address to $0400 (within the loaded PPU tiles). ;
[ce9a]LDA #$04; 0x04 == Upper byte. [ce9c]STA a:PPUADDR; Write it. [ce9f]LDY #$00; 0x00 == Lower byte and loop counter. [cea1]STY a:PPUADDR; Write it.
; ; Begin loading and writing sprites, 5 passes of 256 bytes. ;
[cea4]@_loadLoop:; [$cea4] [cea4]LDA (Temp_Addr_L),Y; Load the address of the sprite. [cea6]STA a:PPUDATA; Write to the PPU data. [cea9]INY; Y++ (loop counter). [ceaa]BNE @_loadLoop; If not incremented back up to 0, loop. [ceac]INC Temp_Addr_U; Increment the upper byte of the write address. [ceae]DEC Temp_00; Decrement the number of passes remaining. [ceb0]BNE @_loadLoop; If > 0, loop.
; ; Loading is complete. Restore the previous bank. ;
[ceb2]PLA; Pop the previous bank from the stack. [ceb3]TAX; X = A (bank) [ceb4]JSR MMC1_UpdateROMBank; Switch back to the bank. [ceb7]RTS
;============================================================================ ; Load the tiles for the area into CHR RAM. ; ; The tiles will be loaded from bank 4. ; ; INPUTS: ; Area_TilesReadAddress: ; The tiles address in the PPU. ; ; Area_TilesIndex: ; The index of the tileset to load. ; ; CurrentROMBank: ; The current ROM bank. ; ; TILE_INDEX_TO_ADDR: ; Lookup table of addresses in bank 4 containing ; the tiles. ; ; TILE_INDEX_TO_PPU_ADDR_UPPER: ; Lookup table of upper bytes of the PPU addresses ; to write to. ; ; OUTPUTS: ; PPUADDR: ; PPUDATA: ; The loaded PPU tile data. ; ; Temp_08: ; Temp_09: ; Clobbered. ; ; CALLS: ; MMC1_UpdateROMBank ; PPU_SetVRAMIncrementAdd1Across ; ; XREFS: ; Game_EnterAreaHandler ; Game_EnterBuilding ; Game_ExitBuilding ; Game_LoadCurrentArea ; Game_LoadFirstLevel ;============================================================================
[ceb8]Area_LoadTiles:; [$ceb8]
; ; Load the address for the tile data based on the tiles index. ;
[ceb8]LDA Area_TilesIndex; Set A = tileset index [ceba]ASL A; Set A = tileset index * 2 (word offset) [cebb]TAY; Set Y = word offset into the pointer table [cebc]LDA TILE_INDEX_TO_ADDR,Y; Read the low byte from the table [cebf]STA Area_TilesReadAddress; Save low byte to TilesAddress [cec1]LDA TILE_INDEX_TO_ADDR+1,Y; Read the high byte from the table [cec4]STA Area_TilesReadAddress+1; Set high byte to TilesAddress
; ; Set the PPU to increment by +1 after each PPUDATA write. ;
[cec6]JSR PPU_SetVRAMIncrementAdd1Across; Set PPUDATA to increment by 1 per write.
; ; Load and set the PPU address for the current set of ; tiles. Set the upper byte from the table and lower ; byte to 0. ; ; Upper byte will be 0x18 or 0x1A. ;
[cec9]LDY Area_TilesIndex; Load the tiles index. [cecb]LDA TILE_INDEX_TO_PPU_ADDR_UPPER,Y; Convert to a PPU address via lookup table (0x18 or 0x1A). [cece]STA a:PPUADDR; Write as the upper byte. [ced1]LDA #$00; 0x00 = Lower byte (start of page). [ced3]STA a:PPUADDR; Write as the lower byte.
; ; Set the remaining number of tiles. ; ; This is a 16-bit integer composed of: ; ; Temp_08: Lower byte ; Temp_09: Upper byte ;
[ced6]STA Temp_08; Set lower byte of the remaining tile count to 0. [ced8]LDA TILE_INDEX_TO_NUM_CHR_PAGES,Y; Load the number of pages to load. [cedb]STA Temp_09; Set as the upper byte.
; ; Save the current bank and switch to bank 4 (tile data). ;
[cedd]LDA a:CurrentROMBank; A = Current ROM bank. [cee0]PHA; Push it to the stack. [cee1]LDX #$04; 4 = Tiles [cee3]JSR MMC1_UpdateROMBank; Switch to bank 4 (tile data)
; ; Begin loading tiles into PPUDATA. ;
[cee6]LDY #$00; Set Y = 0 (start page of loop)
; ; Stream 1 byte from tiles to the PPU. ;
[cee8]@_copyLoop:; [$cee8] [cee8]LDA (Area_TilesReadAddress),Y; Load a byte from the tiles. [ceea]STA a:PPUDATA; Write to the PPU.
; ; Increment our source pointer (Y++). ; ; If it wraps up to 0, we need to increment the upper byte ; of the tiles address it's loading from. ;
[ceed]INY; Y++ (source pointer). [ceee]BNE @_noCarry; If wrapped to 0, jump. [cef0]INC Area_TilesReadAddress+1; Increment the upper byte of the tiles address.
; ; Decrement the 16-bit remaining tile count. ;
[cef2]@_noCarry:; [$cef2] [cef2]LDA Temp_08; Load the lower byte of the remaining count. [cef4]SEC [cef5]SBC #$01; Subtract 1. [cef7]STA Temp_08; And set it. [cef9]LDA Temp_09; Load the upper byte of the remaining count. [cefb]SBC #$00; Subtract carry, if lower wrapped. [cefd]STA Temp_09; And set it. [ceff]BCS @_copyLoop; If count > 0, loop.
; ; Switch back to the previous bank. ;
[cf01]PLA; Pop the previous bank. [cf02]TAX; Transfer to X. [cf03]JSR MMC1_UpdateROMBank; And switch to it. [cf06]RTS
;============================================================================ ; Offsets of the tiles in the bank ; ; Index into the table are based on Area_TilesIndex. ; ; TODO ; ; XREFS: ; Area_LoadTiles ;============================================================================
; ; XREFS: ; Area_LoadTiles ;
[cf07]TILE_INDEX_TO_ADDR:; [$cf07] [cf07].word AREA_TILESETS_EOLIS; [0]: Eolis [cf09].word AREA_TILESETS_BRANCHES; [1]: Branches [cf0b].word AREA_TILESETS_TRUNK; [2]: Trunk [cf0d].word AREA_TILESETS_MIST; [3]: Mist [cf0f].word AREA_TILESETS_DARTMOOR_EVIL_LAIR; [4]: Dartmoor Castle Evil Lair [cf11].word AREA_TILESETS_TOWNS; [5]: Towns [cf13].word AREA_TILESETS_KINGSROOM_GURU_HOSPITAL; [6]: King's Room Guru Room Hospital [cf15].word AREA_TILESETS_SHOPS_HOUSE_TAVERN; [7]: Tavern Tool Shop Key Shop House Meat Shop [cf17].word AREA_TILESETS_MARTIALARTS_MAGICTRAINER; [8]: Martial Arts Magic Trainer
;============================================================================ ; Which parts of the PPU tiles are loaded. ; ; Index into the table are based on Area_TilesIndex. ; ; XREFS: ; Area_LoadTiles ;============================================================================
; ; XREFS: ; Area_LoadTiles ;
[cf19]TILE_INDEX_TO_PPU_ADDR_UPPER:; [$cf19] [cf19].byte $18; [0]: Eolis [cf1a].byte $18; [1]: Branches [cf1b].byte $18; [2]: Trunk [cf1c].byte $18; [3]: Mist [cf1d].byte $18; [4]: Dartmoor Castle Evil Lair [cf1e].byte $18; [5]: Towns [cf1f].byte $1a; [6]: King's Room Guru Room Hospital [cf20].byte $1a; [7]: Tavern Tool Shop Key Shop House Meat Shop [cf21].byte $1a; [8]: Martial Arts Magic Trainer
;============================================================================ ; The number of CHR pages to load for a tiles index. ; ; Index into the table are based on Area_TilesIndex. ; ; The results are further translated as: ; ; 8 == 0x80 ; 6 == 0x60 ; 4 == 0x40 ; ; XREFS: ; Area_LoadTiles ;============================================================================
; ; XREFS: ; Area_LoadTiles ;
[cf22]TILE_INDEX_TO_NUM_CHR_PAGES:; [$cf22] [cf22].byte $08; [0]: Eolis [cf23].byte $08; [1]: Branches [cf24].byte $08; [2]: Trunk [cf25].byte $08; [3]: Mist [cf26].byte $08; [4]: Dartmoor Castle Evil Lair [cf27].byte $08; [5]: Towns [cf28].byte $06; [6]: King's Room Guru Room Hospital [cf29].byte $06; [7]: Tavern Tool Shop Key Shop House Meat Shop [cf2a].byte $04; [8]: Martial Arts Magic Trainer
;============================================================================ ; Set the VRAM address increment per CPU read/write of PPUDATA to 0: Add 1, ; going across. ; ; INPUTS: ; PPU_ControlFlags: ; The saved PPU control flags. ; ; OUTPUTS: ; PPUCTRL: ; PPU_ControlFlags: ; The updated PPU control flags. ; ; XREFS: ; Area_LoadTiles ;============================================================================
[cf2b]PPU_SetVRAMIncrementAdd1Across:; [$cf2b] [cf2b]LDA PPU_ControlFlags; Load the PPU control flags. [cf2d]AND #$fb; Set VRAM address increment per CPU read/write of PPUDATA to 0: Add 1, going across. [cf2f]STA PPU_ControlFlags; Set it to SavedPPUCtrl. [cf31]STA a:PPUCTRL; Set it to PPUCTRL. [cf34]RTS
;============================================================================ ; MAYBE DEADCODE: Clear the PPU buffer. ; ; Both the offset and upper bounds will be reset back to 0. ; ; INPUTS: ; None ; ; OUTPUTS: ; PPUBuffer_ReadOffset: ; The offset to clear. ; ; PPUBuffer_WriteOffset: ; The upper bounds value to clear. ;============================================================================
[cf35]PPUBuffer_Clear:; [$cf35] [cf35]LDA #$00 [cf37]STA PPUBuffer_ReadOffset; Set offset = 0 [cf39]STA PPUBuffer_WriteOffset; Set upper bounds = 0
; ; XREFS: ; PPUBuffer_Draw ;
[cf3b]RETURN_CF3B:; [$cf3b] [cf3b]RTS
;============================================================================ ; Draw from the PPU buffer to the PPU. ; ; This will take the entries in the current buffer and draw ; them to the PPU. Buffer entries may contain a command ; opcode, or it may contain values to write. ; ; Let's look into the two methods for drawing. ; ; 1. Command control ; ; If the first byte to process in the buffer is a value ; decreasing from 0x00 to 0xFA, it will be treated as a ; command, and the entry in ; PPUBUFFER_DRAW_COMMANDS will ; process the remaining bytes (or just return). ; ; The following commands are supported: ; ; 0x00: Write palette data ; ; PPUBuffer_DrawCommand_WritePalette ; ; 0xFC: Rotate 16 pixels of tiles right by 1 pixel at ; a given address (at the next two bytes from ; the buffer). ; ; PPUBuffer_DrawCommand_RotateTilesRight1Pixel ; ; 0xFA: TODO ; PPUBuffer_DrawCommand_RemoveVerticalLines ; ; Commands 0xFF, 0xFE, 0xFD, and 0xFB just return. ; ; Once a command is processed, the operation is complete. ; Further entries in the buffer will be processed in ; future calls. ; ; 2. Data drawing ; ; The first byte at the buffer offset will contain a ; value with a PPUCTRL flag in bit 7 and the number of ; bytes to write in the remaining bit. ; ; The second and third contain the upper and lower bytes ; of PPUADDR (respectively). ; ; Additional bytes are the <length> bytes to write. ; ; Drawing finishes after any of the following conditions ; are met: ; ; 1. The buffer is empty. ; 2. 6 entries from the buffer have been drawn. ; 3. A maximum number of bytes have been written ; (48 bytes). ; ; INPUTS: ; PPUBuffer: ; The PPU buffer to process. ; ; PPUBuffer_ReadOffset: ; The offset within the PPU buffer to process. ; ; PPUBuffer_WriteOffset: ; The upper bounds within the PPU buffer to process. ; ; OUTPUTS: ; PPUCTRL: ; PPU control flags. ; ; PPUADDR: ; PPU write address (reset back to 0). ; ; PPUDATA: ; PPU draw data. ; ; TEMP VARIABLES: ; PPUBuffer_Temp_PendingEntryCount: ; The maximum number of entries remaining to ; process. ; ; PPUBuffer_Temp_TotalByteLength: ; The total number of bytes processed. ; ; CALLS: ; PPUBuffer_DrawCommand_WritePalette ; PPUBuffer_DrawCommand_RotateTilesRight1Pixel ; PPUBuffer_DrawCommand_RemoveVerticalLines ; ; XREFS: ; PPUBuffer_DrawAll ; PPU_HandleOnInterrupt ; Player_DrawSpriteImmediately ;============================================================================
[cf3c]PPUBuffer_Draw:; [$cf3c]
; ; Check if there's anything in the buffer to write. ; If not, we're done. ;
[cf3c]LDA PPUBuffer_ReadOffset; A = current buffer offset [cf3e]CMP PPUBuffer_WriteOffset; Compare against the upper bound of the PPU buffer. [cf40]BEQ RETURN_CF3B; If they're the same, we have nothing to write. We're done.
; ; Check the value in the offset and see if it appears to ; be a command opcode of some sort (an index into ; PPUBUFFER_DRAW_COMMANDS. ;
[cf42]LDX PPUBuffer_ReadOffset; X = current PPU offset [cf44]LDA #$00; Take a value from $00 to $FA (decreasing) and turn it into an index. $00 = 0 $FF = 1 $FE = 2 $FD = 3 $FC = 4 $FB = 5 $FA = 6 [cf46]SEC [cf47]SBC PPUBuffer,X; Get the value from the buffer at this offset. [cf4a]CMP #$07; Check if it's a command code. [cf4c]BCS @_beginDraw; If not, draw normally.
; ; It is a command code. Increment to the next byte, look up ; the entry for the command in our table, and schedule the ; address following for the execution after this returns. ;
[cf4e]INC PPUBuffer_ReadOffset; Consume the byte in the buffer. [cf50]ASL A [cf51]TAY; Begin looking up in the table. [cf52]LDA PPUBUFFER_DRAW_COMMANDS+1,Y; High byte of the command address. [cf55]PHA; Push to the stack. [cf56]LDA PPUBUFFER_DRAW_COMMANDS,Y; Low byte of the command address. [cf59]PHA; Push to the stack. [cf5a]RTS; Return. The next address following will execute.
; ; This is standard data to write to the PPU, not a command. ;
[cf5b]@_beginDraw:; [$cf5b] [cf5b]LDA #$d0; Total byte length that can be written. [cf5d]STA PPUBuffer_Temp_TotalByteLength; Set it. [cf5f]LDA #$06; Max number of entries in the buffer that can be written. [cf61]STA PPUBuffer_Temp_PendingEntryCount; Set it.
; ; Loop through the buffer until either it's cleared or we've ; processed 6 entries. ;
[cf63]@_loopBuffer:; [$cf63] [cf63]LDX PPUBuffer_ReadOffset; X = Read offset into the buffer. [cf65]CPX PPUBuffer_WriteOffset; Compare it to the write offset. [cf67]BEQ @_finishLoop; If it's the same, the loop is finished.
; ; Compute the PPUCTRL to set for this series of writes. ; ; If the most-significant bit is set, we'll set it to ; Add32 Down mode. Otherwise, we'll set it to Add1 Across ; mode. ;
[cf69]LDA PPU_ControlFlags; A = PPU control flags. [cf6b]AND #$fb; Set to Add 1 Across. [cf6d]TAY; Y = Resulting flags. [cf6e]LDA PPUBuffer,X; Load the byte from the buffer. [cf71]BPL @_processData; If bit 7 is set, jump. [cf73]AND #$7f; Else, set VBLank Disable. [cf75]INY; And Inc Add 32 Down. [cf76]INY [cf77]INY [cf78]INY [cf79]@_processData:; [$cf79] [cf79]STY a:PPUCTRL; Store the new control flags.
; ; Load the first byte from the buffer at our offset. ; Mask out bit 7 (our flag above). This will be our ; data length counter. ;
[cf7c]LDA PPUBuffer,X; A = value from the buffer [cf7f]AND #$7f; Mask out control bit 7. [cf81]TAY; Y = A (save until we load the address) [cf82]INX; Set to the next byte. [cf83]LDA PPUBuffer,X; Load the upper PPU byte address from the buffer. [cf86]STA a:PPUADDR; Set as the upper byte in PPUADDR. [cf89]INX; X = X + 1 [cf8a]LDA PPUBuffer,X; Load the lower PPU byte address from the buffer. [cf8d]STA a:PPUADDR; Set as the lower byte in PPUADDR. [cf90]INX; X = X + 1 [cf91]TYA; Y = A (restore from above) [cf92]CLC [cf93]ADC PPUBuffer_Temp_TotalByteLength; Add the length to our running total. [cf95]STA PPUBuffer_Temp_TotalByteLength
; ; Begin writing <length> bytes from the buffer to the PPU. ;
[cf97]@_loopData:; [$cf97] [cf97]LDA PPUBuffer,X; Load the byte to draw at our current offset. [cf9a]INX; Increment the offset. [cf9b]STA a:PPUDATA; Set the byte in our PPUDATA. [cf9e]DEY; Decrement the nuber of bytes to write. [cf9f]BNE @_loopData; If we're not done yet, loop.
; ; We're done writing the data bytes. We can now update the ; offset and check whether to move on to the next, or if ; we're done with the buffer for now. ; ; We're done with the buffer if we've hit 6 entries, maxed ; out our byte length counter, or hit a new command opcode. ;
[cfa1]STX PPUBuffer_ReadOffset; Increment our offset into the buffer to skip the length and address bytes and all the data bytes. [cfa3]DEC PPUBuffer_Temp_PendingEntryCount; Decrement our total allowed entries for this operation. [cfa5]BEQ @_finishLoop; If we can't do any more, we're done. [cfa7]LDY PPUBuffer,X; Load the next byte to process from the buffer. [cfaa]DEY [cfab]CPY #$f9; Check if it's a command byte. [cfad]BCS @_finishLoop; If it is, we're done. This will be handled next call. [cfaf]LDA PPUBuffer_Temp_TotalByteLength; Check the total byte length. [cfb1]BMI @_loopBuffer; If there's room to go, loop.
; ; We're done with everything. Clear out the PPU addresses and ; return. ;
[cfb3]@_finishLoop:; [$cfb3] [cfb3]LDA #$00 [cfb5]STA a:PPUADDR; Set upper PPUADDR to 0. [cfb8]STA a:PPUADDR; Set lower PPUADDR to 0.
; ; v-- Fall through --v ;
;============================================================================ ; No-op draw command. ; ; This is just used as a destination for the draw commands ; lookup table below. It's also the end of the function. ; above. ; ; INPUTS: ; None ; ; OUTPUTS: ; None ; ; XREFS: ; PPUBUFFER_DRAW_COMMANDS ; [$PRG15_MIRROR::cfbe] ; PPUBUFFER_DRAW_COMMANDS ; [$PRG15_MIRROR::cfc0] ; PPUBUFFER_DRAW_COMMANDS ; [$PRG15_MIRROR::cfc2] ; PPUBUFFER_DRAW_COMMANDS ; [$PRG15_MIRROR::cfc6] ;============================================================================
[cfbb]PPUBuffer_DrawCommand_Noop:; [$cfbb] [cfbb]RTS
;============================================================================ ; Handlers for special PPU buffer draw commands. ; ; XREFS: ; PPUBuffer_Draw ;============================================================================
; ; XREFS: ; PPUBuffer_Draw ;
[cfbc]PPUBUFFER_DRAW_COMMANDS:; [$cfbc] [cfbc].word PPUBuffer_DrawCommand_WritePalette-1; [0]: Command 0x00: Write Palette to PPU [cfbe].word PPUBuffer_DrawCommand_Noop-1; [1]: Command 0xFF [cfc0].word PPUBuffer_DrawCommand_Noop-1; [2]: Command 0xFE [cfc2].word PPUBuffer_DrawCommand_Noop-1; [3]: Command 0xFD [cfc4].word PPUBuffer_DrawCommand_RotateTilesRight1Pixel-1; [4]: Command 0xFC: Rotate Tiles Right [cfc6].word PPUBuffer_DrawCommand_Noop-1; [5]: Command 0xFB [cfc8].word PPUBuffer_DrawCommand_RemoveVerticalLines-1; [6]: Command 0xFA: Remove Vertical Lines
;============================================================================ ; Wait for capacity in the PPU buffer. ; ; This will block until the PPU buffer has been flushed and ; there's at least 36 bytes remaining. ; ; INPUTS: ; None. ; ; OUTPUTS: ; None. ; ; CALLS: ; PPUBuffer_HasCapacity ; ; XREFS: ; PPUBuffer_QueueCommandOrLength ; PPUBuffer_WaitForCapacity ;============================================================================
[cfca]PPUBuffer_WaitForCapacity:; [$cfca] [cfca]JSR PPUBuffer_HasCapacity; Is there capacity? [cfcd]BCC PPUBuffer_WaitForCapacity; If not, loop. [cfcf]RTS
;============================================================================ ; Return whether there's at least 36 bytes of PPU buffer free. ; ; XXX Not right. Re-analyze this. ; ; INPUTS: ; PPUBuffer_ReadOffset: ; The current offset into the PPU buffer. ; ; PPUBuffer_WriteOffset: ; The upper bounds of the PPU buffer. ; ; OUTPUTS: ; C: ; 0 = Fewer than 36 bytes available in the buffer. ; 1 = 36 or more bytes available in the buffer. ; ; XREFS: ; PPUBuffer_WaitForCapacity ;============================================================================
[cfd0]PPUBuffer_HasCapacity:; [$cfd0] [cfd0]LDA PPUBuffer_ReadOffset; A = PPU buffer offset. [cfd2]SEC [cfd3]SBC PPUBuffer_WriteOffset; A = offset - upper [cfd5]BEQ @_return; If (offset - upper) == 0, jump to return false. [cfd7]CMP #$24; Set whether there's at least 36 bytes difference. [cfd9]@_return:; [$cfd9] [cfd9]RTS [cfda].byte $09,$80; [$cfda] word
;============================================================================ ; Queue for writing PPU data to the target address. ; ; This will wait for capacity in the PPU buffer and then ; write the given command or length, followed by the target ; PPU address to write to. That's a total of 3 bytes. ; ; Callers can then write additional data to the buffer. ; ; This doesn't update PPUBuffer_WriteOffset. Callers ; are responsible for that. ; ; INPUTS: ; A: ; The command or length to write. ; ; PPU_TargetAddr: ; The PPU target address to write to. ; ; PPUBuffer_WriteOffset: ; The current upper bounds of the PPU target buffer. ; This is where the state will be written to. ; ; OUTPUTS: ; X: ; The new write offset in the PPU buffer. ; ; PPUBuffer: ; The updated PPU buffer. ; ; CALLS: ; PPUBuffer_WaitForCapacity ; ; XREFS: ; DEADCODE_FUN_PRG12__9041 ; IScriptAction_AddInventoryItem_ClearTextBox ; PasswordScreen_DrawMessage ; PasswordScreen_WriteCharTile ; TextBox_Close ; TextBox_DrawItemImage ; TextBox_FillBackground ; UI_DrawString ; Area_SetBlocks_SetAttributes ; Area_SetBlocks_WriteBlockData12 ; Area_SetBlocks_WriteBlockData34 ; DEADCODE_FUN_PRG15_MIRROR__c033 ; IScripts_DrawPortraitTileToPPU ; PPUBuffer_WriteValueMany ; Player_LoadWeaponTile ; Sprites_LoadImageForCurrentSprite ; TextBox_FillPlaceholderTextAtLineWithStartChar ; TextBox_LoadItemSourceTiles ; TextBox_QueuePPUBufferTextBoxLength ; TextBox_WriteChar ; UI_ClearSelectedItemPic ; UI_DrawDigitsZeroPadded ; UI_DrawManaOrHPBar ;============================================================================
[cfdc]PPUBuffer_QueueCommandOrLength:; [$cfdc]
; ; Wait for capacity in the buffer. ;
[cfdc]PHA; Push the length/command to the stack. [cfdd]JSR PPUBuffer_WaitForCapacity; Wait for capacity in the buffer.
; ; There's capacity. Set up to write. ; ; Start with the length or command byte. ;
[cfe0]PLA [cfe1]LDX PPUBuffer_WriteOffset; Load the upper bounds of the buffer. [cfe3]STA PPUBuffer,X; Store the provided value (A) there. [cfe6]INX [cfe7]LDA PPU_TargetAddr_U; Load the upper address byte. [cfe9]STA PPUBuffer,X; Write it to the buffer. [cfec]INX [cfed]LDA PPU_TargetAddr; Load the lower address byte. [cfef]STA PPUBuffer,X; Write it to the buffer. [cff2]INX [cff3]RTS
;============================================================================ ; Wait until the PPU buffer is clear. ; ; This will continuously loop until the PPU buffer's ; offset and upper bounds match. ; ; INPUTS: ; PPUBuffer_ReadOffset: ; The offset to compare. ; ; PPUBuffer_WriteOffset: ; The upper bounds to compare. ; ; OUTPUTS: ; None ; ; XREFS: ; GameLoop_LoadSpriteImages ; Game_MainLoop ; PPUBuffer_WaitUntilClear ;============================================================================
[cff4]PPUBuffer_WaitUntilClear:; [$cff4] [cff4]LDA PPUBuffer_WriteOffset; Load the upper bounds of the buffer. [cff6]CMP PPUBuffer_ReadOffset; Does it == the offset? [cff8]BNE PPUBuffer_WaitUntilClear; If not, loop. [cffa]RTS
;============================================================================ ; Invoke all draws on the PPU buffer until it's cleared. ; ; This will continuously run PPUBuffer_Draw until ; the ; draw buffer has been cleared. ; ; INPUTS: ; PPUBuffer_ReadOffset: ; The offset to compare. ; ; PPUBuffer_WriteOffset: ; The upper bounds to compare. ; ; OUTPUTS: ; None ; ; CALLS: ; PPUBuffer_Draw ; ; XREFS: ; PPUBuffer_DrawAll ; UI_DrawHUD ;============================================================================
[cffb]PPUBuffer_DrawAll:; [$cffb] [cffb]JSR PPUBuffer_Draw; Draw from the buffer. [cffe]LDA PPUBuffer_WriteOffset; Load the upper bounds. [d000]CMP PPUBuffer_ReadOffset; Does it match the offset? [d002]BNE PPUBuffer_DrawAll; If not, loop. [d004]RTS
;============================================================================ ; DEADCODE ; ; This would have loaded background palette 4 and queue ; writing it to the PPU. ; ; It's not used in the shipped game. ;============================================================================
[d005]UNUSED_PPUBuffer_DrawCommand_WriteBackgroundPalette4:; [$d005] [d005]LDA #$04 [d007]JSR Screen_LoadUIPalette [d00a]JMP PPUBuffer_WritePalette
;============================================================================ ; DEADCODE ; ; This would have loaded the background palette for the ; current screen and queue writing it to the PPU. ; ; It's not used in the shipped game. ;============================================================================
[d00d]UNUSED_WriteCurrentBackgroundPalette:; [$d00d] [d00d]LDA a:Screen_PaletteIndex [d010]JSR Screen_LoadUIPalette [d013]JMP PPUBuffer_WritePalette
;============================================================================ ; Draw Command 0x00: Write the screen palette. ; ; This flushes Screen_PaletteData_Tiles to the PPU, ; writing both the background and sprite palette. ; ; INPUTS: ; Screen_PaletteData_Tiles: ; The screen palette to write. ; ; OUTPUTS: ; PPUADDR: ; PPUDATA: ; The written PPU data. ; ; XREFS: ; PPUBUFFER_DRAW_COMMANDS ; [$PRG15_MIRROR::cfbc] ;============================================================================
[d016]PPUBuffer_DrawCommand_WritePalette:; [$d016]
; ; Write all 32 palettes to 0x3F00. ;
[d016]LDA #$3f; 0x3F == Upper byte. [d018]STA a:PPUADDR; Write as the upper PPU address. [d01b]LDX #$00; 0x00 == Lower byte. [d01d]STX a:PPUADDR; Write as the lower PPU address.
; ; Loop through 32 bytes of palette data and write to the PPU. ;
[d020]LDY #$20; Y = 32 (loop counter). [d022]@_paletteLoop:; [$d022] [d022]LDA Screen_PaletteData_Tiles,X; A = Palette byte at the index. [d025]STA a:PPUDATA; Write to the PPU. [d028]INX; Increment the PPU address. [d029]DEY; Y-- (loop counter). [d02a]BNE @_paletteLoop; If > 0, loop.
; ; Avoid palette corruption by setting to address 0x3F00, ; and then zeroing out (0x0000). ; ; See https://www.nesdev.org/wiki/PPU_registers#Palette_corruption ;
[d02c]LDA #$3f; 0x3F == Upper byte. [d02e]STA a:PPUADDR; Write as the upper PPU address. [d031]STY a:PPUADDR; Zero out the lower byte. [d034]STY a:PPUADDR; Now zero out the upper byte. [d037]STY a:PPUADDR; And the lower again. [d03a]RTS
;============================================================================ ; Populate the palette data for HUD/textbox elements. ; ; This will load the palette data for the background of ; the HUD and textbox data stored in bank 11 at the ; address stored in Temp_08 and populate ; Screen_PaletteData_Tiles. ; ; INPUTS: ; Y: ; The palette destination index to load into. ; ; Temp_08: ; The address to load data from in bank 11. ; ; CurrentROMBank: ; The current ROM bank. ; ; OUTPUTS: ; Screen_PaletteData_Tiles: ; The resulting palette data. ; ; Palette_SpritePaletteIndex: ; The index of the loaded palette. ; ; UI_AttributeDataIndex: ; The index of the background color used for ; textboxes and the HUD. ; ; Temp_08: ; Temp_09: ; Clobbered ; ; CALLS: ; MMC1_UpdateROMBank ; Palette_IndexToROMOffset16 ; ; XREFS: ; StartScreen_Draw ; Player_HandleDeath ; Screen_SetupNew ; UNUSED_PPUBuffer_DrawCommand_WriteBackgroundPalette4 ; UNUSED_WriteCurrentBackgroundPalette ;============================================================================
[d03b]Screen_LoadUIPalette:; [$d03b] [d03b]TAY; Y = A (palette index).
; ; Save the current bank and load bank 11 (palette data). ;
[d03c]LDA a:CurrentROMBank; Load the current ROM bank. [d03f]PHA; Push it to the stack. [d040]LDX #$0b [d042]JSR MMC1_UpdateROMBank; Switch to bank 11.
; ; Set the attribute data index for the HUD. ;
[d045]LDA DAT_81f0,Y; Load the HUD attribute data for this index. [d048]STA a:UI_AttributeDataIndex; Set it for the HUD/textboxes.
; ; Restore our current bank. ;
[d04b]PLA; Pop A (previous bank) from the stack. [d04c]TAX; X = A [d04d]JSR MMC1_UpdateROMBank; Switch back to the bank.
; ; Get the offset into the bank. ;
[d050]TYA; A = Y (palette index). [d051]JSR Palette_IndexToROMOffset16; Convert to a relative offset into the bank. [d054]ADC #$00; Add 0 + C (also 0) to the lower byte. [d056]STA Temp_08; Store it. [d058]LDA Temp_09; Load the computed upper byte. [d05a]ADC #$80; Add 0x80. [d05c]STA Temp_09; Store it.
; ; Write that palette into the sprite palette. ;
[d05e]LDY #$0f; Start writing into index 16 (sprite palette). [d060]BNE Screen_SetPaletteData; Load it.
; ; v-- Fall through --v ;
;============================================================================ ; Populate the sprite palette data for the screen. ; ; This will load the palette data for sprites from the ; data stored in bank 11 at the address stored in ; Temp_08 and populate ; Screen_PaletteData_Tiles. ; ; INPUTS: ; Y: ; The palette destination index to load into. ; ; Temp_08: ; The address to load data from in bank 11. ; ; CurrentROMBank: ; The current ROM bank. ; ; OUTPUTS: ; Screen_PaletteData_Tiles: ; The resulting palette data. ; ; Palette_SpritePaletteIndex: ; The index of the loaded palette. ; ; Temp_08: ; Temp_09: ; Clobbered ; ; CALLS: ; MMC1_UpdateROMBank ; Palette_IndexToROMOffset16 ; ; XREFS: ; Game_EnterAreaHandler ; Game_EnterBuilding ; Game_LoadCurrentArea ; Game_LoadFirstLevel ; IScripts_ClearPortraitImage ; IScripts_LoadPortraitTiles ; Screen_Load ;============================================================================
[d062]Screen_LoadSpritePalette:; [$d062] [d062]STA a:Palette_SpritePaletteIndex; Set the palette index based on the provided A.
; ; Compute the ROM address for this palette. ; ; This should be 0x81C0 + palette offset. ;
[d065]JSR Palette_IndexToROMOffset16; Convert the relative address for the palette. [d068]ADC #$c0; Add 0xC0 to the lower byte. [d06a]STA Temp_08; Store it. [d06c]LDA Temp_09; Load the computed upper byte. [d06e]ADC #$81; Add 0x81 to it. [d070]STA Temp_09; Store it back.
; ; Save the previous bank and switch to bank 11 (palette data). ;
[d072]LDY #$1f; Y = Destination for the palette (31).
; ; v-- Fall through --v ;
;============================================================================ ; Populate palette data for the screen. ; ; This will load the palette data from the data stored ; in bank 11 at the address stored in Temp_08 and ; populate Screen_PaletteData_Tiles. ; ; This is common to both Screen_LoadUIPalette ; and Screen_LoadSpritePalette. ; ; INPUTS: ; Y: ; The palette destination index to load into. ; ; Temp_08: ; The address to load data from in bank 11. ; ; CurrentROMBank: ; The current ROM bank. ; ; OUTPUTS: ; Screen_PaletteData_Tiles: ; The resulting palette data. ; ; CALLS: ; MMC1_UpdateROMBank ; ; XREFS: ; Screen_LoadUIPalette ;============================================================================
[d074]Screen_SetPaletteData:; [$d074]
; ; Save the current bank. ;
[d074]LDA a:CurrentROMBank; Load the current ROM bank. [d077]PHA; Push it to the stack. [d078]LDX #$0b [d07a]JSR MMC1_UpdateROMBank; Switch to bank 11.
; ; Copy 16 bytes of palette into ROM. ;
[d07d]TYA; A = Y (palette data index) [d07e]TAX; X = A [d07f]LDY #$0f; Y = 15 (loop counter) [d081]@_loadPaletteLoop:; [$d081] [d081]LDA (Temp_08),Y; Load the byte to copy. [d083]STA Screen_PaletteData_Tiles,X; Store as the palette data at the index. [d086]DEX; X-- (destination index) [d087]DEY; Y-- (loop counter) [d088]BPL @_loadPaletteLoop; If >= 0, loop.
; ; Switch bank to the previous bank. ;
[d08a]PLA; Pop the previous bank. [d08b]TAX; X = A [d08c]JSR MMC1_UpdateROMBank; Switch to the bank. [d08f]RTS
;============================================================================ ; Queue writing the palette to the PPU. ; ; This will write Draw Command 0 to the PPU buffer, ; responsible for writing the palette. ; ; INPUTS: ; Screen_PaletteData_Tiles: ; The palette data to write. ; ; PPUBuffer_WriteOffset: ; The upper bounds of the buffer. ; ; OUTPUTS: ; PPUBuffer: ; The PPU buffer to append to. ; ; PPUBuffer_WriteOffset: ; The new size of the buffer. ; ; XREFS: ; PasswordScreen_Show ; SplashAnimation_DrawScenery ; SplashAnimation_Intro_SomethingA708 ; StartScreen_Draw ; IScripts_ClearPortraitImage ; IScripts_LoadPortraitTiles ; Player_HandleDeath ; Screen_SetFadeOutPalette ; Screen_SetupSprites ; UNUSED_PPUBuffer_DrawCommand_WriteBackgroundPalette4 ; UNUSED_WriteCurrentBackgroundPalette ;============================================================================
[d090]PPUBuffer_WritePalette:; [$d090] [d090]LDA #$00; A = 0 [d092]LDX PPUBuffer_WriteOffset; X = upper bounds of the buffer [d094]STA PPUBuffer,X; Store the 0 in the buffer at the upper bounds. [d097]INX; Increment the upper bounds. [d098]STX PPUBuffer_WriteOffset [d09a]RTS
;============================================================================ ; Return a 16-bit offset for a value. ; ; This multiplies the provided index by 16 and splits the ; 16-bit result into a low byte (A) and a high byte ; (Temp_09). ; ; Carry is cleared on exit so the caller can 16-bit add a ; base address with ADC (low) / ADC (high) cleanly. ; ; This is used by palette code to build a ROM pointer in ; the form of: ; ; PaletteAddr = Base + (index * 16) ; ; INPUTS: ; A: ; The index to convert to a 16-byte offset. ; ; OUTPUTS: ; Temp_09: ; The high byte (index * 16) ; ; A: ; The low byte (index * 16) ; ; C (Carry): ; Cleared (0) for subsequent 16-bit ADC adds. ; ; Y: ; Set to 0 (clobbered). ; ; XREFS: ; Screen_LoadSpritePalette ; Screen_LoadUIPalette ; Screen_SetFadeOutPalette ;============================================================================
[d09b]Palette_IndexToROMOffset16:; [$d09b] [d09b]LDY #$00; Y = 0 [d09d]STY Temp_09; Store it temporarily. [d09f]ASL A; Shift left, upper bit into Carry. [d0a0]ROL Temp_09; Rotate left, carry into bit 0, bit 7 into carry. [d0a2]ASL A; Repeat 3 more times, moving lower nibble into A, upper nibble into Temp_09. [d0a3]ROL Temp_09 [d0a5]ASL A [d0a6]ROL Temp_09 [d0a8]ASL A [d0a9]ROL Temp_09 [d0ab]CLC; Clear carry. [d0ac]RTS; Return A and Temp_09.
;============================================================================ ; Update the current palette for the fade-out stage. ; ; This will iterate through the background palette, ; subtracting a value from each byte depending on the stage ; (increasing along with the stage's value). It works out ; as: ; ; Screen_PaletteData_Tiles[i] = ; Screen_FadeOutStage * 0x10 ; ; INPUTS: ; A: ; The index of the palette to use as a source. ; ; CurrentROMBank: ; The current ROM bank. ; ; Screen_FadeOutStage: ; The current stage of fade-out, between 0 and 3. ; ; FADE_OUT_DELTA_TABLE: ; The table of palette values to subtract, based on ; the stage. ; ; OUTPUTS: ; Screen_PaletteData_Tiles: ; The modified background palette data. ; ; CALLS: ; MMC1_UpdateROMBank ; PPUBuffer_WritePalette ; Palette_IndexToROMOffset16. ; ; XREFS: ; Screen_NextTransitionState ;============================================================================
[d0ad]Screen_SetFadeOutPalette:; [$d0ad]
; ; Get an address for the provided palette in bank 11. ;
[d0ad]JSR Palette_IndexToROMOffset16; Convert the palette to a 16-bit integer. [d0b0]ADC #$00; Add 0 and carry (which is also 0) to the lower byte. [d0b2]STA Temp_08; Store temporarily as the lower byte. [d0b4]LDA Temp_09; Load it back into A. [d0b6]ADC #$80; Add 0x80. [d0b8]STA Temp_09; Store as the upper byte.
; ; Save the current bank and switch to bank 11. ;
[d0ba]LDA a:CurrentROMBank; Load the current bank. [d0bd]PHA; Push it to the stack. [d0be]LDX #$0b; 11 = Sprite info/palettes bank. [d0c0]JSR MMC1_UpdateROMBank; Switch to that bank. [d0c3]LDX a:Screen_FadeOutStage
; ; Beginning looping through the 16 bytes of palette. ; ; This will load the palette for the current screen and ; then subtract a value from a fade-out table ; (FADE_OUT_DELTA_TABLE) based on the fade-out ; cycle (Screen_FadeOutStage). ;
[d0c6]LDY #$0f; Y = 15 (loop counter). [d0c8]@_loop:; [$d0c8] [d0c8]LDA (Temp_08),Y; Load the palette data at Y. [d0ca]SEC; Set C = 1 so SBC won't subtract C. [d0cb]SBC FADE_OUT_DELTA_TABLE,X; Subtract the value form the lookup table at the transition index. [d0ce]BCS @_setPaletteData; If > 0, jump to use this value as the palette.
; ; The subtraction yielded a palette value <= 0, so ; hard-code to 15. ;
[d0d0]LDA #$0f; Set palette = 15. [d0d2]@_setPaletteData:; [$d0d2] [d0d2]STA Screen_PaletteData_Tiles,Y; Set the palette data to A at index Y. [d0d5]DEY; Y-- [d0d6]BPL @_loop; If >= 0, loop.
; ; Restore the previous bank. ;
[d0d8]PLA; Pull the previous bank from the stack. [d0d9]TAX; Set in X. [d0da]JSR MMC1_UpdateROMBank; And switch to that bank. [d0dd]JMP PPUBuffer_WritePalette; Append to the PPU buffer to trigger a screen update.
;============================================================================ ; Table of values to subtract from a palette to fade-out the current screen. ; ; XREFS: ; Screen_SetFadeOutPalette ;============================================================================
; ; XREFS: ; Screen_SetFadeOutPalette ;
[d0e0]FADE_OUT_DELTA_TABLE:; [$d0e0] [d0e0].byte $10; [0]: [d0e1].byte $20; [1]: [d0e2].byte $30; [2]: [d0e3].byte $40; [3]:
;============================================================================ ; Play a sound effect. ; ; INPUTS: ; A: ; The sound ID to play. ; ; OUTPUTS: ; Temp_SoundIDToPlay: ; Clobbered. ; ; CALLS: ; SoundEffect_SetCurrent ; ; XREFS: ; IScripts_PlayFillingSound ; IScripts_PlayGoldChangeSound ; Menu_UpdateAndDraw ; PasswordScreen_DrawAndHandleInputLoop ; PlayerMenu_EquipItem ; PlayerMenu_HandleInvalidChoice ; PlayerMenu_HandleInventoryMenuInput ; PlayerMenu_Show ; Sound_PlayInputSound ; Sound_PlayMoveCursorSound ; SplashAnimation_Maybe_AnimPlayerStep ; StartScreen_CheckHandleInput ; CastMagic_UpdateDeluge ; CastMagic_UpdateFire ; Player_CastMagic ; Player_HandleShieldHitByMagic ; Player_HandleTouchBread ; Player_HandleTouchCoin ; Player_HandleTouchEnemy ; Player_HitEnemyWithMagic ; Player_HitSpriteWithWeapon ; SpriteBehavior_BossDeath ; SpriteBehavior_FlashScreenHitPlayer ; SpriteBehavior_Garbled3 ; SpriteBehavior_Pakukame ; Sprites_ReplaceWithCoinDrop ; Game_DropLadderToMascon ; Game_UnlockDoor ; Player_CheckPushingBlock ; Player_FillHPAndMP ; Player_HandleDeath ; Player_PickUpBattleHelmet ; Player_PickUpBattleSuit ; Player_PickUpBlackOnyx ; Player_PickUpDragonSlayer ; Player_PickUpElixir ; Player_PickUpGlove ; Player_PickUpHourGlass ; Player_PickUpMattock ; Player_PickUpOintment ; Player_PickUpRedPotion ; Player_PickUpWingBoots ; Player_UseHourGlass ; Player_UseMattock ; Player_UseRedPotion ; Player_UseWingBoots ; ScreenEvents_HandleFinalBossKilled ; TextBox_ShowMessageWithSound ;============================================================================
[d0e4]Sound_PlayEffect:; [$d0e4] [d0e4]STA a:Temp_SoundIDToPlay; Store the provided sound effect to play.
; ; Push X and Y on the stack. They will be clobbered when ; playing the sound. ;
[d0e7]TXA; Push X [d0e8]PHA [d0e9]TYA; Push Y [d0ea]PHA [d0eb]LDA a:Temp_SoundIDToPlay; Load the sound ID we stored. [d0ee]JSR SoundEffect_SetCurrent; Play the sound.
; ; Pop X and Y from the stack and restore. ;
[d0f1]PLA; Pop A into Y. [d0f2]TAY [d0f3]PLA; Pop A into X. [d0f4]TAX [d0f5]RTS
;============================================================================ ; TODO: Document Area_ScrollScreenRight ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Screen_SetupNew ;============================================================================
[d0f6]Area_ScrollScreenRight:; [$d0f6] [d0f6]LDA #$00 [d0f8]STA Screen_Maybe_ScrollHorizDirection [d0fa]STA MaybeUnused_006d [d0fc]STA Screen_Maybe_ScrollHorizDirection [d0fe]LDA #$00 [d100]STA PPU_ScrollY [d102]STA PPU_ScrollX [d104]STA PPU_ScrollScreen [d106]STA Screen_LoadBlocksStage [d108]STA Screen_NeighborBlocksLoadedByDirection [d10a]STA Screen_ScrollHorizBlocksLoaded [d10c]STA Screen_MaybeUnused_0075 [d10e]STA Screen_ScrollHorizAttrsLoaded [d110]LDX #$00 [d112]STX PPU_ScrollX [d114]INX [d115]STX PPU_ScrollScreen [d117]LDX #$01 [d119]JSR Area_LoadScrollDataRight [d11c]@_loop:; [$d11c] [d11c]JSR Screen_HandleScroll [d11f]JSR Screen_RunWriteScrollDataHandler [d122]LDA Screen_ScrollDirection [d124]BPL @_loop [d126]RTS
;============================================================================ ; TODO: Document Area_LoadScrollDataRight ; ; INPUTS: ; X ; ; OUTPUTS: ; TODO ; ; XREFS: ; Area_ScrollScreenRight ; Area_ScrollTo ;============================================================================
[d127]Area_LoadScrollDataRight:; [$d127] [d127]STX Screen_ScrollDirection [d129]LDA PPU_ScrollScreen [d12b]STA PPU_ScrollScreenHoriz [d12d]LDA PPU_ScrollX [d12f]STA Screen_ScrollHorizLoadCounter [d131]LDA a:CurrentROMBank [d134]PHA [d135]LDX #$03 [d137]JSR MMC1_UpdateROMBank [d13a]LDA #$00 [d13c]STA Temp_Addr_U [d13e]LDA Area_CurrentScreen [d140]ASL A [d141]ROL Temp_Addr_U [d143]ASL A [d144]ROL Temp_Addr_U [d146]CLC [d147]ADC CurrentArea_ScrollingDataAddr [d149]STA Temp_Addr_L [d14b]LDA Temp_Addr_U [d14d]ADC ScrollingData_U [d14f]STA Temp_Addr_U [d151]LDY #$00 [d153]@_loadScrollData:; [$d153] [d153]LDA (Temp_Addr_L),Y [d155]STA Area_ScreenToTheLeft,Y [d158]INY [d159]CPY #$04 [d15b]BCC @_loadScrollData [d15d]PLA [d15e]TAX [d15f]JSR MMC1_UpdateROMBank [d162]LDX CurrentArea_ROMBank [d164]JSR MMC1_SaveROMBankAndUpdateTo
; ; Load blocks to the left of the screen. ;
[d167]LDA Area_ScreenToTheLeft [d169]JSR Area_LoadBlocks [d16c]LDX #$0f [d16e]LDY #$00 [d170]@_copyLastColumnLeftScreenLoop:; [$d170] [d170]LDA ScreenBuffer,X [d173]STA LastColumnLeftScreen,Y [d176]TXA [d177]CLC [d178]ADC #$10 [d17a]TAX [d17b]INY [d17c]CPY #$10 [d17e]BCC @_copyLastColumnLeftScreenLoop
; ; Load blocks to the right of the screen. ;
[d180]LDA Area_ScreenToTheRight [d182]JSR Area_LoadBlocks [d185]LDX #$00 [d187]LDY #$00 [d189]@_copyRowScreenAboveLoop:; [$d189] [d189]LDA ScreenBuffer,X [d18c]STA FirstColumnInRightScreen,Y [d18f]TXA [d190]CLC [d191]ADC #$10 [d193]TAX [d194]INY [d195]CPY #$10 [d197]BCC @_copyRowScreenAboveLoop
; ; Load blocks from the screen above. ;
[d199]LDA Area_ScreenAbove [d19b]JSR Area_LoadBlocks; Load blocks from screen above. [d19e]LDX #$f0; X = 240 [d1a0]@_copyRowScreenBelowLoop:; [$d1a0] [d1a0]LDA $05d0,X [d1a3]STA $0316,X [d1a6]INX; X++ [d1a7]BNE @_copyRowScreenBelowLoop
; ; Load blocks from the screen below. ;
[d1a9]LDA Area_ScreenBelow [d1ab]JSR Area_LoadBlocks; Load blocks from screen below. [d1ae]LDX #$f0 [d1b0]@LAB_PRG15_MIRROR__d1b0:; [$d1b0] [d1b0]LDA $0510,X [d1b3]STA $0326,X [d1b6]INX [d1b7]BNE @LAB_PRG15_MIRROR__d1b0
; ; Load blocks for the current screen. ;
[d1b9]LDA Area_CurrentScreen [d1bb]JSR Area_LoadBlocks [d1be]JSR MMC1_RestorePrevROMBank [d1c1]LDA Screen_ScrollDirection [d1c3]CMP #$02 [d1c5]BCS @_VerticalScroll [d1c7]TAX [d1c8]LDA #$00 [d1ca]STA Screen_ScrollPlayerTransitionCounter [d1cc]LDA Player_PosY [d1ce]STA Maybe_PlayerY_ForScroll [d1d0]LDA SCREEN_MAYBE_PLAYERX_FOR_SCROLL_MODE,X [d1d3]STA Maybe_PlayerX_ForScroll [d1d5]RTS [d1d6]@_VerticalScroll:; [$d1d6] [d1d6]AND #$01 [d1d8]TAX [d1d9]LDA Player_PosX_Block [d1db]STA Maybe_PlayerX_ForScroll [d1dd]LDA #$00 [d1df]STA Screen_ScrollPlayerTransitionCounter [d1e1]LDA SCREEN_MAYBE_PLAYERX_FOR_SCROLL_MODE_2_,X [d1e4]STA Maybe_PlayerY_ForScroll [d1e6]RTS
; ; XREFS: ; Area_LoadScrollDataRight ;
[d1e7]SCREEN_MAYBE_PLAYERX_FOR_SCROLL_MODE:; [$d1e7] [d1e7].byte $00; [0]: [d1e8].byte $f0; [1]:
; ; XREFS: ; Area_LoadScrollDataRight ;
[d1e9]SCREEN_MAYBE_PLAYERX_FOR_SCROLL_MODE_2_:; [$d1e9] [d1e9].byte $00; [2]: [d1ea].byte $d0; [3]:
;============================================================================ ; Fill the screen buffer with 0s. ; ; This will clear 256 bytes of screen data from $0600 ; to $06FF. ; ; INPUTS: ; None ; ; OUTPUTS: ; ScreenBuffer: ; 256 bytes of screen buffer will be cleared. ; ; XREFS: ; Area_LoadBlocks ;============================================================================
[d1eb]ScreenBuffer_Clear:; [$d1eb] [d1eb]LDX #$00; X = 0 [d1ed]LDA #$00; A = 0
; ; Begin writing empty blocks. ;
[d1ef]@_loop:; [$d1ef] [d1ef]STA ScreenBuffer,X; Write 0 to the screen buffer. [d1f2]INX; X = X + 1 [d1f3]BNE @_loop; If X hasn't wrapped back to 0, loop. [d1f5]RTS
;============================================================================ ; Load blocks for the given screen from the level data. ; ; This will load the compressed block data for a given ; screen. Data is stored in by using a control byte and ; optional data bytes. ; ; Control bytes dictate where the next block comes from. ; The following are supported: ; ; 0x00: Copy the block from one position to the left ; (or last block on previous row, if at column 0). ; ; 0x01: Copy from the block in the row directly above. ; ; 0x02: Copy from the block directly up and to the left ; (or last block 2 rows up, if at column 0) ; ; 0x03: Read the next value as the block. ; ; INPUTS: ; A: ; The index of the screen to load. ; ; Area_ScreenBlocksOffset: ; The offset into the blocks data for the current ; screen. ; ; OUTPUTS: ; ScreenBuffer: ; Updated with loaded block data. ; ; LoadCompressedScreenData_ByteOffset: ; LoadCompressedScreenData_BitOffset: ; Temp_LoadedBlockValue: ; Temp_LoadedBlocksCount: ; Temp_08: ; Temp_09: ; Clobbered. ; ; CALLS: ; Area_LoadNextCompressedScreenBit ; ScreenBuffer_Clear ; ; XREFS: ; Area_LoadScrollDataRight ;============================================================================
[d1f6]Area_LoadBlocks:; [$d1f6] [d1f6]CMP #$ff; Check if the screen index is set. [d1f8]BEQ ScreenBuffer_Clear; If not, then clear the screen buffer.
; ; We'll be loading blocks for this screen. Calculate ; the address we'll be loading from. ;
[d1fa]ASL A; Multiply screen index by 2 [d1fb]TAY; Y = New screen index offset value [d1fc]LDA (Area_ScreenBlocksOffset),Y; Set the lower block data address for this screen index. [d1fe]STA Temp_08; Store it. [d200]INY; Y++ [d201]LDA (Area_ScreenBlocksOffset),Y; Set the upper block data address for this screen index. [d203]CLC [d204]ADC #$80; Add 0x80 to the upper byte. [d206]STA Temp_09; Store this as our total CHR page count.
; ; Set our initial state for compressed block loading. ;
[d208]LDA #$00; A = 0 [d20a]STA LoadCompressedScreenData_ByteOffset; Set byte offset to 0. [d20c]STA Temp_LoadedBlocksCount; Set count to 0. [d20e]STA LoadCompressedScreenData_BitOffset; Set bit offset to 0.
; ; Load the control bits for the block. This will dictate ; how loading will proceed. ;
[d210]@_nextBlock:; [$d210] [d210]LDA #$00 [d212]STA Temp_LoadedBlockValue; Begin loading this block.
; ; Shift our block value left by 2 and add load the next two ; compressed bits into those spots. ;
[d214]JSR Area_LoadNextCompressedScreenBit; Load next bit from compressed data. [d217]ROL Temp_LoadedBlockValue; Shift our block to the left 1 and add the bit. [d219]JSR Area_LoadNextCompressedScreenBit; Load next bit from compressed data. [d21c]ROL Temp_LoadedBlockValue; Shift our block to the left 1 and add the bit.
; ; Check what bits we just loaded. ;
[d21e]LDA Temp_LoadedBlockValue; Load the current block value. [d220]AND #$03; Check the 2 least-significant bits we loaded. [d222]TAX; X = Our loaded bits value [d223]CPX #$03; Is the value 3? [d225]BEQ @_read8BitBlock; If so, start a new block.
; ; Control 0x00, 0x01, or 0x02: ; ; Load the block value stored in the screen buffer at ; the current offset + bitValue-specific offset. ;
[d227]LDA Temp_LoadedBlocksCount; Else, load our blocks count. [d229]CLC [d22a]ADC BLOCK_DATA_OFFSETS_FOR_BIT_VALUES,X; Add that to the value for this bit in the lookup table. [d22d]TAX; Set as X [d22e]LDA ScreenBuffer,X; Load the block value from the screen buffer at the offset for this bit value. [d231]JMP @_storeBlock; Proceed to store the block.
; ; Control 0x03: Read an 8-bit block. ;
[d234]@_read8BitBlock:; [$d234] [d234]LDX #$08; X = 8 (total bits) [d236]LDA #$00; A = 0 (new block value) [d238]STA Temp_LoadedBlockValue; Store that value.
; ; Load the block data. We'll load 8 bits worth. ; ; This is essentially going to be the same as the byte ; value, except the bits loaded may span multiple bytes. ;
[d23a]@_load8BitsOfBlockData:; [$d23a] [d23a]JSR Area_LoadNextCompressedScreenBit; Load next bit from compressed data. [d23d]ROL Temp_LoadedBlockValue; Shift our block to the left 1 and add the bit. [d23f]DEX; X-- (total bits) [d240]BNE @_load8BitsOfBlockData; If we're not at 0, loop. [d242]LDA Temp_LoadedBlockValue; Else, load the resulting block value.
; ; Store the block value in the screen buffer at the ; current offset. ;
[d244]@_storeBlock:; [$d244] [d244]LDX Temp_LoadedBlocksCount; X = loaded block count, as our new offset [d246]STA ScreenBuffer,X; Store the byte in the screen buffer at that offset. [d249]INC Temp_LoadedBlocksCount; Increment our block count. [d24b]BNE @_nextBlock; If we haven't loaded 256 blocks, loop.
; ; We're done loading the blocks. We now need to ; fill in the last row of 16 blocks with zeroes. ;
[d24d]LDX #$f0; X = 0xF0 (offset into the screen buffer to fill) [d24f]LDA #$00; A = 0 (value to store)
; ; Fill the last line. ;
[d251]@_fillLastLine:; [$d251] [d251]STA ScreenBuffer,X; Store in the screen buffer. [d254]INX; X++ [d255]BNE @_fillLastLine; If we haven't hit 16 blocks (value 256 and wrapped around), then loop. [d257]RTS
;============================================================================ ; Load the next bit from compressed data. ; ; This is used when decompressing screen data. It will ; process a bit at a time and make that result available ; to the caller. ; ; INPUTS: ; Temp_07+1: ; The address of the table of bytes to load from, ; based on the byte offset. ; ; LoadCompressedScreenData_ByteOffset: ; The current byte offset. ; ; LoadCompressedScreenData_BitOffset: ; The current bit offset within the byte offset. ; ; OUTPUTS: ; LoadCompressedScreenData_ByteOffset: ; The new byte offset. ; ; LoadCompressedScreenData_BitOffset: ; The new bit offset. ; ; LoadCompressedScreenData_CurByte: ; The new loaded byte. ; ; XREFS: ; Area_LoadBlocks ;============================================================================
[d258]Area_LoadNextCompressedScreenBit:; [$d258] [d258]LDA LoadCompressedScreenData_BitOffset; Load the current bit. [d25a]BNE @_update; Is this 0? If so, branch.
; ; We're at the first bit. We need to load the byte we'll be ; working with from RAM as an index into Temp_07+1. ;
[d25c]LDY LoadCompressedScreenData_ByteOffset; Get the offset of the byte we'll be processing. [d25e]LDA (Temp_08),Y; Load the next byte. [d260]STA LoadCompressedScreenData_CurByte
; ; Shift the current byte left by 1 and increment the bit ; offset by 1, for comparison. ;
[d262]@_update:; [$d262] [d262]ASL LoadCompressedScreenData_CurByte; Shift the current byte we're processing. [d264]PHP; Save all flags. [d265]INC LoadCompressedScreenData_BitOffset; Increment the next bit in the byte.
; ; See if the lower 3 bits are cleared. If so, we need to ; advance the byte offset and reset the bit offset. ;
[d267]LDA LoadCompressedScreenData_BitOffset; Check if we've hit the last bit in the byte. [d269]AND #$07 [d26b]BNE @_return; If we have any data in bits 1-3... [d26d]STA LoadCompressedScreenData_BitOffset; Reset the current bit to 0. [d26f]INC LoadCompressedScreenData_ByteOffset; Increase the byte offset. [d271]@_return:; [$d271] [d271]PLP; Restore all flags. [d272]RTS
; ; XREFS: ; Area_LoadBlocks ;
[d273]BLOCK_DATA_OFFSETS_FOR_BIT_VALUES:; [$d273] [d273].byte $ff; [0]: [d274].byte $f0; [1]: [d275].byte $ef; [2]:
;============================================================================ ; Load block properties from Bank 3 into RAM. ; ; This will loop through all block properties for the ; current area, loading it into a table in RAM. ; ; Each pair of bytes in the block properties come ; together as the two nibbles for a new byte. The ; first byte represents the lower nibble, and the ; second byte's value is shifted into the upper nibble. ; ; INPUTS: ; CurrentROMBank: ; The current ROM bank to save and restore. ; ; CurrentArea_BlockPropertiesAddr: ; The address of the current area's block ; properties data. ; ; OUTPUTS: ; BlockProperties: ; The block properties in RAM to update. ; ; CALLS: ; MMC1_UpdateROMBank ; ; XREFS: ; Screen_StopScrollAndLoadBlockProperties ;============================================================================
[d276]Area_LoadBlockProperties:; [$d276]
; ; Switch to bank 3, where the level data is stored. ;
[d276]LDA a:CurrentROMBank [d279]PHA [d27a]LDX #$03 [d27c]JSR MMC1_UpdateROMBank
; ; Prepare for the read loop. ;
[d27f]LDY #$00; Y = 0 [d281]LDX #$00; X = 0
; ; Load the lower nibble from the block properties. ;
[d283]@_nextBlockProperty:; [$d283] [d283]LDA (CurrentArea_BlockPropertiesAddr),Y; A = block property in the current area at offset Y. [d285]AND #$0f; Retain the lower nibble. [d287]STA Temp_00; Store that for later lookup.
; ; Load the next block property as the upper nibble. ;
[d289]INY; Increment the index in the block properties table.; [d28a]LDA (CurrentArea_BlockPropertiesAddr),Y; Load it into A. [d28c]ASL A; Move the lower nibble to the upper nibble. [d28d]ASL A [d28e]ASL A [d28f]ASL A
; ; Combine the two nibbles for the new value and store it in ; BlockProperties. ;
[d290]ORA Temp_00; OR it to the lower nibble we saved. [d292]STA BlockProperties,X; Store it.
; ; Keep going while the index is positive. ;
[d295]INY; Y = Y + 1 [d296]INX; X = X + 1 [d297]BPL @_nextBlockProperty; If we haven't overflowed, loop.
; ; We're done. Switch back to our previous bank. ;
[d299]PLA; Pull our saved bank. [d29a]TAX; Set to X and... [d29b]JSR MMC1_UpdateROMBank; Switch banks. [d29e]RTS
;============================================================================ ; TODO: Document Screen_StopScrollAndLoadBlockProperties ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Screen_HandleScrollDown ; Screen_HandleScrollLeft ; Screen_HandleScrollRight ; Screen_HandleScrollUp ;============================================================================
[d29f]Screen_StopScrollAndLoadBlockProperties:; [$d29f] [d29f]LDA #$ff [d2a1]STA Screen_ScrollDirection [d2a3]JMP Area_LoadBlockProperties
;============================================================================ ; TODO: Document Screen_HandleScrollUp ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Screen_HandleScroll ;============================================================================
[d2a6]Screen_HandleScrollUp:; [$d2a6] [d2a6]LDA PPU_ScrollY [d2a8]BNE @LAB_PRG15_MIRROR__d2ac [d2aa]LDA #$d0 [d2ac]@LAB_PRG15_MIRROR__d2ac:; [$d2ac] [d2ac]SEC [d2ad]SBC #$01 [d2af]STA PPU_ScrollY [d2b1]BNE @LAB_PRG15_MIRROR__d2b8 [d2b3]DEC PPU_ScrollScreenVert [d2b5]JSR Screen_StopScrollAndLoadBlockProperties [d2b8]@LAB_PRG15_MIRROR__d2b8:; [$d2b8] [d2b8]LDA PPU_ScrollY [d2ba]STA Screen_ScrollVertLoadCounter [d2bc]LDA a:CurrentROMBank [d2bf]PHA [d2c0]LDX #$03 [d2c2]JSR MMC1_UpdateROMBank [d2c5]JSR Screen_LoadDataUp [d2c8]PLA [d2c9]TAX [d2ca]JSR MMC1_UpdateROMBank [d2cd]RTS
;============================================================================ ; TODO: Document Screen_UpdateForScroll ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Game_UpdateForScroll ; Screen_UpdateForScroll ;============================================================================
[d2ce]Screen_UpdateForScroll:; [$d2ce] [d2ce]JSR Game_UpdatePlayerOnScroll; Update the player position during scroll. [d2d1]JSR Screen_HandleScroll; Update the screen state/tiles on scroll. [d2d4]JSR Screen_RunWriteScrollDataHandler; Write data to the PPU based on the scroll direction. [d2d7]LDA Screen_ScrollDirection; Load the scroll direction. [d2d9]CMP #$02; Is it left or right? [d2db]BCS @_isLeftOrRight
; ; The screen is scrolling up, down, or not at all. ;
[d2dd]LDA PPU_ScrollX; Load the scroll X delta. [d2df]BNE Screen_UpdateForScroll; If it's not 0, loop. [d2e1]RTS
; ; The screen is scrolling left or right. ;
[d2e2]@_isLeftOrRight:; [$d2e2] [d2e2]LDA PPU_ScrollY; Load the scroll Y delta. [d2e4]BNE Screen_UpdateForScroll; If it's not 0, loop.
; ; XREFS: ; Screen_HandleScroll ;
[d2e6]RETURN_D2E6:; [$d2e6] [d2e6]RTS; Else, return.
;============================================================================ ; TODO: Document Screen_HandleScroll ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Area_ScrollScreenRight ; Game_MainLoop ; Screen_UpdateForScroll ;============================================================================
[d2e7]Screen_HandleScroll:; [$d2e7] [d2e7]LDX Screen_ScrollDirection [d2e9]BEQ Screen_HandleScrollLeft [d2eb]DEX [d2ec]BEQ Screen_HandleScrollRight [d2ee]DEX [d2ef]BEQ Screen_HandleScrollUp [d2f1]DEX [d2f2]BNE RETURN_D2E6
; ; v-- Fall through --v ;
;============================================================================ ; TODO: Document Screen_HandleScrollDown ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ;============================================================================
[d2f4]Screen_HandleScrollDown:; [$d2f4]
; ; The screen is scrolling down. ;
[d2f4]LDA PPU_ScrollY [d2f6]CLC [d2f7]ADC #$01 [d2f9]STA PPU_ScrollY [d2fb]CMP #$d0 [d2fd]BCC @LAB_PRG15_MIRROR__d308 [d2ff]LDA #$00 [d301]STA PPU_ScrollY [d303]INC PPU_ScrollScreenVert [d305]JSR Screen_StopScrollAndLoadBlockProperties [d308]@LAB_PRG15_MIRROR__d308:; [$d308] [d308]LDA PPU_ScrollY [d30a]STA Screen_ScrollVertLoadCounter [d30c]LDA a:CurrentROMBank [d30f]PHA [d310]LDX #$03 [d312]JSR MMC1_UpdateROMBank [d315]JSR Screen_LoadBlocksDown [d318]PLA [d319]TAX [d31a]JSR MMC1_UpdateROMBank [d31d]RTS
;============================================================================ ; TODO: Document Screen_HandleScrollRight ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Screen_HandleScroll ;============================================================================
[d31e]Screen_HandleScrollRight:; [$d31e]
; ; The screen is scrolling right. ;
[d31e]LDA PPU_ScrollX [d320]CLC [d321]ADC #$01 [d323]STA PPU_ScrollX [d325]PHP [d326]LDA PPU_ScrollScreenHoriz [d328]ADC #$00 [d32a]STA PPU_ScrollScreenHoriz [d32c]STA PPU_ScrollScreen [d32e]PLP [d32f]BCC @LAB_PRG15_MIRROR__d334 [d331]JSR Screen_StopScrollAndLoadBlockProperties [d334]@LAB_PRG15_MIRROR__d334:; [$d334] [d334]LDA PPU_ScrollX [d336]STA Screen_ScrollHorizLoadCounter [d338]LDA a:CurrentROMBank [d33b]PHA [d33c]LDX #$03 [d33e]JSR MMC1_UpdateROMBank [d341]JSR Screen_LoadDataRight [d344]PLA [d345]TAX [d346]JSR MMC1_UpdateROMBank [d349]RTS
;============================================================================ ; TODO: Document Screen_HandleScrollLeft ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Screen_HandleScroll ;============================================================================
[d34a]Screen_HandleScrollLeft:; [$d34a]
; ; The screen is scrolling left. ;
[d34a]LDA Screen_ScrollHorizLoadCounter [d34c]CMP #$fc [d34e]BCS @LAB_PRG15_MIRROR__d364 [d350]LDA PPU_ScrollX [d352]SEC [d353]SBC #$01 [d355]STA PPU_ScrollX [d357]PHP [d358]LDA PPU_ScrollScreen [d35a]SBC #$00 [d35c]STA PPU_ScrollScreen [d35e]PLP [d35f]BNE @LAB_PRG15_MIRROR__d364 [d361]JSR Screen_StopScrollAndLoadBlockProperties [d364]@LAB_PRG15_MIRROR__d364:; [$d364] [d364]LDA Screen_ScrollHorizLoadCounter [d366]SEC [d367]SBC #$01 [d369]STA Screen_ScrollHorizLoadCounter [d36b]LDA PPU_ScrollScreenHoriz [d36d]SBC #$00 [d36f]STA PPU_ScrollScreenHoriz [d371]CMP PPU_ScrollScreen [d373]BNE @_finish [d375]LDA PPU_ScrollX [d377]CMP #$04 [d379]BCS @_finish [d37b]RTS [d37c]@_finish:; [$d37c] [d37c]LDA a:CurrentROMBank [d37f]PHA [d380]LDX #$03 [d382]JSR MMC1_UpdateROMBank [d385]JSR Screen_LoadDataLeft [d388]PLA [d389]TAX [d38a]JSR MMC1_UpdateROMBank [d38d]RTS
;============================================================================ ; Load block attributes when scrolling vertically. ; ; This sets the loading mode to load attributes, and then ; load a round of blocks vertically. ; ; INPUTS: ; X: ; 0 = Load blocks to the top. ; 1 = Load blocks to the bottom. ; ; OUTPUTS: ; Screen_ScrollLoadMode: ; The mode set to loading attributes. ; ; CALLS: ; Screen_LoadBlockDataVert ; ; XREFS: ; Screen_LoadBlocksDown ; Screen_LoadDataUp ;============================================================================
[d38e]Screen_LoadAttrsVert:; [$d38e] [d38e]INC Screen_ScrollLoadMode; Set load mode to 1 (load attributes) [d390]JMP Screen_LoadBlockDataVert; Load the data.
;============================================================================ ; Load screen data for the screen above. ; ; Every 8 counts (7, 15, 23, ...), this will load ; attribute data. ; ; Every 8 counts (3, 11, 19, ...), this will load ; tile data. ; ; INPUTS: ; Screen_ScrollVertLoadCounter: ; The load counter for blocks. ; ; OUTPUTS: ; None. ; ; CALLS: ; Screen_LoadAttrsVert ; Screen_LoadBlockDataVert ; ; XREFS: ; Screen_HandleScrollUp ;============================================================================
[d393]Screen_LoadDataUp:; [$d393] [d393]LDX #$00; 0 = Load Tiles mode [d395]STX Screen_ScrollLoadMode; Set as the default mode. [d397]LDA Screen_ScrollVertLoadCounter; A = Vertical blocks counter [d399]AND #$0f; Keep the lower nibble. [d39b]CMP #$07; Is it 7 (every 8 blocks, starting at 7)? [d39d]BEQ Screen_LoadAttrsVert; If so, load attributes. [d39f]AND #$07; Else, keep the lower 3 bits. [d3a1]CMP #$03; Is it 3 (every 8 blocks, starting at 3)? [d3a3]BEQ Screen_LoadBlockDataVert; If so, load tiles. [d3a5]RTS; Else, return.
;============================================================================ ; Load screen data for the screen below. ; ; Every 16 counts (8, 24, 40, ...), this will load ; attribute data. ; ; Every 8 counts (4, 12, 20, ...), this will load ; tile data. ; ; INPUTS: ; Screen_ScrollVertLoadCounter: ; The load counter for blocks. ; ; OUTPUTS: ; None. ; ; CALLS: ; Screen_LoadAttrsVert ; Screen_LoadBlockDataVert ; ; XREFS: ; Screen_HandleScrollDown ;============================================================================
[d3a6]Screen_LoadBlocksDown:; [$d3a6] [d3a6]LDX #$00; 0 = Load Tiles mode [d3a8]STX Screen_ScrollLoadMode; Set as the default mode. [d3aa]INX; X++ (reuse as "is down" flag, set to 1). [d3ab]LDA Screen_ScrollVertLoadCounter; A = Vertical blocks counter [d3ad]AND #$0f; Keep the lower nibble. [d3af]CMP #$08; Is it 8 (every 16 blocks, starting at 8)? [d3b1]BEQ Screen_LoadAttrsVert; If so, load attributes. [d3b3]AND #$07; Else, keep the lower 3 bits. [d3b5]CMP #$04; Is it 4 (every 8 blocks, starting at 4)? [d3b7]BEQ Screen_LoadBlockDataVert; If so, load tiles. [d3b9]RTS; Else, return.
;============================================================================ ; TODO ; ; direction: ; 1 = up ; 0 = down ; ; XREFS: ; Screen_LoadAttrsVert ; Screen_LoadBlocksDown ; Screen_LoadDataUp ;============================================================================
[d3ba]Screen_LoadBlockDataVert:; [$d3ba] [d3ba]LDA Screen_ScrollVertLoadCounter [d3bc]CLC [d3bd]ADC $d4cb,X [d3c0]STA Temp_00 [d3c2]LDA PPU_ScrollScreenVert [d3c4]ADC #$00 [d3c6]STA Unused_Blocks_0049 [d3c8]LDA Area_CurrentScreen [d3ca]STA MaybeUnused_006d [d3cc]LDA Temp_00 [d3ce]AND #$f0 [d3d0]STA Temp_Blocks_0048 [d3d2]LDA #$00 [d3d4]CLC [d3d5]ADC Temp_Blocks_0048 [d3d7]STA Temp_08 [d3d9]LDA #$06 [d3db]ADC #$00 [d3dd]STA Temp_09 [d3df]LDA Screen_ScrollLoadMode [d3e1]BEQ @LAB_PRG15_MIRROR__d3e6 [d3e3]JMP @LAB_PRG15_MIRROR__d445 [d3e6]@LAB_PRG15_MIRROR__d3e6:; [$d3e6] [d3e6]LDY #$00 [d3e8]LDX #$00 [d3ea]@LAB_PRG15_MIRROR__d3ea:; [$d3ea] [d3ea]STY Temp_06 [d3ec]LDA (Temp_08),Y [d3ee]TAY [d3ef]LDA (CurrentArea_BlockData1StartAddr),Y [d3f1]STA CurrentArea_BlockData1CurAddr [d3f3]LDA (CurrentArea_BlockData2StartAddr),Y [d3f5]STA CurrentArea_BlockData2CurAddr [d3f7]LDA (CurrentArea_BlockData3StartAddr),Y [d3f9]STA CurrentArea_BlockData3CurAddr [d3fb]LDA (CurrentArea_BlockData4StartAddr),Y [d3fd]STA CurrentArea_BlockData4CurAddr [d3ff]LDA Screen_ScrollVertLoadCounter [d401]AND #$08 [d403]LSR A [d404]LSR A [d405]TAY [d406]LDA CurrentArea_BlockData1CurAddr,Y [d409]STA DataArray,X [d40c]LDA CurrentArea_BlockData2CurAddr,Y [d40f]STA DataArray_1_,X [d412]INX [d413]INX [d414]LDY Temp_06 [d416]INY [d417]CPY #$10 [d419]BCC @LAB_PRG15_MIRROR__d3ea [d41b]LDA #$00 [d41d]STA Screen_ScrollVertBlocks_PPUTileMapAddr_U [d41f]LDA Screen_ScrollVertLoadCounter [d421]AND #$f8 [d423]ASL A [d424]ROL Screen_ScrollVertBlocks_PPUTileMapAddr_U [d426]ASL A [d427]ROL Screen_ScrollVertBlocks_PPUTileMapAddr_U [d429]CLC [d42a]ADC #$80 [d42c]STA Screen_ScrollVertBlocks_PPUTileMapAddr_L [d42e]LDA Screen_ScrollVertBlocks_PPUTileMapAddr_U [d430]ADC #$00 [d432]STA Screen_ScrollVertBlocks_PPUTileMapAddr_U [d434]LDA PPU_ScrollScreenHoriz [d436]AND #$01 [d438]ASL A [d439]ASL A [d43a]ORA #$20 [d43c]ORA Screen_ScrollVertBlocks_PPUTileMapAddr_U [d43e]STA Screen_ScrollVertBlocks_PPUTileMapAddr_U [d440]LDA #$01 [d442]STA Screen_NeighborBlocksLoadedByDirection [d444]RTS [d445]@LAB_PRG15_MIRROR__d445:; [$d445] [d445]LDA Screen_ScrollVertLoadCounter [d447]AND #$16 [d449]LSR A [d44a]LSR A [d44b]LSR A [d44c]STA Temp_01 [d44e]LDY #$f8 [d450]LDA #$00 [d452]@LAB_PRG15_MIRROR__d452:; [$d452] [d452]STA $0192,Y [d455]INY [d456]BNE @LAB_PRG15_MIRROR__d452 [d458]@LAB_PRG15_MIRROR__d458:; [$d458] [d458]STY Temp_06 [d45a]LDA (Temp_08),Y [d45c]TAY [d45d]LDA (CurrentArea_BlockAttributesAddr),Y [d45f]STA Temp_00 [d461]LDA Temp_06 [d463]AND #$01 [d465]ORA Temp_01 [d467]TAY [d468]LDA Temp_00 [d46a]AND BLOCK_ATTRS_BITMASKS,Y [d46d]PHA [d46e]LDA Temp_06 [d470]LSR A [d471]TAY [d472]PLA [d473]ORA Screen_ScrollVertPPUAttrData,Y [d476]STA Screen_ScrollVertPPUAttrData,Y [d479]LDY Temp_06 [d47b]INY [d47c]CPY #$10 [d47e]BCC @LAB_PRG15_MIRROR__d458 [d480]LDX #$f0 [d482]LDA Screen_ScrollVertLoadCounter [d484]AND #$10 [d486]BEQ @LAB_PRG15_MIRROR__d48a [d488]LDX #$0f [d48a]@LAB_PRG15_MIRROR__d48a:; [$d48a] [d48a]STX Temp_06 [d48c]LDA Screen_ScrollVertLoadCounter [d48e]AND #$e0 [d490]LSR A [d491]LSR A [d492]STA Temp_00 [d494]CLC [d495]ADC #$08 [d497]TAY [d498]CLC [d499]ADC #$c0 [d49b]STA Screen_ScrollVertBlocks_PPUAttrAddr_L [d49d]LDA PPU_ScrollScreenHoriz [d49f]AND #$01 [d4a1]TAX [d4a2]ASL A [d4a3]ASL A [d4a4]ORA #$23 [d4a6]STA Screen_ScrollVertBlocks_PPUAttrAddr_U [d4a8]LDA SET_BLOCKS_TILEMAP_OFFSETS_L,X [d4ab]STA Temp_08 [d4ad]LDA SET_BLOCKS_TILEMAP_OFFSETS_U,X [d4b0]STA Temp_09 [d4b2]LDX #$00 [d4b4]@LAB_PRG15_MIRROR__d4b4:; [$d4b4] [d4b4]LDA (Temp_08),Y [d4b6]AND Temp_06 [d4b8]ORA Screen_ScrollVertPPUAttrData,X [d4bb]STA (Temp_08),Y [d4bd]STA Screen_ScrollVertPPUAttrData,X [d4c0]INY [d4c1]INX [d4c2]CPX #$08 [d4c4]BCC @LAB_PRG15_MIRROR__d4b4 [d4c6]LDA #$01 [d4c8]STA Screen_MaybeUnused_0075 [d4ca]RTS [d4cb].byte $00; [0]:
; ; XREFS: ; Screen_LoadBlockDataVert ;
[d4cc]BYTE_ARRAY_PRG15_MIRROR__d4cb_1_:; [$d4cc] [d4cc].byte $00; [1]: [d4cd].byte $00,$08; [$d4cd] byte
; ; XREFS: ; Area_SetBlocks_SetAttributes ; Screen_LoadBlockDataVert ; Screen_LoadBlocksHoriz ;
[d4cf]SET_BLOCKS_TILEMAP_OFFSETS_L:; [$d4cf] [d4cf].byte $42; [0]: [d4d0].byte $42; [1]:
; ; XREFS: ; Area_SetBlocks_SetAttributes ; Screen_LoadBlockDataVert ; Screen_LoadBlocksHoriz ;
[d4d1]SET_BLOCKS_TILEMAP_OFFSETS_U:; [$d4d1] [d4d1].byte $02; [0]: [d4d2].byte $02; [1]:
;============================================================================ ; Block attribute masks during screen scrolling. ; ; This is used for both vertical and horizontal scrolling. ; ; XREFS: ; Screen_LoadBlockDataVert ; Screen_LoadBlocksHoriz ;============================================================================
; ; XREFS: ; Screen_LoadBlockDataVert ; Screen_LoadBlocksHoriz ;
[d4d3]BLOCK_ATTRS_BITMASKS:; [$d4d3] [d4d3].byte $03; [0]: Mask bits 0 and 1 [d4d4].byte $0c; [1]: Mask bits 2 and 3 [d4d5].byte $30; [2]: Mask bits 4 and 5 [d4d6].byte $c0; [3]: Mask bits 6 and 7
;============================================================================ ; Load block attributes when scrolling horizontally. ; ; This sets the loading mode to load attributes, and then ; load a round of blocks horizontally. ; ; INPUTS: ; X: ; 0 = Load blocks to the left. ; 1 = Load blocks to the right. ; ; OUTPUTS: ; Screen_ScrollLoadMode: ; The mode set to loading attributes. ; ; CALLS: ; Screen_LoadBlocksHoriz ; ; XREFS: ; Screen_LoadDataLeft ; Screen_LoadDataRight ;============================================================================
[d4d7]Screen_LoadAttrsHoriz:; [$d4d7] [d4d7]INC Screen_ScrollLoadMode; Set load mode to 1 (load attributes). [d4d9]JMP Screen_LoadBlocksHoriz; Load the data.
;============================================================================ ; Load screen data for the screen to the right. ; ; Every 4 counts (2, 6, 10, ...), this will load ; attribute data. ; ; Every 4 counts (1, 5, 9, ...), this will load ; tile data. ; ; INPUTS: ; Screen_ScrollHorizLoadCounter: ; The load counter for blocks. ; ; OUTPUTS: ; None. ; ; CALLS: ; Screen_LoadAttrsHoriz ; Screen_LoadBlocksHoriz ; ; XREFS: ; Screen_HandleScrollRight ;============================================================================
[d4dc]Screen_LoadDataRight:; [$d4dc] [d4dc]LDX #$00; 0 = Load Tiles mode [d4de]STX Screen_ScrollLoadMode; Set as the default mode. [d4e0]INX; X++ (reuse as "is right" flag, set to 1). [d4e1]LDA Screen_ScrollHorizLoadCounter; A = Horizontal blocks counter [d4e3]AND #$0f; Keep the lower nibble. [d4e5]CMP #$02; Is it 2 (every 4 blocks, starting at 2)? [d4e7]BEQ Screen_LoadAttrsHoriz; If so, load attributes. [d4e9]AND #$07; Else, keep the lower 3 bits. [d4eb]CMP #$01; Is it 1 (every 4 blocks, starting at 1)? [d4ed]BEQ Screen_LoadBlocksHoriz; If so, load tiles. [d4ef]RTS; Else, return.
;============================================================================ ; Load screen data for the screen to the left. ; ; Every 16 counts (values 15, 31, 47, ...), this will load ; attribute data. ; ; Every 8 counts (values 6, 14, 22, ...), this will load ; tile data. ; ; INPUTS: ; Screen_ScrollHorizLoadCounter: ; The load counter for blocks. ; ; OUTPUTS: ; None. ; ; CALLS: ; Screen_LoadAttrsHoriz ; Screen_LoadBlocksHoriz ; ; XREFS: ; Screen_HandleScrollLeft ;============================================================================
[d4f0]Screen_LoadDataLeft:; [$d4f0] [d4f0]LDX #$00; 0 = Load Tiles mode [d4f2]STX Screen_ScrollLoadMode; Set as the default mode. [d4f4]LDA Screen_ScrollHorizLoadCounter; A = Horizontal blocks counter [d4f6]AND #$0f; Keep the lower nibble. [d4f8]CMP #$0f; Is it 0xF (every 16, starting at 15)? [d4fa]BEQ Screen_LoadAttrsHoriz; If so, load attributes. [d4fc]AND #$07; Else, keep the lower 3 bits. [d4fe]CMP #$06; Is it 6 (every 8 blocks, starting at 6)? [d500]BEQ Screen_LoadBlocksHoriz; If so, load tiles. [d502]RTS; Else, return.
;============================================================================ ; TODO: Document Screen_LoadBlocksHoriz ; ; INPUTS: ; X ; ; OUTPUTS: ; TODO ; ; XREFS: ; Screen_LoadAttrsHoriz ; Screen_LoadDataLeft ; Screen_LoadDataRight ;============================================================================
[d503]Screen_LoadBlocksHoriz:; [$d503] [d503]LDA Screen_ScrollHorizLoadCounter [d505]CLC [d506]ADC SCROLL_HORIZ_END_POS,X [d509]STA Temp_00 [d50b]LDA PPU_ScrollScreenHoriz [d50d]ADC BYTE_ARRAY_PRG15_MIRROR__d61b,X [d510]STA Screen_Maybe_ScrollHorizDirection [d512]LDA Area_CurrentScreen [d514]STA MaybeUnused_006d [d516]LDA Temp_00 [d518]LSR A [d519]LSR A [d51a]LSR A [d51b]LSR A [d51c]STA Screen_ScrollHorizLoadOffset [d51e]LDA #$00 [d520]STA Temp_08 [d522]LDA #$06 [d524]STA Temp_09 [d526]LDA Screen_ScrollLoadMode [d528]BNE @_loadPPUData
; ; Load 30 blocks to render in the next draw phase. ;
[d52a]LDX #$00; X = 0 (counter)
; ; Determine the offset for the blocks to load. ;
[d52c]@_loadBlocksLoop:; [$d52c] [d52c]LDY Screen_ScrollHorizLoadOffset [d52e]LDA (Temp_08),Y
; ; Load the blocks information. ;
[d530]TAY [d531]LDA (CurrentArea_BlockData1StartAddr),Y [d533]STA CurrentArea_BlockData1CurAddr [d535]LDA (CurrentArea_BlockData3StartAddr),Y [d537]STA CurrentArea_BlockData2CurAddr [d539]LDA (CurrentArea_BlockData2StartAddr),Y [d53b]STA CurrentArea_BlockData3CurAddr [d53d]LDA (CurrentArea_BlockData4StartAddr),Y [d53f]STA CurrentArea_BlockData4CurAddr
; ; Convert the block counter to a tile width. ;
[d541]LDA Screen_ScrollHorizLoadCounter [d543]AND #$08 [d545]LSR A [d546]LSR A [d547]TAY [d548]LDA CurrentArea_BlockData1CurAddr,Y [d54b]STA Temp_0200,X [d54e]LDA CurrentArea_BlockData2CurAddr,Y [d551]STA Temp_0201,X [d554]LDA Temp_08 [d556]CLC [d557]ADC #$10 [d559]STA Temp_08 [d55b]LDA Temp_09 [d55d]ADC #$00 [d55f]STA Temp_09 [d561]INX [d562]INX [d563]CPX #$1e [d565]BCC @_loadBlocksLoop [d567]LDA #$01 [d569]STA Screen_ScrollHorizBlocksLoaded [d56b]TYA [d56c]LSR A [d56d]PHA [d56e]LDA Screen_ScrollHorizLoadOffset [d570]ASL A [d571]STA Screen_ScrollHorizLoadOffset [d573]PLA [d574]CLC [d575]ADC Screen_ScrollHorizLoadOffset [d577]CLC [d578]ADC #$80 [d57a]STA Screen_ScrollHorizBlocks_PPUTileMapAddr_L [d57c]LDA Screen_Maybe_ScrollHorizDirection [d57e]AND #$01 [d580]ASL A [d581]ASL A [d582]ORA #$20 [d584]STA Screen_ScrollHorizBlocks_PPUTileMapAddr_U [d586]RTS
; ; Clear 8 bytes of memory. ; ; This isn't used for any data management purposes. It ; may be to delay some aspect of screen scrolling. ;
[d587]@_loadPPUData:; [$d587] [d587]LDY #$f8; Y = 0xF8 (loop counter) [d589]LDA #$00; A = 0 (value to write) [d58b]@_clearLoop:; [$d58b] [d58b]STA Screen_ScrollHoriz_ZeroData,Y; Write a 0 to this empty block. [d58e]INY; Y++ [d58f]BNE @_clearLoop; If != 0, loop.
; ; Begin loading PPU attribute data and scheduling to draw. ;
[d591]LDX #$00; X = 0 (loop counter)
; ; Start by storing the data load offset and the block ; attribute value from area data, storing in temp. ; ; The screen buffer data will contain the offset into the ; block attributes data. ;
[d593]@_loadAttrDataLoop:; [$d593] [d593]LDY Screen_ScrollHorizLoadOffset; Y = Attr data load offset [d595]LDA (Temp_08),Y; A = Screen buffer data at offset (block attribute offset) [d597]TAY; Y = A [d598]LDA (CurrentArea_BlockAttributesAddr),Y; A = block attribute at resulting screen buffer data offset value [d59a]STA Temp_00; Store it temporarily.
; ; Generate an index 0-3 into a lookup table. This will be: ; ; 0 = Bits 0-1 -- Even loop counter, even load offset ; 1 = Bits 2-3 -- Even loop counter, odd load offset ; 2 = Bits 4-5 -- Odd loop counter, even load offset ; 3 = Bits 6-7 -- Odd loop counter, odd load offset ;
[d59c]LDA Screen_ScrollHorizLoadOffset; A = Attr data load offset [d59e]AND #$01; Keep the least-significant bit (even/odd). [d5a0]STA Temp_01; Store it temporarily. [d5a2]TXA; A = X (loop counter) [d5a3]AND #$01; Keep the loop counter's least-significant bit (even/odd). [d5a5]ASL A; A *= 2 [d5a6]ORA Temp_01; OR with the load offset's even/odd bit. [d5a8]TAY; Y = A (result)
; ; Mask 2 bits of the block attributes based on that index. ;
[d5a9]LDA Temp_00; A = Loaded block attribute [d5ab]AND BLOCK_ATTRS_BITMASKS,Y; AND with the bitmask based on our computed index. [d5ae]PHA; Push the result to the stack.
; ; Generate the destination index into the PPU attribute data. ; ; This will be the loop counter / 2. ;
[d5af]TXA; A = X (loop counter) [d5b0]LSR A; A = A / 2 [d5b1]TAY; Y = A
; ; OR the result with the value already loaded at this loop ; index, and store it back. ;
[d5b2]PLA; Pop the masked block attribute from stack. [d5b3]ORA Screen_ScrollHorizPPUAttrData,Y; OR with the data already set. [d5b6]STA Screen_ScrollHorizPPUAttrData,Y; And store it.
; ; Advance the screen buffer address by 16 bytes. ;
[d5b9]LDA Temp_08; A = Lower byte of Current screen buffer address. [d5bb]CLC [d5bc]ADC #$10; A += 16 [d5be]STA Temp_08; Store as the new lower byte. [d5c0]LDA Temp_09; A = Upper byte of Current screen buffer address. [d5c2]ADC #$00; A += Carry from lower increment [d5c4]STA Temp_09; Store as the new upper byte.
; ; Advance the loop counter, and loop if < 15. ;
[d5c6]INX; X++ (loop counter) [d5c7]CPX #$0f; Is this < 15? [d5c9]BCC @_loadAttrDataLoop; If so, loop.
; ; Generate a bitmask that will be ANDed to screen ; buffer data and OR'd back to scroll PPU attribute ; data futher below. ;
[d5cb]LDX #$cc; Default bitmask to enable bits 2-3, 6-7 (11001100) [d5cd]LDA Screen_ScrollHorizLoadOffset; Load the data load offset. [d5cf]AND #$01; Is it even? [d5d1]BEQ @_updatePPUAttrAddr; If so, jump to skip. [d5d3]LDX #$33; Set bitmask to enable bits 0-1, 4-5 (00110011) [d5d5]@_updatePPUAttrAddr:; [$d5d5] [d5d5]STX Temp_06; Store for later. [d5d7]LDA Screen_ScrollHorizLoadOffset; A = Data load offset. [d5d9]LSR A; A = A / 2 [d5da]CLC [d5db]ADC #$08; A += 8 [d5dd]TAY; Y = A (new screen buffer write offset) [d5de]CLC [d5df]ADC #$c0; A += 0xC0 [d5e1]STA Screen_ScrollHorizBlocks_PPUAttrAddr_L; Set as the lower byte of the PPU attr address. [d5e3]LDA Screen_Maybe_ScrollHorizDirection [d5e5]AND #$01 [d5e7]TAX [d5e8]ASL A [d5e9]ASL A [d5ea]ORA #$23 [d5ec]STA Screen_ScrollHorizBlocks_PPUAttrAddr_U; Set as the upper byte of the PPU attr address. [d5ee]LDA SET_BLOCKS_TILEMAP_OFFSETS_L,X [d5f1]STA Temp_08 [d5f3]LDA SET_BLOCKS_TILEMAP_OFFSETS_U,X [d5f6]STA Temp_09
; ; Update 64 blocks of PPU attribute data. ; ; This will OR the bitmask above to each of the 64 ; bytes of PPU attribute data already computed. ;
[d5f8]LDX #$00; X = 0 (loop counter) [d5fa]@_updatePPUAttrDataLoop:; [$d5fa] [d5fa]LDA (Temp_08),Y; Load the value from the screen buffer write offset at Y. [d5fc]AND Temp_06; AND with the bitmask computed above. [d5fe]ORA Screen_ScrollHorizPPUAttrData,X; OR with the existing PPU attribute data [d601]STA Screen_ScrollHorizPPUAttrData,X; And store it. [d604]STA (Temp_08),Y; Store it back in the screen buffer at write offset Y. [d606]INX; X++ (loop counter)
; ; The next three instructions are effectively unused. ; Legacy code? ;
[d607]TXA; <unused> [d608]AND #$07; <unused> [d60a]TXA; <unused>
; ; Advance the earlier loop counter (which should start at 15) ; by 8 each loop iteration. ;
[d60b]TYA; A = Y (loop counter from initial attribute loading) [d60c]CLC [d60d]ADC #$08; A += 8 [d60f]TAY; Y = A [d610]CPY #$40; Is it < 64? [d612]BCC @_updatePPUAttrDataLoop; If so, loop.
; ; Mark the attribute data as loaded. ;
[d614]LDA #$01 [d616]STA Screen_ScrollHorizAttrsLoaded; Set attribute data loaded. [d618]RTS
; ; XREFS: ; Screen_LoadBlocksHoriz ;
[d619]SCROLL_HORIZ_END_POS:; [$d619] [d619].byte $00; [0]: [d61a].byte $ff; [1]:
; ; XREFS: ; Screen_LoadBlocksHoriz ;
[d61b]BYTE_ARRAY_PRG15_MIRROR__d61b:; [$d61b] [d61b].byte $00; [0]: [d61c].byte $00; [1]:
;============================================================================ ; TODO: Document Screen_RunWriteScrollDataHandler ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Area_ScrollScreenRight ; PPU_HandleOnInterrupt ; Screen_UpdateForScroll ;============================================================================
[d61d]Screen_RunWriteScrollDataHandler:; [$d61d] [d61d]LDA #$03 [d61f]STA Temp_06 [d621]LDX Screen_LoadBlocksStage [d623]@_loop:; [$d623] [d623]LDA Screen_NeighborBlocksLoadedByDirection,X [d625]BNE @LAB_PRG15_MIRROR__d631 [d627]INX [d628]TXA [d629]AND #$03 [d62b]TAX [d62c]DEC Temp_06 [d62e]BPL @_loop [d630]RTS [d631]@LAB_PRG15_MIRROR__d631:; [$d631] [d631]LDA #$00 [d633]STA Screen_NeighborBlocksLoadedByDirection,X [d635]STX Temp_06 [d637]INX [d638]TXA [d639]AND #$03 [d63b]TAX [d63c]STX Screen_LoadBlocksStage [d63e]LDA Temp_06 [d640]AND #$03 [d642]TAX [d643]LDA SCREEN_WRITESCROLL_HANDLERS_U,X [d646]PHA [d647]LDA SCREEN_WRITESCROLL_HANDLERS_L,X [d64a]PHA [d64b]RTS
; ; XREFS: ; Screen_RunWriteScrollDataHandler ;
[d64c]SCREEN_WRITESCROLL_HANDLERS_L:; [$d64c] [d64c].byte <(Screen_WriteScrollVertPPUTileData-1); [0]: [d64d].byte <(Screen_WriteScrollHorizPPUTileData-1); [1]: [d64e].byte <(Screen_WriteScrollVertPPUAttrData-1); [2]: [d64f].byte <(Screen_WriteScrollHorizPPUAttrData-1); [3]:
; ; XREFS: ; Screen_RunWriteScrollDataHandler ;
[d650]SCREEN_WRITESCROLL_HANDLERS_U:; [$d650] [d650].byte >(Screen_WriteScrollVertPPUTileData-1); [0]: [d651].byte >(Screen_WriteScrollHorizPPUTileData-1); [1]: [d652].byte >(Screen_WriteScrollVertPPUAttrData-1); [2]: [d653].byte >(Screen_WriteScrollHorizPPUAttrData-1); [3]:
;============================================================================ ; TODO: Document Screen_WriteScrollVertPPUTileData ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; SCREEN_WRITESCROLL_HANDLERS_L ; [$PRG15_MIRROR::d64c] ; SCREEN_WRITESCROLL_HANDLERS_U ; [$PRG15_MIRROR::d650] ;============================================================================
[d654]Screen_WriteScrollVertPPUTileData:; [$d654] [d654]LDA PPU_ControlFlags [d656]AND #$fb [d658]STA a:PPUCTRL [d65b]LDA Screen_ScrollVertBlocks_PPUTileMapAddr_U [d65d]STA a:PPUADDR [d660]LDA Screen_ScrollVertBlocks_PPUTileMapAddr_L [d662]STA a:PPUADDR [d665]LDX #$00 [d667]@_loop:; [$d667] [d667]LDA DataArray,X [d66a]STA a:PPUDATA [d66d]INX [d66e]CPX #$20 [d670]BCC @_loop [d672]RTS
;============================================================================ ; TODO: Document Screen_WriteScrollHorizPPUTileData ; ; INPUTS: ; None. ; ; OUTPUTS: ; A ; ; XREFS: ; SCREEN_WRITESCROLL_HANDLERS_L ; [$PRG15_MIRROR::d64d] ; SCREEN_WRITESCROLL_HANDLERS_U ; [$PRG15_MIRROR::d651] ;============================================================================
[d673]Screen_WriteScrollHorizPPUTileData:; [$d673] [d673]LDA PPU_ControlFlags [d675]ORA #$04 [d677]STA a:PPUCTRL [d67a]LDA Screen_ScrollHorizBlocks_PPUTileMapAddr_U [d67c]STA a:PPUADDR [d67f]LDA Screen_ScrollHorizBlocks_PPUTileMapAddr_L [d681]STA a:PPUADDR [d684]LDX #$00 [d686]@_loop:; [$d686] [d686]LDA Temp_0200,X [d689]STA a:PPUDATA [d68c]INX [d68d]CPX #$1a [d68f]BCC @_loop [d691]LDA PPU_ControlFlags [d693]AND #$fb [d695]STA a:PPUCTRL [d698]RTS
;============================================================================ ; TODO: Document Screen_WriteScrollVertPPUAttrData ; ; INPUTS: ; None. ; ; OUTPUTS: ; A ; ; XREFS: ; SCREEN_WRITESCROLL_HANDLERS_L ; [$PRG15_MIRROR::d64e] ; SCREEN_WRITESCROLL_HANDLERS_U ; [$PRG15_MIRROR::d652] ;============================================================================
[d699]Screen_WriteScrollVertPPUAttrData:; [$d699] [d699]LDA Screen_ScrollVertBlocks_PPUAttrAddr_U [d69b]STA a:PPUADDR [d69e]LDA Screen_ScrollVertBlocks_PPUAttrAddr_L [d6a0]STA a:PPUADDR [d6a3]LDX #$00 [d6a5]@_loop:; [$d6a5] [d6a5]LDA Screen_ScrollVertPPUAttrData,X [d6a8]STA a:PPUDATA [d6ab]INX [d6ac]CPX #$08 [d6ae]BCC @_loop [d6b0]RTS
;============================================================================ ; TODO: Document Screen_WriteScrollHorizPPUAttrData ; ; INPUTS: ; None. ; ; OUTPUTS: ; A ; ; XREFS: ; SCREEN_WRITESCROLL_HANDLERS_L ; [$PRG15_MIRROR::d64f] ; SCREEN_WRITESCROLL_HANDLERS_U ; [$PRG15_MIRROR::d653] ;============================================================================
[d6b1]Screen_WriteScrollHorizPPUAttrData:; [$d6b1] [d6b1]LDX #$00 [d6b3]@_loop:; [$d6b3] [d6b3]LDA Screen_ScrollHorizBlocks_PPUAttrAddr_U [d6b5]STA a:PPUADDR [d6b8]TXA [d6b9]ASL A [d6ba]ASL A [d6bb]ASL A [d6bc]CLC [d6bd]ADC Screen_ScrollHorizBlocks_PPUAttrAddr_L [d6bf]STA a:PPUADDR [d6c2]LDA Screen_ScrollHorizPPUAttrData,X [d6c5]STA a:PPUDATA [d6c8]INX [d6c9]CPX #$07 [d6cb]BCC @_loop [d6cd]RTS
;============================================================================ ; Handle a breakable floor block the player is standing on. ; ; This is called when the game determines the player is ; standing on a breakable floor block. ; ; The block will be taken through a transition phase, ; depending on the duration in which the player has been ; on the block. This will go through 3 transitions. ; ; The transitions go from: ; ; 0x34 -> 0x2C -> 0x5C -> 0x13 ; ; INPUTS: ; X: ; The block position to update. ; ; Blocks_Result: ; The block type at the offset. ; ; BREAKABLE_FLOOR_TRANSITIONS: ; Sequence of breakable floor block types. ; ; OUTPUTS: ; ScreenBuffer: ; The updated screen buffer. ; ; Arg_BlockAttributesIndex: ; Updated block type. ; ; Temp_00: ; Clobbered. ; ; CALLS: ; Area_SetBlocks ; ; XREFS: ; Player_CheckOnBreakableBlock ;============================================================================
[d6ce]Area_HandleBreakableFloor:; [$d6ce] [d6ce]STX Temp_00; Temporarily store the block position. [d6d0]LDX #$00; X = 0 (loop counter) [d6d2]LDA Blocks_Result; A = block type (value to search for) [d6d4]@_checkBlockLoop:; [$d6d4] [d6d4]CMP BREAKABLE_FLOOR_TRANSITIONS,X; Does the block match the table at this index? [d6d7]BEQ @_finishLoop; If so, jump. [d6d9]INX; Else, X++ [d6da]CPX #$03; Is X < 3? [d6dc]BCC @_checkBlockLoop; If so, loop. [d6de]BCS @_updateBlocks; Else, jump to update the block. [d6e0]@_finishLoop:; [$d6e0] [d6e0]INX; X++ (table index of block to set) [d6e1]@_updateBlocks:; [$d6e1] [d6e1]LDA BREAKABLE_FLOOR_TRANSITIONS,X; Load the block to set to. [d6e4]STA a:Arg_BlockAttributesIndex; DEADCODE: This is overridden in Area_SetBlocks [d6e7]LDX Temp_00; X = Block position (stored earlier) [d6e9]STA ScreenBuffer,X; Store in the screen buffer as the new block. [d6ec]JMP Area_SetBlocks; And set it for the area data.
;============================================================================ ; Sequence of block types for breakable floor transitions. ; ; XREFS: ; Area_HandleBreakableFloor ;============================================================================
; ; XREFS: ; Area_HandleBreakableFloor ;
[d6ef]BREAKABLE_FLOOR_TRANSITIONS:; [$d6ef] [d6ef].byte $34; [0]:
; ; XREFS: ; Area_HandleBreakableFloor ;
[d6f0]BREAKABLE_FLOOR_TRANSITIONS_1_:; [$d6f0] [d6f0].byte $2c; [1]:
; ; XREFS: ; Area_HandleBreakableFloor ;
[d6f1]BREAKABLE_FLOOR_TRANSITIONS_2_:; [$d6f1] [d6f1].byte $5c; [2]: [d6f2].byte $13; [3]:
;============================================================================ ; Screen PPU start addresses for updating blocks. ; ; This maps a value to the upper byte of the PPU ; start address for use when updating blocks on the ; screen. ; ; This is used in Area_SetBlocks_SetAttributes. ; ; XREFS: ; Area_SetBlocks_SetAttributes ;============================================================================
; ; XREFS: ; Area_SetBlocks_SetAttributes ;
[d6f3]SET_BLOCKS_SCREEN_TILEMAP_ADDRS_U:; [$d6f3] [d6f3].byte $20; [0]: Screen 0 [d6f4].byte $24; [1]: Screen 1
;============================================================================ ; Open the path to Mascon. ; ; This will animate the removal of the stone coverings on ; the fountain and then drop down the ladder so it can be ; climbed to Mascon. ; ; INPUTS: ; PathToMascon_FountainCoverPos: ; The block position of the top of the fountain ; cover. ; ; GAME_LADDER_TO_MASCON_BLOCK_OFFSETS: ; The offsets for animating the fountain cover. ; ; OUTPUTS: ; PathToMascon_LadderBlocksRemaining: ; The number of ladder blocks remaining to place. ; ; PathToMascon_LadderPos: ; The position of the next ladder block to place. ; ; ScreenBuffer: ; The updated screen buffer. ; ; CALLS: ; Area_SetBlocks ; Game_DropLadderToMascon ; WaitForInterrupt ; ; XREFS: ; Player_CheckPushingBlock ; ScreenEvents_HandlePathToMasconEvent ;============================================================================
[d6f5]Game_OpenPathToMascon:; [$d6f5]
; ; Push the stone covering block offset to the stack for later. ;
[d6f5]TXA; A = X [d6f6]PHA; Push A to the stack.
; ; Clear the top stone covering. ;
[d6f7]LDX PathToMascon_FountainCoverPos; X = Block position (in $YX form). [d6f9]LDA a:MASCON_FOUNTAIN_BLOCK_1_AIR; A = Block ID to place. [d6fc]STA ScreenBuffer,X; Store it in the screen buffer at the target location. [d6ff]JSR Area_SetBlocks; Update the block on the screen.
; ; Clear the bottom stone covering. ;
[d702]LDA PathToMascon_FountainCoverPos; X = Block position (in $YX form). [d704]CLC [d705]ADC #$10; X += 16 (next row down; YPos += 1). [d707]TAX; X = A [d708]LDA a:MASCON_FOUNTAIN_BLOCK_2_AIR; A = Block ID to place. [d70b]STA ScreenBuffer,X; Store it in the screen buffer at the target location. [d70e]JSR Area_SetBlocks; Update the block on the screen.
; ; Pop the original stone covering block offset. ;
[d711]PLA; Pull A from the stack (original block position) [d712]TAX; X = A [d713]LDA GAME_LADDER_TO_MASCON_BLOCK_OFFSETS,X; A = Block position offset, based on the arguments. [d716]CLC [d717]ADC PathToMascon_FountainCoverPos; A += Target block position. [d719]TAX; X = A [d71a]STX PathToMascon_FountainCoverPos; Update the block position.
; ; Place the top stone covering. ;
[d71c]LDA a:MASCON_FOUNTAIN_BLOCK_1_STONE; A = Stone block. [d71f]STA ScreenBuffer,X; Place it in the screen buffer at the offset. [d722]JSR Area_SetBlocks; Update the block on the screen.
; ; Place the bottom stone covering. ;
[d725]LDA PathToMascon_FountainCoverPos; A = Block position. [d727]CLC [d728]ADC #$10; A += 16 (next row). [d72a]TAX; X = A [d72b]LDA a:MASCON_FOUNTAIN_BLOCK_2_STONE; A = Stone block. [d72e]STA ScreenBuffer,X; Place it in the screen buffer. [d731]JSR Area_SetBlocks; Update the block on the screen.
; ; Prepare to animate the dropping of the ladder. ;
[d734]LDA #$07 [d736]STA PathToMascon_LadderBlocksRemaining; Set the ladder block count to 7. [d738]LDA #$22 [d73a]STA PathToMascon_LadderPos; Set the position of the top of the ladder to X=2, Y=2.
; ; Check if the Path to Mascon quest is not yet complete. ;
[d73c]LDA a:Quests; Load the completed quests. [d73f]AND #$20; Is Path to Mascon completed? [d741]BNE @_clearCoverings; If it is, jump.
; ; Path to Mascon is not complete. Wait for 30 interrupts. ;
[d743]LDX #$1e; X = 30 (loop counter) [d745]@_waitforInterruptLoop:; [$d745] [d745]JSR WaitForInterrupt; Wait for an interrupt. [d748]DEX; X--; [d749]BNE @_waitforInterruptLoop; If not 0, loop.
; ; Clear the top stone block on the fountain. ;
[d74b]@_clearCoverings:; [$d74b] [d74b]LDX PathToMascon_FountainCoverPos; X = Fountain cover block position. [d74d]LDA a:MASCON_FOUNTAIN_BLOCK_1_AIR; A = Air block. [d750]STA ScreenBuffer,X; Set in the screen buffer at the cover position. [d753]JSR Area_SetBlocks; Update the block on the screen.
; ; Clear the bottom stone block on the fountain. ;
[d756]LDA PathToMascon_FountainCoverPos; A = Fountain cover block position. [d758]CLC [d759]ADC #$10; A += 16 (next row). [d75b]TAX; X = A [d75c]LDA a:MASCON_FOUNTAIN_BLOCK_2_AIR; A = Air block. [d75f]STA ScreenBuffer,X; Set in the screen buffer at the cover position. [d762]JSR Area_SetBlocks; Update the block on the screen.
; ; Drop the ladder to the path to Mascon. ;
[d765]JMP Game_DropLadderToMascon; Drop the ladder.
;============================================================================ ; Two cleared fountain stone block coverings. ; ; XREFS: ; Game_OpenPathToMascon ;============================================================================
; ; XREFS: ; Game_OpenPathToMascon ;
[d768]MASCON_FOUNTAIN_BLOCK_1_AIR:; [$d768] [d768].byte $42; Air
; ; XREFS: ; Game_OpenPathToMascon ;
[d769]MASCON_FOUNTAIN_BLOCK_2_AIR:; [$d769] [d769].byte $42; Air
;============================================================================ ; Two set fountain stone block coverings. ; ; XREFS: ; Game_OpenPathToMascon ;============================================================================
; ; XREFS: ; Game_OpenPathToMascon ;
[d76a]MASCON_FOUNTAIN_BLOCK_1_STONE:; [$d76a] [d76a].byte $88; Stone cover
; ; XREFS: ; Game_OpenPathToMascon ;
[d76b]MASCON_FOUNTAIN_BLOCK_2_STONE:; [$d76b] [d76b].byte $88; Stone cover
;============================================================================ ; Lookup table for quickly calculating a relative X or Y position for placing ; a block. ; ; At index 0, X will be incremented by 1. ; ; At index 1, Y will be incremented by 1 (screen wrap-around). ; ; XREFS: ; Game_OpenPathToMascon ;============================================================================
; ; XREFS: ; Game_OpenPathToMascon ;
[d76c]GAME_LADDER_TO_MASCON_BLOCK_OFFSETS:; [$d76c] [d76c].byte $01; [0]: X + 1 [d76d].byte $ff; [1]: Y + 1
;============================================================================ ; Drop the ladder down to open the path to Mascon. ; ; This will animate placing all the ladder blocks, clearing ; a path up to the door to Mascon. ; ; Despite this only ever being called from the screen leading ; to Mascon, this will first check if it's on that screen. ; This appears to be code from some older design. ; ; INPUTS: ; Area_Region: ; The current region. ; ; Area_CurrentScreen: ; The current screen. ; ; PathToMascon_LadderPos: ; The starting ladder position to place. ; ; PathToMascon_LadderBlocksRemaining: ; The total number of ladder blocks to place. ; ; OUTPUTS: ; ScreenBuffer: ; The updated screen buffer. ; ; PathToMascon_LadderPos: ; PathToMascon_LadderBlocksRemaining: ; Clobbered. ; ; PathToMascon_Opening: ; Cleared (0). ; ; CALLS: ; Area_SetBlocks ; Sound_PlayEffect ; WaitForInterrupt ; ; XREFS: ; Game_OpenPathToMascon ;============================================================================
[d76e]Game_DropLadderToMascon:; [$d76e]
; ; Check if we're on the screen with the path to Mascon. ; ; NOTE: This is only ever called from this screen, ; so it's interesting that this check exists. ;
[d76e]LDA a:Area_Region; Load the current region. [d771]CMP #$01; Are we in Trunk? [d773]BNE @_dropLadderLoop; If not, jump. [d775]LDA Area_CurrentScreen; Load the current screen. [d777]CMP #$28; Is it the screen with the blocked path? [d779]BNE @_dropLadderLoop; If not, jump.
; ; We're on the right screen. Mark the path as opened. ;
[d77b]LDA a:Quests; Load the quests. [d77e]ORA #$20; Mark the path to Mascon opened. [d780]STA a:Quests; Store it.
; ; Wait for 4 interrupts. ;
[d783]@_dropLadderLoop:; [$d783] [d783]JSR WaitForInterrupt [d786]JSR WaitForInterrupt [d789]JSR WaitForInterrupt [d78c]JSR WaitForInterrupt
; ; Play the Drop Ladder sound effect. ;
[d78f]LDA #$17; 0x17 == Drop ladder sound effect. [d791]JSR Sound_PlayEffect; Play the sound effect.
; ; Place the ladder block. ;
[d794]LDX PathToMascon_LadderPos; X = Next ladder block position. [d796]LDA a:DROP_LADDER_TO_MASCON_LADDER_BLOCK; A = Ladder block. [d799]STA ScreenBuffer,X; Store in the screen buffer at X. [d79c]JSR Area_SetBlocks; Update blocks on the screen.
; ; Increment the ladder position, decrement the number of blocks ; to place, and loop. ;
[d79f]LDA PathToMascon_LadderPos; A = Ladder position. [d7a1]CLC [d7a2]ADC #$10; A += 16 (next row) [d7a4]STA PathToMascon_LadderPos; Store as the new position. [d7a6]DEC PathToMascon_LadderBlocksRemaining; Decrement the number of ladder blocks to place. [d7a8]BNE @_dropLadderLoop; If there are still blocks remaining, loop.
; ; Clear the "Opening Path" flag. ;
[d7aa]LDA #$00 [d7ac]STA PathToMascon_Opening; Set opening to 0. [d7ae]RTS
; ; XREFS: ; Game_DropLadderToMascon ;
[d7af]DROP_LADDER_TO_MASCON_LADDER_BLOCK:; [$d7af] [d7af].byte $20; Ladder block
;============================================================================ ; TODO: Document SpriteBehavior_EnemyUnused18_SomethingSetBlocks ; ; INPUTS: ; X ; ; OUTPUTS: ; A ; ; XREFS: ; SpriteBehavior_EnemyUnused18__9991 ;============================================================================
[d7b0]SpriteBehavior_EnemyUnused18_SomethingSetBlocks:; [$d7b0] [d7b0]TXA [d7b1]PHA [d7b2]TYA [d7b3]PHA [d7b4]LDX a:Something_UnusedSprite_ScreenBufferOffset [d7b7]LDA a:Something_UnusedSprite_BlockOffset [d7ba]STA ScreenBuffer,X [d7bd]JSR Area_SetBlocks [d7c0]PLA [d7c1]TAY [d7c2]PLA [d7c3]TAX [d7c4]RTS
;============================================================================ ; TODO: Document Area_SetBlocks ; ; INPUTS: ; A ; X ; ; OUTPUTS: ; A ; ; XREFS: ; Area_HandleBreakableFloor ; Game_DropLadderToMascon ; Game_OpenPathToMascon ; Player_UseMattock ; SpriteBehavior_EnemyUnused18_SomethingSetBlocks ;============================================================================
[d7c5]Area_SetBlocks:; [$d7c5] [d7c5]STA a:Arg_BlockAttributesIndex [d7c8]STX Temp_00 [d7ca]TXA [d7cb]PHA [d7cc]LDA a:CurrentROMBank [d7cf]PHA [d7d0]LDX #$03 [d7d2]JSR MMC1_UpdateROMBank [d7d5]LDA Temp_00 [d7d7]PHA [d7d8]TAX [d7d9]JSR Area_SetPPUAddrForBlockIndex [d7dc]JSR Area_SetBlocks_WriteBlockData12 [d7df]LDA a:PPU_TargetAddr [d7e2]CLC [d7e3]ADC #$20 [d7e5]STA a:PPU_TargetAddr [d7e8]LDA a:PPU_TargetAddr_U [d7eb]ADC #$00 [d7ed]STA a:PPU_TargetAddr_U [d7f0]JSR Area_SetBlocks_WriteBlockData34 [d7f3]PLA [d7f4]JSR Area_SetBlocks_SetAttributes [d7f7]PLA [d7f8]TAX [d7f9]JSR MMC1_UpdateROMBank [d7fc]PLA [d7fd]TAX [d7fe]RTS
;============================================================================ ; Write two blocks from area data 1 and 2 to the PPU buffer. ; ; This will load two tiles from the area data from ; CurrentArea_BlockData1StartAddr and ; CurrentArea_BlockData2StartAddr at offset ; Arg_BlockAttributesIndex and write them to the PPU ; buffer at the provided offset. ; ; INPUTS: ; X: ; The offset within the PPU buffer to write to. ; ; CurrentArea_BlockData1StartAddr: ; The block data used for the first block. ; ; CurrentArea_BlockData2StartAddr: ; The block data used for the second block. ; ; Arg_BlockAttributesIndex: ; The offset within the area block data to read from. ; ; OUTPUTS: ; PPUBuffer: ; The updated PPU buffer. ; ; PPUBuffer_WriteOffset: ; The updated upper bounds of the PPU buffer. ; ; CALLS: ; PPUBuffer_QueueCommandOrLength ; ; XREFS: ; Area_SetBlocks ;============================================================================
[d7ff]Area_SetBlocks_WriteBlockData12:; [$d7ff]
; ; Set the tile length to 2. ;
[d7ff]LDA #$02 [d801]JSR PPUBuffer_QueueCommandOrLength; Write length of 2.
; ; Load the first block and write to the PPU. ;
[d804]LDY a:Arg_BlockAttributesIndex; Load the block position within the level. [d807]LDA (CurrentArea_BlockData1StartAddr),Y; Load the block at that position. [d809]STA PPUBuffer,X; Store it the PPU buffer.
; ; Load the second block and write to the PPU. ;
[d80c]INX; X++ [d80d]LDA (CurrentArea_BlockData2StartAddr),Y; Load the block position within the level. [d80f]STA PPUBuffer,X; Load the block at that position.Store it the PPU buffer. [d812]INX; X++
; ; Set the new upper bounds of the buffer. ;
[d813]STX PPUBuffer_WriteOffset; Set X as the new upper bounds of the PPU buffer. [d815]RTS
;============================================================================ ; Write two blocks from area data 3 and 4 to the PPU buffer. ; ; This will load two tiles from the area data from ; CurrentArea_BlockData3StartAddr and ; CurrentArea_BlockData4StartAddr at offset ; Arg_BlockAttributesIndex and write them to the PPU ; buffer at the provided offset. ; ; INPUTS: ; X: ; The offset within the PPU buffer to write to. ; ; CurrentArea_BlockData3StartAddr: ; The block data used for the first block. ; ; CurrentArea_BlockData4StartAddr: ; The block data used for the second block. ; ; Arg_BlockAttributesIndex: ; The offset within the area block data to read from. ; ; OUTPUTS: ; PPUBuffer: ; The updated PPU buffer. ; ; PPUBuffer_WriteOffset: ; The updated upper bounds of the PPU buffer. ; ; CALLS: ; PPUBuffer_QueueCommandOrLength ; ; XREFS: ; Area_SetBlocks ;============================================================================
[d816]Area_SetBlocks_WriteBlockData34:; [$d816]
; ; Set the tile length to 2. ;
[d816]LDA #$02 [d818]JSR PPUBuffer_QueueCommandOrLength
; ; Load the first block and write to the PPU. ;
[d81b]LDY a:Arg_BlockAttributesIndex [d81e]LDA (CurrentArea_BlockData3StartAddr),Y [d820]STA PPUBuffer,X
; ; Load the second block and write to the PPU. ;
[d823]INX [d824]LDA (CurrentArea_BlockData4StartAddr),Y [d826]STA PPUBuffer,X [d829]INX
; ; Set the new upper bounds of the buffer. ;
[d82a]STX PPUBuffer_WriteOffset [d82c]RTS
;============================================================================ ; TODO: Document Area_SetBlocks_SetAttributes ; ; INPUTS: ; A ; ; OUTPUTS: ; TODO ; ; XREFS: ; Area_SetBlocks ;============================================================================
[d82d]Area_SetBlocks_SetAttributes:; [$d82d]
; ; Create a normalized offset from the index. ; ; This will be the index + 32. ; ; Effectively, this is ensuring we always have bit 5 ; set, and are starting at a value of >= 32 for the ; bit math. ;
[d82d]CLC [d82e]ADC #$20; A = index + 32 [d830]STA Temp_00; Store it.
; ; Create a floored offset from the index. ; ; This will be one of 0, 8, 16, 24, 32, 40, 48, or 56. ; It will be stored in PPU_TargetAddr for later. ;
[d832]LSR A; A = A / 4 [d833]LSR A [d834]AND #$38; Round down to a multiple of 8. [d836]STA a:PPU_TargetAddr; Store it.
; ; Compute the lower starting byte for the PPU address. ; ; This is where the block will be written to on the ; screen. ; ; Result is the floored offset (multiple of 8) OR'd ; with half the lower nibble of the normalized offset. ;
[d839]LDA Temp_00; A = normalized offset. [d83b]AND #$0f; A = lower nibble of A. [d83d]LSR A; A = A / 2 [d83e]ORA a:PPU_TargetAddr; OR with the floored offset. [d841]STA a:PPU_TargetAddr; And store it.
; ; Compute a corner index (0..3) based on the offset. ; ; The corner is composed of bits 0 and 4, packed into ; 2 bits. So: ; ; 0000 0000 == 0 ; 0000 0001 == 1 ; 0001 0000 == 2 ; 0001 0001 == 3 ;
[d844]LDA Temp_00; A = normalized offset. [d846]AND #$10; Keep bit 4 (result = 0 or 16) [d848]LSR A; Convert to value 0 or 2. [d849]LSR A [d84a]LSR A [d84b]STA Temp_06; Store the result. [d84d]LDA Temp_00; A = floored offset. [d84f]AND #$01; Keep bit 0 (result = 0 or 1). [d851]ORA Temp_06; OR with the value we just calculated (result = 0..3) [d853]STA Temp_06; Store it.
; ; Load the block attributes for this block. ;
[d855]LDY a:Arg_BlockAttributesIndex; Y = block attributes index for the new block. [d858]LDA (CurrentArea_BlockAttributesAddr),Y; A = block attributes for that index. [d85a]LDX Temp_06; X = corner (0..3 value from above). [d85c]AND SET_BLOCKS_TILE_CORNER_MASK,X; A = block attributes for the given corner. [d85f]PHA; Push it to the stack.
; ; Get the screen index we're drawing to. ;
[d860]LDA PPU_ScrollScreen; A = Scroll screen to update. [d862]AND #$01; Keep the right-most bit (0 or 1) [d864]TAX; X = result as an index for the lookup tables.
; ; Compute a start address for that screen. ; ; NOTE: Despite using a lookup table, this will always ; be $0242 (TextBox_AttributeData), since all ; values in the tables are the same for all indexes. ; ; This is code that could be optimized away. ;
[d865]LDA SET_BLOCKS_TILEMAP_OFFSETS_L,X; A = lower byte of PPU address [d868]STA Temp_Addr_L; Store it. [d86a]LDA SET_BLOCKS_TILEMAP_OFFSETS_U,X; A = upper byte of PPU address [d86d]STA Temp_Addr_U; Store it.
; ; Compute the upper starting byte for the PPU address. ;
[d86f]LDA SET_BLOCKS_SCREEN_TILEMAP_ADDRS_U,X; A = tilemap address for the screen. [d872]STA a:PPU_TargetAddr_U; Store as the upper byte of the tilemap address.
; ; Combine the new data for this corner with the existing ; data. ;
[d875]LDY a:PPU_TargetAddr; Y = lower byte of tilemap address computed above. [d878]LDA (Temp_Addr_L),Y; Load the data for this block. [d87a]LDX Temp_06; X = corner value (0..3) [d87c]AND SET_BLOCKS_TILE_CORNER_MASK_INVERT,X; Invert the corner mask (keep all non-updated corner data). [d87f]STA Temp_06; Store as the new value. [d881]PLA; Pull the updated block attributes for the corner. [d882]ORA Temp_06; OR it with the non-updating corner values. [d884]STA (Temp_Addr_L),Y; And store it. [d886]PHA; And push it to the stack.
; ; Create a final PPU attribute target address to draw to. ; ; This is going to be an address starting at $23C0. ;
[d887]LDA a:PPU_TargetAddr_U; Load the upper byte of the PPU address. [d88a]ORA #$03; Ensure it starts at 0x03 (address >= $03XX). Realistically, $23C0 (we guarantee 0x20 or 0x24 above for the screen). [d88c]STA a:PPU_TargetAddr_U; Store as the new upper byte. [d88f]LDA a:PPU_TargetAddr; Load the lower byte. [d892]ORA #$c0; Ensure it starts at 0xC0 (address >= $XXC0) [d894]STA a:PPU_TargetAddr; Store it.
; ; Write the attribute data to the PPU buffer. ;
[d897]LDA #$01 [d899]JSR PPUBuffer_QueueCommandOrLength; Queue 1 byte. [d89c]PLA; Pull the attribute data to write from stack. [d89d]STA PPUBuffer,X; Store it in the buffer at X. [d8a0]INX; X++ (new offset) [d8a1]STX PPUBuffer_WriteOffset; Store it. [d8a3]RTS
; ; XREFS: ; Area_SetBlocks_SetAttributes ;
[d8a4]SET_BLOCKS_TILE_CORNER_MASK:; [$d8a4] [d8a4].byte $03; [0]: [d8a5].byte $0c; [1]: [d8a6].byte $30; [2]: [d8a7].byte $c0; [3]:
; ; XREFS: ; Area_SetBlocks_SetAttributes ;
[d8a8]SET_BLOCKS_TILE_CORNER_MASK_INVERT:; [$d8a8] [d8a8].byte $fc; [0]: [d8a9].byte $f3; [1]: [d8aa].byte $cf; [2]: [d8ab].byte $3f; [3]:
;============================================================================ ; TODO: Document Area_SetPPUAddrForBlockIndex ; ; INPUTS: ; X ; ; OUTPUTS: ; TODO ; ; XREFS: ; Area_SetBlocks ;============================================================================
[d8ac]Area_SetPPUAddrForBlockIndex:; [$d8ac]
; ; Build an address based on the X, Y position. ; ; X = X * 2 ; Y = Y * 4 ;
[d8ac]LDA #$00 [d8ae]STA a:PPU_TargetAddr_U [d8b1]TXA [d8b2]AND #$0f [d8b4]ASL A [d8b5]STA a:PPU_TargetAddr [d8b8]TXA [d8b9]AND #$f0 [d8bb]ASL A [d8bc]ROL a:PPU_TargetAddr_U [d8bf]ASL A [d8c0]ROL a:PPU_TargetAddr_U [d8c3]CLC [d8c4]ADC a:PPU_TargetAddr [d8c7]STA a:PPU_TargetAddr [d8ca]LDA PPU_ScrollScreen [d8cc]AND #$01 [d8ce]TAY [d8cf]LDA a:PPU_TargetAddr_U [d8d2]ORA BYTE_ARRAY_PRG15_MIRROR__d8ea,Y [d8d5]STA a:PPU_TargetAddr_U [d8d8]LDA a:PPU_TargetAddr [d8db]CLC [d8dc]ADC #$80 [d8de]STA a:PPU_TargetAddr [d8e1]LDA a:PPU_TargetAddr_U [d8e4]ADC #$00 [d8e6]STA a:PPU_TargetAddr_U [d8e9]RTS
; ; XREFS: ; Area_SetPPUAddrForBlockIndex ;
[d8ea]BYTE_ARRAY_PRG15_MIRROR__d8ea:; [$d8ea] [d8ea].byte $20; [0]: [d8eb].byte $24; [1]:
;============================================================================ ; Reset the state for the player upon death. ; ; INPUTS: ; Player_Flags: ; The player's current flags. ; ; OUTPUTS: ; Player_InvincibilityPhase: ; Player_MovementTick: ; Player_StatusFlag: ; Cleared. ; ; CALLS: ; Screen_ClearSprites ; Screen_LoadUIPalette ; MMC1_LoadBankAndJump ; Screen_ResetSpritesForGamePlay ; Player_DrawSprite ; PPUBuffer_WritePalette ; PlayerDeath_ResetSelectedItemState ; Player_DrawBody ; Player_DrawDeathAnimation ; Player_Spawn ; Screen_FadeToBlack ; Screen_NextTransitionState ; Game_InitStateForSpawn ; WaitForInterrupt ; WaitForNextFrame ; ; XREFS: ; Game_MainLoop ;============================================================================
[d8ec]Player_HandleDeath:; [$d8ec] [d8ec]LDX #$ff; X = 0xFF [d8ee]TXS; Set as stack pointer
; ; Switch to bank 14 (Logic) ;
[d8ef]LDX #$0e [d8f1]JSR MMC1_UpdateROMBank; Switch to bank 14.
; ; Clear all player bits but the facing direction. ;
[d8f4]LDA Player_Flags; Load the player's flags. [d8f6]AND #$40; Remove all but the facing direction bit. [d8f8]STA Player_Flags; Store it back.
; ; Clear out more player state. ;
[d8fa]LDA #$00 [d8fc]STA Player_StatusFlag; Set player status to 0. [d8fe]STA Player_MovementTick; Set the movement tick to 0. [d900]STA Player_InvincibilityPhase; Set invincibility phase to 0.
; ; Push the inventory state to the stack. ;
[d902]LDA a:SelectedWeapon; Load the selected weapon. [d905]PHA; Push to the stack. [d906]LDA a:SelectedArmor; Load the selected armor. [d909]PHA; Push to the stack. [d90a]LDA a:SelectedShield; Load the selected shield. [d90d]PHA; Push to the stack. [d90e]LDA a:SelectedMagic; Load the selected magic. [d911]PHA; Push to the stack. [d912]LDA a:SelectedItem; Load the selected item. [d915]PHA; Push to the stack.
; ; Wait for interrupt and call other functions to ; reset state. ;
[d916]JSR WaitForInterrupt; Wait for interrupt. [d919]JSR Screen_ResetSpritesForGamePlay; Reset animations. [d91c]JSR PlayerDeath_ResetSelectedItemState; Reset selected item state. [d91f]JSR Player_DrawSprite; Update player sprite.
; ; Prepare state for screen transitions. ;
[d922]LDA #$00 [d924]STA a:Screen_TransitionCounter; Clear the screen transition counter.
; ; Reset the player death state. ;
[d927]STA a:PlayerIsDead; Clear the dead flag. [d92a]LDA #$ff [d92c]STA a:Player_DeathAnimationPhase; Clear the death animation phase. [d92f]STA a:Player_DeathAnimationCounter; Clear the death animation counter. [d932]STA a:Screen_FadeOutStage; Clear the palette index.
; ; Clear the sprites and music. ;
[d935]JSR Screen_ClearSprites; Clear sprites from the screen. [d938]LDA #$00 [d93a]STA Music_Current; Clear the music.
; ; Play the death sound effect. ;
[d93c]LDA #$16; 0x16 == Player death sound. [d93e]JSR Sound_PlayEffect; Play the sound effect.
; ; Begin our death animation loop. ; ; This will dissolve the user's sprite. ;
[d941]@_dissolvePlayerLoop:; [$d941] [d941]JSR WaitForNextFrame; Wait for the next frame. [d944]JSR Screen_ResetSpritesForGamePlay; Reset animations. [d947]JSR Screen_NextTransitionState; Prepare for the next screen state. [d94a]JSR Player_DrawBody; Draw the player. [d94d]JSR Player_DrawDeathAnimation; Draw the next cycle of the death animation.
; ; Check whether to reset the animation state. ;
[d950]LDA a:Player_DeathAnimationPhase; Load the animation phase. [d953]CMP #$ff; Is it 0xFF (complete)? [d955]BNE @_prepareNextLoop; If not, prepare for the next loop. [d957]LDA a:Screen_TransitionCounter; Load the screen transition counter. [d95a]CMP #$10; Is it >= 16? [d95c]BCC @_prepareNextLoop; If so, prepare for next loop.
; ; Clear animation state. ;
[d95e]LDA #$00 [d960]STA a:Player_DeathAnimationPhase; Clear the animation phase. [d963]STA a:Player_DeathAnimationCounter; Clear the animation counter. [d966]@_prepareNextLoop:; [$d966] [d966]INC a:Screen_TransitionCounter; Increment the screen transition counter. [d969]BNE @_continueDissolvePlayerLoop; If transition counter != 0, loop.
; ; We're past the player death animation. ; ; Load the palette and update the screen. ;
[d96b]LDA a:Screen_PaletteIndex [d96e]JSR Screen_LoadUIPalette; Load the palette. [d971]JSR PPUBuffer_WritePalette; Append 0 to the PPU buffer.
; ; Restore the player's inventory. ;
[d974]PLA; Pop the selected item from the stack. [d975]STA a:SelectedItem; Set it. [d978]PLA; Pop the magic from the stack. [d979]STA a:SelectedMagic; Set it. [d97c]PLA; Pop the shield from the stack. [d97d]STA a:SelectedShield; Set it. [d980]PLA; Pop the armor from the stack. [d981]STA a:SelectedArmor; Set it. [d984]PLA; Pop the weapon from the stack. [d985]STA a:SelectedWeapon; Set it.
; ; Set the death music. ;
[d988]LDA #$08; 0x08 == Death music. [d98a]STA Music_Current; Set as the current music.
; ; Clear the sprite loaded state. ;
[d98c]LDA #$ff [d98e]STA Screen_ReadyState; Set loaded state to 0xFF.
; ; Run IScript 0xFF via jump to IScripts_Begin. ;
[d990]JSR MMC1_LoadBankAndJump; Run the IScript: [d993].byte BANK_12_LOGIC; Bank = 12 [d994].word IScripts_Begin-1; Address = IScripts_Begin [d996]@_afterIScriptFarJump:; [$d996] [d996]LDA #$00; 0 = No music [d998]STA Music_Current; Set it.
; ; Set initial gold and experience via jump ; to Player_SetInitialExpAndGold. ;
[d99a]JSR MMC1_LoadBankAndJump; Run: [d99d].byte BANK_12_LOGIC; Bank = 12 [d99e].word Player_SetInitialExpAndGold-1; Address = Player_SetInitialExpAndGold [d9a0]@_afterSetExpGoldFarJump:; [$d9a0] [d9a0]JSR Screen_FadeToBlack; Fade the screen to black. [d9a3]JSR Game_InitStateForSpawn; Reset state for spawn. [d9a6]JMP Player_Spawn; Spawn the player. [d9a9]@_continueDissolvePlayerLoop:; [$d9a9] [d9a9]JMP @_dissolvePlayerLoop
;============================================================================ ; Begin the end-game sequence following the final boss kill. ; ; This will freeze the game briefly, fade to black, and then ; briefly wait while playing music before moving the player ; into the King's room for the final dialogue sequence. ; ; INPUTS: ; None. ; ; OUTPUTS: ; None. ; ; CALLS: ; EndGame_BeginKingsRoomSequence ; Screen_ClearSprites ; Screen_ResetSpritesForGamePlay ; Game_DrawScreenInFrozenState ; Screen_FadeToBlack ; WaitForNextFrame ; ; XREFS: ; ScreenEvents_HandleFinalBossKilled ;============================================================================
[d9ac]EndGame_Begin:; [$d9ac]
; ; Clear all sprites from the screen. ;
[d9ac]JSR Screen_ClearSprites; Clear sprites.
; ; Wait 120 frames before beginning the fade-out. ;
[d9af]LDX #$78; X = 120 (loop counter) [d9b1]@_beforeFadeLoop:; [$d9b1] [d9b1]TXA; A = X [d9b2]PHA; Push to the stack. [d9b3]JSR WaitForNextFrame; Wait for the next frame. [d9b6]JSR Screen_ResetSpritesForGamePlay; Reset for this frame. [d9b9]JSR Game_DrawScreenInFrozenState; Draw the screen and player in a semi-paused state. [d9bc]PLA; Pop our loop counter. [d9bd]TAX; X = A [d9be]DEX; X-- [d9bf]BNE @_beforeFadeLoop; If > 0, loop.
; ; Fade the screen to black. ;
[d9c1]JSR Screen_FadeToBlack; Fade to black.
; ; Wait another 120 frames before switching screens. ;
[d9c4]LDX #$78; X = 120 (loop counter) [d9c6]@_beforeTransitionLoop:; [$d9c6] [d9c6]TXA; A = X (loop counter) [d9c7]PHA; Push to the stack. [d9c8]JSR WaitForNextFrame; Wait for the next frame. [d9cb]JSR Screen_ResetSpritesForGamePlay; Reset for this frame. [d9ce]PLA; Pop our loop counter. [d9cf]TAX; X = A (loop counter) [d9d0]DEX; X-- [d9d1]BNE @_beforeTransitionLoop; If > 0, loop.
; ; Move the player to the King's room and begin the ; end-game dialogue sequence. ;
[d9d3]JMP EndGame_BeginKingsRoomSequence; Begin the next transition into the King's room.
;============================================================================ ; Draw the player's death animation. ; ; This will make us of the Clear Vertical Lines PPU ; draw command ; (PPUBuffer_DrawCommand_RemoveVerticalLines), ; clearing away lines from the player sprite in a set ; pattern until a counter is reached and the player is gone. ; ; This will run for 250 frames. ; ; INPUTS: ; Player_DeathAnimationPhase: ; The phase to pass to the draw command. ; ; Player_DeathAnimationCounter: ; The animation counter, which will govern ; phase, tile address, and total animation ; time. ; ; InterruptCounter: ; The current interrupt counter. ; ; PPUBuffer_WriteOffset: ; The upper bounds of the PPU buffer. ; ; DEATH_ANIMATION_DRAW_ADDR_LOWER: ; A mapping of counter values to player tile ; lower address bytes. ; ; OUTPUTS: ; Player_DeathAnimationCounter: ; The updated death animation counter. ; ; Player_DeathAnimationPhase: ; The updated death animation phase. ; ; PPUBuffer: ; The PPU buffer to write to. ; ; PPUBuffer_WriteOffset: ; The new upper bounds of the PPU buffer. ; ; XREFS: ; Player_HandleDeath ;============================================================================
[d9d6]Player_DrawDeathAnimation:; [$d9d6] [d9d6]LDA a:Player_DeathAnimationPhase; Load the death animation phase. [d9d9]CMP #$08; Is it >= 8? [d9db]BCS @_return; If so, return. [d9dd]LDA a:Player_DeathAnimationCounter; Load the animation death counter. [d9e0]BPL @_dissolveSprite; If >= 0, jump to dissolve the sprite. [d9e2]LDA InterruptCounter; Else, load the interrupt counter. [d9e4]AND #$03; Every 3 out of 4 ticks... [d9e6]BNE @_return; Return. [d9e8]LDA #$00 [d9ea]STA a:Player_DeathAnimationCounter; Clear the death counter.
; ; Queue draw command 0xFA, dissolving the character's sprite ; by removal of vertical lines. ; ; This is ; PPUBuffer_DrawCommand_RemoveVerticalLines. ; ; Draw 250 frames. ;
[d9ed]@_dissolveSprite:; [$d9ed] [d9ed]LDX PPUBuffer_WriteOffset; X = PPU buffer upper bounds. [d9ef]LDA #$fa; A = 0xFA (Remove Vertical Lines draw command). [d9f1]STA PPUBuffer,X; Set that as the command to execute.
; ; Set the current player phase for the draw. ;
[d9f4]INX; X++ [d9f5]LDA a:Player_DeathAnimationPhase; Load the animation phase. [d9f8]STA PPUBuffer,X; Set that as the phase parameter.
; ; Set the upper byte of the address to 0. ;
[d9fb]INX; X++ [d9fc]LDA #$00 [d9fe]STA PPUBuffer,X; Set 0 as the upper address.
; ; Set the lower byte of the address based on the counter. ;
[da01]INX; X++ [da02]LDY a:Player_DeathAnimationCounter; Load the animation counter. [da05]LDA DEATH_ANIMATION_DRAW_ADDR_LOWER,Y; Load the lower address for Y. [da08]STA PPUBuffer,X; Set it as the lower address.
; ; Store the new upper bounds of the PPU buffer. ;
[da0b]INX; X++ [da0c]STX PPUBuffer_WriteOffset; Set as the new upper bounds for the PPU buffer.
; ; Update the animation counter. ;
[da0e]INC a:Player_DeathAnimationCounter; Increment the animation counter.
; ; If >= 8, reset the counter and increment the phase. ;
[da11]LDA a:Player_DeathAnimationCounter; Load the new animation counter. [da14]CMP #$08; Is it < 8? [da16]BCC @_return; If so, return.
; ; Reset the animation counter and increment the phase. ; Player_HandleDeath will see this and prepare the ; next ; round of vertical line removals. ;
[da18]LDA #$ff [da1a]STA a:Player_DeathAnimationCounter; Set the animation counter to 0xFF. [da1d]INC a:Player_DeathAnimationPhase; Increment the animation phase. [da20]@_return:; [$da20] [da20]RTS
;============================================================================ ; Lower bytes for the player's tiles to update during death animation. ; ; XREFS: ; Player_DrawDeathAnimation ;============================================================================
; ; XREFS: ; Player_DrawDeathAnimation ;
[da21]DEATH_ANIMATION_DRAW_ADDR_LOWER:; [$da21] [da21].byte $00; [0]: [da22].byte $10; [1]: [da23].byte $20; [2]: [da24].byte $30; [3]: [da25].byte $40; [4]: [da26].byte $50; [5]: [da27].byte $60; [6]: [da28].byte $70; [7]:
;============================================================================ ; DEADCODE: Clear the fade-out stage for the screen transitions. ;============================================================================
[da29]UNUSED_Screen_ClearFadeOutStage:; [$da29] [da29]LDA #$ff [da2b]STA a:Screen_FadeOutStage [da2e]RTS
;============================================================================ ; Fade the screen out to black. ; ; This is called during door transitions or after ; dissolving a character during death. ; ; INPUTS: ; None ; ; OUTPUTS: ; Screen_FadeOutStage: ; Clobbered. ; ; CALLS: ; WaitForInterrupt ; Screen_NextTransitionState ; ; XREFS: ; EndGame_Begin ; Player_CheckHandleEnterDoor ; Player_EnterDoorToInside ; Player_EnterDoorToOutside ; _afterSetExpGoldFarJump ; [$PRG15_MIRROR::d9a0] ;============================================================================
[da2f]Screen_FadeToBlack:; [$da2f] [da2f]LDA #$00; X = 0 (loop counter) [da31]STA a:Screen_FadeOutStage; Set as the index into the screen palette lookup. [da34]@_loop:; [$da34] [da34]JSR WaitForInterrupt; Wait for the next interrupt. [da37]JSR Screen_NextTransitionState; Update the screen for this palete. [da3a]LDA a:Screen_FadeOutStage; Load the palette index. [da3d]CMP #$04; Is it < 4? [da3f]BCC @_loop; If so, loop. [da41]RTS
;============================================================================ ; Handle the next state for a screen transition. ; ; This may be used when entering a building or when dying. ; An outer loop will call this each tick. It won't begin ; the fadeout until Screen_FadeOutStage is a value 0-4. ; ; The palette will increase at periodic intervals, ; eventually ending with a black screen. ; ; INPUTS: ; Screen_FadeOutStage: ; The current palette index. ; ; Screen_FadeOutCounter: ; The counter for this loop. ; ; Screen_PaletteIndex: ; The palette index to apply for the transition. ; ; OUTPUTS: ; Screen_FadeOutStage: ; The new palette index. ; ; Screen_FadeOutCounter: ; The fade-out counter. ; ; XREFS: ; Player_HandleDeath ; Screen_FadeToBlack ;============================================================================
[da42]Screen_NextTransitionState:; [$da42] [da42]LDA a:Screen_FadeOutStage; Load the current palette index. [da45]CMP #$04; Is it >= 4? [da47]BCS @_doneFadeOut; If so, jump to finish up. [da49]LDA a:Screen_FadeOutCounter; A = fadeout counter. [da4c]CLC [da4d]ADC #$32; A += 0x32 (50) [da4f]STA a:Screen_FadeOutCounter; Store as the new value. [da52]BCC @_doneFadeOut; If 0, don't alter the palette.
; ; Fade out to the next palette towards black. ;
[da54]LDA a:Screen_PaletteIndex; Load the screen palette index. [da57]JSR Screen_SetFadeOutPalette; Load the palette into memory. [da5a]INC a:Screen_FadeOutStage; Increment the palette index to the next transition state. [da5d]@_doneFadeOut:; [$da5d] [da5d]LDA a:Screen_TransitionCounter; Load the screen transition counter. [da60]CMP #$50; Is it < 80? [da62]BNE @_return; If so, return.
; ; We're doing player death, and have finished dissolving ; the player. We can now begin fading to black. ;
[da64]LDA #$00 [da66]STA a:Screen_FadeOutStage; Set the palette to 0. [da69]@_return:; [$da69] [da69]RTS
;============================================================================ ; Initialize the state for the Faxanadu start screen. ; ; INPUTS: ; None. ; ; OUTPUTS: ; Area_CurrentArea: ; Set to Eolis. ; ; Area_Region: ; Set to Eolis. ; ; CALLS: ; Player_InitInventoryState ; Game_InitStateForSpawn ; Game_ShowStartScreen ; ; XREFS: ; Game_Init ;============================================================================
[da6a]Game_InitStateForStartScreen:; [$da6a] [da6a]LDX #$ff; X = 0xFF [da6c]TXS; Copy to stack pointer. [da6d]LDA #$00; A = 0 [da6f]STA Area_CurrentArea; Set as the current area (Eolis) [da71]STA a:Area_Region; Set as the current region (Eolis) [da74]JSR Player_InitInventoryState; Initialize the player inventory. [da77]JSR Game_InitStateForSpawn; Initiate the spawn state. [da7a]JMP Game_ShowStartScreen; Show the start screen.
;============================================================================ ; TODO: Document Game_InitStateForSpawn ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Game_InitStateForStartScreen ;============================================================================
[da7d]Game_InitStateForSpawn:; [$da7d] [da7d]JSR PPU_WaitUntilFlushed; Wait until the PPU is flushed. [da80]LDA #$00 [da82]STA a:PlayerIsDead; Set the player as alive. [da85]STA PPUBuffer_WriteOffset; Reset PPU buffer upper bounds to 0. [da87]STA PPUBuffer_ReadOffset; Reset PPU buffer offset to 0. [da89]STA Screen_ReadyState; Mark the screen as loaded and ready. [da8b]JSR Player_SetInitialState; Initialize the player's initial state. [da8e]JSR Player_ClearVisibleMagic; Clear any visible magic on screen. [da91]JSR Sprites_LoadCommon; Load sprites from bank 8. [da94]LDA #$01 [da96]STA a:Maybe_Game_Ready; Set that the game is ready to play. [da99]LDA #$00 [da9b]STA PPU_ScrollX; Clear the scrolling screen pixel state. [da9d]STA PPU_ScrollScreen; Clear the scrolling screen state. [da9f]RTS
;============================================================================ ; Setup state for entering a new screen. ; ; This is called when switching screens or entering through ; a door. ; ; This will set up the game and screen state for the ; destination and load new sprites. ; ; INPUTS: ; None. ; ; OUTPUTS: ; None. ; ; CALLS: ; Screen_ResetForGamePlay ; GameLoop_LoadSpriteImages ; PPU_WaitUntilFlushed ; Screen_Load ; WaitForNextFrame ; ; XREFS: ; Player_CheckHandleEnterDoor ;============================================================================
[daa0]Game_SetupEnterScreen:; [$daa0] [daa0]JSR PPU_WaitUntilFlushed; Wait until the PPU is flushed. [daa3]JSR Screen_Load; Load the screen state. [daa6]JSR Screen_ResetForGamePlay; Reset the screen and enable interrupts. [daa9]JSR WaitForNextFrame; Wait for the next frame. [daac]JMP GameLoop_LoadSpriteImages; Load the sprite images.
;============================================================================ ; Setup state for entering a building. ; ; This will set up the game and screen state for the ; destination building and load new sprites. ; ; INPUTS: ; None. ; ; OUTPUTS: ; None. ; ; CALLS: ; Game_EnterBuilding ; Screen_ResetForGamePlay ; GameLoop_LoadSpriteImages ; PPU_WaitUntilFlushed ; WaitForNextFrame ; ; XREFS: ; EndGame_MoveToKingsRoom ; Player_EnterDoorToInside ; Player_Spawn ;============================================================================
[daaf]Game_SetupEnterBuilding:; [$daaf] [daaf]JSR PPU_WaitUntilFlushed; Wait until the PPU is flushed. [dab2]JSR Game_EnterBuilding; Load state for the building. [dab5]JSR Screen_ResetForGamePlay; Reset the screen and enable interrupts. [dab8]JSR WaitForNextFrame; Wait for the next frame. [dabb]JMP GameLoop_LoadSpriteImages; Load the sprite images.
;============================================================================ ; Setup state for exiting a building. ; ; This will set up the game and screen state for the ; area outside the building and load new sprites. ; ; INPUTS: ; None. ; ; OUTPUTS: ; None. ; ; CALLS: ; Game_ExitBuilding ; Screen_ResetForGamePlay ; GameLoop_LoadSpriteImages ; PPU_WaitUntilFlushed ; WaitForNextFrame ; ; XREFS: ; Game_BeginExitBuilding ;============================================================================
[dabe]Game_SetupExitBuilding:; [$dabe] [dabe]JSR PPU_WaitUntilFlushed; Wait until the PPU is flushed. [dac1]JSR Game_ExitBuilding; Load state for the area outside the building. [dac4]JSR Screen_ResetForGamePlay; Reset the screen and enable interrupts. [dac7]JSR WaitForNextFrame; Wait for the next frame. [daca]JMP GameLoop_LoadSpriteImages; Load the sprite images.
;============================================================================ ; Setup state for a new area. ; ; This will set up the game and screen state for the ; area and load new sprites. ; ; INPUTS: ; None. ; ; OUTPUTS: ; None. ; ; CALLS: ; Game_EnterAreaHandler ; Screen_ResetForGamePlay ; GameLoop_LoadSpriteImages ; PPU_WaitUntilFlushed ; WaitForNextFrame ; ; XREFS: ; Player_CheckSwitchScreen_SwitchAreaHoriz ;============================================================================
[dacd]Game_SetupNewArea:; [$dacd] [dacd]JSR PPU_WaitUntilFlushed; Wait until the PPU is flushed. [dad0]JSR Game_EnterAreaHandler; Load state for the new area. [dad3]JSR Screen_ResetForGamePlay; Reset the screen and enable interrupts. [dad6]JSR WaitForNextFrame; Wait for the next frame. [dad9]JMP GameLoop_LoadSpriteImages; Load the sprite images.
;============================================================================ ; Setup state for an area in a new region of the game. ; ; This will set up the game and screen state for the ; area and load new sprites. ; ; This is used when exiting a door to the outside, or ; when using the controller 2 debug code to switch areas. ; ; INPUTS: ; DOOR_OUTSIDE_REGION_INDEXES: ; Mapping of regions to areas to place the player. ; ; OUTPUTS: ; Area_CurrentArea: ; The new area to set. ; ; CALLS: ; Game_LoadCurrentArea ; Game_MainLoop ; Screen_ResetForGamePlay ; GameLoop_LoadSpriteImages ; PPU_WaitUntilFlushed ; WaitForNextFrame ; ; XREFS: ; Debug_ChooseArea ; Player_EnterDoorToOutside ;============================================================================
[dadc]Game_SetupAndLoadOutsideArea:; [$dadc] [dadc]JSR PPU_WaitUntilFlushed; Wait until the PPU is flushed. [dadf]LDX a:Area_Region; Load the region. [dae2]LDA DOOR_OUTSIDE_REGION_INDEXES,X; Load the area of the game to place the player for this region. [dae5]STA Area_CurrentArea; Store as the new area. [dae7]JSR Game_LoadCurrentArea; Load data for the area. [daea]JSR Screen_ResetForGamePlay; Reset the screen and enable interrupts. [daed]JSR WaitForNextFrame; Wait for the next frame. [daf0]JSR GameLoop_LoadSpriteImages; Load the sprite images. [daf3]JMP Game_MainLoop; Run the game mainloop.
;============================================================================ ; DEADCODE ; ; Would assign the area based on transitioning from another. ;============================================================================
[daf6]FUN_PRG15_MIRROR__daf6:; [$daf6] [daf6]LDX a:Area_Region [daf9]LDA DOOR_OUTSIDE_REGION_INDEXES,X [dafc]STA Area_CurrentArea
; ; XREFS: ; FUN_PRG15_MIRROR__daf6 ; Game_SetupAndLoadOutsideArea ;
[dafe]DOOR_OUTSIDE_REGION_INDEXES:; [$dafe] [dafe].byte AREA_EOLIS; [0]: Eolis [daff].byte AREA_APOLUNE; [1]: Trunk [db00].byte AREA_FOREPAW; [2]: Mist [db01].byte AREA_CONFLATE; [3]: Branch [db02].byte AREA_DAYBREAK; [4]: Dartmoor [db03].byte AREA_EVIL_FORTRESS; [5]: Evil Fortress
;============================================================================ ; Place the player in the King's room and move toward the King. ; ; This is part of the end-game sequence, and takes place ; immediately after the final boss is killed and the screen ; fades out. ; ; The player will be placed back in the King's room, and ; will then begin moving automatically toward the King. ; ; INPUTS: ; None. ; ; OUTPUTS: ; None. ; ; CALLS: ; EndGame_MoveToKingsRoom ; EndGame_MainLoop ; ; XREFS: ; EndGame_Begin ;============================================================================
[db04]EndGame_BeginKingsRoomSequence:; [$db04] [db04]JSR EndGame_MoveToKingsRoom; Place the player in the King's room. [db07]JMP EndGame_MainLoop; Move the player toward the King.
;============================================================================ ; Spawn the player. ; ; This will set the player's HP and MP to max, reset ; quest state and timed items, and place the player in ; the latest temple. ; ; INPUTS: ; Quests: ; The completed quests. ; ; OUTPUTS: ; Quests: ; The updated completed quests. ; ; Player_HP_U: ; The filled player HP. ; ; Player_MP: ; The filled player MP. ; ; CALLS: ; Game_ClearTimedItems ; Game_SpawnInTemple ; Game_SetupEnterBuilding ; Game_MainLoop ; ; XREFS: ; Game_ShowStartScreen ;============================================================================
[db0a]Player_Spawn:; [$db0a] [db0a]LDA #$50 [db0c]STA a:Player_HP_U; Set the initial player HP to 80. [db0f]STA a:Player_MP; Set the initial player MP to 80. [db12]LDA a:Quests; Load the completed quests. [db15]AND #$ef; Disable the Mattock barrier to Mascon quest. [db17]STA a:Quests; Store it. [db1a]JSR Game_ClearTimedItems; Cleared all timed items. [db1d]JSR Game_SpawnInTemple; Spawn in the temple. [db20]JSR Game_SetupEnterBuilding; Change the state to be inside a building. [db23]JMP Game_MainLoop; Start the mainloop.
;============================================================================ ; Start the game. ; ; This runs after the intro animation finishes, placing ; the player at the gates of Eolis with no MP and no ; experience. ; ; INPUTS: ; None ; ; OUTPUTS: ; Player_MP: ; Set to 0. ; ; Experience: ; Set to 0. ; ; See Fallthrough. ; ; CALLS: ; Screen_ResetForGamePlay ; Game_ClearTimedItems ; Game_LoadFirstLevel ; GameLoop_LoadSpriteImages ; PPU_WaitUntilFlushed ; WaitForNextFrame ; ; FALLTHROUGH: ; Game_MainLoop ; ; XREFS: ; Game_ShowStartScreen ;============================================================================
[db26]Game_Start:; [$db26] [db26]JSR Game_ClearTimedItems; Clear all timed items. [db29]JSR PPU_WaitUntilFlushed; Wait until everything is drawn to the screen. [db2c]JSR Game_LoadFirstLevel; Load the first level.
; ; Clear the player's MP. ;
[db2f]LDA #$00 [db31]STA a:Player_MP; Set MP to 0.
; ; Clear the player's experience. ;
[db34]LDA #$00 [db36]STA a:Experience; Set lower byte of experience to 0. [db39]STA a:Experience_U; And upper byte.
; ; Prepare state for the first screen of Eolis, including the ; intro trigger sprite entity. ;
[db3c]JSR Screen_ResetForGamePlay; Reset the screen and enable interrupts. [db3f]JSR WaitForNextFrame; Wait for the next frame. [db42]JSR GameLoop_LoadSpriteImages; Load images for the current sprite entities (which will be the intro trigger).
; ; v-- Fall through --v ;
;============================================================================ ; Mainloop for the game. ; ; This controls all input, rendering, and eventing while ; playing the game. ; ; INPUTS: ; None. ; ; OUTPUTS: ; None. ; ; XREFS: ; Game_MainLoop ; Game_SetupAndLoadOutsideArea ; Player_Spawn ;============================================================================
[db45]Game_MainLoop:; [$db45] [db45]LDX #$ff; X = 0xFF [db47]TXS; Transfer to the stack.
; ; Switch to bank 14 to run sprite logic. ;
[db48]LDX #$0e; Bank 14 = Logic [db4a]JSR MMC1_UpdateROMBank; Switch to the bank.
; ; Prepare to draw sprites. ;
[db4d]JSR WaitForNextFrame; Wait for the next frame. [db50]JSR Screen_ResetSpritesForGamePlay; Reset the sprite state for the screen. [db53]JSR GameLoop_UpdatePlayer [db56]JSR Player_DrawShield; Draw the player's shield sprite. [db59]JSR Player_DrawBody; Draw the player's body sprite. [db5c]JSR Player_DrawWeapon; Draw the player's weapon sprite. [db5f]JSR Player_CastMagic; Check and handle casting magic. [db62]JSR GameLoop_CheckUseCurrentItem; Active selected item? [db65]JSR Sprites_UpdateAll; Update all sprites. [db68]JSR GameLoop_CountdownItems [db6b]JSR Fog_OnTick [db6e]JSR GameLoop_CheckShowPlayerMenu [db71]JSR GameLoop_RunScreenEventHandlers [db74]JSR GameLoop_CheckPauseGame [db77]LDA a:PlayerIsDead [db7a]BEQ @_playerIsNotDead [db7c]JMP Player_HandleDeath [db7f]@_playerIsNotDead:; [$db7f] [db7f]LDA Screen_ScrollDirection [db81]BMI Game_MainLoop [db83]JSR PPUBuffer_WaitUntilClear [db86]LDA a:Maybe_Game_Ready [db89]BEQ @LAB_PRG15_MIRROR__db91 [db8b]LDA Screen_ScrollDirection [db8d]CMP #$02 [db8f]BCC @LAB_PRG15_MIRROR__dbaf [db91]@LAB_PRG15_MIRROR__db91:; [$db91] [db91]JSR Screen_ResetSpritesForGamePlay [db94]JSR WaitForNextFrame [db97]JSR Screen_ClearSprites [db9a]JSR Screen_LoadSpriteInfo [db9d]JSR WaitForNextFrame [dba0]JSR GameLoop_LoadSpriteImages [dba3]JSR PPU_WaitUntilFlushed [dba6]JSR Game_UpdateForScroll [dba9]JSR Screen_ResetForGamePlay [dbac]JMP Game_MainLoop [dbaf]@LAB_PRG15_MIRROR__dbaf:; [$dbaf] [dbaf]JSR WaitForNextFrame [dbb2]JSR Screen_ResetSpritesForGamePlay [dbb5]JSR Player_DrawShield [dbb8]JSR Player_DrawBody [dbbb]JSR Player_DrawWeapon [dbbe]JSR Screen_ClearSprites [dbc1]JSR Screen_LoadSpriteInfo [dbc4]JSR WaitForNextFrame [dbc7]JSR GameLoop_LoadSpriteImages [dbca]@_scrolling:; [$dbca] [dbca]JSR WaitForNextFrame [dbcd]JSR Screen_ResetSpritesForGamePlay [dbd0]JSR Game_UpdatePlayerOnScroll [dbd3]JSR Player_DrawShield [dbd6]JSR Player_DrawBody [dbd9]JSR Player_DrawWeapon [dbdc]JSR Screen_HandleScroll [dbdf]JSR Screen_HandleScroll [dbe2]JSR Screen_HandleScroll [dbe5]JSR Screen_HandleScroll [dbe8]LDA Screen_ScrollDirection [dbea]BPL @_scrolling [dbec]JMP Game_MainLoop
;============================================================================ ; Mainloop for the end of the game. ; ; This will act as a normal mainloop, except the button ; inputs will be simulated to approach the King and trigger ; the dialogue. ; ; The actual ending-game sequence happens after interacting ; with the King, which uses an IScript action to initiate ; the end-game outro sequence. ; ; INPUTS: ; None. ; ; OUTPUTS: ; None. ; ; CALLS: ; EndGame_MovePlayerTowardKing ; MMC1_UpdateROMBank ; Fog_OnTick ; GameLoop_CheckShowPlayerMenu ; GameLoop_CheckUseCurrentItem ; GameLoop_CountdownItems ; GameLoop_RunScreenEventHandlers ; Screen_ResetSpritesForGamePlay ; Player_CastMagic ; Player_DrawBody ; Player_DrawShield ; Player_DrawWeapon ; Sprites_UpdateAll ; WaitForNextFrame ; ; XREFS: ; EndGame_BeginKingsRoomSequence ; EndGame_MainLoop ;============================================================================
[dbef]EndGame_MainLoop:; [$dbef] [dbef]LDX #$ff; X = 0xFF (unused -- noop)
; ; Switch to bank 14. ;
[dbf1]LDX #$0e [dbf3]JSR MMC1_UpdateROMBank; Set bank to 14 (Logic).
; ; Wait for a frame and prepare for renders. ;
[dbf6]JSR WaitForNextFrame; Wait for the next frame. [dbf9]JSR Screen_ResetSpritesForGamePlay
; ; Move the player a step/jump toward the King, or begin ; interaction, depending on position. ;
[dbfc]JSR EndGame_MovePlayerTowardKing; Simulate the player's button press. [dbff]JSR GameLoop_UpdatePlayer; Update the player's position/state.
; ; Perform all the standard mainloop operations. ;
[dc02]JSR Player_DrawShield; Draw the shield. [dc05]JSR Player_DrawBody; Draw the armor. [dc08]JSR Player_DrawWeapon; Draw the weapon. [dc0b]JSR Player_CastMagic; No-op [dc0e]JSR GameLoop_CheckUseCurrentItem; No-op [dc11]JSR Sprites_UpdateAll; Update sprites. [dc14]JSR GameLoop_CountdownItems; No-op [dc17]JSR Fog_OnTick; No-op [dc1a]JSR GameLoop_CheckShowPlayerMenu; No-op [dc1d]JSR GameLoop_RunScreenEventHandlers; No-op [dc20]JMP EndGame_MainLoop; Loop.
;============================================================================ ; Move the player toward the King in the end-game. ; ; This will move the player by simulating holding down the ; Left joypad button, pressing the A button when at the ; stairs, and pressing Up when at the King. ; ; This is all done by factoring in the current player position: ; ; 1. Press Left until at X=79. ; ; 2. At X=79, keep pressing Jump and left. ; ; 3. At X=68, release and press Up. ; ; INPUTS: ; Player_PosX_Block: ; The current player X position. ; ; OUTPUTS: ; Joy1_ButtonMask: ; Joy1_ChangedButtonMask: ; The simulated button presses. ; ; XREFS: ; EndGame_MainLoop ;============================================================================
[dc23]EndGame_MovePlayerTowardKing:; [$dc23] [dc23]LDA Player_PosX_Block; A = player X. [dc25]CMP #$61; Is it >= 97? [dc27]BCS @_setButtonsLeft; If so, jump to just press Left. [dc29]CMP #$50; Is it >= 80? [dc2b]BCS @_setJumpLeft; If so, jump to press Left+A. [dc2d]CMP #$44; Is it >= 68? [dc2f]BCS @_setButtonsLeft; If so, jump to press Left.
; ; Simulate pressing Up to engage with the King. ;
[dc31]LDA #$08; 0x08 == Up button bitmask. [dc33]STA Joy1_ButtonMask; Set as the current button mask. [dc35]STA Joy1_ChangedButtonMask; Set as the changed button mask. [dc37]RTS
; ; Simulate pressing Left. ;
[dc38]@_setButtonsLeft:; [$dc38] [dc38]LDA #$02; 0x02 == Left button bitmask. [dc3a]STA Joy1_ButtonMask; Set as the current button mask. [dc3c]STA Joy1_ChangedButtonMask; Set as the changed button mask. [dc3e]RTS
; ; Simulate pressing Left+A to jump left. ;
[dc3f]@_setJumpLeft:; [$dc3f] [dc3f]LDA #$82; 0x82 == Left + A button bitmask. [dc41]STA Joy1_ButtonMask; Set as the current button mask. [dc43]STA Joy1_ChangedButtonMask; Set as the changed button mask.
; ; XREFS: ; Game_DrawScreenInFrozenState ;
[dc45]RETURN_DC45:; [$dc45] [dc45]RTS
;============================================================================ ; Draw the player and sprites in a frozen state. ; ; This is used when needing to draw sprites without anything ; moving around or interacting with each other. ; ; It's invoked when writing messages, filling HP/MP, or in ; the end-game sequence. ; ; INPUTS: ; CurrentROMBank: ; The current ROM bank. ; ; Screen_ReadyState: ; The loaded state for the sprites. ; ; If 0xFF, this function will immediately return. ; ; Player_StatusFlag: ; The player's current status flag. ; ; OUTPUTS: ; None. ; ; CALLS: ; Player_DrawBody ; Player_DrawShield ; Player_DrawWeapon ; Sprites_UpdateAll ; MMC1_UpdateROMBank ; MMC1_UpdatePRGBankToStackA ; ; XREFS: ; IScripts_UpdatePortraitAnimation ; EndGame_Begin ; Player_FillHPAndMP ; Player_UseRedPotion ;============================================================================
[dc46]Game_DrawScreenInFrozenState:; [$dc46] [dc46]LDA Screen_ReadyState; Load the loaded state. [dc48]CMP #$ff; Is it 0xFF? [dc4a]BEQ RETURN_DC45; If so, then return.
; ; Switch to bank 14. ;
[dc4c]LDA a:CurrentROMBank; Load the current bank. [dc4f]PHA; Push to the stack. [dc50]LDX #$0e [dc52]JSR MMC1_UpdateROMBank; Switch to bank 14.
; ; Temporarily clear Wing Boots state. ;
[dc55]LDA Player_StatusFlag; Load the player's status flag. [dc57]PHA; Push to the stack so it can be restored later. [dc58]AND #$7f; Keep all but the Wing Boots flag. [dc5a]STA Player_StatusFlag; Store it.
; ; Draw the player. ;
[dc5c]JSR Player_DrawShield; Draw the shield. [dc5f]JSR Player_DrawBody; Draw the armor. [dc62]JSR Player_DrawWeapon; Draw the weapon.
; ; Restore the player's status flags. ;
[dc65]PLA; Pop the status flag. [dc66]STA Player_StatusFlag; Store it.
; ; Draw all the sprites, but with sprites paused. ; ; This flag will will disable all behavioral updates for ; sprites. ;
[dc68]LDA #$01; A = 1 [dc6a]STA a:Sprites_UpdatesPaused; Set as the paused state for sprites. [dc6d]JSR Sprites_UpdateAllStates; Update all sprites. [dc70]LDA #$00; A = 0 [dc72]STA a:Sprites_UpdatesPaused; Clear the paused state. [dc75]JMP MMC1_UpdatePRGBankToStackA; Restore the previous bank.
;============================================================================ ; TODO: Document Game_LoadAreaTable ; ; INPUTS: ; A ; ; OUTPUTS: ; TODO ; ; XREFS: ; Game_EnterAreaHandler ; Game_EnterBuilding ; Game_ExitBuilding ; Game_LoadCurrentArea ; Game_LoadFirstLevel ;============================================================================
[dc78]Game_LoadAreaTable:; [$dc78]
; ; XXX Set the address for the area in the areas table. ;
[dc78]ASL A [dc79]TAY [dc7a]LDA ROMBankStart,Y [dc7d]STA Area_ScreenBlocksOffset [dc7f]LDA ROMBankStart+1,Y [dc82]CLC [dc83]ADC #$80 [dc85]STA Area_ScreenBlocksOffset_U
; ; Save the current ROM bank and switch to bank 3, where the ; areas table lives. ;
[dc87]LDA a:CurrentROMBank [dc8a]PHA [dc8b]LDX #$03 [dc8d]JSR MMC1_UpdateROMBank [dc90]LDA a:ROMBankStart
; ; Set the start of the area's pointer table in ; 0x{@address 007C}. ; ; This will be AREAS_TABLE_PTR in Bank 3. ;
[dc93]STA Temp_Addr_L [dc95]LDA a:AREAS_TABLE_PTR+1 [dc98]CLC [dc99]ADC #$80 [dc9b]STA Temp_Addr_U [dc9d]LDA Area_CurrentArea [dc9f]ASL A [dca0]TAY [dca1]LDA (Temp_Addr_L),Y [dca3]STA CurrentArea_TableAddr [dca5]INY [dca6]LDA (Temp_Addr_L),Y [dca8]CLC [dca9]ADC #$80 [dcab]STA CurrentArea_TableAddr_U [dcad]LDY #$00 [dcaf]LDA (CurrentArea_TableAddr),Y [dcb1]STA Temp_Addr_L [dcb3]INY [dcb4]LDA (CurrentArea_TableAddr),Y [dcb6]CLC [dcb7]ADC #$80 [dcb9]STA Temp_Addr_U
; ; Store the addresses of the block attributes and 4 groups of ; block data in addresses {@address 007E}. ;
[dcbb]LDY #$00 [dcbd]LDX #$05 [dcbf]@LAB_PRG15_MIRROR__dcbf:; [$dcbf] [dcbf]LDA (Temp_Addr_L),Y [dcc1]STA CurrentArea_BlockAttributesAddr,Y [dcc4]INY [dcc5]LDA (Temp_Addr_L),Y [dcc7]CLC [dcc8]ADC #$80 [dcca]STA CurrentArea_BlockAttributesAddr,Y [dccd]INY [dcce]DEX [dccf]BNE @LAB_PRG15_MIRROR__dcbf [dcd1]LDY #$02
; ; Load the block properties, scrolling data, door locations, ; and door destinations addresses. ;
[dcd3]LDA (CurrentArea_TableAddr),Y [dcd5]STA CurrentArea_BlockPropertiesAddr [dcd7]INY [dcd8]LDA (CurrentArea_TableAddr),Y [dcda]CLC [dcdb]ADC #$80 [dcdd]STA CurrentArea_BlockPropertiesAddr_U [dcdf]LDY #$04 [dce1]LDA (CurrentArea_TableAddr),Y [dce3]STA CurrentArea_ScrollingDataAddr [dce5]INY [dce6]LDA (CurrentArea_TableAddr),Y [dce8]CLC [dce9]ADC #$80 [dceb]STA ScrollingData_U [dced]LDY #$06 [dcef]LDA (CurrentArea_TableAddr),Y [dcf1]STA CurrentArea_DoorLocationsAddr [dcf3]INY [dcf4]LDA (CurrentArea_TableAddr),Y [dcf6]CLC [dcf7]ADC #$80 [dcf9]STA CurrentArea_DoorLocationsAddr_U [dcfb]LDY #$08 [dcfd]LDA (CurrentArea_TableAddr),Y [dcff]STA CurrentArea_DoorDestinationsAddr [dd01]INY [dd02]LDA (CurrentArea_TableAddr),Y [dd04]CLC [dd05]ADC #$80 [dd07]STA CurrentArea_DoorDestinationsAddr_U
; ; Switch back to the previous bank. ;
[dd09]PLA [dd0a]TAX [dd0b]JSR MMC1_UpdateROMBank [dd0e]RTS
;============================================================================ ; Update the scroll state during a mainloop iteration. ; ; INPUTS: ; None. ; ; OUTPUTS: ; None. ; ; CALLS: ; Screen_UpdateForScroll ; ; XREFS: ; Game_MainLoop ;============================================================================
[dd0f]Game_UpdateForScroll:; [$dd0f] [dd0f]JSR Screen_UpdateForScroll [dd12]RTS
;============================================================================ ; Set up state for a new screen. ; ; This is called when loading a new screen or entering a ; building. It's responsible for the following: ; ; * Setting the player sprite position and drawing it. ; * Loading common sprites. ; * Setting area/screen state (palette, positions, ; scrolling). ; * Drawing the HUD. ; ; INPUTS: ; Area_LoadingScreenIndex: ; The screen index being loaded in the area. ; ; Screen_DestPaletteOrIndex: ; The screen palette being loaded. ; ; Screen_StartPosYX: ; The starting position for the player. ; ; OUTPUTS: ; Area_CurrentScreen: ; The loaded screen index. ; ; Player_Something_ScrollPosY: ; The player's Y scroll position, set to 0. ; ; Player_PosX_Block: ; Player_PosY: ; The starting position for the player on the ; screen, based on Screen_StartPosYX. ; ; PPU_ScrollScreenHoriz: ; PPU_ScrollScreenVert: ; The scroll screen, set to 0. ; ; Screen_PaletteIndex: ; The loaded palette. ; ; Screen_Maybe_ScrollXCounter: ; The horizontal scroll counter, set to 0. ; ; CALLS: ; Area_ScrollScreenRight ; Player_DrawSpriteImmediately ; Screen_LoadUIPalette ; Sprites_LoadCommon ; UI_DrawHUDSprites ; UI_SetHUDPPUAttributes ; ; XREFS: ; Game_EnterBuilding ; Screen_Load ;============================================================================
[dd13]Screen_SetupNew:; [$dd13] [dd13]JSR Player_DrawSpriteImmediately; Draw the player sprite. [dd16]JSR Sprites_LoadCommon [dd19]LDA Area_LoadingScreenIndex [dd1b]STA Area_CurrentScreen [dd1d]JSR Area_ScrollScreenRight [dd20]JSR UI_DrawHUDSprites [dd23]LDA Screen_DestPaletteOrIndex [dd25]STA a:Screen_PaletteIndex [dd28]JSR Screen_LoadUIPalette [dd2b]LDA #$00 [dd2d]STA PPU_ScrollScreenHoriz [dd2f]STA PPU_ScrollScreenVert [dd31]STA Screen_Maybe_ScrollXCounter [dd33]STA Player_Something_ScrollPosY [dd35]LDA Screen_StartPosYX [dd37]AND #$f0 [dd39]STA Player_PosY [dd3b]LDA Screen_StartPosYX [dd3d]ASL A [dd3e]ASL A [dd3f]ASL A [dd40]ASL A [dd41]STA Player_PosX_Block [dd43]JMP UI_SetHUDPPUAttributes
;============================================================================ ; Load the current screen. ; ; This will set up the palette, reset sprites, load ; metadata for the screen (sprites, values, and special ; events), and prepare for rendering. ; ; INPUTS: ; Screen_ReadyState: ; The current loaded state. ; ; OUTPUTS: ; Screen_ReadyState: ; The new loaded state (0). ; ; CALLS: ; Screen_SetupNew ; Screen_ClearSprites ; Screen_LoadSpriteInfo ; Screen_LoadSpritePalette ; PPUBuffer_WritePalette ; ; XREFS: ; Game_EnterAreaHandler ; Game_ExitBuilding ; Game_LoadCurrentArea ; Game_LoadFirstLevel ; Game_SetupEnterScreen ;============================================================================
[dd46]Screen_Load:; [$dd46] [dd46]JSR Screen_SetupNew; Draw the HUD and prepare setup for the screen. [dd49]LDA #$00 [dd4b]JSR Screen_LoadSpritePalette; Load the palette.
; ; v-- Fall through --v ;
;============================================================================ ; Set up sprites and palette for the screen. ; ; This will clear out the existing sprites and then load ; in new ones. ; ; After load, the screen will be set as ready and the ; palette written. ; ; INPUTS: ; Screen_ReadyState: ; The screen's current ready state. ; ; OUTPUTS: ; Screen_ReadyState: ; The new state, set to ready. ; ; CALLS: ; Screen_ClearSprites ; Screen_LoadSpriteInfo ; PPUBuffer_WritePalette ; ; XREFS: ; Game_EnterBuilding ;============================================================================
[dd4e]Screen_SetupSprites:; [$dd4e]
; ; Clear all the sprite data for the screen and load in ; new sprites. ; ; Note that this code checks Screen_ReadyState ; and only loads if it's not 1, but in the shipped game the ; only values are 0 and 0xFF, so the path always runs. ;
[dd4e]JSR Screen_ClearSprites; Clear current sprite data. [dd51]LDA Screen_ReadyState; Get the screen state. [dd53]CMP #$01; Is it 1? (DEADCODE: It never will be). [dd55]BEQ @_setReady; If not, skip loading sprites.
; ; Load sprite information. Despite the conditional, this ; will always execute, since the screen state is never 1 ; in the shipped again. ;
[dd57]JSR Screen_LoadSpriteInfo; Load information for the screen.
; ; Mark the screen as ready, and write the palette. ;
[dd5a]@_setReady:; [$dd5a] [dd5a]LDA #$00 [dd5c]STA Screen_ReadyState; Set the state to ready. [dd5e]JMP PPUBuffer_WritePalette; Write the palette to the PPU buffer.
;============================================================================ ; Spawn the player in a temple. ; ; This is called when starting up a new life for the player. ; The player will be spawned in a temple, placed in the ; specified area and screen, using the correct palette and ; player positions. ; ; INPUTS: ; TempleSpawnPoint: ; The index of the template in which the player will ; be spawned. ; ; OUTPUTS: ; Area_CurrentScreen: ; The starting screen outside the template. ; ; Area_Region: ; The area in which the player will be placed outside ; the template. ; ; Player_PosX_Block: ; The X position the player will be in outside the ; temple. ; ; Player_PosY: ; The Y position the player will be in outside the ; temple. ; ; Area_DestScreen: ; TODO ; ; Area_CurrentArea: ; TODO ; ; Screen_PaletteIndex: ; TODO: The index of the palette outside the temple? ; ; Area_Music_Outside: ; The music to play outside the temple. ; ; Screen_DestPaletteOrIndex: ; The palette inside the temple. ; ; Building_TilesIndex: ; Index for the building tiles. ; ; Screen_StartPosYX: ; The start position inside the temple. ; ; Areas_DefaultMusic: ; The music to play inside the temple. ; ; Area_LoadingScreenIndex: ; TODO ; ; Player_Flags: ; The new player flags (set to face left inside ; the temple). ; ; XREFS: ; Player_Spawn ;============================================================================
[dd61]Game_SpawnInTemple:; [$dd61]
; ; Set the starting area/screen/position information outside the ; temple. ;
[dd61]LDX a:TempleSpawnPoint; X = index of the temple spawn point. [dd64]LDA START_SCREEN_FOR_TEMPLE_SPAWN,X; Set the current screen for outside the temple. [dd67]STA Area_CurrentScreen [dd69]LDA START_MAYBE_NEXT_AREA_FOR_TEMPLE_SPAWN,X [dd6c]STA a:Area_Region [dd6f]LDA START_PLAYERPOSX_FULL_FOR_TEMPLE_SPAWN,X [dd72]STA Player_PosX_Block [dd74]LDA MAYBE_START_PLAYERPOSY_FOR_TEMPLE_SPAWN,X [dd77]STA Player_PosY [dd79]LDA DEST_SCREEN_FOR_TEMPLE_SPAWN,X [dd7c]STA a:Area_DestScreen [dd7f]LDY CURRENT_REGION_FOR_TEMPLE_SPAWN,X [dd82]STY Area_CurrentArea [dd84]LDA AREA_PALETTE_INDEXES,Y [dd87]STA a:Screen_PaletteIndex [dd8a]LDA AREA_TO_MUSIC_TABLE,Y [dd8d]STA a:Area_Music_Outside [dd90]LDA #$12 [dd92]STA Screen_DestPaletteOrIndex [dd94]LDA #$06 [dd96]STA a:Building_TilesIndex [dd99]LDA #$9e [dd9b]STA Screen_StartPosYX [dd9d]LDA #$0e [dd9f]STA a:Areas_DefaultMusic [dda2]LDA #$01 [dda4]STA Area_LoadingScreenIndex
; ; Set the player to face left inside the temple. ;
[dda6]LDA Player_Flags [dda8]AND #$bf [ddaa]STA Player_Flags [ddac]RTS
;============================================================================ ; A mapping of Temple spawn positions to regions. ; ; XREFS: ; Game_SpawnInTemple ;============================================================================
; ; XREFS: ; Game_SpawnInTemple ;
[ddad]CURRENT_REGION_FOR_TEMPLE_SPAWN:; [$ddad] [ddad].byte REGION_EOLIS; [0]: First town [ddae].byte REGION_BRANCH; [1]: Apolune [ddaf].byte REGION_BRANCH; [2]: Forepaw [ddb0].byte REGION_MIST; [3]: Fog [ddb1].byte REGION_BRANCH; [4]: Victim [ddb2].byte REGION_BRANCH; [5]: Conflate [ddb3].byte REGION_BRANCH; [6]: Daybreak [ddb4].byte REGION_BRANCH; [7]: Final town
;============================================================================ ; A mapping of Temple spawn positions to outside X positions. ; ; XREFS: ; Game_SpawnInTemple ;============================================================================
; ; XREFS: ; Game_SpawnInTemple ;
[ddb5]START_PLAYERPOSX_FULL_FOR_TEMPLE_SPAWN:; [$ddb5] [ddb5].byte $50; [0]: First town [ddb6].byte $50; [1]: Apolune [ddb7].byte $30; [2]: Forepaw [ddb8].byte $90; [3]: Fog [ddb9].byte $30; [4]: Victim [ddba].byte $90; [5]: Conflate [ddbb].byte $60; [6]: Daybreak [ddbc].byte $30; [7]: Final town
;============================================================================ ; A mapping of Temple spawn positions to outside Y positions. ; ; XREFS: ; Game_SpawnInTemple ;============================================================================
; ; XREFS: ; Game_SpawnInTemple ;
[ddbd]MAYBE_START_PLAYERPOSY_FOR_TEMPLE_SPAWN:; [$ddbd] [ddbd].byte $90; [0]: First town [ddbe].byte $90; [1]: Apolune [ddbf].byte $90; [2]: Forepaw [ddc0].byte $80; [3]: Fog [ddc1].byte $90; [4]: Victim [ddc2].byte $90; [5]: Conflate [ddc3].byte $90; [6]: Daybreak [ddc4].byte $90; [7]: Final town
;============================================================================ ; TODO: A mapping of Temple spawn positions to <TODO>. ; ; XREFS: ; Game_SpawnInTemple ;============================================================================
; ; XREFS: ; Game_SpawnInTemple ;
[ddc5]DEST_SCREEN_FOR_TEMPLE_SPAWN:; [$ddc5] [ddc5].byte $02; [0]: First town [ddc6].byte $0b; [1]: Apolune [ddc7].byte $10; [2]: Forepaw [ddc8].byte $1e; [3]: Fog [ddc9].byte $23; [4]: Victim [ddca].byte $2b; [5]: Conflate [ddcb].byte $33; [6]: Daybreak [ddcc].byte $3c; [7]: Final town
;============================================================================ ; A mapping of Temple spawn positions to next areas. ; ; XREFS: ; Game_SpawnInTemple ;============================================================================
; ; XREFS: ; Game_SpawnInTemple ;
[ddcd]START_MAYBE_NEXT_AREA_FOR_TEMPLE_SPAWN:; [$ddcd] [ddcd].byte REGION_EOLIS; [0]: First town [ddce].byte REGION_TRUNK; [1]: Apolune [ddcf].byte REGION_TRUNK; [2]: Forepaw [ddd0].byte REGION_MIST; [3]: Fog [ddd1].byte REGION_MIST; [4]: Victim [ddd2].byte REGION_BRANCH; [5]: Conflate [ddd3].byte REGION_BRANCH; [6]: Daybreak [ddd4].byte REGION_DARTMOOR; [7]: Final town
;============================================================================ ; A mapping of Temple spawn positions to start screens. ; ; XREFS: ; Game_SpawnInTemple ;============================================================================
; ; XREFS: ; Game_SpawnInTemple ;
[ddd5]START_SCREEN_FOR_TEMPLE_SPAWN:; [$ddd5] [ddd5].byte $02; [0]: First town [ddd6].byte $01; [1]: Apolune [ddd7].byte $03; [2]: Forepaw [ddd8].byte $06; [3]: Fog [ddd9].byte $06; [4]: Victim [ddda].byte $08; [5]: Conflate [dddb].byte $0b; [6]: Daybreak [dddc].byte $0c; [7]: Final town
;============================================================================ ; Move the player to the King's Room for the end-game. ; ; This sets the state to load the King's Room and places ; the player in the correct place. The Temple music will ; be played while in the room. ; ; INPUTS: ; Player_Flags: ; The player's flags. ; ; OUTPUTS: ; Areas_DefaultMusic: ; The music to play for the area (Temple music). ; ; Area_CurrentScreen: ; Area_DestScreen: ; The screen to load (68). ; ; Area_LoadingScreenIndex: ; The new loading screen index. ; ; Screen_DestPaletteOrIndex: ; The palette for the room. ; ; Area_Region: ; The updated region (Eolis). ; ; Screen_StartPosYX: ; The starting X/Y position (X=13, Y=9). ; ; Player_Flags: ; The updated player's flags. ; ; Building_TilesIndex: ; The tiles for the room. ; ; CALLS: ; Game_SetupEnterBuilding ; ; XREFS: ; EndGame_BeginKingsRoomSequence ;============================================================================
[dddd]EndGame_MoveToKingsRoom:; [$dddd]
; ; Move the player back to Eolis. ;
[dddd]LDA #$00 [dddf]STA Area_LoadingScreenIndex; Set the screen index to 0. [dde1]STA a:Area_Region; Set the region to Eolis.
; ; Play the Temple music. ;
[dde4]LDA #$0d [dde6]STA a:Areas_DefaultMusic; Play the Temple music.
; ; Move the player to the right of the room. ;
[dde9]LDA #$9d [ddeb]STA Screen_StartPosYX; Set the start position to X=13, Y=9.
; ; Load the palette for the King's room. ;
[dded]LDA #$11 [ddef]STA Screen_DestPaletteOrIndex; Set the Palette for the King's room.
; ; Set the King's Room screen. ;
[ddf1]LDA #$44 [ddf3]STA Area_CurrentScreen; Set the screen to 68. [ddf5]STA a:Area_DestScreen [ddf8]LDA #$06 [ddfa]STA a:Building_TilesIndex; Set the tiles to 6.
; ; Face the player to the left. ;
[ddfd]LDA Player_Flags; Load the player flags. [ddff]AND #$bf; Set to facing left. [de01]STA Player_Flags; Store it. [de03]JMP Game_SetupEnterBuilding; Trigger the Enter Building logic.
;============================================================================ ; TODO: Document Game_EnterBuilding ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Game_SetupEnterBuilding ;============================================================================
[de06]Game_EnterBuilding:; [$de06]
; ; Unset the player's current weapon. Players can't attack in ; buildings. ;
[de06]LDA #$ff [de08]STA a:Player_CurWeapon; Clear the weapon.
; ; Set the music for this area. ;
[de0b]LDA a:Areas_DefaultMusic; Load the default music for the area. [de0e]STA Music_Current; Set as the current music.
; ; Push a buch of state to return to when exiting the door. ;
[de10]LDA Area_CurrentArea; Load the current area. [de12]STA a:Area_SavedArea; Save it for when exiting the building. [de15]LDA Area_CurrentScreen; Load the current screen in the area. [de17]STA a:Area_SavedScreen; Save it. [de1a]LDA a:Screen_PaletteIndex; Load the current palette. [de1d]STA a:Screen_SavedPalette; Save it.
; ; Save the player's X/Y position (rounded to the nearest ; block). ;
[de20]LDA Player_PosY; Load the player's Y position. [de22]AND #$f0; Normalize to a block offset. [de24]STA a:Player_SavedPosXY; Save it. [de27]LDA Player_PosX_Block; Load the player's block X position. [de29]AND #$f0; Normalize it to a block offset. [de2b]LSR A; Shift to the lower byte. [de2c]LSR A [de2d]LSR A [de2e]LSR A [de2f]ORA a:Player_SavedPosXY; Combine it with the saved X/Y position. [de32]STA a:Player_SavedPosXY; And save it.
; ; Set the new area to enter and load it. ;
[de35]LDX #$04 [de37]STX Area_CurrentArea; Set the current area to 4 (inside buildings) [de39]LDA AREA_TO_SCREEN_DATA_BANKS,X; Load the bank for this area. [de3c]STA CurrentArea_ROMBank; Store as the current area ROM bank. [de3e]LDX CurrentArea_ROMBank [de40]JSR MMC1_SaveROMBankAndUpdateTo; Push the current bank and switch to the new one. [de43]LDX Area_CurrentArea; Load the current area again. [de45]LDA AREA_TO_BANK_OFFSET,X; Get the index into the screen data table in this bank. [de48]JSR Game_LoadAreaTable; Load the area table for the screen. [de4b]JSR MMC1_RestorePrevROMBank; Restore the previous bank.
; ; Set the index into the tiles for this area and load them. ;
[de4e]LDA a:Building_TilesIndex [de51]STA Area_TilesIndex [de53]JSR Area_LoadTiles
; ; Load the palette. ;
[de56]LDA #$01 [de58]JSR Screen_LoadSpritePalette
; ; Set up the screen and the sprites. ;
[de5b]JSR Screen_SetupNew [de5e]LDA a:Area_DestScreen [de61]STA Area_CurrentScreen [de63]JMP Screen_SetupSprites
;============================================================================ ; TODO: Document Game_ExitBuilding ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Game_SetupExitBuilding ;============================================================================
[de66]Game_ExitBuilding:; [$de66] [de66]LDA a:SelectedWeapon [de69]STA a:Player_CurWeapon [de6c]LDA a:Areas_DefaultMusic [de6f]STA Music_Current [de71]LDX a:Area_SavedArea [de74]STX Area_CurrentArea [de76]LDA AREA_TO_SCREEN_DATA_BANKS,X [de79]STA CurrentArea_ROMBank [de7b]LDX CurrentArea_ROMBank [de7d]JSR MMC1_SaveROMBankAndUpdateTo [de80]LDX Area_CurrentArea [de82]LDA AREA_TO_BANK_OFFSET,X [de85]JSR Game_LoadAreaTable [de88]JSR MMC1_RestorePrevROMBank [de8b]LDX Area_CurrentArea [de8d]LDA CURRENT_AREA_TO_TILES_INDEX_TABLE,X [de90]STA Area_TilesIndex [de92]JSR Area_LoadTiles [de95]LDA a:Area_SavedScreen [de98]STA Area_LoadingScreenIndex [de9a]LDA a:Screen_SavedPalette [de9d]STA Screen_DestPaletteOrIndex [de9f]LDA a:Player_SavedPosXY [dea2]STA Screen_StartPosYX [dea4]JMP Screen_Load
;============================================================================ ; Load the very first level. ; ; This will load the gates to Eolis, loading in all the ; area data, screen, tile data, and music. ; ; It also takes care of setting the player's initial HP. ; ; INPUTS: ; AREA_TO_SCREEN_DATA_BANKS: ; Mapping of areas to PRG ROM banks. ; ; AREA_TO_BANK_OFFSET: ; Mapping of areas to sprite banks. ; ; CURRENT_AREA_TO_TILES_INDEX_TABLE: ; Mapping of areas to tile indexes. ; ; AREA_TO_MUSIC_TABLE: ; Mapping of areas to music. ; ; OUTPUTS: ; Area_CurrentArea: ; The current area (Eolis). ; ; Area_CurrentScreen: ; The current screen. ; ; Screen_DestPaletteOrIndex: ; The palette for the area. ; ; Area_LoadingScreenIndex: ; The current screen index. ; ; Screen_StartPosYX: ; The starting X/Y position. ; ; Areas_DefaultMusic: ; The default music for the area. ; ; CurrentROMBank: ; The starting ROM bank. ; ; Music_Current: ; The current music to play. ; ; Player_HP_U: ; The player's starting HP (16). ; ; Area_TilesIndex: ; The tiles index for the area. ; ; CALLS: ; Area_LoadTiles ; Screen_Load ; MMC1_RestorePrevROMBank ; MMC1_SaveROMBankAndUpdateTo ; Screen_LoadSpritePalette ; Player_SetInitialState ; ; XREFS: ; Game_Start ;============================================================================
[dea7]Game_LoadFirstLevel:; [$dea7] [dea7]LDA #$00; A = 0
; ; Set this as the current town. ;
[dea9]STA Area_CurrentArea; Set the current area to 0 (Eolis).
; ; Set the initial state of the player, and hard-code their ; health to the starting amount of 16HP. ;
[deab]JSR Player_SetInitialState; Set the initial player state. [deae]LDA #$10 [deb0]STA a:Player_HP_U; Set the HP to 16. [deb3]LDX Area_CurrentArea; X = Current area
; ; Load the block properties for this area. ; ; The bank containing the properties for the area will ; be loaded temporarily while fetching the block properties. ;
[deb5]LDA AREA_TO_SCREEN_DATA_BANKS,X; A = PRG bank for the area. [deb8]STA CurrentArea_ROMBank; Store as the current bank. [deba]LDX CurrentArea_ROMBank; X = current bank. [debc]JSR MMC1_SaveROMBankAndUpdateTo; Push the current bank and update to this area's bank.
; ; Load the area information. ; ; NOTE: There's a bug here. This *should* be passing in the ; area index, but it's mistakenly passing in the resulting ; ROM bank. In the shipped game, this is 0 in both cases, ; but it means that any shuffling around of banks will do ; the wrong thing if not starting with area 0, bank 0. ;
[debf]LDA AREA_TO_BANK_OFFSET,X; A = sprite bank for the area. [dec2]JSR Game_LoadAreaTable; Load the area information. [dec5]JSR MMC1_RestorePrevROMBank; Restore the previous bank.
; ; Start on the current screen. ;
[dec8]LDX Area_CurrentArea; X = current area. [deca]LDA #$00 [decc]STA Area_CurrentScreen; Current screen = 0 [dece]STA Area_LoadingScreenIndex; Screen index = 0
; ; Load the tiles and images for the area. ;
[ded0]LDX Area_CurrentArea; X = current area. [ded2]LDA CURRENT_AREA_TO_TILES_INDEX_TABLE,X; A = tiles index. [ded5]STA Area_TilesIndex; Store that. [ded7]JSR Area_LoadTiles; Load the tileset pages for that index.
; ; Load the palette for the area. ;
[deda]LDA #$00 [dedc]JSR Screen_LoadSpritePalette; Load the palette for that index. [dedf]LDX Area_CurrentArea; X = current area. [dee1]LDA AREA_PALETTE_INDEXES,X; Load the palette. [dee4]STA Screen_DestPaletteOrIndex; And store it as the current one for the area.
; ; Load the music for this area. ;
[dee6]LDA AREA_TO_MUSIC_TABLE,X; Load the music for the area. [dee9]STA Music_Current; And store it as the current music. [deeb]STA a:Areas_DefaultMusic; And as the default for the area.
; ; Set the start X/Y position for the player as X=1, Y=9. ;
[deee]LDA #$91 [def0]STA Screen_StartPosYX; Set start position. [def2]JMP Screen_Load; Load this screen.
;============================================================================ ; Load the information on the current area. ; ; This will load the area data, tile data, palette, and ; music. ; ; INPUTS: ; AREA_TO_SCREEN_DATA_BANKS: ; Mapping of areas to PRG ROM banks. ; ; AREA_TO_BANK_OFFSET: ; Mapping of areas to sprite banks. ; ; CURRENT_AREA_TO_TILES_INDEX_TABLE: ; Mapping of areas to tile indexes. ; ; AREA_TO_MUSIC_TABLE: ; Mapping of areas to music. ; ; OUTPUTS: ; Area_CurrentScreen: ; The current screen. ; ; Screen_DestPaletteOrIndex: ; The palette for the area. ; ; Areas_DefaultMusic: ; The default music for the area. ; ; CurrentROMBank: ; The starting ROM bank. ; ; Music_Current: ; The current music to play. ; ; Area_TilesIndex: ; The tiles index for the area. ; ; CALLS: ; Area_LoadTiles ; Screen_Load ; MMC1_RestorePrevROMBank ; MMC1_SaveROMBankAndUpdateTo ; Screen_LoadSpritePalette ; Player_SetInitialState ; ; XREFS: ; Game_SetupAndLoadOutsideArea ;============================================================================
[def5]Game_LoadCurrentArea:; [$def5] [def5]JSR Player_SetInitialState; Set the initial player state.
; ; Load the bank for this area. ;
[def8]LDX Area_CurrentArea; X = current area. [defa]LDA AREA_TO_SCREEN_DATA_BANKS,X; Load the ROM bank for the area. [defd]STA CurrentArea_ROMBank; Store that as the current bank. [deff]LDX CurrentArea_ROMBank; X = current bank. [df01]JSR MMC1_SaveROMBankAndUpdateTo; Save the current bank and switch to it.
; ; Load the bank for the sprites for this area. ;
[df04]LDX Area_CurrentArea; X = current area. [df06]LDA AREA_TO_BANK_OFFSET,X; A = bank for the sprites.
; ; Load information on the area. ;
[df09]JSR Game_LoadAreaTable; Load the table of information on this area. [df0c]JSR MMC1_RestorePrevROMBank; Restore to the previous bank. [df0f]LDA Area_LoadingScreenIndex; A = current screen index. [df11]STA Area_CurrentScreen; Store as the current screen.
; ; Load the tiles for the area. ;
[df13]LDX Area_CurrentArea; X = current area. [df15]LDA CURRENT_AREA_TO_TILES_INDEX_TABLE,X; A = tiles index for the area. [df18]STA Area_TilesIndex; Store as the current tiles index. [df1a]JSR Area_LoadTiles; Load all tileset images for that index.
; ; Load the palette for the area. ;
[df1d]LDA #$00 [df1f]JSR Screen_LoadSpritePalette; Load palette 0. [df22]LDX Area_CurrentArea; X = current area. [df24]LDA AREA_PALETTE_INDEXES,X; Load the palette for the area. [df27]STA Screen_DestPaletteOrIndex; Store as the current palette.
; ; Load the music for the area. ;
[df29]LDA AREA_TO_MUSIC_TABLE,X; Load the music for the area. [df2c]STA Music_Current; Set as the current music. [df2e]STA a:Areas_DefaultMusic; Store as the default. [df31]JMP Screen_Load; Load this screen.
;============================================================================ ; A mapping of areas to the ROM banks containing block/area data. ; ; XREFS: ; Game_EnterAreaHandler ; Game_ExitBuilding ; Game_LoadCurrentArea ; Game_LoadFirstLevel ;============================================================================
; ; XREFS: ; Game_EnterAreaHandler ; Game_ExitBuilding ; Game_LoadCurrentArea ; Game_LoadFirstLevel ;
[df34]AREA_TO_SCREEN_DATA_BANKS:; [$df34] [df34].byte BANK_0_AREA_DATA; [0]: Eolis [df35].byte BANK_1_AREA_DATA; [1]: Apolune [df36].byte BANK_0_AREA_DATA; [2]: Forepaw [df37].byte BANK_0_AREA_DATA; [3]: Mascon
; ; XREFS: ; Game_EnterBuilding ;
[df38]AREA_TO_SCREEN_DATA_BANKS_4_:; [$df38] [df38].byte BANK_2_AREA_DATA; [4]: Victim [df39].byte BANK_1_AREA_DATA; [5]: Conflate [df3a].byte BANK_2_AREA_DATA; [6]: Daybreak [df3b].byte BANK_2_AREA_DATA; [7]: Evil Fortress
; ; XREFS: ; Game_EnterAreaHandler ; Game_ExitBuilding ; Game_LoadCurrentArea ; Game_LoadFirstLevel ;
[df3c]AREA_TO_BANK_OFFSET:; [$df3c] [df3c].byte BANK_0_AREA_DATA; [0]: Eolis [df3d].byte BANK_0_AREA_DATA; [1]: Apolune [df3e].byte BANK_1_AREA_DATA; [2]: Forepaw [df3f].byte BANK_2_AREA_DATA; [3]: Mascon
; ; XREFS: ; Game_EnterBuilding ;
[df40]AREA_TO_BANK_OFFSET_4_:; [$df40] [df40].byte BANK_1_AREA_DATA; [4]: Victim [df41].byte BANK_1_AREA_DATA; [5]: Conflate [df42].byte BANK_0_AREA_DATA; [6]: Daybreak [df43].byte BANK_2_AREA_DATA; [7]: Evil Fortress
;============================================================================ ; Pointers into 0xCF07 addressed by chunk ID ; ; XREFS: ; Game_EnterAreaHandler ; Game_ExitBuilding ; Game_LoadCurrentArea ; Game_LoadFirstLevel ;============================================================================
; ; XREFS: ; Game_EnterAreaHandler ; Game_ExitBuilding ; Game_LoadCurrentArea ; Game_LoadFirstLevel ;
[df44]CURRENT_AREA_TO_TILES_INDEX_TABLE:; [$df44] [df44].byte $00; [0]: Eolis [df45].byte $02; [1]: First town [df46].byte $03; [2]: [df47].byte $05; [3]: [df48].byte $06; [4]: [df49].byte $01; [5]: [df4a].byte $04; [6]: [df4b].byte $04; [7]: Final boss room
;============================================================================ ; Mapping of areas to palette indexes. ; ; XREFS: ; Game_LoadCurrentArea ; Game_LoadFirstLevel ; Game_SpawnInTemple ;============================================================================
; ; XREFS: ; Game_LoadCurrentArea ; Game_LoadFirstLevel ; Game_SpawnInTemple ;
[df4c]AREA_PALETTE_INDEXES:; [$df4c] [df4c].byte PALETTE_EOLIS; [0]: Eolis [df4d].byte PALETTE_OUTSIDE; [1]: [df4e].byte PALETTE_MIST; [2]: [df4f].byte PALETTE_TOWN; [3]: [df50].byte PALETTE_TOWN; [4]: [df51].byte PALETTE_BRANCH; [5]: [df52].byte PALETTE_DARTMOOR; [6]: [df53].byte PALETTE_EVIL_FORTRESS; [7]: [df54].byte $00,$00,$00,$00,$00,$00,$00,$00; [$df54] undefined
; ; XREFS: ; Game_EnterAreaHandler ; Game_LoadCurrentArea ; Game_LoadFirstLevel ; Game_SpawnInTemple ;
[df5c]AREA_TO_MUSIC_TABLE:; [$df5c] [df5c].byte MUSIC_EOLIS; [0]: Eolis [df5d].byte MUSIC_APOLUNE; [1]: Apolune [df5e].byte MUSIC_FOREPAW; [2]: Forepaw [df5f].byte MUSIC_MASCON_VICTIM; [3]: Mascon [df60].byte MUSIC_MASCON_VICTIM; [4]: Victim [df61].byte MUSIC_CONFLATE; [5]: Conflate [df62].byte MUSIC_DAYBREAK; [6]: Daybreak [df63].byte MUSIC_EVIL_FORTRESS; [7]: Evil Fortress
;============================================================================ ; TODO: Document Game_EnterAreaHandler ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Game_SetupNewArea ;============================================================================
[df64]Game_EnterAreaHandler:; [$df64] [df64]LDX Area_CurrentArea [df66]LDA AREA_TO_SCREEN_DATA_BANKS,X [df69]STA CurrentArea_ROMBank [df6b]LDX CurrentArea_ROMBank [df6d]JSR MMC1_SaveROMBankAndUpdateTo [df70]LDX Area_CurrentArea [df72]LDA AREA_TO_BANK_OFFSET,X [df75]JSR Game_LoadAreaTable [df78]JSR MMC1_RestorePrevROMBank [df7b]LDA Area_LoadingScreenIndex [df7d]STA Area_CurrentScreen [df7f]LDX Area_CurrentArea [df81]LDA AREA_TO_MUSIC_TABLE,X [df84]STA Music_Current [df86]STA a:Areas_DefaultMusic [df89]LDA CURRENT_AREA_TO_TILES_INDEX_TABLE,X [df8c]STA Area_TilesIndex [df8e]JSR Area_LoadTiles [df91]LDA #$00 [df93]JSR Screen_LoadSpritePalette [df96]JMP Screen_Load
;============================================================================ ; Debug code to switch the current area region. ; ; This allows the player to control which part of the game ; they're in. ; ; By pressing Up on the second controller, the player will ; be moved up a region. ; ; By pressing Down, they player will be moved down a region. ; ; This isn't activated by default, but can be swapped in ; through the following Game Genie codes (overriding the ; ability to pause): ; ; OPYISU ; NIYIVU ; ; INPUTS: ; Joy2_ButtonMask: ; The second controller's button mask. ; ; OUTPUTS: ; Area_Region: ; The updated region. ; ; Area_CurrentScreen: ; Set to 0. ; ; CALLS: ; Game_SetupAndLoadOutsideArea ;============================================================================
[df99]Debug_ChooseArea:; [$df99] [df99]LDA Joy2_ButtonMask; Load the player 2 controller buttons. [df9b]AND #$0c; Is Up or Down pressed? [df9d]BEQ RETURN_DFDD; If not, return.
; ; Figure out which region to switch to based on button press. ;
[df9f]AND #$04; Is Down pressed? [dfa1]BNE @_nextArea; If so, switch to the next area.
; ; Up was pressed. Go forward a region. ;
[dfa3]INC a:Area_Region; Increment the region. [dfa6]LDA a:Area_Region; A = incremented region. [dfa9]CMP #$06; Is it in bounds? [dfab]BCC @_switchArea; If so, switch to the area. [dfad]LDA #$00; Else... [dfaf]STA a:Area_Region; Set the region to (Eolis). [dfb2]BEQ @_switchArea; And switch to it.
; ; Down was pressed. Go back a region. ;
[dfb4]@_nextArea:; [$dfb4] [dfb4]DEC a:Area_Region; Decrement the region. [dfb7]BPL @_switchArea; If it's in bounds, switch to the previous area. [dfb9]LDA #$05; Else, set the region to the final fortress. [dfbb]STA a:Area_Region; Store as the new region.
; ; Set the current screen to 0 and load it. ;
[dfbe]@_switchArea:; [$dfbe] [dfbe]LDA #$00 [dfc0]STA Area_CurrentScreen; Set current screen to 0. [dfc2]JMP Game_SetupAndLoadOutsideArea; Load the area.
;============================================================================ ; Update the fog state on tick. ; ; This will first check if the area supports fog (Forepaw ; area with the Mist palette). ; ; If fog is allowed, it will proceed to animate fog ; every odd tick and update fog state every even tick. ; ; INPUTS: ; Area_CurrentArea: ; The current area. ; ; Screen_PaletteIndex: ; The screen's palette. ; ; Fog_StateIndex: ; The current fog index. ; ; Fog_TileIndex: ; The current fog generator value. ; ; OUTPUTS: ; Fog_StateIndex: ; The updated fog index. ; ; Fog_TileIndex: ; The updated fog generator value. ; ; CALLS: ; Fog_UpdateTiles ; ; XREFS: ; EndGame_MainLoop ; Game_MainLoop ;============================================================================
[dfc5]Fog_OnTick:; [$dfc5] [dfc5]LDA Area_CurrentArea; Load the current area. [dfc7]CMP #$02; Is it Forepaw? [dfc9]BNE RETURN_E011; If not, return. [dfcb]LDA a:Screen_PaletteIndex; Load the current palette. [dfce]CMP #$0a; Is it Mist? [dfd0]BNE RETURN_E011; If not, return. [dfd2]LDA Fog_StateIndex; Load the fog index. [dfd4]LSR A; Is it an odd value? [dfd5]BCC Fog_UpdateTiles; If so, jump to rotate tiles.
; ; Decrement the fog generator and check if it reached 0. ; If so, increment the fog index. ;
[dfd7]DEC Fog_TileIndex; Decrement the fog generator. [dfd9]BNE RETURN_E011; If != 0, return. [dfdb]INC Fog_StateIndex; Else, increment the fog index.
; ; XREFS: ; Debug_ChooseArea ;
[dfdd]RETURN_DFDD:; [$dfdd] [dfdd]RTS
;============================================================================ ; Animate a fog tile and update fog generation state. ; ; This will take a fog tile represented by the current ; fog generation value and rotate each row of its contents ; one pixel to the right, wrapping around to the left. ; ; The fog generation value is then incremented by 2, ; wrapping around from 7 to 0. If it hits 0, the fog ; index is inremented and a new starting fog generation ; value is chosen based on that index. ; ; INPUTS: ; Fog_TileIndex: ; The current fog generator value. ; ; Fog_StateIndex: ; The current fog index value. ; ; PPUBuffer_WriteOffset: ; The current upper bounds of the PPU buffer. ; ; OUTPUTS: ; Fog_TileIndex: ; The updated fog generator value. ; ; Fog_StateIndex: ; The updated fog index value. ; ; PPUBuffer: ; The updated PPU buffer, with the rotate-right ; command scheduled. ; ; PPUBuffer_WriteOffset: ; The updated upper bounds of the PPU buffer. ; ; ; XREFS: ; Fog_OnTick ;============================================================================
[dfde]Fog_UpdateTiles:; [$dfde] [dfde]LDX PPUBuffer_WriteOffset; Load the upper bounds of the fog generator.
; ; Queue drawing command "Rotate tiles right." ;
[dfe0]LDA #$fc; 0xFC == Rotate Tiles Right draw command. [dfe2]STA PPUBuffer,X; Set as the command.
; ; Set the upper byte of the tile address to 0x18. ;
[dfe5]INX; X++ [dfe6]LDA #$18; 0x18 == Upper byte of the sprite tile address. [dfe8]STA PPUBuffer,X; Set it as the upper byte for the draw command.
; ; Calculate a lower byte for the tile based on the ; fog tile index. ; ; This will be the existing value * 16 (tile data size). ;
[dfeb]INX; X++ [dfec]LDA Fog_TileIndex; Load the fog generator value. [dfee]INC Fog_TileIndex; Increment for later (not involved in the math). [dff0]ASL A; Multiply the value we loaded by 16. [dff1]ASL A [dff2]ASL A [dff3]ASL A [dff4]STA PPUBuffer,X; Set as the lower byte for the draw command.
; ; Set the new upper bounds for the PPU buffer. ; ; This will increment by 3. ;
[dff7]INX; X++ [dff8]STX PPUBuffer_WriteOffset; Set as the new upper bounds.
; ; Update the fog tile index. ; ; It'll increment by 2 (from above and here), keeping and ; wrapping around with a value range of 0..7. ;
[dffa]INC Fog_TileIndex; Increment the fog generator value. [dffc]LDA Fog_TileIndex; Load the result. [dffe]AND #$07; Keep it in range 0..7. [e000]STA Fog_TileIndex; Store it back. [e002]BNE RETURN_E011; If != 0, we're done.
; ; The fog generator value hit 0. Increment the fog ; index and look up a new starting fog generator value. ;
[e004]INC Fog_StateIndex; Increment the fog index. [e006]LDA Fog_StateIndex; Load it. [e008]LSR A; Divide by 2. [e009]AND #$03; And convert to an index in the lookup table. [e00b]TAX; X = A [e00c]LDA FOG_START_TILE_INDEXES,X; Load a starting fog generator value at that index. [e00f]STA Fog_TileIndex; And set it.
; ; XREFS: ; Fog_OnTick ; Fog_UpdateTiles ;
[e011]RETURN_E011:; [$e011] [e011]RTS
; ; XREFS: ; Fog_UpdateTiles ;
[e012]FOG_START_TILE_INDEXES:; [$e012] [e012].byte $18; [0]: [e013].byte $06; [1]: [e014].byte $30; [2]: [e015].byte $0c; [3]:
;============================================================================ ; Check whether the Player Menu can and should be shown. ; ; The menu can be shown anywhere other than the first ; screen of the first town (where the game begins). ; ; If the menu can be shown, then it will be shown if ; the Select button has been pressed. ; ; INPUTS: ; Area_CurrentArea: ; The current area. ; ; Area_CurrentScreen: ; The current screen of the current area. ; ; Joy1_ChangedButtonMask: ; The bitmask of newly-pressed buttons. ; ; OUTPUTS: ; None ; ; CALLS: ; MMC1_LoadBankAndJump ; ; XREFS: ; EndGame_MainLoop ; Game_MainLoop ;============================================================================
[e016]GameLoop_CheckShowPlayerMenu:; [$e016]
; ; Check to make sure we're not outside the walls of Eolis. ;
[e016]LDA Area_CurrentArea; A = current area. [e018]BNE @_allowInventory; If it's > Eolis, the player can open the inventory. Jump. [e01a]LDA Area_CurrentScreen; A = current screen. [e01c]BEQ RETURN_E02A; If it's 0, then we're outside the walls. No inventory allowed. Return. [e01e]@_allowInventory:; [$e01e] [e01e]LDA Joy1_ChangedButtonMask; A = controller 1 button mask. [e020]AND #$20; Is Select pressed? [e022]BEQ RETURN_E02A; If not, return.
; ; Open the inventory via jump to PlayerMenu_Show. ;
[e024]JSR MMC1_LoadBankAndJump; Open the Player Menu. [e027].byte BANK_12_LOGIC; Bank = 12 [e028].word PlayerMenu_Show-1; Address = PlayerMenu_Show
; ; XREFS: ; GameLoop_CheckPauseGame ; GameLoop_CheckShowPlayerMenu ;
[e02a]RETURN_E02A:; [$e02a] [e02a]RTS
;============================================================================ ; Check whether the player has paused. ; ; If they have, then stay paused until the player ; unpauses. ; ; This function won't return until the player unpaused. ; While paused, Game_PausedState will be set. ; ; INPUTS: ; Joy1_ChangedButtonMask: ; The changed button mask to check. ; ; OUTPUTS: ; Game_PausedState: ; 1 while paused. 0 on return. ; ; CALLS: ; WaitForNextFrame ; Sprites_FlipRanges ; ; XREFS: ; Game_MainLoop ;============================================================================
[e02b]GameLoop_CheckPauseGame:; [$e02b]
; ; Check if the player pressed Start. ;
[e02b]LDA Joy1_ChangedButtonMask; Load the button state. [e02d]AND #$10; Check if the Pause button is pressed. [e02f]BEQ RETURN_E02A; If paused...
; ; The player paused. Set the flag and wait until unpaused. ;
[e031]LDA #$01 [e033]STA a:Game_PausedState; Set the Paused flag. [e036]@_waitForUnpause:; [$e036] [e036]JSR WaitForNextFrame; Wait... [e039]JSR Sprites_FlipRanges
; ; Check if the player unpaused. ;
[e03c]LDA Joy1_ChangedButtonMask; Check if the Pause button was pressed again. [e03e]AND #$10 [e040]BEQ @_waitForUnpause; Until pause is pressed again, loop.
; ; Unpause the game. ;
[e042]LDA #$00; Clear the Paused flag. [e044]STA a:Game_PausedState [e047]RTS
;============================================================================ ; Update player state during a screen scroll. ; ; This will preserve the controller buttons that were ; pressed at the beginning of the screen scroll, regardless ; of any changes made to those buttons since. ; ; It will then position the player based off of a scroll ; transition counter, and increment that counter. This ; counter helps coordinate at which points the player's ; sprite appears to move and where it ends up. ; ; INPUTS: ; Joy1_PrevButtonMask: ; The controller 1 buttons pressed at the beginning ; of the screen scroll. ; ; Screen_ScrollPlayerTransitionCounter: ; The counter used to control player position during ; scroll. ; ; OUTPUTS: ; Joy1_ButtonMask: ; The restored button mask. ; ; Screen_ScrollPlayerTransitionCounter: ; The updated counter. ; ; CALLS: ; Game_MovePlayerOnScroll ; ; XREFS: ; Game_MainLoop ; Screen_UpdateForScroll ;============================================================================
[e048]Game_UpdatePlayerOnScroll:; [$e048]
; ; Preserve the previous pressed buttons while scrolling. ;
[e048]LDA Joy1_PrevButtonMask; Load the previous button mask. [e04a]STA Joy1_ButtonMask; Set it as the current button mask. [e04c]JSR Game_MovePlayerOnScroll; Update the player position. [e04f]INC Screen_ScrollPlayerTransitionCounter; Increment the screen scroll transition counter. [e051]RTS
;============================================================================ ; TODO: Document Game_MovePlayerOnScrollLeft ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Game_MovePlayerOnScroll ;============================================================================
[e052]Game_MovePlayerOnScrollLeft:; [$e052]
; ; The screen is scrolling to the screen to the left. ; ; If we're in the first 4 frames of the screen transition, ; move the player left 4px per frame until it wraps the ; screen. ;
[e052]LDA Screen_ScrollPlayerTransitionCounter; Load the transition counter. [e054]CMP #$04; Is it >= 4? [e056]BCS @LAB_PRG15_MIRROR__e065; If so, jump. [e058]LDA Player_PosX_Block; Load the player X position. [e05a]SEC [e05b]SBC #$04; Subtract 4. [e05d]STA Player_PosX_Block; Store it. [e05f]LDA Screen_Maybe_ScrollXCounter; Load our screen scroll counter. [e061]SBC #$00; Subtract carry. [e063]STA Screen_Maybe_ScrollXCounter; Store it.
; ; Update Maybe_PlayerX_ForScroll based on the ; player's X position. ;
[e065]@LAB_PRG15_MIRROR__e065:; [$e065] [e065]LDA Player_PosX_Block [e067]STA Maybe_PlayerX_ForScroll [e069]RTS
;============================================================================ ; Move the player while the screen is scrolling. ; ; This will position the player on the screen based on the ; state of the screen scroll. ; ; When scrolling horizontally, it will inch the player ; toward the nearest screen boundary until it wraps. ; ; When scrolling vertically, nothing useful happens. ; ; NOTE: The function is full of useless code that's not ; needed. Conditionals and variable settings that ; never get used by anything else. ; ; Y scrolling effects aren't in Faxanadu, and the ; code managing that doesn't end up doing anything ; useful. ; ; For X scrolling, we set some variables that also ; either don't get used or don't have any true ; impact on the logic in which they're used. ; ; INPUTS: ; Screen_ScrollDirection: ; The direction in which the screen is scrolling. ; ; Screen_ScrollPlayerTransitionCounter: ; The transition counter used during screen scroll ; to help position the player. ; ; Player_PosX_Block: ; The player's current X position. ; ; OUTPUTS: ; Player_PosX_Block: ; The updated player position. ; ; Screen_Maybe_ScrollXCounter: ; An updated X value used during screen scroll. ; This is set in the first 4 frames of scrolling ; left or right, but is thought to ultimately be ; useless. ; ; Maybe_PlayerX_ForScroll: ; Maybe_PlayerY_ForScroll ; Ultimately unused values. Remnants of dead code. ; ; XREFS: ; Game_UpdatePlayerOnScroll ;============================================================================
[e06a]Game_MovePlayerOnScroll:; [$e06a]
; ; Check which direction the screen is scrolling. ;
[e06a]LDX Screen_ScrollDirection; Load the screen scroll direction. [e06c]BEQ Game_MovePlayerOnScrollLeft; If 0, jump to handle scrolling left. [e06e]DEX [e06f]BEQ @_scrollRight; If 1, jump to handle scrolling right. [e071]DEX [e072]BEQ @_scrollUp; If 2, jump to handle scrolling up.
; ; The screen is scrolling to the screen below. ;
[e074]LDA Screen_ScrollPlayerTransitionCounter; Load the transition counter. [e076]CMP #$08; Is it < 8? [e078]BCC @_return1; If so, return. [e07a]LDA Maybe_PlayerY_ForScroll; Load the player's Y position during scroll. [e07c]SEC [e07d]SBC #$04; Subtract 4 [e07f]STA Maybe_PlayerY_ForScroll; Store it. [e081]SEC [e082]@_return1:; [$e082] [e082]RTS
; ; The screen is scrolling to the screen above. ;
[e083]@_scrollUp:; [$e083] [e083]LDA Screen_ScrollPlayerTransitionCounter; Load the transition counter. [e085]CMP #$08; Is it < 8? [e087]BCC @_return2; If so, return. [e089]LDA Maybe_PlayerY_ForScroll; Load the player Y position during scroll. [e08b]CLC [e08c]ADC #$04; Add 4. [e08e]STA Maybe_PlayerY_ForScroll; Store it. [e090]SEC [e091]@_return2:; [$e091] [e091]RTS
; ; The screen is scrolling to the screen to the right. ; ; If we're in the first 4 frames of the screen transition, ; move the player right 4px per frame until it wraps the ; screen. ;
[e092]@_scrollRight:; [$e092] [e092]LDA Screen_ScrollPlayerTransitionCounter; Load the transition counter. [e094]CMP #$04; Is it >= 4? [e096]BCS @_finishScrollRight; If so, jump. [e098]LDA Player_PosX_Block; Load the player X position. [e09a]CLC [e09b]ADC #$04; Add 4. [e09d]STA Player_PosX_Block; Store it. [e09f]LDA Screen_Maybe_ScrollXCounter; Load our screen scroll counter. [e0a1]ADC #$00; Add carry. [e0a3]STA Screen_Maybe_ScrollXCounter; Store it.
; ; Update Maybe_PlayerX_ForScroll based on the ; player's X position. ;
[e0a5]@_finishScrollRight:; [$e0a5] [e0a5]LDA Player_PosX_Block [e0a7]STA Maybe_PlayerX_ForScroll [e0a9]RTS
;============================================================================ ; Set the initial state for a player on level load. ; ; This will face the player right, set their movement ; speed, status, disable invincibility, and other state. ; ; INPUTS: ; None ; ; OUTPUTS: ; Screen_Maybe_ScrollXCounter: ; TODO ; ; Player_Something_ScrollPosY: ; TODO ; ; PPU_ScrollScreenHoriz: ; TODO ; ; Player_MoveAcceleration: ; The initial player movement speed (0 in ; lower byte). ; ; Player_StatusFlag: ; The player's status (0). ; ; Player_InvincibilityPhase: ; The invincibility phase (0). ; ; Player_Flags: ; The player will be facing right. ; ; Player_DrawTileReadOffset: ; TODO (set to 0xFF). ; ; Player_Unused_00ab: ; TODO (set to 2). ; ; XREFS: ; Game_InitStateForSpawn ; Game_LoadCurrentArea ; Game_LoadFirstLevel ;============================================================================
[e0aa]Player_SetInitialState:; [$e0aa]
; ; Clear scroll state. ;
[e0aa]LDA #$00 [e0ac]STA Screen_Maybe_ScrollXCounter [e0ae]STA Player_Something_ScrollPosY [e0b0]STA PPU_ScrollScreenVert [e0b2]STA PPU_ScrollScreenHoriz; DEADCODE: Overridden below.
; ; Set the starting speed, status, and iframes. ;
[e0b4]STA Player_MoveAcceleration; Set the player speed to 0. [e0b6]STA Player_StatusFlag; Set the player status to 0. [e0b8]STA Player_InvincibilityPhase; Set the invincibility phase to 0. [e0ba]STA PPU_ScrollScreenVert; Set this again, apparently.
; ; Face the player to the right. ;
[e0bc]LDA #$40; 0x04 = Face player right bit. [e0be]STA Player_Flags; Set that on the player's flags.
; ; Set the accessory information. ;
[e0c0]LDA #$ff [e0c2]STA Player_DrawTileReadOffset
; ; Unused value. This is written to but never read from. ;
[e0c4]LDA #$02 [e0c6]STA Player_Unused_00ab [e0c8]RTS
; ; XREFS: ; GameLoop_UpdatePlayer ;
[e0c9]RETURN_E0C9:; [$e0c9] [e0c9]RTS
;============================================================================ ; Check and update state for the player. ; ; This is run on every game tick to check what the ; player is doing and to update its state. ; ; This checks for attacking, climbing, jumping, iframes, ; entering doors, falling to the ground, pushing blocks, ; and moving between screens. ; ; INPUTS: ; Screen_ScrollDirection: ; The current screen scroll direction. ; ; OUTPUTS: ; None. ; ; CALLS: ; Player_CheckHandleAttack ; Player_CheckHandleClimb ; Player_CheckHandleJump ; Player_HandleIFrames ; Player_CheckHandleEnterDoor ; Player_CheckOnBreakableBlock ; Player_CheckPushingBlock ; Player_CheckSwitchScreen ; ; XREFS: ; EndGame_MainLoop ; Game_MainLoop ;============================================================================
[e0ca]GameLoop_UpdatePlayer:; [$e0ca] [e0ca]JSR Player_CheckHandleAttack; Check if the player has attacked and handle it. [e0cd]JSR Player_CheckHandleClimb [e0d0]JSR Player_CheckHandleJump; Check if the player has jumped and handle it. [e0d3]JSR Player_HandleIFrames; Handle any pending iframes.
; ; Check if screen scrolling is occurring. ;
[e0d6]LDA Screen_ScrollDirection; A = scrolling activity. [e0d8]CMP #$04; Is the screen scrolling in either direction? [e0da]BCC RETURN_E0C9; If so, return.
; ; The screen is not scrolling. ;
[e0dc]JSR Player_CheckHandleEnterDoor; Check if the player is entering a door. [e0df]JSR Player_CheckOnBreakableBlock [e0e2]JSR Player_CheckPushingBlock; Check if the player is pushing the block to open a path to Mascon. [e0e5]JMP Player_CheckSwitchScreen; Check the block the player is on and handle it.
;============================================================================ ; Handle any invincibility frames that may be set. ; ; Invincibility frames start at 0x3C. ; ; If the player has invincibility frames, the frame counter ; will be reduced by 1. ; ; The user's knockback will be reset when the counter hits ; 0x38, at which point the player can safely move. ; ; The user is invincible until the counter hits 0. ; ; INPUTS: ; Player_InvincibilityPhase: ; The current invincibility phase, from 0x3C to 0. ; ; Player_StatusFlag: ; The player's current status flag. ; ; OUTPUTS: ; Player_StatusFlag: ; The new status flag with knockback removed, when ; hitting 0x39 or lower. ; ; CALLS: ; Player_SetStandardAcceleration ; ; XREFS: ; GameLoop_UpdatePlayer ;============================================================================
[e0e8]Player_HandleIFrames:; [$e0e8] [e0e8]LDA Player_InvincibilityPhase; Check the current invincibility phase. [e0ea]BEQ @_removeIFrames; If the phase is now 1, remove iframe status.
; ; Invincibility is active. Reduce it by 1. ;
[e0ec]DEC Player_InvincibilityPhase; Reduce our invincibility phase by 1.
; ; Check where we are in the phase. It starts at 0x3C, and ; until it hits 0x39, it will be in knockback phase. ;
[e0ee]LDA Player_InvincibilityPhase; Check the invincibility phase. [e0f0]CMP #$39; Is it 0x39? [e0f2]BEQ @_resetSpeed; If so, reset the speed but don't change the player status. [e0f4]BCC @_removeIFrames; If not over, update the player status.
; ; Set the knockback speed and return, leaving the knockback ; state intact. ;
[e0f6]JMP Player_SetStandardAcceleration; Set standard acceleration.
; ; Set the knockback speed, but then clear the knockback. ;
[e0f9]@_resetSpeed:; [$e0f9] [e0f9]JSR Player_SetStandardAcceleration; Reset the player speed.
; ; Clear knockback from the player flags. ;
[e0fc]@_removeIFrames:; [$e0fc] [e0fc]LDA Player_StatusFlag; Load the player's flags. [e0fe]AND #$fd; Clear the knockback bit. [e100]STA Player_StatusFlag; Save them. [e102]RTS
;============================================================================ ; TODO: Document Player_CheckHandleAttack ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; GameLoop_UpdatePlayer ;============================================================================
[e103]Player_CheckHandleAttack:; [$e103] [e103]LDA Player_Flags; Load the player's flags. [e105]BMI @_updateHitPhase; If already attacking, jump.
; ; The player is not yet attacking. Check first if the ; player is allowed to attack. ;
[e107]JSR Player_IsClimbing; Is the player climbing? [e10a]BCS @_clearAttacking; If so, clear any attacking state and return.
; ; The player isn't climbing. Check if they've attacked. ;
[e10c]LDA Joy1_ChangedButtonMask; Load the controller 1 button mask. [e10e]AND #$40; Is the B button set? [e110]BEQ @_clearAttacking; If not, clear any attacking state and return. [e112]LDA Player_StatusFlag; Load the player's status flags. [e114]AND #$01; Was the player in attack mode? [e116]BEQ @_setAttacking; If so, set the attacking state. [e118]RTS
; ; Clear the attacking state for the player. ;
[e119]@_clearAttacking:; [$e119] [e119]LDA Player_StatusFlag; Load the player's flags. [e11b]AND #$fe; Clear the Attacking bit. [e11d]STA Player_StatusFlag; Store it. [e11f]RTS
; ; Set the attacking state for the player. ; ; This will update the flags and begin the animation ; phase timer. ;
[e120]@_setAttacking:; [$e120] [e120]LDA Player_Flags; Load the player's flags. [e122]ORA #$80; Set the Attacking bit. [e124]STA Player_Flags; Store it. [e126]LDA Player_StatusFlag; Load the player's status flags. [e128]ORA #$01; Set the Attacking bit. [e12a]STA Player_StatusFlag; Store it.
; ; Clear the phase state. ;
[e12c]LDA #$00 [e12e]STA PlayerHitsPhaseTimer; Set phase timer to 0. [e130]STA PlayerHitsPhaseCounter; Set phase counter to 0. [e132]@_updateHitPhase:; [$e132] [e132]INC PlayerHitsPhaseTimer; Increment the current phase timer. [e134]LDA PlayerHitsPhaseTimer; A = updated phase timer. [e136]LDX PlayerHitsPhaseCounter; X = phase counter. [e138]CMP PLAYER_HIT_PHASES,X; Check against the phase lookup table. [e13b]BCC @_return; If we're not transitioning to the next phase count, return.
; ; We're done with that phase. Move to the next and ; reset the timer. ;
[e13d]LDA #$00 [e13f]STA PlayerHitsPhaseTimer; Reset the current phase's timer to 0. [e141]INX; Phase counter++ [e142]CPX #$03; Is it >= 3 (end of phases)? [e144]BCS @_finishAttacking; If so, finish the attacking state. [e146]STX PlayerHitsPhaseCounter; Else, increment the phase counter for the next frame. [e148]@_return:; [$e148] [e148]RTS [e149]@_finishAttacking:; [$e149] [e149]LDA Player_Flags; Load the player's flags. [e14b]AND #$7f; Clear the Attacking flag. [e14d]STA Player_Flags; Store it.
; ; XREFS: ; Player_CheckHandleJump ;
[e14f]RETURN_E14F:; [$e14f] [e14f]RTS
; ; XREFS: ; Player_CheckHandleAttack ;
[e150]PLAYER_HIT_PHASES:; [$e150] [e150].byte $08; [0]: 8 frames thrusting out weapon [e151].byte $03; [1]: 3 frames of waiting [e152].byte $08; [2]: 8 frames withdrawing weapon
;============================================================================ ; Handle knockback when getting hit. ; ; This will knock the player back in the direction opposite ; where the player was facing. ; ; It will temporarily flip the facing bit and then move in ; that direction. The original bit will then be restored. ; ; INPUTS: ; Player_Flags: ; The player's flags. ; ; OUTPUTS: ; Player_Flags: ; The updated flags, with any new state applied ; from the move but the flipped bit retained. ; ; Temp_00: ; Clobbered. ; ; CALLS: ; Player_KnockbackHoriz ; ; XREFS: ; Player_CheckHandleJump ;============================================================================
[e153]Player_HandleKnockback:; [$e153]
; ; Temporarily store the player's current flags. ;
[e153]LDA Player_Flags; Load the player's flags. [e155]AND #$40; Take only the facing bit. [e157]PHA; Push it to the stack.
; ; Flip the facing bit and knock back in the flipped direction. ;
[e158]EOR #$40; Flip the facing bit temporarily for the purpose of knockback. [e15a]STA Player_Flags; Store as the new flags. [e15c]JSR Player_KnockbackHoriz; Move based on the facing bit.
; ; Restore the original flags, merging with any updated bits ; from the move. ;
[e15f]PLA; Pop the original flags. [e160]STA Temp_00; Store it. [e162]LDA Player_Flags; Load the flags set above. [e164]AND #$bf; Clear the facing bit. [e166]ORA Temp_00; OR with the original facing bit. [e168]STA Player_Flags; Store it. [e16a]RTS
;============================================================================ ; Move the player left or right due to knockback. ; ; The player will be knocked back based on the direction ; they're facing. ; ; INPUTS: ; Player_Flags: ; The player's current flags. ; ; OUTPUTS: ; None. ; ; CALLS: ; Player_TryMoveLeft ; Player_TryMoveRight ; ; XREFS: ; Player_HandleKnockback ;============================================================================
[e16b]Player_KnockbackHoriz:; [$e16b] [e16b]LDA Player_Flags; Load the player's flags. [e16d]AND #$40; Is the player facing right? [e16f]BNE Player_TryMoveRight; If so, move right. [e171]JMP Player_TryMoveLeft
;============================================================================ ; TODO: Document Player_CheckHandleJump ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; GameLoop_UpdatePlayer ;============================================================================
[e174]Player_CheckHandleJump:; [$e174] [e174]LDA Screen_ScrollDirection [e176]BPL RETURN_E14F [e178]LDA Player_StatusFlag [e17a]AND #$02 [e17c]BNE Player_HandleKnockback [e17e]LDA Player_StatusFlag [e180]BMI @_playerCanFly [e182]LDA Player_Flags [e184]AND #$05 [e186]BEQ @_playerCanFly [e188]LDA Player_Flags [e18a]AND #$20 [e18c]BEQ @_playerNotMoving [e18e]LDA Player_Flags [e190]AND #$40 [e192]BNE Player_TryMoveRight [e194]JMP Player_TryMoveLeft [e197]@_playerCanFly:; [$e197] [e197]LDA Player_Flags [e199]BMI @LAB_PRG15_MIRROR__e1be [e19b]LDA Joy1_ButtonMask [e19d]AND #$03 [e19f]BEQ @_playerNotMoving [e1a1]INC Player_MovementTick [e1a3]LSR A [e1a4]BCS Player_TryMoveRight [e1a6]LSR A [e1a7]BCC @_playerNotMoving [e1a9]JMP Player_TryMoveLeft [e1ac]@_playerNotMoving:; [$e1ac] [e1ac]LDA Joy1_ButtonMask [e1ae]AND #$0c [e1b0]BEQ @LAB_PRG15_MIRROR__e1be [e1b2]LDA Player_PosX_Block [e1b4]AND #$0f [e1b6]BEQ @LAB_PRG15_MIRROR__e1be [e1b8]LDA Player_Flags [e1ba]AND #$08 [e1bc]BNE @LAB_PRG15_MIRROR__e1c5 [e1be]@LAB_PRG15_MIRROR__e1be:; [$e1be] [e1be]LDA Player_Flags [e1c0]AND #$df [e1c2]STA Player_Flags [e1c4]RTS [e1c5]@LAB_PRG15_MIRROR__e1c5:; [$e1c5] [e1c5]INC Player_MovementTick [e1c7]LDA Player_PosX_Block [e1c9]AND #$0f [e1cb]CMP #$08 [e1cd]BCC Player_TryMoveLeft
;============================================================================ ; Try moving the player right. ; ; This is used when handling knockback or jumping. It will ; move based on any existing acceleration, or the default ; if the player wasn't previously moving, and then try to ; position the player. That may position up againt a block, ; at the edge of the screen, or onto the next screen. ; ; INPUTS: ; Player_Flags: ; The player's current flags. ; ; Player_PosX_Block: ; Player_PosX_Frac: ; The player's current X position. ; ; Area_ScreenToTheRight: ; The ID of the screen to the right. ; ; Screen_Maybe_ScrollXCounter: ; The counter used for scrolling. ; ; OUTPUTS: ; Player_Flags: ; The updated flags. ; ; Player_PosX_Block: ; Player_PosX_Frac: ; The updated player X position. ; ; Player_MoveAcceleration: ; The updated move acceleration. ; ; Area_CurrentScreen: ; The new screen, if switching screens. ; ; Screen_Maybe_ScrollXCounter: ; The updated counter used for scrolling. ; ; Screen_ScrollPlayerTransitionCounter: ; The transition counter for the player movement, ; if scrolling screens. ; ; CALLS: ; Area_ScrollTo ; Player_UpdateAcceleration ; Player_SetStandardAcceleration ; Player_CheckIfPassable ; Player_SetPosAtRightEdge ; Screen_IsEdge ; ; XREFS: ; Player_CheckHandleJump ; Player_KnockbackHoriz ;============================================================================
[e1cf]Player_TryMoveRight:; [$e1cf]
; ; Check whether the player is moving. ;
[e1cf]LDA Player_Flags; Load the player's flags. [e1d1]AND #$20; Check the moving bit. [e1d3]BNE @_setMoving; If set, jump to handle movement.
; ; The player is not moving. Reset the movement speed. ;
[e1d5]JSR Player_SetStandardAcceleration; Start with the standard acceleration.
; ; Calculate movement speed and set the player to move to ; the right. ;
[e1d8]@_setMoving:; [$e1d8] [e1d8]JSR Player_UpdateAcceleration; Calculate the new walking speed. [e1db]LDA Player_Flags; Load the player's flags. [e1dd]ORA #$60; Set moving and to the right. [e1df]STA Player_Flags; Store as the new flags.
; ; Update the player's X position to factor in acceleration. ;
[e1e1]LDA Player_PosX_Frac; Load the fractional X position. [e1e3]CLC [e1e4]ADC Player_MoveAcceleration; Add the move acceleration. [e1e6]STA Player_PosX_Frac; Store as the new fractional position. [e1e8]LDA Player_PosX_Block; Load the full X position. [e1ea]ADC Player_MoveAcceleration_U; Add the move acceleration. [e1ec]STA Player_PosX_Block; And store it.
; ; Check if the block there is passable. ;
[e1ee]LDX #$01 [e1f0]JSR Player_CheckIfPassable; Is the block to the right passable? [e1f3]BEQ @_checkAtRightEdge; If not, jump to check for an edge or transition boundary.
; ; The block is not passable. Cap the position. ;
[e1f5]LDA Player_PosX_Block; Load the player X position. [e1f7]AND #$f0; Cap it. [e1f9]STA Player_PosX_Block; And store it. [e1fb]JMP @_return
; ; Check if the player is far enough to the right for ; any screen transition logic. ;
[e1fe]@_checkAtRightEdge:; [$e1fe] [e1fe]LDA Player_PosX_Block; X = Player X position. [e200]CMP #$f1; Is it too far left to perform a screen transition? [e202]BCC @_return; If so, return.
; ; Check if the player is at the edge and can scroll. ;
[e204]LDY #$01 [e206]JSR Screen_IsEdge; Check if the screen is the right-most edge. [e209]BCS Player_SetPosAtRightEdge; If so, jump to set the position there.
; ; There's a screen to the right. Transition there. ;
[e20b]LDA Area_ScreenToTheRight; A = Neighboring screen to the right. [e20d]STA Area_CurrentScreen; Store as the new current screen. [e20f]LDX #$01 [e211]JSR Area_ScrollTo; Begin scrolling to the right. [e214]INC Screen_Maybe_ScrollXCounter; Increment the scroll counter. [e216]LDA #$00 [e218]STA Screen_ScrollPlayerTransitionCounter; Clear the transition counter. [e21a]@_return:; [$e21a] [e21a]RTS
;============================================================================ ; Position the player at the right edge of the screen. ; ; The player will be positioned at 0xF0. ; ; INPUTS: ; None. ; ; OUTPUTS: ; Player_PosX_Block: ; The updated X position. ; ; XREFS: ; Player_TryMoveRight ;============================================================================
[e21b]Player_SetPosAtRightEdge:; [$e21b] [e21b]LDA #$f0 [e21d]STA Player_PosX_Block; Set the X position to 0xF0. [e21f]RTS
;============================================================================ ; Try moving the player left. ; ; This is used when handling knockback or jumping. It will ; move based on any existing acceleration, or the default ; if the player wasn't previously moving, and then try to ; position the player. That may position up againt a block, ; at the edge of the screen, or onto the next screen. ; ; INPUTS: ; Player_Flags: ; The player's current flags. ; ; Player_PosX_Block: ; Player_PosX_Frac: ; The player's current X position. ; ; Area_ScreenToTheLeft: ; The ID of the screen to the left. ; ; Screen_Maybe_ScrollXCounter: ; The counter used for scrolling. ; ; OUTPUTS: ; Player_Flags: ; The updated flags. ; ; Player_PosX_Block: ; Player_PosX_Frac: ; The updated player X position. ; ; Player_MoveAcceleration: ; The updated move acceleration. ; ; Area_CurrentScreen: ; The new screen, if switching screens. ; ; Screen_Maybe_ScrollXCounter: ; The updated counter used for scrolling. ; ; Screen_ScrollPlayerTransitionCounter: ; The transition counter for the player movement, ; if scrolling screens. ; ; CALLS: ; Area_ScrollTo ; Player_UpdateAcceleration ; Player_SetStandardAcceleration ; Player_CheckIfPassable ; Player_SetPosAtRightEdge ; Screen_IsEdge ; ; XREFS: ; Player_CheckHandleJump ; Player_KnockbackHoriz ;============================================================================
[e220]Player_TryMoveLeft:; [$e220]
; ; Check whether the player is moving. ;
[e220]LDA Player_Flags; Load the player's flags. [e222]AND #$20; Check the moving bit. [e224]BNE @_setMoving; If set, jump to handle movement.
; ; The player is not moving. Reset the movement speed. ;
[e226]JSR Player_SetStandardAcceleration; Start with the standard acceleration.
; ; Calculate movement speed and set the player to move to ; the left. ;
[e229]@_setMoving:; [$e229] [e229]JSR Player_UpdateAcceleration; Calculate the new walking speed. [e22c]LDA Player_Flags; Load the player's flags. [e22e]AND #$bf; Set the player to face left. [e230]ORA #$20; Set the player to moving. [e232]STA Player_Flags; Store as the new flags.
; ; Update the player's X position to factor in acceleration. ;
[e234]LDA Player_PosX_Frac; Load the fractional X position. [e236]SEC [e237]SBC Player_MoveAcceleration; Subtract the move acceleration. [e239]STA Player_PosX_Frac; Store as the new fractional position. [e23b]LDA Player_PosX_Block; Load the full X position. [e23d]SBC Player_MoveAcceleration_U; Subtract the move acceleration. [e23f]PHP; Push all flags. [e240]BCS @_saveFlags; If >= 0, jump. [e242]LDA #$00; Else, cap at 0, so it doesn't wrap. [e244]@_saveFlags:; [$e244] [e244]STA Player_PosX_Block; Store the new X position.
; ; Check if the block there is passable. ;
[e246]LDX #$00 [e248]JSR Player_CheckIfPassable; Is the block to the left passable? [e24b]BEQ @_checkAtLeftEdge; If not, jump to check for an edge or transition boundary.
; ; The block is not passable. Cap the position. ;
[e24d]PLP; Pop the flags from the acceleration calculation. [e24e]LDA Player_PosX_Block; Load the player X position. [e250]AND #$0f; Is this in the first 16 pixels? [e252]BEQ @_return1; If so, we're done. [e254]LDA Player_PosX_Block; Load the player X position. [e256]AND #$f0; Keep all but pixels 0-16. [e258]CLC [e259]ADC #$10; Add 16 pixels. [e25b]STA Player_PosX_Block; Store as the new position. [e25d]@_return1:; [$e25d] [e25d]RTS; And we're done.
; ; Check if the player is far enough to the left for ; any screen transition logic. ;
[e25e]@_checkAtLeftEdge:; [$e25e] [e25e]PLP; Pop the flags from the acceleration calculation. [e25f]BCS @_return2; If it didn't overflow off the left, return.
; ; Check if the player is at the edge and can scroll. ;
[e261]LDY #$00 [e263]JSR Screen_IsEdge; Check if the screen is the left-most edge. [e266]BCS @_setAtLeftEdge; If at the edge, jump to cap the position.
; ; There's a screen to the left. Transition there. ;
[e268]LDA Area_ScreenToTheLeft; A = Neighboring screen to the left. [e26a]STA Area_CurrentScreen; Store as the new current screen. [e26c]LDX #$00 [e26e]JSR Area_ScrollTo; Begin scrolling to the left. [e271]LDA #$00 [e273]STA Player_PosX_Block; Set the X position to 0. [e275]STA Screen_ScrollPlayerTransitionCounter; Clear the transition counter. [e277]@_return2:; [$e277] [e277]RTS
; ; The player is at the left-most edge, but didn't pass it. ;
[e278]@_setAtLeftEdge:; [$e278] [e278]LDA #$00 [e27a]STA Player_PosX_Block; Set the X position to 0. [e27c]RTS; And return. [e27d].byte $00; [0]: [e27e].byte $0f; [1]:
;============================================================================ ; Set the standard player acceleration. ; ; This will set to the default aceleration of 0xC0. ; ; INPUTS: ; None. ; ; OUTPUTS: ; Player_MoveAcceleration: ; Player_MoveAcceleration+1: ; The default acceleration (0xC0). ; ; XREFS: ; Player_HandleIFrames ; Player_TryMoveLeft ; Player_TryMoveRight ; Player_UpdateAcceleration ;============================================================================
[e27f]Player_SetStandardAcceleration:; [$e27f] [e27f]LDA #$c0 [e281]STA Player_MoveAcceleration; Set the lower byte of movement acceleation to 0xC0. [e283]LDA #$00 [e285]STA Player_MoveAcceleration_U; Set the upper byte to 0. [e287]RTS
;============================================================================ ; Update the player's position based on knockback. ; ; This will accelerate the player by 8 pixels when its ; position is next updated. ; ; INPUTS: ; None. ; ; OUTPUTS: ; Player_MoveAcceleration: ; Player_MoveAcceleration+1: ; The updated acceleration. ; ; XREFS: ; Player_UpdateAcceleration ;============================================================================
[e288]Player_UpdatePosFromKnockback:; [$e288]
; ; The player's in knockback state. Bump the player ; back a bunch. ;
[e288]LDA #$00 [e28a]STA Player_MoveAcceleration; Set lower acceleration byte to 0. [e28c]LDA #$08 [e28e]STA Player_MoveAcceleration_U; Set upper to 8. [e290]RTS
;============================================================================ ; TODO: Document Player_UpdateAcceleration ; ; INPUTS: ; None. ; ; OUTPUTS: ; A ; ; XREFS: ; Player_TryMoveLeft ; Player_TryMoveRight ;============================================================================
[e291]Player_UpdateAcceleration:; [$e291] [e291]LDA Player_StatusFlag [e293]AND #$02 [e295]BNE Player_UpdatePosFromKnockback [e297]LDA Joy1_ButtonMask [e299]AND #$0c [e29b]BEQ @LAB_PRG15_MIRROR__e2a3 [e29d]LDA Player_Flags [e29f]AND #$08 [e2a1]BNE Player_SetStandardAcceleration [e2a3]@LAB_PRG15_MIRROR__e2a3:; [$e2a3] [e2a3]LDA Player_MoveAcceleration [e2a5]CMP #$80 [e2a7]LDA Player_MoveAcceleration_U [e2a9]SBC #$01 [e2ab]BCS @_return [e2ad]LDA a:PlayerTitle [e2b0]LSR A [e2b1]LSR A [e2b2]AND #$03 [e2b4]TAX [e2b5]LDA Player_MoveAcceleration [e2b7]CLC [e2b8]ADC BYTE_ARRAY_PRG15_MIRROR__e2c4,X [e2bb]STA Player_MoveAcceleration [e2bd]LDA Player_MoveAcceleration_U [e2bf]ADC #$00 [e2c1]STA Player_MoveAcceleration_U [e2c3]@_return:; [$e2c3] [e2c3]RTS
; ; XREFS: ; Player_UpdateAcceleration ;
[e2c4]BYTE_ARRAY_PRG15_MIRROR__e2c4:; [$e2c4] [e2c4].byte $02; [0]: [e2c5].byte $04; [1]: [e2c6].byte $06; [2]: [e2c7].byte $08; [3]:
;============================================================================ ; TODO: Document Player_CheckHandleClimb ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; GameLoop_UpdatePlayer ;============================================================================
[e2c8]Player_CheckHandleClimb:; [$e2c8] [e2c8]LDA Player_StatusFlag; A = Player status flags. [e2ca]BPL @LAB_PRG15_MIRROR__e2d6 [e2cc]LDA Joy1_ButtonMask [e2ce]BPL @LAB_PRG15_MIRROR__e2d6 [e2d0]JSR Player_CheckIfOnLadder [e2d3]JMP @LAB_PRG15_MIRROR__e2f4 [e2d6]@LAB_PRG15_MIRROR__e2d6:; [$e2d6] [e2d6]JSR Player_CheckIfOnLadder [e2d9]LDA Player_Flags [e2db]AND #$08 [e2dd]BEQ @LAB_PRG15_MIRROR__e2fe [e2df]LDA Joy1_ButtonMask [e2e1]AND #$0c [e2e3]BEQ @LAB_PRG15_MIRROR__e2fe [e2e5]LDA Player_Flags [e2e7]ORA #$10 [e2e9]STA Player_Flags [e2eb]LDA Player_PosX_Block [e2ed]AND #$0f [e2ef]BEQ @LAB_PRG15_MIRROR__e2f4 [e2f1]JMP Player_CheckHandleClimbMaybeSide [e2f4]@LAB_PRG15_MIRROR__e2f4:; [$e2f4] [e2f4]LDA Joy1_ButtonMask [e2f6]LSR A [e2f7]LSR A [e2f8]LSR A [e2f9]BCS Player_CheckHandleClimbDown [e2fb]LSR A [e2fc]BCS Player_CheckHandleClimbUp [e2fe]@LAB_PRG15_MIRROR__e2fe:; [$e2fe] [e2fe]JMP Player_CheckHandleClimbMaybeSide
;============================================================================ ; TODO: Document Player_CheckHandleClimbUp ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_CheckHandleClimb ;============================================================================
[e301]Player_CheckHandleClimbUp:; [$e301] [e301]LDA Player_Flags [e303]AND #$da [e305]STA Player_Flags [e307]LDX #$02 [e309]JSR Player_CheckIfPassable [e30c]BNE @LAB_PRG15_MIRROR__e343 [e30e]INC Player_MovementTick [e310]LDA Player_StatusFlag [e312]BPL @LAB_PRG15_MIRROR__e31e [e314]LDA Player_PosY [e316]SEC [e317]SBC #$01 [e319]STA Player_PosY [e31b]JMP @LAB_PRG15_MIRROR__e32b [e31e]@LAB_PRG15_MIRROR__e31e:; [$e31e] [e31e]LDA BYTE_00a0 [e320]SEC [e321]SBC #$a0 [e323]STA BYTE_00a0 [e325]LDA Player_PosY [e327]SBC #$00 [e329]STA Player_PosY [e32b]@LAB_PRG15_MIRROR__e32b:; [$e32b] [e32b]BCS @LAB_PRG15_MIRROR__e343 [e32d]LDY #$02 [e32f]JSR Screen_IsEdge [e332]BCS @LAB_PRG15_MIRROR__e344 [e334]LDA Area_ScreenAbove [e336]STA Area_CurrentScreen [e338]LDX #$02 [e33a]JSR Area_ScrollTo [e33d]DEC Player_Something_ScrollPosY [e33f]LDA #$c0 [e341]STA Player_PosY [e343]@LAB_PRG15_MIRROR__e343:; [$e343] [e343]RTS [e344]@LAB_PRG15_MIRROR__e344:; [$e344] [e344]LDA #$00 [e346]STA Player_PosY [e348]RTS
;============================================================================ ; TODO: Document Player_CheckHandleClimbDown ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_CheckHandleClimb ;============================================================================
[e349]Player_CheckHandleClimbDown:; [$e349] [e349]LDA Player_Flags [e34b]AND #$da [e34d]STA Player_Flags [e34f]LDX #$03 [e351]JSR Player_CheckIfPassable [e354]BNE RETURN_E393 [e356]INC Player_MovementTick [e358]LDA Player_StatusFlag [e35a]BPL @LAB_PRG15_MIRROR__e36c [e35c]LDA BYTE_00a0 [e35e]CLC [e35f]ADC #$80 [e361]STA BYTE_00a0 [e363]LDA Player_PosY [e365]ADC #$01 [e367]STA Player_PosY [e369]JMP Player_MoveDownScreen [e36c]@LAB_PRG15_MIRROR__e36c:; [$e36c] [e36c]LDA BYTE_00a0 [e36e]CLC [e36f]ADC #$c0 [e371]STA BYTE_00a0 [e373]LDA Player_PosY [e375]ADC #$00 [e377]STA Player_PosY
;============================================================================ ; TODO: Document Player_MoveDownScreen ; ; INPUTS: ; A ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_CheckHandleClimbDown ;============================================================================
[e379]Player_MoveDownScreen:; [$e379] [e379]CMP #$c1 [e37b]BCC RETURN_E393 [e37d]LDY #$03 [e37f]JSR Screen_IsEdge [e382]BCS Player_SetPosAtBottomEdge [e384]LDA Area_ScreenBelow [e386]STA Area_CurrentScreen [e388]LDX #$03 [e38a]JSR Area_ScrollTo [e38d]INC Player_Something_ScrollPosY [e38f]LDA #$00 [e391]STA Player_PosY
; ; XREFS: ; Player_CheckHandleClimbDown ; Player_MoveDownScreen ;
[e393]RETURN_E393:; [$e393] [e393]RTS
;============================================================================ ; Set the player at the bottom-most position of the screen. ; ; INPUTS: ; None. ; ; OUTPUTS: ; Player_PosY: ; The updated player position (set to 0xC0). ; ; XREFS: ; Player_MoveDownScreen ;============================================================================
[e394]Player_SetPosAtBottomEdge:; [$e394] [e394]LDA #$c0 [e396]STA Player_PosY; Set player Y position to 0xC0 (192). [e398]RTS
;============================================================================ ; TODO: Document Player_CheckHandleClimbMaybeSide ; ; INPUTS: ; X ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_CheckHandleClimb ;============================================================================
[e399]Player_CheckHandleClimbMaybeSide:; [$e399] [e399]LDA Something_Player_ClimbLadderCheckPos [e39b]CMP #$20 [e39d]BCC @LAB_PRG15_MIRROR__e3a5
; ; Clear the player's Jumping state. ;
[e39f]LDA Player_Flags; Load the player's flags. [e3a1]AND #$fe; Clear the Jumping bit. [e3a3]STA Player_Flags; Save it back. [e3a5]@LAB_PRG15_MIRROR__e3a5:; [$e3a5] [e3a5]LDA Player_Flags [e3a7]LSR A [e3a8]BCC @LAB_PRG15_MIRROR__e3ad [e3aa]JMP Player_StayOnLadderAndContinue [e3ad]@LAB_PRG15_MIRROR__e3ad:; [$e3ad] [e3ad]LDA #$03 [e3af]JSR Player_CheckIfPassable [e3b2]BEQ @LAB_PRG15_MIRROR__e3b7 [e3b4]JMP Player_StayOnLadderAndContinue [e3b7]@LAB_PRG15_MIRROR__e3b7:; [$e3b7] [e3b7]JSR Area_CheckCanClimbAdjacent [e3ba]LDA Blocks_Result [e3bc]BEQ @LAB_PRG15_MIRROR__e3cd [e3be]LDA Maybe_ClimbLadderOffset [e3c0]CMP #$08 [e3c2]BCS @LAB_PRG15_MIRROR__e3c9 [e3c4]INC Maybe_ClimbLadderOffset [e3c6]JMP Player_ContinueHandleClimbOrJump [e3c9]@LAB_PRG15_MIRROR__e3c9:; [$e3c9] [e3c9]LDA #$00 [e3cb]STA Maybe_ClimbLadderOffset [e3cd]@LAB_PRG15_MIRROR__e3cd:; [$e3cd] [e3cd]LDA Player_StatusFlag [e3cf]BPL @LAB_PRG15_MIRROR__e3d1 [e3d1]@LAB_PRG15_MIRROR__e3d1:; [$e3d1] [e3d1]LDA Player_Flags [e3d3]ORA #$04 [e3d5]STA Player_Flags [e3d7]LDA Player_Flags [e3d9]AND #$08 [e3db]BNE Player_StayOnLadderAndContinue [e3dd]LDA Player_StatusFlag [e3df]BPL @LAB_PRG15_MIRROR__e3f5 [e3e1]LDA Joy1_ButtonMask [e3e3]BPL @LAB_PRG15_MIRROR__e3f5 [e3e5]LDA BYTE_00a0 [e3e7]CLC [e3e8]ADC #$00 [e3ea]STA BYTE_00a0 [e3ec]LDA Player_PosY [e3ee]ADC #$01 [e3f0]STA Player_PosY [e3f2]JMP Area_ScrollScreenDown [e3f5]@LAB_PRG15_MIRROR__e3f5:; [$e3f5] [e3f5]LDA Player_PosY [e3f7]CLC [e3f8]ADC #$08 [e3fa]STA Player_PosY
;============================================================================ ; TODO: Document Area_ScrollScreenDown ; ; INPUTS: ; A ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_CheckHandleClimbMaybeSide ;============================================================================
[e3fc]Area_ScrollScreenDown:; [$e3fc] [e3fc]CMP #$c1 [e3fe]BCC @LAB_PRG15_MIRROR__e41e [e400]LDY #$03 [e402]JSR Screen_IsEdge [e405]BCC @LAB_PRG15_MIRROR__e40e [e407]LDA #$c0 [e409]STA Player_PosY [e40b]JMP @LAB_PRG15_MIRROR__e42c [e40e]@LAB_PRG15_MIRROR__e40e:; [$e40e] [e40e]LDA Area_ScreenBelow [e410]STA Area_CurrentScreen [e412]LDX #$03 [e414]JSR Area_ScrollTo [e417]INC Player_Something_ScrollPosY [e419]LDA #$00 [e41b]STA Player_PosY [e41d]RTS [e41e]@LAB_PRG15_MIRROR__e41e:; [$e41e] [e41e]LDX #$03 [e420]JSR Player_CheckIfPassable [e423]BEQ @LAB_PRG15_MIRROR__e42b [e425]LDA Player_PosY [e427]AND #$f0 [e429]STA Player_PosY [e42b]@LAB_PRG15_MIRROR__e42b:; [$e42b] [e42b]RTS [e42c]@LAB_PRG15_MIRROR__e42c:; [$e42c] [e42c]LDA Player_Flags [e42e]AND #$fb [e430]STA Player_Flags [e432]RTS
;============================================================================ ; TODO: Document Player_ClearJumpingAndHoldingToClimb ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_ContinueHandleClimbOrJump ;============================================================================
[e433]Player_ClearJumpingAndHoldingToClimb:; [$e433] [e433]LDA Player_Flags [e435]AND #$fc [e437]STA Player_Flags
; ; XREFS: ; Player_ContinueHandleClimbOrJump ;
[e439]RETURN_E439:; [$e439] [e439]RTS
;============================================================================ ; TODO: Document Player_StayOnLadderAndContinue ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_CheckHandleClimbMaybeSide ;============================================================================
[e43a]Player_StayOnLadderAndContinue:; [$e43a]
; ; Clear the Falling Off state. ;
[e43a]LDA Player_Flags; Load the player's flags. [e43c]AND #$fb; Clear the Falling Off flag. [e43e]STA Player_Flags; Save it back. [e440]LDA #$00 [e442]STA Maybe_ClimbLadderOffset
; ; v-- Fall through --v ;
;============================================================================ ; TODO: Document Player_ContinueHandleClimbOrJump ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_CheckHandleClimbMaybeSide ;============================================================================
[e444]Player_ContinueHandleClimbOrJump:; [$e444]
; ; Check if the player is jumping. ;
[e444]LDA Player_Flags; Load the player's flags. [e446]LSR A; Shift the Jump flag into Carry. [e447]BCS @_isJumping; If set (player is jumping), then jump (branch).
; ; The player is not jumping. ;
[e449]LDX Joy1_ChangedButtonMask; X = Controller 1 changed button mask. [e44b]BPL Player_ClearJumpingAndHoldingToClimb; If the player is holding down A, then check grabbing for a ladder and return. [e44d]JSR Player_IsClimbing [e450]BCS RETURN_E439 [e452]LDX #$02 [e454]JSR Player_CheckIfPassable [e457]BNE RETURN_E4B6 [e459]LDA Player_Flags [e45b]ORA #$03 [e45d]STA Player_Flags [e45f]LDA #$00 [e461]STA Something_Player_ClimbLadderCheckPos [e463]@_isJumping:; [$e463] [e463]LDX Something_Player_ClimbLadderCheckPos [e465]CPX #$10 [e467]BCC @LAB_PRG15_MIRROR__e46b [e469]BCS @LAB_PRG15_MIRROR__e4a2 [e46b]@LAB_PRG15_MIRROR__e46b:; [$e46b] [e46b]LDA Player_PosY [e46d]SEC [e46e]SBC BYTE_ARRAY_PRG15_MIRROR__e4d6,X [e471]STA Player_PosY [e473]BCS @LAB_PRG15_MIRROR__e481 [e475]LDY #$02 [e477]JSR Screen_IsEdge [e47a]BCC Area_ScrollScreenUp [e47c]LDA #$00 [e47e]JMP @LAB_PRG15_MIRROR__e49a [e481]@LAB_PRG15_MIRROR__e481:; [$e481] [e481]LDX #$02 [e483]JSR Player_CheckIfPassable [e486]BEQ Area_Something_IncDAT00a6 [e488]LDX Something_Player_ClimbLadderCheckPos [e48a]LDA Player_PosY [e48c]AND #$0f [e48e]TAX [e48f]LDA Player_PosY [e491]AND #$f0 [e493]CPX #$00 [e495]BEQ @LAB_PRG15_MIRROR__e49a [e497]CLC [e498]ADC #$10 [e49a]@LAB_PRG15_MIRROR__e49a:; [$e49a] [e49a]STA Player_PosY [e49c]LDA #$0f [e49e]STA Something_Player_ClimbLadderCheckPos [e4a0]BNE Area_Something_IncDAT00a6 [e4a2]@LAB_PRG15_MIRROR__e4a2:; [$e4a2] [e4a2]LDA Player_PosY [e4a4]CLC [e4a5]ADC BYTE_ARRAY_PRG15_MIRROR__e4d6,X [e4a8]STA Player_PosY [e4aa]LDX #$03 [e4ac]JSR Player_CheckIfPassable [e4af]BNE Maybe_SetPlayerForScrollUp
; ; XREFS: ; Player_ContinueHandleClimbOrJump ;
[e4b1]Area_Something_IncDAT00a6:; [$e4b1] [e4b1]LDX Something_Player_ClimbLadderCheckPos [e4b3]INX [e4b4]STX Something_Player_ClimbLadderCheckPos
; ; XREFS: ; Player_ContinueHandleClimbOrJump ;
[e4b6]RETURN_E4B6:; [$e4b6] [e4b6]RTS
;============================================================================ ; TODO: Document Area_ScrollScreenUp ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_ContinueHandleClimbOrJump ;============================================================================
[e4b7]Area_ScrollScreenUp:; [$e4b7] [e4b7]LDA Area_ScreenAbove [e4b9]STA Area_CurrentScreen [e4bb]LDX #$02 [e4bd]JSR Area_ScrollTo [e4c0]DEC Player_Something_ScrollPosY [e4c2]LDA #$c0 [e4c4]STA Player_PosY [e4c6]JMP Maybe_SetPlayerForScrollUp
;============================================================================ ; TODO: Document Maybe_SetPlayerForScrollUp ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Area_ScrollScreenUp ;============================================================================
[e4c9]Maybe_SetPlayerForScrollUp:; [$e4c9] [e4c9]LDA Player_PosY [e4cb]AND #$f0 [e4cd]STA Player_PosY [e4cf]LDA Player_Flags [e4d1]AND #$fe [e4d3]STA Player_Flags [e4d5]RTS
; ; XREFS: ; Player_ContinueHandleClimbOrJump ;
[e4d6]BYTE_ARRAY_PRG15_MIRROR__e4d6:; [$e4d6] [e4d6].byte $08; [0]: [e4d7].byte $04; [1]: [e4d8].byte $04; [2]: [e4d9].byte $04; [3]: [e4da].byte $04; [4]: [e4db].byte $02; [5]: [e4dc].byte $02; [6]: [e4dd].byte $01; [7]: [e4de].byte $01; [8]: [e4df].byte $01; [9]: [e4e0].byte $01; [10]: [e4e1].byte $00; [11]: [e4e2].byte $00; [12]: [e4e3].byte $00; [13]: [e4e4].byte $00; [14]: [e4e5].byte $00; [15]: [e4e6].byte $00; [16]: [e4e7].byte $00; [17]: [e4e8].byte $00; [18]: [e4e9].byte $00; [19]: [e4ea].byte $00; [20]: [e4eb].byte $01; [21]: [e4ec].byte $01; [22]: [e4ed].byte $01; [23]: [e4ee].byte $01; [24]: [e4ef].byte $02; [25]: [e4f0].byte $02; [26]: [e4f1].byte $04; [27]: [e4f2].byte $04; [28]: [e4f3].byte $04; [29]: [e4f4].byte $04; [30]: [e4f5].byte $08; [31]:
;============================================================================ ; TODO: Document Area_CheckCanClimbAdjacent ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_CheckHandleClimbMaybeSide ;============================================================================
[e4f6]Area_CheckCanClimbAdjacent:; [$e4f6] [e4f6]LDX #$00 [e4f8]LDA Joy1_ChangedButtonMask; Load the controller 1 button mask. [e4fa]AND #$03; If Left or Right pressed? [e4fc]BEQ @_returnFalse; If not, jump to return a false result. [e4fe]AND #$01; Keep the Right Button bit. [e500]BEQ @LAB_PRG15_MIRROR__e503 [e502]INX [e503]@LAB_PRG15_MIRROR__e503:; [$e503] [e503]LDA Player_PosX_Block [e505]CLC [e506]ADC BYTE_PRG15_MIRROR__e524,X [e509]STA Arg_PixelPosX [e50b]LDA Player_PosY [e50d]CLC [e50e]ADC #$20 [e510]STA Arg_PixelPosY [e512]CMP #$f0 [e514]BCC @_returnFalse [e516]JSR Area_ConvertPixelsToBlockPos [e519]JSR ScreenBuffer_IsBlockImpassable [e51c]STA Blocks_Result [e51e]RTS [e51f]@_returnFalse:; [$e51f] [e51f]LDA #$00; A = 0 (false). [e521]STA Blocks_Result; Store it as the result. [e523]RTS
; ; XREFS: ; Area_CheckCanClimbAdjacent ;
[e524]BYTE_PRG15_MIRROR__e524:; [$e524] [e524].byte $00; [$e524] byte
; ; XREFS: ; Area_CheckCanClimbAdjacent ;
[e525]BYTE_PRG15_MIRROR__e525:; [$e525] [e525].byte $0f; [$e525] byte
;============================================================================ ; TODO: Document Player_CheckHandleEnterDoor ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; GameLoop_UpdatePlayer ;============================================================================
[e526]Player_CheckHandleEnterDoor:; [$e526] [e526]LDA Joy1_ChangedButtonMask [e528]AND #$08 [e52a]BEQ @_return [e52c]JSR Area_SetStateFromDoorDestination; Set the new state based on the other end of the door. [e52f]LDA Blocks_Result; Check the result of that. [e531]BEQ @_return [e533]LDA Temp_BlockPos2 [e535]CMP #$fe [e537]BCS Player_EnterDoorToOutside
; ; The player is going inside. ;
[e539]JSR Game_RunDoorRequirementHandler [e53c]LDA a:CurrentDoor_KeyRequirement [e53f]BNE @_return
; ; The door is not locked. ;
[e541]LDA Temp_BlockPos2 [e543]CMP #$20 [e545]BCS Player_EnterDoorToInside [e547]JSR Screen_FadeToBlack [e54a]LDX #$06 [e54c]@_paletteCheckLoop:; [$e54c] [e54c]LDA Screen_DestPaletteOrIndex [e54e]CMP @_return+1,X [e551]BEQ @_setupArea [e553]DEX [e554]BPL @_paletteCheckLoop [e556]BMI @_enterScreen [e558]@_setupArea:; [$e558] [e558]LDA $e570,X [e55b]CMP a:Areas_DefaultMusic [e55e]BEQ @_enterScreen [e560]STA Music_Current [e562]STA a:Areas_DefaultMusic [e565]@_enterScreen:; [$e565] [e565]JMP Game_SetupEnterScreen [e568]@_return:; [$e568] [e568]RTS
;============================================================================ ; Mapping of palettes to accompanying music. ; ; These two tables work together, along with a loop, to ; match up palettes to the music that should play when ; switching screens. ;============================================================================
[e569].byte PALETTE_OUTSIDE; [0]: [e56a].byte PALETTE_TOWER; [1]: [e56b].byte PALETTE_MIST; [2]: [e56c].byte PALETTE_SUFFER; [3]: [e56d].byte PALETTE_DARTMOOR; [4]:
; ; XREFS: ; Player_CheckHandleEnterDoor ;
[e56e]Palette_ARRAY_PRG15_MIRROR__e569_5_:; [$e56e] [e56e].byte PALETTE_FRATERNAL; [5]:
; ; XREFS: ; Player_CheckHandleEnterDoor ;
[e56f]Palette_ARRAY_PRG15_MIRROR__e569_6_:; [$e56f] [e56f].byte PALETTE_KING_GRIEVES_ROOM; [6]: [e570].byte MUSIC_APOLUNE; [0]: [e571].byte MUSIC_MAYBE_TOWER; [1]: [e572].byte MUSIC_FOREPAW; [2]: [e573].byte MUSIC_MAYBE_TOWER; [3]: [e574].byte MUSIC_DAYBREAK; [4]:
; ; XREFS: ; Player_CheckHandleEnterDoor ;
[e575]Music_ARRAY_PRG15_MIRROR__e570_5_:; [$e575] [e575].byte MUSIC_MAYBE_TOWER; [5]:
; ; XREFS: ; Player_CheckHandleEnterDoor ;
[e576]Music_ARRAY_PRG15_MIRROR__e570_6_:; [$e576] [e576].byte MUSIC_MAYBE_TOWER; [6]:
;============================================================================ ; TODO: Document Player_EnterDoorToInside ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_CheckHandleEnterDoor ;============================================================================
[e577]Player_EnterDoorToInside:; [$e577] [e577]LDX Area_LoadingScreenIndex [e579]STX a:Area_DestScreen [e57c]LDA Screen_DestPaletteOrIndex [e57e]STA Area_LoadingScreenIndex [e580]TAX [e581]LDA BUILDING_PALETTES,X [e584]STA Screen_DestPaletteOrIndex [e586]LDA BUILDING_MAYBE_TILES_INDEXES,X [e589]STA a:Building_TilesIndex [e58c]LDA BUILDING_START_POSITIONS,X [e58f]STA Screen_StartPosYX [e591]LDA a:Areas_DefaultMusic [e594]STA a:Area_Music_Outside [e597]LDA BUILDING_MUSIC,X [e59a]STA a:Areas_DefaultMusic [e59d]LDA Area_CurrentArea
; ; Where does this door take the player? ;
[e59f]CMP #$04 [e5a1]BNE @LAB_PRG15_MIRROR__e5a6 [e5a3]JMP Game_BeginExitBuilding [e5a6]@LAB_PRG15_MIRROR__e5a6:; [$e5a6] [e5a6]LDA Player_Flags [e5a8]AND #$bf [e5aa]STA Player_Flags [e5ac]JSR Screen_FadeToBlack [e5af]JMP Game_SetupEnterBuilding
;============================================================================ ; TODO: Document Player_EnterDoorToOutside ; ; INPUTS: ; A ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_CheckHandleEnterDoor ;============================================================================
[e5b2]Player_EnterDoorToOutside:; [$e5b2]
; ; The player opened a door back to the outside. ;
[e5b2]LSR A [e5b3]LDA a:Area_Region [e5b6]ROL A [e5b7]PHA [e5b8]TAY
; ; Check if the door is locked. ;
[e5b9]LDA DOOR_LOOKUP_REQUIREMENTS,Y [e5bc]STA a:CurrentDoor_KeyRequirement [e5bf]JSR Game_RunDoorRequirementHandler [e5c2]PLA [e5c3]TAY [e5c4]LDA a:CurrentDoor_KeyRequirement [e5c7]BNE SUB_RETURN_E5DA
; ; This door is not locked. ;
[e5c9]LDA MAYBE_REGION_TRANSITION_MAP,Y [e5cc]STA a:Area_Region [e5cf]LDA MAYBE_SCREEN_TRANSITION_MAP,Y [e5d2]STA Area_LoadingScreenIndex [e5d4]JSR Screen_FadeToBlack [e5d7]JMP Game_SetupAndLoadOutsideArea
; ; XREFS: ; Area_SetStateFromDoorDestination ; Player_EnterDoorToOutside ;
[e5da]SUB_RETURN_E5DA:; [$e5da] [e5da]RTS
; ; XREFS: ; Player_EnterDoorToOutside ;
[e5db]DOOR_LOOKUP_REQUIREMENTS:; [$e5db] [e5db].byte $00; [0]: No key required [e5dc].byte $04; [1]: "J" key required [e5dd].byte $00; [2]: No key required [e5de].byte $03; [3]: "Q" key required [e5df].byte $00; [4]: No key required [e5e0].byte $01; [5]: "A" key required [e5e1].byte $00; [6]: No key required [e5e2].byte $07; [7]: Ring of Dworf required [e5e3].byte $00; [8]: No key required [e5e4].byte $08; [9]: Demon's Ring required [e5e5].byte $00; [10]: No key required [e5e6].byte $00; [11]: No key required
; ; XREFS: ; Player_EnterDoorToOutside ;
[e5e7]MAYBE_REGION_TRANSITION_MAP:; [$e5e7] [e5e7].byte REGION_EOLIS; [0]: [e5e8].byte REGION_TRUNK; [1]: [e5e9].byte REGION_EOLIS; [2]: [e5ea].byte REGION_MIST; [3]: [e5eb].byte REGION_TRUNK; [4]: [e5ec].byte REGION_BRANCH; [5]: [e5ed].byte REGION_MIST; [6]: [e5ee].byte REGION_DARTMOOR; [7]: [e5ef].byte REGION_BRANCH; [8]: [e5f0].byte REGION_EVIL_FORTRESS; [9]: [e5f1].byte REGION_DARTMOOR; [10]: [e5f2].byte REGION_EVIL_FORTRESS; [11]:
; ; XREFS: ; Player_EnterDoorToOutside ;
[e5f3]MAYBE_SCREEN_TRANSITION_MAP:; [$e5f3] [e5f3].byte $00; [0]: [e5f4].byte $00; [1]: [e5f5].byte $08; [2]: [e5f6].byte $11; [3]: [e5f7].byte $28; [4]: [e5f8].byte $00; [5]: [e5f9].byte $1f; [6]: [e5fa].byte $00; [7]: [e5fb].byte $27; [8]: [e5fc].byte $08; [9]: [e5fd].byte $0e; [10]: [e5fe].byte $0e; [11]:
; ; XREFS: ; Player_EnterDoorToInside ;
[e5ff]BUILDING_MUSIC:; [$e5ff] [e5ff].byte MUSIC_KINGS_ROOM; [0]: King's Room [e600].byte MUSIC_TEMPLE; [1]: Temple [e601].byte MUSIC_SHOP; [2]: Hospital [e602].byte MUSIC_SHOP; [3]: Tavern [e603].byte MUSIC_SHOP; [4]: Tool Shop [e604].byte MUSIC_SHOP; [5]: Key Shop [e605].byte MUSIC_SHOP; [6]: House [e606].byte MUSIC_SHOP; [7]: Meat Shop [e607].byte MUSIC_SHOP; [8]: Martial Arts [e608].byte MUSIC_SHOP; [9]: Magic Shop
;============================================================================ ; Mapping of building indexes to palette indexes. ; ; XREFS: ; Player_EnterDoorToInside ;============================================================================
; ; XREFS: ; Player_EnterDoorToInside ;
[e609]BUILDING_PALETTES:; [$e609] [e609].byte PALETTE_KINGS_ROOM; [0]: King's Room [e60a].byte PALETTE_TEMPLE; [1]: Temple [e60b].byte PALETTE_HOSPITAL; [2]: Hospital [e60c].byte PALETTE_TAVERN; [3]: Tavern [e60d].byte PALETTE_TOOL_SHOP; [4]: Tool Shop [e60e].byte PALETTE_KEY_SHOP; [5]: Key Shop [e60f].byte PALETTE_HOUSE; [6]: House [e610].byte PALETTE_MEAT_SHOP; [7]: Meat Shop [e611].byte PALETTE_MARTIAL_ARTS; [8]: Martial Arts [e612].byte PALETTE_MAGIC_SHOP; [9]: Magic Shop
; ; XREFS: ; Player_EnterDoorToInside ;
[e613]BUILDING_MAYBE_TILES_INDEXES:; [$e613] [e613].byte $06; [0]: King's Room [e614].byte $06; [1]: Temple [e615].byte $06; [2]: Hospital [e616].byte $07; [3]: Tavern [e617].byte $07; [4]: Tool Shop [e618].byte $07; [5]: Key Shop [e619].byte $07; [6]: House [e61a].byte $07; [7]: Meat Shop [e61b].byte $08; [8]: Martial Arts [e61c].byte $08; [9]: Magic Shop
; ; XREFS: ; Player_EnterDoorToInside ;
[e61d]BUILDING_START_POSITIONS:; [$e61d] [e61d].byte $9e; [0]: King's Room [e61e].byte $9e; [1]: Temple [e61f].byte $9e; [2]: Hospital [e620].byte $8e; [3]: Tavern [e621].byte $7e; [4]: Tool Shop [e622].byte $7e; [5]: Key Shop [e623].byte $7e; [6]: House [e624].byte $7e; [7]: Meat Shop [e625].byte $8e; [8]: Martial Arts [e626].byte $8e; [9]: Magic Shop
;============================================================================ ; Return whether the screen is the edge in a given direction. ; ; INPUTS: ; Y: ; The direction to check relative to the screen. ; ; 0 = Screen left ; 1 = Screen right ; 2 = Screen above ; 3 = Screen below ; ; OUTPUTS: ; C: ; 1 if this screen is the edge. ; 0 if it has a neighboring screen. ; ; XREFS: ; Area_ScrollScreenDown ; Player_CheckHandleClimbUp ; Player_ContinueHandleClimbOrJump ; Player_MoveDownScreen ; Player_TryMoveLeft ; Player_TryMoveRight ;============================================================================
[e627]Screen_IsEdge:; [$e627] [e627]LDA Area_ScreenToTheLeft,Y; Load the screen ID at the given neighbor. [e62a]CMP #$ff; If 0xFF, C = 1. Else, C = 0 [e62c]RTS
;============================================================================ ; TODO: Document Area_CanMoveUp ; ; INPUTS: ; None. ; ; OUTPUTS: ; A ; ; XREFS: ; Area_CanPlayerMoveRight ;============================================================================
[e62d]Area_CanMoveUp:; [$e62d] [e62d]LDA Player_PosY [e62f]LSR A [e630]LSR A [e631]LSR A [e632]LSR A [e633]TAX [e634]LDY FirstColumnInRightScreen,X [e637]JSR Area_IsBlockImpassable [e63a]STA Blocks_Result [e63c]INX [e63d]LDY FirstColumnInRightScreen,X [e640]JSR Area_IsBlockImpassable [e643]ORA Blocks_Result [e645]STA Blocks_Result [e647]LDA Player_PosY [e649]AND #$0f [e64b]BEQ @LAB_PRG15_MIRROR__e658 [e64d]INX [e64e]LDY FirstColumnInRightScreen,X [e651]JSR Area_IsBlockImpassable [e654]ORA Blocks_Result [e656]STA Blocks_Result [e658]@LAB_PRG15_MIRROR__e658:; [$e658] [e658]LDA Blocks_Result [e65a]RTS
;============================================================================ ; TODO: Document Area_CanPlayerMoveRight ; ; INPUTS: ; None. ; ; OUTPUTS: ; Z ; ; XREFS: ; Player_CheckIfPassable ;============================================================================
[e65b]Area_CanPlayerMoveRight:; [$e65b] [e65b]LDA Player_PosX_Block [e65d]CLC [e65e]ADC #$10 [e660]STA Arg_PixelPosX [e662]BCS Area_CanMoveUp
; ; v-- Fall through --v ;
;============================================================================ ; TODO: Document Area_CanPlayerMoveAtY ; ; INPUTS: ; None. ; ; OUTPUTS: ; A ; ; XREFS: ; Area_CanPlayerMoveLeft ;============================================================================
[e664]Area_CanPlayerMoveAtY:; [$e664] [e664]LDA Player_PosY [e666]STA Arg_PixelPosY [e668]JSR Area_ConvertPixelsToBlockPos [e66b]JSR ScreenBuffer_IsBlockImpassable [e66e]STA Blocks_Result [e670]TXA [e671]CLC [e672]ADC #$10 [e674]TAX [e675]JSR ScreenBuffer_IsBlockImpassable [e678]ORA Blocks_Result [e67a]STA Blocks_Result [e67c]LDA Player_PosY [e67e]AND #$0f [e680]BEQ @_returnResult [e682]TXA [e683]CLC [e684]ADC #$10 [e686]TAX [e687]JSR ScreenBuffer_IsBlockImpassable [e68a]ORA Blocks_Result [e68c]STA Blocks_Result [e68e]@_returnResult:; [$e68e] [e68e]LDA Blocks_Result [e690]RTS
;============================================================================ ; TODO: Document Area_CanPlayerMoveLeft ; ; INPUTS: ; None. ; ; OUTPUTS: ; Z ; ; XREFS: ; Player_CheckIfPassable ;============================================================================
[e691]Area_CanPlayerMoveLeft:; [$e691] [e691]LDA Player_PosX_Block [e693]SEC [e694]SBC #$01 [e696]STA Arg_PixelPosX [e698]BCS Area_CanPlayerMoveAtY [e69a]LDA Player_PosY [e69c]LSR A [e69d]LSR A [e69e]LSR A [e69f]LSR A [e6a0]TAX [e6a1]LDY LastColumnLeftScreen,X [e6a4]JSR Area_IsBlockImpassable [e6a7]STA Blocks_Result [e6a9]INX [e6aa]LDY LastColumnLeftScreen,X [e6ad]JSR Area_IsBlockImpassable [e6b0]ORA Blocks_Result [e6b2]STA Blocks_Result [e6b4]LDA Player_PosY [e6b6]AND #$0f [e6b8]BEQ @LAB_PRG15_MIRROR__e6c5 [e6ba]INX [e6bb]LDY LastColumnLeftScreen,X [e6be]JSR Area_IsBlockImpassable [e6c1]ORA Blocks_Result [e6c3]STA Blocks_Result [e6c5]@LAB_PRG15_MIRROR__e6c5:; [$e6c5] [e6c5]LDA Blocks_Result [e6c7]RTS
;============================================================================ ; TODO: Document Player_CheckIfPassable ; ; INPUTS: ; X ; ; OUTPUTS: ; Z ; ; XREFS: ; Area_ScrollScreenDown ; Player_CheckHandleClimbDown ; Player_CheckHandleClimbMaybeSide ; Player_CheckHandleClimbUp ; Player_ContinueHandleClimbOrJump ; Player_TryMoveLeft ; Player_TryMoveRight ;============================================================================
[e6c8]Player_CheckIfPassable:; [$e6c8] [e6c8]TXA [e6c9]BEQ Area_CanPlayerMoveLeft [e6cb]DEX [e6cc]BEQ Area_CanPlayerMoveRight [e6ce]DEX [e6cf]BEQ Area_CanPlayerMoveUp [e6d1]LDA Player_PosY [e6d3]CLC [e6d4]ADC #$20 [e6d6]STA Arg_PixelPosY [e6d8]CMP #$d0 [e6da]BCC Area_CanMoveImmediatelyRight [e6dc]LDA Player_PosX_Block [e6de]LSR A [e6df]LSR A [e6e0]LSR A [e6e1]LSR A [e6e2]TAX [e6e3]LDY FirstRowBelowScreen,X [e6e6]JSR Area_IsBlockImpassable [e6e9]STA Blocks_Result [e6eb]LDA Player_PosX_Block [e6ed]AND #$0f [e6ef]BEQ @_returnResult [e6f1]INX [e6f2]LDY FirstRowBelowScreen,X [e6f5]JSR Area_IsBlockImpassable [e6f8]ORA Blocks_Result [e6fa]STA Blocks_Result [e6fc]@_returnResult:; [$e6fc] [e6fc]LDA Blocks_Result [e6fe]RTS
;============================================================================ ; TODO: Document Area_CanMoveImmediatelyRight ; ; INPUTS: ; None. ; ; OUTPUTS: ; A ; ; XREFS: ; Area_CanPlayerMoveUp ; Player_CheckIfPassable ;============================================================================
[e6ff]Area_CanMoveImmediatelyRight:; [$e6ff] [e6ff]LDA Player_PosX_Block [e701]CLC [e702]ADC #$04 [e704]STA Arg_PixelPosX [e706]JSR Area_ConvertPixelsToBlockPos [e709]JSR ScreenBuffer_IsBlockImpassable [e70c]STA Blocks_Result [e70e]LDA Player_PosX_Block [e710]AND #$0f [e712]SEC [e713]SBC #$04 [e715]CMP #$08 [e717]BCS @_returnResult [e719]INX [e71a]JSR ScreenBuffer_IsBlockImpassable [e71d]ORA Blocks_Result [e71f]STA Blocks_Result [e721]@_returnResult:; [$e721] [e721]LDA Blocks_Result [e723]RTS
;============================================================================ ; TODO: Document Area_CanPlayerMoveUp ; ; INPUTS: ; None. ; ; OUTPUTS: ; Z ; ; XREFS: ; Player_CheckIfPassable ;============================================================================
[e724]Area_CanPlayerMoveUp:; [$e724] [e724]LDA Player_PosY [e726]SEC [e727]SBC #$01 [e729]STA Arg_PixelPosY [e72b]CMP #$f0 [e72d]BCC Area_CanMoveImmediatelyRight [e72f]LDA Player_PosX_Block [e731]LSR A [e732]LSR A [e733]LSR A [e734]LSR A [e735]TAX [e736]LDY LastRowAboveScreen,X [e739]JSR Area_IsBlockImpassable [e73c]STA Blocks_Result [e73e]LDA Player_PosX_Block [e740]AND #$0f [e742]BEQ @_returnResult [e744]INX [e745]LDY LastRowAboveScreen,X [e748]JSR Area_IsBlockImpassable [e74b]ORA Blocks_Result [e74d]STA Blocks_Result [e74f]@_returnResult:; [$e74f] [e74f]LDA Blocks_Result [e751]RTS
;============================================================================ ; TODO: Document Player_CheckIfOnLadder ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_CheckHandleClimb ;============================================================================
[e752]Player_CheckIfOnLadder:; [$e752]
; ; Build the target block pixel position to look up. ; ; This will offset the X by +7, which helps grab onto the block ; if just to the left of it. ;
[e752]LDA Player_PosX_Block; A = player X position [e754]CLC [e755]ADC #$07; Add 7. [e757]STA Arg_PixelPosX; Store the player's new X pixel position [e759]LDA Player_PosY; Load the player's Y position [e75b]STA Arg_PixelPosY; Store the player's Y pixel position
; ; Check if the block at this position is climbable. ;
[e75d]JSR Area_ConvertPixelsToBlockPos; Convert to a block position. [e760]LDY ScreenBuffer,X; Load the block at that position. [e763]JSR Area_GetBlockProperty; Load the block property for that block. [e766]STA Blocks_Result; Store it. [e768]JSR Area_IsBlockClimbable; Check if it's climbable. [e76b]BCS @_resetCanClimb
; ; The block is not climbable. Check if the player is ; overlapping a block to the right, and check that. ;
[e76d]LDA Player_PosX_Block; A = player X position. [e76f]AND #$0f [e771]CMP #$08 [e773]BNE @_checkBlockBelow [e775]INX [e776]LDY ScreenBuffer,X [e779]JSR Area_GetBlockProperty [e77c]STA Blocks_Result [e77e]JSR Area_IsBlockClimbable [e781]BCS @_resetCanClimb [e783]@_checkBlockBelow:; [$e783] [e783]LDA Player_PosX_Block [e785]CLC [e786]ADC #$07 [e788]STA Arg_PixelPosX [e78a]LDA Player_PosY [e78c]CLC [e78d]ADC #$1f [e78f]STA Arg_PixelPosY [e791]JSR Area_ConvertPixelsToBlockPos [e794]LDY ScreenBuffer,X [e797]JSR Area_GetBlockProperty [e79a]STA Blocks_Result [e79c]JSR Area_IsBlockClimbable [e79f]BCS @_resetCanClimb [e7a1]LDA Player_PosX_Block [e7a3]AND #$0f [e7a5]CMP #$08 [e7a7]BNE @_resetCanClimb [e7a9]INX [e7aa]LDY ScreenBuffer,X [e7ad]JSR Area_GetBlockProperty [e7b0]STA Blocks_Result
; ; Reset the can-climb bit. ;
[e7b2]@_resetCanClimb:; [$e7b2] [e7b2]LDA Player_Flags; Load the player's flags. [e7b4]AND #$f7; Clear the can-climb bit. [e7b6]JSR Area_IsBlockClimbable; Is the current block climbable? [e7b9]BCC @_clearBit5AndReturn; If not, jump.
; ; Set the can-climb bit. ;
[e7bb]ORA #$08; Else, set the can-climbable bit. [e7bd]STA Player_Flags; Set the ladder flag bit [e7bf]RTS [e7c0]@_clearBit5AndReturn:; [$e7c0] [e7c0]AND #$ef; Clear bit 5. [e7c2]STA Player_Flags; Store it. [e7c4]RTS
;============================================================================ ; Set state from the area at the other end of the door. ; ; This checks if the player is in front of a door (being ; careful to check any overlapping blocks) and then looks up ; the area at the other end of the door. ; ; If a matching door is found (one that maps to the current ; screen and block position), then a new screen index, start ; position, palette, and door key requirement for the ; current screen will be set. This prepares for switching to ; the new area. ; ; INPUTS: ; Player_PosX_Block: ; The X position of the player. ; ; Player_PosY: ; The Y position of the player. ; ; OUTPUTS: ; Blocks_Result: ; 1 if a door could be entered. ; 0 if one could not. ; ; Screen_StartPosYX: ; The start position when switching to the new area. ; ; Area_LoadingScreenIndex: ; The new screen when switching to the new area. ; ; Screen_DestPaletteOrIndex: ; The new palette when switching to the new area. ; ; CurrentDoor_KeyRequirement: ; The key requirement for the matching doro when ; switching to the new area. ; ; Temp_Addr_L ; Temp_Addr_U ; Arg_PixelPosX: ; Arg_PixelPosX: ; Temp_BlockPos2 ; Clobbered. ; ; CALLS: ; Area_ConvertPixelsToBlockPos ; Area_GetBlockProperty ; ; XREFS: ; Player_CheckHandleEnterDoor ;============================================================================
[e7c5]Area_SetStateFromDoorDestination:; [$e7c5]
; ; Convert the player position to a normalized block position. ;
[e7c5]LDA Player_PosX_Block; Load the player's full X position. [e7c7]CLC [e7c8]ADC #$07; Add 7. [e7ca]STA Arg_PixelPosX; Store as the X pixel position argument. [e7cc]LDA Player_PosY; Load the player's full Y position. [e7ce]STA Arg_PixelPosY; Store as the Y pixel position argument. [e7d0]JSR Area_ConvertPixelsToBlockPos; Conver to block positions.
; ; Load the block at this position from the screen and ; check what it is. ;
[e7d3]LDY ScreenBuffer,X; Load the block from the screen buffer. [e7d6]JSR Area_GetBlockProperty; Get the block property. [e7d9]CMP #$03; Is it a door? [e7db]BEQ @_blockIsDoor
; ; It's not a door. The player may still be near a door. ; Let's round to the nearest block to the right and check ; again. ;
[e7dd]LDA Player_PosX_Block; Load the player's full X position. [e7df]AND #$0f; Check the lower nibble. [e7e1]CMP #$08; Is it on a full block boundary? [e7e3]BNE @_blockIsNotDoor; _blockIsAir [e7e5]INX; Increment the block position.
; ; We normalized to the next overlapped block. Now check ; to see if it's a door. ;
[e7e6]LDY ScreenBuffer,X; Load the block at that position. [e7e9]JSR Area_GetBlockProperty; Load the block property. [e7ec]CMP #$03; Is it a door? [e7ee]BEQ @_blockIsDoor; If so, jump.
; ; The player was not standing in front of a door. ; Consider this block passable. ;
[e7f0]@_blockIsNotDoor:; [$e7f0] [e7f0]LDA #$00 [e7f2]STA Blocks_Result; Set the resulting block property as air. [e7f4]RTS
; ; The player is standing in front of a door. ; ; Check to see where they are. ;
[e7f5]@_blockIsDoor:; [$e7f5] [e7f5]LDA Area_CurrentArea; Load the current region. [e7f7]CMP #$04; Is this the area around Victim(?) ? [e7f9]BNE @LAB_PRG15_MIRROR__e7fe; If not, jump and continue on.
; ; We won't be handling anything in this area. We're done here. ;
[e7fb]JMP SUB_RETURN_E5DA; Else, we're done. [e7fe]@LAB_PRG15_MIRROR__e7fe:; [$e7fe] [e7fe]LDA #$01 [e800]STA Blocks_Result; Mark the resulting block as solid by default. [e802]STX Temp_BlockPos; Store our block X position temporarily.
; ; Get the location of the doors for this area and store ; the address for access below. ;
[e804]LDA CurrentArea_DoorLocationsAddr; Load the lower byte of the doors address. [e806]STA Temp_Addr_L; Store it temporarily. [e808]LDA CurrentArea_DoorLocationsAddr_U; Load the upper byte. [e80a]STA Temp_Addr_U; Store it temporarily.
; ; Switch to Bank 3 (level data). ;
[e80c]LDA a:CurrentROMBank; Load the current ROM bank. [e80f]PHA; Push it to the stack. [e810]LDX #$03 [e812]JSR MMC1_UpdateROMBank; Load bank 3.
; ; Check the byte stored in address stored starting at ; Temp_Addr_L to see if it's an air block or the screen ; index. ;
[e815]@_doorCheckLoop:; [$e815] [e815]LDY #$00; Y = 0 [e817]LDA (Temp_Addr_L),Y; Load the first byte from the referenced address. [e819]CMP #$ff; Is it 0xFF? [e81b]BEQ @_returnAirBlock; If so, consider this air and return. [e81d]CMP Area_CurrentScreen; Is it the current screen index? [e81f]BNE @LAB_PRG15_MIRROR__e852; If not, jump.
; ; That was a match. ; ; Next, check the next byte in the address referenced by ; Temp_Addr_L to see if it's the stored block position ; from above (Temp_BlockPos). ;
[e821]INY; Y++ [e822]LDA (Temp_Addr_L),Y; Load the next byte from the referenced address. [e824]CMP Temp_BlockPos; Is it the block position we stored? [e826]BNE @LAB_PRG15_MIRROR__e852; If not, jump.
; ; That was a match too. ; ; Load the next byte referenced in Temp_Addr_L and store ; that ; as a block position in Temp_BlockPos2. ;
[e828]INY; Y++ [e829]LDA (Temp_Addr_L),Y; Load the next byte from the referenced address. [e82b]STA Temp_BlockPos2; Store that byte as a temporary block position.
; ; Load the next byte from the address referenced in ; Temp_Addr_L and store as the area's start X/Y ; location (Screen_StartPosYX). ;
[e82d]INY; Y++ [e82e]LDA (Temp_Addr_L),Y; Load the next byte from the referenced address. [e830]STA Screen_StartPosYX; Store as the area's starting X/Y position. [e832]LDA Temp_BlockPos2; A = Byte we had just stored up above.
; ; Check the current area. If it's around Mascon somewhere, ; we'll need to reduce the value by 32. ; ; TODO: Check why this is. ;
[e834]LDY Area_CurrentArea; Y = current area [e836]CPY #$03; Is the current area 3? [e838]BNE @LAB_PRG15_MIRROR__e83d; If not, jump.
; ; We'll need to normalize the block position. ;
[e83a]SEC [e83b]SBC #$20; Reduce it by 32. [e83d]@LAB_PRG15_MIRROR__e83d:; [$e83d] [e83d]ASL A; Multiply the block position by 4. [e83e]ASL A
; ; Load state from the door destination at this block. ; ; We'll load the screen index, palette, and the key ; requirement. ;
[e83f]TAY; Y = A (block position) [e840]LDA (CurrentArea_DoorDestinationsAddr),Y; Load the screen index from the door destination. [e842]STA Area_LoadingScreenIndex; Store it as the new screen index. [e844]INY; Y++ [e845]LDA (CurrentArea_DoorDestinationsAddr),Y; Load the palette from the door destination. [e847]STA Screen_DestPaletteOrIndex; Store it as the new palette. [e849]INY; Y++ [e84a]LDA (CurrentArea_DoorDestinationsAddr),Y; Load the key requirement. [e84c]STA a:CurrentDoor_KeyRequirement; Store it. [e84f]JMP @_restoreBankAndReturn; We're done. Restore our bank and set our results.
; ; Skip the 4 bytes of information of the area at the other end ; of the door destination. We'll set up to process the next one. ;
[e852]@LAB_PRG15_MIRROR__e852:; [$e852] [e852]LDA Temp_Addr_L; Load the lower byte of the door destination address we stored. [e854]CLC [e855]ADC #$04; Add 4 (skip the information found on the other side of that door). [e857]STA Temp_Addr_L; Store it back out. [e859]LDA Temp_Addr_U; Load the upper byte of the address. [e85b]ADC #$00; Add the carry flag, if the lower byte overflowed. [e85d]STA Temp_Addr_U; Store it. [e85f]JMP @_doorCheckLoop
; ; Consider this air. ;
[e862]@_returnAirBlock:; [$e862] [e862]LDA #$00 [e864]STA Blocks_Result; Set the resulting block property to 0 (air).
; ; Restore the previous ROM bank and return. ;
[e866]@_restoreBankAndReturn:; [$e866] [e866]PLA; Pop the previous bank from the stack. [e867]TAX; X = A (bank) [e868]JSR MMC1_UpdateROMBank; Update to the bank. [e86b]RTS
;============================================================================ ; Convert a screen position from pixels to blocks. ; ; This will operate off of the stored PixelPosX and PixelPosY. ; ; INPUTS: ; Arg_PixelPosX: ; The X pixel position to convert. ; ; Arg_PixelPosY: ; The Y pixel position to convert. ; ; Outputs: ; X: ; The new block position. ; ; XREFS: ; CastMagic_CheckDirection_CheckImpassable ; CurrentSprite_CalculateVisibility ; CurrentSprite_CanMoveInDirection ; FUN_PRG14__854c ; Player_CalculateVisibility ; SpriteBehavior_NecronAides ; Sprites_MoveRight__86c6 ; Area_CanMoveImmediatelyRight ; Area_CanPlayerMoveAtY ; Area_CheckCanClimbAdjacent ; Area_SetStateFromDoorDestination ; CastMagic_CalculateVisibility ; Player_CheckIfOnLadder ; Player_CheckOnBreakableBlock ; Player_CheckPushingBlock ; Player_CheckSwitchScreen ; Player_UseMattock ;============================================================================
[e86c]Area_ConvertPixelsToBlockPos:; [$e86c] [e86c]LDA Arg_PixelPosY; Load the stored Y coordinate for comparison [e86e]AND #$f0; Clear out the lower nibble [e870]STA Temp_00 [e872]LDA Arg_PixelPosX; Load the stored X coordinate for comparison [e874]LSR A; Convert X to block positions [e875]LSR A [e876]LSR A [e877]LSR A [e878]ORA Temp_00; Include the Y block position [e87a]TAX; Store in X and return [e87b]RTS
;============================================================================ ; Return whether a block at a given screen buffer index is impassable. ; ; INPUTS: ; Y: ; The index into the blocks table. ; ; BLOCK_PROPERTY_IMPASSABLE_MAP: ; A map of impassable blocks. ; ; OUTPUTS: ; A: ; 1 if the block is impassable. ; 0 if it is passable. ; ; CALLS: ; Area_GetBlockProperty ; ; XREFS: ; CastMagic_CheckDirection_CheckImpassable ; CurrentSprite_CanMoveInDirection ; Sprites_MoveRight__86c6 ; Area_CanMoveImmediatelyRight ; Area_CanPlayerMoveAtY ; Area_CheckCanClimbAdjacent ;============================================================================
[e87c]ScreenBuffer_IsBlockImpassable:; [$e87c] [e87c]LDY ScreenBuffer,X; Load the block at the given index, and fall through.
; ; v-- Fall through --v ;
;============================================================================ ; Return whether a block at a given block property index is impassable. ; ; INPUTS: ; Y: ; The index into the blocks table. ; ; BLOCK_PROPERTY_IMPASSABLE_MAP: ; A map of impassable blocks. ; ; OUTPUTS: ; A: ; 1 if the block is impassable. ; 0 if it is passable. ; ; CALLS: ; Area_GetBlockProperty ; ; XREFS: ; Area_CanMoveUp ; Area_CanPlayerMoveLeft ; Area_CanPlayerMoveUp ; Player_CheckIfPassable ;============================================================================
[e87f]Area_IsBlockImpassable:; [$e87f] [e87f]JSR Area_GetBlockProperty; Get the property of the provided block, and fall through.
; ; v-- Fall through --v ;
;============================================================================ ; Return whether a block property is an impassable type. ; ; INPUTS: ; A: ; The block property type. ; ; BLOCK_PROPERTY_IMPASSABLE_MAP: ; The map of impassable block properties. ; ; OUTPUTS: ; A: ; 1 if the block is impassable. ; 0 if it is passable. ; ; XREFS: ; Area_IsBlockImpassableOrLadder ;============================================================================
[e882]Area_GetFromImpassableMap:; [$e882] [e882]TAY; Y = A [e883]LDA BLOCK_PROPERTY_IMPASSABLE_MAP,Y; A = Impassable flag for the block property type. [e886]RTS; And return it.
;============================================================================ ; Return whether a block at the given screen buffer index is impassable or a ; ladder. ; ; INPUTS: ; X: ; The index within the screen buffer, for ; getting the block property index. ; ; BLOCK_PROPERTY_IMPASSABLE_MAP: ; A map of impassable block types. ; ; OUTPUTS: ; A: ; 1 if the block is impassable or a ladder. ; 0 if it is passable. ; ; CALLS: ; Area_GetBlockProperty ; ; XREFS: ; FUN_PRG14__854c ;============================================================================
[e887]Area_IsBlockImpassableOrLadder:; [$e887] [e887]LDY ScreenBuffer,X; Load the block property offset from the screen buffer at X. [e88a]JSR Area_GetBlockProperty; Load the block property. [e88d]CMP #$02; Is it a ladder? [e88f]BNE Area_GetFromImpassableMap; If not a ladder, look up from the passable map.
; ; This is a ladder. ;
[e891]LDA #$01; Return true. [e893]RTS
;============================================================================ ; DEADCODE: Set whether a block is truly air. ; ; This loads the specified block property and then ; determines if it's actually air. This will be stored in ; Blocks_Result. ; ; INPUTS: ; X: ; The index into the screen buffer. ; ; OUTPUTS: ; Blocks_Result: ; The result of the check. ; ; 1 for solid-like. ; 0 for air-like. ; ; CALLS: ; Area_GetBlockProperty ;============================================================================
[e894]Area_StoreBlockIsAir:; [$e894]
; ; Load the block property at this screen buffer offset. ;
[e894]LDY ScreenBuffer,X; Y = block property at screen buffer X [e897]JSR Area_GetBlockProperty; Load the block property. [e89a]STA Blocks_Result; Store that in a temporary variable.
; ; Check if this is a solid block. ;
[e89c]CMP #$01; Is it solid? [e89e]BEQ @_blockIsSolid; If so, then jump.
; ; Check if this is a ladder. ;
[e8a0]CMP #$02; Is it a ladder? [e8a2]BEQ @_blockIsSolid; If so, then jump.
; ; Check if this is a block transitioning to another screen. ;
[e8a4]CMP #$0a; Is this a transition to a screen? [e8a6]BEQ @_blockIsSolid; If so, then jump.
; ; It's not solid. Store a result of 0 (air). ;
[e8a8]LDA #$00 [e8aa]STA Blocks_Result; Set result = 0. [e8ac]RTS
; ; It is solid. Store a result of 1 (solid). ;
[e8ad]@_blockIsSolid:; [$e8ad] [e8ad]LDA #$01 [e8af]STA Blocks_Result; Set result = 1. [e8b1]RTS
;============================================================================ ; Determine if the current block is climbable. ; ; This will check the provided block property (stored in ; a temp variable) and check if it can be climbed. ; ; INPUTS: ; Blocks_Result: ; The block property to check. ; ; OUTPUTS: ; C: ; 1 if it's climbable. ; 0 if it is not. ; ; XREFS: ; Player_CheckIfOnLadder ;============================================================================
[e8b2]Area_IsBlockClimbable:; [$e8b2] [e8b2]PHA; Push A to the stack. [e8b3]LDA Blocks_Result; Load the block property provided in the temp variable. [e8b5]CMP #$02; Is it a ladder? [e8b7]BEQ @_isLadder; If so, jump. [e8b9]CMP #$0a; TODO: Is it... something else ladder-like? [e8bb]BNE @_isNotLadder; If not, jump.
; ; This is a ladder. ;
[e8bd]@_isLadder:; [$e8bd] [e8bd]PLA; Pop A. [e8be]SEC; Set carry to 1 as a truthy result. [e8bf]RTS [e8c0]@_isNotLadder:; [$e8c0] [e8c0]PLA; Pop A. [e8c1]CLC; Set carry to 0 as a falsy result. [e8c2]RTS
;============================================================================ ; Return the block property referenced at the given screen buffer index. ; ; This will load the block property referenced in the screen ; buffer and then fall through to ; Area_GetBlockProperty. ; ; Block properties are stored with upper and lower nibbles ; representing different blocks. This will look up the ; appropriate block property based on whether the provided ; index is even or odd, and return a value with just a lower ; nibble set based on the properties. ; ; INPUTS: ; X: ; The index into the screen buffer. ; ; BlockProperties: ; The block properties to load from. ; ; OUTPUTS: ; A: ; A byte containing the property. ; ; XREFS: ; CurrentSprite_CalculateVisibility ; Player_CalculateVisibility ; SpriteBehavior_NecronAides ; CastMagic_CalculateVisibility ; Player_CheckSwitchScreen ;============================================================================
[e8c3]ScreenBuffer_LoadBlockProperty:; [$e8c3] [e8c3]LDY ScreenBuffer,X; Load the block property index from the screen buffer.
; ; v-- Fall through --v ;
;============================================================================ ; Return the block property at the given index. ; ; Block properties are stored with upper and lower nibbles ; representing different blocks. This will look up the ; appropriate block property based on whether the provided ; index is even or odd, and return a value with just a lower ; nibble set based on the properties. ; ; INPUTS: ; Y: ; The index into the block properties. ; ; BlockProperties: ; The block properties to load from. ; ; OUTPUTS: ; A: ; A byte containing the property. ; ; XREFS: ; Area_IsBlockImpassable ; Area_IsBlockImpassableOrLadder ; Area_SetStateFromDoorDestination ; Area_StoreBlockIsAir ; Player_CheckIfOnLadder ; Player_CheckOnBreakableBlock ; Player_CheckPushingBlock ;============================================================================
[e8c6]Area_GetBlockProperty:; [$e8c6] [e8c6]TYA; A = Y [e8c7]LSR A; Y = even block offset based on the index. [e8c8]TAY; Y = A [e8c9]BCC @_isEven; Check whether this is even or odd.
; ; Load the block property at the index, moving the data in ; the upper nibble to the lower nibble. ;
[e8cb]LDA BlockProperties,Y; Load the block property at the index. [e8ce]LSR A; Shift to the lower nibble. [e8cf]LSR A [e8d0]LSR A [e8d1]LSR A [e8d2]RTS
; ; Load the block property, masking out the upper nibble. ;
[e8d3]@_isEven:; [$e8d3] [e8d3]LDA BlockProperties,Y; Load the block property at the index. [e8d6]AND #$0f; Mask out the upper nibble, giving just the lower. [e8d8]RTS
;============================================================================ ; Map of block property IDs to impassibility flags. ; ; XREFS: ; Area_GetFromImpassableMap ;============================================================================
; ; XREFS: ; Area_GetFromImpassableMap ;
[e8d9]BLOCK_PROPERTY_IMPASSABLE_MAP:; [$e8d9] [e8d9].byte $00; [0]: Air [e8da].byte $01; [1]: Solid [e8db].byte $00; [2]: Ladder [e8dc].byte $00; [3]: Door [e8dd].byte $00; [4]: Foreground [e8de].byte $01; [5]: Breakable floor [e8df].byte $01; [6]: Pushable [e8e0].byte $01; [7]: ?? [e8e1].byte $01; [8]: ?? [e8e2].byte $00; [9]: Maybe: Transition down [e8e3].byte $00; [10]: Maybe: Transition up [e8e4].byte $01; [11]: Breakable by Mattock [e8e5].byte $00; [12]: Area transition left-to-right [e8e6].byte $00; [13]: Area transition right-to-left [e8e7].byte $00; [14]: ?? [e8e8].byte $00; [15]: ??
;============================================================================ ; Begin scrolling to an adjacent room. ; ; This will begin clearing out state for the current room ; and then begin scrolling to the next in the given ; direction. ; ; INPUTS: ; Areas_DefaultMusic: ; The default music for the current area. ; ; CurrentScreen_SpecialEventID: ; The event ID for the current screen being ; scrolled from. ; ; OUTPUTS: ; CastMagic_Type: ; The cast magic type, reset to 0xFF. ; ; InterruptCounter: ; The interrupt counter, reset to 0. ; ; Player_MovementTick: ; The player movement tick, reset to 0. ; ; CALLS: ; Area_LoadScrollDataRight ; ; XREFS: ; Area_ScrollScreenDown ; Area_ScrollScreenUp ; Player_CheckHandleClimbUp ; Player_MoveDownScreen ; Player_TryMoveLeft ; Player_TryMoveRight ;============================================================================
[e8e9]Area_ScrollTo:; [$e8e9] [e8e9]LDA a:CurrentScreen_SpecialEventID; Load the current screen's event ID. [e8ec]AND #$7f; Keep the numeric ID. [e8ee]CMP #$01; Is it 1 (boss room)? [e8f0]BNE @_clearStates; If not, jump.
; ; The player is scrolling from a boss room. Reset the music ; back to the default music for the area. ;
[e8f2]LDA a:Areas_DefaultMusic; Load the default music for the area. [e8f5]STA Music_Current; Set as the current music. [e8f7]@_clearStates:; [$e8f7] [e8f7]LDA #$00; A = 0 [e8f9]STA Player_MovementTick; Set player movement tick to 0. [e8fb]STA InterruptCounter; Set interrupt counter to 0.
; ; Clear any cast magic. ;
[e8fd]LDA #$ff; A = 0xFF (unset) [e8ff]STA a:CastMagic_Type; Set as the magic type on screen. [e902]JMP Area_LoadScrollDataRight; Now scroll to the room.
;============================================================================ ; Check if the player is on a breakable block. ; ; This will check the blocks below the player to see if ; they're on a breakable block. If so, the block breaking ; logic will activate. ; ; This is not checked on every frame. ; ; INPUTS: ; InterruptCounter: ; The current interrupt counter. ; ; Player_Flags: ; The player's current flags, used to determine ; the facing direction. ; ; Player_PosX_Block: ; The player's current X position in blocks. ; ; Player_PosY: ; The player's current Y position. ; ; OUTPUTS: ; Arg_PixelPosX: ; Arg_PixelPosY: ; Blocks_Result: ; Clobbered. ; ; CALLS: ; Area_ConvertPixelsToBlockPos ; Area_GetBlockProperty ; Area_HandleBreakableFloor ; ; XREFS: ; GameLoop_UpdatePlayer ;============================================================================
[e905]Player_CheckOnBreakableBlock:; [$e905]
; ; Only check for falling periodically. ;
[e905]LDA InterruptCounter; Load the interrupt counter. [e907]AND #$07; Are we ready to check for falling? [e909]BNE @_return; If not, return.
; ; Begin checking against a block 32 pixels down. ;
[e90b]LDA Player_PosY; A = Player's Y position. [e90d]CLC [e90e]ADC #$20; A += 32 [e910]STA Arg_PixelPosY; Store as the Y argument for block checks. [e912]CMP #$f0; Is it >= 0xEF? (one block below the screen) [e914]BCS @_return; If so, stop falling.
; ; The player may be permitted to fall. Check the block ; it's on. ;
[e916]LDA Player_Flags; Load the player's flags. [e918]AND #$40; Keep only the facing bit. [e91a]ROL A; Shift this into bit 0. [e91b]ROL A [e91c]ROL A [e91d]AND #$01; Mask out everything else. [e91f]PHA; Push it to the stack.
; ; Check the first block the player is overlapping. ;
[e920]TAX; X = facing value. [e921]LDA Player_PosX_Block; A = Player X position. [e923]CLC [e924]ADC @_CHECK_BREAKABLE_BLOCK_X_OFFSETS,X; Load a block offset to round to the nearest overlapping blocks. [e927]STA Arg_PixelPosX; Store as the X position to check. [e929]JSR Area_ConvertPixelsToBlockPos; Convert it to a block position. [e92c]LDY ScreenBuffer,X; Load the block value from the screen. [e92f]STY Blocks_Result; Store as the block result. [e931]JSR Area_GetBlockProperty; And get its corresponding property. [e934]CMP #$05; Is it 5 (breakable floor)? [e936]BNE @_checkNextBlock; If not, check the next overlapping block. [e938]PLA; Pop the facing bit from the stack. [e939]JMP Area_HandleBreakableFloor; Handle breakable floor logic.
; ; Check the second block the player is overlapping. ;
[e93c]@_checkNextBlock:; [$e93c] [e93c]PLA; Pop the facing bit from the stack. [e93d]EOR #$01; XOR the facing bit to check the other direction. [e93f]TAX; X = A [e940]LDA Player_PosX_Block; A = Player X position. [e942]CLC [e943]ADC @_CHECK_BREAKABLE_BLOCK_X_OFFSETS,X; Load a block offset to round to the next overlapping blocks. [e946]STA Arg_PixelPosX; Store as the X position to check. [e948]JSR Area_ConvertPixelsToBlockPos; Convert it to a block position. [e94b]LDY ScreenBuffer,X; Load the block value from the screen. [e94e]STY Blocks_Result; Store as the block result. [e950]JSR Area_GetBlockProperty [e953]CMP #$05; Is it 5 (breakable floor)? [e955]BNE @_return; If not, return. [e957]JMP Area_HandleBreakableFloor; Handle breakable floor logic. [e95a]@_return:; [$e95a] [e95a]RTS
;============================================================================ ; Map of facing bit values to block X offsets for checking breakable blocks. ; ; XREFS: ; Player_CheckOnBreakableBlock ;============================================================================
[e95b]@_CHECK_BREAKABLE_BLOCK_X_OFFSETS:; [$e95b] [e95b].byte $04; [0]: Facing left [e95c].byte $0c; [1]: Facing right
;============================================================================ ; TODO: Document Player_CheckPushingBlock ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; GameLoop_UpdatePlayer ;============================================================================
[e95d]Player_CheckPushingBlock:; [$e95d] [e95d]LDA Joy1_ButtonMask [e95f]AND #$03 [e961]BEQ @LAB_PRG15_MIRROR__e9b9 [e963]LDA a:SpecialItems [e966]AND #$40 [e968]BEQ @LAB_PRG15_MIRROR__e9b9 [e96a]LDA a:Quests [e96d]AND #$07 [e96f]CMP #$07 [e971]BNE @LAB_PRG15_MIRROR__e9b9 [e973]LDA Player_Flags [e975]AND #$40 [e977]ROL A [e978]ROL A [e979]ROL A [e97a]AND #$01 [e97c]TAX [e97d]LDA Player_PosY [e97f]STA Arg_PixelPosY [e981]LDA Player_PosX_Block [e983]CLC [e984]ADC BYTE_ARRAY_PRG15_MIRROR__e9be,X [e987]STA Arg_PixelPosX
; ; Fetch the block property at this position. ;
[e989]JSR Area_ConvertPixelsToBlockPos [e98c]LDY ScreenBuffer,X [e98f]JSR Area_GetBlockProperty
; ; Check if the block is pushable. If so, we'll handle it. ;
[e992]CMP #$06 [e994]BNE @LAB_PRG15_MIRROR__e9b9
; ; This is pushable. Increase the push counter. ; ; We'll play a sound when we hit 0x3F, and finish pushing ; after 0x60. ;
[e996]INC BlockPushCounter [e998]LDA BlockPushCounter [e99a]CMP #$3f [e99c]BNE @LAB_PRG15_MIRROR__e9a3 [e99e]LDA #$0f [e9a0]JSR Sound_PlayEffect [e9a3]@LAB_PRG15_MIRROR__e9a3:; [$e9a3] [e9a3]LDA BlockPushCounter [e9a5]CMP #$60 [e9a7]BCC @_return [e9a9]LDA #$01 [e9ab]STA PathToMascon_Opening [e9ad]STX PathToMascon_FountainCoverPos [e9af]LDA Joy1_ButtonMask [e9b1]LSR A [e9b2]AND #$01 [e9b4]TAX [e9b5]JMP Game_OpenPathToMascon [e9b8]@_return:; [$e9b8] [e9b8]RTS [e9b9]@LAB_PRG15_MIRROR__e9b9:; [$e9b9] [e9b9]LDA #$00 [e9bb]STA BlockPushCounter [e9bd]RTS
; ; XREFS: ; Player_CheckPushingBlock ;
[e9be]BYTE_ARRAY_PRG15_MIRROR__e9be:; [$e9be] [e9be].byte $ff; [0]: [e9bf].byte $10; [1]:
;============================================================================ ; TODO: Document Player_CheckSwitchScreen ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; GameLoop_UpdatePlayer ;============================================================================
[e9c0]Player_CheckSwitchScreen:; [$e9c0]
; ; Check the current block the player is on. ;
[e9c0]LDA Player_PosY [e9c2]STA Arg_PixelPosY [e9c4]LDA Player_PosX_Block [e9c6]STA Arg_PixelPosX [e9c8]JSR Area_ConvertPixelsToBlockPos [e9cb]JSR ScreenBuffer_LoadBlockProperty [e9ce]CMP #$09 [e9d0]BEQ @LAB_PRG15_MIRROR__e9de [e9d2]CMP #$0a [e9d4]BEQ @LAB_PRG15_MIRROR__e9de [e9d6]CMP #$0d [e9d8]BEQ @LAB_PRG15_MIRROR__e9de [e9da]CMP #$0c [e9dc]BNE @_return
; ; Check the next block. ;
[e9de]@LAB_PRG15_MIRROR__e9de:; [$e9de] [e9de]LDA Player_PosX_Block [e9e0]CLC [e9e1]ADC #$0f [e9e3]STA Arg_PixelPosX [e9e5]JSR Area_ConvertPixelsToBlockPos [e9e8]JSR ScreenBuffer_LoadBlockProperty [e9eb]CMP #$09 [e9ed]BEQ @LAB_PRG15_MIRROR__e9fb [e9ef]CMP #$0a [e9f1]BEQ @LAB_PRG15_MIRROR__e9fb [e9f3]CMP #$0d [e9f5]BEQ @LAB_PRG15_MIRROR__e9fb [e9f7]CMP #$0c [e9f9]BNE @_return
; ; Check if this is a horizontal transition block. If so, ; switch the area. ;
[e9fb]@LAB_PRG15_MIRROR__e9fb:; [$e9fb] [e9fb]CMP #$0c [e9fd]BEQ Player_CheckSwitchScreen_SwitchAreaHoriz [e9ff]CMP #$0d [ea01]BEQ Player_CheckSwitchScreen_SwitchAreaHoriz
; ; This is a vertical transition block. Switch the ; area. ;
[ea03]LDA Area_CurrentArea [ea05]ASL A [ea06]TAX [ea07]LDA AREA_SCREEN_COMPARATORS,X [ea0a]STA Temp_Addr_L [ea0c]LDA AREA_SCREEN_COMPARATORS+1,X [ea0f]STA Temp_Addr_U [ea11]LDY #$00 [ea13]@LAB_PRG15_MIRROR__ea13:; [$ea13] [ea13]LDA (Temp_Addr_L),Y [ea15]CMP #$ff [ea17]BEQ @_return [ea19]CMP Area_CurrentScreen [ea1b]BNE @LAB_PRG15_MIRROR__ea2f [ea1d]INY [ea1e]LDA (Temp_Addr_L),Y [ea20]STA Area_LoadingScreenIndex [ea22]INY [ea23]LDA (Temp_Addr_L),Y [ea25]STA Screen_StartPosYX [ea27]INY [ea28]LDA (Temp_Addr_L),Y [ea2a]STA Screen_DestPaletteOrIndex [ea2c]JMP Game_SetupEnterScreen [ea2f]@LAB_PRG15_MIRROR__ea2f:; [$ea2f] [ea2f]INY [ea30]INY [ea31]INY [ea32]INY [ea33]JMP @LAB_PRG15_MIRROR__ea13 [ea36]@_return:; [$ea36] [ea36]RTS
; ; XREFS: ; Player_CheckSwitchScreen ;
[ea37]AREA_SCREEN_COMPARATORS:; [$ea37] [ea37].word BYTE_PRG15_MIRROR__ea4f; Eolis [ea39].word BYTE_PRG15_MIRROR__ea47; Apolune [ea3b].word BYTE_PRG15_MIRROR__ea4f; Forepaw [ea3d].word BYTE_PRG15_MIRROR__ea4f; Mascon [ea3f].word BYTE_PRG15_MIRROR__ea4f; Victim [ea41].word BYTE_PRG15_MIRROR__ea4f; Conflate [ea43].word BYTE_PRG15_MIRROR__ea4f; Daybreak [ea45].word BYTE_PRG15_MIRROR__ea4f; Evil Fortress [ea47]BYTE_PRG15_MIRROR__ea47:; [$ea47] [ea47].byte $0c; Current screen comparator [ea48].byte $16; New screen index [ea49].byte $b3; Y, X [ea4a].byte $06; Area [ea4b].byte $16; Current screen comparator [ea4c].byte $0c; New screen index [ea4d].byte $2d; Y, X [ea4e].byte $06; Area
; ; XREFS: ; AREA_SCREEN_COMPARATORS ; [$PRG15_MIRROR::ea37] ;
[ea4f]BYTE_PRG15_MIRROR__ea4f:; [$ea4f] [ea4f].byte $ff; [$ea4f] byte
;============================================================================ ; Begin exiting a building. ; ; This will face the player to the right and begin playing the ; music in the outside world. ; ; It will then proceed to set up state for the outside area. ; ; INPUTS: ; Player_Flags: ; The current player's flags. ; ; Area_Music_Outside: ; The music played outside in this area. ; ; OUTPUTS: ; Player_Flags: ; The updated player's flags, facing right. ; ; Areas_DefaultMusic: ; The updated music to play. ; ; CALLS: ; Game_SetupExitBuilding ; ; XREFS: ; Player_CheckSwitchScreen_SwitchAreaHoriz ; Player_EnterDoorToInside ;============================================================================
[ea50]Game_BeginExitBuilding:; [$ea50]
; ; Face the player to the right. ;
[ea50]LDA Player_Flags; Load the player flags. [ea52]ORA #$40; Face the player right. [ea54]STA Player_Flags; Store the flags. [ea56]LDA a:Area_Music_Outside; Load the music used outside the building. [ea59]STA a:Areas_DefaultMusic; Set as the music to play. [ea5c]JMP Game_SetupExitBuilding; Jump to finish the exit-building logic.
;============================================================================ ; TODO: Document Player_CheckSwitchScreen_SwitchAreaHoriz ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_CheckSwitchScreen ;============================================================================
[ea5f]Player_CheckSwitchScreen_SwitchAreaHoriz:; [$ea5f]
; ; Check if the player is in the area of Victim. ;
[ea5f]LDA Area_CurrentArea [ea61]CMP #$04 [ea63]BEQ Game_BeginExitBuilding [ea65]ASL A [ea66]TAY [ea67]LDA AREA_TOWN_TRANSITIONS_DATA,Y [ea6a]STA Temp_Addr_L [ea6c]LDA AREA_TOWN_TRANSITIONS_DATA+1,Y [ea6f]STA Temp_Addr_U [ea71]LDY #$00 [ea73]@LAB_PRG15_MIRROR__ea73:; [$ea73] [ea73]LDA (Temp_Addr_L),Y [ea75]CMP #$ff [ea77]BEQ @_return [ea79]CMP Area_CurrentScreen [ea7b]BNE @LAB_PRG15_MIRROR__ea94 [ea7d]INY [ea7e]LDA (Temp_Addr_L),Y [ea80]STA Area_CurrentArea [ea82]INY [ea83]LDA (Temp_Addr_L),Y [ea85]STA Area_LoadingScreenIndex [ea87]INY [ea88]LDA (Temp_Addr_L),Y [ea8a]STA Screen_StartPosYX [ea8c]INY [ea8d]LDA (Temp_Addr_L),Y [ea8f]STA Screen_DestPaletteOrIndex [ea91]JMP Game_SetupNewArea [ea94]@LAB_PRG15_MIRROR__ea94:; [$ea94] [ea94]TYA [ea95]CLC [ea96]ADC #$05 [ea98]TAY [ea99]BCC @LAB_PRG15_MIRROR__ea73 [ea9b]@_return:; [$ea9b] [ea9b]RTS
; ; XREFS: ; Player_CheckSwitchScreen_SwitchAreaHoriz ;
[ea9c]AREA_TOWN_TRANSITIONS_DATA:; [$ea9c] [ea9c].word TOWN_TRANSITIONS_EMPTY; [0]: Eolis [ea9e].word TOWN_TRANSITIONS_APOLUNE; [1]: Apolune [eaa0].word TOWN_TRANSITIONS_FOREPAW; [2]: Forepaw [eaa2].word TOWN_TRANSITIONS_MASCON; [3]: Mascon [eaa4].word TOWN_TRANSITIONS_EMPTY; [4]: Victim [eaa6].word TOWN_TRANSITIONS_CONFLATE; [5]: Conflate [eaa8].word TOWN_TRANSITIONS_DAYBREAK; [6]: Daybreak [eaaa].word TOWN_TRANSITIONS_EMPTY; [7]: Evil Fortress
; ; XREFS: ; AREA_TOWN_TRANSITIONS_DATA ; [$PRG15_MIRROR::ea9e] ;
[eaac]TOWN_TRANSITIONS_APOLUNE:; [$eaac] [eaac].byte $00; [$eaac] byte .byte AREA_MASCON [eaae].byte $02,$92; [$eaae] byte .byte PALETTE_TOWN [eab1].byte $07; Entering Apolune from Trunk (screen 7) [eab2].byte AREA_MASCON; |-> Switch to area 3 [eab3].byte $00; |-> Screen index 0 [eab4].byte $92; |-> Start position Y=9, X=2 [eab5].byte PALETTE_TOWN; '-> Palette 27 [eab6].byte $08; [$eab6] byte .byte AREA_MASCON [eab8].byte $01,$9e; [$eab8] byte .byte PALETTE_TOWN [eabb].byte $1a; Entering Forepaw from Trunk (screen 26) [eabc].byte AREA_MASCON; |-> Switch to area 3 [eabd].byte $02; |-> Screen index 2 [eabe].byte $92; |-> Start position X=2, Y=9 [eabf].byte PALETTE_TOWN; '-> Palette 27 [eac0].byte $1d; [$eac0] byte .byte AREA_MASCON [eac2].byte $03,$9e; [$eac2] byte .byte PALETTE_TOWN [eac5].byte $ff; [$eac5] byte
; ; XREFS: ; AREA_TOWN_TRANSITIONS_DATA ; [$PRG15_MIRROR::eaa2] ;
[eac6]TOWN_TRANSITIONS_MASCON:; [$eac6] [eac6].byte $00; Exiting left from Apolune screen 0 [eac7].byte AREA_APOLUNE; |-> Switch to area 1 [eac8].byte $07; |-> Screen index 6 [eac9].byte $7e; |-> Start position Y=7, X=14 [eaca].byte PALETTE_OUTSIDE; '-> Palette 6 [eacb].byte $01; Exit right from Apolune screen 1 [eacc].byte AREA_APOLUNE; |-> Switch to area 1 [eacd].byte $08; |-> Screen index 8 [eace].byte $71; |-> Start position Y=7, X=1 [eacf].byte PALETTE_OUTSIDE; '-> Palette 6 [ead0].byte $02; Exiting left from Forepaw to Trunk (screen 2) [ead1].byte AREA_APOLUNE; |-> Switch to area 1 [ead2].byte $1a; |-> Screen index 26 [ead3].byte $7e; |-> Start position X=14, Y=7 [ead4].byte PALETTE_OUTSIDE; '-> Palette 6 [ead5].byte $03; Exiting right from Forepaw to Branch (screen 3) [ead6].byte AREA_APOLUNE; |-> Switch to area 3 [ead7].byte $1d; |-> Screen index 29 [ead8].byte $71; |-> Start position X=1, Y=7 [ead9].byte PALETTE_OUTSIDE; |-> Palette 6 [eada].byte $04; Exiting left from Mascon to Mist (screen 4) [eadb].byte AREA_FOREPAW; |-> Switch to area 2 [eadc].byte $09; |-> Screen index 9 [eadd].byte $9e; |-> Start position X=14, Y=9 [eade].byte PALETTE_MIST; '-> Palette 10 [eadf].byte $05; Exiting right from Mascon to Mist (screen 5) [eae0].byte AREA_FOREPAW; |-> Switch to area 2 [eae1].byte $0c; |-> Screen index 12 [eae2].byte $91; |-> Start position X=1, Y=9 [eae3].byte PALETTE_MIST; '-> Palette 10 [eae4].byte $06; Exit left from Victim to Mist from screen 6 [eae5].byte AREA_FOREPAW; |-> Switch to area 2 [eae6].byte $22; |-> Screen index 34 [eae7].byte $9e; |-> Start position X=14, Y=9 [eae8].byte PALETTE_MIST; '-> Palette 10 [eae9].byte $07; Exit right from Victim to Mist on screen 7 [eaea].byte AREA_FOREPAW; |-> Switch to area 7 [eaeb].byte $25; |-> Screen index = 37 [eaec].byte $91; |-> Start position X=1, Y=9 [eaed].byte PALETTE_MIST; '-> Palette 10 [eaee].byte $08; Exit left from Conflate to Branch [eaef].byte AREA_CONFLATE; |-> Switch to area 5 [eaf0].byte $0d; |-> Screen index 13 [eaf1].byte $7e; |-> Start position X=14, Y=7 [eaf2].byte PALETTE_BRANCH; '-> Palette 8 [eaf3].byte $0a; Exit left from Daybreak to Branch [eaf4].byte AREA_CONFLATE; |-> Switch to area 5 [eaf5].byte $23; |-> Screen index 35 [eaf6].byte $7e; |-> Start position X=14, Y=7 [eaf7].byte PALETTE_BRANCH; '-> Palette 8 [eaf8].byte $0b; Exit right from Daybreak to Branch [eaf9].byte AREA_CONFLATE; |-> Switch to area 5 [eafa].byte $24; |-> Screen index 36 [eafb].byte $71; |-> Start position X=1, Y=7 [eafc].byte PALETTE_BRANCH; '-> Palette 8 [eafd].byte $0c; Exit left from Dartmoor to Branch (screen 12) [eafe].byte AREA_DAYBREAK; |-> Switch to area 6 [eaff].byte $03; |-> Screen index 3 [eb00].byte $7e; |-> Start position X=14, Y=7 [eb01].byte PALETTE_DARTMOOR; '-> Palette 12 [eb02].byte $ff; [$eb02] byte
; ; XREFS: ; AREA_TOWN_TRANSITIONS_DATA ; [$PRG15_MIRROR::eaa0] ;
[eb03]TOWN_TRANSITIONS_FOREPAW:; [$eb03] [eb03].byte $09; Entering Mascon from Mist (screen 9) [eb04].byte AREA_MASCON; |-> Switch to area 3 [eb05].byte $04; |-> Screen index 4 [eb06].byte $91; |-> Start position X=1, Y=9 [eb07].byte PALETTE_TOWN; '-> Palette 27 [eb08].byte $0c; Entering left to Mascon from Mist [eb09].byte AREA_MASCON; |-> Switch to area 3 [eb0a].byte $05; |-> Screen index 5 [eb0b].byte $9e; |-> Start position X=14, Y=9 [eb0c].byte PALETTE_TOWN; '-> Palette 27 [eb0d].byte $22; Enering right to Victim from Mist (screen 34) [eb0e].byte AREA_MASCON; |-> Switch to area 3 [eb0f].byte $06; |-> Screen index 6 [eb10].byte $91; |-> Start position X=1, Y=9 [eb11].byte PALETTE_TOWN; '-> Palette 27 [eb12].byte $25; Entering left to Victim from Mist (screen 37) [eb13].byte AREA_MASCON; |-> Switch to area 3 [eb14].byte $07; |-> Screen index 7 [eb15].byte $9e; |-> Start position X=14, Y=9 [eb16].byte PALETTE_TOWN; '-> Palette 27 [eb17].byte $ff; [$eb17] byte
; ; XREFS: ; AREA_TOWN_TRANSITIONS_DATA ; [$PRG15_MIRROR::eaa6] ;
[eb18]TOWN_TRANSITIONS_CONFLATE:; [$eb18] [eb18].byte $0d; Entering right to Conflate from Branch (screen 13) [eb19].byte AREA_MASCON; |-> Switch to area 3 [eb1a].byte $08; |-> Screen index 8 [eb1b].byte $91; |-> Start position X=1, Y=9 [eb1c].byte PALETTE_TOWN; '-> Palette 27 [eb1d].byte $23; Entering Daybreak from Branch (screen 35) [eb1e].byte AREA_MASCON; |-> Switch to area 3 [eb1f].byte $0a; |-> Screen index 10 [eb20].byte $92; |-> Start position X=2, Y=9 [eb21].byte PALETTE_TOWN; '-> Palette 27 [eb22].byte $24; Entering Daybreak from Branch (screen 36) [eb23].byte AREA_MASCON; |-> Switch to area 3 [eb24].byte $0b; |-> Screen index 11 [eb25].byte $9e; |-> Start position X=14, Y=9 [eb26].byte PALETTE_TOWN; '-> Palette 27 [eb27].byte $ff; [$eb27] byte
; ; XREFS: ; AREA_TOWN_TRANSITIONS_DATA ; [$PRG15_MIRROR::eaa8] ;
[eb28]TOWN_TRANSITIONS_DAYBREAK:; [$eb28] [eb28].byte $03; Entering right to Dartmoor from Branch (screen 3) [eb29].byte AREA_MASCON; |-> Switch to area 3 [eb2a].byte $0c; |-> Screen index 12 [eb2b].byte $92; |-> Start position X=2, Y=9 [eb2c].byte PALETTE_TOWN; '-> Palette 27 [eb2d].byte $ff; [$eb2d] byte
; ; XREFS: ; AREA_TOWN_TRANSITIONS_DATA ; [$PRG15_MIRROR::ea9c] ; AREA_TOWN_TRANSITIONS_DATA ; [$PRG15_MIRROR::eaa4] ; AREA_TOWN_TRANSITIONS_DATA ; [$PRG15_MIRROR::eaaa] ;
[eb2e]TOWN_TRANSITIONS_EMPTY:; [$eb2e] [eb2e].byte $ff; [$eb2e] byte
;============================================================================ ; Run the door requirement handler function for the current door. ; ; This will look up the door requirement handler function ; corresponding to CurrentDoor_KeyRequirement and run it, ; checking that the requirements for the door have been met. ; The Checks and behavior are entirely up to that function. ; ; INPUTS: ; CurrentDoor_KeyRequirement: ; The key requirement for the current door. ; ; DOOR_REQUIREMENT_LOOKUP_FUNC_ADDRS: ; The lookup table of door requirement handlers. ; ; OUTPUTS: ; None ; ; XREFS: ; Player_CheckHandleEnterDoor ; Player_EnterDoorToOutside ;============================================================================
[eb2f]Game_RunDoorRequirementHandler:; [$eb2f] [eb2f]LDA a:CurrentDoor_KeyRequirement [eb32]BEQ @_return [eb34]ASL A [eb35]TAY [eb36]LDA DOOR_REQUIREMENT_LOOKUP_FUNC_ADDRS+1,Y [eb39]PHA [eb3a]LDA DOOR_REQUIREMENT_LOOKUP_FUNC_ADDRS,Y [eb3d]PHA [eb3e]@_return:; [$eb3e] [eb3e]RTS
;============================================================================ ; A mapping of door requirement lookup function addresses. ; ; XREFS: ; Game_RunDoorRequirementHandler ;============================================================================
; ; XREFS: ; Game_RunDoorRequirementHandler ;
[eb3f]DOOR_REQUIREMENT_LOOKUP_FUNC_ADDRS:; [$eb3f] [eb3f].byte $3d; [0]: No key, return
; ; XREFS: ; Game_RunDoorRequirementHandler ;
[eb40]DOOR_REQUIREMENT_LOOKUP_FUNC_ADDRS_1:; [$eb40] [eb40].byte $eb; [0]: [eb41].byte $50; [1]: "A" Key [eb42].byte $eb; [1]: [eb43].byte $60; [2]: "K" Key [eb44].byte $eb; [2]: [eb45].byte $70; [3]: "Q" Key [eb46].byte $eb; [3]: [eb47].byte $80; [4]: "J" Key [eb48].byte $eb; [4]: [eb49].byte $90; [5]: "Jo" Key [eb4a].byte $eb; [5]: [eb4b].byte $a0; [6]: Ring of Elf [eb4c].byte $eb; [6]: [eb4d].byte $b0; [7]: Ring of Dworf [eb4e].byte $eb; [7]: [eb4f].byte $c0; [8]: Demon's Ring [eb50].byte $eb; [8]:
;============================================================================ ; Attempt to open a door marked with the "A" Key. ; ; This will check if the player has the "A" Key, and ; if they do, the door will be unlocked and entered. ; The key will be consumed. ; ; If the door can't be unlocked, a message will be ; displayed. ; ; INPUTS: ; SelectedItem: ; The selected item. ; ; CALLS: ; Game_UnlockDoorWithUsableItem ; MMC1_LoadBankAndJump ; ; XREFS: ; DOOR_REQUIREMENT_LOOKUP_FUNC_ADDRS ; [$PRG15_MIRROR::eb41] ;============================================================================
[eb51]Game_OpenDoorWithAKey:; [$eb51] [eb51]LDA a:SelectedItem; Load the selected item. [eb54]CMP #$04; Is this the "A" Key? [eb56]BEQ Game_UnlockDoorWithUsableItem; If so, unlock the door and enter.
; ; Run IScript 0x7D (via jump to IScripts_Begin). ;
[eb58]LDA #$7d; 0x7D == "A" Key required IScript. [eb5a]JSR MMC1_LoadBankAndJump; Run the IScript: [eb5d].byte BANK_12_LOGIC; Bank = 12 [eb5e].word IScripts_Begin-1; Address = IScripts_Begin [eb60]@_afterFarJump:; [$eb60] [eb60]RTS
;============================================================================ ; Attempt to open a door marked with the "K" Key. ; ; This will check if the player has the "K" Key, and ; if they do, the door will be unlocked and entered. ; The key will be consumed. ; ; If the door can't be unlocked, a message will be ; displayed. ; ; INPUTS: ; SelectedItem: ; The selected item. ; ; CALLS: ; Game_UnlockDoorWithUsableItem ; MMC1_LoadBankAndJump ; ; XREFS: ; DOOR_REQUIREMENT_LOOKUP_FUNC_ADDRS ; [$PRG15_MIRROR::eb43] ;============================================================================
[eb61]Game_OpenDoorWithKKey:; [$eb61] [eb61]LDA a:SelectedItem; Load the selected item. [eb64]CMP #$05; Is this the "K" Key? [eb66]BEQ Game_UnlockDoorWithUsableItem; If so, unlock the door and enter.
; ; Run IScript 0x7C (via jump to IScripts_Begin). ;
[eb68]LDA #$7c; 0x7C == "K" Key required IScript. [eb6a]JSR MMC1_LoadBankAndJump; Run the IScript: [eb6d].byte BANK_12_LOGIC; Bank = 12 [eb6e].word IScripts_Begin-1; Address = IScripts_Begin [eb70]@_afterFarJump:; [$eb70] [eb70]RTS
;============================================================================ ; Attempt to open a door marked with the "Q" Key. ; ; This will check if the player has the "Q" Key, and ; if they do, the door will be unlocked and entered. ; The key will be consumed. ; ; If the door can't be unlocked, a message will be ; displayed. ; ; INPUTS: ; SelectedItem: ; The selected item. ; ; CALLS: ; Game_UnlockDoorWithUsableItem ; MMC1_LoadBankAndJump ; ; XREFS: ; DOOR_REQUIREMENT_LOOKUP_FUNC_ADDRS ; [$PRG15_MIRROR::eb45] ;============================================================================
[eb71]Game_OpenDoorWithQKey:; [$eb71] [eb71]LDA a:SelectedItem; Load the selected item. [eb74]CMP #$06; Is this the "Q" Key? [eb76]BEQ Game_UnlockDoorWithUsableItem; If so, unlock the door and enter.
; ; Run IScript 0x7B (via jump to IScripts_Begin). ;
[eb78]LDA #$7b; 0x7B == "Q" Key required IScript. [eb7a]JSR MMC1_LoadBankAndJump; Run the IScript: [eb7d].byte BANK_12_LOGIC; Bank = 12 [eb7e].word IScripts_Begin-1; Address = IScripts_Begin [eb80]@_afterFarJump:; [$eb80] [eb80]RTS
;============================================================================ ; Attempt to open a door marked with the "J" Key. ; ; This will check if the player has the "J" Key, and ; if they do, the door will be unlocked and entered. ; The key will be consumed. ; ; If the door can't be unlocked, a message will be ; displayed. ; ; INPUTS: ; SelectedItem: ; The selected item. ; ; CALLS: ; Game_UnlockDoorWithUsableItem ; MMC1_LoadBankAndJump ; ; XREFS: ; DOOR_REQUIREMENT_LOOKUP_FUNC_ADDRS ; [$PRG15_MIRROR::eb47] ;============================================================================
[eb81]Game_OpenDoorWithJKey:; [$eb81] [eb81]LDA a:SelectedItem; Load the selected item. [eb84]CMP #$07; Is this the "J" Key? [eb86]BEQ Game_UnlockDoorWithUsableItem; If so, unlock the door and enter.
; ; Run IScript 0x7 (via jump to IScripts_Begin). ;
[eb88]LDA #$02; 0x02 == "J" Key required IScript. [eb8a]JSR MMC1_LoadBankAndJump; Run the IScript: [eb8d].byte BANK_12_LOGIC; Bank = 12 [eb8e].word IScripts_Begin-1; Address = IScripts_Begin [eb90]@_afterFarJump:; [$eb90] [eb90]RTS
;============================================================================ ; Attempt to open a door marked with the "Jo" Key. ; ; This will check if the player has the "Jo" Key, and ; if they do, the door will be unlocked and entered. ; The key will be consumed. ; ; If the door can't be unlocked, a message will be ; displayed. ; ; INPUTS: ; SelectedItem: ; The selected item. ; ; CALLS: ; Game_UnlockDoorWithUsableItem ; MMC1_LoadBankAndJump ; ; XREFS: ; DOOR_REQUIREMENT_LOOKUP_FUNC_ADDRS ; [$PRG15_MIRROR::eb49] ;============================================================================
[eb91]Game_OpenDoorWithJoKey:; [$eb91] [eb91]LDA a:SelectedItem; Load the selected item. [eb94]CMP #$08; Is this the "Jo" Key? [eb96]BEQ Game_UnlockDoorWithUsableItem; If so, unlock the door and enter.
; ; Run IScript 0x7E (via jump to IScripts_Begin). ;
[eb98]LDA #$7e; 0x7E == "Jo" Key required IScript. [eb9a]JSR MMC1_LoadBankAndJump; Run the IScript: [eb9d].byte BANK_12_LOGIC; Bank = 12 [eb9e].word IScripts_Begin-1; Address = IScripts_Begin [eba0]@_afterFarJump:; [$eba0] [eba0]RTS
;============================================================================ ; Attempt to open a door marked with the Ring of Elf. ; ; This will check if the player has the Ring of Elf, and ; if they do, the door will be unlocked and entered. ; ; If the door can't be unlocked, a message will be ; displayed. ; ; INPUTS: ; SelectedItem: ; The selected item. ; ; CALLS: ; Game_UnlockDoorWithUsableItem ; MMC1_LoadBankAndJump ; ; XREFS: ; DOOR_REQUIREMENT_LOOKUP_FUNC_ADDRS ; [$PRG15_MIRROR::eb4b] ;============================================================================
[eba1]Game_OpenDoorWithRingOfElf:; [$eba1] [eba1]LDA a:SpecialItems; Load the special items. [eba4]AND #$80; Does the player have the Ring of Elf? [eba6]BNE Game_UnlockDoor; If so, unlock the door and enter.
; ; Run IScript 0x7F (via jump to IScripts_Begin). ;
[eba8]LDA #$7f; 0x7F == Ring required IScript. [ebaa]JSR MMC1_LoadBankAndJump; Run the IScript: [ebad].byte BANK_12_LOGIC; Bank = 12 [ebae].word IScripts_Begin-1; Address = IScripts_Begin [ebb0]@_afterFarJump:; [$ebb0] [ebb0]RTS
;============================================================================ ; Attempt to open a door marked with the Ring of Dworf. ; ; This will check if the player has the Ring of Dworf, and ; if they do, the door will be unlocked and entered. ; ; If the door can't be unlocked, a message will be ; displayed. ; ; INPUTS: ; SelectedItem: ; The selected item. ; ; CALLS: ; Game_UnlockDoorWithUsableItem ; MMC1_LoadBankAndJump ; ; XREFS: ; DOOR_REQUIREMENT_LOOKUP_FUNC_ADDRS ; [$PRG15_MIRROR::eb4d] ;============================================================================
[ebb1]Game_OpenDoorWithRingOfDworf:; [$ebb1] [ebb1]LDA a:SpecialItems; Load the special items. [ebb4]AND #$20; Does the player have the Ring of Dworf? [ebb6]BNE Game_UnlockDoor; If so, unlock the door and enter.
; ; Run IScript 0x7F (via jump to IScripts_Begin). ;
[ebb8]LDA #$7f; 0x7F == Ring required IScript. [ebba]JSR MMC1_LoadBankAndJump; Run the IScript: [ebbd].byte BANK_12_LOGIC; Bank = 12 [ebbe].word IScripts_Begin-1; Address = IScripts_Begin [ebc0]@_afterFarJump:; [$ebc0] [ebc0]RTS
;============================================================================ ; Attempt to open a door marked with the Demon's Ring. ; ; This will check if the player has the Demon's Ring, and ; if they do, the door will be unlocked and entered. ; ; If the door can't be unlocked, a message will be ; displayed. ; ; INPUTS: ; SelectedItem: ; The selected item. ; ; CALLS: ; Game_UnlockDoorWithUsableItem ; MMC1_LoadBankAndJump ; ; XREFS: ; DOOR_REQUIREMENT_LOOKUP_FUNC_ADDRS ; [$PRG15_MIRROR::eb4f] ;============================================================================
[ebc1]Game_OpenDoorWithDemonsRing:; [$ebc1] [ebc1]LDA a:SpecialItems; Load the special items. [ebc4]AND #$10; Does the player have the Demon's Ring? [ebc6]BNE Game_UnlockDoor; If so, unlock the door and enter.
; ; Run IScript 0x7F (via jump to IScripts_Begin). ;
[ebc8]LDA #$7f; 0x7F == Ring required IScript. [ebca]JSR MMC1_LoadBankAndJump; Run the IScript: [ebcd].byte BANK_12_LOGIC; Bank = 12 [ebce].word IScripts_Begin-1; Address = IScripts_Begin [ebd0]@_afterFarJump:; [$ebd0] [ebd0]RTS
;============================================================================ ; TODO: Document Game_UnlockDoorWithUsableItem ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Game_OpenDoorWithAKey ; Game_OpenDoorWithJKey ; Game_OpenDoorWithJoKey ; Game_OpenDoorWithKKey ; Game_OpenDoorWithQKey ;============================================================================
[ebd1]Game_UnlockDoorWithUsableItem:; [$ebd1]
; ; Run IScript 0x84 (via jump to IScripts_Begin). ;
[ebd1]LDA #$84; 0x84 == Used key IScript. [ebd3]JSR MMC1_LoadBankAndJump; Run the IScript: [ebd6].byte BANK_12_LOGIC; Bank = 12 [ebd7].word IScripts_Begin-1; Address = IScripts_Begin [ebd9]@_afterFarJump:; [$ebd9] [ebd9]LDA #$ff [ebdb]STA a:SelectedItem [ebde]JSR UI_ClearSelectedItemPic
;============================================================================ ; TODO: Document Game_UnlockDoor ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Game_OpenDoorWithDemonsRing ; Game_OpenDoorWithRingOfDworf ; Game_OpenDoorWithRingOfElf ;============================================================================
[ebe1]Game_UnlockDoor:; [$ebe1] [ebe1]LDA #$00 [ebe3]STA a:CurrentDoor_KeyRequirement [ebe6]LDA #$84 [ebe8]LDA #$06 [ebea]JSR Sound_PlayEffect [ebed]RTS
;============================================================================ ; TODO: Document Player_DrawBody ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; EndGame_MainLoop ; Game_DrawScreenInFrozenState ; Game_MainLoop ; Player_HandleDeath ;============================================================================
[ebee]Player_DrawBody:; [$ebee] [ebee]LDA a:IScript_PortraitID [ebf1]BMI @LAB_PRG15_MIRROR__ebf4 [ebf3]RTS
; ; Check the loaded state for the screen to determine ; the arguments to use. ; ; NOTE: This seems to be old code. Nothing sets ; Screen_ReadyState to anything but 0x00 or 0xFF. ; It appears that at some point they simplified this, which ; makes all of this dead code. ;
[ebf4]@LAB_PRG15_MIRROR__ebf4:; [$ebf4] [ebf4]LDA Screen_ReadyState [ebf6]CMP #$01 [ebf8]BEQ @LAB_PRG15_MIRROR__ec11 [ebfa]CMP #$05 [ebfc]BEQ @LAB_PRG15_MIRROR__ec11
; ; Ths code path should always be hit when this function is ; called. ;
[ebfe]LDA Player_PosX_Block [ec00]STA Arg_DrawSprite_PosX [ec02]LDA Screen_Maybe_ScrollXCounter [ec04]STA Unused_Sprite_ScrollPosX [ec06]LDA Player_PosY [ec08]STA Arg_DrawSprite_PosY [ec0a]LDA Player_Something_ScrollPosY [ec0c]STA Unused_Sprite_ScrollPosY [ec0e]JMP @LAB_PRG15_MIRROR__ec21
; ; This whole section seems unreachable. Screen_ReadyState ; should never reach these values. This may be dead code. ;
[ec11]@LAB_PRG15_MIRROR__ec11:; [$ec11] [ec11]LDA Maybe_PlayerX_ForScroll [ec13]STA Arg_DrawSprite_PosX [ec15]LDA PPU_ScrollScreenHoriz [ec17]STA Unused_Sprite_ScrollPosX [ec19]LDA Maybe_PlayerY_ForScroll [ec1b]STA Arg_DrawSprite_PosY [ec1d]LDA PPU_ScrollScreenVert [ec1f]STA Unused_Sprite_ScrollPosY [ec21]@LAB_PRG15_MIRROR__ec21:; [$ec21] [ec21]JSR Player_CalculateVisibility [ec24]JSR Player_SetFacingLeft [ec27]JSR Player_GetBodySpriteFrameOffset [ec2a]PHA [ec2b]LDA a:SelectedArmor [ec2e]ASL A [ec2f]STA Temp_00 [ec31]LDA a:SelectedShield [ec34]CMP #$03 [ec36]LDA #$00 [ec38]ROL A [ec39]EOR #$01 [ec3b]ORA Temp_00 [ec3d]TAX [ec3e]PLA [ec3f]CLC [ec40]ADC PLAYER_BODY_TILEINFO_START_OFFSETS,X [ec43]JSR Sprite_SetPlayerAppearanceAddr [ec46]JMP FUN_PRG15_MIRROR__ec58
; ; XREFS: ; Player_DrawBody ;
[ec49]PLAYER_BODY_TILEINFO_START_OFFSETS:; [$ec49] [ec49].byte $00; [0]: Leather Armor [ec4a].byte $08; [1]: Leather Armor + Shield [ec4b].byte $10; [2]: Studded Mail [ec4c].byte $18; [3]: Studded Mail + Shield [ec4d].byte $20; [4]: Full Plate [ec4e].byte $28; [5]: Full Plate + Shield [ec4f].byte $30; [6]: Battle Suit [ec50].byte $38; [7]: Battle Suit + Shield
;============================================================================ ; TODO: Document Player_SetFacingLeft ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_DrawBody ;============================================================================
[ec51]Player_SetFacingLeft:; [$ec51] [ec51]LDA Player_Flags [ec53]AND #$40 [ec55]STA CurrentSprite_FlipMask [ec57]RTS
;============================================================================ ; TODO: Document FUN_PRG15_MIRROR__ec58 ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_DrawBody ;============================================================================
[ec58]FUN_PRG15_MIRROR__ec58:; [$ec58] [ec58]LDA Player_Flags [ec5a]BPL @_return [ec5c]LDA PlayerHitsPhaseCounter [ec5e]CMP #$02 [ec60]BNE @_return [ec62]LDY #$00 [ec64]LDA Player_Flags [ec66]AND #$40 [ec68]BEQ @LAB_PRG15_MIRROR__ec6b [ec6a]INY [ec6b]@LAB_PRG15_MIRROR__ec6b:; [$ec6b] [ec6b]LDA BYTE_ARRAY_PRG15_MIRROR__eca2,Y [ec6e]JSR Player_CalcValueAndFFForNeg [ec71]LDA Player_PosX_Block [ec73]CLC [ec74]ADC Temp_04 [ec76]STA Arg_DrawSprite_PosX [ec78]LDA Player_PosX_Block [ec7a]ADC Temp_05 [ec7c]CMP Player_PosX_Block [ec7e]BNE @_return [ec80]LDA Player_PosY [ec82]STA Arg_DrawSprite_PosY [ec84]JSR Player_CalculateVisibility [ec87]LDA a:SelectedArmor [ec8a]ASL A [ec8b]STA Temp_00 [ec8d]LDY #$00 [ec8f]LDA a:SelectedShield [ec92]CMP #$03 [ec94]BCS @LAB_PRG15_MIRROR__ec97 [ec96]INY [ec97]@LAB_PRG15_MIRROR__ec97:; [$ec97] [ec97]TYA [ec98]ORA Temp_00 [ec9a]TAY [ec9b]LDA BYTE_ARRAY_PRG15_MIRROR__eca4,Y [ec9e]JMP Sprite_SetPlayerAppearanceAddr [eca1]@_return:; [$eca1] [eca1]RTS
; ; XREFS: ; FUN_PRG15_MIRROR__ec58 ;
[eca2]BYTE_ARRAY_PRG15_MIRROR__eca2:; [$eca2] [eca2].byte $f8; [0]:
; ; XREFS: ; FUN_PRG15_MIRROR__ec58 ;
[eca3]BYTE_ARRAY_PRG15_MIRROR__eca2_1_:; [$eca3] [eca3].byte $10; [1]:
; ; XREFS: ; FUN_PRG15_MIRROR__ec58 ;
[eca4]BYTE_ARRAY_PRG15_MIRROR__eca4:; [$eca4] [eca4].byte $63; [0]: Leather Armor [eca5].byte $67; [1]: Leather Armor + Shield [eca6].byte $64; [2]: Studded Mail [eca7].byte $68; [3]: Studded Mail + Shield [eca8].byte $65; [4]: Full Plate [eca9].byte $69; [5]: Full Plate + Shield [ecaa].byte $66; [6]: Battle Suit [ecab].byte $6a; [7]: Battle Suit + Shield
;============================================================================ ; TODO: Document Player_GetBodySpriteFrameOffset ; ; INPUTS: ; None. ; ; OUTPUTS: ; A ; ; XREFS: ; Player_DrawBody ;============================================================================
[ecac]Player_GetBodySpriteFrameOffset:; [$ecac] [ecac]LDA Player_Flags [ecae]LSR A [ecaf]BCC @LAB_PRG15_MIRROR__ecb8 [ecb1]LDA Player_Flags [ecb3]BMI @LAB_PRG15_MIRROR__eccc [ecb5]LDA #$03 [ecb7]RTS [ecb8]@LAB_PRG15_MIRROR__ecb8:; [$ecb8] [ecb8]JSR Player_IsClimbing [ecbb]BCC @LAB_PRG15_MIRROR__ecc8 [ecbd]LDA Player_MovementTick [ecbf]AND #$10 [ecc1]ASL A [ecc2]ASL A [ecc3]STA CurrentSprite_FlipMask [ecc5]LDA #$07 [ecc7]RTS [ecc8]@LAB_PRG15_MIRROR__ecc8:; [$ecc8] [ecc8]LDA Player_Flags [ecca]BPL @LAB_PRG15_MIRROR__ecd2 [eccc]@LAB_PRG15_MIRROR__eccc:; [$eccc] [eccc]LDX PlayerHitsPhaseCounter [ecce]LDA BYTE_ARRAY_PRG15_MIRROR__ecf3,X [ecd1]RTS [ecd2]@LAB_PRG15_MIRROR__ecd2:; [$ecd2] [ecd2]LDA Player_StatusFlag [ecd4]BPL @LAB_PRG15_MIRROR__ecdd [ecd6]LDA Joy1_ButtonMask [ecd8]BPL @LAB_PRG15_MIRROR__ecdd [ecda]LDA #$03 [ecdc]RTS [ecdd]@LAB_PRG15_MIRROR__ecdd:; [$ecdd] [ecdd]LDA Player_Flags [ecdf]AND #$20 [ece1]BEQ @LAB_PRG15_MIRROR__ecea [ece3]LDA Player_MovementTick [ece5]LSR A [ece6]LSR A [ece7]LSR A [ece8]AND #$03 [ecea]@LAB_PRG15_MIRROR__ecea:; [$ecea] [ecea]TAX [eceb]LDA BYTE_ARRAY_PRG15_MIRROR__ecef,X [ecee]RTS
; ; XREFS: ; Player_GetBodySpriteFrameOffset ;
[ecef]BYTE_ARRAY_PRG15_MIRROR__ecef:; [$ecef] [ecef].byte $00; [0]: [ecf0].byte $01; [1]: [ecf1].byte $02; [2]: [ecf2].byte $01; [3]:
; ; XREFS: ; Player_GetBodySpriteFrameOffset ;
[ecf3]BYTE_ARRAY_PRG15_MIRROR__ecf3:; [$ecf3] [ecf3].byte $04; [0]: [ecf4].byte $05; [1]: [ecf5].byte $06; [2]:
;============================================================================ ; Return whether the player is climbing a ladder. ; ; This will depend on the following conditions: ; ; 1. Whether the player is in front of a ladder. ; ; 2. Whether the player is either: ; ; 1. Directly on a ladder block, or ; ; 2. Not falling off the ladder. ; ; 3. Whether the climbing bit is set. ; ; INPUTS: ; Player_Flags: ; The player's current flags. ; ; Player_PosX_Block: ; The full X position of the player. ; ; OUTPUTS: ; C: ; 1 if the player is climbing. ; 0 if the player is not. ; ; XREFS: ; Player_CastMagic ; Player_CheckHandleAttack ; Player_ContinueHandleClimbOrJump ; Player_GetBodySpriteFrameOffset ;============================================================================
[ecf6]Player_IsClimbing:; [$ecf6]
; ; Check if the player can currently climb an available ladder. ;
[ecf6]LDA Player_Flags; Load the player's flags. [ecf8]AND #$08; Can the player currently climb? [ecfa]BEQ @_returnFalse; If not, return false.
; ; A ladder is available to climb. ; ; Check if the player's X position is on an even block boundary. ;
[ecfc]LDA Player_PosX_Block; Load the player's X position. [ecfe]AND #$0f; Is it on a block boundary? [ed00]BEQ @_checkClimbing; If not, then jump to check if the player is climbing.
; ; The player's X position is on an even byte boundary. ; ; Check if the player is currently falling off a ledge or ; ladder. ;
[ed02]LDA Player_Flags; Load the player's flags. [ed04]AND #$04; Is the player currently falling? [ed06]BNE @_returnFalse; If so, return false.
; ; Check if the player is currently climbing. ;
[ed08]@_checkClimbing:; [$ed08] [ed08]LDA Player_Flags; Load the player's flags. [ed0a]AND #$10; Is the player climbing? [ed0c]BEQ @_returnFalse; If not, return false. [ed0e]SEC; Set C = 1 to return true. [ed0f]RTS [ed10]@_returnFalse:; [$ed10] [ed10]CLC; Set C = 0 to return false. [ed11]RTS
;============================================================================ ; Draw the player sprite immediately. ; ; This will load the body/armor and draw it, followed by the ; weapon (if equipped) and then the shield (if equipped and ; not the Battle Helmet). ; ; The sprite will be flushed to the PPU immediately. This ; is done during screen setup, and differs from the normal ; behavior in Player_DrawSprite. ; ; INPUTS: ; None. ; ; OUTPUTS: ; Player_DrawTileReadOffset: ; Temp_00: ; Clobbered. ; ; CALLS: ; Player_LoadArmorTile ; Player_LoadShieldTile ; Player_LoadWeaponTile ; Player_LoadArmorTilesToPPU ; Player_LoadShieldTilesToPPU ; Player_LoadWeaponTilesToPPU ; PPUBuffer_Draw ; ; XREFS: ; Screen_SetupNew ;============================================================================
[ed12]Player_DrawSpriteImmediately:; [$ed12]
; ; Load and draw the player's sprite. ;
[ed12]JSR Player_LoadArmorTilesToPPU; Load the armor sprite information. [ed15]@_drawArmorLoop:; [$ed15] [ed15]JSR Player_LoadArmorTile; Draw a tile of armor. [ed18]JSR PPUBuffer_Draw; Draw to the PPU. [ed1b]INC Player_DrawTileReadOffset; Increment the read offset. [ed1d]DEC Temp_00; Decrement the tile count. [ed1f]BPL @_drawArmorLoop; If >= 0, loop.
; ; Load and draw the weapon sprite, if one is equipped. ;
[ed21]JSR Player_LoadWeaponTilesToPPU; Load the weapon sprite information. [ed24]LDA Player_DrawTileReadOffset; Get the tile read offset. [ed26]BMI @_loadShield; If < 0, skip weapon drawing.
; ; The player has a weapon equipped. Draw the tiles. ;
[ed28]@_drawWeaponLoop:; [$ed28] [ed28]JSR Player_LoadWeaponTile; Draw a tile of the weapon. [ed2b]JSR PPUBuffer_Draw; Draw to the PPU. [ed2e]INC Player_DrawTileReadOffset; Increment the tile read offset. [ed30]DEC Temp_00; Decrement the tile count. [ed32]BPL @_drawWeaponLoop; If >= 0, loop.
; ; Load and draw the shield sprite. ;
[ed34]@_loadShield:; [$ed34] [ed34]JSR Player_LoadShieldTilesToPPU; Load the shield sprite information. [ed37]LDA Player_DrawTileReadOffset; Load the tile read offset. [ed39]BMI @_return; If < 0 (unset), return. [ed3b]@_drawShieldLoop:; [$ed3b] [ed3b]JSR Player_LoadShieldTile; Draw a tile of the shield. [ed3e]INC Player_DrawTileReadOffset; Increment the tile read offset. [ed40]DEC Temp_00; Decrement the tile count. [ed42]BPL @_drawShieldLoop; If >= 0, loop. [ed44]@_return:; [$ed44] [ed44]RTS
;============================================================================ ; Draw the player sprite. ; ; This will load the body/armor and draw it, followed by the ; weapon (if equipped) and then the shield (if equipped and ; not the Battle Helmet). ; ; INPUTS: ; None. ; ; OUTPUTS: ; Player_DrawTileReadOffset: ; Temp_00: ; Clobbered. ; ; CALLS: ; Player_LoadArmorTile ; Player_LoadShieldTile ; Player_LoadWeaponTile ; Player_LoadArmorTilesToPPU ; Player_LoadShieldTilesToPPU ; Player_LoadWeaponTilesToPPU ; ; XREFS: ; Player_HandleDeath ; Player_SetArmor ; Player_SetShield ; Player_SetWeapon ;============================================================================
[ed45]Player_DrawSprite:; [$ed45]
; ; Load and draw the player's sprite. ;
[ed45]JSR Player_LoadArmorTilesToPPU; Load the armor sprite information. [ed48]@_drawArmorLoop:; [$ed48] [ed48]JSR Player_LoadArmorTile; Draw a tile of armor. [ed4b]INC Player_DrawTileReadOffset; Increment the read offset. [ed4d]DEC Temp_00; Decrement the tile count. [ed4f]BPL @_drawArmorLoop; If >= 0, loop.
; ; Load and draw the weapon sprite, if one is equipped. ;
[ed51]JSR Player_LoadWeaponTilesToPPU; Load the weapon sprite information. [ed54]LDA Player_DrawTileReadOffset; Get the tile read offset. [ed56]BMI @_loadShield; If < 0, skip weapon drawing.
; ; The player has a weapon equipped. Draw the tiles. ;
[ed58]@_drawWeaponLoop:; [$ed58] [ed58]JSR Player_LoadWeaponTile; Draw a tile of the weapon. [ed5b]INC Player_DrawTileReadOffset; Increment the tile read offset. [ed5d]DEC Temp_00; Decrement the tile count. [ed5f]BPL @_drawWeaponLoop; If >= 0, loop.
; ; Load and draw the shield sprite. ;
[ed61]@_loadShield:; [$ed61] [ed61]JSR Player_LoadShieldTilesToPPU; Load the shield sprite information. [ed64]LDA Player_DrawTileReadOffset; Load the tile read offset. [ed66]BMI @_return; If < 0 (unset), return. [ed68]@_drawShieldLoop:; [$ed68] [ed68]JSR Player_LoadShieldTile; Draw a tile of the shield. [ed6b]INC Player_DrawTileReadOffset; Increment the tile read offset. [ed6d]DEC Temp_00; Decrement the tile count. [ed6f]BPL @_drawShieldLoop; If >= 0, loop. [ed71]@_return:; [$ed71] [ed71]RTS
;============================================================================ ; Load all tiles for the player's armor into the PPU. ; ; This will load the tiles for the selected armor into ; the PPU, for all poses (walking, climbing, jumping, etc.). ; These tiles come from TILES_PLAYER_ARMOR_ADDRS_INDEX. ; ; Different sets of tiles are chosen based on whether ; there's a shield equipped. ; ; It's called when spawning a player or when changing ; weapons/armor. ; ; INPUTS: ; SelectedArmor: ; The currently-selected armor. ; ; SelectedShield: ; The currently-selected shield. ; ; PLAYER_ARMOR_TILE_COUNTS: ; Tile counts for all poses for each armor. ; ; OUTPUTS: ; Player_ArmorLookupIndex: ; Player_DrawTileReadOffset: ; Player_SpriteSegmentPPUAddr_U: ; Player_SpriteSegmentPPUAddr_L: ; Temp_00: ; Clobbered. ; ; CALLS: ; Player_LoadArmorSpriteTilesAddr ; ; XREFS: ; Player_DrawSprite ; Player_DrawSpriteImmediately ;============================================================================
[ed72]Player_LoadArmorTilesToPPU:; [$ed72]
; ; Clear initial state for drawing the armor. ;
[ed72]LDA #$00 [ed74]STA Player_DrawTileReadOffset; Set the tile read offset to 0. [ed76]STA Player_SpriteSegmentPPUAddr_L; Set the sprite segment PPU addresses to 0. [ed78]STA Player_SpriteSegmentPPUAddr_U
; ; Calculate an index into the tile information tables for the ; armor (and possibly armor + shield, if the Battle Suit isn't ; equipped). ;
[ed7a]LDA a:SelectedArmor; Load the selected armor. [ed7d]ASL A; Convert to a word boundary. [ed7e]LDY a:SelectedShield; Load the selected shield. [ed81]CPY #$03; Is it the Battle Helmet? [ed83]BCS @_loadArmor; If so, jump (skip special armor + shield logic).
; ; There's a shield equipped, and the player isn't wielding ; the full Battle Suit + Battle Helmet. Set the index to be ; odd so we look up a sprite entry compatible with a shield. ;
[ed85]ORA #$01; Offset the lookup index to include armor + shield tiles. [ed87]@_loadArmor:; [$ed87] [ed87]STA Player_ArmorLookupIndex; Store the lookup index based on the armor word boundary and possibly the shield bit. [ed89]TAX; X = result.
; ; Determine the number of tiles to load from the lookup table. ; ; The caller will use this to iterate through the tiles to ; load. ;
[ed8a]LDA PLAYER_ARMOR_TILE_COUNTS,X; Load the tile count. [ed8d]STA Temp_00; Store it for the parent caller to use.
; ; Normalize the lookup index to be based on a 4-byte ; boundary (2 bytes for the armor address, 2 for the ; armor + shield address), and then load the sprite ; tiles. ;
[ed8f]TXA; A = Lookup index. [ed90]ASL A; Multiply by 2 (so index considers armor and armor + shield entries). [ed91]TAY; Y = result. [ed92]JMP Player_LoadArmorSpriteTilesAddr; Load the sprite tiles information.
;============================================================================ ; The number of available tiles used for all player poses ; for a given armor/shield combination. ; ; XREFS: ; Player_LoadArmorTilesToPPU ;============================================================================
; ; XREFS: ; Player_LoadArmorTilesToPPU ;
[ed95]PLAYER_ARMOR_TILE_COUNTS:; [$ed95] [ed95].byte $33; [0]: Leather Armor (51 tiles) [ed96].byte $27; [1]: Leather Armor + Shield (39 tiles) [ed97].byte $33; [2]: Studded Mail (51 tiles) [ed98].byte $27; [3]: Studded Mail + Shield (39 tiles) [ed99].byte $34; [4]: Full Plate (52 tiles) [ed9a].byte $28; [5]: Full Plate + Shield (40 tiles) [ed9b].byte $32; [6]: Battle Helmet (50 tiles) [ed9c].byte $32; [7]: Battle Helmet + Shield (50 tiles)
;============================================================================ ; Load all tiles for the player's weapon into the PPU. ; ; This will load the tiles for the selected weapon into ; the PPU, for all poses (walking, swinging, etc.). ; These tiles come from TILES_PLAYER_WEAPON_ADDRS_INDEX. ; ; It's called when spawning a player or when changing ; weapons/armor. ; ; INPUTS: ; Player_CurWeapon: ; The currently-selected weapon. ; ; PLAYER_WEAPON_TILE_COUNTS: ; Tile counts for all poses for each weapon. ; ; OUTPUTS: ; Player_DrawTileReadOffset: ; Player_SpriteSegmentPPUAddr_U: ; Player_SpriteSegmentPPUAddr_L: ; Temp_00: ; Clobbered. ; ; CALLS: ; Player_LoadWeaponSpriteTileAddrs ; ; XREFS: ; Player_DrawSprite ; Player_DrawSpriteImmediately ;============================================================================
[ed9d]Player_LoadWeaponTilesToPPU:; [$ed9d] [ed9d]LDA #$00 [ed9f]STA Player_DrawTileReadOffset; Set the tile read offset to 0. [eda1]LDA a:Player_CurWeapon; Load the selected weapon. [eda4]CMP #$ff; Is it 0xFF (unset)? [eda6]BNE @_loadWeaponTiles; If not, jump to load tiles.
; ; No sword is equipped. Set the offset to 0xFF, to disable ; drawing. ;
[eda8]STA Player_DrawTileReadOffset; Set the read offset to 0xFF. [edaa]RTS
; ; Determine the number of tiles to load from the lookup table. ; ; The caller will use this to iterate through the tiles to ; load. ;
[edab]@_loadWeaponTiles:; [$edab] [edab]TAX; X = weapon. [edac]LDA PLAYER_WEAPON_TILE_COUNTS,X; Load the tile count. [edaf]STA Temp_00; Store it for the parent caller to use.
; ; Set the PPU address to load tiles into and load them. ;
[edb1]TXA; X = tile count. [edb2]ASL A; Convert to a word boundary. [edb3]TAY; Y = resulting index. [edb4]LDA PLAYER_WEAPON_TILE_PPU_ADDRS,Y; Load the lower byte of the target PPU address for weapons. [edb7]STA Player_SpriteSegmentPPUAddr_L; Set it for load. [edb9]LDA PLAYER_WEAPON_TILE_PPU_ADDRS+1,Y; Load the upper byte of the target PPU address for weapons. [edbc]STA Player_SpriteSegmentPPUAddr_U; Set it for load. [edbe]JMP Player_LoadWeaponSpriteTileAddrs; Load the sprite tiles information.
;============================================================================ ; The number of available tiles used for all player poses ; for a given weapon. ; ; XREFS: ; Player_LoadWeaponTilesToPPU ;============================================================================
; ; XREFS: ; Player_LoadWeaponTilesToPPU ;
[edc1]PLAYER_WEAPON_TILE_COUNTS:; [$edc1] [edc1].byte $02; [0]: Dagger [edc2].byte $05; [1]: Long Sword [edc3].byte $06; [2]: Giant Blade [edc4].byte $08; [3]: Dragon Slayer
;============================================================================ ; PPU target addresses for each weapon type's loaded tiles. ; ; XREFS: ; Player_LoadWeaponTilesToPPU ;============================================================================
; ; XREFS: ; Player_LoadWeaponTilesToPPU ;
[edc5]PLAYER_WEAPON_TILE_PPU_ADDRS:; [$edc5] [edc5].word $0380; [0]: Dagger [edc7].word $0380; [1]: Long Sword [edc9].word $0380; [2]: Giant Blade [edcb].word $0340; [3]: Dragon Slayer
;============================================================================ ; Load all tiles for the player's shield into the PPU. ; ; This will load the tiles for the selected shield into ; the PPU, for all poses (walking, swinging, etc.). ; These tiles come from a range starting at ; TILES_SHIELDS_START_SMALL_SHIELD_01. ; ; It's called when spawning a player or when changing ; weapons/armor. ; ; Tile indexes and counts are static. They aren't loaded ; from a lookup table like with weapons/armor. There are ; always 5 tiles for a shield. ; ; The exception is the Battle Helmet, which is not rendered ; directly, as it's part of the armor tiles. ; ; INPUTS: ; SelectedShield: ; The currently-selected shield. ; ; OUTPUTS: ; Player_DrawTileReadOffset: ; Player_SpriteSegmentPPUAddr_U: ; Player_SpriteSegmentPPUAddr_L: ; Temp_00: ; Clobbered. ; ; CALLS: ; Player_LoadShieldSpriteTileAddrs ; ; XREFS: ; Player_DrawSprite ; Player_DrawSpriteImmediately ;============================================================================
[edcd]Player_LoadShieldTilesToPPU:; [$edcd] [edcd]LDA #$00 [edcf]STA Player_DrawTileReadOffset; Set the tile read offset to 0. [edd1]LDA a:SelectedShield; Load the selected shield. [edd4]CMP #$03; Is it the Battle Helmet (or unset -- 0xFF)? [edd6]BCC @_loadShieldTiles; If not, then jump to handle standard shields.
; ; This is the Battle Helmet or there's no helmet equipped. ; In either case, set the loaded value as the result. ;
[edd8]STA Player_DrawTileReadOffset; Set the read offset to the shield value. This will be 3 or 0xFF. [edda]RTS; And return.
; ; An actual shield is equipped. Begin setting the lookup ; index for the shield and a tile count of 5. ;
[eddb]@_loadShieldTiles:; [$eddb] [eddb]ASL A; Convert the shield index to a word value for lookup. [eddc]TAY; Y = resulting index. [eddd]LDA #$05; 5 tiles. [eddf]STA Temp_00; Set as the tile count for the load loop.
; ; Set the PPU address to load into to 0x0300. ;
[ede1]LDA #$00 [ede3]STA Player_SpriteSegmentPPUAddr_L; Set the lower byte of the PPU address to 0. [ede5]LDA #$03 [ede7]STA Player_SpriteSegmentPPUAddr_U; And upper to 3. [ede9]JMP Player_LoadShieldSpriteTileAddrs; Load the sprite tiles information.
;============================================================================ ; Set the player's current weapon. ; ; If the player is in an area where the weapon can be ; equipped, then equip and redraw the player sprite. ; ; INPUTS: ; A: ; The weapon to set: ; ; 0 = Hand Dagger ; 1 = Long Sword ; 2 = Giant Blade ; 3 = Dragon Slayer ; ; OUTPUTS: ; SelectedWeapon: ; The updated selected weapon. ; ; Player_CurWeapon: ; The weapon currently equipped by the player, ; if in an area allowing that. ; ; CALLS: ; Player_DrawSprite ; ; XREFS: ; Player_Equip ;============================================================================
[edec]Player_SetWeapon:; [$edec] [edec]PHA; Push the weapon to the stack.
; ; Check that the player is not in a building. ; ; A weapon cannot be set while in a building. ;
[eded]LDA Area_CurrentArea; Set A = current area. [edef]CMP #$04; Is this the building? [edf1]BEQ @_cannotEquip; If so, branch.
; ; The weapon can be equipped. Equip it and redraw the player. ;
[edf3]PLA; Pop the weapon from the stack. [edf4]STA a:SelectedWeapon; Set as the current weapon in the inventory. [edf7]STA a:Player_CurWeapon; Store as the current weapon. [edfa]JSR Player_DrawSprite; Draw the player sprite. [edfd]CLC; Clear Carry. [edfe]RTS
; ; The weapon cannot currently be equipped. Set the weapon ; but do not draw. ;
[edff]@_cannotEquip:; [$edff] [edff]PLA; Pop the weapon from the stack. [ee00]STA a:SelectedWeapon; Set as the current weapon. [ee03]CLC; Clear Carry. [ee04]RTS
;============================================================================ ; Set the player's current armor. ; ; The armor will be equipped and the player sprite redrawn. ; ; INPUTS: ; A: ; 0 = Leather ; 1 = Studded Mail ; 2 = Full Plate ; 3 = Battle Suit ; ; OUTPUTS: ; SelectedArmor: ; The new selected armor. ; ; CALLS: ; Player_DrawSprite ; ; XREFS: ; Player_Equip ;============================================================================
[ee05]Player_SetArmor:; [$ee05] [ee05]STA a:SelectedArmor; Set the selected armor. [ee08]JSR Player_DrawSprite; Draw the player sprite. [ee0b]CLC; Clear Carry. [ee0c]RTS
;============================================================================ ; Set the player's current shield. ; ; The shield will be equipped and the player sprite redrawn. ; ; INPUTS: ; A: ; The shield to set: ; ; 0 = Small ; 1 = Large ; 2 = Magic ; 3 = Battle Helmet ; ; OUTPUTS: ; SelectedShield: ; The new selected armor ; ; CALLS: ; Player_DrawSprite ; ; XREFS: ; Player_Equip ;============================================================================
[ee0d]Player_SetShield:; [$ee0d] [ee0d]STA a:SelectedShield; Set the selected shield. [ee10]JSR Player_DrawSprite; Draw the player sprite. [ee13]CLC; Clear Carry. [ee14]RTS
;============================================================================ ; Load the start address for the current armor's tiles. ; ; This will locate the address in bank 8 where the tiles ; can be read, in preparation for drawing to the screen. ; ; The initial address in bank 8 ({@address PRG8::8000}) points to an ; index table mapping armor IDs to tile start addresses. ; ; INPUTS: ; CurrentROMBank: ; The current ROM bank. ; ; OUTPUTS: ; Player_SpriteTileReadAddr_U: ; Player_SpriteTileReadAddr_L: ; Address where tile data can be read. ; ; CALLS: ; MMC1_UpdateROMBank ; ; XREFS: ; Player_LoadArmorTilesToPPU ;============================================================================
[ee15]Player_LoadArmorSpriteTilesAddr:; [$ee15]
; ; Save the current ROM bank and switch to bank 8 (sprites). ;
[ee15]LDA a:CurrentROMBank; Load the current ROM bank. [ee18]PHA; Push the bank to the stack. [ee19]LDX #$08; 8 = Sprites bank. [ee1b]JSR MMC1_UpdateROMBank; Switch to the bank.
; ; Load the address of the index of armor types to tile IDs. ;
[ee1e]LDA a:TILES_ARMOR_ADDRS_INDEX_REF; Load the lower byte of the address of the armor index. [ee21]STA Player_SpriteTileReadAddr_L; Set as the lower byte. [ee23]LDA a:TILES_ARMOR_ADDRS_INDEX_REF+1; Load the upper byte. [ee26]CLC [ee27]ADC #$80; Add 0x80 to the upper byte. [ee29]STA Player_SpriteTileReadAddr_U; And set it as the upper byte.
; ; Load the tiles start address of the armor type from the index. ;
[ee2b]LDA (Player_SpriteTileReadAddr_L),Y; Read the lower byte of the tiles start address. [ee2d]PHA; Push it to the stack. [ee2e]INY; Increment the offset. [ee2f]LDA (Player_SpriteTileReadAddr_L),Y; Read the upper byte of the tiles start address. [ee31]CLC [ee32]ADC #$80; Add 0x80 to it. [ee34]STA Player_SpriteTileReadAddr_U; And store as the upper byte of the address. [ee36]PLA; Pop the lower byte. [ee37]STA Player_SpriteTileReadAddr_L; And store it as the lower byte of the address.
; ; Switch back to the saved bank. ;
[ee39]PLA; Pop the saved ROM bank from the stack. [ee3a]TAX; Set it to X [ee3b]JSR MMC1_UpdateROMBank; And switch back to it. [ee3e]RTS
;============================================================================ ; Load the start address for the current weapon's tiles. ; ; This will locate the address in bank 8 where the tiles ; can be read, in preparation for drawing to the screen. ; ; The initial address in bank 8 ({@address PRG8::8002}) points to an ; index table mapping weapon IDs to tile start addresses. ; ; INPUTS: ; CurrentROMBank: ; The current ROM bank. ; ; OUTPUTS: ; Player_SpriteTileReadAddr_U: ; Player_SpriteTileReadAddr_L: ; Address where tile data can be read. ; ; CALLS: ; MMC1_UpdateROMBank ; ; XREFS: ; Player_LoadWeaponTilesToPPU ;============================================================================
[ee3f]Player_LoadWeaponSpriteTileAddrs:; [$ee3f]
; ; Save the current ROM bank and switch to bank 8 (sprites). ;
[ee3f]LDA a:CurrentROMBank; Load the current ROM bank. [ee42]PHA; Push the bank to the stack. [ee43]LDX #$08; 8 = Sprites bank. [ee45]JSR MMC1_UpdateROMBank; Switch to the bank.
; ; Load the address of the index of weapon types to tile IDs. ;
[ee48]LDA a:TILES_WEAPON_ADDRS_INDEX_REF; Load the lower byte of the address of the weapon index. [ee4b]STA Player_SpriteTileReadAddr_L; Set as the lower byte. [ee4d]LDA a:TILES_WEAPON_ADDRS_INDEX_REF+1; Load the upper byte. [ee50]CLC [ee51]ADC #$80; Add 0x80 to the upper byte. [ee53]STA Player_SpriteTileReadAddr_U; And set it as the upper byte.
; ; Load the tiles start address of the weapon type from the ; index. ;
[ee55]LDA (Player_SpriteTileReadAddr_L),Y; Read the lower byte of the tiles start address. [ee57]PHA; Push it to the stack. [ee58]INY; Increment the offset. [ee59]LDA (Player_SpriteTileReadAddr_L),Y; Read the upper byte of the tiles start address. [ee5b]CLC [ee5c]ADC #$80; Add 0x80 to it. [ee5e]STA Player_SpriteTileReadAddr_U; And store as the upper byte of the address. [ee60]PLA; Pop the lower byte. [ee61]STA Player_SpriteTileReadAddr_L; And store it as the lower byte of the address.
; ; Switch back to the saved bank. ;
[ee63]PLA; Pop the saved ROM bank from the stack. [ee64]TAX; Set it to X [ee65]JSR MMC1_UpdateROMBank; And switch back to it. [ee68]RTS
;============================================================================ ; Load the start address for the current shield's tiles. ; ; This will locate the address in bank 8 where the tiles ; can be read, in preparation for drawing to the screen. ; ; The initial address in bank 8 ({@address PRG8::800C}) points to an ; index table mapping shield IDs to tile start addresses. ; ; INPUTS: ; CurrentROMBank: ; The current ROM bank. ; ; OUTPUTS: ; Player_SpriteTileReadAddr_U: ; Player_SpriteTileReadAddr_L: ; Address where tile data can be read. ; ; CALLS: ; MMC1_UpdateROMBank ; ; XREFS: ; Player_LoadShieldTilesToPPU ;============================================================================
[ee69]Player_LoadShieldSpriteTileAddrs:; [$ee69]
; ; Save the current ROM bank and switch to bank 8 (sprites). ;
[ee69]LDA a:CurrentROMBank; Load the current ROM bank. [ee6c]PHA; Push the bank to the stack. [ee6d]LDX #$08; 8 = Sprites bank. [ee6f]JSR MMC1_UpdateROMBank; Switch to the bank.
; ; Load the address of the index of shield types to tile IDs. ;
[ee72]LDA a:TILES_SHIELDS_ADDRS_INDEX_REF; Load the lower byte of the address of the shield index. [ee75]STA Player_SpriteTileReadAddr_L; Set as the lower byte. [ee77]LDA a:TILES_SHIELDS_ADDRS_INDEX_REF+1; Load the upper byte. [ee7a]CLC [ee7b]ADC #$80; Add 0x80 to the upper byte. [ee7d]STA Player_SpriteTileReadAddr_U; And set it as the upper byte.
; ; Load the tiles start address of the shield type from the index. ;
[ee7f]LDA (Player_SpriteTileReadAddr_L),Y; Read the lower byte of the tiles start address. [ee81]PHA; Push it to the stack. [ee82]INY; Increment the offset. [ee83]LDA (Player_SpriteTileReadAddr_L),Y; Read the upper byte of the tiles start address. [ee85]CLC [ee86]ADC #$80; Add 0x80 to it. [ee88]STA Player_SpriteTileReadAddr_U; And store as the upper byte of the address. [ee8a]PLA; Pop the lower byte. [ee8b]STA Player_SpriteTileReadAddr_L; And store it as the lower byte of the address.
; ; Switch back to the saved bank. ;
[ee8d]PLA; Pop the saved ROM bank from the stack. [ee8e]TAX; Set it to X. [ee8f]JSR MMC1_UpdateROMBank; And switch back to it. [ee92]RTS
;============================================================================ ; Load a tile of the player's shield into the PPU. ; ; This is repeatedly called in a loop. ; ; The process for loading a tile involves: ; ; 1. Switching to bank 8. ; 2. Loading the start of the tiles address. ; 3. Splitting the nibbles of the tile ID to a 2-byte ; offset into the tile address. ; 4. Writing to the PPU buffer, to load into the PPU. ; ; As a note, there's a lot of back-and-forth bank switching ; that seems entirely unnecessary. Probably an area that ; could be optimized. ; ; INPUTS: ; CurrentROMBank: ; The current ROM bank. ; ; Player_SpriteSegmentPPUAddr_U: ; Player_SpriteSegmentPPUAddr_L: ; Current address in the PPU to write to. ; ; OUTPUTS: ; PPUBuffer: ; PPUBuffer_WriteOffset: ; Updated to queue PPU tile loading. ; ; Player_SpriteSegmentPPUAddr_U: ; Player_SpriteSegmentPPUAddr_L: ; Next address in the PPU to write to. ; ; Temp_Addr_L: ; Temp_Addr_U: ; PPU_TargetAddr: ; PPU_TargetAddr+1: ; Clobbered. ; ; CALLS: ; MMC1_UpdateROMBank ; PPUBuffer_QueueCommandOrLength ; ; XREFS: ; Player_DrawSprite ; Player_DrawSpriteImmediately ;============================================================================
[ee93]Player_LoadShieldTile:; [$ee93]
; ; Save the current ROM bank and switch to bank 8 (sprites). ;
[ee93]LDA a:CurrentROMBank; Load the current ROM bank. [ee96]PHA; Push the bank to the stack. [ee97]LDX #$08; 8 = Sprites bank. [ee99]JSR MMC1_UpdateROMBank; Switch to the bank.
; ; Load the address for the start of the shield tile IDs. ;
[ee9c]LDA a:USHORT_800a; Load the lower byte of the tile IDs start address. [ee9f]STA Temp_Addr_L; Store as the lower byte of the address to read from. [eea1]LDA a:USHORT_800a+1; Load the upper byte of the tile IDs start address. [eea4]STA Temp_Addr_U; Store as the upper byte of the address to read from.
; ; Draw the shield tile. ;
[eea6]JMP Player_LoadSpriteTile; Draw the tile.
;============================================================================ ; Load a tile of the player's armor into the PPU. ; ; This is repeatedly called in a loop. ; ; The process for loading a tile involves: ; ; 1. Switching to bank 8. ; 2. Loading the start of the tiles address. ; 3. Splitting the nibbles of the tile ID to a 2-byte ; offset into the tile address. ; 4. Writing to the PPU buffer, to load into the PPU. ; ; As a note, there's a lot of back-and-forth bank switching ; that seems entirely unnecessary. Probably an area that ; could be optimized. ; ; INPUTS: ; CurrentROMBank: ; The current ROM bank. ; ; Player_SpriteSegmentPPUAddr_U: ; Player_SpriteSegmentPPUAddr_L: ; Current address in the PPU to write to. ; ; OUTPUTS: ; PPUBuffer: ; PPUBuffer_WriteOffset: ; Updated to queue PPU tile loading. ; ; Player_SpriteSegmentPPUAddr_U: ; Player_SpriteSegmentPPUAddr_L: ; Next address in the PPU to write to. ; ; Temp_Addr_L: ; Temp_Addr_U: ; PPU_TargetAddr: ; PPU_TargetAddr+1: ; Clobbered. ; ; CALLS: ; MMC1_UpdateROMBank ; PPUBuffer_QueueCommandOrLength ; ; XREFS: ; Player_DrawSprite ; Player_DrawSpriteImmediately ;============================================================================
[eea9]Player_LoadArmorTile:; [$eea9]
; ; Save the current ROM bank and switch to bank 8 (sprites). ;
[eea9]LDA a:CurrentROMBank; Load the current ROM bank. [eeac]PHA; Push the bank to the stack. [eead]LDX #$08; 8 = Sprites bank. [eeaf]JSR MMC1_UpdateROMBank; Switch to the bank.
; ; Load the address for the start of the armor tile IDs. ;
[eeb2]LDA a:USHORT_8004; Load the lower byte of the tile IDs start address. [eeb5]STA Temp_Addr_L; Store as the lower byte of the address to read from. [eeb7]LDA a:USHORT_8004+1; Load the upper byte of the tile IDs start address. [eeba]STA Temp_Addr_U; Store as the upper byte of the address to read from.
; ; Draw the armor tile. ;
[eebc]JMP Player_LoadSpriteTile; Draw the tile.
;============================================================================ ; Load a tile of the player's weapon into the PPU. ; ; This is called when spawning a player or when changing ; weapons/armor. ; ; This is repeatedly called in a loop. ; ; The process for loading a tile involves: ; ; 1. Switching to bank 8. ; 2. Loading the start of the tiles address. ; 3. Splitting the nibbles of the tile ID to a 2-byte ; offset into the tile address. ; 4. Writing to the PPU buffer, to load into the PPU. ; ; As a note, there's a lot of back-and-forth bank switching ; that seems entirely unnecessary. Probably an area that ; could be optimized. ; ; INPUTS: ; CurrentROMBank: ; The current ROM bank. ; ; Player_SpriteSegmentPPUAddr_U: ; Player_SpriteSegmentPPUAddr_L: ; Current address in the PPU to write to. ; ; OUTPUTS: ; PPUBuffer: ; PPUBuffer_WriteOffset: ; Updated to queue PPU tile loading. ; ; Player_SpriteSegmentPPUAddr_U: ; Player_SpriteSegmentPPUAddr_L: ; Next address in the PPU to write to. ; ; Temp_Addr_L: ; Temp_Addr_U: ; PPU_TargetAddr: ; PPU_TargetAddr+1: ; Clobbered. ; ; CALLS: ; MMC1_UpdateROMBank ; PPUBuffer_QueueCommandOrLength ; ; XREFS: ; Player_DrawSprite ; Player_DrawSpriteImmediately ;============================================================================
[eebf]Player_LoadWeaponTile:; [$eebf]
; ; Save the current ROM bank and switch to bank 8 (sprites). ;
[eebf]LDA a:CurrentROMBank; Load the current ROM bank. [eec2]PHA; Push the bank to the stack. [eec3]LDX #$08; 8 = Sprites bank. [eec5]JSR MMC1_UpdateROMBank; Switch to the bank.
; ; Load the address for the start of the weapon tile IDs. ;
[eec8]LDA a:TILES_WEAPONS_START_REF; Load the lower byte of the tile IDs start address. [eecb]STA Temp_Addr_L; Store as the lower byte of the address to read from. [eecd]LDA a:TILES_WEAPONS_START_REF+1; Load the upper byte of the tile IDs start address. [eed0]STA Temp_Addr_U; Store as the upper byte of the address to read from.
; ; v-- Fall through --v ;
; ; Restore the previously-saved bank. ; ; ; XREFS: ; Player_LoadArmorTile ; Player_LoadShieldTile ;
[eed2]Player_LoadSpriteTile:; [$eed2] [eed2]PLA; Pop the saved ROM bank from the stack. [eed3]TAX; Set it to X. [eed4]JSR MMC1_UpdateROMBank; And switch back to it.
; ; And then save it again and switch away again, back to ; bank 8. ;
[eed7]LDA a:CurrentROMBank; Load the current ROM bank. [eeda]PHA; Push it to the stack. [eedb]LDX #$08; 8 = Sprites bank. [eedd]JSR MMC1_UpdateROMBank; Switch to the bank.
; ; Prepare the read address for the tile. ;
[eee0]LDY Player_DrawTileReadOffset; Load the tile read offset. [eee2]LDA #$00; A = 0 [eee4]STA Temp_05; Store it temporarily.
; ; Convert to a 2-byte value. The lower nibble will be stored ; as the lower byte in Temp_04, and the upper nibble ; as the upper byte in Temp_05. Effectively, splitting ; the nibbles into two bytes. ;
[eee6]LDA (Player_SpriteTileReadAddr_L),Y; Load the tile ID at the offset. [eee8]ASL A; Shift left, upper bit into Carry. [eee9]ROL Temp_05; Rotate left, carry into bit 0, bit 7 into carry. [eeeb]ASL A; Repeat 3 more times, moving lower nibble into A, upper nibble into Temp_05. [eeec]ROL Temp_05 [eeee]ASL A [eeef]ROL Temp_05 [eef1]ASL A [eef2]ROL Temp_05; Final storage of the upper byte. [eef4]STA Temp_04; Final storage of the lower byte.
; ; Restore the saved bank. ;
[eef6]PLA; Pop the saved ROM bank from the stack. [eef7]TAX; Set it to X. [eef8]JSR MMC1_UpdateROMBank; And switch back to it.
; ; Convert the normalized 16-bit tile ID to an address. ; ; 0x80 + the upper byte of the tiles address will be ; added to the upper byte from the tile ID. ; ; The lower byte of the tiles address will be added to the ; lower byte from the tile ID. ; ; The result is the tile data address. ; ; Start with the lower byte of the address. ;
[eefb]LDA Temp_04; Load the lower byte from the normalized tile ID. [eefd]CLC [eefe]ADC Temp_Addr_L; Add to the start of the tile IDs. [ef00]STA Temp_04; Store as the new lower byte.
; ; Now handle the upper byte of the address. ;
[ef02]LDA Temp_05; Load the upper byte from the normalized tile ID. [ef04]ADC Temp_Addr_U; Add the upper byte of the address. [ef06]CLC [ef07]ADC #$80; Add 0x80. [ef09]STA Temp_05; Store as the new upper byte.
; ; Switch bank to bank 8 (where this probably should have ; stayed all along). ;
[ef0b]LDA a:CurrentROMBank; Load the current bank again. [ef0e]PHA; Push it to the stack. [ef0f]LDX #$08; 8 = Sprites bank [ef11]JSR MMC1_UpdateROMBank; Switch to it again.
; ; Set the target PPU address to write the tiles to for the ; PPU buffer draw command. ;
[ef14]LDA Player_SpriteSegmentPPUAddr_U; Load the upper byte of the PPU address of the sprite data. [ef16]STA a:PPU_TargetAddr_U; Store as the upper byte to write to for the PPU draw command. [ef19]LDA Player_SpriteSegmentPPUAddr_L; Load the lower byte. [ef1b]STA a:PPU_TargetAddr; And store it.
; ; Queue 16 bytes (1 tile) to draw to the PPU. ;
[ef1e]LDA #$10; A = 16 bytes. [ef20]JSR PPUBuffer_QueueCommandOrLength; Queue it as the draw length.
; ; Prepare a loop for writing bytes for the tile. ;
[ef23]LDY #$00; Y = 0 (loop counter/read offset). [ef25]@_copyLoop:; [$ef25] [ef25]LDA (Temp_04),Y; Load a byte of the tile. [ef27]STA PPUBuffer,X; Write it to the PPU buffer. [ef2a]INX; X++ (buffer position). [ef2b]INY; Y++ (loop counter). [ef2c]CPY #$10; Is it < 16? [ef2e]BCC @_copyLoop; If so, loop.
; ; 16 bytes are written. Update the PPU buffer write offset ; and restore the bank. ;
[ef30]STX PPUBuffer_WriteOffset; Update the PPU buffer write offset. [ef32]PLA; Pop the saved bank from the stack. [ef33]TAX; Set in X. [ef34]JSR MMC1_UpdateROMBank; And update to the bank.
; ; Increment the PPU address for the next tile. ;
[ef37]LDA Player_SpriteSegmentPPUAddr_L; Load the lower byte of the PPU address. [ef39]CLC [ef3a]ADC #$10; Add 16 (next tile). [ef3c]STA Player_SpriteSegmentPPUAddr_L; Store it. [ef3e]LDA Player_SpriteSegmentPPUAddr_U; Load the upper byte. [ef40]ADC #$00; Add Carry (if the lower byte wrapped). [ef42]STA Player_SpriteSegmentPPUAddr_U; And store it. [ef44]RTS
;============================================================================ ; DEAD CODE: Unset the special event ID. ; ; This is not used in the game. ;============================================================================
[ef45]UNUSED_ClearScreenSpecialEventID:; [$ef45] [ef45]LDA #$ff [ef47]STA a:CurrentScreen_SpecialEventID [ef4a]RTS
;============================================================================ ; Run special event handlers for the current screen. ; ; This will run code specific to some screens in the game, ; as indicated by the special event ID stored in a screen's ; metadata. ; ; See Screen_LoadSpecialEventID. ; ; If a handler is found, it will be run directly following ; this function. ; ; INPUTS: ; CurrentScreen_SpecialEventID: ; The loaded special event ID for the current ; screen. ; ; SPECIAL_SCREEN_EVENT_LOOKUP_TABLE: ; Table mapping special event IDs to handler ; functions. ; ; OUTPUTS: ; Stack (A): ; The address of the handler to run, if any. ; ; XREFS: ; EndGame_MainLoop ; Game_MainLoop ;============================================================================
[ef4b]GameLoop_RunScreenEventHandlers:; [$ef4b]
; ; Check if this screen has a special event ID. ;
[ef4b]LDA a:CurrentScreen_SpecialEventID; Load the screen's special event ID. [ef4e]CMP #$ff; Is it 0xFF? [ef50]BEQ @_return; If so, nothing to run. Return.
; ; Check if there's an entry in the table. ;
[ef52]AND #$7f; Take the lower 7 bits. [ef54]ASL A; Convert to a word boundary for the lookup table. [ef55]CMP #$06; Is it >= 6 (out of bounds)? [ef57]BCS @_return; If so, return.
; ; Load the address of the handler and push as the new address ; to run. ;
[ef59]TAY; Y = A [ef5a]LDA SPECIAL_SCREEN_EVENT_LOOKUP_TABLE+1,Y; Load the lower byte of the handler address. [ef5d]PHA; Push it to the stack. [ef5e]LDA SPECIAL_SCREEN_EVENT_LOOKUP_TABLE,Y; Load the upper byte. [ef61]PHA; Push it. [ef62]@_return:; [$ef62] [ef62]RTS
;============================================================================ ; Special screen events lookup table. ; ; Each maps a special event ID to a handler that will run ; on each tick while on the screen. ; ; MOD NOTES: ; By moving this table later into the bank and updating ; both the address in ; GameLoop_RunScreenEventHandlers ; and the table length in that function (6), you could ; likely add new special handlers for screens ; ; XREFS: ; GameLoop_RunScreenEventHandlers ;============================================================================
; ; XREFS: ; GameLoop_RunScreenEventHandlers ;
[ef63]SPECIAL_SCREEN_EVENT_LOOKUP_TABLE:; [$ef63] [ef63].byte $68; [0]: Handle pushable block on path to Mascon.
; ; XREFS: ; GameLoop_RunScreenEventHandlers ;
[ef64]SPECIAL_SCREEN_EVENT_LOOKUP_TABLE_1:; [$ef64] [ef64].byte $ef; [0]: [ef65].byte $9d; [1]: Handle a standard boss battle. [ef66].byte $ef; [1]: [ef67].byte $d3; [2]: Handle end-game sequence after killing the final boss. [ef68].byte $ef; [2]:
;============================================================================ ; Handle checking the path to Mascon at the final fountain. ; ; This is a special screen event handler that checks whether ; the player has completed the quests to reach Mascon. ; ; This is only executed on the first tick for the screen, ; and disabled immediately after. If the fountains were ; completed, this will remove the block obscuring the ; ladder to Mascon and then drop down the ladder. ; ; INPUTS: ; Quests: ; The quests completed. ; ; OUTPUTS: ; CurrentScreen_SpecialEventID: ; The unset special event ID, to prevent this from ; running more than once. ; ; PathToMascon_FountainCoverPos: ; Set to the block position to clear. ; ; PathToMascon_Opening: ; Set to 1. ; ; CALLS: ; Game_OpenPathToMascon ; ; XREFS: ; SPECIAL_SCREEN_EVENT_LOOKUP_TABLE ; [$PRG15_MIRROR::ef63] ;============================================================================
[ef69]ScreenEvents_HandlePathToMasconEvent:; [$ef69]
; ; Check if the path to Mascon has been opened. ;
[ef69]LDA a:Quests; Load the list of completed quests. [ef6c]AND #$20; Is the Mascon path completed? [ef6e]BEQ @_notCompleted; If not, jump to return.
; ; The quest has been completed. Turn off this handler (clear ; the event ID) and manage the transition for the path to ; Mascon. ;
[ef70]LDA #$ff [ef72]STA a:CurrentScreen_SpecialEventID; Unset the special event ID. [ef75]LDA #$01 [ef77]STA PathToMascon_Opening; Set the path to Mascon as opened. [ef79]LDA #$56 [ef7b]STA PathToMascon_FountainCoverPos; Set the pushable block to X=6, Y=5. [ef7d]LDX #$00 [ef7f]JMP Game_OpenPathToMascon; Drop the ladder down to reach Mascon.
; ; The criteria for the path wasn't set. Clear out this event ; ID so it won't be run until the next time the player is ; on the screen. ;
[ef82]@_notCompleted:; [$ef82] [ef82]LDA #$ff [ef84]STA a:CurrentScreen_SpecialEventID; Unset the special event ID. [ef87]RTS
;============================================================================ ; DEADCODE: Check if there are sprites not of a given entity. ; ; This does not appear to be used anywhere. It only ; references itself. ;============================================================================
[ef88]Sprites_HasSpritesNotOfType:; [$ef88] [ef88]STA Temp_00 [ef8a]LDX #$07 [ef8c]@LAB_PRG15_MIRROR__ef8c:; [$ef8c] [ef8c]LDA CurrentSprites_Entities,X [ef8f]CMP #$ff [ef91]BEQ @LAB_PRG15_MIRROR__ef99 [ef93]CMP Temp_00 [ef95]BEQ @LAB_PRG15_MIRROR__ef99 [ef97]SEC [ef98]RTS [ef99]@LAB_PRG15_MIRROR__ef99:; [$ef99] [ef99]DEX [ef9a]BPL @LAB_PRG15_MIRROR__ef8c [ef9c]CLC [ef9d]RTS
;============================================================================ ; Handle checking for a boss on the screen. ; ; This is a special event handler used for boss rooms. ; It will default the music to boss battle music, and then ; check for the presence of a boss on every game tick. ; ; Once a boss has been defeated, the music will be set back ; to the default for the area and the handler will be shut ; down. ; ; INPUTS: ; CurrentScreen_SpecialEventID: ; The special event ID for the screen, used to ; determine which bits of logic should run. ; ; Areas_DefaultMusic: ; The default music for the area. ; ; OUTPUTS: ; Music_Current: ; The current music being played. ; ; CurrentScreen_SpecialEventID: ; The updated special event ID. ; ; CALLS: ; Sprites_HasBoss ; ; XREFS: ; SPECIAL_SCREEN_EVENT_LOOKUP_TABLE ; [$PRG15_MIRROR::ef65] ;============================================================================
[ef9e]ScreenEvents_HandleBoss:; [$ef9e]
; ; Check if bit 7 is set. If not, prepare the boss battle music ; and initial state. This would only be done once on the screen. ;
[ef9e]LDA a:CurrentScreen_SpecialEventID [efa1]BMI @_checkForBoss
; ; This is the first tick on a boss battle screen. Take the ; opportunity to cap any item durations, and default the music ; to the boss battle music. ;
[efa3]ORA #$80; Set bit 7 to only run this conditional on the first game tick. [efa5]STA a:CurrentScreen_SpecialEventID; Save it. [efa8]JSR LimitItemDurations; Cap item durations within bounds. [efab]LDA #$0a [efad]STA Music_Current; Set the music to the boss battle music.
; ; Check if there's a boss on screen. ;
[efaf]@_checkForBoss:; [$efaf] [efaf]JSR Sprites_HasBoss; Check for a boss. [efb2]BCS @_return; If there is one, we're done. Return.
; ; The boss has been defeated. Restore the game state. ;
[efb4]LDA a:Areas_DefaultMusic; Load the default music for the area. [efb7]STA Music_Current; Set it as the current music. [efb9]LDA #$ff [efbb]STA a:CurrentScreen_SpecialEventID; Clear the special event ID so this doesn't run again. [efbe]@_return:; [$efbe] [efbe]RTS
;============================================================================ ; Ensure Ointment and Hour Glass durations don't go below 0. ; ; If either goes below 0, it will be set to 0. ; ; INPUTS: ; DurationOintment: ; The Ointment duration. ; ; DurationHourGlass: ; The Hour Glass duration. ; ; OUTPUTS: ; DurationOintment: ; The updated Ointment duration. ; ; DurationHourGlass: ; The updated Hour Glass duration. ; ; XREFS: ; ScreenEvents_HandleBoss ;============================================================================
[efbf]LimitItemDurations:; [$efbf] [efbf]LDA a:DurationOintment; Load the Ointment duration. [efc2]BMI @_checkHourGlass; If >= 0, move on to the Hour Glass. [efc4]LDA #$00 [efc6]STA a:DurationOintment; Set the Ointment duration to 0. [efc9]@_checkHourGlass:; [$efc9] [efc9]LDA a:DurationHourGlass; Load the Hour Glass duration. [efcc]BMI @_return; If >= 0, we're done. [efce]LDA #$00 [efd0]STA a:DurationHourGlass; Set the Hour Glass duration to 0. [efd3]@_return:; [$efd3] [efd3]RTS
;============================================================================ ; Handle checking for the death of the final boss. ; ; This is a special event handler used for the final boss ; room. ; ; If the boss dies (rather, if all sprite entities are ; cleared from the screen), this will switch back to the ; opening music and area, placing the player back in the ; King's room for the end game outro. ; ; INPUTS: ; None. ; ; OUTPUTS: ; Music_Current: ; Set to the Eolis music. ; ; CALLS: ; Sound_PlayEffect ; Sprites_HasCurrentSprites ; EndGame_Begin ; ; XREFS: ; SPECIAL_SCREEN_EVENT_LOOKUP_TABLE ; [$PRG15_MIRROR::ef67] ;============================================================================
[efd4]ScreenEvents_HandleFinalBossKilled:; [$efd4] [efd4]JSR Sprites_HasCurrentSprites; Are there sprites on screen? [efd7]BCS @_return; If so, return.
; ; The final boss is dead. Transition toward the end game ; sequence. ; ; Start by switching the music to the Eolis music. ;
[efd9]LDA #$07; 0x07 == Eolis music [efdb]STA Music_Current; Set as the current music.
; ; Play the "Player got hit" sound. ;
[efdd]LDA #$04; 0x04 == Player hit sound effect. [efdf]JSR Sound_PlayEffect; Play it. [efe2]JMP EndGame_Begin; Begin the end-game sequence. [efe5]@_return:; [$efe5] [efe5]RTS
;============================================================================ ; Return whether the given sprite entity is *not* on the screen. ; ; This will loop through the sprites on screen and check if any ; match the entity type provided. ; ; INPUTS: ; A: ; The sprite entity to check for. ; ; CurrentSprites_Entities: ; The list of current sprite entities on screen. ; ; OUTPUTS: ; C: ; 0 = Sprite entity is on screen. ; 1 = Sprite entity is not on screen. ; ; Temp_00: ; Clobbered. ; ; XREFS: ; SpriteBehavior_BattleHelmetDroppedByZoradohna ; SpriteBehavior_BattleSuitDroppedByZoradohna ; SpriteBehavior_BlackOnyxDropFromZoradohna ; SpriteBehavior_DragonSlayerDroppedByKingGrieve ; SpriteBehavior_MattockDroppedFromRipasheiku ; SpriteBehavior_PendantDroppedFromRipasheiku ; SpriteBehavior_WingBootsDroppedByZorugeriru ;============================================================================
[efe6]Maybe_IsSpriteEntityNotOnScreen:; [$efe6] [efe6]STA Temp_00; Temp_00 = Sprite type for comparison. [efe8]LDY #$07; Y = 7
; ; Check the entity for this index in the current sprites list. ;
[efea]@_spriteLoop:; [$efea] [efea]LDA CurrentSprites_Entities,Y; Load the entity of the sprite at loop index. [efed]CMP Temp_00; Is it the entity the caller is checking for? [efef]BNE @_prepareNextLoopIter; If not, prepare for the next loop iteration.
; ; The sprite entity is on the screen. Clear carry as the result. ;
[eff1]CLC; Return false. [eff2]RTS [eff3]@_prepareNextLoopIter:; [$eff3] [eff3]DEY; Y-- [eff4]BPL @_spriteLoop; If Y >= 0, loop.
; ; The sprite entity is not on the screen. Set carry as the ; result. ;
[eff6]SEC; Return true. [eff7]RTS
;============================================================================ ; Return whether there's a boss on screen. ; ; This loops through all the sprite entities on the screen, ; looks up the category for each, and checks if that ; category is a boss. ; ; INPUTS: ; CurrentSprites_Entities: ; The list of current sprite entities on screen. ; ; SPRITE_CATEGORIES_BY_ENTITY: ; Lookup table of entities to categories. ; ; OUTPUTS: ; C: ; 0 if there is no boss on screen. ; 1 if there is a boss on screen. ; ; XREFS: ; ScreenEvents_HandleBoss ;============================================================================
[eff8]Sprites_HasBoss:; [$eff8] [eff8]LDX #$07; X = 7 (loop counter).
; ; Check if this sprite is a boss. ;
[effa]@_loop:; [$effa] [effa]LDY CurrentSprites_Entities,X; Load the sprite entity at that index. [effd]LDA SPRITE_CATEGORIES_BY_ENTITY,Y; Load the category for the entity. [f000]CMP #$07; Is it 7 (boss)? [f002]BNE @_notBoss; If not, jump.
; ; There is a boss on screen. Return true. ;
[f004]SEC; Return true. [f005]RTS [f006]@_notBoss:; [$f006] [f006]DEX; X-- [f007]BPL @_loop; If X >= 0, loop.
; ; There is no boss on screen. Return false. ;
[f009]CLC; Return false. [f00a]RTS
;============================================================================ ; Return whether any sprites are currently on screen. ; ; INPUTS: ; CurrentSprites_Entities: ; The types of sprites currently on the screen. ; ; OUTPUTS: ; C: ; 1 if there are sprites on the screen. ; 0 if there are no sprites. ; ; XREFS: ; ScreenEvents_HandleFinalBossKilled ;============================================================================
[f00b]Sprites_HasCurrentSprites:; [$f00b] [f00b]LDY #$07; Y = 7 (loop counter). [f00d]@_loop:; [$f00d] [f00d]LDA CurrentSprites_Entities,Y; Load the sprite entity at that index. [f010]CMP #$ff; Is it unset? [f012]BEQ @_prepareNextLoopIter; If yes, jump to prepare for the next loop iteration.
; ; A sprite entity was found. Return true. ;
[f014]SEC; Return true. [f015]RTS [f016]@_prepareNextLoopIter:; [$f016] [f016]DEY; Y-- [f017]BPL @_loop; If Y >= 0, loop.
; ; A sprite entity was not found. Return false. ;
[f019]CLC; Return false. [f01a]RTS
;============================================================================ ; Draw part of a portrait to the screen as sprites. ; ; This will load the portrait tileinfo as an offset from ; the value referenced in PORTRAIT_APPEARANCES_OFFSET ; (TILEINFO_PORTRAITS_ADDRS) and drawn as a sprite to ; the screen. ; ; INPUTS: ; A: ; The offset into the portrait tileinfo table. ; ; CurrentROMBank: ; The current ROM bank to switch back to. ; ; OUTPUTS: ; Temp_Addr_U: ; Temp_Addr_L: ; Clobbered. ; ; CALLS: ; MMC1_UpdateROMBank ; Sprite_Draw ; ; XREFS: ; IScripts_DrawPortraitAnimationFrame ;============================================================================
[f01b]Sprite_DrawPortraitPartAppearance:; [$f01b] [f01b]TAY; Y = A (appearance offset).
; ; Save the current ROM bank and switch to bank 7. ;
[f01c]LDA a:CurrentROMBank; Load the current ROM bank. [f01f]PHA; Push it to the stack. [f020]LDX #$07; Bank 7 = Sprites [f022]JSR MMC1_UpdateROMBank; Switch to it.
; ; Load the address to load sprite images from. ; ; With the addresses stored in PORTRAIT_APPEARANCES_OFFSET ; and ; PORTRAIT_APPEARANCES_OFFSET+1, this should put us at a ; starting point of {@address PRG7::AFD5}, or {@address PRG7::B0D5} if ; offset overflowed. ;
[f025]TYA; A = Y (appearance offset). [f026]ASL A; Convert to a word boundary for an index into the tileinfo table. [f027]TAY; Y = A (index). [f028]PHP; Push flags to the stack. [f029]LDA a:PORTRAIT_APPEARANCES_OFFSET; Load the lower byte of the portraits tileinfo address. [f02c]STA Temp_Addr_L; Store as the lower byte in the read address. [f02e]LDA a:PORTRAIT_APPEARANCES_OFFSET+1; Load the upper byte of the portraits tileinfo address. [f031]PLP; Pop flags from the stack. [f032]ADC #$80; Add 0x80 to the upper byte. [f034]STA Temp_Addr_U; And store it. [f036]JMP Sprite_Draw; Draw the portrait sprite.
;============================================================================ ; TODO: Document Sprite_SetPlayerAppearanceAddr ; ; INPUTS: ; A ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_DrawShield ; Player_DrawWeapon ; FUN_PRG15_MIRROR__ec58 ; Player_DrawBody ;============================================================================
[f039]Sprite_SetPlayerAppearanceAddr:; [$f039] [f039]TAY [f03a]LDA a:CurrentROMBank [f03d]PHA [f03e]LDX #$07 [f040]JSR MMC1_UpdateROMBank [f043]TYA
; ; Load the address to load sprite images from. ; ; With the addresses stored in PLAYER_APPEARANCES_OFFSET ; and ; PLAYER_APPEARANCES_OFFSET+1, this should put us at a ; starting ; point of {@address PRG7::A9F1}, or {@address PRG7::AAF1} if offset ; overflowed. ;
[f044]ASL A [f045]TAY [f046]PHP [f047]LDA a:PLAYER_APPEARANCES_OFFSET [f04a]STA Temp_Addr_L [f04c]LDA a:PLAYER_APPEARANCES_OFFSET+1 [f04f]PLP [f050]ADC #$80 [f052]STA Temp_Addr_U [f054]JMP Sprite_Draw
;============================================================================ ; TODO: Document Sprite_SetAppearanceAddrFromOffset ; ; INPUTS: ; A ; ; OUTPUTS: ; TODO ; ; XREFS: ; Sprite_EnterNextAppearancePhase ; CastMagic_SetAppearance ; UI_DrawPromptInputSymbol ;============================================================================
[f057]Sprite_SetAppearanceAddrFromOffset:; [$f057]
; ; Switch to ROM bank 7 (PRG). ;
[f057]TAY; Y = A (offset) [f058]LDA a:CurrentROMBank; Load the current ROM bank. [f05b]PHA; Push it to the stack. [f05c]LDX #$07 [f05e]JSR MMC1_UpdateROMBank; Switch to bank 7 (PRG).
; ; Load the address to load sprite images from. ; ; With the addresses stored in SPRITE_APPEARANCES_OFFSET ; and ; SPRITE_APPEARANCES_OFFSET+1, this should put us at a ; starting ; point of {@address PRG7::9036}, or {@address PRG7::9136} if offset ; overflowed. ;
[f061]TYA; A = Y (restore offset) [f062]ASL A; Multiply offset by 2, to get word boundaries in the lookup table. [f063]TAY; Y = A (updated offset) [f064]PHP; Push flags and stack pointer. [f065]LDA a:SPRITE_APPEARANCES_OFFSET; Load the lower byte of the address to read from. [f068]STA Temp_Addr_L; Store it for processing. [f06a]LDA a:SPRITE_APPEARANCES_OFFSET+1; Load the upper byte. [f06d]PLP; Pop the flags and stack pointer. [f06e]ADC #$80; Add 0x80 + carry to the upper byte. [f070]STA Temp_Addr_U; Store it.
; ; v-- Fall through --v ;
;============================================================================ ; Begin drawing a sprite. ; ; This will load the tiles information for a sprite, ; determining the main bounding box, the extents, the ; pivot point when flipping, and generating a suitable ; X and Y position to begin drawing tiles. ; ; Tile information begins with a header in the following ; form: ; ; Byte 0: Upper nibble = Row count - 1 ; Lower nibble = Column count -1 ; Byte 1: Offset X in pixels (if positive, extends the ; sprite right; if negative, extends left) ; Byte 2: Offset Y in pixels (if positive, extends the ; sprite downward; if negative, extends upward) ; Byte 3: Block-aligned X pixel pivot position used to ; render the main body of the sprite in a stable ; position when flipping horizontally (only used ; in Sprite_Draw_FlippedHoriz. ; ; This is followed by bytes pointing to tile indexes for the ; list of available tiles for the sprite (with 0xFF meaning ; "empty tile"). Sprite_Draw_Standard and ; Sprite_Draw_FlippedHoriz handles this. ; ; INPUTS: ; ; ; XREFS: ; Sprite_DrawPortraitPartAppearance ; Sprite_SetPlayerAppearanceAddr ;============================================================================
[f072]Sprite_Draw:; [$f072]
; ; Load the offset within our starting address stored in ; Temp_Addr_L. ;
[f072]LDA (Temp_Addr_L),Y; Load the offset for the lower byte. [f074]STA Sprite_TileInfo_ReadAddr_L; Store it. [f076]INY; Y++ (offset) [f077]LDA (Temp_Addr_L),Y; Load the upper byte. [f079]ADC #$80; Add 0x80 + carry to it. [f07b]STA Sprite_TileInfo_ReadAddr_U; Store it.
; ; Load a byte from that offset. The upper nibble will be ; stored at Temp_Sprites_RowsRemaining and the lower nibble ; will ; be in Temp_Sprites_ColsRemaining. ;
[f07d]LDY #$00; Y = 0 (read offset) [f07f]LDA (Sprite_TileInfo_ReadAddr_L),Y; Load the value at that address. [f081]AND #$0f; Discard the upper nibble. [f083]STA Temp_Sprites_ColsRemaining; Store it. [f085]LDA (Sprite_TileInfo_ReadAddr_L),Y; Load it again. [f087]LSR A; Move upper nibble to lower. [f088]LSR A [f089]LSR A [f08a]LSR A [f08b]STA Temp_Sprites_RowsRemaining; Store it.
; ; Initialize Sprite_TileInfo_OffsetX and ; Sprite_TileInfo_OffsetY ; to 0 by default. ;
[f08d]LDA #$00; A = 0 [f08f]STA Sprite_TileInfo_OffsetX; Set to 0. [f091]STA Sprite_TileInfo_OffsetY; Set to 0.
; ; Load the X offset extending from the main bounding box ; to fill with tiles. ; ; This may be negative (filling tiles left of the main bounding ; box of the sprite) or positive (adding padding left of the ; sprite). ;
[f093]INY; Y++ (read offset) [f094]LDA (Sprite_TileInfo_ReadAddr_L),Y; Load the X offset byte. [f096]BPL @_calcX; If > 0, jump. [f098]DEC Sprite_TileInfo_OffsetX; Set to 0xFF. This will set C=1 for the add below. [f09a]@_calcX:; [$f09a] [f09a]CLC [f09b]ADC Arg_DrawSprite_PosX; Add Arg_DrawSprite_PosX to the X offset (which may be negative, extending left). [f09d]STA Temp_Sprites_NormXPos; Store it.
; ; Calculate the new value for Sprite_TileInfo_OffsetX. ;
[f09f]LDA #$00; A = 0 [f0a1]ADC Sprite_TileInfo_OffsetX; Add our stored value. [f0a3]STA Sprite_TileInfo_OffsetX; Store it back out.
; ; Update the drawn X position to factor in any screen scrolling ; offsets. ;
[f0a5]LDA Temp_Sprites_NormXPos; Load the new calculated X position. [f0a7]SEC [f0a8]SBC PPU_ScrollX; Subtract any scrolling offset that may be set. [f0aa]STA Temp_Sprites_NormXPos; Store it back.
; ; Load the Y offset extending from the main bounding box ; to fill with tiles. ; ; This may be negative (filling tiles above the main bounding ; box of the sprite) or positive (adding padding above the ; sprite). ;
[f0ac]INY; Y++ (read offset) [f0ad]LDA (Sprite_TileInfo_ReadAddr_L),Y; Load the Y offset byte. [f0af]BPL @_calcY; If > 0, jump. [f0b1]DEC Sprite_TileInfo_OffsetY; Set to 0xFF. This will set C=1 for the add below. [f0b3]@_calcY:; [$f0b3] [f0b3]CLC [f0b4]ADC Arg_DrawSprite_PosY; Add Arg_DrawSprite_PosY to the Y offset (which may be negative, extending up). [f0b6]STA Temp_Sprites_NormYPos; Store it.
; ; Add Carry to the offset, if it's set. ;
[f0b8]LDA #$00 [f0ba]ADC Sprite_TileInfo_OffsetY; A = Y offset + carry (from above). [f0bc]STA Sprite_TileInfo_OffsetY; Store it.
; ; And then always offset by 32 pixels (the HUD height). ;
[f0be]LDA Temp_Sprites_NormYPos; Load the normalized Y position. [f0c0]CLC [f0c1]ADC #$20; Add 32 (HUD height in pixels). [f0c3]STA Temp_Sprites_NormYPos; Store it.
; ; Add Carry again to the Y offset, if set. This happens if ; the HUD offset causes the value to overflow. ;
[f0c5]LDA Sprite_TileInfo_OffsetY; Load the Y offset. [f0c7]ADC #$00; Add Carry, if set. [f0c9]STA Sprite_TileInfo_OffsetY; Store it.
; ; Load the block-aligned X pixel pivot point into the sprite ; used to anchor the sprite when flipping. This is generally ; the left-most tile of the main body of the sprite. When ; the sprite turns around, it pivots at this point. ; ; Only Sprite_Draw_FlippedHoriz uses this. ;
[f0cb]INY; Y++ (read offset). [f0cc]LDA (Sprite_TileInfo_ReadAddr_L),Y; Load the pivot point in pixels. [f0ce]STA Temp_Addr_L; Store it for use if drawing flipped.
; ; Advance our start read address for this sprite to skip the 4 ; bytes we just read. ;
[f0d0]LDA Sprite_TileInfo_ReadAddr_L; Load the lower byte of the sprite address. [f0d2]CLC [f0d3]ADC #$04; Advance by 4 (skipping loaded state). [f0d5]STA Sprite_TileInfo_ReadAddr_L; Set as the new lower byte of the address. [f0d7]LDA Sprite_TileInfo_ReadAddr_U; Load the upper byte. [f0d9]ADC #$00; Add the carry, if lower overflowed. [f0db]STA Sprite_TileInfo_ReadAddr_U; Set it.
; ; We'll continue on based on whether the sprite is flipped. ;
[f0dd]LDA CurrentSprite_FlipMask; Load the flip mask. [f0df]AND #$c0; Keep only the horiz/vert flip bits. [f0e1]STA CurrentSprite_FlipMask; Store as a new flip mask. [f0e3]LDA CurrentSprite_FlipMask; Load the resulting flip mask. [f0e5]AND #$40; Check if it's flipped horizontally. [f0e7]BEQ Sprite_Draw_Standard; If not, draw in standard orientation. [f0e9]JMP Sprite_Draw_FlippedHoriz; Else, draw horizontally flipped.
;============================================================================ ; Draw all tiles for a sprite in the standard orientation. ; ; This will loop through all rows of the sprite, then all ; columns in a row, drawing each tile of the sprite. ; ; Tiles are read from the sprite information in bank 7, ; which is in the following form: ; ; Byte 0: Upper = Row count - 1 ; Lower = Column count -1 ; Byte 1: Offset X (between first tile and "front" tile ; used for alignment) ; Byte 2: Offset Y ; Byte 3: Block-aligned pixel position of the "front" ; tile for alignment when flipping horizontally ; ; Followed by bytes pointing to tile indexes for the list ; of available tiles for the sprite (with 0xFF meaning ; "empty tile"). ; ; INPUTS: ; ; ; XREFS: ; Sprite_Draw ;============================================================================
[f0ec]Sprite_Draw_Standard:; [$f0ec]
; ; The sprite is not flipped. ;
[f0ec]LDA #$00; A = 0. [f0ee]STA Temp_Sprites_TileYOffset; Store it as the starting tiles Y offset. [f0f0]TAY; Y = A (0, loop counter). [f0f1]@_drawRowLoop:; [$f0f1] [f0f1]LDA Temp_Sprites_ColsRemaining; Load the number of tile columns remaining. [f0f3]PHA; Push it to the stack. [f0f4]LDA #$00; A = 0. [f0f6]STA Temp_Sprites_TileXOffset; Store as the start tiles X offset.
; ; Check if the current sprite data value is not 0xFF (unset). ;
[f0f8]@_drawColLoop:; [$f0f8] [f0f8]LDA (Sprite_TileInfo_ReadAddr_L),Y; Load the value at the sprite data read address. [f0fa]CMP #$ff; Is it 0xFF? [f0fc]BEQ @_prepareNextCol; If so, skip this row.
; ; It's not unset. ; ; Begin checking if we can add to the PPU. ; ; First we'll check if we can add sprites, or if the slots ; are full. ;
[f0fe]LDA Sprites_SlotsFull; Can we add sprites this frame? [f100]BNE @_endColLoop; If not, jump, try the next row.
; ; We can add sprites. ; ; Calculate the X coordinate as: ; ; X = Temp_Sprites_NormXPos + ; SPRITE_TILE_BLOCK_POSITIONS[Temp_Sprites_TileXOffset] ; ; With Temp_Sprites_TileXOffset being a block ; offset (each block being 16 pixels), the sprites are ; effectively started at a block position and then added ; normalized to a pixel position. ;
[f102]LDX Temp_Sprites_TileXOffset; X = tile X offset. [f104]LDA Temp_Sprites_NormXPos; A = normalized X position. [f106]ADC SPRITE_TILE_BLOCK_POSITIONS,X; A += the offset from the lookup table at X. [f109]STA Temp_00; Store it temporarily.
; ; Check if this tile would fit on screen in the X direction. ;
[f10b]LDA Sprite_TileInfo_OffsetX; Load the default X position. [f10d]ADC #$00; Add carry (from overflow), if set. [f10f]BNE @_endColLoop; If it doesn't fit on screen, jump.
; ; X could fit on screen. Check Y. ; ; This will be calculated as: ; ; Y = Temp_Sprites_NormYPos + ; SPRITE_TILE_BLOCK_POSITIONS[Temp_Sprites_TileYOffset] ; ; Same logic applies as aboe. ;
[f111]LDX Temp_Sprites_TileYOffset; X = tile Y offset. [f113]LDA Temp_Sprites_NormYPos; A = normalized Y position. [f115]ADC SPRITE_TILE_BLOCK_POSITIONS,X; A += the offset from the lookup table at X. [f118]STA Temp_01; Store it temporarily.
; ; Check if this tile would fit on screen in the Y direction. ;
[f11a]LDA Sprite_TileInfo_OffsetY; Load the default Y position. [f11c]ADC #$00; Add carry (from overflow), if set. [f11e]BNE @_endColLoop; If it doesn't fit on screen, jump.
; ; The sprite fits on screen. ; ; We'll begin writing the sprite to DMA. ;
[f120]TYA; A = Y (loop counter) [f121]PHA; Push our loop counter to the stack. [f122]LDA Sprites_SpriteSlot; Load the slot for this sprite. [f124]ASL A; Multiply by 4 (for Y, tile ID, attributes, X bytes). [f125]ASL A [f126]EOR Sprites_StartSlotRange
; ; Write the sprite Y position to DMA. ;
[f128]TAX; X = A (read offset) [f129]LDA Temp_01; Load the Y position. [f12b]STA SPRITE_0_RANGE_1_START,X; Write the Y position to DMA.
; ; Write the sprite tile ID to DMA. ; ; The byte value is an index into the list of PPU tile IDs. ;
[f12e]INX; X++ (read offset) [f12f]LDA (Sprite_TileInfo_ReadAddr_L),Y; Load the tile index. [f131]CLC [f132]ADC Sprites_PPUOffset; Add the PPU starting offset for the list of tiles. [f134]STA SPRITE_0_RANGE_1_START,X; Write the tile ID to DMA.
; ; Write the sprite attributes. ; ; This will primarily be the palette, but it may also contain ; a flip mask. The flip mask will be normalized based on ; whether the full sprite is flipped. ;
[f137]INX; X++ (read offset) [f138]INY; Y++ (loop counter) [f139]LDA (Sprite_TileInfo_ReadAddr_L),Y; Load the attribute. [f13b]EOR CurrentSprite_FlipMask; XOR with the flip mask. [f13d]STA Temp_01; And store it.
; ; Calculate visibility for this tile. ; ; This will check if the tile is even or odd, using that ; as an index into DRAW_SPRITE_VISIBILITY_MASK. ; ; If the tile is not visible, it will be set to background ; priority. ;
[f13f]LDA Temp_Sprites_TileXOffset; Load the tile X offset. [f141]AND #$01; Convert to an even/odd value for the visibility lookup table. [f143]TAY; Y = result. [f144]LDA MovingSpriteVisibility; Load the moving visibility of the sprite. [f146]AND DRAW_SPRITE_VISIBILITY_MASK,Y; AND with the lookup table, determining priority. [f149]BEQ @_storeAttribute; If non-0, keep foreground priority. Else...
; ; The tile is obscured. Set the sprite to be a ; background sprite. ;
[f14b]LDA Temp_01; Load the attribute calculated above. [f14d]ORA #$20; Set background priority. [f14f]STA Temp_01; And store it.
; ; Write the tile attribute to DMA. ;
[f151]@_storeAttribute:; [$f151] [f151]LDA Temp_01; Load the calculated attribute. [f153]STA SPRITE_0_RANGE_1_START,X; Store the tile attributes to DMA.
; ; Write the sprite X position to DMA. ;
[f156]INX; X++ (read offset) [f157]LDA Temp_00; Load the X position. [f159]STA SPRITE_0_RANGE_1_START,X; Write the X coordinate to DMA.
; ; Prepare for the next column. ;
[f15c]JSR Sprites_IncNextSpriteSlot; Finish up with this sprite's state. [f15f]PLA; Pull A (loop counter) from stack. [f160]TAY; Y = A (loop counter) [f161]@_endColLoop:; [$f161] [f161]INY; Y++ (loop counter) [f162]@_prepareNextCol:; [$f162] [f162]INY; Y++ (loop counter) [f163]INC Temp_Sprites_TileXOffset; Increment the tile X offset. [f165]DEC Temp_Sprites_ColsRemaining; Decrement the number of columns remaining. [f167]BPL @_drawColLoop; If there are columns remaining, loop.
; ; We're on the next row. Reset the column count and advance ; the row counter. ;
[f169]PLA; Pop the original total columns remaining. [f16a]STA Temp_Sprites_ColsRemaining; Store as the new columns count for the next row. [f16c]INC Temp_Sprites_TileYOffset; Increment the tile Y offset. [f16e]DEC Temp_Sprites_RowsRemaining; Decrement the number of rows remaining. [f170]BMI Sprite_Draw_Finish; If there are no more rows, finish the drawing. [f172]JMP @_drawRowLoop; Else, loop to draw the next row.
;============================================================================ ; TODO: Document Sprite_Draw_Finish ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Sprite_Draw_FlippedHoriz ; Sprite_Draw_Standard ;============================================================================
[f175]Sprite_Draw_Finish:; [$f175] [f175]PLA [f176]TAX [f177]JSR MMC1_UpdateROMBank [f17a]LDA #$00 [f17c]STA Sprites_PPUOffset [f17e]STA MovingSpriteVisibility [f180]RTS
;============================================================================ ; TODO: Document Sprite_Draw_FlippedHoriz ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Sprite_Draw ;============================================================================
[f181]Sprite_Draw_FlippedHoriz:; [$f181]
; ; The sprite is flipped. ;
[f181]LDA #$00 [f183]STA Temp_05; Set Temp_05 = 0. [f185]LDY Temp_Sprites_ColsRemaining; Load the number of columns remaining for a row. [f187]INY; Y++ (convert from 0-based count) [f188]TYA; A = result [f189]ASL A; Multiply the columns by 8. [f18a]ASL A [f18b]ASL A [f18c]STA Temp_04; Store for temporary use. [f18e]LDA Temp_Addr_L; A = 4th byte from the sprite information. [f190]ASL A; A = A * 2 [f191]SEC [f192]SBC Temp_04; A = A - normalized column value. [f194]BPL @LAB_PRG15_MIRROR__f198; If negative, jump. [f196]DEC Temp_05; Else, it's positive. Decrement Temp_05. [f198]@LAB_PRG15_MIRROR__f198:; [$f198] [f198]CLC [f199]ADC Temp_Sprites_NormXPos; A = A + normalized X position [f19b]STA Temp_Sprites_NormXPos; Store as the new position. [f19d]LDA Sprite_TileInfo_OffsetX [f19f]ADC Temp_05 [f1a1]STA Sprite_TileInfo_OffsetX [f1a3]LDA #$00 [f1a5]STA Temp_Sprites_TileYOffset [f1a7]TAY [f1a8]@LAB_PRG15_MIRROR__f1a8:; [$f1a8] [f1a8]LDA Temp_Sprites_ColsRemaining [f1aa]STA Temp_Sprites_TileXOffset [f1ac]@LAB_PRG15_MIRROR__f1ac:; [$f1ac] [f1ac]LDA (Sprite_TileInfo_ReadAddr_L),Y [f1ae]CMP #$ff [f1b0]BEQ @LAB_PRG15_MIRROR__f216 [f1b2]LDA Sprites_SlotsFull [f1b4]BNE @LAB_PRG15_MIRROR__f215 [f1b6]LDX Temp_Sprites_TileXOffset [f1b8]LDA Temp_Sprites_NormXPos [f1ba]ADC SPRITE_TILE_BLOCK_POSITIONS,X [f1bd]STA Temp_00 [f1bf]LDA Sprite_TileInfo_OffsetX [f1c1]ADC #$00 [f1c3]BNE @LAB_PRG15_MIRROR__f215 [f1c5]LDX Temp_Sprites_TileYOffset [f1c7]LDA Temp_Sprites_NormYPos [f1c9]ADC SPRITE_TILE_BLOCK_POSITIONS,X [f1cc]STA Temp_01 [f1ce]LDA Sprite_TileInfo_OffsetY [f1d0]ADC #$00 [f1d2]BNE @LAB_PRG15_MIRROR__f215 [f1d4]TYA [f1d5]PHA [f1d6]LDA Sprites_SpriteSlot [f1d8]ASL A [f1d9]ASL A [f1da]EOR Sprites_StartSlotRange [f1dc]TAX [f1dd]LDA Temp_01 [f1df]STA SPRITE_0_RANGE_1_START,X [f1e2]INX [f1e3]LDA (Sprite_TileInfo_ReadAddr_L),Y [f1e5]CLC [f1e6]ADC Sprites_PPUOffset [f1e8]STA SPRITE_0_RANGE_1_START,X [f1eb]INX [f1ec]INY [f1ed]LDA (Sprite_TileInfo_ReadAddr_L),Y [f1ef]EOR CurrentSprite_FlipMask [f1f1]STA Temp_01 [f1f3]LDA Temp_Sprites_TileXOffset [f1f5]AND #$01 [f1f7]TAY [f1f8]LDA MovingSpriteVisibility [f1fa]AND BYTE_ARRAY_PRG15_MIRROR__f226,Y [f1fd]BEQ @LAB_PRG15_MIRROR__f205 [f1ff]LDA Temp_01 [f201]ORA #$20 [f203]STA Temp_01 [f205]@LAB_PRG15_MIRROR__f205:; [$f205] [f205]LDA Temp_01 [f207]STA SPRITE_0_RANGE_1_START,X [f20a]INX [f20b]LDA Temp_00 [f20d]STA SPRITE_0_RANGE_1_START,X [f210]JSR Sprites_IncNextSpriteSlot [f213]PLA [f214]TAY [f215]@LAB_PRG15_MIRROR__f215:; [$f215] [f215]INY [f216]@LAB_PRG15_MIRROR__f216:; [$f216] [f216]INY [f217]DEC Temp_Sprites_TileXOffset [f219]BPL @LAB_PRG15_MIRROR__f1ac [f21b]INC Temp_Sprites_TileYOffset [f21d]DEC Temp_Sprites_RowsRemaining [f21f]BPL @LAB_PRG15_MIRROR__f1a8 [f221]JMP Sprite_Draw_Finish
; ; XREFS: ; Sprite_Draw_Standard ;
[f224]DRAW_SPRITE_VISIBILITY_MASK:; [$f224] [f224].byte $01; [0]: Trailing visibility [f225].byte $02; [1]: Leading visibility
; ; XREFS: ; Sprite_Draw_FlippedHoriz ;
[f226]BYTE_ARRAY_PRG15_MIRROR__f226:; [$f226] [f226].byte $02; [0]: [f227].byte $01; [1]:
;============================================================================ ; Increment the sprite slot, and mark when full. ; ; This alternates the sprite slots between 0-31 and 32-63. ; When populating 0-31, it will track whether all slots are ; full, and if so, Sprites_SlotsFull will be set ; to prevent further sprites from being added. ; ; INPUTS: ; Sprites_SpriteSlot: ; The current slot for the next sprite. ; ; OUTPUTS: ; Sprites_SpriteSlot: ; The next slot for the next sprite, if updated. ; ; Sprites_SlotsFull: ; Set to 1 (rather, incremented, but practically) ; if the slots are full. ; ; XREFS: ; Sprite_Draw_FlippedHoriz ; Sprite_Draw_Standard ;============================================================================
[f228]Sprites_IncNextSpriteSlot:; [$f228]
; ; Alternate between sprites 0-31 and sprites 32-63. ; ; The bit being flipped is bit 5, value 32. ;
[f228]LDA Sprites_SpriteSlot; Load the sprite slot ID. [f22a]EOR #$20; Cap the value to < 32. [f22c]STA Sprites_SpriteSlot; Store the result.
; ; If we're populating sprites 0-31, we'll check if we've ; hit the cap. If we're populating 32-63, we're done. ;
[f22e]AND #$20; Check if we're in 32-63. [f230]BNE @_return; If so, return.
; ; It's in sprite range 0-31. ;
[f232]INC Sprites_SpriteSlot; Increase the sprite slot. [f234]LDA Sprites_SpriteSlot; Load the new slot. [f236]CMP #$20; Are there slots left? [f238]BCC @_return; If so, return.
; ; The slots are full. Mark it. ;
[f23a]INC Sprites_SlotsFull; Mark slots as full. [f23c]@_return:; [$f23c] [f23c]RTS
; ; XREFS: ; Sprite_Draw_FlippedHoriz ; Sprite_Draw_Standard ;
[f23d]SPRITE_TILE_BLOCK_POSITIONS:; [$f23d] [f23d].byte $00; [0]: [f23e].byte $08; [1]: [f23f].byte $10; [2]: [f240].byte $18; [3]: [f241].byte $20; [4]: [f242].byte $28; [5]: [f243].byte $30; [6]: [f244].byte $38; [7]: [f245].byte $40; [8]: [f246].byte $48; [9]: [f247].byte $50; [10]: [f248].byte $58; [11]: [f249].byte $60; [12]: [f24a].byte $68; [13]: [f24b].byte $70; [14]: [f24c].byte $78; [15]:
;============================================================================ ; Draw the portrait to the PPU. ; ; This will load the portrait tiles and palette and ; draw them to the PPU. ; ; INPUTS: ; A: ; The portrait ID to draw. ; ; OUTPUTS: ; IScript_PortraitID: ; The new portrait ID. ; ; Palette_SavedIndex: ; A saved copy of the current palette ID. ; ; PPU_TargetAddr: ; PPU_TargetAddr+1: ; Clobbered. ; ; CALLS: ; Game_WaitForOAMReset ; IScripts_LoadPortraitTilesAddress ; IScripts_DrawPortraitTileToPPU ; PPUBuffer_WritePalette ; Screen_LoadSpritePalette ; ; XREFS: ; IScripts_Begin ;============================================================================
[f24d]IScripts_LoadPortraitTiles:; [$f24d]
; ; Check if a portrait ID is set. ;
[f24d]STA a:IScript_PortraitID; Set the current portrait ID. [f250]TAX; X = result. [f251]BMI @_return; If unset, return.
; ; The portrait ID is set. Load the address and wait for an ; OAM reset. ;
[f253]JSR IScripts_LoadPortraitTilesAddress; Load the tiles address for this portrait ID. [f256]JSR Game_WaitForOAMReset; Wait for OAM reset.
; ; Save the sprite palette and switch to palette 2 for the ; portrait. ;
[f259]LDA a:Palette_SpritePaletteIndex; Load the current sprite palette index [f25c]STA a:Palette_SavedIndex; And save it for later. [f25f]LDA #$02 [f261]JSR Screen_LoadSpritePalette; Load palette 2. [f264]JSR PPUBuffer_WritePalette; Write the palette to the PPU.
; ; Set the target PPU address to draw the portrait to. ; This will be $0900. ;
[f267]LDA #$09; Upper byte = 0x09. [f269]STA PPU_TargetAddr_U; Set it. [f26b]LDA #$00; Lower byte = 0x00. [f26d]STA PPU_TargetAddr; Set it.
; ; Begin drawing the portrait. This will loop up to ; 256 times, drawing 16 tiles each. ;
[f26f]LDY #$00; Y = 0 (loop counter). [f271]@_drawLoop:; [$f271] [f271]TYA; A = Y (loop counter). [f272]PHA; Push it to the stack. [f273]JSR IScripts_DrawPortraitTileToPPU; Draw a 16-byte tile to the PPU. [f276]BCS @_finishDrawing [f278]PLA; Pop the loop counter from the stack. [f279]TAY; Set back in Y. [f27a]INY; Y++ [f27b]BNE @_drawLoop; If not wrapped to 0, loop. [f27d]@_return:; [$f27d] [f27d]RTS
; ; The portrait is finished. ;
[f27e]@_finishDrawing:; [$f27e] [f27e]PLA; Pop the loop counter from the stack. [f27f]TAY; Set back in Y. [f280]RTS
;============================================================================ ; Clear the portrait from the textbox. ; ; INPUTS: ; Palette_SavedIndex: ; The palette index to restore. ; ; OUTPUTS: ; IScript_PortraitID: ; Set to 0xFF. ; ; CALLS: ; Game_WaitForOAMReset ; Screen_LoadSpritePalette ; PPUBuffer_WritePalette ; MMC1_LoadBankAndJump ; GameLoop_LoadSpriteImages ; ; XREFS: ; IScriptAction_EndScript ;============================================================================
[f281]IScripts_ClearPortraitImage:; [$f281] [f281]LDA #$ff [f283]STA a:IScript_PortraitID; Clear the portrait ID. [f286]JSR Game_WaitForOAMReset; Wait for OAM reset. [f289]LDA a:Palette_SavedIndex; Load the saved sprite palette ID. [f28c]JSR Screen_LoadSpritePalette; Load that palette's data. [f28f]JSR PPUBuffer_WritePalette; Write to the PPU buffer.
; ; Switch to bank 14 and run ; GameLoop_LoadSpriteImages ; (from bank 15). ;
[f292]JSR MMC1_LoadBankAndJump; Load sprite imagesin bank 14. [f295].byte BANK_14_LOGIC; Bank = 14 Address = GameLoop_LoadSpriteImages [f296].word GameLoop_LoadSpriteImages-1; GameLoop_LoadSpriteImages [$PRG15_MIRROR::f296] [f298]@_afterFarJump:; [$f298] [f298]RTS
; ; The portrait ID is unset. There's nothing to do. ; ; ; XREFS: ; IScripts_DrawPortraitAnimationFrame ;
[f299]IScripts_DrawPortraitAnimationFrame_Unset:; [$f299] [f299]PLA; Clean up the stack, popping the pushed frame. [f29a]RTS
;============================================================================ ; Draw a frame of portrait animation. ; ; This will draw the general portrait image and then ; layer on the eyes and mouth, animating them. ; ; Eyes will blink every other frame. Mouths will move ; every other frame. ; ; INPUTS: ; IScript_PortraitID: ; The current portrait ID. ; ; OUTPUTS: ; Sprites_PPUOffset: ; Clobbered. ; ; CALLS: ; IScripts_GetPortraitOffset ; Sprite_DrawPortraitPartAppearance ; ; XREFS: ; IScripts_UpdatePortraitAnimation ;============================================================================
[f29b]IScripts_DrawPortraitAnimationFrame:; [$f29b] [f29b]PHA; Push the frame to the stack. [f29c]LDA a:IScript_PortraitID; Load the current portrait ID. [f29f]BMI IScripts_DrawPortraitAnimationFrame_Unset; If 0xFF, jump to finish.
; ; There's an active portrait. Begin preparing by clearing ; the flip mask state. ;
[f2a1]LDA #$00 [f2a3]STA CurrentSprite_FlipMask; Set the flip mask to 0 (not flipped).
; ; Update the general portrait image. ;
[f2a5]LDA #$90 [f2a7]STA Sprites_PPUOffset; Set the PPU offset to $0090. [f2a9]JSR IScripts_GetPortraitOffset; Get the portrait offset into the tileinfo for the ID. [f2ac]JSR Sprite_DrawPortraitPartAppearance; Draw as the portrait.
; ; Update the eye animation. ; ; This will switch to new tiles every frame. ;
[f2af]LDA #$90 [f2b1]STA Sprites_PPUOffset; Set the PPU offset to $0090. [f2b3]PLA; Pull the frame from the stack. [f2b4]PHA; Push it again. We still have it in A. [f2b5]AND #$01; Generate an even/odd index from it. [f2b7]TAX; X = result. [f2b8]JSR IScripts_GetPortraitOffset; Get the offset into the table for the portrait ID. [f2bb]CLC [f2bc]ADC PORTRAIT_EYE_APPEARANCE_OFFSETS,X; Add the eyes animation offset for this even/odd frame. [f2bf]JSR Sprite_DrawPortraitPartAppearance; Draw as the eyes.
; ; Update the mouth animation. ; ; This will switch to new tiles every other frame. ;
[f2c2]LDA #$90 [f2c4]STA Sprites_PPUOffset; Set the PPU offset to $0090. [f2c6]PLA; Pull the frame from the stack. [f2c7]LSR A; Divide the frame by 2 (reducing the animation rate to switch every other frame). [f2c8]AND #$01; Convert that to an even/odd value. [f2ca]TAX; X = result. [f2cb]JSR IScripts_GetPortraitOffset; Get the offset into the table for the portrait ID. [f2ce]CLC [f2cf]ADC PORTRAIT_MOUTH_APPEARANCE_OFFSETS,X; Add the mouth animation offset for this even/odd frame. [f2d2]JMP Sprite_DrawPortraitPartAppearance; Draw as the mouth.
;============================================================================ ; Return the offset into the portraits table for the ID. ; ; This takes a portrait ID and returns an offset into ; TILEINFO_PORTRAITS_ADDRS. ; ; INPUTS: ; IScript_PortraitID: ; The current portrait ID. ; ; OUTPUTS: ; A: ; The offset into the table. ; ; XREFS: ; IScripts_DrawPortraitAnimationFrame ;============================================================================
[f2d5]IScripts_GetPortraitOffset:; [$f2d5] [f2d5]LDA a:IScript_PortraitID; Load the current portrait ID. [f2d8]ASL A; Multiply by 4. [f2d9]ASL A [f2da]CLC [f2db]ADC a:IScript_PortraitID; And add the ID again (effectively multiplying by a total of 5). [f2de]RTS; Return it as A.
;============================================================================ ; Offsets for the portrait eye blinking animation frames. ; ; XREFS: ; IScripts_DrawPortraitAnimationFrame ;============================================================================
; ; XREFS: ; IScripts_DrawPortraitAnimationFrame ;
[f2df]PORTRAIT_EYE_APPEARANCE_OFFSETS:; [$f2df] [f2df].byte $01; [0]: [f2e0].byte $02; [1]:
;============================================================================ ; Offsets for the portrait mouth movement animation frames. ; ; XREFS: ; IScripts_DrawPortraitAnimationFrame ;============================================================================
; ; XREFS: ; IScripts_DrawPortraitAnimationFrame ;
[f2e1]PORTRAIT_MOUTH_APPEARANCE_OFFSETS:; [$f2e1] [f2e1].byte $03; [0]: [f2e2].byte $04; [1]:
;============================================================================ ; Load the address for portrait tiles. ; ; This will switch to bank 8 and load the address used for ; the portrait tiles for the current portrait ID. ; ; Tiles are loaded from offsets into ; SPRITE_TILES_PORTRAITS_ADDRS_INDEX. ; ; INPUTS: ; CurrentROMBank: ; The current ROM bank to switch back to after load. ; ; IScript_PortraitID: ; The current portrait ID. ; ; SPRITE_TILES_PORTRAITS_ADDRS_INDEX: ; The pointer to the start of the portrait tiles ; list. ; ; OUTPUTS: ; Temp_Addr_U: ; Temp_Addr_L: ; The address for the portrait tiles. ; ; CALLS: ; MMC1_UpdatePRGBankToStackA ; MMC1_UpdateROMBank ; ; XREFS: ; IScripts_LoadPortraitTiles ;============================================================================
[f2e3]IScripts_LoadPortraitTilesAddress:; [$f2e3]
; ; Switch to bank 8 for the portrait sprites. ;
[f2e3]LDA a:CurrentROMBank; Load the current ROM bank. [f2e6]PHA; Push it to the stack. [f2e7]LDX #$08; Bank 8 = Sprites [f2e9]JSR MMC1_UpdateROMBank; Switch to the bank.
; ; Convert a portrait ID to a word boundary for later lookup. ;
[f2ec]LDA a:IScript_PortraitID; Load the portrait ID. [f2ef]ASL A; Convert to a word boundary. [f2f0]TAY; Y = result
; ; Load the address for the portrait tiles table. ;
[f2f1]LDA a:TILES_PORTRAITS_ADDRS_INDEX_REF; Load the lower byte of the start address for the portrait tiles. [f2f4]STA Temp_Addr_L; Store as the lower byte. [f2f6]LDA a:TILES_PORTRAITS_ADDRS_INDEX_REF+1; Load the upper byte of the portrait address. [f2f9]CLC [f2fa]ADC #$80; Add 0x80 to it. [f2fc]STA Temp_Addr_U; And store as the upper byte.
; ; Load the portrait from the sprite tiles list, using the ; portrait ID as the index. ;
[f2fe]LDA (Temp_Addr_L),Y; Load the lower byte of the address based on the potrait ID. [f300]PHA; Push it to the stack. [f301]INY; Y++ (next offset) [f302]LDA (Temp_Addr_L),Y; Load the upper byte of the tile. [f304]CLC [f305]ADC #$80; Add 0x80 to it. [f307]STA Temp_Addr_U; Store as the new upper byte. [f309]PLA; Pop the lower byte. [f30a]STA Temp_Addr_L; Store as the lower byte.
; ; Switch back to the stored bank. ;
[f30c]JMP MMC1_UpdatePRGBankToStackA; Switch to the previous bank.
; ; The byte for the tile was 0xFF. Restore the bank ; and set a result of C=1, causing drawing to finish. ; ; ; XREFS: ; IScripts_DrawPortraitTileToPPU ;
[f30f]IScripts_DrawPortraitTileToPPU_IsDone:; [$f30f] [f30f]PLA; Pop the saved bank from the stack. [f310]TAX; X = A (bank) [f311]JSR MMC1_UpdateROMBank; Switch to it. [f314]SEC; Set C=1 for the result. [f315]RTS; Return it.
;============================================================================ ; Draw a portrait tile to the PPU. ; ; This will load the next byte for the portrait, which ; represents an offset into the portrait tile list. ; ; If this is 0xFF, then drawing is complete, and this will ; return with C=1. ; ; Otherwise, the tile will be drawn to the PPU and state ; will be prepared for the next tile. ; ; INPUTS: ; CurrentROMBank: ; The current ROM bank to return to. ; ; Temp_Addr_U: ; Temp_Addr_L: ; The tile information address to read from. ; ; OUTPUTS: ; C: ; 1 = Drawing is finished (byte was 0xFF). ; 0 = There are more tiles to draw. ; ; PPUBuffer: ; PPUBuffer_WriteOffset: ; The updated PPU buffer state. ; ; PPU_TargetAddr: ; The updated PPU target address (incremented ; by 16). ; ; CALLS: ; MMC1_UpdateROMBank ; PPUBuffer_QueueCommandOrLength ; ; XREFS: ; IScripts_LoadPortraitTiles ;============================================================================
[f316]IScripts_DrawPortraitTileToPPU:; [$f316]
; ; Save the current bank and switch to bank 8. ;
[f316]LDA a:CurrentROMBank; Load the current ROM bank. [f319]PHA; Push it to the stack. [f31a]LDX #$08; Bank 8 = Portrait sprites [f31c]JSR MMC1_UpdateROMBank; Switch to the bank.
; ; Load the tile and see if it's set or if it's 0xFF. ;
[f31f]LDA (Temp_Addr_L),Y; Load the tile index from the tiles address. [f321]CMP #$ff; Is it 0xFF? [f323]BEQ IScripts_DrawPortraitTileToPPU_IsDone; If so, jump to finish.
; ; Generate an offset into the tiles list, turning each nibble ; of the loaded byte into a 16-bit value. ; ; This is equivalent to: ; ; A (lower) = (byte << 4) & 0xFF ; Temp_05 (upper) = (byte >> 4) & 0xFF ;
[f325]LDA #$00; A = 0 (offset). [f327]STA Temp_05 [f329]LDA (Temp_Addr_L),Y [f32b]ASL A [f32c]ROL Temp_05 [f32e]ASL A [f32f]ROL Temp_05 [f331]ASL A [f332]ROL Temp_05 [f334]ASL A [f335]ROL Temp_05 [f337]CLC
; ; Make the above relative to the address stored in ; TILES_PORTRAITS_START_REF (+ $8000). ;
[f338]ADC a:TILES_PORTRAITS_START_REF; Add the tile ID to the lower byte. [f33b]STA Temp_04; Store in Temp_04. [f33d]LDA Temp_05; Load the calculated upper byte. [f33f]ADC a:TILES_PORTRAITS_START_REF+1; Add the upper byte for the relative start address. [f342]CLC [f343]ADC #$80; Add 0x80. [f345]STA Temp_05; And store as a new upper byte.
; ; Begin writing to the PPU buffer. ;
[f347]LDA #$10; Length = 16 (tile bytes). [f349]JSR PPUBuffer_QueueCommandOrLength; Queue that as the PPU buffer length.
; ; Begin populating the buffer with the tile's bytes. ;
[f34c]LDY #$00; Y = 0 (loop counter). [f34e]@_drawLoop:; [$f34e] [f34e]LDA (Temp_04),Y; Load the next byte for the tile. [f350]STA PPUBuffer,X; Store it in the PPU buffer. [f353]INX; X++ (destination). [f354]INY; Y++ (loop counter). [f355]CPY #$10; Have we hit 16 bytes? [f357]BCC @_drawLoop; If not, loop.
; ; All tiles have been drawn. Update the write offset and ; advance the PPU address. ;
[f359]STX PPUBuffer_WriteOffset; Store the new PPU buffer write offset. [f35b]LDA PPU_TargetAddr; Load the lower byte of the target address. [f35d]CLC [f35e]ADC #$10; Advance by 16 bytes. [f360]STA PPU_TargetAddr; Store it. [f362]LDA PPU_TargetAddr_U; Load the upper byte of the target address. [f364]ADC #$00; Add carry, if lower wrapped. [f366]STA PPU_TargetAddr_U; Store it.
; ; Restore the previous bank. ;
[f368]PLA; Pop the bank from the stack. [f369]TAX; X = A (bank). [f36a]JSR MMC1_UpdateROMBank; Switch to it. [f36d]CLC; Set C=0 for the result. [f36e]RTS
;============================================================================ ; Set the ID of the sound effect to play. ; ; This assigns the sound effect to play during the next ; hardware interrupt. ; ; In theory, it also works off of a priority table to ; determine which sound effect should be playing, but ; in practice this was either disabled or never fully ; implemented. See the notes in the code. ; ; INPUTS: ; A: ; The ID of the sound effect to set. ; ; SoundEffect_Unused_PriorityID: ; The currently-played sound effect. ; ; SOUND_PRIORITIES: ; The table of sound effect priorities. ; ; OUTPUTS: ; SoundEffect_Unused_PriorityID: ; The new value for the current sound ID, if set. ; ; SoundEffect_Current: ; The new value for the next sound effect, if set. ; ; XREFS: ; Sound_PlayEffect ;============================================================================
[f36f]SoundEffect_SetCurrent:; [$f36f] [f36f]CMP #$1d; Length of sound IDs array [f371]BCS @_return; If out of bounds, return.
; ; DEADCODE: Check the priority level of this sound effect. ; ; This code is run, but nothing really happens. Let's explore ; what it's doing: ; ; 1. It's taking SoundEffect_Unused_PriorityID and ; comparing it to the ID of the sound effect to play. ; ; 2. If the new sound effect has a lower value, the variable ; will be set to the new sound effect ID. ; ; 3. It then returns. ; ; This seems to work like a priority level. I'm guessing it's ; to choose a higher-priority ID. ; ; HOWEVER, this ID never gets used anywhere else, and it's only ; ever set to 0. That means any time this is called, the current ; sound effect ID's value in the table is compared against the ; first (ID=0) entry, which is 0, and therefore the sound effect's ; value will never be less (nothing in there has bit 7 set). ; ; This makes all this code a bit worthless, and a candidate for ; removal. ;
[f373]TAX; X = new sound ID [f374]LDY a:SoundEffect_Unused_PriorityID; Y = current sound ID [f377]LDA SOUND_PRIORITIES,Y; A = Sound type for current sound from map [f37a]CMP SOUND_PRIORITIES,X; Is this already the current sound? [f37d]BEQ @_queueNext; If yes, then jump [f37f]BCC @_queueNext; If it's a lower priority (larger number), then jump. [f381]STX a:SoundEffect_Unused_PriorityID; Set as the current sound. [f384]RTS
; ; Set the sound effect to play at the next hardware interrupt. ;
[f385]@_queueNext:; [$f385] [f385]STX SoundEffect_Current; Queue this up as the next effect. [f387]@_return:; [$f387] [f387]RTS
;============================================================================ ; DEADCODE: Table of sound effect priorities, indexed by sound ID. ; ; While code does reference this, no ID but 0 will ever be ; used. See the note in SoundEffect_SetCurrent. ; ; XREFS: ; SoundEffect_SetCurrent ;============================================================================
; ; XREFS: ; SoundEffect_SetCurrent ;
[f388]SOUND_PRIORITIES:; [$f388] [f388].byte $00; [0]: [f389].byte $08; [1]: [f38a].byte $15; [2]: Hit enemy with weapon [f38b].byte $14; [3]: Enemy is dead [f38c].byte $04; [4]: Player hit [f38d].byte $19; [5]: Magic [f38e].byte $0b; [6]: Open door [f38f].byte $0d; [7]: [f390].byte $10; [8]: Item picked up [f391].byte $11; [9]: Touched coin [f392].byte $17; [10]: Cast magic hit an obstacle [f393].byte $09; [11]: Cursor moved [f394].byte $13; [12]: Text input [f395].byte $0a; [13]: [f396].byte $07; [14]: Password character entered [f397].byte $0c; [15]: Block pushed [f398].byte $12; [16]: Coin dropped [f399].byte $0f; [17]: [f39a].byte $0e; [18]: [f39b].byte $06; [19]: Filling HP or MP [f39c].byte $18; [20]: Tilte magic cast [f39d].byte $04; [21]: Player taking step [f39e].byte $01; [22]: [f39f].byte $02; [23]: [f3a0].byte $03; [24]: [f3a1].byte $05; [25]: Gold changed [f3a2].byte $03; [26]: Item used [f3a3].byte $10; [27]: Touched meat [f3a4].byte $02; [28]:
;============================================================================ ; Load glyph data for all strings into the PPU tile map. ; ; Glyph data is located in Bank 13 at $8000. ; ; INPUTS: ; CurrentROMBank: ; The current ROM bank. ; ; OUTPUTS: ; PPUADDR: ; Updated with the write position. ; ; Temp_Int24: ; Clobbered. ; ; CALLS: ; MMC1_UpdateROMBank ; PPU_WriteGlyphTile ; ; XREFS: ; PasswordScreen_Show ;============================================================================
[f3a5]PPU_LoadGlyphsForStrings:; [$f3a5] [f3a5]LDA a:CurrentROMBank [f3a8]PHA [f3a9]LDX #$0d [f3ab]JSR MMC1_UpdateROMBank [f3ae]LDA #$00 [f3b0]STA Temp_Int24 [f3b2]LDA #$80 [f3b4]STA Temp_Int24_M [f3b6]LDA #$12 [f3b8]STA a:PPUADDR [f3bb]LDA #$00 [f3bd]STA a:PPUADDR [f3c0]LDX #$60 [f3c2]@LAB_PRG15_MIRROR__f3c2:; [$f3c2] [f3c2]JSR PPU_WriteGlyphTile [f3c5]JSR PPU_WriteGlyphTile [f3c8]LDA Temp_Int24 [f3ca]CLC [f3cb]ADC #$08 [f3cd]BCC @LAB_PRG15_MIRROR__f3d1 [f3cf]INC Temp_Int24_M [f3d1]@LAB_PRG15_MIRROR__f3d1:; [$f3d1] [f3d1]STA Temp_Int24 [f3d3]DEX [f3d4]BNE @LAB_PRG15_MIRROR__f3c2
;============================================================================ ; Update the ROM bank in register A pushed to the stack. ; ; INPUTS: ; Stack (A): ; The stack to pop A from, containing the bank. ; ; OUTPUTS: ; Stack (A): ; The stack will be popped. ; ; CALLS: ; MMC1_UpdateROMBank ; ; XREFS: ; Game_DrawScreenInFrozenState ; IScripts_LoadPortraitTilesAddress ; Screen_LoadSpriteInfo ; Sprites_LoadSpriteValue ; TextBox_LoadAndShowMessage ; TextBox_LoadItemSourceTiles ; TextBox_ShowNextChar ; TextBox_WriteChar ; UI_DrawSelectedItem ;============================================================================
[f3d6]MMC1_UpdatePRGBankToStackA:; [$f3d6] [f3d6]PLA; Pop the bank from the stack. [f3d7]TAX; X = A [f3d8]JSR MMC1_UpdateROMBank; Switch to the bank. [f3db]RTS
;============================================================================ ; Write a glyph to the list of tiles in the PPU. ; ; This is used by PPU_LoadGlyphsForStrings to ; write a glyph tile for later use. ; ; INPUTS: ; Temp_Int24: ; The temporary address to read bytes from. ; ; OUTPUTS: ; PPUDATA: ; Data will be written to the PPU. ; ; XREFS: ; PPU_LoadGlyphsForStrings ;============================================================================
[f3dc]PPU_WriteGlyphTile:; [$f3dc] [f3dc]LDY #$00; Y = 0 [f3de]@_loop:; [$f3de] [f3de]LDA (Temp_Int24),Y; Load the byte from the temp string address. [f3e0]STA a:PPUDATA; Write to the PPU. [f3e3]INY; Y++ [f3e4]CPY #$08; Have we written 8 bytes? [f3e6]BNE @_loop; If not, loop. [f3e8]RTS
;============================================================================ ; Load the specified message and progressively show it. ; ; Each character in the message will be shown one-by-one. ; ; INPUTS: ; A: ; The message ID to show. ; ; OUTPUTS: ; None ; ; CALLS: ; Messages_Load ; TextBox_LoadAndShowMessage ; ; XREFS: ; IScriptAction_ShowBuySellMenu ;============================================================================
[f3e9]IScripts_LoadAndShowMessage:; [$f3e9] [f3e9]JSR Messages_Load; Load the message. [f3ec]@_loop:; [$f3ec] [f3ec]JSR TextBox_LoadAndShowMessage; Show the next character from the message. [f3ef]LDA a:MessageID; Is it 0xFF (end of message)? [f3f2]BNE @_loop; If not, loop. [f3f4]RTS
;============================================================================ ; Load a message from the message list. ; ; This will switch to Bank 13 and load a message from ; there at the specified message index. ; ; This works by scanning strings. It starts at the first ; message ($8300) and walks up to the messag index (or, ; technically, down from the message index - 1 to 0), ; scanning every character until the message terminator ; (0xFF) is hit. ; ; When we both hit the last message and hit the last ; terminator, we're done. We'll have the address of ; the message and the length. ; ; After loading, this will restore the bank and then ; fall through to TextBox_ClearPasswordSize. ; ; INPUTS: ; A: ; The 1-based index of the message to load. ; ; MESSAGE_STRINGS: ; The list of messages to scan. ; ; OUTPUTS: ; Message_ProcessedLength: ; The length of the message string. ; ; TextBox_Timer: ; The starting message character position (0). ; ; Message_StartAddr+1: ; Message_StartAddr: ; The address of the start of the message string. ; ; PPU_TargetAddr+1: ; PPU_TargetAddr: ; Set to address $1400. ; ; TextBox_MessagePaused: ; TextBox_CharPosForLine: ; TextBox_DrawnLineNum: ; TextBox_LineScrollOffset: ; Textbox_TitleCharOffset: ; All set to 0. ; ; Unused_Arg_Text_NumLines: ; Set to 4. ; ; XREFS: ; IScriptAction_OpenShop ; IScriptAction_ShowMessage ; IScriptAction_ShowQuestionMessage ; IScriptAction_ShowQuestionMessageCheckIfDismissed ; IScriptAction_ShowSellMenu ; IScriptAction_ShowUnskippableMessage ; IScripts_ShowFinalMessage ; IScripts_LoadAndShowMessage ;============================================================================
[f3f5]Messages_Load:; [$f3f5] [f3f5]STA a:MessageID
; ; Save our current bank. ;
[f3f8]LDA a:CurrentROMBank; A = Our current bank [f3fb]PHA; Push A to the stack.
; ; Switch to Bank 13 for the strings. ;
[f3fc]LDX #$0d; Update to Bank 13. [f3fe]JSR MMC1_UpdateROMBank
; ; Set the start of the messages string (MESSAGE_STRINGS). ;
[f401]LDX #$00; Set the lower byte of the address for the strings. [f403]STX Temp_Int24 [f405]LDX #$83; Set the upper byte of the address for the strings. [f407]STX Temp_Int24_M
; ; Set the start of our message length for the ; string scan. ;
[f409]LDY #$00; Y = 0 (start of our lookup offset) [f40b]STY a:TextBox_Timer; Set start character pos to 0
; ; Prepare to loop through the message characters, starting ; at Message ID - 1 and going through 0. ; ; This will effectively from message ID 0 to the end of the ; provided message ID. We'll be doing a full string scan of ; every string along the way (there's no lookup table). ;
[f40e]LDX a:MessageID; X = MessageID [f411]DEX; X-- [f412]BEQ @_afterLoops; If 0, don't do the message string index loop.
; ; Loop through characters in the current message string. ; ; We'll loop until we hit the end of a string. ;
[f414]@_messageLoop:; [$f414] [f414]LDA (Temp_Int24),Y; A = Character at Y from the current string. [f416]INY; Y++
; ; If Y == 0, increment our upper byte position. This ; will happen when Y wraps around from 0xFF to 0. ;
[f417]BNE @_checkChar; If Y != 0, jump. [f419]INC Temp_Int24_M; Y is 0. Increment upper byte position.
; ; Check if we hit a string terminator (0xFF). If so, ; decrement the local message ID index. ;
[f41b]@_checkChar:; [$f41b] [f41b]CMP #$ff; Is A (the character) 0xFF? [f41d]BNE @_messageLoop; If not 0xFF, loop. [f41f]DEX; A is 0xFF. X-- to the previous message ID. [f420]BNE @_messageLoop; If X != 0, loop.
; ; We found the start position of our string (or were there ; already). We're done. Begin storing state. ;
[f422]@_afterLoops:; [$f422] [f422]STY a:Message_ProcessedLength; Message length = Y [f425]LDA Temp_Int24; Load the new lower byte of the string position. [f427]STA a:Message_StartAddr; Store it. [f42a]LDA Temp_Int24_M; Load the new upper byte of the string position. [f42c]STA a:Message_StartAddr_U; Store it.
; ; Restore our previous bank. ;
[f42f]PLA; Pop A (our previous bank) off the stack. [f430]TAX; X = A [f431]JSR MMC1_UpdateROMBank; Switch bank to the bank.
; ; v-- Fall through --v ;
;============================================================================ ; Clear the size of the password text box. ; ; INPUTS: ; None. ; ; OUTPUTS: ; PPU_TargetAddr+1: ; PPU_TargetAddr: ; Set to address $1400. ; ; TextBox_MessagePaused: ; TextBox_CharPosForLine: ; TextBox_DrawnLineNum: ; TextBox_LineScrollOffset: ; Textbox_TitleCharOffset: ; All set to 0. ; ; Unused_Arg_Text_NumLines: ; Set to 4. ; ; CALLS: ; PPU_IncrementAddrBy32 ; PPUBuffer_WriteValueMany ; ; XREFS: ; IScriptAction_ShowPassword ;============================================================================
[f434]TextBox_ClearPasswordSize:; [$f434] [f434]LDA #$00 [f436]STA a:TextBox_LineScrollOffset; Set line scroll offset to 0. [f439]STA a:TextBox_CharPosForLine; Set character position in line to 0. [f43c]STA a:TextBox_DrawnLineNum; Set the drawn line number to 0. [f43f]STA a:TextBox_MessagePaused; Clear the message paused state. [f442]STA a:Textbox_TitleCharOffset; Set the title character offset to 0. [f445]LDA #$04 [f447]STA a:Unused_Arg_Text_NumLines; Set the textbo height to 4.
; ; v-- Fall through --v ;
;============================================================================ ; Clear the text tiles from the textbox. ; ; This will loop through the text tiles and clear them out. ; Tiles $1400 through $1800 will be cleared. ; ; INPUTS: ; None. ; ; OUTPUTS: ; PPU_TargetAddr: ; The PPU target address ($1400). ; ; CALLS: ; PPU_IncrementAddrBy32 ; PPUBuffer_WriteValueMany ; ; XREFS: ; IScripts_FillPlaceholderText ;============================================================================
[f44a]TextBox_ClearTextTiles:; [$f44a]
; ; Set the PPU target address to $1400. These are the ; text tiles. ;
[f44a]LDA #$14; 0x14 == upper byte of the PPU address. [f44c]STA PPU_TargetAddr_U; Store it. [f44e]LDA #$00; 0x00 == lower byte. [f450]STA PPU_TargetAddr; Store it. [f452]LDY #$20; Y = 32 (loop counter).
; ; Write the value 0x00 for bytes $1400 through $1800. ;
[f454]@_writeLoop:; [$f454] [f454]TYA; A = Y (loop counter) [f455]PHA; Push to the stack. [f456]LDA #$00; A = 0 [f458]LDY #$20; Y = 32 [f45a]JSR PPUBuffer_WriteValueMany; Write 0x00 32 times to the PPU buffer. [f45d]JSR PPU_IncrementAddrBy32; Address += 32 [f460]PLA; Pull A from the stack. [f461]TAY; Y = A (loop counter) [f462]DEY; Y-- [f463]BNE @_writeLoop; If > 0, loop.
; ; XREFS: ; Maybe_TextBox_ShowCurrentMessageID ;
[f465]TextBox_ClearShopSizeAtOffset_return:; [$f465] [f465]RTS
;============================================================================ ; Show the next character in a textbox. ; ; This will temporarily switch to bank 13 (strings), ; increment the position to show, and show it. ; ; INPUTS: ; CurrentROMBank: ; The current ROM bank. ; ; TextBox_Timer: ; The current message character position. ; ; OUTPUTS: ; TextBox_Timer: ; The incremented position. ; ; TextBox_PlayTextSound: ; Set to 1. ; ; CALLS: ; MMC1_UpdateROMBank ; MMC1_UpdatePRGBankToStackA ; Maybe_TextBox_ShowCurrentMessageID ; ; XREFS: ; IScriptAction_OpenShop ; IScriptAction_ShowMessage ; IScriptAction_ShowQuestionMessage ; IScriptAction_ShowQuestionMessageCheckIfDismissed ; IScriptAction_ShowSellMenu ; IScriptAction_ShowUnskippableMessage ; IScripts_ShowFinalMessage ;============================================================================
[f466]TextBox_ShowNextChar:; [$f466]
; ; Save the current bank and switch to bank 13 (strings). ;
[f466]LDA a:CurrentROMBank; A = current bank [f469]PHA; Push it to the stack. [f46a]LDX #$0d; X = 13 (bank) [f46c]JSR MMC1_UpdateROMBank; Switch to bank 13.
; ; Show the next character. ;
[f46f]INC a:TextBox_Timer; Increment the next character position. [f472]LDA #$01; A = 1 [f474]STA a:TextBox_PlayTextSound; Store it. [f477]JSR Maybe_TextBox_ShowCurrentMessageID; Show this character in the message.
; ; Restore the bank and return. ;
[f47a]JMP MMC1_UpdatePRGBankToStackA; Restore the pushed bank.
;============================================================================ ; TODO: Document TextBox_LoadAndShowMessage ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; IScripts_LoadAndShowMessage ;============================================================================
[f47d]TextBox_LoadAndShowMessage:; [$f47d]
; ; Save the current ROM. ;
[f47d]LDA a:CurrentROMBank [f480]PHA
; ; Switch to Bank 13, where the strings are. ;
[f481]LDX #$0d [f483]JSR MMC1_UpdateROMBank [f486]LDA #$00 [f488]STA a:TextBox_PlayTextSound [f48b]JSR TextBox_DisplayMessage
; ; Restore the previous bank. ;
[f48e]JMP MMC1_UpdatePRGBankToStackA
;============================================================================ ; TODO: Document Maybe_TextBox_ShowCurrentMessageID ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; TextBox_ShowNextChar ;============================================================================
[f491]Maybe_TextBox_ShowCurrentMessageID:; [$f491] [f491]LDA a:MessageID; Load the message ID to show. [f494]BEQ TextBox_ClearShopSizeAtOffset_return; If unset, return. [f496]LDA a:TextBox_MessagePaused [f499]BNE TextBox_ClearShopSizeAtOffset_return [f49b]LDA a:TextBox_Timer; Load the character position to display. [f49e]AND #$03; Check the first 2 bits. [f4a0]BNE TextBox_ClearShopSizeAtOffset_return; If neither bit is set, return.
; ; v-- Fall through --v ;
;============================================================================ ; TODO: Document TextBox_DisplayMessage ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; TextBox_LoadAndShowMessage ;============================================================================
[f4a2]TextBox_DisplayMessage:; [$f4a2] [f4a2]LDA a:Textbox_TitleCharOffset [f4a5]BEQ TextBox_ShowMessage_LoadMessage [f4a7]CMP #$90 [f4a9]BEQ @LAB_PRG15_MIRROR__f4c3 [f4ab]AND #$0f [f4ad]STA Temp_Int24
; ; Convert the player's title to an index in the string list. ;
[f4af]LDA a:PlayerTitle; Load the player's title. [f4b2]ASL A; Shift left 4, converting to an index. [f4b3]ASL A [f4b4]ASL A [f4b5]ASL A [f4b6]ORA Temp_Int24 [f4b8]TAX; X = A [f4b9]LDA PLAYER_TITLE_STRINGS,X; Look up the string. [f4bc]INC a:Textbox_TitleCharOffset [f4bf]CMP #$20 [f4c1]BNE TextBox_ShowMessageWithSound [f4c3]@LAB_PRG15_MIRROR__f4c3:; [$f4c3] [f4c3]LDA #$00 [f4c5]STA a:Textbox_TitleCharOffset
; ; v-- Fall through --v ;
;============================================================================ ; TODO: Document TextBox_ShowMessage_LoadMessage ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; TextBox_DisplayMessage ; TextBox_ShowMessage_Newline ;============================================================================
[f4c8]TextBox_ShowMessage_LoadMessage:; [$f4c8] [f4c8]LDA a:Message_StartAddr [f4cb]STA Temp_Int24 [f4cd]LDA a:Message_StartAddr_U [f4d0]STA Temp_Int24_M [f4d2]LDY a:Message_ProcessedLength [f4d5]INC a:Message_ProcessedLength [f4d8]BNE @LAB_PRG15_MIRROR__f4dd [f4da]INC a:Message_StartAddr_U [f4dd]@LAB_PRG15_MIRROR__f4dd:; [$f4dd] [f4dd]LDA (Temp_Int24),Y [f4df]CMP #$ff [f4e1]BEQ TextBox_ShowMessage_EndOfMessage [f4e3]CMP #$fe [f4e5]BEQ TextBox_ShowMessage_Newline [f4e7]CMP #$fd [f4e9]BEQ TextBox_ShowMessage_Space [f4eb]CMP #$fb [f4ed]BEQ TextBox_ShowMessage_ShowPlayerTitle [f4ef]CMP #$fc [f4f1]BEQ TextBox_ShowMessage_Pause
; ; v-- Fall through --v ;
;============================================================================ ; TODO: Document TextBox_ShowMessageWithSound ; ; INPUTS: ; A ; ; OUTPUTS: ; TODO ; ; XREFS: ; TextBox_DisplayMessage ; TextBox_ShowMessage_ShowPlayerTitle ;============================================================================
[f4f3]TextBox_ShowMessageWithSound:; [$f4f3] [f4f3]LDX a:TextBox_PlayTextSound [f4f6]BEQ TextBox_ShowMessage
; ; Play the message sound effect. ;
[f4f8]PHA; Push A to the stack. [f4f9]LDA #$01; 0x01 == Message sound effect. [f4fb]JSR Sound_PlayEffect; Play it. [f4fe]PLA; Pop A.
; ; v-- Fall through --v ;
;============================================================================ ; TODO: Document TextBox_ShowMessage ; ; INPUTS: ; A ; ; OUTPUTS: ; TODO ; ; XREFS: ; PasswordScreen_ShowNextChar ; TextBox_ShowMessageWithSound ;============================================================================
[f4ff]TextBox_ShowMessage:; [$f4ff] [f4ff]JSR TextBox_WriteChar
; ; v-- Fall through --v ;
;============================================================================ ; TODO: Document TextBox_ShowMessage_Space ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; TextBox_ShowMessage_LoadMessage ;============================================================================
[f502]TextBox_ShowMessage_Space:; [$f502]
; ; If it's 0xFD, it's a space. ;
[f502]INC a:TextBox_CharPosForLine [f505]LDA a:TextBox_CharPosForLine [f508]CMP #$10 [f50a]BCC TextBox_ShowMessage_Return
; ; Load the next character in the message string. ;
[f50c]LDA a:Message_StartAddr [f50f]STA Temp_Int24 [f511]LDA a:Message_StartAddr_U [f514]STA Temp_Int24_M [f516]LDY a:Message_ProcessedLength [f519]LDA (Temp_Int24),Y
; ; If it's 0xFF, the message ends. ;
[f51b]CMP #$ff [f51d]BEQ TextBox_ShowMessage_EndOfMessage
; ; If it's 0xFC, finish this part of the message. ;
[f51f]CMP #$fc [f521]BEQ TextBox_ShowMessage_Return
; ; v-- Fall through --v ;
;============================================================================ ; TODO: Document TextBox_ShowMessage_Newline ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; TextBox_ShowMessage_LoadMessage ;============================================================================
[f523]TextBox_ShowMessage_Newline:; [$f523]
; ; If it's 0xFE, it's a newline. ;
[f523]LDA a:TextBox_CharPosForLine [f526]BEQ TextBox_ShowMessage_LoadMessage
; ; v-- Fall through --v ;
;============================================================================ ; TODO: Document TextBox_ShowMessage_IncLineAndReset ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; TextBox_ShowMessage_Prepare4Lines ;============================================================================
[f528]TextBox_ShowMessage_IncLineAndReset:; [$f528]
; ; Increment the line and reset the character position. ;
[f528]LDA #$00; A = 0 [f52a]STA a:TextBox_CharPosForLine; Set as the character position. [f52d]LDY a:TextBox_DrawnLineNum; Y = line number [f530]INY; Y++
; ; Check if we've reached the end of the textbox. ; If so, prepare the state for the next 4 lines. ;
[f531]CPY #$04; Is the line number 4? [f533]BEQ TextBox_ShowMessage_Fill4Lines; If so, jump.
; ; We're not at the end of the textbox. Increment and return. ;
[f535]STY a:TextBox_DrawnLineNum; Els, set as the number of drawn lines. [f538]RTS
;============================================================================ ; Prepare a 4-line textbox for writing. ; ; This will set the initial draw position and data for ; a textbox covering 4 lines. It will then fill it with ; placeholder data and then clear it. ; ; This ensures state is set up to write text, and that ; the buffer is prepared. ; ; INPUTS: ; TextBox_CharPosForLine: ; The 1-based starting character position. ; ; If 0, this will just return. ; ; TextBox_DrawnLineNum: ; The last drawn line number. ; ; TextBox_LineScrollOffset: ; The line scroll offset of the tex tbox. ; ; TextBox_X: ; The X position of the text box. ; ; TextBox_Y: ; The Y position of the text box. ; ; OUTPUTS: ; TextBox_ContentsX: ; The X position to begin writing to. ; ; TextBox_ContentsY: ; The Y position to begin writing to. ; ; TextBox_LineScrollOffset: ; The scroll offset of the line. ; ; TextBox_DrawnLineNum: ; The drawn line number. ; ; CALLS: ; PPU_IncrementAddrBy16 ; PPU_SetAddrForTextBoxPos ; PPUBuffer_WriteValueMany ; TextBox_FillPlaceholderTextForLineCapped ; ; XREFS: ; TextBox_PrepareContinueMessage ;============================================================================
[f539]TextBox_ShowMessage_Prepare4Lines:; [$f539]
; ; Check if anything's ready to be written for this line. ;
[f539]LDA a:TextBox_CharPosForLine; Load the character position (1-based; 0 for not ready). [f53c]BNE TextBox_ShowMessage_IncLineAndReset; If > 0, the line can be drawn. [f53e]RTS
;============================================================================ ; Handle a pause in the message the player must acknowledge. ; ; This is message code 0xFC. ; ; If the very next code is 0xFF, this will simply end the ; message. ; ; INPUTS: ; Message_ProcessedLength: ; The processed number of bytes of the message. ; ; Message_StartAddr: ; Message_StartAddr+1: ; The address of the loaded message string. ; ; OUTPUTS: ; MessageID: ; Set to 0 if the message ends. ; ; TextBox_MessagePaused: ; Set to 0xFF if paused. ; ; Temp_Int24: ; Temp_Int24+1: ; Clobbered. ; ; XREFS: ; TextBox_ShowMessage_LoadMessage ;============================================================================
[f53f]TextBox_ShowMessage_Pause:; [$f53f] [f53f]LDA a:Message_StartAddr; Load the lower byte of the message string address. [f542]STA Temp_Int24; Store it for length calculation. [f544]LDA a:Message_StartAddr_U; Load the upper byte. [f547]STA Temp_Int24_M; Store it.
; ; Check if the next byte is 0xFF, in which case we just want ; to end the message immediately. ;
[f549]LDY a:Message_ProcessedLength; Load the message length. [f54c]LDA (Temp_Int24),Y; Load the next byte of the message. [f54e]CMP #$ff; Is it 0xFF (end of message)? [f550]BEQ TextBox_ShowMessage_EndOfMessage; If true, then end message.
; ; The message isn't ending. We're paused instead. ;
[f552]LDA #$ff [f554]STA a:TextBox_MessagePaused; Set the message as paused.
; ; XREFS: ; TextBox_ShowMessage_Space ;
[f557]TextBox_ShowMessage_Return:; [$f557] [f557]RTS
; ; This is the end of the message. ; ; ; XREFS: ; TextBox_ShowMessage_LoadMessage ; TextBox_ShowMessage_Pause ; TextBox_ShowMessage_Space ;
[f558]TextBox_ShowMessage_EndOfMessage:; [$f558] [f558]LDA #$00; A = 0 [f55a]STA a:MessageID; Store as the message ID (terminating message). [f55d]RTS
;============================================================================ ; Show the player's current title in the textbox. ; ; This is message code 0xFB. ; ; INPUTS: ; PlayerTitle: ; The player's current title. ; ; PLAYER_TITLE_STRINGS: ; The table of title strings. ; ; OUTPUTS: ; Textbox_TitleCharOffset: ; The start offset/counter for title writing. ; ; CALLS: ; TextBox_ShowMessageWithSound ; ; XREFS: ; TextBox_ShowMessage_LoadMessage ;============================================================================
[f55e]TextBox_ShowMessage_ShowPlayerTitle:; [$f55e]
; ; If it's 0xFB, the title rank will be inserted. ;
[f55e]LDA #$81; A = 0x81 [f560]STA a:Textbox_TitleCharOffset; Store it. [f563]LDA a:PlayerTitle; Load the player's title. [f566]ASL A; Convert to an index in the Player Titles table. [f567]ASL A [f568]ASL A [f569]ASL A [f56a]TAX; X = A [f56b]LDA PLAYER_TITLE_STRINGS,X; Load the title string at X. [f56e]JMP TextBox_ShowMessageWithSound; Show this title in the textbox.
;============================================================================ ; TODO: Document TextBox_ShowMessage_Fill4Lines ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; TextBox_ShowMessage_IncLineAndReset ;============================================================================
[f571]TextBox_ShowMessage_Fill4Lines:; [$f571]
; ; Set the start position where text will be drawn. ;
[f571]LDA a:TextBox_X; X = textbox tile X coordinate. [f574]CLC [f575]ADC #$02; X += 2 [f577]STA TextBox_ContentsX; Store as the text X start position. [f579]LDA a:TextBox_Y; Y = textbox tile Y coordinate. [f57c]CLC [f57d]ADC #$02; Y += 2 [f57f]STA TextBox_ContentsY; Store as the text Y start position.
; ; Write 3 lines. ; ; This will be the available text characters, filling ; up the buffer. It will contain: ; ; @ABCDEFGHIJKLMNO ; PQRSTUVWXYZ[\]^_ ; `abcdefghijklmno ; pqrstuvwxyz{}|~. ; ; This will effectively reserve space in the buffer. ;
[f581]JSR PPU_SetAddrForTextBoxPos; Set the PPU address for the text position. [f584]LDX a:TextBox_LineScrollOffset; X = scroll line offset [f587]JSR TextBox_FillPlaceholderTextForLineCapped; Write a line and advance. [f58a]JSR TextBox_FillPlaceholderTextForLineCapped; Write a line and advance. [f58d]JSR TextBox_FillPlaceholderTextForLineCapped; Write a line and advance.
; ; Clear the final line. ;
[f590]LDA #$00; A = 0 (start offset) [f592]LDY #$10; Y = 16 (line length) [f594]JSR PPUBuffer_WriteValueMany; Write '0' 16 times, clearing the line.
; ; Set the starting text position to the beginning text char/pos ; of the text box we just wrote to. ;
[f597]LDA #$00; A = 0 [f599]STA PPU_TargetAddr; Store A as the lower byte of the PPU target address. [f59b]LDA a:TextBox_LineScrollOffset; A = scroll-aware line offset. [f59e]CLC [f59f]ADC #$14; A += 20 [f5a1]STA PPU_TargetAddr_U; Store as the upper byte of the PPU target addres.
; ; Clear out each of the lines that were just written. ;
[f5a3]LDX #$10; X = 16 [f5a5]@LAB_PRG15_MIRROR__f5a5:; [$f5a5] [f5a5]TXA; A = X [f5a6]PHA; Push A to the stack. [f5a7]LDA #$00; A = 0 [f5a9]LDY #$10; Y = 16 [f5ab]JSR PPUBuffer_WriteValueMany; Write '0' 16 times, clearing the line. [f5ae]JSR PPU_IncrementAddrBy16; Increment the offset by 16. [f5b1]PLA; Pop A from the stack (now 16). [f5b2]TAX; X = A [f5b3]DEX; X-- [f5b4]BNE @LAB_PRG15_MIRROR__f5a5; If X > 0, loop.
; ; Increase the Y position to draw to by 3, after these lines. ;
[f5b6]INC TextBox_ContentsY; TextBox_ContentsY += 3 [f5b8]INC TextBox_ContentsY [f5ba]INC TextBox_ContentsY
; ; Draw a line of text to fill the address. ; ; This will be: ; ; PQRSTUVWXYZ[\]^_ ;
[f5bc]JSR PPU_SetAddrForTextBoxPos; Set the text draw position back. [f5bf]LDA a:TextBox_LineScrollOffset; A = line scroll offset. [f5c2]JSR TextBox_FillPlaceholderTextForLine; Write a line of text.
; ; Store the new line number offset. ;
[f5c5]INX; X++ [f5c6]TXA; A = X [f5c7]AND #$03; Cap the line number from 0..4. [f5c9]STA a:TextBox_LineScrollOffset; Store it. [f5cc]RTS
;============================================================================ ; Write a line of arbitrary text, taking into account textbox scrolling. ; ; The text will be placeholder text, intended to be ; cleared later. ; ; This is used when a maximum of 4 lines will be shown. ; The provided line number will be capped to a value ; between 0 and 4 and written to the textbox draw address. ; ; INPUTS: ; X: ; The line number to write to. ; ; This will be capped to a displayed line number. ; ; OUTPUTS: ; PPUBuffer_WriteOffset: ; The new upper bounds of the PPU buffer. ; ; CALLS: ; PPUBuffer_QueueCommandOrLength ; PPUBuffer_Set ; PPU_IncrementAddrBy32 ; ; XREFS: ; TextBox_ShowMessage_Fill4Lines ;============================================================================
[f5cd]TextBox_FillPlaceholderTextForLineCapped:; [$f5cd] [f5cd]INX; lineNum++ [f5ce]TXA; A = X
; ; Cap the linenum to lines 0-4. ;
[f5cf]AND #$03
; ; v-- Fall through --v ;
;============================================================================ ; Write a line of arbitrary text. ; ; The text will be placeholder text, intended to be ; cleared later. ; ; INPUTS: ; A: ; The line number to write to. ; ; This will be a 0-based index into the textbox. ; ; OUTPUTS: ; PPUBuffer_WriteOffset: ; The new upper bounds of the PPU buffer. ; ; CALLS: ; PPUBuffer_QueueCommandOrLength ; PPUBuffer_Set ; PPU_IncrementAddrBy32 ; ; XREFS: ; TextBox_ShowMessage_Fill4Lines ;============================================================================
[f5d1]TextBox_FillPlaceholderTextForLine:; [$f5d1] [f5d1]TAX; X = A
; ; Calculate a character offset for the line number. ; Lines are 16 characters per line. ;
[f5d2]ASL A; Multiply by 16. [f5d3]ASL A [f5d4]ASL A [f5d5]ASL A
; ; Add 64, giving us a value of "A". ;
[f5d6]ADC #$40; Add 64 (character code of "A"). [f5d8]TAY; Y = A
; ; v-- Fall through --v ;
;============================================================================ ; Write a line of arbitrary text, starting with a character. ; ; The text will be placeholder text, intended to be ; cleared later. ; ; INPUTS: ; A: ; The line number to write to. ; ; This will be a 0-based index into the textbox. ; ; OUTPUTS: ; PPUBuffer_WriteOffset: ; The new upper bounds of the PPU buffer. ; ; CALLS: ; PPUBuffer_QueueCommandOrLength ; PPUBuffer_Set ; PPU_IncrementAddrBy32 ; ; XREFS: ; IScripts_FillPlaceholderText ;============================================================================
[f5d9]TextBox_FillPlaceholderTextAtLineWithStartChar:; [$f5d9] [f5d9]TXA; A = X (offset) [f5da]PHA; Push A to the stack.
; ; Set the write length to 16 characters (max for a line). ;
[f5db]LDA #$10; A = 16 [f5dd]JSR PPUBuffer_QueueCommandOrLength; Queue this as the write length. [f5e0]TYA; A = Y
; ; Begin the write loop. ;
[f5e1]LDY #$10; Y = 16 [f5e3]@_loop:; [$f5e3] [f5e3]JSR PPUBuffer_Set; Write the value to the buffer. [f5e6]CLC [f5e7]ADC #$01; A++ (next character value) [f5e9]DEY; Y-- [f5ea]BNE @_loop; If Y > 0, loop. [f5ec]STX PPUBuffer_WriteOffset; Store the new offset as the PPU upper bounds. [f5ee]PLA; Pop A from the stack. [f5ef]TAX; X = A [f5f0]JMP PPU_IncrementAddrBy32; Increment the PPU address by 32.
;============================================================================ ; Write an ASCII value to the textbox at the next position. ; ; INPUTS: ; A: ; The ASCII value to write. ; ; OUTPUTS: ; PPUBuffer_WriteOffset: ; The new upper bounds of the PPU buffer. ; ; PPU_TargetAddr: ; PPU_TargetAddr+1: ; The PPU target address to write to. ; ; Temp_Int24: ; Temp_Int24+1: ; Clobbered. ; ; CALLS: ; PPUBuffer_QueueCommandOrLength ; PPUBuffer_Set ; PPU_IncrementAddrBy32 ; TextBox_Write8BytesFromTemp ; ; XREFS: ; TextBox_ShowMessage ;============================================================================
[f5f3]TextBox_WriteChar:; [$f5f3]
; ; Normalize the ASCII value into an index in the characters ; table. ;
[f5f3]SEC; C = 1 [f5f4]SBC #$20; A -= 32 (normalize the value to a character lookup table)
; ; Set the address of the string address. ; ; Each glyph is 8 bytes, so this will normalize the character ; index to the proper byte offset. ;
[f5f6]LDX #$00; X = 0 [f5f8]STX Temp_Int24_M; Set middle byte of string address to 0. [f5fa]ASL A; A = A * 8 (each glyph is 8 bytes) [f5fb]ASL A [f5fc]ROL Temp_Int24_M; Rotate upper byte left and add carry. [f5fe]ASL A [f5ff]ROL Temp_Int24_M [f601]ADC #$00 [f603]STA Temp_Int24
; ; Make the address relative to $8000. ;
[f605]LDA Temp_Int24_M; Load the upper byte of of the address. [f607]ADC #$80; Add 0x80 to it. [f609]STA Temp_Int24_M; Store it back.
; ; Compute the PPU target address. ;
[f60b]LDA #$00; A = 0 [f60d]STA PPU_TargetAddr_U; Store as the starting upper byte of the PPU target address. [f60f]LDA a:TextBox_CharPosForLine; A = character position. [f612]ASL A; Convert to a 16 byte address boundary. [f613]ASL A [f614]ASL A [f615]ASL A [f616]STA PPU_TargetAddr; Store as the lower byte of the PPU target address. [f618]LDA a:TextBox_DrawnLineNum; A = drawn line number. [f61b]CLC [f61c]ADC a:TextBox_LineScrollOffset; A += line scroll offset [f61f]AND #$03; Cap to a 0..4 line number. [f621]ADC #$14; A += 20 [f623]STA PPU_TargetAddr_U; Set as the new upper byte of the PPU target address.
; ; Save the current bank and switch to bank 13 (strings). ;
[f625]LDA a:CurrentROMBank; A = current bank [f628]PHA; Push A to the stack. [f629]LDX #$0d; A = bank 13 [f62b]JSR MMC1_UpdateROMBank; Switch to it.
; ; Queue up the drawn line. ;
[f62e]LDA #$10; A = 16 (line length) [f630]JSR PPUBuffer_QueueCommandOrLength; Queue as the line length. [f633]JSR TextBox_Write8BytesFromTemp; Write the first half of the line. [f636]JSR TextBox_Write8BytesFromTemp; Write the second half.
; ; Finish up and restore the bank. ;
[f639]STX PPUBuffer_WriteOffset; Store the new upper bounds for the PPU buffer. [f63b]JMP MMC1_UpdatePRGBankToStackA; Restore to the previous bank.
;============================================================================ ; Write values from temp to the PPU buffer. ; ; This will write all values from Temp_Int24 ; until a value 128-255 are hit. ; ; INPUTS: ; X: ; The destination index into the buffer to write to. ; ; OUTPUTS: ; None ; ; CALLS: ; PPUBuffer_WriteFromTemp ; ; XREFS: ; TextBox_WriteChar ;============================================================================
[f63e]TextBox_Write8BytesFromTemp:; [$f63e] [f63e]LDY #$00; Y = 0 [f640]@_loop:; [$f640] [f640]JSR PPUBuffer_WriteFromTemp; Write a value from temp. [f643]TYA; A = Y [f644]AND #$07; Consider values 0-127. [f646]BNE @_loop; If not 0, loop. [f648]RTS
; ; XREFS: ; TextBox_DisplayMessage ; TextBox_ShowMessage_ShowPlayerTitle ;
[f649]PLAYER_TITLE_STRINGS:; [$f649] [f649].byte "Novice "; [$f649] char [f651].byte " "; [$f651] char [f659].byte "Aspirant"; [$f659] char [f661].byte " "; [$f661] char [f669].byte "Battler "; [$f669] char [f671].byte " "; [$f671] char [f679].byte "Fighter "; [$f679] char [f681].byte " "; [$f681] char [f689].byte "Adept "; [$f689] char [f691].byte " "; [$f691] char [f699].byte "Chevalie"; [$f699] char [f6a1].byte "r "; [$f6a1] char [f6a9].byte "Veteran "; [$f6a9] char [f6b1].byte " "; [$f6b1] char [f6b9].byte "Warrior "; [$f6b9] char [f6c1].byte " "; [$f6c1] char [f6c9].byte "Swordman"; [$f6c9] char [f6d1].byte " "; [$f6d1] char [f6d9].byte "Hero "; [$f6d9] char [f6e1].byte " "; [$f6e1] char [f6e9].byte "Soldier "; [$f6e9] char [f6f1].byte " "; [$f6f1] char [f6f9].byte "Myrmidon"; [$f6f9] char [f701].byte " "; [$f701] char [f709].byte "Champion"; [$f709] char [f711].byte " "; [$f711] char [f719].byte "Superher"; [$f719] char [f721].byte "o "; [$f721] char [f729].byte "Paladin "; [$f729] char [f731].byte " "; [$f731] char [f739].byte "Lord "; [$f739] char [f741].byte " "; [$f741] char
;============================================================================ ; Experience requirements for each title. ; ; XREFS: ; Player_SetInitialExpAndGold ; Player_CheckReachedNextTitle ;============================================================================
; ; XREFS: ; Player_SetInitialExpAndGold ; Player_CheckReachedNextTitle ;
[f749]PLAYER_TITLE_EXP_NEEDED:; [$f749] [f749].word $03e8; [0]: Aspirant (1,000 exp)
; ; XREFS: ; PlayerMenu_ShowStatusMenu ;
[f74b]PLAYER_TITLE_EXP_NEEDED_1_:; [$f74b] [f74b].word $0898; [1]: Battler (2,200 exp) [f74d].word $0dac; [2]: Fighter (3,500 exp) [f74f].word $12c0; [3]: Adept (4,800 exp) [f751].word $1838; [4]: Chevalier (6,200 exp) [f753].word $1f40; [5]: Veteran (8,000 exp) [f755].word $2710; [6]: Warrior (10,000 exp) [f757].word $30d4; [7]: Swordman (12,500 exp) [f759].word $3a98; [8]: Hero (15,000 exp) [f75b].word $4650; [9]: Soldier (18,000 exp) [f75d].word $55f0; [10]: Myrmidon (22,000 exp) [f75f].word $6590; [11]: Champion (26,000 exp) [f761].word $7530; [12]: Superhero (30,000 exp) [f763].word $88b8; [13]: Paladin (35,000 exp) [f765].word $afc8; [14]: Lord (45,000 exp)
;============================================================================ ; Starting gold at each player title. ; ; XREFS: ; Player_SetInitialExpAndGold ;============================================================================
; ; XREFS: ; Player_SetInitialExpAndGold ;
[f767]PLAYER_TITLE_GOLD:; [$f767] [f767].word $01f4; [0]: [f769].word $0320; [1]: [f76b].word $04b0; [2]: [f76d].word $0640; [3]: [f76f].word $0834; [4]: [f771].word $0af0; [5]: [f773].word $0dac; [6]: [f775].word $10cc; [7]: [f777].word $1450; [8]: [f779].word $1838; [9]: [f77b].word $1d4c; [10]: [f77d].word $2328; [11]: [f77f].word $2904; [12]: [f781].word $32c8; [13]: [f783].word $3a98; [14]:
;============================================================================ ; Return the inventory for the given item. ; ; This takes an item with inventory bits and returns the ; inventory it resides in as a number between 1-5. ; ; INPUTS: ; A: ; The inventory item with inventory bits set. ; ; OUTPUTS: ; A: ; The index of the inventory: ; ; 0 = Weapons ; 1 = Armor ; 2 = Shields ; 3 = Magic ; 4 = Special ; ; XREFS: ; IScriptAction_OpenShop ; Player_AddToInventory ; Player_Equip ; Player_LacksItem ; Player_RemoveItem ; Shop_GetPlayerHasSelectedItem ; TextBox_DrawItemName ; TextBox_LoadItemSourceTiles ;============================================================================
[f785]Player_GetInventoryIndexForItem:; [$f785] [f785]LSR A; Shift right 5, turning inventory bits into an index. [f786]LSR A [f787]LSR A [f788]LSR A [f789]LSR A [f78a]RTS
;============================================================================ ; Convert an inventory index to a bit for an item. ; ; This takes an inventory index and shifts it left 5, ; which turns it into an inventory bitmask that can be ; OR'd with an item ID. ; ; INPUTS: ; A: ; The inventory index. ; ; 0 = Weapons ; 1 = Armor ; 2 = Shields ; 3 = Magic ; 4 = Special ; ; OUTPUTS: ; A: ; The inventory bitmask. ; ; XREFS: ; IScriptAction_ShowSellMenu ; PlayerMenu_DrawInventoryItems ; PlayerMenu_EquipItem ; PlayerMenu_ShowStatusMenu ;============================================================================
[f78b]Player_GetInventoryBitForIndex:; [$f78b] [f78b]ASL A; Shift left 5 (here and the next 4 after falling through).
;============================================================================ ; Multiply a value by 16. ; ; This seems to be primarily used for textbox widths. ; 16 is the max width of text in a box. ; ; INPUTS: ; A: ; The value to multiply. ; ; OUTPUTS: ; A: ; The resulting value. ; ; XREFS: ; Menu_UpdateAndDraw ; PlayerMenu_ShowStatusMenu ; PlayerMenu_ShowSubmenu ; TextBox_DrawItemName ; TextBox_GetBackingAttributeData ;============================================================================
[f78c]Math_MultiplyBy16:; [$f78c] [f78c]ASL A; Shift left 4. [f78d]ASL A [f78e]ASL A [f78f]ASL A [f790]RTS
;============================================================================ ; Return the attribute data for a given position in a textbox. ; ; This will load the data from the current area's block attributes. ; ; INPUTS: ; CurrentArea_BlockAttributesAddr: ; The address of the loaded block attributes data ; for the screen. ; ; CurrentROMBank: ; The current ROM bank. ; ; ScreenBuffer: ; The loaded screen buffer. ; ; TextBox_ContentsX: ; The X position to load. ; ; TextBox_ContentsY: ; The Y position to load. ; ; OUTPUTS: ; A: ; The loaded attribute data. ; ; Temp_Int24: ; Clobbered. ; ; CALLS: ; MMC1_UpdateROMBank ; Math_MultiplyBy16 ; ; XREFS: ; TextBox_GetBackgroundAttributeData ;============================================================================
[f791]TextBox_GetBackingAttributeData:; [$f791]
; ; Preserve X and Y on the stack. ;
[f791]TYA; A = Y [f792]PHA; Push it to the stack. [f793]TXA; A = X [f794]PHA; Push it to the stack.
; ; Preserve the current ROM bank. ;
[f795]LDA a:CurrentROMBank; A = Current ROM bank. [f798]PHA; Push it to the stack.
; ; Switch to bank 3 (area metadata). ;
[f799]LDX #$03; X = 3 (Area metadata bank) [f79b]JSR MMC1_UpdateROMBank; Switch to the bank. [f79e]LDA a:TextBox_ContentsY; A = Tile Y position for the text in the textbox. [f7a1]JSR Math_MultiplyBy16; Convert to an index in the block attributes data. [f7a4]AND #$f0; Keep the upper nibble. [f7a6]STA Temp_Int24; Store for the final result. [f7a8]ORA a:TextBox_ContentsX; OR the index with the tile X position for the text. [f7ab]SEC [f7ac]SBC #$20; Subtract 32.
; ; Load the data attribute from the screen buffer at this index. ;
[f7ae]TAX; X = A (screen buffer lookup index) [f7af]LDY ScreenBuffer,X; Y = Screen buffer data at the index. [f7b2]LDA (CurrentArea_BlockAttributesAddr),Y; Load the block attributes at the given offset. [f7b4]JMP textBox_GetAreaBehindTextBox_restoreAndReturn; Jump to finish loading.
;============================================================================ ; TODO: Document Textbox_Maybe_GetAreaBehindTextbox ; ; INPUTS: ; Y ; ; OUTPUTS: ; A ; ; XREFS: ; TextBox_Close ;============================================================================
[f7b7]Textbox_Maybe_GetAreaBehindTextbox:; [$f7b7] [f7b7]TYA [f7b8]PHA [f7b9]TXA [f7ba]PHA [f7bb]LDA a:CurrentROMBank [f7be]PHA [f7bf]LDX #$03 [f7c1]JSR MMC1_UpdateROMBank [f7c4]LDA a:TextBox_ContentsY [f7c7]ASL A [f7c8]ASL A [f7c9]ASL A [f7ca]AND #$f0 [f7cc]STA Temp_Int24 [f7ce]LDA a:TextBox_ContentsX [f7d1]LSR A [f7d2]ORA Temp_Int24 [f7d4]SEC [f7d5]SBC #$20 [f7d7]TAX [f7d8]LDY ScreenBuffer,X [f7db]LDA a:TextBox_ContentsX [f7de]AND #$01 [f7e0]STA Temp_Int24 [f7e2]LDA a:TextBox_ContentsY [f7e5]ASL A [f7e6]AND #$02 [f7e8]ORA Temp_Int24 [f7ea]ASL A [f7eb]TAX [f7ec]LDA CurrentArea_BlockData1StartAddr,X [f7ee]STA Temp_Int24 [f7f0]LDA CurrentArea_BlockData1StartAddr+1,X [f7f2]STA Temp_Int24_M [f7f4]LDA (Temp_Int24),Y
; ; XREFS: ; TextBox_GetBackingAttributeData ;
[f7f6]textBox_GetAreaBehindTextBox_restoreAndReturn:; [$f7f6] [f7f6]STA Temp_Int24; Store the result temporarily.
; ; Restore the previous ROM bank. ;
[f7f8]PLA; Pull the save ROM bank from the stack. [f7f9]TAX; X = A (ROM bank). [f7fa]JSR MMC1_UpdateROMBank; Restore the bank.
; ; Restore the original X and Y registers from the stack. ;
[f7fd]PLA; Pull the original X from the stack. [f7fe]TAX; Set back as X. [f7ff]PLA; Pull the original Y from the stack. [f800]TAY; Set back as Y.
; ; Load the result we calculated and return it. ;
[f801]LDA Temp_Int24; Load the calculated result. [f803]RTS; And return it.
;============================================================================ ; Set the PPU address for drawing text. ; ; INPUTS: ; PPU_ScrollScreen: ; The screen being drawn into. ; ; TextBox_ContentsX: ; The X position to draw text into. ; ; TextBox_ContentsY: ; The Y position to draw text into. ; ; OUTPUTS: ; PPU_TargetAddr: ; PPU_TargetAddr+1: ; The target address to draw to. ; ; XREFS: ; IScriptAction_AddInventoryItem_ClearTextBox ; IScripts_FillPlaceholderText ; PlayerMenu_DrawStringLines ; PlayerMenu_ShowStatusMenu ; PlayerMenu_ShowSubmenu ; TextBox_Close ; TextBox_DrawItemImage ; TextBox_DrawItemName ; TextBox_Open ; TextBox_DrawZeroPaddedNumber ; TextBox_ShowMessage_Fill4Lines ; UI_DrawDigitsNoLeadingZeroes ;============================================================================
[f804]PPU_SetAddrForTextBoxPos:; [$f804] [f804]LDA PPU_ScrollScreen; A = Scroll screen used for drawing. [f806]AND #$01; Convert to 0/1 (even/odd). [f808]ORA #$08; A += 8 [f80a]STA PPU_TargetAddr_U; Store as the PPU target address. [f80c]LDA a:TextBox_ContentsY; Load the text Y position. [f80f]ASL A; A = A * 16 [f810]ASL A [f811]ASL A [f812]ASL A [f813]ROL PPU_TargetAddr_U; Rotate the upper byte of the target address left. [f815]ASL A; A << 1 [f816]ROL PPU_TargetAddr_U; Rotate the upper byte of the target address left. [f818]ORA a:TextBox_ContentsX; OR it with the X position. [f81b]STA PPU_TargetAddr; Store as the lower byte of the target address. [f81d]RTS
;============================================================================ ; Increment the PPU target address by 16. ; ; INPUTS: ; PPU_TargetAddr: ; PPU_TargetAddr+1: ; The existing PPU target address. ; ; OUTPUTS: ; PPU_TargetAddr: ; PPU_TargetAddr+1: ; The updated PPU target address. ; ; XREFS: ; TextBox_LoadItemSourceTiles ; TextBox_ShowMessage_Fill4Lines ;============================================================================
[f81e]PPU_IncrementAddrBy16:; [$f81e] [f81e]LDA #$10; A = 16 [f820]BNE PPU_IncrementAddrBy; Unconditionally jump to PPU_IncrementAddrBy.
;============================================================================ ; Increment the PPU target address by 8. ; ; INPUTS: ; PPU_TargetAddr: ; PPU_TargetAddr+1: ; The existing PPU target address. ; ; OUTPUTS: ; PPU_TargetAddr: ; PPU_TargetAddr+1: ; The updated PPU target address. ; ; XREFS: ; TextBox_FillBackground ;============================================================================
[f822]PPU_IncrementAddrBy8:; [$f822] [f822]LDA #$08; A = 8 [f824]BNE PPU_IncrementAddrBy; Unconditionally jump to PPU_IncrementAddrBy.
;============================================================================ ; Increment the PPU target address by 32. ; ; INPUTS: ; PPU_TargetAddr: ; PPU_TargetAddr+1: ; The existing PPU target address. ; ; OUTPUTS: ; PPU_TargetAddr: ; PPU_TargetAddr+1: ; The updated PPU target address. ; ; XREFS: ; IScriptAction_AddInventoryItem_ClearTextBox ; TextBox_Close ; TextBox_DrawItemImage ; TextBox_Open ; TextBox_ClearTextTiles ; TextBox_FillPlaceholderTextAtLineWithStartChar ;============================================================================
[f826]PPU_IncrementAddrBy32:; [$f826] [f826]LDA #$20; Set the offset to 32.
; ; v-- Fall through --v ;
;============================================================================ ; Increment the PPU target address by a specified amount. ; ; INPUTS: ; A: ; The amount to increment by. ; ; PPU_TargetAddr: ; PPU_TargetAddr+1: ; The existing PPU target address. ; ; OUTPUTS: ; PPU_TargetAddr: ; PPU_TargetAddr+1: ; The updated PPU target address. ; ; XREFS: ; PlayerMenu_DrawStringLines ; PPU_IncrementAddrBy16 ; PPU_IncrementAddrBy8 ;============================================================================
[f828]PPU_IncrementAddrBy:; [$f828] [f828]CLC [f829]ADC PPU_TargetAddr; A += PPU_TargetAddr [f82b]BCC @_storeAndReturn; If it overflowed, jump. No need to increment the upper byte. [f82d]INC PPU_TargetAddr_U; Else, increment the upper byte by 1. [f82f]@_storeAndReturn:; [$f82f] [f82f]STA PPU_TargetAddr; Set the new target address. [f831]RTS
;============================================================================ ; Queue a write to the PPU buffer for the textbox width. ; ; INPUTS: ; TextBox_Width: ; The width of the textbox. ; ; OUTPUTS: ; Y: ; The textbox width. ; ; CALLS: ; PPUBuffer_QueueCommandOrLength ; ; XREFS: ; TextBox_Open ;============================================================================
[f832]TextBox_QueuePPUBufferTextBoxLength:; [$f832] [f832]LDA a:TextBox_Width; A = Textbox width [f835]TAY; Y = A [f836]JMP PPUBuffer_QueueCommandOrLength; Queue as the length.
;============================================================================ ; Fill a line of text within a textbox with a value. ; ; This will write to the PPU buffer for a given width. The ; width is expected to be at the last text position within ; the textbox. It will stop before it hits the padding ; before the text on the line. ; ; INPUTS: ; A: ; The value to write. ; ; X: ; The offset to write to. ; ; Y: ; The text position (+ 2). ; ; OUTPUTS: ; None. ; ; CALLS: ; PPUBuffer_Set ; ; XREFS: ; IScriptAction_AddInventoryItem_ClearTextBox ; TextBox_Open ; TextBox_FillPPUBufferForTextWidth ;============================================================================
[f839]TextBox_FillPPUBufferForTextWidth:; [$f839] [f839]JSR PPUBuffer_Set; Set the value in the buffer at the given offset. [f83c]DEY; Y-- [f83d]CPY #$02; Is it 2? [f83f]BNE TextBox_FillPPUBufferForTextWidth; If not, loop. [f841]RTS
;============================================================================ ; Write a value from a temp location to the PPU buffer. ; ; This will load a byte from Temp_Int24 and write ; it to the PPU buffer. The source offset will be ; advanced by 1. ; ; INPUTS: ; X: ; The destination index within the PPU buffer. ; ; Y: ; The offset from the address to load from. ; ; Temp_Int24: ; The location of the address to load from. ; ; OUTPUTS: ; X: ; The new offset to write to (X + 1). ; ; XREFS: ; PasswordScreen_DrawMessage ; TextBox_LoadItemSourceTiles ; TextBox_Write8BytesFromTemp ;============================================================================
[f842]PPUBuffer_WriteFromTemp:; [$f842] [f842]LDA (Temp_Int24),Y; Load the value from Temp_Int24 at offset Y. [f844]INY; Y++
; ; v-- Fall through --v ;
;============================================================================ ; Write a value to the PPU buffer. ; ; This will write the value and then increment the offset. ; ; INPUTS: ; A: ; The value to write. ; ; X: ; The offset at which to write the buffer. ; ; OUTPUTS: ; X: ; The new offset to write to (X + 1). ; ; PPUBuffer: ; The PPU buffer to write to. ; ; XREFS: ; DEADCODE_FUN_PRG12__9041 ; PasswordScreen_WriteCharTile ; TextBox_Close ; TextBox_DrawItemImage ; TextBox_FillBackground ; TextBox_Open ; UI_DrawString ; PPUBuffer_WriteValueMany ; TextBox_FillPPUBufferForTextWidth ; TextBox_FillPlaceholderTextAtLineWithStartChar ; UI_DrawDigitsZeroPadded ; UI_DrawManaOrHPBar ;============================================================================
[f845]PPUBuffer_Set:; [$f845] [f845]STA PPUBuffer,X; Store A in the PPU buffer at X. [f848]INX; X++ [f849]RTS
;============================================================================ ; Write a value many types to the PPU buffer. ; ; This will write the provided value the specified number ; of times, starting at a given offset. ; ; The first byte written will be the length, and the ; remaining bytes will be the provided value repeated. ; ; INPUTS: ; A: ; The value to write. ; ; Y: ; The number of times to write the value. ; ; OUTPUTS: ; X: ; The new offset in the buffer after these values. ; ; XREFS: ; TextBox_ClearTextTiles ; TextBox_ShowMessage_Fill4Lines ;============================================================================
[f84a]PPUBuffer_WriteValueMany:; [$f84a] [f84a]PHA; Push A to the stack [f84b]TYA; A = Y (length) [f84c]JSR PPUBuffer_QueueCommandOrLength; Write the length to the buffer. [f84f]PLA; Pop A from the stack [f850]@_loop:; [$f850] [f850]JSR PPUBuffer_Set; Set the value in the PPU buffer. [f853]DEY; Y-- [f854]BNE @_loop; If Y > 0, loop [f856]STX PPUBuffer_WriteOffset; Set the upper bounds to write. [f858]RTS; Return with the new offset.
;============================================================================ ; Performs a "Far JSR" to a code in another PRG bank. ; ; This takes in a bank in a the address within the bank (as ; an upper byte and a lower byte) from the next three bytes ; following this call. Specifically, it: ; ; 1. Steals the JSR return address off the stack to locate ; the inline data. ; ; 2. Rewrites the return address so RTS will skip past the ; 3 data bytes. ; ; 3. Reads the inline bytes via (Temp_Int24),Y to ; get the bank/target. ; ; 4. Switches to the bank. ; ; 5. Pushes a trampoline address ($C5F8) and the target ; address so that RTS returns into the trampoline, which ; then JSRs to the target and later restores the original ; bank before returning to the caller. ; ; This is notably used for invoking IScripts. ; ; INPUTS: ; A: ; The bank, upper address byte, and lower address ; byte in stack order. ; ; OUTPUTS: ; None ; ; XREFS: ; Player_CheckHandlePressUpOnNPC ; Player_HandleTouchNPC ; GameLoop_CheckShowPlayerMenu ; Game_DecGloveDuration ; Game_DecHourGlassDuration ; Game_DecOintmentDuration ; Game_DecWingBootsDuration ; Game_OpenDoorWithAKey ; Game_OpenDoorWithDemonsRing ; Game_OpenDoorWithJKey ; Game_OpenDoorWithJoKey ; Game_OpenDoorWithKKey ; Game_OpenDoorWithQKey ; Game_OpenDoorWithRingOfDworf ; Game_OpenDoorWithRingOfElf ; Game_ShowStartScreen ; Game_UnlockDoorWithUsableItem ; IScripts_ClearPortraitImage ; Player_HandleDeath ; Player_PickUpBattleHelmet ; Player_PickUpBattleSuit ; Player_PickUpBlackOnyx ; Player_PickUpDragonSlayer ; Player_PickUpElixir ; Player_PickUpGlove ; Player_PickUpHourGlass ; Player_PickUpMagicalRod ; Player_PickUpMattock ; Player_PickUpOintment ; Player_PickUpPendant ; Player_PickUpPoison ; Player_PickUpRedPotion ; Player_PickUpWingBoots ; Player_UseElixir ; Player_UseHourGlass ; Player_UseMattock ; Player_UseRedPotion ; Player_UseWingBoots ;============================================================================
[f859]MMC1_LoadBankAndJump:; [$f859]
; ; Save out the X, Y, and Z values to temporary variables. ;
[f859]STA BankedCallSetup_SavedA; Set BankedCallSetup_SavedA = A [f85b]STX BankedCallSetup_SavedX; Set BankedCallSetup_SavedX = X [f85d]STY BankedCallSetup_SavedY; Set BankedCallSetup_SavedY = Y
; ; Pop two bytes of A from the stack and transfer to the lower ; and middle bytes (in order) of Temp_Int24. ;
[f85f]PLA; Pop A from the stack. [f860]STA Temp_Int24; Lower byte of Temp_Int24 = A [f862]PLA; Pull A from the stack. [f863]STA Temp_Int24_M; Middle byte of Temp_Int24 = A
; ; Save A to X. We'll increment this below if we overflow ; the add. ;
[f865]TAX; X = A
; ; Compute a new return low = Temp_Int24.L + 3. ;
[f866]LDA Temp_Int24; A = lower byte of Temp_Int24. [f868]CLC; C = 0 [f869]ADC #$03; A = A + 3 [f86b]STA Temp_Int24_U; Upper byte of Temp_Int24 = A
; ; If the upper byte overflowed, increment X. ;
[f86d]BCC @_storeCurrentAddress; If carry is cleared, jump. [f86f]INX; X = X + 1
; ; Preserve the target address and ROM bank on the stack. ;
[f870]@_storeCurrentAddress:; [$f870] [f870]TXA; A = X (our adjusted offset) [f871]PHA; Push A to stack [f872]LDA Temp_Int24_U; A = upper byte of Temp_Int24 [f874]PHA; Push A to stack [f875]LDA a:CurrentROMBank; A = CurrentROMBank [f878]PHA; Push A to stack
; ; Store the loaded address and bank to jump to. ;
[f879]LDY #$03; Y = 3 [f87b]LDA (Temp_Int24),Y; A = Upper byte of the address to jump to. [f87d]STA Maybe_Temp4; Maybe_Temp4 = A [f87f]DEY; Y-- (2) [f880]LDA (Temp_Int24),Y; A = Lower byte of the address to jump to. [f882]STA Temp_Int24_U; Upper byte of Temp_Int24 = A [f884]DEY; Y-- (1) [f885]LDA (Temp_Int24),Y; A = Bank to load [f887]TAX; X = A [f888]JSR MMC1_UpdateROMBank; Update the ROM bank to X.
; ; Push the trampoline address $C5F8. ;
[f88b]LDA #$f8; A = 0xF8 [f88d]PHA; Push A to stack [f88e]LDA #$c5; A = 0xC5 [f890]PHA; Push A to stack
; ; Push the target address for the trampoline. ;
[f891]LDA Maybe_Temp4; A = Maybe_Temp4 [f893]PHA; Push A to stack [f894]LDA Temp_Int24_U; A = upper byte of Temp_Int24. [f896]PHA; Push A to stack
; ; Restore the original values for A, X, and Y. ;
[f897]LDA BankedCallSetup_SavedA; A = BankedCallSetup_SavedA [f899]LDX BankedCallSetup_SavedX; X = BankedCallSetup_SavedX [f89b]LDY BankedCallSetup_SavedY; Y = BankedCallSetup_SavedY [f89d]RTS
;============================================================================ ; TODO: Document PPU_WriteTilesFromCHRRAM ; ; INPUTS: ; X ; Y ; ; OUTPUTS: ; TODO ; ; XREFS: ; SplashAnimation_DrawScenery ; StartScreen_Draw ; UI_DrawHUDSprites ;============================================================================
[f89e]PPU_WriteTilesFromCHRRAM:; [$f89e] [f89e]LDA a:CurrentROMBank [f8a1]PHA [f8a2]JSR MMC1_UpdateROMBank [f8a5]STY Temp_Int24 [f8a7]LDA PPU_TargetAddr_U [f8a9]STA a:PPUADDR [f8ac]LDA PPU_TargetAddr [f8ae]STA a:PPUADDR [f8b1]LDY #$00 [f8b3]@LAB_PRG15_MIRROR__f8b3:; [$f8b3] [f8b3]LDX #$10 [f8b5]@LAB_PRG15_MIRROR__f8b5:; [$f8b5] [f8b5]LDA (IScriptOrCHRAddr),Y [f8b7]STA a:PPUDATA [f8ba]INY [f8bb]BNE @LAB_PRG15_MIRROR__f8bf [f8bd]INC IScriptOrCHRAddr_U [f8bf]@LAB_PRG15_MIRROR__f8bf:; [$f8bf] [f8bf]DEX [f8c0]BNE @LAB_PRG15_MIRROR__f8b5 [f8c2]DEC Temp_Int24 [f8c4]BNE @LAB_PRG15_MIRROR__f8b3 [f8c6]PLA [f8c7]TAX [f8c8]JMP MMC1_UpdateROMBank
;============================================================================ ; PPU address locations where statuc symbols are positioned. ; ; XREFS: ; UI_DrawHUDSprites ;============================================================================
; ; XREFS: ; UI_DrawHUDSprites ;
[f8cb]UI_STATUS_SYMBOL_PPU_ADDR_L:; [$f8cb] [f8cb].byte $41; [0]: 0x2041 -- "M" (Magic)
; ; XREFS: ; UI_DrawHUDSprites ;
[f8cc]UI_STATUS_SYMBOL_PPU_ADDR_L_1_:; [$f8cc] [f8cc].byte $61; [1]: 0x2061 -- "P" (Power) [f8cd].byte $6e; [2]: 0x206E -- "G" (Gold) [f8ce].byte $4e; [3]: 0x204E -- "E" (Experience) [f8cf].byte $56; [4]: 0x2056 -- "T" (Time) [f8d0].byte $5b; [5]: 0x205B -- Top-left of "[" (Selected item) [f8d1].byte $7b; [6]: 0x207B -- Bottom-left of "[" (Selected item)
;============================================================================ ; Tile indexes to display in the status area. ; ; Each of these will be placed horizontally at a start ; location (by UI_STATUS_SYMBOL_PPU_ADDR_L). ; ; A 0x00 means to end the run. ; ; XREFS: ; UI_DrawHUDSprites ;============================================================================
; ; XREFS: ; UI_DrawHUDSprites ;
[f8d2]UI_STATUS_TILES:; [$f8d2] [f8d2].byte $1c; [0]: "M" (Magic)
; ; XREFS: ; UI_DrawHUDSprites ;
[f8d3]UI_STATUS_TILES_1_:; [$f8d3] [f8d3].byte $0a; [1]: ":"
; ; XREFS: ; UI_DrawHUDSprites ;
[f8d4]UI_STATUS_TILES_2_:; [$f8d4] [f8d4].byte $00; [2]: [f8d5].byte $1f; [3]: "P" (Power) [f8d6].byte $0a; [4]: ":" [f8d7].byte $00; [5]: [f8d8].byte $16; [6]: "G" (Gold) [f8d9].byte $3a; [7]: ":" [f8da].byte $00; [8]: [f8db].byte $14; [9]: "E" (Experience) [f8dc].byte $3a; [10]: ":" [f8dd].byte $00; [11]: [f8de].byte $23; [12]: "T" (Time) [f8df].byte $3a; [13]: ":" [f8e0].byte $00; [14]: [f8e1].byte $2c; [15]: Top of "[" (current item) [f8e2].byte $3c; [16]: <blank space> [f8e3].byte $3d; [17]: <blank space> [f8e4].byte $2e; [18]: Top of "]" (current item) [f8e5].byte $00; [19]: [f8e6].byte $2d; [20]: Bottom of "[" (current item) [f8e7].byte $3e; [21]: <blank space> [f8e8].byte $3f; [22]: <blank space> [f8e9].byte $2f; [23]: Bottom of "]" (current item) [f8ea].byte $00; [24]:
;============================================================================ ; Draw the status symbols on the screen. ; ; This will draw the following: ; ; "P" (Power) ; "M" (Magic) ; "E" (Experience) ; "G" (Gold) ; "T" (Time) ; Parts of "[ ]" for item selection ; ; It will also draw the selected item. ; ; INPUTS: ; None ; ; OUTPUTS: ; None ; ; XREFS: ; Screen_SetupNew ;============================================================================
[f8eb]UI_DrawHUDSprites:; [$f8eb] [f8eb]LDA #$0a [f8ed]STA a:UI_MPAndHPBarWidth
; ; Fill the entire status area with 0 (blank). ; This is 4 rows of tiles. ;
[f8f0]LDA #$20; Set the upper byte for the draw position. [f8f2]STA a:PPUADDR; Store it. [f8f5]LDA #$00; Set the lower byte for the draw position. [f8f7]STA a:PPUADDR; Store it. [f8fa]LDY #$80; Set Y = 128 (number of tiles to draw) [f8fc]LDA #$00; Set the value to draw (blank tile). [f8fe]JSR PPU_FillData; Clear all 128 tiles.
; ; Prepare to draw the symbols. ; ; This will loop through all positions, and then all tiles ; for that position. These are done as two separate lookup ; tables: ; ; 1. UI_STATUS_SYMBOL_PPU_ADDR_L: The lower ; addresses ; of the PPU draw positions for placing each set of tiles. ; ; This is incremented by 1 every time we finish placing tiles ; for that area. ; ; 2. UI_STATUS_TILES: The tiles to draw at the ; current ; ; Each run of tiles terminates with a 0x00. The next index would ; then match the next position. This is incremented by 1 every ; time we place a tile. ;
[f901]LDX #$00; Set X = 0 (start index in UI_STATUS_SYMBOL_PPU_ADDR_L. [f903]LDY #$00; Set Y = 0 (start index at UI_STATUS_TILES.
; ; Set the PPUADDR to $20XX, where XX comes from the ; UI_STATUS_SYMBOL_PPU_ADDR_L lookup table. ; ; Each of these will be the location of a static symbol ; to show in the status area. ;
[f905]@_goToNextPosition:; [$f905] [f905]LDA #$20 [f907]STA a:PPUADDR; Store the upper draw position byte as 0x20. [f90a]LDA UI_STATUS_SYMBOL_PPU_ADDR_L,X; Load the lower byte from the lookup table based on the index. [f90d]STA a:PPUADDR; Store it as the lower byte.
; ; Lay out tiles for UI elements starting at the current symbol ; address. ; ; This will write data from the UI_STATUS_TILES ; table ; until it hits an end market (0x00). ; ; This is generally 2-4 tiles per address. ;
[f910]@_drawTiles:; [$f910] [f910]LDA UI_STATUS_TILES,Y; Load the tile to draw at the current tile index. [f913]BEQ @_advanceIndexes; If this is 0x00, we've reached the end of the run. Advance indexes. [f915]STA a:PPUDATA; Else, write the tile. [f918]INY; tileIndex++ [f919]BNE @_drawTiles; Loop.
; ; Advance to the next set of tiles and the next lower address ; (draw position) in the lookup tables. ;
[f91b]@_advanceIndexes:; [$f91b] [f91b]INY; tileIndex++ [f91c]INX; position++
; ; Check if we've completed the lookup table. ; ; There's only 7 addresses/positions to work with. ;
[f91d]CPX #$07; Have we reached the end (draw position 7)? [f91f]BNE @_goToNextPosition; If not, loop.
; ; We've finished drawing the status UI. ;
[f921]JSR UI_DrawSelectedItem [f924]LDA #$40 [f926]STA IScriptOrCHRAddr [f928]LDA #$81 [f92a]STA IScriptOrCHRAddr_U [f92c]LDA #$00 [f92e]STA PPU_TargetAddr [f930]LDA #$10 [f932]STA PPU_TargetAddr_U [f934]LDX #$0a [f936]LDY #$3c [f938]JSR PPU_WriteTilesFromCHRRAM [f93b]RTS
;============================================================================ ; Check if the player reached the next title. ; ; If the player's experience passed the necessary threshold, ; the title will be increased by 1. It will never go up more ; than 1. ; ; INPUTS: ; Experience_U: ; The upper byte of the experience to check. ; ; Experience: ; The lower byte of the experience to check. ; ; NextPlayerTitle: ; The current player title is checked. ; ; OUTPUTS: ; NextPlayerTitle: ; The player title is updated as needed. ; ; XREFS: ; Player_UpdateExperience ;============================================================================
[f93c]Player_CheckReachedNextTitle:; [$f93c] [f93c]LDA a:NextPlayerTitle [f93f]CMP #$0f; Have we hit the max title? (There are 15) [f941]BEQ @_return; If yes, exit. [f943]ASL A; Convert to an index in the lookup table.
; ; Load the 16-bit experience level for the title. ;
[f944]TAX [f945]LDA a:Experience; Load the lower byte from the lookup table. [f948]CMP PLAYER_TITLE_EXP_NEEDED,X; Compare it. [f94b]LDA a:Experience_U; Load the upper byte from the lookup table. [f94e]SBC PLAYER_TITLE_EXP_NEEDED+1,X; Compare it. [f951]BCC @_return; If we're under, exit.
; ; The player has met the next title's requirements. ;
[f953]INC a:NextPlayerTitle; We have enough. Increase the player's next title. [f956]@_return:; [$f956] [f956]RTS
;============================================================================ ; Update experience for the player. ; ; This will update from upper and lower values in RAM. ; ; INPUTS: ; Temp_Int24: ; The lower byte of experience to add. ; ; Temp_Int24+1: ; The upper byte of experience to add. ; ; OUTPUTS: ; None ; ; XREFS: ; Player_AddExperienceFromSprite ; Player_Add100XP ;============================================================================
[f957]Player_UpdateExperience:; [$f957]
; ; Update the lower byte of experience. ;
[f957]LDA a:Experience; Load the current lower byte of experience. [f95a]CLC [f95b]ADC Temp_Int24 [f95d]STA a:Experience; Set the new lower byte of experience.
; ; Update the upper byte of experience. ;
[f960]LDA a:Experience_U; Load the current upper byte of experience. [f963]ADC Temp_Int24_M [f965]STA a:Experience_U; Set the new upper byte of experience. [f968]BCC @LAB_PRG15_MIRROR__f972; Can we add to the experience, or did we hit a max?
; ; We hit the maximum amount of experience. Make sure ; this is capped. ;
[f96a]LDA #$ff [f96c]STA a:Experience; Set lower byte of experience to max. [f96f]STA a:Experience_U; Set upper byte of experience to max. [f972]@LAB_PRG15_MIRROR__f972:; [$f972] [f972]JSR Player_CheckReachedNextTitle; Check if the next title has been hit.
; ; Fall through to the next function. ;
;============================================================================ ; Render the player experience. ; ; This will set the draw position to 0x2050 (the location of ; the first digit of the experience), store out the experience ; to render, and then trigger the render. ; ; INPUTS: ; Experience_U: ; The upper byte of the experience. ; ; Experience: ; The lower byte of the experience. ; ; OUTPUTS: ; PPU_TargetAddr+1: ; The upper byte of the location to render to. ; ; PPU_TargetAddr: ; The lower byte of the location to render to. ; ; $ee: ; TODO: Currently unknown. ; ; CALLS: ; UI_DrawDigitsZeroPadded ; ; XREFS: ; UI_DrawHUD ;============================================================================
[f975]UI_DrawPlayerExperience:; [$f975]
; ; Set the address of the first digit of the experience to write ; (0x2050). ;
[f975]LDA #$20 [f977]STA PPU_TargetAddr_U; Store 0x20 as the upper byte. [f979]LDA #$50 [f97b]STA PPU_TargetAddr; Store 0x50 as the upper byte.
; ; Store the new experience back out to Temp_Int24 and ; Temp_Int24+1. ;
[f97d]LDA a:Experience; Load the lower byte of the experience. [f980]STA Temp_Int24; Save to Temp_Int24. [f982]LDA a:Experience_U; Load the upper byte of the experience. [f985]STA Temp_Int24_M; Save to Temp_Int24+1. [f987]LDA #$00 [f989]STA Temp_Int24_U [f98b]LDY #$05 [f98d]JMP UI_DrawDigitsZeroPadded; Trigger the render.
;============================================================================ ; Draw the number of seconds remaining for an item. ; ; This is used for items like the Wingboots and Glove. ; ; Only the lower byte of the 24-bit value is ever needed. ; ; INPUTS: ; A: ; The number of seconds to draw. ; ; OUTPUTS: ; None ; ; SIDE EFFECTS: ; $ee: ; Temp_Int24+1: ; Temp_Int24: ; PPU_TargetAddr+1: ; PPU_TargetAddr: ; Clobbered. ; ; CALLS: ; UI_DrawDigitsZeroPadded ; ; XREFS: ; Game_DecWingBootsDuration ; Player_UseWingBoots ; UI_DrawHUD ;============================================================================
[f990]UI_DrawTimeValue:; [$f990]
; ; Set the number of seconds remaining as a 24-bit integer. ; ; Only the lower byte is used. The rest are 0'd out. ;
[f990]STA Temp_Int24; Set number of seconds as the lower byte. [f992]LDA #$00 [f994]STA Temp_Int24_M; Set middle byte = 0. [f996]STA Temp_Int24_U; Set upper byte = 0.
; ; Set the draw position (first digit of time remaining). ; This is 0x2058. ;
[f998]LDA #$20 [f99a]STA PPU_TargetAddr_U; Set high byte of position as 0x20. [f99c]LDA #$58 [f99e]STA PPU_TargetAddr; Set high byte of position as 0x58.
; ; Draw the digits with zero-padding. ;
[f9a0]LDY #$02; Set the number of digits to draw. [f9a2]JMP UI_DrawDigitsZeroPadded; Draw the digits with zero-padding.
;============================================================================ ; Subtract gold from the player. ; ; This takes in gold as the 24-bit ; $ee:Temp_Int24+1:Temp_Int24 ; and reduces those values from the player's gold. ; ; After subtracting, this will be drawn to the screen. ; ; INPUTS: ; $ee: ; The upper byte of the 24-bit gold value to subtract. ; ; Temp_Int24+1: ; The middle byte of the 24-bit gold value to ; subtract. ; ; Temp_Int24: ; The lower byte of the 24-bit gold value to ; subtract. ; ; OUTPUTS: ; Gold_U: ; The new upper byte of the current gold value. ; ; Gold_M: ; The new middle byte of the current gold value. ; ; Gold: ; The new lower byte of the current gold value. ; ; CALLS: ; UI_DrawGoldValue ; ; XREFS: ; IScripts_ProgressivelySubtractGold ;============================================================================
[f9a5]Player_SubtractGold:; [$f9a5]
; ; Subtract from the lower byte. ;
[f9a5]LDA a:Gold; Load the lower byte of the current amount. [f9a8]SEC [f9a9]SBC Temp_Int24; Subtract the provided value. [f9ab]STA a:Gold; Store as the new lower byte.
; ; Subtract from the middle byte. ;
[f9ae]LDA a:Gold_M; Load the middle byte of the current amount. [f9b1]SBC Temp_Int24_M; Subtract the provided value and the carry. [f9b3]STA a:Gold_M; Store as the new middle byte.
; ; Subtract from the upper byte. ;
[f9b6]LDA a:Gold_U; Load the upper byte of the current amount. [f9b9]SBC #$00; Subtract the carry. [f9bb]STA a:Gold_U; Store as the new upper byte.
; ; Draw the new amount. ;
[f9be]JMP UI_DrawGoldValue; Draw the new amount of gold.
;============================================================================ ; Add gold to the player. ; ; This takes in gold as the 24-bit ; $ee:Temp_Int24+1:Temp_Int24 ; and adds those values from the player's gold. ; ; After adding, this will be drawn to the screen. ; ; INPUTS: ; $ee: ; The upper byte of the 24-bit gold value to add. ; ; Temp_Int24+1: ; The middle byte of the 24-bit gold value to add. ; ; Temp_Int24: ; The lower byte of the 24-bit gold value to add. ; ; OUTPUTS: ; Gold_U: ; The new upper byte of the current gold value. ; ; Gold_M: ; The new middle byte of the current gold value. ; ; Gold: ; The new lower byte of the current gold value. ; ; CALLS: ; UI_DrawDigitsZeroPadded ; ; XREFS: ; IScripts_ProgressivelyAddGold ;============================================================================
[f9c1]Player_AddGold:; [$f9c1]
; ; Add to the lower byte. ;
[f9c1]LDA a:Gold; Load the lower byte of the current amount. [f9c4]CLC [f9c5]ADC Temp_Int24; Add the new amount. [f9c7]STA a:Gold; Store as the new lower byte.
; ; Add to the middle byte. ;
[f9ca]LDA a:Gold_M; Load the middle byte of the current amount. [f9cd]ADC Temp_Int24_M; Add the new amount + carry. [f9cf]STA a:Gold_M; Store as the new middle byte.
; ; Add to the upper byte. ;
[f9d2]LDA a:Gold_U; Load the upper byte of the current amount. [f9d5]ADC #$00; Add the carry. [f9d7]STA a:Gold_U; Store as the new upper byte.
; ; If there's no carry, draw the value. Otherwise, we've ; maxed out the gold, so set that explicitly and then draw. ;
[f9da]BCC UI_DrawGoldValue; If no carry, draw the current amount.
; ; Carry was set. We're maxed. Make it official. ;
[f9dc]LDA #$ff [f9de]STA a:Gold; Set lower to max. [f9e1]STA a:Gold_M; Set middle to max. [f9e4]STA a:Gold_U; Set upper to max.
; ; v-- Fall through --v ;
;============================================================================ ; Draw the current gold display in the status UI. ; ; The gold will be rendered at the start position of ; 0x2070 (first digit of the gold amount). ; ; This is rendered zero-padded. ; ; INPUTS: ; Gold_U: ; Upper byte of the 24-bit gold value. ; ; Gold_M: ; Middle byte of the 24-bit gold value. ; ; Gold: ; Lower byte of the 24-bit gold value. ; ; OUTPUTS: ; Temp_Int24: ; Temp_Int24+1: ; $ee: ; PPU_TargetAddr: ; PPU_TargetAddr+1: ; Clobbered. ; ; CALLS: ; UI_DrawDigitsZeroPadded;; ; ; XREFS: ; Player_HandleTouchCoin ; Player_AddGold ; Player_SubtractGold ; UI_DrawHUD ;============================================================================
[f9e7]UI_DrawGoldValue:; [$f9e7]
; ; Set the draw position for the first digit of gold. ; This is 0x2070. ;
[f9e7]LDA #$20 [f9e9]STA PPU_TargetAddr_U; Set the upper byte to 0x20. [f9eb]LDA #$70 [f9ed]STA PPU_TargetAddr; Set the lower byte to 0x70.
; ; Load the gold into the 24-bit temporary integer used for ; drawing digits. ;
[f9ef]LDA a:Gold; Load the lower byte of the gold value. [f9f2]STA Temp_Int24; Set a the lower byte for the digits to draw. [f9f4]LDA a:Gold_M; Load the middle byte of the gold value. [f9f7]STA Temp_Int24_M; Set a the middle byte for the digits to draw. [f9f9]LDA a:Gold_U; Load the upper byte of the gold value. [f9fc]STA Temp_Int24_U; Set a the upper byte for the digits to draw.
; ; Draw the new amount as 7 digits, zero-padded. ;
[f9fe]LDY #$07 [fa00]JMP UI_DrawDigitsZeroPadded; Draw 7 digits of gold.
;============================================================================ ; Draw a 0-padded 24-bit number at the textbox draw position. ; ; This will draw a number up to a specific number of digits ; (maximum of 7) at the current textbox draw position, ; filling the width with leading zeros. ; ; It starts by populating all zeros, and then follows up ; by filling in any values from the number. ; ; INPUTS: ; Y: ; The number of digits in total. ; ; $ee: ; The upper byte of the 24-bit value. ; ; Temp_Int24+1: ; The middle byte of the 24-bit value. ; ; Temp_Int24: ; The lower byte of the 24-bit value. ; ; OUTPUTS: ; PPUBuffer_WriteOffset: ; The new write offset into the PPU buffer. ; ; CALLS: ; PPU_SetAddrForTextBoxPos ; PPUBuffer_QueueCommandOrLength ; PPUBuffer_Set ; UI_PopulateDigits ; ; XREFS: ; PlayerMenu_ShowStatusMenu ;============================================================================
[fa03]TextBox_DrawZeroPaddedNumber:; [$fa03] [fa03]JSR PPU_SetAddrForTextBoxPos; Set the PPU position address for the text.
; ; v-- Fall through --v ;
;============================================================================ ; Draw a 0-padded 24-bit number to the screen. ; ; This will draw a number up to a specific number of digits ; (maximum of 7) at the current PPU target address, filling ; the width with leading zeros. ; ; It starts by populating all zeros, and then follows up ; by filling in any values from the number. ; ; INPUTS: ; Y: ; The number of digits in total. ; ; $ee: ; The upper byte of the 24-bit value. ; ; Temp_Int24+1: ; The middle byte of the 24-bit value. ; ; Temp_Int24: ; The lower byte of the 24-bit value. ; ; OUTPUTS: ; PPUBuffer_WriteOffset: ; The new write offset into the PPU buffer. ; ; CALLS: ; PPU_SetAddrForTextBoxPos ; PPUBuffer_QueueCommandOrLength ; PPUBuffer_Set ; UI_PopulateDigits ; ; XREFS: ; UI_DrawGoldValue ; UI_DrawPlayerExperience ; UI_DrawTimeValue ;============================================================================
[fa06]UI_DrawDigitsZeroPadded:; [$fa06] [fa06]TYA; A = Y (number of digits). [fa07]PHA; Push it to the stack. [fa08]JSR UI_PopulateDigits; Populate the ASCII digits to render.
; ; Queue the number of tiles to draw in the PPU buffer. ; ; ; XREFS: ; UI_DrawDigitsNoLeadingZeroes ;
[fa0b]UI_DrawDigitsZeroPadded_Populated:; [$fa0b] [fa0b]PLA; Pull the number of digits from the stack. [fa0c]TAY; Y = A [fa0d]JSR PPUBuffer_QueueCommandOrLength; Set as the number of tiles to draw in the PPU buffer. [fa10]STY Temp_Int24; Store the number temporarily. [fa12]LDA #$07; A = 7 (maximum number of digits). [fa14]SEC [fa15]SBC Temp_Int24; Subtract the number of tiles to draw. [fa17]TAY; Y = result (loop counter). [fa18]@_drawDigitsLoop:; [$fa18] [fa18]LDA UI_DigitsToRender,Y; Load the digit to draw at this index. [fa1b]JSR PPUBuffer_Set; Set it in the PPU buffer. [fa1e]INY; Y++ [fa1f]CPY #$07; Is it 7 (max)? [fa21]BNE @_drawDigitsLoop; If not, loop. [fa23]STX PPUBuffer_WriteOffset; Store the resulting PPU buffer write offset. [fa25]RTS
;============================================================================ ; Draw a space-padded 24-bit number to the screen. ; ; This will draw a number up to a specific number of digits ; (maximum of 7) at the current PPU target address, filling ; the width with spaces. ; ; It starts by populating all spaces, and then follows up ; by filling in any values from the number. ; ; INPUTS: ; Y: ; The number of digits in total. ; ; $ee: ; The upper byte of the 24-bit value. ; ; Temp_Int24+1: ; The middle byte of the 24-bit value. ; ; Temp_Int24: ; The lower byte of the 24-bit value. ; ; OUTPUTS: ; PPUBuffer_WriteOffset: ; The new write offset into the PPU buffer. ; ; CALLS: ; PPU_SetAddrForTextBoxPos ; PPUBuffer_QueueCommandOrLength ; PPUBuffer_Set ; UI_PopulateDigitsNoLeadingZeroes ; ; XREFS: ; Shop_Draw ;============================================================================
[fa26]UI_DrawDigitsNoLeadingZeroes:; [$fa26] [fa26]JSR PPU_SetAddrForTextBoxPos; Set the PPU position address for the text. [fa29]TYA; A = Y (number of digits) [fa2a]PHA; Push it to the stack. [fa2b]JSR UI_PopulateDigitsNoLeadingZeroes; Populate the digits with no leading zeros. [fa2e]JMP UI_DrawDigitsZeroPadded_Populated; Draw the digits.
;============================================================================ ; Populate digits but with leading "0"s empty. ; ; This will populate the digits and then loop through them ; (up to 6 digits), NULing out all leading "0" digits so ; they don't display. ; ; INPUTS: ; X: ; The starting index into the digits. ; ; OUTPUTS: ; UI_DigitsToRender: ; The updated digits. ; ; CALLS: ; UI_PopulateDigits ; ; XREFS: ; UI_DrawDigitsNoLeadingZeroes ;============================================================================
[fa31]UI_PopulateDigitsNoLeadingZeroes:; [$fa31] [fa31]JSR UI_PopulateDigits; Populate the ASCII digits. [fa34]INX; Start at the provided index + 1.
; ; Check if the digit displays "0". If not, we're done. ;
[fa35]@_loop:; [$fa35] [fa35]LDA UI_DigitsToRender,X; Load the current ASCII digit. [fa38]CMP #$30; Is it an ASCII "0"? [fa3a]BNE @_return; If not, we're done.
; ; Clear out the digit entirely. ;
[fa3c]LDA #$00; NUL out this digit. [fa3e]STA UI_DigitsToRender,X; Store that as the new digit value. [fa41]INX; i++ [fa42]CPX #$06; Are we at the end of the loop? [fa44]BNE @_loop; If not, loop again. [fa46]@_return:; [$fa46] [fa46]RTS
;============================================================================ ; Populate RAM with the digits to set based in the status UI. ; ; This will take the 24-bit value stored in ; $ee:Temp_Int24+1:Temp_Int24. ; ; It will loop through every digit, convert to ASCII, and ; store in UI_DigitsToRender for render. ; ; INPUTS: ; $ee: ; The upper byte of the 24-bit value. ; ; Temp_Int24+1: ; The middle byte of the 24-bit value. ; ; Temp_Int24: ; The lower byte of the 24-bit value. ; ; OUTPUTS: ; UI_DigitsToRender: ; The resulting digits in ASCII form. ; ; CALLS: ; UI_GetValueForDigit ; ; XREFS: ; UI_DrawDigitsZeroPadded ; UI_PopulateDigitsNoLeadingZeroes ;============================================================================
[fa47]UI_PopulateDigits:; [$fa47]
; ; Prepare to loop 7 times (the max number of digits). ;
[fa47]LDX #$06; Set the upper bound for the loop. We'll count down.
; ; Get the value for the next digit and convert to ASCII. ;
[fa49]@_loop:; [$fa49] [fa49]JSR UI_GetValueForDigit; Get the value for this digit. [fa4c]ORA #$30; Normalize the value to an ASCII digit. [fa4e]STA UI_DigitsToRender,X; Store in the target position in RAM. [fa51]DEX; i-- [fa52]BPL @_loop; Loop if we're not done. [fa54]RTS
;============================================================================ ; Return a numeric value for status line UI display. ; ; This takes a 24-bit value (representing the gold or ; experience, in practice) and converts it to a numeric ; value between 0 and 9. ; ; It does this by considering the unsigned integer value ; (stored as ; $ee:Temp_Int24+1:Temp_Int24). ; ; It loops for each of the 24 bits, left-shifting and ; rotating the most-significant bit into A (the resulting ; value). If A >= 10, it will subtract 10 and set the ; quotient bit incrementing @{symbol Temp3_L}. Rinse and ; repeat. ; ; After 24 iterations, the the remainder will be in A, and ; this will be a value between 0 and 9. ; ; The upper, middle, and lower values will be modified to ; divide by 10, which allows this to be called repeatedly ; to get every digit. ; ; INPUTS: ; $ee: ; The upper byte of the 24-bit value. ; ; Temp_Int24+1: ; The middle byte of the 24-bit value. ; ; Temp_Int24: ; The lower byte of the 24-bit value. ; ; OUTPUTS: ; A: ; The resulting digit (between 0 and 9). ; ; $ee: ; Upper byte of the floor of the value / 10. ; ; Temp_Int24+1: ; Middle byte of the floor of the value / 10. ; ; Temp_Int24: ; Lower byte of the floor of the value / 10. ; ; XREFS: ; UI_PopulateDigits ;============================================================================
[fa55]UI_GetValueForDigit:; [$fa55] [fa55]LDY #$18; Prepare to loop over the 24 bits. [fa57]LDA #$00; Set A (our value) = 0. [fa59]@_loop:; [$fa59] [fa59]ASL Temp_Int24; Shift the low byte << 1; Set C as the out going bit. [fa5b]ROL Temp_Int24_M; Set mid byte = (mid << 1) | C [fa5d]ROL Temp_Int24_U; Set high byte = (high << 1) | C [fa5f]ROL A; Set A = (A << 1) | outgoing bit of high byte
; ; If the remainder (A) >= 10, subtract 10 and set the ; quotient bit (by incrementing Temp_Int24). ;
[fa60]CMP #$0a; Is A >= 10? [fa62]BCC @_finishLoopIter; Branch if not. [fa64]SBC #$0a; A >= 10, so subtract 10. [fa66]INC Temp_Int24; Set quotient bit to 1. [fa68]@_finishLoopIter:; [$fa68] [fa68]DEY; i-- (process next bit) [fa69]BNE @_loop; Loop until we hit 0. [fa6b]RTS [fa6c]STATUS_BAR_PPU_ADDR_L:; [$fa6c] [fa6c].byte $63; [0]: Power bar
; ; XREFS: ; UI_DrawManaOrHPBar ;
[fa6d]STATUS_BAR_PPU_ADDR_L_1_:; [$fa6d] [fa6d].byte $43; [1]: Mana bar [fa6e].byte $08; [0]: Power bar
; ; XREFS: ; UI_DrawManaOrHPBar ;
[fa6f]BYTE_ARRAY_PRG15_MIRROR__fa6e_1_:; [$fa6f] [fa6f].byte $09; [1]: Manab ar [fa70].byte $0c; [0]: Power bar
; ; XREFS: ; UI_DrawManaOrHPBar ;
[fa71]BYTE_ARRAY_PRG15_MIRROR__fa70_1_:; [$fa71] [fa71].byte $0d; [1]: Mana bar [fa72]BYTE_ARRAY_PRG15_MIRROR__fa72:; [$fa72] [fa72].byte $c0; [0]: [fa73]BYTE_ARRAY_PRG15_MIRROR__fa72_1_:; [$fa73] [fa73].byte $d0; [1]: [fa74].byte $60; [2]:
;============================================================================ ; TODO: Document UI_DrawPlayerHPValue ; ; INPUTS: ; A ; ; OUTPUTS: ; TODO ; ; XREFS: ; IScriptAction_AddHP ; Player_AddHP ; UI_DrawPlayerHP ;============================================================================
[fa75]UI_DrawPlayerHPValue:; [$fa75]
; ; Cap the amount of HP to 80, if over. ;
[fa75]CMP #$51 [fa77]BCC @LAB_PRG15_MIRROR__fa7b [fa79]LDA #$50 [fa7b]@LAB_PRG15_MIRROR__fa7b:; [$fa7b] [fa7b]STA a:Temp_AddedHPValue [fa7e]STA a:Player_HP_U [fa81]LDY #$00 [fa83]BEQ UI_DrawManaOrHPBar
; ; v-- Fall through --v ;
;============================================================================ ; Set the number of mana points for the player. ; ; Once set, the mana bar will be drawn to reflect the new amount. ; ; INPUT: ; A: ; The mana points to set. ; ; OUTPUT: ; None ; ; XREFS: ; IScriptAction_AddMP ; Player_AddMP ; Player_ReduceMP ; UI_DrawHUD ;============================================================================
[fa85]Player_SetMP:; [$fa85]
; ; Cap the number of mana points to 80, if over. ;
[fa85]CMP #$51; Check if over 80 mana points. [fa87]BCC @_storePoints; If not over, we'll store what was passed in. [fa89]LDA #$50; Cap to 80 mana points. [fa8b]@_storePoints:; [$fa8b] [fa8b]STA a:Player_MP; Set the player's current mana points. [fa8e]LDY #$01
; ; v-- Fall through --v ;
;============================================================================ ; TODO: Document UI_DrawManaOrHPBar ; ; INPUTS: ; A ; Y ; ; OUTPUTS: ; TODO ; ; XREFS: ; UI_DrawPlayerHPValue ;============================================================================
[fa90]UI_DrawManaOrHPBar:; [$fa90]
; ; Set the upper byte of the 24-bit integer of the value to draw. ;
[fa90]STA Temp_Int24_U [fa92]LDA a:UI_MPAndHPBarWidth [fa95]ASL A [fa96]ASL A [fa97]ASL A [fa98]CMP Temp_Int24_U [fa9a]BCS @LAB_PRG15_MIRROR__fa9e [fa9c]STA Temp_Int24_U [fa9e]@LAB_PRG15_MIRROR__fa9e:; [$fa9e] [fa9e]STY Maybe_Temp4
; ; Set the draw position for the bar to update. ; This will be based on the lower address from the ; STATUS_BAR_PPU_ADDR_L lookup table. ; ; Bar 0 is the Mana bar. ; Bar 1 is the Power bar. ;
[faa0]LDA #$20 [faa2]STA PPU_TargetAddr_U [faa4]LDA STATUS_BAR_PPU_ADDR_L,Y [faa7]STA PPU_TargetAddr [faa9]LDX a:UI_MPAndHPBarWidth [faac]INX [faad]TXA [faae]JSR PPUBuffer_QueueCommandOrLength [fab1]LDA Temp_Int24_U [fab3]LSR A [fab4]LSR A [fab5]LSR A [fab6]BEQ @LAB_PRG15_MIRROR__fac6 [fab8]PHA [fab9]STA Temp_Int24 [fabb]LDA $fa6e,Y [fabe]@LAB_PRG15_MIRROR__fabe:; [$fabe] [fabe]JSR PPUBuffer_Set [fac1]DEC Temp_Int24 [fac3]BNE @LAB_PRG15_MIRROR__fabe [fac5]PLA [fac6]@LAB_PRG15_MIRROR__fac6:; [$fac6] [fac6]CMP a:UI_MPAndHPBarWidth [fac9]BEQ @LAB_PRG15_MIRROR__fae2 [facb]PHA [facc]LDA $fa70,Y [facf]JSR PPUBuffer_Set [fad2]PLA [fad3]TAY [fad4]LDA #$07 [fad6]@LAB_PRG15_MIRROR__fad6:; [$fad6] [fad6]INY [fad7]CPY a:UI_MPAndHPBarWidth [fada]BEQ @LAB_PRG15_MIRROR__fae2 [fadc]JSR PPUBuffer_Set [fadf]JMP @LAB_PRG15_MIRROR__fad6 [fae2]@LAB_PRG15_MIRROR__fae2:; [$fae2] [fae2]LDA #$0b [fae4]JSR PPUBuffer_Set [fae7]STX PPUBuffer_WriteOffset [fae9]LDA Temp_Int24_U [faeb]AND #$07 [faed]JSR Math_MultiplyBy16 [faf0]TAY [faf1]LDA #$10 [faf3]STA PPU_TargetAddr_U [faf5]LDX Maybe_Temp4 [faf7]LDA BYTE_ARRAY_PRG15_MIRROR__fa72,X [fafa]STA PPU_TargetAddr [fafc]LDA #$10 [fafe]JSR PPUBuffer_QueueCommandOrLength [fb01]LDA Maybe_Temp4 [fb03]BNE @LAB_PRG15_MIRROR__fb14
; ; DEADCODE: All this is wired off behind Maybe_Temp4, which ; is never actually set. This appears to be code they ; were working on but never completed/got right. Which ; would not be a shock since part of it reaches into ; program code instead of an array of values. ;
[fb05]@LAB_PRG15_MIRROR__fb05:; [$fb05] [fb05]LDA BYTE_PRG15_MIRROR__fb2f,Y [fb08]INY [fb09]JSR PPUBuffer_Set [fb0c]TYA [fb0d]AND #$0f [fb0f]BNE @LAB_PRG15_MIRROR__fb05 [fb11]STX PPUBuffer_WriteOffset [fb13]RTS [fb14]@LAB_PRG15_MIRROR__fb14:; [$fb14] [fb14]LDA BYTE_PRG15_MIRROR__fb37,Y [fb17]INY [fb18]JSR PPUBuffer_Set [fb1b]TYA [fb1c]AND #$07 [fb1e]BNE @LAB_PRG15_MIRROR__fb14
; ; XXX Is this correct? It's reading from program code. ;
[fb20]@LAB_PRG15_MIRROR__fb20:; [$fb20] [fb20]LDA @LAB_PRG15_MIRROR__fb27,Y [fb23]INY [fb24]JSR PPUBuffer_Set [fb27]@LAB_PRG15_MIRROR__fb27:; [$fb27] [fb27]TYA [fb28]AND #$07 [fb2a]BNE @LAB_PRG15_MIRROR__fb20 [fb2c]STX PPUBuffer_WriteOffset [fb2e]RTS
; ; XREFS: ; LAB_PRG15_MIRROR__fb05 [$PRG15_MIRROR::fb05] ;
[fb2f]BYTE_PRG15_MIRROR__fb2f:; [$fb2f] [fb2f].byte $00,$ff,$00,$00,$00,$00,$ff,$00; [$fb2f] byte
; ; XREFS: ; LAB_PRG15_MIRROR__fb14 [$PRG15_MIRROR::fb14] ;
[fb37]BYTE_PRG15_MIRROR__fb37:; [$fb37] [fb37].byte $00,$ff,$00,$00,$00,$00,$ff,$00; [$fb37] byte [fb3f].byte $00,$ff,$80,$80,$80,$80,$ff,$00; [$fb3f] byte [fb47].byte $00,$ff,$00,$00,$00,$00,$ff,$00; [$fb47] byte [fb4f].byte $00,$ff,$c0,$c0,$c0,$c0,$ff,$00; [$fb4f] byte [fb57].byte $00,$ff,$00,$00,$00,$00,$ff,$00; [$fb57] byte [fb5f].byte $00,$ff,$e0,$e0,$e0,$e0,$ff,$00; [$fb5f] byte [fb67].byte $00,$ff,$00,$00,$00,$00,$ff,$00; [$fb67] byte [fb6f].byte $00,$ff,$f0,$f0,$f0,$f0,$ff,$00; [$fb6f] byte [fb77].byte $00,$ff,$00,$00,$00,$00,$ff,$00; [$fb77] byte [fb7f].byte $00,$ff,$f8,$f8,$f8,$f8,$ff,$00; [$fb7f] byte [fb87].byte $00,$ff,$00,$00,$00,$00,$ff,$00; [$fb87] byte [fb8f].byte $00,$ff,$fc,$fc,$fc,$fc,$ff,$00; [$fb8f] byte [fb97].byte $00,$ff,$00,$00,$00,$00,$ff,$00; [$fb97] byte [fb9f].byte $00,$ff,$fe,$fe,$fe,$fe,$ff,$00; [$fb9f] byte [fba7].byte $00,$ff,$00,$00,$00,$00,$ff,$00; [$fba7] byte
;============================================================================ ; TODO: Document UI_DrawSelectedItem ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; UI_DrawHUDSprites ;============================================================================
[fbaf]UI_DrawSelectedItem:; [$fbaf] [fbaf]LDA #$13 [fbb1]STA a:PPUADDR [fbb4]LDA #$c0 [fbb6]STA a:PPUADDR [fbb9]LDA a:SelectedItem [fbbc]BPL @LAB_PRG15_MIRROR__fbc5 [fbbe]LDY #$40 [fbc0]LDA #$00 [fbc2]JMP PPU_FillData [fbc5]@LAB_PRG15_MIRROR__fbc5:; [$fbc5] [fbc5]ASL A [fbc6]ASL A [fbc7]TAY [fbc8]LDA a:CurrentROMBank [fbcb]PHA [fbcc]LDX #$0a [fbce]JSR MMC1_UpdateROMBank [fbd1]@LAB_PRG15_MIRROR__fbd1:; [$fbd1] [fbd1]LDA ITEM_TILEMAP_INDEXES,Y [fbd4]JSR UI_Maybe_GetItemSpritePPUTileAddr [fbd7]TYA [fbd8]PHA [fbd9]LDY #$00 [fbdb]@LAB_PRG15_MIRROR__fbdb:; [$fbdb] [fbdb]LDA (Temp_Int24),Y [fbdd]STA a:PPUDATA [fbe0]INY [fbe1]CPY #$10 [fbe3]BNE @LAB_PRG15_MIRROR__fbdb [fbe5]PLA [fbe6]TAY [fbe7]INY [fbe8]TYA [fbe9]AND #$03 [fbeb]BNE @LAB_PRG15_MIRROR__fbd1 [fbed]JMP MMC1_UpdatePRGBankToStackA
;============================================================================ ; TODO: Document UI_Maybe_GetItemSpritePPUTileAddr ; ; INPUTS: ; A ; ; OUTPUTS: ; TODO ; ; XREFS: ; TextBox_LoadItemSourceTiles ; UI_DrawSelectedItem ;============================================================================
[fbf0]UI_Maybe_GetItemSpritePPUTileAddr:; [$fbf0] [fbf0]STA Temp_Int24_M [fbf2]LDA #$00 [fbf4]LSR Temp_Int24_M [fbf6]ROR A [fbf7]LSR Temp_Int24_M [fbf9]ROR A [fbfa]LSR Temp_Int24_M [fbfc]ROR A [fbfd]LSR Temp_Int24_M [fbff]ROR A [fc00]ADC #$00 [fc02]STA Temp_Int24 [fc04]LDA Temp_Int24_M [fc06]ADC #$85 [fc08]STA Temp_Int24_M [fc0a]RTS
;============================================================================ ; TODO: Document Player_SetItem ; ; INPUTS: ; A ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_Equip ;============================================================================
[fc0b]Player_SetItem:; [$fc0b] [fc0b]STA a:SelectedItem [fc0e]LDX #$13 [fc10]STX PPU_TargetAddr_U [fc12]LDX #$c0 [fc14]STX PPU_TargetAddr [fc16]ORA #$80
;============================================================================ ; TODO: Document TextBox_LoadItemSourceTiles ; ; INPUTS: ; A ; ; OUTPUTS: ; TODO ; ; XREFS: ; TextBox_DrawItemImage ;============================================================================
[fc18]TextBox_LoadItemSourceTiles:; [$fc18] [fc18]PHA [fc19]JSR Player_GetInventoryIndexForItem [fc1c]TAX [fc1d]LDA FUN_PRG15_MIRROR__fc0b__LOWER_ADDR_TABLE,X [fc20]STA Temp_Int24_U [fc22]LDA FUN_PRG15_MIRROR__fc0b__UPPER_ADDR_TABLE,X [fc25]STA Maybe_Temp4 [fc27]PLA [fc28]AND #$1f [fc2a]ASL A [fc2b]ASL A [fc2c]TAY [fc2d]LDA a:CurrentROMBank [fc30]PHA [fc31]LDX #$0a [fc33]JSR MMC1_UpdateROMBank [fc36]@LAB_PRG15_MIRROR__fc36:; [$fc36] [fc36]LDA (Temp_Int24_U),Y [fc38]JSR UI_Maybe_GetItemSpritePPUTileAddr [fc3b]TYA [fc3c]PHA [fc3d]LDA #$10 [fc3f]JSR PPUBuffer_QueueCommandOrLength [fc42]LDY #$00 [fc44]@LAB_PRG15_MIRROR__fc44:; [$fc44] [fc44]JSR PPUBuffer_WriteFromTemp [fc47]CPY #$10 [fc49]BNE @LAB_PRG15_MIRROR__fc44 [fc4b]STX PPUBuffer_WriteOffset [fc4d]JSR PPU_IncrementAddrBy16 [fc50]PLA [fc51]TAY [fc52]INY [fc53]TYA [fc54]AND #$03 [fc56]BNE @LAB_PRG15_MIRROR__fc36 [fc58]JMP MMC1_UpdatePRGBankToStackA
; ; XREFS: ; TextBox_LoadItemSourceTiles ;
[fc5b]FUN_PRG15_MIRROR__fc0b__LOWER_ADDR_TABLE:; [$fc5b] [fc5b].byte $a0; [0]: [fc5c].byte $b0; [1]: [fc5d].byte $c0; [2]: [fc5e].byte $d0; [3]: [fc5f].byte $e4; [4]:
; ; XREFS: ; TextBox_LoadItemSourceTiles ;
[fc60]FUN_PRG15_MIRROR__fc0b__UPPER_ADDR_TABLE:; [$fc60] [fc60].byte $b4; [0]: [fc61].byte $b4; [1]: [fc62].byte $b4; [2]: [fc63].byte $b4; [3]: [fc64].byte $b4; [4]:
;============================================================================ ; Show the start screen and handle game start or password selection. ; ; This is shown just after the game boots. The screen will ; be drawn and will wait on input from the player, allowing ; them to start the game or enter the password screen. ; ; INPUTS: ; None. ; ; OUTPUTS: ; None. ; ; CALLS: ; Game_Start ; MMC1_LoadBankAndJump ; PasswordScreen_Show ; Player_SetInitialExpAndGold ; Player_SetStartGameState ; Player_Spawn ; Screen_ResetSpritesForNonGame ; SplashAnimation_RunIntro ; StartScreen_CheckHandleInput ; StartScreen_Draw ; WaitForNextFrame ; ; XREFS: ; Game_InitStateForStartScreen ;============================================================================
[fc65]Game_ShowStartScreen:; [$fc65] [fc65]LDX #$ff; X = 0xFF [fc67]TXS; Store X in memory.
; ; Switch to bank 12 and run StartScreen_Draw. ;
[fc68]JSR MMC1_LoadBankAndJump; Jump to: [fc6b].byte BANK_12_LOGIC; Bank = 12 [fc6c].word StartScreen_Draw-1; Address = StartScreen_Draw
; ; Wait for a choice at the game's start screen. ;
[fc6e]@_waitForInput:; [$fc6e] [fc6e]JSR WaitForNextFrame; Wait for the next frame. [fc71]JSR Screen_ResetSpritesForNonGame
; ; Switch to bank 12 and run StartScreen_CheckHandleInput. ;
[fc74]JSR MMC1_LoadBankAndJump; Jump to: [fc77].byte BANK_12_LOGIC; Bank = 12 [fc78].word StartScreen_CheckHandleInput-1; Address = StartScreen_CheckHandleInput [fc7a]@_afterCheckHandleInputFarJump:; [$fc7a] [fc7a]LDA Joy1_ChangedButtonMask; Check the changed controller 1 button mask. [fc7c]AND #$10; Was the Start button pressed? [fc7e]BEQ @_waitForInput; If not, loop.
; ; The player pressed Start. Check what they chose. ; ; But first, play the titlescreen music. ;
[fc80]LDA #$08 [fc82]STA Music_Current [fc84]LDA a:DAT_0687; Check the chosen option. [fc87]BEQ @_startGame; If 0, they chose to start the game. Else, fall through.
; ; The player chose to enter the Password screen. ; ; Switch to bank 12 and run PasswordScreen_Show. ;
[fc89]JSR MMC1_LoadBankAndJump; Jump to: [fc8c].byte BANK_12_LOGIC; Bank = 12 [fc8d].word PasswordScreen_Show-1; Address = PasswordScreen_Show
; ; Switch to bank 12 and run Player_SetInitialExpAndGold. ;
[fc8f]@_afterPasswordScreenShow:; [$fc8f] [fc8f]JSR MMC1_LoadBankAndJump; Jump to: [fc92].byte BANK_12_LOGIC; Bank = 12 [fc93].word Player_SetInitialExpAndGold-1; Address = Player_SetInitialExpAndGold [fc95]@_afterSetExpGoldFarJump:; [$fc95] [fc95]JMP Player_Spawn
; ; The player chose to start the game. ; ; Switch to bank 12 and run SplashAnimation_RunIntro. ;
[fc98]@_startGame:; [$fc98] [fc98]JSR MMC1_LoadBankAndJump; Jump to: [fc9b].byte BANK_12_LOGIC; Bank = 12 [fc9c].word SplashAnimation_RunIntro-1; Address = SplashAnimation_RunIntro
; ; Switch to bank 12 and run Player_SetStartGameState. ;
[fc9e]@_afterRunIntroFarJump:; [$fc9e] [fc9e]JSR MMC1_LoadBankAndJump; Jump to: [fca1].byte BANK_12_LOGIC; Bank = 12 [fca2].word Player_SetStartGameState-1; Address = Player_SetStartGameState
; ; Begin the game. ;
[fca4]@_afterSetStartGameStateFarJump:; [$fca4] [fca4]JMP Game_Start
;============================================================================ ; Draw an input-requesting symbol. ; ; This is used to position and animate a symbol indicating ; a need for input. ; ; This is used for terminator symbols on textboxes and for the ; item selection symbols on the start screen and Buy/Sell menus. ; ; INPUTS: ; A: ; The sprite and animation frame offset. ; ; X: ; The X position of the symbol. ; ; Y: ; The Y position of the symbol. ; ; OUTPUTS: ; None. ; ; CALLS: ; Sprite_SetAppearanceAddrFromOffset ; ; XREFS: ; Menu_UpdateAndDraw ; PasswordScreen_DrawSelectionCursor ; StartScreen_CheckHandleInput ; TextBox_DrawDownArrowTerminatorSymbol ; TextBox_DrawQuestionMarkTerminatorSymbol ; TextBox_DrawUpArrowTerminatorSymbol ;============================================================================
[fca7]UI_DrawPromptInputSymbol:; [$fca7] [fca7]STX Arg_DrawSprite_PosX; Set the X argument. [fca9]STY Arg_DrawSprite_PosY; Set the Y argument. [fcab]LDX #$00 [fcad]STX CurrentSprite_FlipMask; Clear the flip mask. [fcaf]JMP Sprite_SetAppearanceAddrFromOffset; Set the animation frame and offset of the sprite.
;============================================================================ ; Fill the PPU with a single byte repeated `count` times. ; ; This will always write the value at least once and then ; decrement. If a count of 0 is passed, it will decrement ; to 0xFF, and then loop back down to 0. ; ; INPUTS: ; A: ; The value to write. ; ; Y: ; The number of times to write the value. ; ; If 0, this will write 256 times. ; ; OUTPUTS: ; PPUDATA: ; This will be filled with values. ; ; XREFS: ; PasswordScreen_Show ; PPU_ClearAllTilemaps ; PPU_FillData ; UI_DrawHUDSprites ; UI_DrawSelectedItem ;============================================================================
[fcb2]PPU_FillData:; [$fcb2] [fcb2]STA a:PPUDATA; Store the value in PPUDATA. [fcb5]DEY; Y-- [fcb6]BNE PPU_FillData; If not 0, loop. [fcb8]RTS
;============================================================================ ; Clear all tiles across all nametables. ; ; This will fill every tile with 0. ; ; INPUTS: ; None ; ; OUTPUTS: ; PPUADDR: ; The updated address. ; ; CALLS: ; PPU_FillData ; ; XREFS: ; PasswordScreen_Show ; StartScreen_Draw ;============================================================================
[fcb9]PPU_ClearAllTilemaps:; [$fcb9]
; ; Set the start position to the top-left ($2000). ;
[fcb9]LDA #$20 [fcbb]STA a:PPUADDR [fcbe]LDA #$00 [fcc0]STA a:PPUADDR
; ; Prepare the value and loop counter. ;
[fcc3]TAY; Y = 0 (value to write) [fcc4]LDX #$04; X = 4 (loop counter)
; ; Begin the loop. ;
[fcc6]@_loop:; [$fcc6] [fcc6]JSR PPU_FillData; Fill 256 bytes of data. [fcc9]DEX; X-- [fcca]BNE @_loop; If not 0, loop. [fccc]RTS [fccd].byte $bd,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fccd] undefined [fcd5].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fcd5] undefined [fcdd].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fcdd] undefined [fce5].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fce5] undefined [fced].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fced] undefined [fcf5].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fcf5] undefined [fcfd].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fcfd] undefined [fd05].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fd05] undefined [fd0d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fd0d] undefined [fd15].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fd15] undefined [fd1d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fd1d] undefined [fd25].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fd25] undefined [fd2d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fd2d] undefined [fd35].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fd35] undefined [fd3d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fd3d] undefined [fd45].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fd45] undefined [fd4d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fd4d] undefined [fd55].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fd55] undefined [fd5d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fd5d] undefined [fd65].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fd65] undefined [fd6d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fd6d] undefined [fd75].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fd75] undefined [fd7d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fd7d] undefined [fd85].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fd85] undefined [fd8d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fd8d] undefined [fd95].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fd95] undefined [fd9d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fd9d] undefined [fda5].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fda5] undefined [fdad].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fdad] undefined [fdb5].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fdb5] undefined [fdbd].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fdbd] undefined [fdc5].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fdc5] undefined [fdcd].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fdcd] undefined [fdd5].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fdd5] undefined [fddd].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fddd] undefined [fde5].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fde5] undefined [fded].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fded] undefined [fdf5].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fdf5] undefined [fdfd].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fdfd] undefined [fe05].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fe05] undefined [fe0d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fe0d] undefined [fe15].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fe15] undefined [fe1d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fe1d] undefined [fe25].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fe25] undefined [fe2d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fe2d] undefined [fe35].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fe35] undefined [fe3d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fe3d] undefined [fe45].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fe45] undefined [fe4d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fe4d] undefined [fe55].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fe55] undefined [fe5d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fe5d] undefined [fe65].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fe65] undefined [fe6d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fe6d] undefined [fe75].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fe75] undefined [fe7d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fe7d] undefined [fe85].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fe85] undefined [fe8d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fe8d] undefined [fe95].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fe95] undefined [fe9d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fe9d] undefined [fea5].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fea5] undefined [fead].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fead] undefined [feb5].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$feb5] undefined [febd].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$febd] undefined [fec5].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fec5] undefined [fecd].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fecd] undefined [fed5].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fed5] undefined [fedd].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fedd] undefined [fee5].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fee5] undefined [feed].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$feed] undefined [fef5].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fef5] undefined [fefd].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$fefd] undefined [ff05].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ff05] undefined [ff0d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ff0d] undefined [ff15].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ff15] undefined [ff1d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ff1d] undefined [ff25].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ff25] undefined [ff2d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ff2d] undefined [ff35].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ff35] undefined [ff3d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ff3d] undefined [ff45].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ff45] undefined [ff4d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ff4d] undefined [ff55].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ff55] undefined [ff5d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ff5d] undefined [ff65].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ff65] undefined [ff6d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ff6d] undefined [ff75].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ff75] undefined [ff7d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ff7d] undefined [ff85].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ff85] undefined [ff8d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ff8d] undefined [ff95].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ff95] undefined [ff9d].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ff9d] undefined [ffa5].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ffa5] undefined [ffad].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ffad] undefined [ffb5].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ffb5] undefined [ffbd].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ffbd] undefined [ffc5].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ffc5] undefined [ffcd].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ffcd] undefined [ffd5].byte $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff; [$ffd5] undefined [ffdd].byte $ff,$ff,$ff; [$ffdd] undefined .byte $20; Game title [ffe1].byte $20,$20,$20,$20,$20,$20,$20,$46; [$ffe1] string [ffe9].byte $41,$58,$41,$4e,$41,$44,$55; [$ffe9] string .word $4227; PRG Checksum [fff2].word $0000; CHR CHecksum [fff4].byte $48; CHR size: 0 = 8KiB CHR type: 0 = CHR ROM PRG size: 5 = 512KiB [fff5].byte $04; Mapper: 4 = MMC Nametable: 0 = Horizontal arrangement [fff6].byte $01; Title encoding: 1 = ASCII [fff7].byte $07; Title length: 8 bytes [fff8].byte $18; Licensee Code: Hudson Soft [fff9].byte $94; Header validation byte [fffa].word OnInterrupt; OnInterrupt [$PRG15_MIRROR::fffa] [fffc].word Game_Init; Game_Init [$PRG15_MIRROR::fffc] [fffe].byte $d5; [$fffe] undefined
; ; XREFS: ; MMC1_Init ;
[ffff]MMC1_SERIAL:; [$ffff] [ffff].byte $c9; [$ffff] undefined