;============================================================================ ; Faxanadu (U).nes ; ; PRG15_MIRROR ($c000 - $ffdf) ;============================================================================
.segment "PRG15_MIRROR" .reloc
;============================================================================ ; 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:
; ; Set the PPUADDR to 0x23C0, the top row where the status ; bar resides. ;
[c000]a9 23LDA #$23 [c002]8d 06 20STA a:PPUADDR; Set upper PPUADDR as 0x23. [c005]a9 c0LDA #$c0 [c007]8d 06 20STA a:PPUADDR; Set upper PPUADDR as 0xC0. [c00a]ae 8d 03LDX a:UI_AttributeDataIndex; X = HUD attribute data index, computed when setting up the screen. [c00d]bd 1b c0LDA a: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]a2 08LDX #$08; X = 8 (loop counter). [c012]@_writeLoop: [c012]8d 07 20STA a:PPUDATA; Write to the PPU. [c015]caDEX; X-- [c016]d0 faBNE @_writeLoop; If X > 0, loop.
; ; Draw the HUD. ;
[c018]4c 58 c0JMP 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]00.byte $00; [0]: Dartmoor, Castle of Fraternal, King Grieve's Room [c01c]55.byte $55; [1]: Start Screen [c01d]aa.byte $aa; [2]: Most exterior areas. [c01e]ff.byte $ff; [3]: Most interior areas. [c01f]41.byte $41; [4]: Here and below are unused. [c020]20.byte $20; [5]: [c021]04.byte $04; [6]: [c022]07.byte $07; [7]: [c023]08.byte $08; [8]: [c024]09.byte $09; [9]: [c025]0a.byte $0a; [10]: [c026]61.byte $61; [11]: [c027]20.byte $20; [12]: [c028]04.byte $04; [13]: [c029]0b.byte $0b; [14]: [c02a]0c.byte $0c; [15]: [c02b]0d.byte $0d; [16]: [c02c]0e.byte $0e; [17]: [c02d]56.byte $56; [18]: [c02e]20.byte $20; [19]: [c02f]03.byte $03; [20]: [c030]0f.byte $0f; [21]: [c031]10.byte $10; [22]: [c032]11.byte $11; [23]:
;============================================================================ ; DEADCODE ;============================================================================
[c033]DEADCODE_FUN_PRG15_MIRROR__c033: [c033]a0 00LDY #$00 [c035]b1 02LDA (Temp_Addr_L),Y [c037]85 e8STA PPU_TargetAddr [c039]c8INY [c03a]b1 02LDA (Temp_Addr_L),Y [c03c]85 e9STA PPU_TargetAddr_U [c03e]c8INY [c03f]b1 02LDA (Temp_Addr_L),Y [c041]48PHA [c042]20 dc cfJSR PPUBuffer_QueueCommandOrLength [c045]68PLA [c046]85 00STA Temp_00 [c048]a0 03LDY #$03 [c04a]@LAB_PRG15_MIRROR__c04a: [c04a]b1 02LDA (Temp_Addr_L),Y [c04c]9d 00 05STA a:PPUBuffer,X [c04f]e8INX [c050]c8INY [c051]c6 00DEC Temp_00 [c053]d0 f5BNE @LAB_PRG15_MIRROR__c04a [c055]86 20STX PPUBuffer_WriteOffset [c057]60RTS
;============================================================================ ; 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]a9 00LDA #$00 [c05a]20 90 f9JSR UI_DrawTimeValue; Draw the time value. [c05d]20 e7 f9JSR UI_DrawGoldValue; Draw the gold value. [c060]20 fb cfJSR PPUBuffer_DrawAll; Flush to the PPU. [c063]20 75 f9JSR UI_DrawPlayerExperience; Draw the player experience value. [c066]20 fb cfJSR PPUBuffer_DrawAll; Flush to the PPU. [c069]20 fb cfJSR PPUBuffer_DrawAll; Flush to the PPU. [c06c]20 bd c0JSR UI_DrawPlayerHP; Draw the player's HP bar. [c06f]20 fb cfJSR PPUBuffer_DrawAll; Flush to the PPU. [c072]ad 9a 03LDA a:Player_MP; Load the player's MP. [c075]20 85 faJSR Player_SetMP; Set it and draw. [c078]4c fb cfJMP 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:
; ; Update the player's HP with the provided value. ;
[c07b]18CLC; Clear carry so it's not added. [c07c]6d 31 04ADC a:Player_HP_U; Add the provided HP to the player's health. [c07f]8d 31 04STA a:Player_HP_U; Store as the new health.
; ; Check against the cap. ;
[c082]c9 50CMP #$50; Cap at 80 HP. [c084]90 05BCC @_drawHP; Jump if it's under. [c086]a9 50LDA #$50; Else, cap it to 80HP. [c088]8d 31 04STA a:Player_HP_U; Store as the new health. [c08b]@_drawHP: [c08b]4c 75 faJMP 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:
; ; Reduce the lower byte of player health. ;
[c08e]ad 32 04LDA a:Player_HP_L; Load the lower byte of the player's health. [c091]38SEC [c092]ed bc 04SBC a:Arg_PlayerHealthDelta_L; Subtract the specified lower byte of health. [c095]8d 32 04STA a:Player_HP_L; Store it as the new player health.
; ; Reduce the upper byte of player health. ;
[c098]ad 31 04LDA a:Player_HP_U; Load the upper byte of the player's health. [c09b]ed bd 04SBC a:Arg_PlayerHealthDelta_U; Subtract the specified upper byte of health. [c09e]8d 31 04STA a:Player_HP_U; Store it as the new player health. [c0a1]b0 17BCS @_isStillAlive; If there's still health left, the player is still alive.
; ; The player is out of health. They may die. ;
[c0a3]a9 00LDA #$00 [c0a5]8d 31 04STA a:Player_HP_U; Set the player's upper byte of health to 0. [c0a8]20 bd c0JSR UI_DrawPlayerHP; Draw the health.
; ; Check if the player has an elixir before killing them. ;
[c0ab]ad 2c 04LDA a:SpecialItems; Load the player's special items. [c0ae]29 08AND #$08; Check if the player has an elixir. [c0b0]f0 03BEQ @_killPlayer; If not, kill the player.
; ; The player has an elixir. Fill up their health instead ; of killing the player. ;
[c0b2]4c ca c4JMP Player_UseElixir; Use the Elixir.
; ; Mark the player as dead. ;
[c0b5]@_killPlayer: [c0b5]a9 01LDA #$01; Mark the player as dead. [c0b7]8d 38 04STA a:PlayerIsDead; Store that.
; ; Draw the player's health. ;
[c0ba]@_isStillAlive: [c0ba]4c bd c0JMP 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]ad 31 04LDA a:Player_HP_U; Load the player's current health. [c0c0]4c 75 faJMP 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]ae c0 03LDX a:SelectedMagic; Load the selected magic spell. [c0c6]ad 9a 03LDA a:Player_MP; Load the player's total MP. [c0c9]38SEC [c0ca]fd a9 b7SBC a:MAGIC_COSTS,X; Look up the cost of this spell. [c0cd]90 08BCC @_notEnoughMP; If there's not enough MP for the spell, then jump.
; ; The player has enough MP for the spell. ;
[c0cf]8d 9a 03STA a:Player_MP; Else, store the reduced MP. [c0d2]20 85 faJSR Player_SetMP; Set that on the player and draw. [c0d5]18CLC; Clear the carry flag to indicate a success. [c0d6]60RTS
; ; The player does not have enough mana. ;
[c0d7]@_notEnoughMP: [c0d7]38SEC; Set the carry flag to 1 to indicate not enough MP. [c0d8]60RTS
;============================================================================ ; 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:
; ; Add the provided MP to the player's MP. ;
[c0d9]18CLC; Clear carry so it's not added. [c0da]6d 9a 03ADC a:Player_MP; Add to the player's MP. [c0dd]8d 9a 03STA a:Player_MP; Store it. [c0e0]c9 50CMP #$50; Check if it's > 80 points. [c0e2]90 05BCC @_drawMP; If we're under the cap, return.
; ; Cap the MP to the total allowed amount. ;
[c0e4]a9 50LDA #$50; Cap to 80 points.
; ; Store it. ;
[c0e6]8d 9a 03STA a:Player_MP [c0e9]@_drawMP: [c0e9]4c 85 faJMP 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]8aTXA [c0ed]48PHA [c0ee]a9 00LDA #$00 [c0f0]8d c0 04STA a:Temp1_SomethingChangedHP [c0f3]8d c1 04STA a:Temp2_SomethingChangedHP [c0f6]a2 10LDX #$10 [c0f8]2e bc 04ROL a:Arg_PlayerHealthDelta_L [c0fb]2e bd 04ROL a:Arg_PlayerHealthDelta_U [c0fe]@LAB_PRG15_MIRROR__c0fe: [c0fe]2e c0 04ROL a:Temp1_SomethingChangedHP [c101]2e c1 04ROL a:Temp2_SomethingChangedHP [c104]ad c0 04LDA a:Temp1_SomethingChangedHP [c107]cd be 04CMP a:SpriteBehaviorUnknown20_SomethingXOrY [c10a]ad c1 04LDA a:Temp2_SomethingChangedHP [c10d]ed bf 04SBC a:BYTE_04bf [c110]90 12BCC @LAB_PRG15_MIRROR__c124 [c112]ad c0 04LDA a:Temp1_SomethingChangedHP [c115]ed be 04SBC a:SpriteBehaviorUnknown20_SomethingXOrY [c118]8d c0 04STA a:Temp1_SomethingChangedHP [c11b]ad c1 04LDA a:Temp2_SomethingChangedHP [c11e]ed bf 04SBC a:BYTE_04bf [c121]8d c1 04STA a:Temp2_SomethingChangedHP [c124]@LAB_PRG15_MIRROR__c124: [c124]2e bc 04ROL a:Arg_PlayerHealthDelta_L [c127]2e bd 04ROL a:Arg_PlayerHealthDelta_U [c12a]caDEX [c12b]d0 d1BNE @LAB_PRG15_MIRROR__c0fe [c12d]68PLA [c12e]aaTAX [c12f]60RTS
;============================================================================ ; 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]a2 07LDX #$07; X = 7 (loop counter -- looping 8 times) [c132]@_clearSpritesLoop: [c132]a9 ffLDA #$ff [c134]9d cc 02STA a:CurrentSprites_Entities,X; Set sprite to 0xFF (cleared). [c137]a9 00LDA #$00 [c139]9d 44 03STA a:CurrentSprites_HP,X; Set sprite hit points to 0. [c13c]9d 4c 03STA a:CurrentSprites_HitCounter,X; Set sprite hit counter to 0. [c13f]9d d4 02STA a:CurrentSprites_Behaviors,X; Set sprite subtype to 0. [c142]caDEX; X-- [c143]10 edBPL @_clearSpritesLoop; If we're not done, loop.
; ; We're out of the loop. Reset the PPU draw offset and restore ; screen state. ;
[c145]20 6f cdJSR CurrentSprite_ResetPPUTileOffset; Reset the PPU offset. [c148]a9 ffLDA #$ff [c14a]8d c7 03STA a:IScript_PortraitID; Clear any portrait shown on screen. [c14d]a5 0bLDA PPU_Mask; Load the current screen color mode. [c14f]29 feAND #$fe; Set to color mode (clear bit 1) [c151]85 0bSTA PPU_Mask; Store the new color mode. [c153]60RTS
;============================================================================ ; 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:
; ; Set the address of the sprites-for-screen lookup table ; for the area. ;
[c154]a5 24LDA Area_CurrentArea; A = Current area index. [c156]0aASL A; Convert to a word offset for the lookup table.
; ; Set the starting address of the screen data. ;
[c157]a8TAY; Y = A [c158]b9 10 82LDA a:AREA_SPRITE_ADDRESSES,Y; Load the upper byte of the address. [c15b]85 02STA Temp_Addr_L; Store for reading. [c15d]b9 11 82LDA a:AREA_SPRITE_ADDRESSES+1,Y; Load the lower byte of the address. [c160]85 03STA Temp_Addr_U; Store it.
; ; Check the first byte of the address. If 0xFF, ; nothing will be loaded. ;
[c162]a0 01LDY #$01; Y = 1 [c164]b1 02LDA (Temp_Addr_L),Y; Load the address for the screens list. [c166]c9 ffCMP #$ff; Is it 0xFF? [c168]f0 49BEQ RETURN_C1B3; If so, there's nothing to load. Return.
; ; Set the start address for a screen's sprite and metadata ; information. ;
[c16a]a5 63LDA Area_CurrentScreen; A = Current screen index. [c16c]0aASL A; Convert to a word offset for the lookup table. [c16d]a8TAY; Y = A
; ; Read the address for the screen information. ;
[c16e]b1 02LDA (Temp_Addr_L),Y; Load the lower byte of the screen address. [c170]85 caSTA Sprites_ReadInfoAddr; Store it as the lower byte of the of the sprites read address. [c172]c8INY; Y++ [c173]b1 02LDA (Temp_Addr_L),Y; Load the upper byte. [c175]85 cbSTA 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]a0 00LDY #$00; Y = 0 [c179]@_readLoop: [c179]b1 caLDA (Sprites_ReadInfoAddr),Y; Load the next byte from the screen data. [c17b]c9 ffCMP #$ff; Is it 0xFF? [c17d]d0 10BNE @_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]c8INY; Y++ [c180]98TYA; A = Y [c181]18CLC [c182]65 caADC Sprites_ReadInfoAddr; Increment the read address by 2 bytes. [c184]85 ccSTA Screen_ExtraInfoAddr [c186]a5 cbLDA Sprites_ReadInfoAddr_U; Load the upper byte. [c188]69 00ADC #$00; Add carry, if lower byte overflowed. [c18a]85 cdSTA Screen_ExtraInfoAddr_U; Store as the new upper byte. [c18c]4c 99 c1JMP Screen_LoadSpecialEventID; Read the extra screen information.
; ; Increment by 2 (the sprite entity and value). ;
[c18f]@_prepareNextLoopIter: [c18f]c8INY; Y++ [c190]c8INY; Y++ [c191]d0 e6BNE @_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:
; ; No special event information was found for the screen. ; Clear that state (set to 0xFF). ;
[c193]a9 ffLDA #$ff [c195]8d 2e 04STA a:CurrentScreen_SpecialEventID; Clear the special event ID (0xFF). [c198]60RTS
;============================================================================ ; 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:
; ; 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]a0 00LDY #$00; Y = 0 (loop counter) [c19b]@_scanLoop: [c19b]b1 ccLDA (Screen_ExtraInfoAddr),Y; Load the next byte in the screen info data. [c19d]c9 ffCMP #$ff; Is it 0xFF? [c19f]d0 0eBNE @_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]c8INY; Y++ [c1a2]b1 ccLDA (Screen_ExtraInfoAddr),Y; Load the next byte to see what we're working with. [c1a4]c9 80CMP #$80; Is it 0x80? [c1a6]d0 ebBNE 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]c8INY; Y++ [c1a9]b1 ccLDA (Screen_ExtraInfoAddr),Y; Load the next byte containing the special event ID. [c1ab]8d 2e 04STA a:CurrentScreen_SpecialEventID; Store it while on the screen. [c1ae]60RTS [c1af]@_noFFMarker: [c1af]c8INY; Y++
; ; If we hit 256 loops, we're done. ;
[c1b0]d0 e9BNE @_scanLoop; If we haven't wrapped, then loop. [c1b2]60RTS; Else, we're done.
; ; XREFS: ; Screen_LoadAllScreenInfo ;
[c1b3]RETURN_C1B3: [c1b3]60RTS
;============================================================================ ; 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:
; ; Switch to ROM bank 11, where sprite information lives. ;
[c1b4]ad 00 01LDA a:CurrentROMBank; Load the current ROM bank. [c1b7]48PHA; Push it to the stack. [c1b8]a2 0bLDX #$0b [c1ba]20 1a ccJSR MMC1_UpdateROMBank; Switch to bank 11.
; ; Load all the sprites for this screen. ;
[c1bd]20 54 c1JSR Screen_LoadAllScreenInfo; Load the sprite information for the screen.
; ; Switch bank to our previous bank. ;
[c1c0]68PLA; Pop A (our saved bank). [c1c1]aaTAX; X = A [c1c2]20 1a ccJSR MMC1_UpdateROMBank; And switch back to it.
; ; Begin our loop for reading all sprites for the screen. ;
[c1c5]@_readSpriteLoop: [c1c5]a0 00LDY #$00; Y = 0 (info read offset)
; ; Switch back to ROM bank 11 (sprite info). ;
[c1c7]ad 00 01LDA a:CurrentROMBank; A = current ROM bank [c1ca]48PHA; Push to the stack. [c1cb]a2 0bLDX #$0b [c1cd]20 1a ccJSR MMC1_UpdateROMBank; Switch to bank 11.
; ; Read information on the next sprite on the screen. ;
[c1d0]b1 caLDA (Sprites_ReadInfoAddr),Y; Read the current byte of the screen information. [c1d2]c9 ffCMP #$ff; Is it 0xFF (our end-of-sprites list)? [c1d4]f0 2cBEQ @_restoreBankAndReturn; If it is, we're done. Restore our bank and return.
; ; Store the sprite's entity ID. ;
[c1d6]8d 8b 03STA 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]c8INY; Y++ [c1da]b1 caLDA (Sprites_ReadInfoAddr),Y; Load the next byte. [c1dc]29 f0AND #$f0; Take only the upper nibble. [c1de]8d 8a 03STA 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]b1 caLDA (Sprites_ReadInfoAddr),Y; Load the same byte. [c1e3]0aASL A; Shift the X coordinate from the lower nibble to the upper nibble. [c1e4]0aASL A [c1e5]0aASL A [c1e6]0aASL A [c1e7]8d 89 03STA a:CurrentSprite_XPos; Store as the sprite's X position.
; ; Restore our bank. ;
[c1ea]68PLA; Pull A (the saved bank) from the stack. [c1eb]aaTAX; X = A [c1ec]20 1a ccJSR 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]20 05 c2JSR Sprites_PopulateNextAvailableSprite; Add this sprite to the next available slot.
; ; Increment the read address for the next sprite in the ; screen data. ;
[c1f2]a5 caLDA Sprites_ReadInfoAddr; A = lower byte of the screen information address [c1f4]18CLC [c1f5]69 02ADC #$02; Increment by 2 (sprite entity ID and position). [c1f7]85 caSTA Sprites_ReadInfoAddr; Store as the new lower byte of the address. [c1f9]a5 cbLDA Sprites_ReadInfoAddr_U; A = upper byte of the screen information address [c1fb]69 00ADC #$00; Add carry if the lower byte overflowed. [c1fd]85 cbSTA Sprites_ReadInfoAddr_U; Store it as the new upper byte of the address. [c1ff]4c c5 c1JMP @_readSpriteLoop; Loop to read the next sprite. [c202]@_restoreBankAndReturn: [c202]4c d6 f3JMP 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:
; ; Prepare the sprite loop. ;
[c205]a2 07LDX #$07; 7 = loop counter [c207]@_loop: [c207]8e 78 03STX a:CurrentSpriteIndex; Set it [c20a]bd cc 02LDA a:CurrentSprites_Entities,X; Load the sprite entity at this index. [c20d]c9 ffCMP #$ff; Is it 0xFF (unset)? [c20f]d0 41BNE @_prepareNextIter; If not, jump to prepare for next loop.
; ; This sprite index is unset. Populate it. ;
[c211]a9 00LDA #$00; 0 = unset [c213]9d e4 02STA a:CurrentSprites_Phases,X; Clear the sprite phase. [c216]9d dc 02STA a:CurrentSprites_Flags,X; Clear the sprite flags. [c219]a9 ffLDA #$ff; 0xFF = unset [c21b]9d 34 03STA a:CurrentSprites_HitByMagicBehavior,X; Clear the hit by magic behavior.
; ; Set the sprite's starting X/Y position. ;
[c21e]ad 89 03LDA a:CurrentSprite_XPos; Load the current sprite X position to set. [c221]95 baSTA CurrentSprites_XPos_Full,X; Set for the sprite index. [c223]ad 8a 03LDA a:CurrentSprite_YPos; Load the Y position. [c226]95 c2STA CurrentSprites_YPos,X; Set for the sprite index. [c228]ad 8b 03LDA a:CurrentSprite_Entity; Load the sprite entity. [c22b]9d cc 02STA a:CurrentSprites_Entities,X; Set it. [c22e]a8TAY; Y = A (entity) [c22f]b9 df b4LDA a:SPRITE_ENTITIES_HITBOX_TYPES,Y; Load the hitbox type for the entity. [c232]9d 04 03STA a:CurrentSprites_HitBoxTypes,X; Set it. [c235]b9 a9 b5LDA a:SPRITE_ENTITIES_HP,Y; Load the HP for the entity. [c238]9d 44 03STA a:CurrentSprites_HP,X; Set it.
; ; Prepare the behavior scripts for the entity. ;
[c23b]98TYA; A = Y (entity) [c23c]0aASL A; Convert to a word boundary. [c23d]a8TAY; Y = A [c23e]b9 2d adLDA a:SPRITE_BSCRIPTS,Y; Load the lower byte of the behavior script for the entity. [c241]9d 54 03STA a:CurrentSprites_BehaviorAddrs_L,X; Set the lower byte of the address. [c244]b9 2e adLDA a:SPRITE_BSCRIPTS+1,Y; Load the upper byte. [c247]9d 5c 03STA a:CurrentSprites_BehaviorAddrs_U,X; Set the upper byte. [c24a]a9 ffLDA #$ff; A = 0xFF (unset) [c24c]9d d4 02STA a:CurrentSprites_Behaviors,X; Clear the current behavior state. [c24f]4c 5b c2JMP Sprites_LoadSpriteValue; Load the value for the sprite and return. [c252]@_prepareNextIter: [c252]ae 78 03LDX a:CurrentSpriteIndex; X = current sprite index. [c255]caDEX; X-- [c256]10 afBPL @_loop; If >= 0, loop. [c258]60RTS
;============================================================================ ; Banks storing the images for a range of sprites. ; ; XREFS: ; Sprites_StoreBankForCurrentSprite ;============================================================================
; ; XREFS: ; Sprites_StoreBankForCurrentSprite ;
[c259]SPRITE_IMAGE_BANKS: [c259]06.byte $06; [0]: Bank for sprites 0-54
; ; XREFS: ; Sprites_StoreBankForCurrentSprite ;
[c25a]SPRITE_IMAGE_BANKS_1_: [c25a]07.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]8aTXA; A = X (sprite index) [c25c]a8TAY; Y = A
; ; Save the current ROM bank and switch to bank 11. ;
[c25d]ad 00 01LDA a:CurrentROMBank; Get the current ROM bank. [c260]48PHA; Push to the stack. [c261]a2 0bLDX #$0b; X = 11 (bank) [c263]20 1a ccJSR MMC1_UpdateROMBank; Switch to bank 11.
; ; Restore our sprite index to X so it's not clobbered ; when this returns. ;
[c266]98TYA; A = Y [c267]aaTAX; X = A
; ; Load the extra information for this sprite and store it ; in the sprite list. ;
[c268]a0 00LDY #$00; Y = 0 [c26a]b1 ccLDA (Screen_ExtraInfoAddr),Y; A = Extra information for the sprite. [c26c]9d 6c 03STA a:CurrentSprites_Values,X; Store in CurrentSprites_Values. [c26f]c9 ffCMP #$ff; Is it 0xFF (unset)? [c271]f0 06BEQ @_restoreBankAndReturn; If so, we're done. Jump.
; ; Increment the position in the info data. ;
[c273]e6 ccINC Screen_ExtraInfoAddr; Increment the lower byte of the offset in the extra info data. [c275]d0 02BNE @_restoreBankAndReturn; If this didn't wrap to 0, we're done. Jump. [c277]e6 cdINC Screen_ExtraInfoAddr_U; Else, increment the upper byte as well. [c279]@_restoreBankAndReturn: [c279]4c d6 f3JMP 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]a0 00LDY #$00; Y = 0 [c27e]ad 8b 03LDA a:CurrentSprite_Entity; A = current sprite entity [c281]c9 37CMP #$37; Is it < sprite 55 (Unused Child sprite)? [c283]90 01BCC @_loadBank; If yes, then load bank from table at index 0. [c285]c8INY; Else, load bank from table at index 1.
; ; Load the image for the bank. ;
[c286]@_loadBank: [c286]b9 59 c2LDA a:SPRITE_IMAGE_BANKS,Y; Load the address for the sprite entity. [c289]8d 86 03STA a:CurrentSprite_TilesBank; Store as the bank for this sprite. [c28c]@_return: [c28c]60RTS
;============================================================================ ; 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:
; ; Save state and prepare for image loading. ;
[c28d]ad 78 03LDA a:CurrentSpriteIndex; A = current sprite index [c290]48PHA; Push it to the stack. [c291]20 6f cdJSR CurrentSprite_ResetPPUTileOffset; Reset the PPU drawing coordinates.
; ; Begin our loop for all 7 sprite slots. ;
[c294]a2 07LDX #$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]8e 78 03STX a:CurrentSpriteIndex; Store it as the currently-processed sprite index. [c299]bd cc 02LDA a:CurrentSprites_Entities,X; Load the associated sprite entity ID. [c29c]c9 ffCMP #$ff; Is it 0xFF (unset)? [c29e]f0 3cBEQ @_nextLoop; If so, jump and prepare for the next loop. [c2a0]8d 8b 03STA a:CurrentSprite_Entity; Store this as the entity at this index.
; ; Load the image for this sprite. ;
[c2a3]20 7c c2JSR Sprites_StoreBankForCurrentSprite; Store the image bank for the current sprite. [c2a6]20 b5 cdJSR 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]ad 8b 03LDA a:CurrentSprite_Entity; Load the current entity. [c2ac]c9 30CMP #$30; Is it the Pakukame boss? [c2ae]d0 19BNE @_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]ad 8b 03LDA a:CurrentSprite_Entity; Load the Pakukame entity ID [c2b3]48PHA; Push to the stack. [c2b4]a5 9aLDA CurrentSprite_StartPPUTileOffset; Load the sprite's starting PPU tile offset. [c2b6]48PHA; Push it to the stack. [c2b7]a9 09LDA #$09; A = 9 (Lilith) [c2b9]8d 8b 03STA a:CurrentSprite_Entity; Store as the new entity. [c2bc]20 7c c2JSR Sprites_StoreBankForCurrentSprite; Store the bank for Lilith. [c2bf]20 b5 cdJSR Sprites_LoadImageForCurrentSprite; Load Lilth's image from the bank. [c2c2]68PLA; Pop the PPU offset row. [c2c3]85 9aSTA CurrentSprite_StartPPUTileOffset; Store it again. [c2c5]68PLA; Pop the current sprite entity. [c2c6]8d 8b 03STA 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]ae 78 03LDX a:CurrentSpriteIndex; X = current sprite index [c2cc]a5 9aLDA CurrentSprite_StartPPUTileOffset; A = Start PPU tile offset [c2ce]9d 2c 03STA a: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]b5 c2LDA CurrentSprites_YPos,X; Load the Y position for this sprite. [c2d3]c9 f0CMP #$f0; Is it less than 0xF0? [c2d5]90 05BCC @_nextLoop; If so, loop.
; ; It's off-screen. Unset it. ;
[c2d7]a9 ffLDA #$ff; Else, it's off the screen. [c2d9]9d cc 02STA a:CurrentSprites_Entities,X; Unset the entity.
; ; Done with any loading required for the sprite. Prepare for ; the next loop, if needed. ;
[c2dc]@_nextLoop: [c2dc]ae 78 03LDX a:CurrentSpriteIndex; X = current sprite index (loop counter). [c2df]caDEX; X-- [c2e0]10 b4BPL @_loop; If X >= 0, loop.
; ; We're done. Restore the original current sprite index and ; wait until everything's drawn to the screen. ;
[c2e2]68PLA; Pull A (current sprite index) from the stack. [c2e3]8d 78 03STA a:CurrentSpriteIndex; Store that back as the current sprite index. [c2e6]4c f4 cfJMP 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:
; ; These two lines are deadcode. It might be the developers ; attempting to hard-code Deluge and Thunder during testing? ; Each gets overriden immediately. ;
[c2e9]a9 00LDA #$00; A = 0 (deadcode) [c2eb]a9 01LDA #$01; A = 1 (deadcode) [c2ed]ad b3 02LDA a:CastMagic_Type; A = Cast magic type
; ; If 0xFF, return. ;
[c2f0]30 22BMI @_return
; ; This was an active, cast magic spell. Set state. ;
[c2f2]20 15 c3JSR CastMagic_CalculateVisibility; Calculate visibility for the magic spell based on foreground elements.
; ; Manage the X position of the spell. ;
[c2f5]ad b6 02LDA a:CastMagic_XPos_Full; Load the magic's X position. [c2f8]85 27STA Arg_DrawSprite_PosX; Set as an argument for future appearance updates.
; ; Set some unused state not used in the finished game. ;
[c2fa]a5 9fLDA Screen_Maybe_ScrollXCounter [c2fc]85 2bSTA Unused_Sprite_ScrollPosX
; ; Manage the Y position of the spell. ;
[c2fe]ad b8 02LDA a:CastMagic_YPos_Full [c301]85 28STA Arg_DrawSprite_PosY; Set as an argument for future appearance updates.
; ; Set more unused state not used in the finished game. ;
[c303]a5 a2LDA Player_Something_ScrollPosY [c305]85 2aSTA Unused_Sprite_ScrollPosY
; ; Set the finish handler for this type as the next function ; to run. ;
[c307]ad b3 02LDA a:CastMagic_Type; Load the magic type. [c30a]0aASL A; Convert to a word boundary for the lookup table. [c30b]a8TAY; Y = A [c30c]b9 28 bbLDA a:CAST_MAGIC_UPDATE_FINISH_HANDLERS+1,Y; Load the lower byte of the finish handler. [c30f]48PHA; Push to the stack. [c310]b9 27 bbLDA a:CAST_MAGIC_UPDATE_FINISH_HANDLERS,Y; Load the upper byte. [c313]48PHA; Push. [c314]@_return: [c314]60RTS; 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:
; ; Set the default to visible. ;
[c315]a9 00LDA #$00 [c317]85 b8STA Temp_MovingSpriteVisibility; Set the default visibility to visible.
; ; Convert the cast magic's position to block positions. ;
[c319]ad b8 02LDA a:CastMagic_YPos_Full; Load the magic's Y position. [c31c]85 b6STA Arg_PixelPosY; Set it as an argument for getting the block property. [c31e]ad b6 02LDA a:CastMagic_XPos_Full; Load the magic's X position. [c321]18CLC [c322]69 04ADC #$04; Add 4 to that to better check block alignment. [c324]85 b5STA Arg_PixelPosX; Set it as an argument. [c326]20 6c e8JSR Area_ConvertPixelsToBlockPos; Convert to a block position. [c329]20 c3 e8JSR ScreenBuffer_LoadBlockProperty; Get the block property for this position.
; ; Check if a block overlaps the left. ;
[c32c]c9 04CMP #$04; Is this block type 4? [c32e]f0 08BEQ @_leftIsObscured; If so, the left is obscured. [c330]c9 0dCMP #$0d; Else, is this block type 13? [c332]f0 04BEQ @_leftIsObscured; If so, the left is obscured. [c334]c9 09CMP #$09; Else, is this block type 9? [c336]d0 06BNE @_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]a5 b8LDA Temp_MovingSpriteVisibility; Load the cast magic's visibility state. [c33a]09 01ORA #$01; Mark the left-hand side as obscured. [c33c]85 b8STA Temp_MovingSpriteVisibility; Store it.
; ; Now check the right-hand side of the magic. ;
[c33e]@_checkRightObscured: [c33e]a5 b5LDA Arg_PixelPosX; Load the pixel X position. [c340]18CLC [c341]69 08ADC #$08; Add 8 to it, to better check the right side. [c343]85 b5STA Arg_PixelPosX; Store as the new pixel argument. [c345]20 6c e8JSR Area_ConvertPixelsToBlockPos; Convert to a block position. [c348]20 c3 e8JSR ScreenBuffer_LoadBlockProperty; Load the block property.
; ; Check if a block overlaps the right. ;
[c34b]c9 04CMP #$04; Is this block 4? [c34d]d0 12BNE @_checkNeedToFlipBits; If not, it's not obscured. [c34f]c9 04CMP #$04; Again, is this block 4? [c351]f0 08BEQ @_rightIsObscured; If so, the right is obscured. [c353]c9 0dCMP #$0d; Else, is this block 13? [c355]f0 04BEQ @_rightIsObscured; If so, the right is obscured. [c357]c9 09CMP #$09; Else, is this block 9? [c359]d0 06BNE @_checkNeedToFlipBits; If not, it's not obscured. Move to the next step.
; ; The trailing side of the magic is obscured. ;
[c35b]@_rightIsObscured: [c35b]a5 b8LDA Temp_MovingSpriteVisibility; Load the cast magic's visibility state. [c35d]09 02ORA #$02; Mark the right-hand side as obscured. [c35f]85 b8STA 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]ae 78 03LDX a:CurrentSpriteIndex; X = current sprite index. [c364]ad b4 02LDA a:CastMagic_Flags; A = cast magic flags. [c367]29 40AND #$40; Is it facing right? [c369]f0 0dBEQ @_facingRight; If so, there's nothing to do. We're done. [c36b]a5 b8LDA Temp_MovingSpriteVisibility; A = cast magic visibility [c36d]f0 0bBEQ @_storeVisibility; If the block is fully visible, jump. [c36f]c9 03CMP #$03; Else, is it full obscured? [c371]f0 07BEQ @_storeVisibility; If yes, jump.
; ; Swap the bits to face the other direction. ;
[c373]49 03EOR #$03; Swap the visibility of leading and trailing flags. [c375]4c 7a c3JMP @_storeVisibility; Jump to store. [c378]@_facingRight: [c378]a5 b8LDA Temp_MovingSpriteVisibility; Load the previously-calculated visibility from above. [c37a]@_storeVisibility: [c37a]85 26STA MovingSpriteVisibility; Store the new visibility state. [c37c]60RTS
;============================================================================ ; 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]ac b3 02LDY a:CastMagic_Type; Y = cast magic type [c380]18CLC [c381]79 87 c3ADC a:SPRITE_MAGIC_IMAGE_ADDRS_U,Y; Add the value from the lookup table. [c384]4c 57 f0JMP 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]95.byte $95; [0]: Deluge [c388]99.byte $99; [1]: Thunder [c389]9b.byte $9b; [2]: Fire [c38a]9d.byte $9d; [3]: Death [c38b]a1.byte $a1; [4]: Tilte [c38c]a5.byte $a5; [5]: UNUSED: Deluge after first hit [c38d]99.byte $99; [6]: Thunder after first hit [c38e]9b.byte $9b; [7]: Fire after first hit [c38f]a5.byte $a5; [8]: UNUSED: Death after first hit [c390]a5.byte $a5; [9]: UNUSED [c391]a5.byte $a5; [10]: UNUSED: Hit wall effect [c392]a5.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]ad b4 02LDA a:CastMagic_Flags; Load the cast magic's flags. [c396]29 40AND #$40; Take only the facing direction bit. [c398]85 29STA CurrentSprite_FlipMask; Set as the sprite's flip mask. [c39a]60RTS
;============================================================================ ; 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]20 93 c3JSR CastMagic_UpdateSpriteDirection; Set the sprite's direction. [c39e]a5 1aLDA InterruptCounter; Load the interrupt counter. [c3a0]4aLSR A; Shift the interrupt counter to generate an appearance index. [c3a1]4aLSR A [c3a2]29 03AND #$03; Keep the 2 right-most bits (effectively, bits 3 and 4 of the interrupt counter). [c3a4]4c 7d c3JMP 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]20 93 c3JSR CastMagic_UpdateSpriteDirection; Set the sprite's direction. [c3aa]a5 1aLDA InterruptCounter; Load the interrupt counter. [c3ac]4aLSR A; Shift the interrupt counter to generate a value to check against. [c3ad]4aLSR A [c3ae]b0 05BCS @_return; If this should not update the appearance, return. [c3b0]29 01AND #$01; Keep the right-most bit (effectively, bit 2 of the interrupt counter). [c3b2]4c 7d c3JMP CastMagic_SetAppearance; Set that as the appearance index. [c3b5]@_return: [c3b5]60RTS
;============================================================================ ; 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]a5 1aLDA InterruptCounter; Load the interrupt counter. [c3b8]29 02AND #$02; Is bit 1 set? [c3ba]d0 01BNE @_updateFire; If so, jump to update. [c3bc]60RTS [c3bd]@_updateFire: [c3bd]20 93 c3JSR CastMagic_UpdateSpriteDirection; Set the sprite's direction. [c3c0]a5 1aLDA InterruptCounter; Load the interrupt counter. [c3c2]4aLSR A; Shift the interrupt counter to generate an appearance index. [c3c3]4aLSR A [c3c4]29 01AND #$01; Keep the right-most bit (effectively, bit 2 of the interrupt counter). [c3c6]4c 7d c3JMP 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]20 93 c3JSR CastMagic_UpdateSpriteDirection; Update the sprite's direction. [c3cc]a5 1aLDA InterruptCounter; Load the interrupt counter. [c3ce]4aLSR A; Shift the interrupt counter to generate an appearance index. [c3cf]4aLSR A [c3d0]4aLSR A [c3d1]29 03AND #$03; Keep the 2 right-most bits (effectively, bits 3 and 4 of the interrupt counter). [c3d3]4c 7d c3JMP 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]20 93 c3JSR CastMagic_UpdateSpriteDirection; Update the sprite's direction.
; ; Check if the first bit of the magic's phase is 0 or 1. ;
[c3d9]ad ba 02LDA a:CastMagic_Phase; Load the magic's phase. [c3dc]4aLSR A; Shift bit 0 into Carry. [c3dd]b0 0dBCS @_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]a0 00LDY #$00; Y = 0 [c3e1]a5 1aLDA InterruptCounter; A = interrupt counter [c3e3]29 08AND #$08; Keep bit 3. [c3e5]f0 01BEQ @_setAppearance1; If 0, jump to use 0 as the appearance value. [c3e7]c8INY; Y++ (appearance value == 1) [c3e8]@_setAppearance1: [c3e8]98TYA; A = 0 or 1 (appearance value) [c3e9]4c 7d c3JMP 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]a5 1aLDA InterruptCounter; Load the interrupt counter. [c3ee]4aLSR A; Divide by 2. [c3ef]4aLSR A; And place in Carry. [c3f0]90 01BCC @_setAppearance2; If 0, jump to update appearance. [c3f2]60RTS
; ; 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]29 01AND #$01; Keep bit 0. [c3f5]18CLC [c3f6]69 02ADC #$02; Add 2. [c3f8]4c 7d c3JMP 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]20 93 c3JSR CastMagic_UpdateSpriteDirection; Update the sprite direction. [c3fe]a9 00LDA #$00 [c400]4c 7d c3JMP 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:
; ; Set the default flip mask of the sprite to 0 (face right). ;
[c403]a9 00LDA #$00 [c405]85 29STA 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]ad bb 02LDA a:CastMagic_Unused_HitWallDeltaPosY; A = Delta Y position. [c40a]0aASL A; A *= 2 [c40b]85 00STA Temp_00; Save it temporarily.
; ; Position and draw the top explosion sprite. ; ; This will be MagicY - (DeltaY * 2). ;
[c40d]ad b8 02LDA a:CastMagic_YPos_Full; A = Magic Y position. [c410]38SEC [c411]e5 00SBC Temp_00; A -= Effect delta Y position. [c413]85 28STA Arg_DrawSprite_PosY; Set as the new sprite's Y position. [c415]a9 02LDA #$02 [c417]20 7d c3JSR 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]ad bb 02LDA a:CastMagic_Unused_HitWallDeltaPosY; A = Delta Y position. [c41d]0aASL A; A *= 2 [c41e]18CLC [c41f]69 10ADC #$10; A += 16 [c421]18CLC [c422]6d b8 02ADC a:CastMagic_YPos_Full; A += Magic Y position. [c425]85 28STA Arg_DrawSprite_PosY; Save it as the new sprite's Y position. [c427]a9 03LDA #$03 [c429]4c 7d c3JMP CastMagic_SetAppearance; Draw the bottom explosion sprite at that position.
;============================================================================ ; Draw the Tilte explosion sprites after hitting an enemy. ; ; There are 4 sprites for the explosion, each moving in a ; diagonal direction outward from the impact point. ; ; This is computed by looping through each of the sprites ; and then calculating new X and Y positions for each, plus ; the appropriate tile appearance offset. XOR masks are used ; to turn the magic counter into a positive or negative ; number (depending on which piece of the explosion this is) ; in order to move that part in the correct direction. ; ; INPUTS: ; CastMagic_Counter: ; The lifetime counter for the explosion. ; ; CastMagic_XPos_Full: ; The center X position for the explosion. ; ; CastMagic_YPos_Full: ; The center Y position for the explosion. ; ; MAGICHITHANDLER_TILTE_X_XORS: ; The XOR table for generating positive/negative X ; deltas for each part of the explosion. ; ; MAGICHITHANDLER_TILTE_Y_XORS: ; The XOR table for generating positive/negative Y ; deltas for each part of the explosion. ; ; MAGICHITHANDLER_TILTE_APPEARANCE_OFFSETS: ; The appearance offsets for each part of the ; explosion. ; ; OUTPUTS: ; Arg_DrawSprite_PosX: ; Arg_DrawSprite_PosY: ; Temp_00: ; Clobbered. ; ; CALLS: ; CastMagic_SetAppearance ; ; XREFS: ; CAST_MAGIC_UPDATE_FINISH_HANDLERS [$PRG14::bb3d] ;============================================================================
[c42c]CastMagic_FinishHandler_TilteAfterFirstHit:
; ; Clear the flip mask for the sprite. ;
[c42c]a9 00LDA #$00; A = 0. [c42e]85 29STA CurrentSprite_FlipMask; Set as the flip mask (cleared).
; ; Prepare our loop. This will loop 4 times (3 -> 0, inclusive), ; calculating X and Y positions and an appearance offset for the ; sprite based on CastMagic_Counter (which will go from 1 ; through 15). ; ; The loop counter corresponds to the corner of the explosion ; being processed: ; ; 0 = Top-left ; 1 = Top-right ; 2 = Bottom-left ; 3 = Bottom-right ;
[c430]a2 03LDX #$03; X = 3 (loop counter).
; ; Begin calculating a distance from the center based on the ; counter value. ; ; This will multiply the counter by 2 and then calculate a ; positive or negative value for X based on which corner ; of the explosion is being processed. ;
[c432]@_loop: [c432]ad b9 02LDA a:CastMagic_Counter; Load the magic counter. [c435]0aASL A; Multiply by 2. [c436]48PHA; Push the result to the stack.
; ; Calculate the X position of the sprite. ;
[c437]85 00STA Temp_00; And store it for temporary usage. [c439]5d 6c c4EOR a:MAGICHITHANDLER_TILTE_X_XORS,X; XOR with the index in our lookup table based on the loop counter. [c43c]10 04BPL @_calcFinalXPosition; If the result is a positive value, jump. [c43e]85 00STA Temp_00; Else, store our XOR'd result in our temp variable. [c440]e6 00INC Temp_00; And increment it.
; ; Add our calculated position to the current X position, ; producing a final result. This will be set as the ; destination for drawing. ;
[c442]@_calcFinalXPosition: [c442]ad b6 02LDA a:CastMagic_XPos_Full; Load the block X position of the magic sprite. [c445]18CLC [c446]65 00ADC Temp_00; Add our temp value. [c448]85 27STA Arg_DrawSprite_PosX; Set as the X position argument.
; ; Calculate the Y position of the sprite. ;
[c44a]68PLA; Pop the previously-calculated magic counter * 2 result. [c44b]85 00STA Temp_00; Store it back in our temp variable. [c44d]5d 70 c4EOR a:MAGICHITHANDLER_TILTE_Y_XORS,X; XOR with the index in our lookup table based on the loop counter. [c450]10 04BPL @_calcFinalYPosition; If the result is a positive value, jump. [c452]85 00STA Temp_00; Else, store our XOR'd result in our temp variable. [c454]e6 00INC Temp_00; And increment it. [c456]@_calcFinalYPosition: [c456]ad b8 02LDA a:CastMagic_YPos_Full; Load the Y position of the magic sprite. [c459]18CLC [c45a]65 00ADC Temp_00; Add the temp variable. [c45c]85 28STA Arg_DrawSprite_PosY; Store as the Y position argument.
; ; Calculate the sprite tile offset. ;
[c45e]8aTXA; A = loop counter value. [c45f]48PHA; Push it to the stack. [c460]bd 74 c4LDA a:MAGICHITHANDLER_TILTE_APPEARANCE_OFFSETS,X; Load the offset based on the counter.
; ; Draw the tile for this part of the explosion. ;
[c463]20 7d c3JSR CastMagic_SetAppearance; Set the appearance of the sprite. [c466]68PLA; Pop our loop counter. [c467]aaTAX; Assign back to X. [c468]caDEX; Decrement the loop counter. [c469]10 c7BPL @_loop; If >= 0, loop. [c46b]60RTS
;============================================================================ ; Table of XORs for creating X deltas for parts of the Tilte explosion. ; ; XREFS: ; CastMagic_FinishHandler_TilteAfterFirstHit ;============================================================================
; ; XREFS: ; CastMagic_FinishHandler_TilteAfterFirstHit ;
[c46c]MAGICHITHANDLER_TILTE_X_XORS: [c46c]ff.byte $ff; [0]: Top-left [c46d]00.byte $00; [1]: Top-right [c46e]ff.byte $ff; [2]: Bottom-left [c46f]00.byte $00; [3]: Bottom-right
;============================================================================ ; Table of XORs for creating X deltas for parts of the Tilte explosion. ; ; XREFS: ; CastMagic_FinishHandler_TilteAfterFirstHit ;============================================================================
; ; XREFS: ; CastMagic_FinishHandler_TilteAfterFirstHit ;
[c470]MAGICHITHANDLER_TILTE_Y_XORS: [c470]ff.byte $ff; [0]: Top-left [c471]ff.byte $ff; [1]: Top-right [c472]00.byte $00; [2]: Bottom-left [c473]00.byte $00; [3]: Bottom-right
;============================================================================ ; Table of appearance offsets for parts of the Tilte explosion. ; ; XREFS: ; CastMagic_FinishHandler_TilteAfterFirstHit ;============================================================================
; ; XREFS: ; CastMagic_FinishHandler_TilteAfterFirstHit ;
[c474]MAGICHITHANDLER_TILTE_APPEARANCE_OFFSETS: [c474]00.byte $00; [0]: Top-left [c475]02.byte $02; [1]: Top-right [c476]01.byte $01; [2]: Bottom-left [c477]03.byte $03; [3]: Bottom-right
;============================================================================ ; 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]a5 a4LDA 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]29 05AND #$05 [c47c]d0 1eBNE GameLoop_UseItem_Return
; ; Check if the Down button has been pressed. If not, return. ;
[c47e]a5 16LDA Joy1_ButtonMask; Load the button mask. [c480]29 04AND #$04; Check if the Down button is pressed. [c482]f0 18BEQ GameLoop_UseItem_Return; If not, return.
; ; Check if the B button has been pressed. If not, return. ;
[c484]ad 19 00LDA a:Joy1_ChangedButtonMask; Load the changed button mask. [c487]29 40AND #$40; Check if the B button is pressed. [c489]f0 11BEQ 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]ad c1 03LDA a:SelectedItem; Load the selected item. [c48e]0aASL A; Multiply by 2 to get a word boundary for address lookup. [c48f]a8TAY; Y = A [c490]c0 22CPY #$22; Is it a valid item? [c492]b0 08BCS GameLoop_UseItem_Return; If not, return.
; ; Look up this item in the jump table and run it. ;
[c494]b9 9e c4LDA a:USE_ITEM_JUMP_TABLE+1,Y; Load the lower byte of the item handler address. [c497]48PHA; Push it. [c498]b9 9d c4LDA a:USE_ITEM_JUMP_TABLE,Y; Load the upper byte. [c49b]48PHA; 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]60RTS
;============================================================================ ; Handler functions for using specific items. ; ; XREFS: ; GameLoop_CheckUseCurrentItem ;============================================================================
; ; XREFS: ; GameLoop_CheckUseCurrentItem ;
[c49d]USE_ITEM_JUMP_TABLE: [c49d]9b c4pointer GameLoop_UseItem_Return-1; [0]: Ring of Elf [c49f]9b c4pointer GameLoop_UseItem_Return-1; [1]: Ring of Ruby [c4a1]9b c4pointer GameLoop_UseItem_Return-1; [2]: Ring of Dworf [c4a3]9b c4pointer GameLoop_UseItem_Return-1; [3]: Demon's Ring [c4a5]9b c4pointer GameLoop_UseItem_Return-1; [4]: "A" Key [c4a7]9b c4pointer GameLoop_UseItem_Return-1; [5]: "K" Key [c4a9]9b c4pointer GameLoop_UseItem_Return-1; [6]: "Q" Key [c4ab]9b c4pointer GameLoop_UseItem_Return-1; [7]: "J" Key [c4ad]9b c4pointer GameLoop_UseItem_Return-1; [8]: "Jo" Key [c4af]15 c6pointer Player_UseMattock-1; [9]: Mattock [c4b1]9b c4pointer GameLoop_UseItem_Return-1; [10]: Magical Rod [c4b3]9b c4pointer GameLoop_UseItem_Return-1; [11]: Crystal [c4b5]9b c4pointer GameLoop_UseItem_Return-1; [12]: Lamp [c4b7]c7 c5pointer Player_UseHourGlass-1; [13]: Hour Glass [c4b9]9b c4pointer GameLoop_UseItem_Return-1; [14]: Book [c4bb]78 c5pointer Player_UseWingBoots-1; [15]: Wing Boots [c4bd]32 c5pointer 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]48PHA; Push A to the stack. [c4c0]a9 ffLDA #$ff [c4c2]8d c1 03STA a:SelectedItem; Set selected item to 0xFF (unset). [c4c5]20 dc c8JSR UI_ClearSelectedItemPic; Update the UI for the cleared image. [c4c8]68PLA; Pop A from the stack. [c4c9]60RTS
;============================================================================ ; 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]ad 2c 04LDA a:SpecialItems; Load the special items. [c4cd]29 f7AND #$f7; Clear the elixir bit. [c4cf]8d 2c 04STA a:SpecialItems; Store it.
; ; Run the "Use Elixir" IScript. ; ; IScript 0x85 via jump to IScripts_Begin. ;
[c4d2]a9 85LDA #$85; 0x85 == Use Elixir. [c4d4]20 59 f8JSR MMC1_LoadBankAndJump; Run IScript: [c4d7]0c.byte BANK_12_LOGIC; Bank = 12 [c4d8]41 82pointer 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:
; ; Progressively fill the HP, 4 units at a time. ; ; Start by playing the sound. ;
[c4da]a9 13LDA #$13; Set sound 0x13 (fill HP/MP bar). [c4dc]20 e4 d0JSR Sound_PlayEffect; Play it.
; ; Add the HP. ;
[c4df]a9 04LDA #$04; Set the HP to 4 units. [c4e1]20 7b c0JSR Player_AddHP; Add it to the player. [c4e4]20 46 dcJSR 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]20 25 caJSR WaitForNextFrame; Wait a bit. [c4ea]20 a8 cbJSR Sprites_FlipRanges [c4ed]20 25 caJSR WaitForNextFrame [c4f0]20 a8 cbJSR Sprites_FlipRanges [c4f3]20 25 caJSR WaitForNextFrame [c4f6]20 a8 cbJSR Sprites_FlipRanges [c4f9]20 25 caJSR WaitForNextFrame [c4fc]20 a8 cbJSR Sprites_FlipRanges
; ; Check if we're done filling up HP. ;
[c4ff]ad 31 04LDA a:Player_HP_U; Load the player's HP. [c502]c9 50CMP #$50; Is it below the cap of 80HP? [c504]90 d4BCC Player_FillHPAndMP; If so, loop.
; ; Progressively fill the MP, 4 units at a time. ; ; Start by playing the sound. ;
[c506]@_fillMPLoop: [c506]a9 13LDA #$13; Set sound 0x13 (fill HP/MP bar). [c508]20 e4 d0JSR Sound_PlayEffect; Play it.
; ; Add the MP. ;
[c50b]a9 04LDA #$04; Set the MP to 4 units. [c50d]20 d9 c0JSR Player_AddMP; Add it to the player. [c510]20 46 dcJSR 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]20 25 caJSR WaitForNextFrame; Wait a bit. [c516]20 a8 cbJSR Sprites_FlipRanges [c519]20 25 caJSR WaitForNextFrame [c51c]20 a8 cbJSR Sprites_FlipRanges [c51f]20 25 caJSR WaitForNextFrame [c522]20 a8 cbJSR Sprites_FlipRanges [c525]20 25 caJSR WaitForNextFrame [c528]20 a8 cbJSR Sprites_FlipRanges
; ; Check if we're done filling up MP. ;
[c52b]ad 9a 03LDA a:Player_MP; Load the player's MP. [c52e]c9 50CMP #$50; Is it below the cap of 80MP? [c530]90 d4BCC @_fillMPLoop; If so, loop. [c532]60RTS
;============================================================================ ; 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:
; ; Run the "Used Red Potion" IScript. ; ; IScript 0x80 via jump to IScripts_Begin. ;
[c533]a9 80LDA #$80; 0x80 == Use Red Potion IScript. [c535]20 59 f8JSR MMC1_LoadBankAndJump; Run IScript: [c538]0c.byte BANK_12_LOGIC; Bank = 12 [c539]41 82pointer IScripts_Begin-1; Address = IScripts_Begin [c53b]@_afterFarJump: [c53b]20 bf c4JSR Player_ClearSelectedItem; Clear the selected item.
; ; Play the initial sound effect. ;
[c53e]a9 1aLDA #$1a; 0x1A == Initial item used sound effect. [c540]20 e4 d0JSR Sound_PlayEffect; Play it.
; ; Set up the HP fill loop. This will fill up to 80. ;
[c543]a2 50LDX #$50; X = Maximum HP / our loop counter. [c545]@_loop: [c545]8aTXA; A = X [c546]48PHA; Push it to the stack.
; ; Play the unit fill sound effect. ;
[c547]a9 13LDA #$13; 0x13 = Unit of HP fill sound effect. [c549]20 e4 d0JSR Sound_PlayEffect; Play the sound.
; ; Add 2HP. ;
[c54c]a9 02LDA #$02 [c54e]20 7b c0JSR Player_AddHP; Add the 2HP to the player. [c551]20 46 dcJSR Game_DrawScreenInFrozenState
; ; Wait 4 frames worth of updates before filling the next 2HP. ;
[c554]20 25 caJSR WaitForNextFrame [c557]20 a8 cbJSR Sprites_FlipRanges [c55a]20 25 caJSR WaitForNextFrame [c55d]20 a8 cbJSR Sprites_FlipRanges [c560]20 25 caJSR WaitForNextFrame [c563]20 a8 cbJSR Sprites_FlipRanges [c566]20 25 caJSR WaitForNextFrame [c569]20 a8 cbJSR Sprites_FlipRanges
; ; Check if we're done. ;
[c56c]68PLA; Pop the max HP. [c56d]aaTAX; X = A [c56e]ad 31 04LDA a:Player_HP_U; Load the player's current HP. [c571]c9 50CMP #$50; Is the player at max HP? [c573]b0 03BCS @_return; If so, return. [c575]caDEX; X-- [c576]d0 cdBNE @_loop; If X > 0, loop. [c578]@_return: [c578]60RTS
;============================================================================ ; 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:
; ; Run the "Wing Boots Used" IScript. ; ; IScript 0x83 via jump to IScripts_Begin. ;
[c579]a9 83LDA #$83; 0x83 == Wing Boots used IScript. [c57b]20 59 f8JSR MMC1_LoadBankAndJump; Run IScript: [c57e]0c.byte BANK_12_LOGIC; Bank = 12 [c57f]41 82pointer IScripts_Begin-1; Address = IScripts_Begin
; ; Remove the item. ;
[c581]@_afterFarJump: [c581]20 bf c4JSR Player_ClearSelectedItem; Clear the selected item.
; ; Play a sound effect for the item usage. ;
[c584]a9 1aLDA #$1a; Set the sound to play to 0x1A (special item sound). [c586]20 e4 d0JSR Sound_PlayEffect; Play the sound.
; ; Calculate the index into the duration table based on ; the player's title. ;
[c589]ad 37 04LDA a:PlayerTitle; Load the player's title. [c58c]4aLSR A; Divide by 4. [c58d]4aLSR A [c58e]aaTAX; X = A (index) [c58f]bd 99 c5LDA a:TITLE_TO_WINGBOOTS_DURATION,X; Look up in the duration table. [c592]8d 29 04STA a:DurationWingBoots; Set that as the starting duration. [c595]20 90 f9JSR UI_DrawTimeValue; Draw the time on the HUD. [c598]60RTS
;============================================================================ ; 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]28.byte $28; [0]: 40 seconds: Novice, Aspirant, Battler, Fighter [c59a]1e.byte $1e; [1]: 30 seconds: Adept, Chevalier, Veteran, Warrior [c59b]14.byte $14; [2]: 20 seconds: Swordman, Hero, Soldier, Myrmidon [c59c]0a.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:
; ; Check if there's any time remaining on the Wing Boots. ;
[c59d]ad 29 04LDA a:DurationWingBoots; Load the remaining time on the Wing Boots. [c5a0]30 25BMI @_return; If the counter is unset (0xFF), return.
; ; Update the player's status to mark Wing Boots as on. ;
[c5a2]a5 a5LDA Player_StatusFlag; Load the player's status flags. [c5a4]09 80ORA #$80; Enable the Wing Boots bit. [c5a6]85 a5STA Player_StatusFlag; Store it.
; ; Update at most once every second. ;
[c5a8]a5 1aLDA InterruptCounter; Load the interrupt counter. [c5aa]29 3fAND #$3f; Check if we're at the 1 second mark. [c5ac]d0 19BNE @_return; If not, return.
; ; Reduce time on the Wing Boots by a second. ;
[c5ae]ce 29 04DEC a:DurationWingBoots; Decrement the remaining time for the Wing Boots. [c5b1]ad 29 04LDA a:DurationWingBoots; Load that duration [c5b4]30 03BMI @_wingBootsDone; If it wrapped from 0 to 0xFF, it's used up. Jump. [c5b6]4c 90 f9JMP UI_DrawTimeValue; Else, draw the time remaining.
; ; The Wing Boots ran out. Remove the flag from the player's ; status. ;
[c5b9]@_wingBootsDone: [c5b9]a5 a5LDA Player_StatusFlag; Load the player's status flags. [c5bb]29 7fAND #$7f; Clear the Wing Boots flag. [c5bd]85 a5STA Player_StatusFlag; Store it.
; ; Run "Wing Boots are gone" IScript. ; ; IScript 0x96 via jump to IScripts_Begin. ;
[c5bf]a9 96LDA #$96; 0x96 == Wing Boots are gone IScript. [c5c1]20 59 f8JSR MMC1_LoadBankAndJump; Run the IScript: [c5c4]0c.byte BANK_12_LOGIC; Bank = 12 [c5c5]41 82pointer IScripts_Begin-1; Address = IScripts_Begin
; ; It will resume here. ;
[c5c7]@_return: [c5c7]60RTS
;============================================================================ ; 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:
; ; Run the "Hour Glass Used" IScript. ; ; IScript 0x82 via jump to IScripts_Begin. ;
[c5c8]a9 82LDA #$82; Set the IScript to run to 0x82. [c5ca]20 59 f8JSR MMC1_LoadBankAndJump; Run IScript: [c5cd]0c.byte BANK_12_LOGIC; Bank = 12 [c5ce]41 82pointer IScripts_Begin-1; Address = IScripts_Begin
; ; Remove the item. ;
[c5d0]@_afterFarJump: [c5d0]20 bf c4JSR Player_ClearSelectedItem; Clear the selected item.
; ; Play a sound effect for the item usage. ;
[c5d3]a9 1aLDA #$1a; Set the sound to play to 0x1A (special item sound). [c5d5]20 e4 d0JSR Sound_PlayEffect; Play the sound.
; ; Reduce the player's health by half. This is the cost of ; the Hour Glass. ;
[c5d8]4e 31 04LSR a:Player_HP_U; Divide the upper value of the player's HP by half. [c5db]6e 32 04ROR a:Player_HP_L; Divide the lower byte of the player's HP by half, and add carry from the upper. [c5de]20 bd c0JSR UI_DrawPlayerHP; Draw the new HP.
; ; Set the duration of the Hour Glass to 15 seconds. ;
[c5e1]a9 0fLDA #$0f; Set the duration to 15 seconds. [c5e3]8d 2a 04STA a:DurationHourGlass
; ; Change the music. ;
[c5e6]a9 0bLDA #$0b; Set the new music to play. [c5e8]85 faSTA Music_Current; And play it. [c5ea]60RTS
;============================================================================ ; 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:
; ; Check if there's any time remaining on the Hour Glass. ;
[c5eb]ad 2a 04LDA a:DurationHourGlass; Load the remaining time on the Hour Glass. [c5ee]30 18BMI @_return; If the counter is unset (0xFF), return.
; ; Update at most once every second. ;
[c5f0]a5 1aLDA InterruptCounter; Load the interrupt counter. [c5f2]29 3fAND #$3f; Check if we're at the 1 second mark. [c5f4]d0 12BNE @_return; If not, return.
; ; Reduce time on the Hour Glass by a second. ;
[c5f6]ce 2a 04DEC a:DurationHourGlass; Decrement the remaining time for the Hour Glass. [c5f9]10 0dBPL @_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]a9 97LDA #$97; 0x97 == Hour Glass is gone IScript. [c5fd]20 59 f8JSR MMC1_LoadBankAndJump; Run the IScript: [c600]0c.byte BANK_12_LOGIC; Bank = 12 [c601]41 82pointer IScripts_Begin-1; Address = IScripts_Begin
; ; Load the area's default music. ;
[c603]@_afterFarJump: [c603]ad d1 03LDA a:Areas_DefaultMusic; Load the music for the area. [c606]85 faSTA Music_Current; Store it. [c608]@_return: [c608]60RTS
;============================================================================ ; 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]a9 64LDA #$64; A = 100 (lower byte of XP) [c60b]8d ec 00STA a:Temp_Int24; Store it as an argument. [c60e]a9 00LDA #$00; A = 0 (upper byte of XP). [c610]8d ed 00STA a:Temp_Int24_M; Store it as an argument. [c613]4c 57 f9JMP 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:
; ; 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]a0 00LDY #$00; Lookup table index = 0 (default) [c618]a5 a4LDA Player_Flags; Load the player's flags. [c61a]29 40AND #$40; Is the player facing right? [c61c]f0 01BEQ @LAB_PRG15_MIRROR__c61f; If not, jump.
; ; The player is facing right. Set our offset into the block ; distances table to 1. ;
[c61e]c8INY; Lookup table index = 1 [c61f]@LAB_PRG15_MIRROR__c61f: [c61f]a5 9eLDA Player_PosX_Block; Get the player's X position. [c621]18CLC [c622]79 8d c6ADC a:USE_MATTOCK_BLOCK_DISTANCES,Y; Add the block distance for the facing direction. [c625]c9 f0CMP #$f0; Is the player too near to the right edge of the screen? [c627]b0 63BCS @_return; If so, return.
; ; Begin fetching the block at this offset. ; ; Same logic as above for block calculation. ;
[c629]85 b5STA Arg_PixelPosX; Store the calculated position as the X argument. [c62b]a5 a1LDA Player_PosY; Load the player's current Y position. [c62d]85 b6STA Arg_PixelPosY; Store that as the Y argument. [c62f]20 6c e8JSR 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]a5 24LDA Area_CurrentArea; Load the current area. [c634]0aASL A; Convert to a 4 byte index. [c635]0aASL A [c636]a8TAY; Y = index [c637]bd 00 06LDA a:ScreenBuffer,X; Load the value from the screen buffer. [c63a]d9 8f c6CMP a:USE_MATTOCK_BLOCK_TRANSITIONS,Y; Is this block already cleared by the Mattock? [c63d]f0 01BEQ @_useMattock; If not, use the Mattock to clear them. [c63f]60RTS; Else, return.
; ; The Mattock will be used. Store the offsets into the ; screen buffer and array on the stack for later. ;
[c640]@_useMattock: [c640]8aTXA; A = X [c641]48PHA; Push to the stack. [c642]98TYA; A = Y [c643]48PHA; Push to the stack.
; ; Run the "Used Mattock" IScript. ; ; IScript 0x81 via jump to IScripts_Begin. ;
[c644]a9 81LDA #$81 [c646]20 59 f8JSR MMC1_LoadBankAndJump; Run IScript: [c649]0c.byte BANK_12_LOGIC; Bank = 12 [c64a]41 82pointer IScripts_Begin-1; Address = IScripts_Begin
; ; Remove the Mattock. ;
[c64c]@_afterFarJump: [c64c]20 bf c4JSR Player_ClearSelectedItem; Clear the selected item.
; ; Play the sound effect for the Mattock usage. ;
[c64f]a9 1aLDA #$1a; 0x1A == Special item sound effect. [c651]20 e4 d0JSR 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]68PLA; Pull A from the stack. [c655]a8TAY; Y = A [c656]68PLA; Pull A from the stack. [c657]aaTAX; X = A
; ; Only update every 4 frames, to get a quick animation effect. ;
[c658]@_loop: [c658]20 25 caJSR WaitForNextFrame; Wait for 4 frames. [c65b]20 25 caJSR WaitForNextFrame [c65e]20 25 caJSR WaitForNextFrame [c661]20 25 caJSR WaitForNextFrame
; ; Update the screen to place the next block in the sequence. ;
[c664]98TYA; A = Y [c665]48PHA; Push to the stack. [c666]8aTXA; A = X [c667]48PHA; Push to the stack. [c668]b9 8f c6LDA a:USE_MATTOCK_BLOCK_TRANSITIONS,Y; Load the block for this position. [c66b]9d 00 06STA a:ScreenBuffer,X; And store it in the screen buffer. [c66e]20 c5 d7JSR Area_SetBlocks; Update the level data.
; ; Update to the next block. ;
[c671]8aTXA; A = X [c672]18CLC [c673]69 10ADC #$10; A += 32 [c675]aaTAX; X = A [c676]ad c9 03LDA a:Arg_BlockAttributesIndex; Load a value. [c679]9d 00 06STA a:ScreenBuffer,X; And write it to the screen buffer. [c67c]20 c5 d7JSR Area_SetBlocks; Upate the level data.
; ; Prepare for the next loop iteration. ;
[c67f]68PLA; Pull A from the stack. [c680]aaTAX; X = A [c681]68PLA; Pull A from the stack. [c682]a8TAY; Y = A [c683]c8INY; Y++ [c684]98TYA; A = Y [c685]29 03AND #$03; Have we finished updating the blocks? [c687]f0 03BEQ @_return; If so, return. [c689]4c 58 c6JMP @_loop; Else, loop. [c68c]@_return: [c68c]60RTS
;============================================================================ ; 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]ff.byte $ff; [0]:
; ; XREFS: ; Player_UseMattock ;
[c68e]USE_MATTOCK_BLOCK_DISTANCES_1_: [c68e]10.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]00.byte $00; [0]: Eolis [c690]00.byte $00; [1]: [c691]00.byte $00; [2]: [c692]00.byte $00; [3]: [c693]63.byte $63; [4]: Apolune [c694]85.byte $85; [5]: [c695]86.byte $86; [6]: [c696]42.byte $42; [7]: [c697]00.byte $00; [8]: Forepaw [c698]00.byte $00; [9]: [c699]00.byte $00; [10]: [c69a]00.byte $00; [11]: [c69b]00.byte $00; [12]: Mascon [c69c]00.byte $00; [13]: [c69d]00.byte $00; [14]: [c69e]00.byte $00; [15]: [c69f]00.byte $00; [16]: Victim [c6a0]00.byte $00; [17]: [c6a1]00.byte $00; [18]: [c6a2]00.byte $00; [19]: [c6a3]00.byte $00; [20]: Conflate [c6a4]00.byte $00; [21]: [c6a5]00.byte $00; [22]: [c6a6]00.byte $00; [23]: [c6a7]00.byte $00; [24]: Daybreak [c6a8]00.byte $00; [25]: [c6a9]00.byte $00; [26]: [c6aa]00.byte $00; [27]: [c6ab]00.byte $00; [28]: Evil Fortress [c6ac]00.byte $00; [29]: [c6ad]00.byte $00; [30]: [c6ae]00.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]a9 ffLDA #$ff [c6b1]8d 28 04STA a:DurationGlove; Clear the Glove duration. [c6b4]8d 27 04STA a:DurationOintment; Clear the Ointment duration. [c6b7]8d 29 04STA a:DurationWingBoots; Clear the Wing Boots duration. [c6ba]8d 2a 04STA a:DurationHourGlass; Clear the Hour Glass duration. [c6bd]60RTS
;============================================================================ ; 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:
; ; Run the "Got Hour Glass" IScript. ; ; IScript 0x8A via jump to IScripts_Begin. ;
[c6be]a9 8aLDA #$8a; 0x8A == Hour Glass picked up IScript. [c6c0]20 59 f8JSR MMC1_LoadBankAndJump; Run IScript: [c6c3]0c.byte BANK_12_LOGIC; Bank = 12 [c6c4]41 82pointer IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c6c6]@_afterFarJump: [c6c6]a9 08LDA #$08; 0x08 == Item picked up sound. [c6c8]20 e4 d0JSR Sound_PlayEffect; Play it.
; ; Pick up the item. ;
[c6cb]a9 0dLDA #$0d; 0xD == Hour Glass. [c6cd]4c cd c8JMP 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]ad 2d 04LDA a:Quests; Load the completed quests. [c6d3]09 08ORA #$08; Mark this quest as completed. [c6d5]8d 2d 04STA 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:
; ; Run the "Got Wing Boots" IScript. ; ; IScript 0x89 via jump to IScripts_Begin. ;
[c6d8]a9 89LDA #$89; 0x89 == Wing boots picked up IScript. [c6da]20 59 f8JSR MMC1_LoadBankAndJump; Run IScript: [c6dd]0c.byte BANK_12_LOGIC; Bank = 12 [c6de]41 82pointer IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c6e0]@_afterFarJump: [c6e0]a9 08LDA #$08; 0x8 == Item pick-up sound. [c6e2]20 e4 d0JSR Sound_PlayEffect; Play it.
; ; Pick up the item. ;
[c6e5]a9 0fLDA #$0f; 0xF == Wing Boots. [c6e7]4c cd c8JMP 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:
; ; Run the "Got Battle Suit" IScript. ; ; IScript 0x8B via jump to IScripts_Begin. ;
[c6ea]a9 8bLDA #$8b; 0x8B == Battle Suit picked up IScript. [c6ec]20 59 f8JSR MMC1_LoadBankAndJump; Run IScript: [c6ef]0c.byte BANK_12_LOGIC; Bank = 12 [c6f0]41 82pointer IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c6f2]@_afterFarJump: [c6f2]a9 08LDA #$08; 0x08 == Item picked up sound. [c6f4]20 e4 d0JSR 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]ae c2 03LDX a:NumberOfWeapons; X = Number of weapons. [c6fa]e0 04CPX #$04; If < 4 [c6fc]90 02BCC @_addItem; Then, add it to the latest slot. [c6fe]a2 03LDX #$03; Else, cap X to 3.
; ; Add the Battle Suit to the inventory. ;
[c700]@_addItem: [c700]a9 03LDA #$03; 0x03 == Battle Suit [c702]9d a1 03STA a:ArmorInventory,X; Set in the inventory slot
; ; Increase the number of armors collected. ;
[c705]e8INX; X++ [c706]8e c3 03STX a:NumberOfArmors; Store as the number of armors. [c709]60RTS
;============================================================================ ; 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:
; ; Run the "Got Battle Helmet" IScript. ; ; IScript 0x8C via jump to IScripts_Begin. ;
[c70a]a9 8cLDA #$8c; 0x8C == Battle Helmet picked up IScript. [c70c]20 59 f8JSR MMC1_LoadBankAndJump; Run it. [c70f]0c.byte BANK_12_LOGIC; Bank = 12 [c710]41 82pointer IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c712]@_afterFarJump: [c712]a9 08LDA #$08; 0x08 == Item picked up sound. [c714]20 e4 d0JSR Sound_PlayEffect; Play it.
; ; Cap the number of shields in the inventory. ;
[c717]ae c4 03LDX a:NumberOfShields; X = Number of shields. [c71a]e0 04CPX #$04; If X < 4: [c71c]90 02BCC @_addItem; Then, add to the latest slot. [c71e]a2 03LDX #$03; Else, cap X to 3.
; ; Add the Battle Helmet to the inventory. ;
[c720]@_addItem: [c720]a9 03LDA #$03; 0x03 == Battle Helmet. [c722]9d a5 03STA a:ShieldInventory,X; Set in the inventory slot.
; ; Increase the number of shields collected. ;
[c725]e8INX; X++ [c726]8e c4 03STX a:NumberOfShields; Store as the number of shields. [c729]60RTS
;============================================================================ ; 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:
; ; Run the "Got Dragon Slayer" IScript. ; ; IScript 0x8D via jump to IScripts_Begin. ;
[c72a]a9 8dLDA #$8d; 0x8D == Dragon Slayer picked up IScript. [c72c]20 59 f8JSR MMC1_LoadBankAndJump; Run it. [c72f]0c.byte BANK_12_LOGIC; Bank = 12 [c730]41 82pointer IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c732]@_afterFarJump: [c732]a9 08LDA #$08; 0x08 == Item picked up sound. [c734]20 e4 d0JSR 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]ae c3 03LDX a:NumberOfArmors; X = number of armors. [c73a]e0 04CPX #$04; If < 4 [c73c]90 02BCC @_addItem; Then, add it to the latest slot. [c73e]a2 03LDX #$03; Else, cap X to 3.
; ; Add the Dragon Slayer to the inventory. ;
[c740]@_addItem: [c740]a9 03LDA #$03; 0x03 == Dragon Slayer [c742]9d 9d 03STA a:WeaponInventory,X; Set in the inventory slot.
; ; Increase the number of weapons collected. ;
[c745]e8INX; X++ [c746]8e c2 03STX a:NumberOfWeapons; Store as the number of weapons. [c749]60RTS
;============================================================================ ; 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]ad 2d 04LDA a:Quests; Load the quests bitmask. [c74d]09 10ORA #$10; Bit 0x10 == Mattock [c74f]8d 2d 04STA 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:
; ; Run the "Got Mattock" IScript. ; ; IScript 0x88 via jump to IScripts_Begin. ;
[c752]a9 88LDA #$88; 0x88 == Mattock picked up IScript. [c754]20 59 f8JSR MMC1_LoadBankAndJump; Run IScript: [c757]0c.byte BANK_12_LOGIC; Bank = 12 [c758]41 82pointer IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c75a]@_afterFarJump: [c75a]a9 08LDA #$08; 0x08 == Item picked up sound. [c75c]20 e4 d0JSR Sound_PlayEffect; Play it.
; ; Load the Mattock into the inventory. ;
[c75f]a9 09LDA #$09; 0x09 == Mattock [c761]4c cd c8JMP 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:
; ; Check if this is the Mattock. ;
[c764]c9 50CMP #$50; 0x50 == Mattock [c766]f0 eaBEQ Player_PickUpMattock; If 0x50, this is the Mattock. Pick it up.
; ; Check if this is the Magical Rod. ;
[c768]c9 57CMP #$57; 0x57 == Magical Rod [c76a]d0 03BNE @_checkBattleSuit [c76c]4c 10 c8JMP Player_PickUpMagicalRod; This is the Magical Rod. Pick it up.
; ; Check if this is the battle suit. ;
[c76f]@_checkBattleSuit: [c76f]c9 58CMP #$58; 0x58 == Battle Suit [c771]d0 03BNE @_checkBattleHelmet [c773]4c ea c6JMP Player_PickUpBattleSuit; This is the Battle Suit. Pick it up.
; ; Check if this is the battle helmet. ;
[c776]@_checkBattleHelmet: [c776]c9 59CMP #$59; 0x59 == Battle Helmet [c778]f0 90BEQ Player_PickUpBattleHelmet; This is the Battle Helmet. Pick it up.
; ; Check if this is the Dragon Slayer. ;
[c77a]c9 5aCMP #$5a; 0x5A == Dragon Slayer [c77c]f0 acBEQ Player_PickUpDragonSlayer; This is the Dragon Slayer. Pick it up.
; ; Check if this is the Mattock. ;
[c77e]c9 5bCMP #$5b; 0x5B == Mattock [c780]f0 c8BEQ Player_PickUpMattockWithQuest; This is the Mattock. Pick it up.
; ; Check if this is the Wing Boots. ;
[c782]c9 55CMP #$55; 0x55 == Wing Boots [c784]d0 03BNE @_checkWingBootsQuest8 [c786]4c d8 c6JMP Player_PickUpWingBoots; This is the Wing Boots. Pick it up.
; ; Check if this is the Wing Boots (with Quest 0x08). ;
[c789]@_checkWingBootsQuest8: [c789]c9 5cCMP #$5c; 0x5C == Wing Boots for Quest 8 [c78b]d0 03BNE @_checkHourGlass [c78d]4c d0 c6JMP Player_PickUpWingBootsWithQuest; This is the Wing Boots for Quest 8. Pick it up.
; ; Check if this is the Hour Glass. ;
[c790]@_checkHourGlass: [c790]c9 56CMP #$56; 0x56 == Hour Glass [c792]d0 03BNE @_checkRedPotion [c794]4c be c6JMP Player_PickUpHourGlass; This is the Hour Glass. Pick it up.
; ; Check if this is the Red Potion. ;
[c797]@_checkRedPotion: [c797]c9 5dCMP #$5d; 0x5D == Red Potion (1) [c799]d0 03BNE @_checkPoison [c79b]4c 26 c8JMP Player_PickUpRedPotion; This is the Red Potion (1). Pick it up.
; ; Check if this is the Poison. ;
[c79e]@_checkPoison: [c79e]c9 5eCMP #$5e; 0x5E == Poison [c7a0]d0 03BNE @_checkGlove1 [c7a2]4c 3c c8JMP Player_PickUpPoison; This is the Poison. Pick it up. [c7a5]@_checkGlove1: [c7a5]c9 5fCMP #$5f; 0x5F == Glove [c7a7]f0 26BEQ Player_PickUpGlove; This is the Glove. Pick it up.
; ; Check if this is the Ointment. ;
[c7a9]c9 60CMP #$60; 0x60 == Ointment [c7ab]d0 03BNE @_checkGlove2 [c7ad]4c 7a c8JMP Player_PickUpOintment; This is the Ointment. Pick it up. [c7b0]@_checkGlove2: [c7b0]38SEC [c7b1]e9 48SBC #$48 [c7b3]a8TAY [c7b4]f0 19BEQ Player_PickUpGlove; This is the Glove. Pick it up.
; ; Check if this is the Black Onyx. ;
[c7b6]88DEY; 0x49 == Black Onyx [c7b7]f0 2bBEQ Player_PickUpBlackOnyx; If 0x49, this is the Black Onyx. Pick it up.
; ; Check if this is the Pendant. ;
[c7b9]88DEY; 0x4A == Pendant [c7ba]f0 3eBEQ Player_PickUpPendant; If 0x4A, this is the pendant. Pick it up.
; ; Check if this is the Red Potion (another variant). ;
[c7bc]88DEY; 0x4B == Red Potion (2) [c7bd]f0 67BEQ Player_PickUpRedPotion; If 0x4B, this is the Red Potion (2). Pick it up.
; ; Check if this is the Poison. ;
[c7bf]88DEY; 0x4C == Poison [c7c0]f0 7aBEQ Player_PickUpPoison; If 0x4C, this is the Poison. Pick it up.
; ; Check if this is the Elixir. ;
[c7c2]88DEY; 0x4D == Elixir [c7c3]d0 03BNE @_checkOintment [c7c5]4c 64 c8JMP Player_PickUpElixir; This is the Elixir. Pick it up.
; ; Check if this is the Ointment. ;
[c7c8]@_checkOintment: [c7c8]88DEY; 0x4E == Ointment [c7c9]d0 03BNE @_exit [c7cb]4c 7a c8JMP Player_PickUpOintment; This is the Ointment. Pick it up. [c7ce]@_exit: [c7ce]60RTS
;============================================================================ ; 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:
; ; Run the "Got Glove" IScript. ; ; IScript 0x92 via jump to IScripts_Begin. ;
[c7cf]a9 92LDA #$92; 0x92 == Glove picked up IScript. [c7d1]20 59 f8JSR MMC1_LoadBankAndJump; Run IScript: [c7d4]0c.byte BANK_12_LOGIC; Bank = 12 [c7d5]41 82pointer IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c7d7]@_afterFarJump: [c7d7]a9 08LDA #$08; 0x08 == Item picked up sound. [c7d9]20 e4 d0JSR Sound_PlayEffect; Play it.
; ; Activate the Glove by setting its duration, and then ; add 100XP to the player. ;
[c7dc]a9 14LDA #$14; A = 20 seconds. [c7de]8d 28 04STA a:DurationGlove; Set as the Glove duration. [c7e1]4c 09 c6JMP 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:
; ; Run the "Got Black Onyx" IScript. ; ; IScript 0x8E via jump to IScripts_Begin. ;
[c7e4]a9 8eLDA #$8e; 0x8E == Black Onyx picked up IScript. [c7e6]20 59 f8JSR MMC1_LoadBankAndJump; Run IScript: [c7e9]0c.byte BANK_12_LOGIC; Bank = 12 [c7ea]41 82pointer IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c7ec]@_afterFarJump: [c7ec]a9 08LDA #$08; 0x08 == Item picked up sound. [c7ee]20 e4 d0JSR Sound_PlayEffect; Play it.
; ; Add the item to the player's special items list. ;
[c7f1]ad 2c 04LDA a:SpecialItems; Load the current special items bitmask. [c7f4]09 01ORA #$01; Bit 0x01 == Black Onyx. [c7f6]8d 2c 04STA a:SpecialItems; Save it back out. [c7f9]60RTS
;============================================================================ ; 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:
; ; Run the "Got Glove" IScript. ; ; IScript 0x8F via jump to IScripts_Begin. ;
[c7fa]a9 8fLDA #$8f; 0x8F = Pendant picked up IScript. [c7fc]20 59 f8JSR MMC1_LoadBankAndJump; Run IScript: [c7ff]0c.byte BANK_12_LOGIC; Bank = 12 [c800]41 82pointer IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c802]@_afterFarJump: [c802]a9 08LDA #$08; 0x08 = Item picked up sound. [c804]20 e4 d0JSR Sound_PlayEffect; Play it.
; ; Add the item to the player's special items list. ;
[c807]ad 2c 04LDA a:SpecialItems; Load the curent special items bitmask. [c80a]09 02ORA #$02; Bit 0x02 == Pendant. [c80c]8d 2c 04STA a:SpecialItems; Save it back out. [c80f]60RTS
;============================================================================ ; 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:
; ; Run the "Got Magical Rod" IScript. ; ; IScript 0x90 via jump to IScripts_Begin. ;
[c810]a9 90LDA #$90; 0x90 == Magical Rod picked up IScript. [c812]20 59 f8JSR MMC1_LoadBankAndJump; Run IScript: [c815]0c.byte BANK_12_LOGIC; Bank = 12 [c816]41 82pointer IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c818]@_afterFarJump: [c818]a9 08LDA #$08; 0x08 == Item picked up sound. [c81a]20 e4 d0JSR Sound_PlayEffect; Play it.
; ; Add the Magical Rod to the inventory. ;
[c81d]ad 2c 04LDA a:SpecialItems; Load the current special items bitmask. [c820]09 04ORA #$04; Bit 0x04 == Magical Rod. [c822]8d 2c 04STA a:SpecialItems; Store it. [c825]60RTS
;============================================================================ ; 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:
; ; Run the "Got Red Potion" IScript. ; ; IScript 0x87 via jump to IScripts_Begin. ;
[c826]a9 87LDA #$87; 0x87 == Red Potion picked up IScript. [c828]20 59 f8JSR MMC1_LoadBankAndJump; Run IScript: [c82b]0c.byte BANK_12_LOGIC; Bank = 12 [c82c]41 82pointer IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c82e]@_afterFarJump: [c82e]a9 08LDA #$08; 0x08 == Item picked up sound. [c830]20 e4 d0JSR Sound_PlayEffect; Play it.
; ; Add the Red Potion to the inventory. ;
[c833]a9 10LDA #$10; 0x10 == Red Potion. [c835]20 cd c8JSR Player_PickUpItem; Pick up the item. [c838]ae 78 03LDX a:CurrentSpriteIndex; X = current sprite index (restored?) [c83b]60RTS
;============================================================================ ; 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:
; ; Run the "Got Poison" IScript. ; ; IScript 0x91 via jump to IScripts_Begin. ;
[c83c]a9 91LDA #$91; 0x91 = Poison picked up IScript. [c83e]20 59 f8JSR MMC1_LoadBankAndJump; Run IScript: [c841]0c.byte BANK_12_LOGIC; Bank = 12 [c842]41 82pointer IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for the player taking damage. ;
[c844]@_afterFarJump: [c844]a9 04LDA #$04; 0x084 = Player took damage sound. [c846]20 e4 d0JSR Sound_PlayEffect; Play it.
; ; Set temporary invincibility frames for the player. ;
[c849]a9 3cLDA #$3c; A = 60 iframes [c84b]85 adSTA Player_InvincibilityPhase; Set it as the starting invincibility phase.
; ; Mark the player as hit. ;
[c84d]a5 a5LDA Player_StatusFlag; Load the player's status flags. [c84f]09 02ORA #$02; Bit 0x02 == Player was hit. [c851]85 a5STA Player_StatusFlag; Save it.
; ; Remove 16 points from the player's health. ;
[c853]a9 00LDA #$00; A = 0 [c855]8d bc 04STA a:Arg_PlayerHealthDelta_L; Store as the lower byte of HP to remove. [c858]a9 10LDA #$10; A = 16 [c85a]8d bd 04STA a:Arg_PlayerHealthDelta_U; Store as the upper byte of HP to remove. [c85d]20 8e c0JSR Player_ReduceHP; Reduce the HP by that amount. [c860]ae 78 03LDX a:CurrentSpriteIndex; X = current sprite index (restored?) [c863]60RTS
;============================================================================ ; 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:
; ; Run the "Got Elixir" IScript. ; ; IScript 0x86 via jump to IScripts_Begin. ;
[c864]a9 86LDA #$86; 0x86 == Elixir picked up IScript. [c866]20 59 f8JSR MMC1_LoadBankAndJump; Run IScript: [c869]0c.byte BANK_12_LOGIC; Bank = 12 [c86a]41 82pointer IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c86c]@_afterFarJump: [c86c]a9 08LDA #$08; 0x08 == Item picked up sound. [c86e]20 e4 d0JSR Sound_PlayEffect; Play it.
; ; Add the Elixir to the inventory. ;
[c871]ad 2c 04LDA a:SpecialItems; Load the current special items bitmask. [c874]09 08ORA #$08; Bit 0x08 == Elixir. [c876]8d 2c 04STA a:SpecialItems; Save it back out. [c879]60RTS
;============================================================================ ; 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:
; ; Run "Got Ointment" IScript. ; ; IScript 0x94 via jump to IScripts_Begin. ;
[c87a]a9 94LDA #$94; 0x94 == Ointment picked up IScript. [c87c]20 59 f8JSR MMC1_LoadBankAndJump; Run IScript: [c87f]0c.byte BANK_12_LOGIC; Bank = 12 [c880]41 82pointer IScripts_Begin-1; Address = IScripts_Begin
; ; Play the sound effect for picking up an item. ;
[c882]@_afterFarJump: [c882]a9 08LDA #$08; 0x08 == Item picked up sound. [c884]20 e4 d0JSR 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]a9 1eLDA #$1e; A = 30 seconds. [c889]8d 27 04STA a:DurationOintment; Set as the Ointment duration. [c88c]4c 09 c6JMP 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]20 eb c5JSR Game_DecHourGlassDuration [c892]20 9d c5JSR Game_DecWingBootsDuration [c895]20 9b c8JSR Game_DecGloveDuration [c898]4c b4 c8JMP 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:
; ; Check if the Glove is active. ;
[c89b]ad 28 04LDA a:DurationGlove; Load the Glove's duration. [c89e]30 13BMI @_return; If it's 0xFF (not activated), return.
; ; Check if a second has passed. ;
[c8a0]a5 1aLDA InterruptCounter; Load the interrupt counter. [c8a2]29 3fAND #$3f; Check if it's an even second boundary. [c8a4]d0 0dBNE @_return; If not, return.
; ; Decrement and check if the Glove is still active. ;
[c8a6]ce 28 04DEC a:DurationGlove; Decrement the Glove's duration by 1 second. [c8a9]10 08BPL @_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]a9 93LDA #$93; 0x93 == Glove is gone IScript. [c8ad]20 59 f8JSR MMC1_LoadBankAndJump; Run the IScript: [c8b0]0c.byte BANK_12_LOGIC; Bank = 12 [c8b1]41 82pointer IScripts_Begin-1; Address = IScripts_Begin [c8b3]@_return: [c8b3]60RTS
;============================================================================ ; 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:
; ; Check if the Ointment is active. ;
[c8b4]ad 27 04LDA a:DurationOintment; Load the Ointment's duration. [c8b7]30 13BMI @_return; If it's 0xFF (not activated), return.
; ; Check if a second has passed. ;
[c8b9]a5 1aLDA InterruptCounter; Load the interrupt counter. [c8bb]29 3fAND #$3f; Check if it's an even second boundary. [c8bd]d0 0dBNE @_return; If not, return.
; ; Decrement and check if the Ointment is still active. ;
[c8bf]ce 27 04DEC a:DurationOintment; Decrement the Ointment's duration by 1 second. [c8c2]10 08BPL @_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]a9 95LDA #$95; 0x95 == Ointment is gone IScript. [c8c6]20 59 f8JSR MMC1_LoadBankAndJump; Run the IScript: [c8c9]0c.byte BANK_12_LOGIC; Bank = 12 [c8ca]41 82pointer IScripts_Begin-1; Address = IScripts_Begin [c8cc]@_return: [c8cc]60RTS
;============================================================================ ; 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:
; ; Check first if the inventory is full. ;
[c8cd]ae c6 03LDX a:NumberOfItems; Load the number of items in the inventory. [c8d0]e0 08CPX #$08; Is there room? [c8d2]b0 07BCS @_return; If full, exit.
; ; There's room. Set the item in the inventory and increment ; the item counter. ;
[c8d4]9d ad 03STA a:ItemInventory,X; Store the item in the next available slot. [c8d7]e8INX; Set the next available slot. [c8d8]8e c6 03STX a:NumberOfItems [c8db]@_return: [c8db]60RTS
;============================================================================ ; 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:
; ; Set the draw position to 0x13C0 (top-left of selected ; item tiles). ;
[c8dc]a9 13LDA #$13; A = 0x13 [c8de]8d e9 00STA a:PPU_TargetAddr_U; Set as upper byte. [c8e1]a9 c0LDA #$c0; A = 0xC0 [c8e3]8d e8 00STA 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]a2 04LDX #$04; X = 4 (counter). [c8e8]@_vertLoop: [c8e8]8aTXA; A = X [c8e9]48PHA; Push A to the stack.
; ; Set the draw length as 16 bytes (this tile). ;
[c8ea]a9 10LDA #$10; A = 16 [c8ec]20 dc cfJSR PPUBuffer_QueueCommandOrLength; Write as the length. [c8ef]a0 10LDY #$10; Y = 16 [c8f1]a9 00LDA #$00; A = 0 [c8f3]@_horizLoop: [c8f3]9d 00 05STA a:PPUBuffer,X; Write a 0 to the PPU buffer. [c8f6]e8INX; X++ [c8f7]88DEY; Y-- [c8f8]d0 f9BNE @_horizLoop; If not 0, loop.
; ; This row is finished. Prepare to draw the next (if any). ;
[c8fa]86 20STX PPUBuffer_WriteOffset; Store X as the new upper bounds. [c8fc]ad e8 00LDA a:PPU_TargetAddr; A = target address to draw to. [c8ff]18CLC [c900]69 10ADC #$10; Increment by 16 (next tile). [c902]8d e8 00STA a:PPU_TargetAddr; Store as the new lower byte of the target address. [c905]ad e9 00LDA a:PPU_TargetAddr_U; Load the upper byte. [c908]69 00ADC #$00; Add the carry flag, if any. [c90a]8d e9 00STA a:PPU_TargetAddr_U; And save it. [c90d]68PLA; Pull A from the stack (full 0..4 loop counter). [c90e]aaTAX; X = A [c90f]caDEX; X-- [c910]d0 d6BNE @_vertLoop; If > 0, loop. [c912]60RTS
;============================================================================ ; 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]78SEI [c914]d8CLD [c915]a2 ffLDX #$ff; X = 0xFF [c917]9aTXS; 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]a9 00LDA #$00; A = 0 [c91a]8d 01 20STA a:PPUMASK; Set as the PPU mask. [c91d]a9 18LDA #$18; A = 0x18 [c91f]8d 00 20STA 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]ad 02 20LDA a:PPUSTATUS; Read PPU status. [c925]@_readPPUStatus1: [c925]ad 02 20LDA a:PPUSTATUS; Read PPU status. [c928]10 fbBPL @_readPPUStatus1; If > -1, loop. [c92a]@_readPPUStatus2: [c92a]ad 02 20LDA a:PPUSTATUS; Read PPU status. [c92d]10 fbBPL @_readPPUStatus2; If > -1, loop.
; ; Set PPUMASK to 0 to allow transferring large amounts of ; Data to VRAM. ;
[c92f]a9 00LDA #$00; A = 0 [c931]8d 01 20STA a:PPUMASK; Set as the PPU mask. [c934]a2 ffLDX #$ff; X = 0xFF [c936]9aTXS; Transfer to stack.
; ; Clear RAM, setting all ranges of bytes used for state to 0. ;
[c937]a2 00LDX #$00; X = 0 (loop counter) [c939]@_loop: [c939]e0 fcCPX #$fc; Is X >= 252? [c93b]b0 02BCS @_clearState; If so, jump. [c93d]95 00STA Temp_00,X; Clear memory at address X. [c93f]@_clearState: [c93f]9d 00 02STA a:Temp_0200,X; Clear memory at $0200 + X [c942]9d 00 03STA a:CurrentSprites_InternalBehaviorStates_4_,X; Clear memory at $0300 + X [c945]9d 00 04STA a:FirstColumnInRightScreen_10_,X; Clear memory at $0400 + X [c948]9d 00 05STA a:PPUBuffer,X; Clear memory at $0500 + X [c94b]9d 00 06STA a:ScreenBuffer,X; Clear memory at $0600 + X [c94e]9d 00 07STA a:SPRITE_0_RANGE_1_START,X; Clear memory at $0700 + X [c951]e8INX; X++ [c952]d0 e5BNE @_loop; If X != 0, loop. [c954]20 bf cbJSR Game_InitMMCAndBank; Initialize the MMC1 chip and bank. [c957]20 78 caJSR Game_InitScreenAndMusic; Initialize the screen and music. [c95a]4c 6a daJMP Game_InitStateForStartScreen; Initialize the start screen.
; ; Check whether the OAM needs to be reset. ; ; ; XREFS: ; OnInterrupt ;
[c95d]OnInterrupt__updatePPUOAMAndAudio: [c95d]a5 1bLDA Game_NeedOAMReset; Check the reset flag. [c95f]f0 0cBEQ OnInterrupt__updatePPUAndAudio; If 0, don't reset. Jump to PPU/audio management. [c961]a9 00LDA #$00 [c963]8d 03 20STA a:OAMADDR; Reset OAMADDR to 0. [c966]85 1bSTA Game_NeedOAMReset; Clear the reset flag. [c968]a9 07LDA #$07 [c96a]8d 14 40STA a:OAMDMA; Initialize the OAM, writing from $0700.
; ; Update the PPU. ; ; ; XREFS: ; OnInterrupt ;
[c96d]OnInterrupt__updatePPUAndAudio: [c96d]20 d6 c9JSR PPU_HandleOnInterrupt; Run PPU on-interrupt handlers.
; ; Update the audio so music keeps playing. ; ; ; XREFS: ; OnInterrupt ;
[c970]OnInterrupt__updateAudio: [c970]e6 1aINC InterruptCounter; Increment the interrupt counter. [c972]ad 00 01LDA a:CurrentROMBank; Load the current ROM bank. [c975]48PHA; Push it to the stack. [c976]a2 05LDX #$05 [c978]20 85 ccJSR MMC1_EnsurePRG; Switch to bank 5. [c97b]20 09 80JSR thunk_Music_HandleOnInterrupt; Run audio interrupt handlers. [c97e]20 03 80JSR thunk_SoundEffects_HandleOnInterrupt; Run sound playback interrupt handlers. [c981]68PLA; Pop the bank from the stack. [c982]aaTAX [c983]20 85 ccJSR MMC1_EnsurePRG; Switch back to the bank. [c986]4c d0 c9JMP 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]a6 14LDX PauseInterruptCounter; Load the interrupt counter. [c98b]e0 02CPX #$02; Is it < 2? [c98d]b0 02BCS @_clearPPUMask; If so, jump. [c98f]e6 14INC PauseInterruptCounter; Increment the counter. [c991]@_clearPPUMask: [c991]a9 00LDA #$00 [c993]8d 01 20STA a:PPUMASK; Clear PPUMASK. [c996]4c 70 c9JMP 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:
; ; Push all registers to the stack. ;
[c999]48PHA; Push A to the stack. [c99a]8aTXA; Set A = X. [c99b]48PHA; Push A (X) to the stack. [c99c]98TYA; Set A = Y. [c99d]48PHA; Push A (Y) to the stack.
; ; Check what will be handled this interrupt. ;
[c99e]a5 13LDA Game_InterruptHandlersEnabled; Check whether interrupt handling code should be run. [c9a0]f0 e7BEQ OnInterrupt__noProcessInterrupts; If not, jump.
; ; Interrupts may be run. See if it's time to run them. ;
[c9a2]a5 10LDA Game_InterruptsHandledLatch; Check if we're on a frame where we should run interrupts. [c9a4]d0 b7BNE OnInterrupt__updatePPUOAMAndAudio; Jump to update the PPU, OAM, and Audio.
; ; Ready to handle routine operations. ;
[c9a6]e6 10INC Game_InterruptsHandledLatch; Flip the toggle.
; ; Reset the OAM. ; ; This will write $00 to OAMADDR and then use OAMDMA. ;
[c9a8]a9 00LDA #$00 [c9aa]8d 03 20STA a:OAMADDR; Clear OAMADDR. [c9ad]85 1bSTA Game_NeedOAMReset; Turn off the reset flag. [c9af]a9 07LDA #$07 [c9b1]8d 14 40STA a:OAMDMA; Initialize the OAM, writing from SPRITE_0_RANGE_1_START ($0700).
; ; Update the PPU state. ;
[c9b4]20 d6 c9JSR PPU_HandleOnInterrupt
; ; Switch to bank 5 (audio logic) and run audio updates. ;
[c9b7]ad 00 01LDA a:CurrentROMBank; Load the current bank. [c9ba]48PHA; Push it to the stack. [c9bb]a2 05LDX #$05 [c9bd]20 85 ccJSR MMC1_EnsurePRG; Switch to bank 5. [c9c0]20 09 80JSR thunk_Music_HandleOnInterrupt; Run audio interrupt handling code. [c9c3]20 03 80JSR thunk_SoundEffects_HandleOnInterrupt; Run sound playback interrupt handling code. [c9c6]68PLA; Pop the previous bank from the stack. [c9c7]aaTAX [c9c8]20 85 ccJSR MMC1_EnsurePRG; Switch back to it.
; ; Handle input management and final state. ;
[c9cb]20 35 caJSR Input_HandleOnInterrupt; Run input interrupt handlers. [c9ce]e6 1aINC InterruptCounter; Increment the interrupt counter.
; ; Restore all registers from the stack. ; ; ; XREFS: ; OnInterrupt ;
[c9d0]OnInterrupt__popAndReturn: [c9d0]68PLA; Pull A from the stack. [c9d1]a8TAY; Set Y = A [c9d2]68PLA; Pull A from the stack. [c9d3]aaTAX; Set X = A [c9d4]68PLA; Pull A from the satck [c9d5]OnHardwareInterrupt: [c9d5]40RTI; 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:
; ; Handle any scrolling-related screen updates and flush ; the PPU buffer to the screen. ;
[c9d6]20 1d d6JSR Screen_RunWriteScrollDataHandler; Run the handler for any screen scrolling. [c9d9]20 3c cfJSR PPUBuffer_Draw; Flush the PPU buffer to the screen. [c9dc]a5 0bLDA PPU_Mask; Load the stored PPU mask. [c9de]8d 01 20STA a:PPUMASK; And set it on the PPU. [c9e1]a5 5aLDA Sprites_Sprite0Mode; Load the sprite 0 mode. [c9e3]30 2cBMI @_setScroll; If 0xFF (no sprite 0), jump. [c9e5]a5 5bLDA PPU_ForceLowerPatternTables; Load the Force Lower Pattern Tables setting. [c9e7]f0 0aBEQ @_clearNametable2400; If not forcing, then jump.
; ; Clear the following flags: ; ; * Nametable 2400 ; * Sprite pattern 1000 ; * BGTable Addr 1000 ;
[c9e9]a5 0aLDA PPU_ControlFlags; Load the saved PPU control flags. [c9eb]29 e6AND #$e6; Clear flags. [c9ed]8d 00 20STA a:PPUCTRL; Set it on the PPU. [c9f0]4c fa c9JMP @_resetScroll; Jump to update scroll positions.
; ; Clear the Nametable 2400 flag. ;
[c9f3]@_clearNametable2400: [c9f3]a5 0aLDA PPU_ControlFlags [c9f5]29 feAND #$fe [c9f7]8d 00 20STA a:PPUCTRL
; ; Reset the screen scroll. ;
[c9fa]@_resetScroll: [c9fa]a9 00LDA #$00 [c9fc]8d 05 20STA a:PPUSCROLL; Clear horizontal screen scroll. [c9ff]8d 05 20STA a:PPUSCROLL; Clear vertical screen scroll.
; ; Wait until sprite 0 isn't hit in the sprite update. ;
[ca02]@_waitSprite0NotHitLoop: [ca02]2c 02 20BIT a:PPUSTATUS; Test the Sprite 0 Hit flag. [ca05]70 fbBVS @_waitSprite0NotHitLoop; If set, loop.
; ; Now wait until it is hit. ;
[ca07]@_waitSprite0Hit: [ca07]2c 02 20BIT a:PPUSTATUS; Test the Sprite 0 Hit flag. [ca0a]50 fbBVC @_waitSprite0Hit; if not set, loop.
; ; Loop 160 times, giving the screen time to update. ;
[ca0c]a2 a0LDX #$a0; X = 160 (loop counter). [ca0e]@_loop160Times: [ca0e]caDEX; X-- [ca0f]d0 fdBNE @_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]a5 0dLDA PPU_ScrollScreen; Load the current scroll screen. [ca13]29 01AND #$01; Convert to an even/odd value (0 or 1) to select the nametable. [ca15]05 0aORA PPU_ControlFlags; OR with the PPU controls. [ca17]8d 00 20STA a:PPUCTRL; Store as the new PPU control flags. [ca1a]a5 0cLDA PPU_ScrollX; Load the calculated scroll X. [ca1c]8d 05 20STA a:PPUSCROLL; Write as the horizontal scroll offset. [ca1f]a9 00LDA #$00; Hard-code scroll Y as 0. [ca21]8d 05 20STA a:PPUSCROLL; Write as the vertical scroll offset. [ca24]60RTS
;============================================================================ ; 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]a9 00LDA #$00; A = 0 [ca27]85 10STA Game_InterruptsHandledLatch; Set as the frame toggle state. [ca29]@_loop: [ca29]a5 10LDA Game_InterruptsHandledLatch; Load the frame toggle state from the interrupt handler. [ca2b]f0 fcBEQ @_loop; If it's 0, loop. [ca2d]60RTS; 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]a5 1aLDA InterruptCounter; Load the interrupt count. [ca30]@_loop: [ca30]c5 1aCMP InterruptCounter; Has the interrupt counter changed? [ca32]f0 fcBEQ @_loop; If not, loop. [ca34]60RTS
;============================================================================ ; 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:
; ; Store the current button mask as the new previous button mask. ;
[ca35]a5 16LDA Joy1_ButtonMask; A = Stored controller 1 button mask [ca37]85 18STA Joy1_PrevButtonMask; Store as the previous button mask.
; ; Prepare to read the controller state. ;
[ca39]a9 01LDA #$01; A = 1 [ca3b]8d 16 40STA a:JOY1; Trigger loading controller 1 bits. [ca3e]a9 00LDA #$00 [ca40]8d 16 40STA a:JOY1; Prepare for read. [ca43]85 16STA Joy1_ButtonMask; Reset controller 1 button mask to 0. [ca45]85 17STA Joy2_ButtonMask; Reset controller 2 button mask to 0.
; ; Read the controller 1 state into the button mask. ;
[ca47]a2 08LDX #$08; X = 8 (loop counter) [ca49]@_loop1: [ca49]ad 16 40LDA a:JOY1; A = Current controller 1 state. [ca4c]29 03AND #$03; Take the first two bits. [ca4e]4aLSR A; Shift right 1. Bit 0 goes into Carry. [ca4f]26 16ROL Joy1_ButtonMask; Rotate the target bitmask left, moving Carry into bit 0. [ca51]05 16ORA Joy1_ButtonMask; OR to the generated bitmask. [ca53]85 16STA Joy1_ButtonMask; Store as the new button mask. [ca55]caDEX; X-- [ca56]d0 f1BNE @_loop1; If > 0, loop.
; ; Read the controller 2 state into the button mask. ; ; This is only used for the debug level skip code. ;
[ca58]a2 08LDX #$08; X = 8 (loop counter). [ca5a]@_loop2: [ca5a]ad 17 40LDA a:JOY2; A = Current controller 2 state. [ca5d]29 01AND #$01; Take the first bit. [ca5f]4aLSR A; Shift right 1. Bit 0 goes into Carry. [ca60]26 17ROL Joy2_ButtonMask; Rotate the target bitmask left, moving Carry into bit 0. [ca62]caDEX; X-- [ca63]d0 f5BNE @_loop2; If > 0, loop.
; ; Compute the changed button mask between the last read and now. ;
[ca65]a5 16LDA Joy1_ButtonMask; Load the controller 1 button mask. [ca67]45 18EOR Joy1_PrevButtonMask; XOR with the previously-changed, yielding the button differences. [ca69]25 16AND Joy1_ButtonMask; AND with the new button mask. [ca6b]85 19STA Joy1_ChangedButtonMask; And store as the changed button mask. [ca6d]60RTS
;============================================================================ ; 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]a6 daLDX Random_Offset; Load the current offset into the bank. [ca70]bd 00 80LDA a:Sprites_UpdateAll,X; Load the byte value at that offset. [ca73]45 16EOR Joy1_ButtonMask; XOR with the controler 1 button mask. [ca75]e6 daINC Random_Offset; Increment the offset. [ca77]60RTS
;============================================================================ ; 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:
; ; Initialize PPU settings. ;
[ca78]a9 10LDA #$10 [ca7a]85 0aSTA PPU_ControlFlags; Set PPU control flags to BGTable and Address $1000. [ca7c]a9 00LDA #$00 [ca7e]85 0cSTA PPU_ScrollX; Clear scroll X. [ca80]85 0dSTA PPU_ScrollScreen; Clear scroll screen. [ca82]85 57STA PPU_ScrollY; Clear scroll Y. [ca84]a9 1eLDA #$1e [ca86]85 0bSTA PPU_Mask; Set screen color mode to enable sprites, BG, and showing left-most 8 pixels. [ca88]20 4f cbJSR Screen_ResetSpritesForNonGame; Reset sprites for a non-game screen. [ca8b]20 bc caJSR PPU_InitAttributeAndNameTables; Initialize attributes and nametables.
; ; Stop any interrupt handling for the game. ;
[ca8e]a9 00LDA #$00 [ca90]85 13STA Game_InterruptHandlersEnabled; Disable interrupt handlers. [ca92]8d 2f 04STA a:Maybe_Game_Ready; Disable the game ready state.
; ; Switch to bank 5 and set up sound. ;
[ca95]ad 00 01LDA a:CurrentROMBank; Load the current ROM bank. [ca98]48PHA; Push it to the stack. [ca99]a2 05LDX #$05; Bank 5 = Sound. [ca9b]20 1a ccJSR MMC1_UpdateROMBank; Switch to it. [ca9e]20 06 80JSR thunk_Audio_InitPlayingState; Initialize the sound play state. [caa1]20 00 80JSR thunk_SoundEffects_Init; Initialize sound. [caa4]68PLA; Pop the previous bank from the stack. [caa5]aaTAX; X = previous bank. [caa6]20 1a ccJSR MMC1_UpdateROMBank; And switch back to it.
; ; Re-enable the game. ;
[caa9]a9 00LDA #$00 [caab]85 10STA Game_InterruptsHandledLatch; Disable the interrupt handlers latch. [caad]8d 20 01STA a:Game_PausedState; Disable the pause state. [cab0]85 5bSTA PPU_ForceLowerPatternTables; Disable forcing lower pattern tables. [cab2]4c 2f cbJMP 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]8d 06 20STA a:PPUADDR; Set the upper byte. [cab8]8e 06 20STX a:PPUADDR; Set the lower byte. [cabb]60RTS
;============================================================================ ; Initialize the attribute and name tables for the PPU. ; ; This will write the following: ; ; * Name table 0: 8 bytes of 0x20 ; * Attribute table 0: 320 bytes of 0x55 ; * Attribute table 1: 320 bytes of 0x55 ; ; INPUTS: ; None. ; ; OUTPUTS: ; PPUDATA: ; PPU state will be set. ; ; Temp_PPU_NameTableValue: ; Clobbered. ; ; CALLS: ; PPU_SetAddr ; PPU_WriteRowsWithInitial ; ; XREFS: ; Game_InitScreenAndMusic ;============================================================================
[cabc]PPU_InitAttributeAndNameTables:
; ; Store a value to write in name table 0. ; ; There's no good reason this is stored in its own variable and then ; almost immediately accessed, except that this code likely contained ; other possible values to include here based on other state. Any such ; code no longer exists. ;
[cabc]a9 20LDA #$20; A = 0x20 (value to write). [cabe]85 23STA Temp_PPU_NameTableValue; Store it temporarily.
; ; Set address 0x2000: Name table 0. ; ; Write 32 to 8 bytes. ;
[cac0]a9 20LDA #$20; Upper byte of table address. [cac2]a2 00LDX #$00; Lower byte of table address. [cac4]20 b5 caJSR PPU_SetAddr; Set that as the address to write to. [cac7]a0 08LDY #$08; 8 full rows. [cac9]a2 00LDX #$00; 0 starting columns before full rows. [cacb]a5 23LDA Temp_PPU_NameTableValue; Value to write (from above). [cacd]20 ed caJSR PPU_WriteRowsWithInitial; Write to name table 0.
; ; 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]a9 23LDA #$23; Upper byte of table address. [cad2]a2 c0LDX #$c0; Lower byte of table address. [cad4]20 b5 caJSR PPU_SetAddr; Set as the address to write to. [cad7]a2 40LDX #$40; Write 64 initial bytes. [cad9]a0 01LDY #$01; Plus 1 full row-length of columns (256 bytes). [cadb]a9 55LDA #$55; Value to write. [cadd]20 ed caJSR PPU_WriteRowsWithInitial; Write to attribute table 0.
; ; Set address 0x27C0: Attribute table 1. ; ; Write 64 entries here with the same information. ;
[cae0]a9 27LDA #$27; Upper byte of table address. [cae2]a2 c0LDX #$c0; Lower byte of table address. [cae4]20 b5 caJSR PPU_SetAddr; Set as the address to write to. [cae7]a2 40LDX #$40; Write 64 initial bytes. [cae9]a0 01LDY #$01; Plus 1 full row-length of columns (256 bytes). [caeb]a9 55LDA #$55
; ; v-- Fall through --v ; Value to write. ;
;============================================================================ ; Write a value repeatedly to the PPU. ; ; This writes a value to the PPU up to X times, and then ; again 256 * Y times. ; ; INPUTS: ; A: ; The value to write. ; ; X: ; Initial number of bytes to write. ; ; Y: ; The number of full rows' worth of columns to fill after ; the initial column span (256 * Y). ; ; OUTPUTS: ; PPUDATA: ; Updated with the new written data. ; ; XREFS: ; PPU_InitAttributeAndNameTables ; PPU_WriteRowsWithInitial ;============================================================================
[caed]PPU_WriteRowsWithInitial: [caed]8d 07 20STA a:PPUDATA; Write the value to PPUDATA. [caf0]caDEX; X-- [caf1]d0 faBNE PPU_WriteRowsWithInitial; 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]88DEY; Y-- [caf4]d0 f7BNE PPU_WriteRowsWithInitial; If Y > 0, loop [caf6]60RTS
;============================================================================ ; 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:
; ; Wait until the PPU buffer has been emptied (flushed to the ; PPU). ;
[caf7]a5 20LDA PPUBuffer_WriteOffset; Load the upper bounds of the PPU buffer. [caf9]c5 1fCMP PPUBuffer_ReadOffset; Has the read offset hit the upper bounds? [cafb]d0 faBNE PPU_WaitUntilFlushed; If not, loop until it's cleared.
; ; Pause interrupt handling for 2 interrupts. ;
[cafd]a9 00LDA #$00 [caff]85 14STA PauseInterruptCounter; Reset the paused interrupt counter. [cb01]85 13STA Game_InterruptHandlersEnabled; Disable processing of interrupts. [cb03]85 5bSTA PPU_ForceLowerPatternTables
; ; Wait for 2 interrupts (at least). ;
[cb05]@_waitForInterruptLoop: [cb05]a5 14LDA PauseInterruptCounter; Load the paused interrupt counter. [cb07]c9 02CMP #$02; Has it reached 2 interrupts? [cb09]90 faBCC @_waitForInterruptLoop; If not, loop. [cb0b]60RTS
;============================================================================ ; DEADCODE ; ; XREFS: ; DEADCODE_FUN_PRG15_MIRROR__cb0c ;============================================================================
[cb0c]DEADCODE_FUN_PRG15_MIRROR__cb0c: [cb0c]ad 02 20LDA a:PPUSTATUS [cb0f]30 fbBMI DEADCODE_FUN_PRG15_MIRROR__cb0c [cb11]@LAB_PRG15_MIRROR__cb11: [cb11]ad 02 20LDA a:PPUSTATUS [cb14]10 fbBPL @LAB_PRG15_MIRROR__cb11 [cb16]60RTS
;============================================================================ ; 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]20 47 cbJSR Screen_ResetSpritesForGamePlay; Reset the sprites for game play mode. [cb1a]a9 01LDA #$01 [cb1c]85 13STA Game_InterruptHandlersEnabled; Enable interrupt handlers. [cb1e]60RTS
;============================================================================ ; DEADCODE ;============================================================================
[cb1f]DEADCODE_FUN_PRG15_MIRROR__cb1f: [cb1f]20 3f cbJSR DEADCODE_Sprites_ResetForDefaultsMode1 [cb22]a9 01LDA #$01 [cb24]85 13STA Game_InterruptHandlersEnabled [cb26]60RTS
;============================================================================ ; 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]20 4f cbJSR Screen_ResetSpritesForNonGame; Reset the sprites for a non-game mode. [cb2a]a9 01LDA #$01 [cb2c]85 13STA Game_InterruptHandlersEnabled; Enable interrupt handlers. [cb2e]60RTS
;============================================================================ ; 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:
; ; Enable VBlank NMI. ;
[cb2f]a5 0aLDA PPU_ControlFlags; Load the PPU control flags. [cb31]09 80ORA #$80; Enable the VBlank bit. [cb33]d0 04BNE @_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]a5 0aLDA PPU_ControlFlags [cb37]29 7fAND #$7f
; ; Write our copy of the bitmask and set on the PPU. ;
[cb39]@_savePPUCtrl: [cb39]85 0aSTA PPU_ControlFlags; Store the saved PPU control flags. [cb3b]8d 00 20STA a:PPUCTRL; Store the flags in the PPU. [cb3e]60RTS
;============================================================================ ; DEADCODE ; ; XREFS: ; DEADCODE_FUN_PRG15_MIRROR__cb1f ;============================================================================
[cb3f]DEADCODE_Sprites_ResetForDefaultsMode1: [cb3f]a9 01LDA #$01 [cb41]85 5aSTA Sprites_Sprite0Mode [cb43]85 5bSTA PPU_ForceLowerPatternTables [cb45]d0 0cBNE 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]a9 00LDA #$00 [cb49]85 5aSTA Sprites_Sprite0Mode; Set sprite 0 to X=8, Y=23. [cb4b]85 5bSTA PPU_ForceLowerPatternTables; Disable forcing lower pattern tables. [cb4d]f0 04BEQ 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]a9 ffLDA #$ff; A = 0xFF (no sprite 0) [cb51]85 5aSTA 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. ; ; SPRITE0_START_Y: ; 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:
; ; Reset state. ; ; Some of this seem to be remnants from an older design. ;
[cb53]a0 00LDY #$00; Y = 0 [cb55]84 33STY Sprites_PPUOffset; Sprites_PPUOffset = 0 [cb57]84 34STY Sprites_Unused_0034; Sprites_Unused_0034 = 0 [cb59]84 35STY Sprites_Unused_0035; Sprites_Unused_0035 = 0 [cb5b]84 38STY Sprites_Unused_0038; Sprites_Unused_0038 = 0 [cb5d]84 37STY Sprites_Unused_0037; Sprites_Unused_0037 = 0 [cb5f]84 39STY Sprites_SlotsFull; Set sprite slots to not be full. [cb61]84 25STY Sprites_SpriteSlot; Set the starting sprite slot to 0. [cb63]a5 5aLDA Sprites_Sprite0Mode; Load our byte from before.
; ; Check whether sprite 0 should be hard-coded. ;
[cb65]30 1bBMI @_beginFillSpriteSlots; If 0xFF, then jump. [cb67]e6 25INC Sprites_SpriteSlot; Increment the sprite slot (start new sprites at sprite 1). [cb69]a8TAY; 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]b9 96 cbLDA a:SPRITE0_START_Y,Y; Load the Y for the sprite. [cb6d]8d 00 07STA a:SPRITE_0_RANGE_1_START; Store for sprite 0's Y. [cb70]a9 7fLDA #$7f; 0x7F == tile ID [cb72]8d 01 07STA a:SPRITE0_TILE; Store for sprite 0's tile ID. [cb75]a9 23LDA #$23; 0x23 == Palette index 3, behind background [cb77]8d 02 07STA a:SPRITE0_ATTRS; Store for sprite 0's attributes. [cb7a]b9 98 cbLDA a:SPRITE0_START_X,Y; Load the X for the sprite. [cb7d]8d 03 07STA a:SPRITE0_X; Store for sprite 0's X. [cb80]a0 04LDY #$04; Y = 4 (loop counter).
; ; Begin filling all sprite slots with a Y = 240 (off-screen). ;
[cb82]@_beginFillSpriteSlots: [cb82]a9 f0LDA #$f0; A = 0xF0 (default Y for unused slots). [cb84]@_fillSpriteSlotsLoop: [cb84]99 00 07STA a:SPRITE_0_RANGE_1_START,Y; Set it for this sprite slot's Y. [cb87]c8INY; Increment slot byte by 4. [cb88]c8INY [cb89]c8INY [cb8a]c8INY [cb8b]d0 f7BNE @_fillSpriteSlotsLoop; If not 0, loop.
; ; Flip the start of the sprite slot range between 0 and 32. ;
[cb8d]a5 1cLDA Sprites_StartSlotRange; Load the frame flags. [cb8f]29 80AND #$80; Cap to 0 or 32. [cb91]49 80EOR #$80; Flip it between 0 and 32. [cb93]85 1cSTA Sprites_StartSlotRange; Store it. [cb95]60RTS
;============================================================================ ; Start Y locations for the default sprite 0. ; ; XREFS: ; Sprites_Reset ;============================================================================
; ; XREFS: ; Sprites_Reset ;
[cb96]SPRITE0_START_Y: [cb96]17.byte $17; [0]: SPRITE_0_RANGE_1_START when Sprites_Sprite0Mode == 0 [cb97]48.byte $48; [1]: SPRITE_0_RANGE_1_START when Sprites_Sprite0Mode == 0xFF
;============================================================================ ; Start X locations for the default sprite 0. ; ; XREFS: ; Sprites_Reset ;============================================================================
; ; XREFS: ; Sprites_Reset ;
[cb98]SPRITE0_START_X: [cb98]08.byte $08; [0]: SPRITE0_X when Sprites_Sprite0Mode == 0 [cb99]00.byte $00; [1]: 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]20 47 cbJSR Screen_ResetSpritesForGamePlay [cb9d]a9 01LDA #$01 [cb9f]85 1bSTA Game_NeedOAMReset [cba1]a5 1aLDA InterruptCounter [cba3]@_waitForInterruptLoop: [cba3]c5 1aCMP InterruptCounter [cba5]f0 fcBEQ @_waitForInterruptLoop [cba7]60RTS
;============================================================================ ; 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]a2 04LDX #$04; X = 4 (sprite data offset) [cbaa]a0 84LDY #$84; Y = 0x84 (start of loop counter) [cbac]@_loop: [cbac]bd 00 07LDA a:SPRITE_0_RANGE_1_START,X; Load the sprite Y from this offset in range 1. [cbaf]48PHA; Push it to the stack. [cbb0]b9 00 07LDA a:SPRITE_0_RANGE_1_START,Y; Load the sprite Y from this offset in range 2. [cbb3]9d 00 07STA a:SPRITE_0_RANGE_1_START,X; Copy to range 1. [cbb6]68PLA; Pop the previous Y from the stack. [cbb7]99 00 07STA a:SPRITE_0_RANGE_1_START,Y; Copy it to range 2. [cbba]e8INX; X++ (sprite data offset) [cbbb]c8INY; Y++ (loop counter) [cbbc]d0 eeBNE @_loop; If not wrapped to 0, loop. [cbbe]60RTS
;============================================================================ ; 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]a9 00LDA #$00 [cbc1]85 12STA MMC1_ShiftSync [cbc3]20 d0 cbJSR MMC1_Init; Initialize the MMC1 chipset. [cbc6]a2 0eLDX #$0e [cbc8]8e 00 01STX a:CurrentROMBank; Set the previous and current ROM banks to 14. [cbcb]86 11STX SavedPRGBank [cbcd]4c 1a ccJMP MMC1_UpdateROMBank; Switch to the bank.
;============================================================================ ; Initialize the MMC1 chip. ; ; INPUTS: ; None. ; ; OUTPUTS: ; None. ; ; XREFS: ; Game_InitMMCAndBank ;============================================================================
[cbd0]MMC1_Init: [cbd0]a9 ffLDA #$ff [cbd2]8d ff ffSTA a:$ffff [cbd5]a9 0eLDA #$0e; Horizontal Mirroring Regular Mirroring Swap ROM bank at 0x8000 Swap 8K of VROM at PPU 0x0000 Don't reset [cbd7]8d ff 9fSTA a:$9fff [cbda]4aLSR A [cbdb]8d ff 9fSTA a:$9fff [cbde]4aLSR A [cbdf]8d ff 9fSTA a:$9fff [cbe2]4aLSR A [cbe3]8d ff 9fSTA a:$9fff [cbe6]4aLSR A [cbe7]8d ff 9fSTA a:$9fff; VROM_SIZE_SELECT [cbea]a9 00LDA #$00; Select VROM bank at 0x0000 Switch 4KB only Don't reset [cbec]8d ff bfSTA a:$bfff [cbef]4aLSR A [cbf0]8d ff bfSTA a:$bfff [cbf3]4aLSR A [cbf4]8d ff bfSTA a:$bfff [cbf7]4aLSR A [cbf8]8d ff bfSTA a:$bfff [cbfb]4aLSR A [cbfc]8d ff bfSTA a:$bfff; VROM_PAGE_SELECT_1 [cbff]a9 00LDA #$00; Select VROM bank at 0x1000 Switch 4KB only Don't reset [cc01]8d ff dfSTA a:$dfff [cc04]4aLSR A [cc05]8d ff dfSTA a:$dfff [cc08]4aLSR A [cc09]8d ff dfSTA a:$dfff [cc0c]4aLSR A [cc0d]8d ff dfSTA a:$dfff [cc10]4aLSR A [cc11]8d ff dfSTA a:$dfff [cc14]60RTS
;============================================================================ ; 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_LoadScrollData ; Game_EnterAreaHandler ; Game_EnterBuilding ; Game_ExitBuilding ; Game_LoadCurrentArea ; Game_LoadFirstLevel ;============================================================================
[cc15]MMC1_SaveROMBankAndUpdateTo: [cc15]ad 00 01LDA a:CurrentROMBank; Get the currently-loaded ROM bank [cc18]85 11STA 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_LoadScrollData ; 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_PopBank ; 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]8e 00 01STX a:CurrentROMBank; Set the bank we're switching to. [cc1d]@_setupMMC1: [cc1d]a9 01LDA #$01 [cc1f]85 12STA MMC1_ShiftSync
; ; Write 5 LSBs of A to $FFFF, one bit per write (MMC1 serial protocol): ;
[cc21]8aTXA; ROM_PAGE_SELECT parameter in X [cc22]8d ff ffSTA a:$ffff [cc25]4aLSR A [cc26]8d ff ffSTA a:$ffff [cc29]4aLSR A [cc2a]8d ff ffSTA a:$ffff [cc2d]4aLSR A [cc2e]8d ff ffSTA a:$ffff [cc31]4aLSR A [cc32]8d ff ffSTA a:$ffff
; ; 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]a5 12LDA MMC1_ShiftSync [cc37]c9 01CMP #$01 [cc39]f0 47BEQ @_finish
; ; Slow path. Re-initialize the MMC1. ;
[cc3b]a9 ffLDA #$ff [cc3d]8d ff ffSTA a:$ffff; Reset the MMC1 shifter/controler. [cc40]a9 0eLDA #$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]8d ff 9fSTA a:$9fff [cc45]4aLSR A [cc46]8d ff 9fSTA a:$9fff [cc49]4aLSR A [cc4a]8d ff 9fSTA a:$9fff [cc4d]4aLSR A [cc4e]8d ff 9fSTA a:$9fff [cc51]4aLSR A [cc52]8d ff 9fSTA a:$9fff
; ; Set CHR bank 0 = 0. ;
[cc55]a9 00LDA #$00; Select VROM bank at 0x000 Switch 4K only Don't reset [cc57]8d ff bfSTA a:$bfff [cc5a]4aLSR A [cc5b]8d ff bfSTA a:$bfff [cc5e]4aLSR A [cc5f]8d ff bfSTA a:$bfff [cc62]4aLSR A [cc63]8d ff bfSTA a:$bfff [cc66]4aLSR A [cc67]8d ff bfSTA a:$bfff
; ; Set CHR bank 1 = 0. ;
[cc6a]a9 00LDA #$00; Select VROM bank at 0x1000 Switch 4K only Don't reset [cc6c]8d ff dfSTA a:$dfff [cc6f]4aLSR A [cc70]8d ff dfSTA a:$dfff [cc73]4aLSR A [cc74]8d ff dfSTA a:$dfff [cc77]4aLSR A [cc78]8d ff dfSTA a:$dfff [cc7b]4aLSR A [cc7c]8d ff dfSTA a:$dfff [cc7f]4c 1d ccJMP @_setupMMC1 [cc82]@_finish: [cc82]c6 12DEC MMC1_ShiftSync [cc84]60RTS
;============================================================================ ; 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]8e 00 01STX 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]a5 12LDA MMC1_ShiftSync [cc8a]f0 46BEQ @_fastPath
; ; The sync state was set, so something is switching banks. ; Increment the sync state and reset the MMC1 chip. ;
[cc8c]e6 12INC MMC1_ShiftSync
; ; Reset MMC1. ;
[cc8e]a9 ffLDA #$ff [cc90]8d ff ffSTA a:$ffff [cc93]a9 0eLDA #$0e; Horizontal Mirroring Regular Mirroring Swap ROM bank at 0x8000 Swap 8K of VROM at PPU 0x000 Don't reset [cc95]@_setMMC1State: [cc95]8d ff 9fSTA a:$9fff [cc98]4aLSR A [cc99]8d ff 9fSTA a:$9fff [cc9c]4aLSR A [cc9d]8d ff 9fSTA a:$9fff [cca0]4aLSR A [cca1]8d ff 9fSTA a:$9fff [cca4]4aLSR A [cca5]8d ff 9fSTA a:$9fff
; ; Set CHR bank 0 = 0 ;
[cca8]a9 00LDA #$00 [ccaa]8d ff bfSTA a:$bfff [ccad]4aLSR A [ccae]8d ff bfSTA a:$bfff [ccb1]4aLSR A [ccb2]8d ff bfSTA a:$bfff [ccb5]4aLSR A [ccb6]8d ff bfSTA a:$bfff [ccb9]4aLSR A [ccba]8d ff bfSTA a:$bfff
; ; Set CHR bank 1 = 0. ;
[ccbd]a9 00LDA #$00 [ccbf]8d ff dfSTA a:$dfff [ccc2]4aLSR A [ccc3]8d ff dfSTA a:$dfff [ccc6]4aLSR A [ccc7]8d ff dfSTA a:$dfff [ccca]4aLSR A [cccb]8d ff dfSTA a:$dfff [ccce]4aLSR A [cccf]8d ff dfSTA a:$dfff
; ; Set the bank. ;
[ccd2]@_fastPath: [ccd2]8aTXA [ccd3]8d ff ffSTA a:$ffff [ccd6]4aLSR A [ccd7]8d ff ffSTA a:$ffff [ccda]4aLSR A [ccdb]8d ff ffSTA a:$ffff [ccde]4aLSR A [ccdf]8d ff ffSTA a:$ffff [cce2]4aLSR A [cce3]8d ff ffSTA a:$ffff [cce6]60RTS
;============================================================================ ; 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_LoadScrollData ; Game_EnterAreaHandler ; Game_EnterBuilding ; Game_ExitBuilding ; Game_LoadCurrentArea ; Game_LoadFirstLevel ;============================================================================
[cce7]MMC1_RestorePrevROMBank: [cce7]a6 11LDX SavedPRGBank [cce9]4c 1a ccJMP MMC1_UpdateROMBank
;============================================================================ ; Remove vertical lines from buffered tiles. ; ; This PPUBuffer draw command will read 16 bytes from the PPU ; buffer and progressively remove vertical lines for them. It's ; used for the player death animation. ; ; This takes the following input from the PPU buffer: ; ; 1. The animation phase. ; 2. The upper address of tiles within the PPU. ; 3. The lower address of tiles within the PPU. ; ; Starting at this address, this will take each of the 16 tiles ; in order and apply a mask to AND out pixels from the tile, ; applying a dissolving animation (using the masking table at ; PPUBUFFER_DRAWCOMMAND_REMOVE_VERT_LINES_MASKS. ; ; INPUTS: ; PPUBuffer: ; The PPU buffer to read arguments 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. ; ; PPUBUFFER_DRAWCOMMAND_REMOVE_VERT_LINES_MASKS: ; The masking table for line removal. ; ; OUTPUTS: ; PPUBuffer_ReadOffset: ; The updated offset within the PPU buffer. ; ; PPUADDR: ; The written PPU address. ; ; PPUDATA: ; The written PPU data. ; ; Temp_06: ; Temp_07: ; Temp_08: ; Clobbered. ; ; TEMP VARIABLES: ; Temp_06 ; ; XREFS: ; PPUBUFFER_DRAW_COMMANDS ; [$PRG15_MIRROR::cfc8] ;============================================================================
[ccec]PPUBuffer_DrawCommand_RemoveVerticalLines:
; ; Read the first byte from the buffer as the phase. ;
[ccec]a6 1fLDX PPUBuffer_ReadOffset; Load our current PPU buffer offset into X. [ccee]bd 00 05LDA a:PPUBuffer,X; Load the first address byte from the buffer into A. [ccf1]e8INX; Increment buffer offset (X = X + 1). [ccf2]85 08STA Temp_08; Store the upper byte (A) into a temporary variable.
; ; Read the upper address from the buffer and set in PPUADDR. ;
[ccf4]bd 00 05LDA a:PPUBuffer,X; Load the next byte from the buffer into A. [ccf7]e8INX; Increment buffer offset. [ccf8]a8TAY; Y = lower byte. [ccf9]8c 06 20STY a:PPUADDR; Write the upper byte to PPUADDR.
; ; Read the lower address from the buffer and set in PPUADDR. ;
[ccfc]bd 00 05LDA a:PPUBuffer,X [ccff]e8INX [cd00]86 1fSTX PPUBuffer_ReadOffset; Set the new offset. [cd02]aaTAX [cd03]8e 06 20STX a:PPUADDR; Write the lower byte to PPUADDR.
; ; Begin preparing for the loop. There will be 16 iterations. ;
[cd06]a9 10LDA #$10; Set to 16 iterations in Temp_06. [cd08]85 06STA Temp_06 [cd0a]ad 07 20LDA a:PPUDATA; Read a byte from PPUDATA. [cd0d]@_loop: [cd0d]86 07STX Temp_07; Store X (offset) into our temp state. [cd0f]ad 07 20LDA a:PPUDATA; Read the next byte of data.
; ; Set the address again. The PPUDATA read advanced the offset. ;
[cd12]8c 06 20STY a:PPUADDR; Set upper byte of PPU address to Y. [cd15]8e 06 20STX 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]48PHA [cd19]a5 06LDA Temp_06; Load the loop index. [cd1b]18CLC [cd1c]65 08ADC Temp_08; Add the phase byte to it. [cd1e]29 07AND #$07; Retain only the 3 least-significant bits. [cd20]aaTAX; Store in X. [cd21]68PLA
; ; 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]3d 33 cdAND a:PPUBUFFER_DRAWCOMMAND_REMOVE_VERT_LINES_MASKS,X [cd25]8d 07 20STA a:PPUDATA; Set as the new PPU data.
; ; Load the offset into the buffer and increment. ;
[cd28]a6 07LDX Temp_07; Load the saved offset. [cd2a]e8INX; Increment by 1.
; ; Prepare either for the next loop or for termination. ;
[cd2b]ad 07 20LDA a:PPUDATA; Load our next PPU data byte for the next loop. [cd2e]c6 06DEC Temp_06; Decrement our loop index by 1. [cd30]d0 dbBNE @_loop; If not 0, loop. [cd32]60RTS
; ; XREFS: ; PPUBuffer_DrawCommand_RemoveVerticalLines ;
[cd33]PPUBUFFER_DRAWCOMMAND_REMOVE_VERT_LINES_MASKS: [cd33]fe.byte $fe; [0]: [cd34]df.byte $df; [1]: [cd35]f7.byte $f7; [2]: [cd36]fd.byte $fd; [3]: [cd37]bf.byte $bf; [4]: [cd38]ef.byte $ef; [5]: [cd39]7f.byte $7f; [6]: [cd3a]fb.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:
; ; Read the first two bytes from the offset and set as ; the PPUADDR. ;
[cd3b]a6 1fLDX PPUBuffer_ReadOffset; Load our current PPU buffer offset. [cd3d]bd 00 05LDA a:PPUBuffer,X; Load the first address byte from the buffer. [cd40]e8INX [cd41]a8TAY; Y = byte 1 [cd42]8c 06 20STY a:PPUADDR; Set as the upper byte of the PPU address. [cd45]bd 00 05LDA a:PPUBuffer,X; Load the next address byte. [cd48]e8INX [cd49]86 1fSTX PPUBuffer_ReadOffset; Update the offset to skip these bytes. [cd4b]aaTAX [cd4c]8e 06 20STX a:PPUADDR; Set as the lower byte of the PPU address.
; ; Begin preparing for drawing. ; ; We'll draw 16 bytes from the buffer. ;
[cd4f]a9 10LDA #$10; Set our loop for 16 bytes. [cd51]85 06STA Temp_06 [cd53]ad 07 20LDA a:PPUDATA; Load byte 1 from the PPU. [cd56]@_loop: [cd56]ad 07 20LDA a:PPUDATA; Load a byte from the PPU.
; ; Set the address again. The PPUDATA read advanced the offset. ;
[cd59]8c 06 20STY a:PPUADDR; Set upper byte of PPU address to Y. [cd5c]8e 06 20STX a:PPUADDR; Set lower byte of PPU address to X.
; ; Rotate the tile data from this address right by 1 pixel. ;
[cd5f]48PHA [cd60]4aLSR A; Rotate the pixel data right by 1. [cd61]68PLA [cd62]6aROR A; OR it to the data rotated left by 7. [cd63]8d 07 20STA a:PPUDATA; Store the rotated data. [cd66]e8INX; Increment the lower byte of the PPU address. [cd67]ad 07 20LDA a:PPUDATA
; ; Check if we're at the end of the loop. ; ; If we've processed 16 entries, we're done. ;
[cd6a]c6 06DEC Temp_06; Decrement our loop counter. [cd6c]d0 e8BNE @_loop; If it's > 0, loop. [cd6e]60RTS
;============================================================================ ; 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]a9 09LDA #$09 [cd71]85 99STA CurrentSprite_TargetPPUTileAddr_U; Set upper to 0x09. [cd73]a9 00LDA #$00 [cd75]85 98STA CurrentSprite_TargetPPUTileAddr; Set lower to 0x00. [cd77]60RTS
;============================================================================ ; 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:
; ; Save the current ROM bank to the stack. ;
[cd78]ad 00 01LDA a:CurrentROMBank; Load the current bank. [cd7b]48PHA; Push to the stack.
; ; Switch to the bank for the current sprite's images. ;
[cd7c]ae 86 03LDX a:CurrentSprite_TilesBank; Load the bank where the images for the current sprite are found [cd7f]20 1a ccJSR 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]ad 00 80LDA a:ROMBankStart; Read byte from $8000. [cd85]85 02STA 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]ad 01 80LDA a:ROMBankStart+1; Read byte from $8001. [cd8a]18CLC [cd8b]69 80ADC #$80; Add 0x80. [cd8d]85 03STA 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]ad 8b 03LDA a:CurrentSprite_Entity; Load the current sprite ID [cd92]c9 37CMP #$37; Check if we're on the next bank. [cd94]90 02BCC @_loadImages [cd96]e9 37SBC #$37; We are, so subtract 55 for the new sprite index.
; ; Compute the address for the sprite image. ;
[cd98]@_loadImages: [cd98]0aASL A; Convert the sprite ID to a word boundary. [cd99]a8TAY; Y = A [cd9a]b1 02LDA (Temp_Addr_L),Y; Get the pointer to the sprite data from the bank address computed above. [cd9c]85 96STA CurrentSprite_LoadTileAddr; A = Lower byte of the image data address. [cd9e]c8INY [cd9f]b1 02LDA (Temp_Addr_L),Y; A = Upper byte of the address. [cda1]18CLC [cda2]69 80ADC #$80; Add 0x80 to the upper address. [cda4]85 97STA 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]ad 8b 03LDA a:CurrentSprite_Entity; Load the current sprite ID [cda9]a8TAY [cdaa]b9 1b ceLDA a:SPRITES_PPU_TILE_COUNTS,Y; Get the number of tiles needed [cdad]85 9bSTA CurrentSprite_LoadTileCount; Store that for the render
; ; Restore the previous bank. ;
[cdaf]68PLA; Pop the previous bank. [cdb0]aaTAX; Transfer to X. [cdb1]20 1a ccJSR MMC1_UpdateROMBank; Update to the bank. [cdb4]60RTS
;============================================================================ ; 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:
; ; 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]a5 98LDA CurrentSprite_TargetPPUTileAddr; Load the lower byte of the current PPU tile address to read from. [cdb7]85 00STA Temp_00; Store it temporarily. [cdb9]a5 99LDA CurrentSprite_TargetPPUTileAddr_U; Load the upper byte of the PPU tile address. [cdbb]06 00ASL Temp_00; Multiply by 16. [cdbd]2aROL A [cdbe]06 00ASL Temp_00 [cdc0]2aROL A [cdc1]06 00ASL Temp_00 [cdc3]2aROL A [cdc4]06 00ASL Temp_00 [cdc6]2aROL A; A = Lower byte of the offset. [cdc7]85 9aSTA 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]20 78 cdJSR CurrentSprite_LoadTilesInfo; Look up the sprite data pointer for this. [cdcc]a5 9bLDA CurrentSprite_LoadTileCount; A = Tile count. [cdce]d0 01BNE @_loadSpriteToPPUBuffer; If count > 0, jump to load tile data. [cdd0]60RTS
; ; Switch to the bank containing the tiles to load. ;
[cdd1]@_loadSpriteToPPUBuffer: [cdd1]ad 00 01LDA a:CurrentROMBank; Load the current ROM bank. [cdd4]48PHA; Push it to the stack. [cdd5]ae 86 03LDX a:CurrentSprite_TilesBank; X = Bank for the tiles. [cdd8]20 1a ccJSR MMC1_UpdateROMBank; Switch to that bank.
; ; Set the PPU address to load tiles into. ;
[cddb]a5 99LDA CurrentSprite_TargetPPUTileAddr_U; Load the upper byte of the target PPU address. [cddd]85 e9STA PPU_TargetAddr_U; Set it. [cddf]a5 98LDA CurrentSprite_TargetPPUTileAddr; Load the lower byte. [cde1]85 e8STA PPU_TargetAddr; Set it.
; ; Queue loading 16 bytes for the current tile. ;
[cde3]a9 10LDA #$10; A = 16 (bytes) [cde5]20 dc cfJSR 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]a0 00LDY #$00; Y = 0 (loop counter) [cdea]@_copySpriteImage: [cdea]b1 96LDA (CurrentSprite_LoadTileAddr),Y; Load a byte from the tile. [cdec]9d 00 05STA a:PPUBuffer,X; Write to the PPU buffer at X. [cdef]e8INX; X++ (write offset) [cdf0]c8INY; Y++ (loop counter) [cdf1]c0 10CPY #$10; Is X < 16? [cdf3]90 f5BCC @_copySpriteImage; If so, loop.
; ; 16 bytes of sprite data was written to the PPU buffer. ; Update the new write position for the buffer. ;
[cdf5]86 20STX PPUBuffer_WriteOffset; Store the resulting PPU buffer write offset.
; ; Restore the previous bank. ;
[cdf7]68PLA; Pull the previous bank from the stack. [cdf8]aaTAX; Set to X. [cdf9]20 1a ccJSR MMC1_UpdateROMBank; And switch to it.
; ; Update the source tile address to advance to the next tile. ;
[cdfc]a5 96LDA CurrentSprite_LoadTileAddr; Load the lower byte of the tile address. [cdfe]18CLC [cdff]69 10ADC #$10; Add 16 (the bytes we just read). [ce01]85 96STA CurrentSprite_LoadTileAddr; And store it. [ce03]a5 97LDA CurrentSprite_LoadTileAddr_U; Load the upper byte. [ce05]69 00ADC #$00; Add Carry, if the lower byte wrapped. [ce07]85 97STA CurrentSprite_LoadTileAddr_U; And store it.
; ; Update the target PPU tile address to advance to the next ; tile. ;
[ce09]a5 98LDA CurrentSprite_TargetPPUTileAddr; Load the lower byte of the target address. [ce0b]18CLC [ce0c]69 10ADC #$10; Add 16. [ce0e]85 98STA CurrentSprite_TargetPPUTileAddr; And store it. [ce10]a5 99LDA CurrentSprite_TargetPPUTileAddr_U; Load the upper byte. [ce12]69 00ADC #$00; Add Carry, if the lower byte wrapped. [ce14]85 99STA CurrentSprite_TargetPPUTileAddr_U; And store it.
; ; Decrement the tile count, and loop if there are tiles ; remaining. ;
[ce16]c6 9bDEC CurrentSprite_LoadTileCount; Decrement the remaining tile count. [ce18]d0 b7BNE @_loadSpriteToPPUBuffer; If > 0, loop. [ce1a]60RTS
;============================================================================ ; The number of PPU tiles each sprite needs. ; ; XREFS: ; CurrentSprite_LoadTilesInfo ;============================================================================
; ; XREFS: ; CurrentSprite_LoadTilesInfo ;
[ce1b]SPRITES_PPU_TILE_COUNTS: [ce1b]01.byte $01; [0]: [ce1c]01.byte $01; [1]: Dropped: Bread [ce1d]01.byte $01; [2]: Dropped: Coin [ce1e]01.byte $01; [3]: Enemy: ? [ce1f]10.byte $10; [4]: Enemy: Raiden [ce20]10.byte $10; [5]: Enemy: Necron Aides [ce21]10.byte $10; [6]: Enemy: Zombie [ce22]08.byte $08; [7]: Enemy: Hornet [ce23]06.byte $06; [8]: Enemy: Bihoruda [ce24]06.byte $06; [9]: Enemy: Lilith [ce25]07.byte $07; [10]: Magic: ? [ce26]06.byte $06; [11]: Enemy: Yuinaru [ce27]0c.byte $0c; [12]: Enemy: Snowman [ce28]10.byte $10; [13]: Enemy: Nash [ce29]10.byte $10; [14]: Enemy: Fire Giant [ce2a]12.byte $12; [15]: Enemy: Ishiisu [ce2b]0d.byte $0d; [16]: Enemy: Execution Hood [ce2c]26.byte $26; [17]: Boss: Rokusutahn [ce2d]10.byte $10; [18]: Boss: unused (round body of snake boss) [ce2e]00.byte $00; [19]: Effect: Enemy death [ce2f]00.byte $00; [20]: Effect: Lightning ball [ce30]16.byte $16; [21]: Enemy: Charron [ce31]17.byte $17; [22]: Enemy: ? (Unused) [ce32]10.byte $10; [23]: Enemy: Geributa [ce33]0e.byte $0e; [24]: Enemy: Sugata [ce34]12.byte $12; [25]: Enemy: Grimlock [ce35]0c.byte $0c; [26]: Enemy: Giant Bees [ce36]0e.byte $0e; [27]: Enemy: Myconid [ce37]10.byte $10; [28]: Enemy: Naga [ce38]10.byte $10; [29]: Enemy: Skeleton Knight (unused) [ce39]12.byte $12; [30]: Enemy: Giant Strider [ce3a]12.byte $12; [31]: Enemy: Sir Gawaine [ce3b]1f.byte $1f; [32]: Enemy: Maskman [ce3c]16.byte $16; [33]: Enemy: Wolfman [ce3d]0f.byte $0f; [34]: Enemy: Yareeka [ce3e]10.byte $10; [35]: Enemy: Magman [ce3f]13.byte $13; [36]: Enemy: Curly-tailed guy with spear (unused) [ce40]10.byte $10; [37]: Enemy: ? (unused) [ce41]11.byte $11; [38]: Enemy: Ikeda [ce42]10.byte $10; [39]: Enemy: Muppet guy (unused) [ce43]10.byte $10; [40]: Enemy: Lamprey [ce44]10.byte $10; [41]: Enemy: ? (unused) [ce45]13.byte $13; [42]: Enemy: Monodron [ce46]0c.byte $0c; [43]: Enemy: Winged skeleton (unused) [ce47]12.byte $12; [44]: Enemy: Tamazutsu [ce48]3e.byte $3e; [45]: Boss: Ripasheiku [ce49]33.byte $33; [46]: Boss: Zoradohna [ce4a]1c.byte $1c; [47]: Boss: Borabohra [ce4b]0e.byte $0e; [48]: Boss: Pakukame [ce4c]25.byte $25; [49]: Boss: Zorugeriru [ce4d]54.byte $54; [50]: Boss: King Grieve [ce4e]69.byte $69; [51]: Boss: Shadow Eura [ce4f]10.byte $10; [52]: NPC: Walking Man 1 [ce50]10.byte $10; [53]: NPC: Blue lady (unused) [ce51]09.byte $09; [54]: NPC: Child (unused) [ce52]08.byte $08; [55]: NPC: Armor Salesman [ce53]0b.byte $0b; [56]: NPC: Martial Artist [ce54]0b.byte $0b; [57]: NPC: Priest [ce55]14.byte $14; [58]: NPC: King [ce56]0c.byte $0c; [59]: NPC: Magic Teacher [ce57]08.byte $08; [60]: NPC: Key Salesman [ce58]0a.byte $0a; [61]: NPC: Smoking Man [ce59]0e.byte $0e; [62]: NPC: Man in Chair [ce5a]0a.byte $0a; [63]: NPC: Sitting Man [ce5b]0d.byte $0d; [64]: NPC: Meat Salesman [ce5c]10.byte $10; [65]: NPC: Lady in Blue Dress with Cup [ce5d]10.byte $10; [66]: NPC: Guard [ce5e]0b.byte $0b; [67]: NPC: Doctor [ce5f]0e.byte $0e; [68]: NPC: Walking Woman 1 [ce60]0d.byte $0d; [69]: NPC: Walking Woman 2 [ce61]09.byte $09; [70]: Enemy: Eyeball (unused) [ce62]08.byte $08; [71]: Enemy: Zozura [ce63]02.byte $02; [72]: Item: Glove [ce64]02.byte $02; [73]: Item: Black Onyx [ce65]04.byte $04; [74]: Item: Pendant [ce66]02.byte $02; [75]: Item: Red Potion [ce67]02.byte $02; [76]: Item: Poison [ce68]04.byte $04; [77]: Item: Elixir [ce69]02.byte $02; [78]: Item: Ointment [ce6a]00.byte $00; [79]: Trigger: Intro [ce6b]02.byte $02; [80]: Item: Mattock [ce6c]00.byte $00; [81]: Magic: ? [ce6d]0c.byte $0c; [82]: Effect: Fountain [ce6e]00.byte $00; [83]: Magic: ? [ce6f]00.byte $00; [84]: Magic: Enemy Fireball [ce70]04.byte $04; [85]: Item: Wing Boots [ce71]02.byte $02; [86]: Item: Hour Glass [ce72]04.byte $04; [87]: Item: Magical Rod [ce73]02.byte $02; [88]: Item: Battle Suit [ce74]04.byte $04; [89]: Item: Battle Helmet [ce75]04.byte $04; [90]: Item: Dragon Slayer [ce76]02.byte $02; [91]: Item: Mattock [ce77]04.byte $04; [92]: Item: Wing Boots (from quest) [ce78]02.byte $02; [93]: Item: Red Potion [ce79]02.byte $02; [94]: Item: Poison [ce7a]02.byte $02; [95]: Item: Glove [ce7b]02.byte $02; [96]: Item: Ointment [ce7c]0c.byte $0c; [97]: Effect: Spring of Trunk [ce7d]0c.byte $0c; [98]: Effect: Spring of Sky [ce7e]0c.byte $0c; [99]: Effect: Spring of Tower [ce7f]00.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:
; ; Save the current ROM bank and switch to bank 8. ;
[ce80]ad 00 01LDA a:CurrentROMBank; Load the current ROM bank. [ce83]48PHA; Push it to the stack. [ce84]a2 08LDX #$08; Bank 8 == sprite data. [ce86]20 1a ccJSR MMC1_UpdateROMBank; Switch to the bank.
; ; Set the start address for the sprites to load. ;
[ce89]ad 08 80LDA a:TILES_COMMON_START_REF; Load the lower byte of the address of the first sprite (a coin). [ce8c]85 02STA Temp_Addr_L; Store it. [ce8e]ad 09 80LDA a:TILES_COMMON_START_REF+1; Load the upper byte. [ce91]18CLC; Clear Carry. [ce92]69 80ADC #$80; Add 0x80 to the upper byte. [ce94]85 03STA 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]a9 05LDA #$05; 5 = number of passes of 256 bytes of sprite tiles to load. [ce98]85 00STA Temp_00; Store it for the loop.
; ; Set the PPU address to $0400 (within the loaded PPU tiles). ;
[ce9a]a9 04LDA #$04; 0x04 == Upper byte. [ce9c]8d 06 20STA a:PPUADDR; Write it. [ce9f]a0 00LDY #$00; 0x00 == Lower byte and loop counter. [cea1]8c 06 20STY a:PPUADDR; Write it.
; ; Begin loading and writing sprites, 5 passes of 256 bytes. ;
[cea4]@_loadLoop: [cea4]b1 02LDA (Temp_Addr_L),Y; Load the address of the sprite. [cea6]8d 07 20STA a:PPUDATA; Write to the PPU data. [cea9]c8INY; Y++ (loop counter). [ceaa]d0 f8BNE @_loadLoop; If not incremented back up to 0, loop. [ceac]e6 03INC Temp_Addr_U; Increment the upper byte of the write address. [ceae]c6 00DEC Temp_00; Decrement the number of passes remaining. [ceb0]d0 f2BNE @_loadLoop; If > 0, loop.
; ; Loading is complete. Restore the previous bank. ;
[ceb2]68PLA; Pop the previous bank from the stack. [ceb3]aaTAX; X = A (bank) [ceb4]20 1a ccJSR MMC1_UpdateROMBank; Switch back to the bank. [ceb7]60RTS
;============================================================================ ; 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:
; ; Load the address for the tile data based on the tiles index. ;
[ceb8]a5 95LDA Area_TilesIndex; Set A = tileset index [ceba]0aASL A; Set A = tileset index * 2 (word offset) [cebb]a8TAY; Set Y = word offset into the pointer table [cebc]b9 07 cfLDA a:TILE_INDEX_TO_ADDR,Y; Read the low byte from the table [cebf]85 93STA Area_TilesReadAddress; Save low byte to TilesAddress [cec1]b9 08 cfLDA a:TILE_INDEX_TO_ADDR+1,Y; Read the high byte from the table [cec4]85 94STA Area_TilesReadAddress+1; Set high byte to TilesAddress
; ; Set the PPU to increment by +1 after each PPUDATA write. ;
[cec6]20 2b cfJSR 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]a4 95LDY Area_TilesIndex; Load the tiles index. [cecb]b9 19 cfLDA a:TILE_INDEX_TO_PPU_ADDR_UPPER,Y; Convert to a PPU address via lookup table (0x18 or 0x1A). [cece]8d 06 20STA a:PPUADDR; Write as the upper byte. [ced1]a9 00LDA #$00; 0x00 = Lower byte (start of page). [ced3]8d 06 20STA 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]85 08STA Temp_08; Set lower byte of the remaining tile count to 0. [ced8]b9 22 cfLDA a:TILE_INDEX_TO_NUM_CHR_PAGES,Y; Load the number of pages to load. [cedb]85 09STA Temp_09; Set as the upper byte.
; ; Save the current bank and switch to bank 4 (tile data). ;
[cedd]ad 00 01LDA a:CurrentROMBank; A = Current ROM bank. [cee0]48PHA; Push it to the stack. [cee1]a2 04LDX #$04; 4 = Tiles [cee3]20 1a ccJSR MMC1_UpdateROMBank; Switch to bank 4 (tile data)
; ; Begin loading tiles into PPUDATA. ;
[cee6]a0 00LDY #$00; Set Y = 0 (start page of loop)
; ; Stream 1 byte from tiles to the PPU. ;
[cee8]@_copyLoop: [cee8]b1 93LDA (Area_TilesReadAddress),Y; Load a byte from the tiles. [ceea]8d 07 20STA 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]c8INY; Y++ (source pointer). [ceee]d0 02BNE @_noCarry; If wrapped to 0, jump. [cef0]e6 94INC Area_TilesReadAddress+1; Increment the upper byte of the tiles address.
; ; Decrement the 16-bit remaining tile count. ;
[cef2]@_noCarry: [cef2]a5 08LDA Temp_08; Load the lower byte of the remaining count. [cef4]38SEC [cef5]e9 01SBC #$01; Subtract 1. [cef7]85 08STA Temp_08; And set it. [cef9]a5 09LDA Temp_09; Load the upper byte of the remaining count. [cefb]e9 00SBC #$00; Subtract carry, if lower wrapped. [cefd]85 09STA Temp_09; And set it. [ceff]b0 e7BCS @_copyLoop; If count > 0, loop.
; ; Switch back to the previous bank. ;
[cf01]68PLA; Pop the previous bank. [cf02]aaTAX; Transfer to X. [cf03]20 1a ccJSR MMC1_UpdateROMBank; And switch to it. [cf06]60RTS
;============================================================================ ; Offsets of the tiles into bank 4. ; ; Index into the table are based on Area_TilesIndex. ; ; XREFS: ; Area_LoadTiles ;============================================================================
; ; XREFS: ; Area_LoadTiles ;
[cf07]TILE_INDEX_TO_ADDR: [cf07]00 80pointer AREA_TILESETS_EOLIS; [0]: Eolis [cf09]00 98pointer AREA_TILESETS_BRANCH; [1]: Branch [cf0b]00 88pointer AREA_TILESETS_TRUNK; [2]: Trunk [cf0d]00 90pointer AREA_TILESETS_MIST; [3]: Mist [cf0f]00 b8pointer AREA_TILESETS_DARTMOOR_EVIL_LAIR; [4]: Dartmoor Castle Evil Lair [cf11]00 a0pointer AREA_TILESETS_TOWNS; [5]: Towns [cf13]00 a8pointer AREA_TILESETS_KINGSROOM_GURU_HOSPITAL; [6]: King's Room Guru Room Hospital [cf15]00 aepointer AREA_TILESETS_SHOPS_HOUSE_TAVERN; [7]: Tavern Tool Shop Key Shop House Meat Shop [cf17]00 b4pointer 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]18.byte $18; [0]: Eolis [cf1a]18.byte $18; [1]: Branches [cf1b]18.byte $18; [2]: Trunk [cf1c]18.byte $18; [3]: Mist [cf1d]18.byte $18; [4]: Dartmoor Castle Evil Lair [cf1e]18.byte $18; [5]: Towns [cf1f]1a.byte $1a; [6]: King's Room Guru Room Hospital [cf20]1a.byte $1a; [7]: Tavern Tool Shop Key Shop House Meat Shop [cf21]1a.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]08.byte $08; [0]: Eolis [cf23]08.byte $08; [1]: Branches [cf24]08.byte $08; [2]: Trunk [cf25]08.byte $08; [3]: Mist [cf26]08.byte $08; [4]: Dartmoor Castle Evil Lair [cf27]08.byte $08; [5]: Towns [cf28]06.byte $06; [6]: King's Room Guru Room Hospital [cf29]06.byte $06; [7]: Tavern Tool Shop Key Shop House Meat Shop [cf2a]04.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]a5 0aLDA PPU_ControlFlags; Load the PPU control flags. [cf2d]29 fbAND #$fb; Set VRAM address increment per CPU read/write of PPUDATA to 0: Add 1, going across. [cf2f]85 0aSTA PPU_ControlFlags; Set it to SavedPPUCtrl. [cf31]8d 00 20STA a:PPUCTRL; Set it to PPUCTRL. [cf34]60RTS
;============================================================================ ; 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]a9 00LDA #$00 [cf37]85 1fSTA PPUBuffer_ReadOffset; Set offset = 0 [cf39]85 20STA PPUBuffer_WriteOffset; Set upper bounds = 0
; ; XREFS: ; PPUBuffer_Draw ;
[cf3b]RETURN_CF3B: [cf3b]60RTS
;============================================================================ ; 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:
; ; Check if there's anything in the buffer to write. ; If not, we're done. ;
[cf3c]a5 1fLDA PPUBuffer_ReadOffset; A = current buffer offset [cf3e]c5 20CMP PPUBuffer_WriteOffset; Compare against the upper bound of the PPU buffer. [cf40]f0 f9BEQ 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]a6 1fLDX PPUBuffer_ReadOffset; X = current PPU offset [cf44]a9 00LDA #$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]38SEC [cf47]fd 00 05SBC a:PPUBuffer,X; Get the value from the buffer at this offset. [cf4a]c9 07CMP #$07; Check if it's a command code. [cf4c]b0 0dBCS @_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]e6 1fINC PPUBuffer_ReadOffset; Consume the byte in the buffer. [cf50]0aASL A [cf51]a8TAY; Begin looking up in the table. [cf52]b9 bd cfLDA a:PPUBUFFER_DRAW_COMMANDS+1,Y; High byte of the command address. [cf55]48PHA; Push to the stack. [cf56]b9 bc cfLDA a:PPUBUFFER_DRAW_COMMANDS,Y; Low byte of the command address. [cf59]48PHA; Push to the stack. [cf5a]60RTS; Return. The next address following will execute.
; ; This is standard data to write to the PPU, not a command. ;
[cf5b]@_beginDraw: [cf5b]a9 d0LDA #$d0; Total byte length that can be written. [cf5d]85 22STA PPUBuffer_Temp_TotalByteLength; Set it. [cf5f]a9 06LDA #$06; Max number of entries in the buffer that can be written. [cf61]85 21STA PPUBuffer_Temp_PendingEntryCount; Set it.
; ; Loop through the buffer until either it's cleared or we've ; processed 6 entries. ;
[cf63]@_loopBuffer: [cf63]a6 1fLDX PPUBuffer_ReadOffset; X = Read offset into the buffer. [cf65]e4 20CPX PPUBuffer_WriteOffset; Compare it to the write offset. [cf67]f0 4aBEQ @_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]a5 0aLDA PPU_ControlFlags; A = PPU control flags. [cf6b]29 fbAND #$fb; Set to Add 1 Across. [cf6d]a8TAY; Y = Resulting flags. [cf6e]bd 00 05LDA a:PPUBuffer,X; Load the byte from the buffer. [cf71]10 06BPL @_processData; If bit 7 is set, jump. [cf73]29 7fAND #$7f; Else, set VBLank Disable. [cf75]c8INY; And Inc Add 32 Down. [cf76]c8INY [cf77]c8INY [cf78]c8INY [cf79]@_processData: [cf79]8c 00 20STY 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]bd 00 05LDA a:PPUBuffer,X; A = value from the buffer [cf7f]29 7fAND #$7f; Mask out control bit 7. [cf81]a8TAY; Y = A (save until we load the address) [cf82]e8INX; Set to the next byte. [cf83]bd 00 05LDA a:PPUBuffer,X; Load the upper PPU byte address from the buffer. [cf86]8d 06 20STA a:PPUADDR; Set as the upper byte in PPUADDR. [cf89]e8INX; X = X + 1 [cf8a]bd 00 05LDA a:PPUBuffer,X; Load the lower PPU byte address from the buffer. [cf8d]8d 06 20STA a:PPUADDR; Set as the lower byte in PPUADDR. [cf90]e8INX; X = X + 1 [cf91]98TYA; Y = A (restore from above) [cf92]18CLC [cf93]65 22ADC PPUBuffer_Temp_TotalByteLength; Add the length to our running total. [cf95]85 22STA PPUBuffer_Temp_TotalByteLength
; ; Begin writing <length> bytes from the buffer to the PPU. ;
[cf97]@_loopData: [cf97]bd 00 05LDA a:PPUBuffer,X; Load the byte to draw at our current offset. [cf9a]e8INX; Increment the offset. [cf9b]8d 07 20STA a:PPUDATA; Set the byte in our PPUDATA. [cf9e]88DEY; Decrement the nuber of bytes to write. [cf9f]d0 f6BNE @_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]86 1fSTX PPUBuffer_ReadOffset; Increment our offset into the buffer to skip the length and address bytes and all the data bytes. [cfa3]c6 21DEC PPUBuffer_Temp_PendingEntryCount; Decrement our total allowed entries for this operation. [cfa5]f0 0cBEQ @_finishLoop; If we can't do any more, we're done. [cfa7]bc 00 05LDY a:PPUBuffer,X; Load the next byte to process from the buffer. [cfaa]88DEY [cfab]c0 f9CPY #$f9; Check if it's a command byte. [cfad]b0 04BCS @_finishLoop; If it is, we're done. This will be handled next call. [cfaf]a5 22LDA PPUBuffer_Temp_TotalByteLength; Check the total byte length. [cfb1]30 b0BMI @_loopBuffer; If there's room to go, loop.
; ; We're done with everything. Clear out the PPU addresses and ; return. ;
[cfb3]@_finishLoop: [cfb3]a9 00LDA #$00 [cfb5]8d 06 20STA a:PPUADDR; Set upper PPUADDR to 0. [cfb8]8d 06 20STA 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]60RTS
;============================================================================ ; Handlers for special PPU buffer draw commands. ; ; XREFS: ; PPUBuffer_Draw ;============================================================================
; ; XREFS: ; PPUBuffer_Draw ;
[cfbc]PPUBUFFER_DRAW_COMMANDS: [cfbc]15 d0pointer PPUBuffer_DrawCommand_WritePalette-1; [0]: Command 0x00: Write Palette to PPU [cfbe]ba cfpointer PPUBuffer_DrawCommand_Noop-1; [1]: Command 0xFF [cfc0]ba cfpointer PPUBuffer_DrawCommand_Noop-1; [2]: Command 0xFE [cfc2]ba cfpointer PPUBuffer_DrawCommand_Noop-1; [3]: Command 0xFD [cfc4]3a cdpointer PPUBuffer_DrawCommand_RotateTilesRight1Pixel-1; [4]: Command 0xFC: Rotate Tiles Right [cfc6]ba cfpointer PPUBuffer_DrawCommand_Noop-1; [5]: Command 0xFB [cfc8]eb ccpointer 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]20 d0 cfJSR PPUBuffer_HasCapacity; Is there capacity? [cfcd]90 fbBCC PPUBuffer_WaitForCapacity; If not, loop. [cfcf]60RTS
;============================================================================ ; 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]a5 1fLDA PPUBuffer_ReadOffset; A = PPU buffer offset. [cfd2]38SEC [cfd3]e5 20SBC PPUBuffer_WriteOffset; A = offset - upper [cfd5]f0 02BEQ @_return; If (offset - upper) == 0, jump to return false. [cfd7]c9 24CMP #$24; Set whether there's at least 36 bytes difference. [cfd9]@_return: [cfd9]60RTS [cfda]09 80.byte $09,$80; 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_WriteBlockDataColumn1 ; Area_SetBlocks_WriteBlockDataColumn2 ; 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_SetHUDBarValue ;============================================================================
[cfdc]PPUBuffer_QueueCommandOrLength:
; ; Wait for capacity in the buffer. ;
[cfdc]48PHA; Push the length/command to the stack. [cfdd]20 ca cfJSR PPUBuffer_WaitForCapacity; Wait for capacity in the buffer.
; ; There's capacity. Set up to write. ; ; Start with the length or command byte. ;
[cfe0]68PLA [cfe1]a6 20LDX PPUBuffer_WriteOffset; Load the upper bounds of the buffer. [cfe3]9d 00 05STA a:PPUBuffer,X; Store the provided value (A) there. [cfe6]e8INX [cfe7]a5 e9LDA PPU_TargetAddr_U; Load the upper address byte. [cfe9]9d 00 05STA a:PPUBuffer,X; Write it to the buffer. [cfec]e8INX [cfed]a5 e8LDA PPU_TargetAddr; Load the lower address byte. [cfef]9d 00 05STA a:PPUBuffer,X; Write it to the buffer. [cff2]e8INX [cff3]60RTS
;============================================================================ ; 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]a5 20LDA PPUBuffer_WriteOffset; Load the upper bounds of the buffer. [cff6]c5 1fCMP PPUBuffer_ReadOffset; Does it == the offset? [cff8]d0 faBNE PPUBuffer_WaitUntilClear; If not, loop. [cffa]60RTS
;============================================================================ ; 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]20 3c cfJSR PPUBuffer_Draw; Draw from the buffer. [cffe]a5 20LDA PPUBuffer_WriteOffset; Load the upper bounds. [d000]c5 1fCMP PPUBuffer_ReadOffset; Does it match the offset? [d002]d0 f7BNE PPUBuffer_DrawAll; If not, loop. [d004]60RTS
;============================================================================ ; 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]a9 04LDA #$04 [d007]20 3b d0JSR Screen_LoadUIPalette [d00a]4c 90 d0JMP 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]ad d0 03LDA a:Screen_PaletteIndex [d010]20 3b d0JSR Screen_LoadUIPalette [d013]4c 90 d0JMP 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:
; ; Write all 32 palettes to 0x3F00. ;
[d016]a9 3fLDA #$3f; 0x3F == Upper byte. [d018]8d 06 20STA a:PPUADDR; Write as the upper PPU address. [d01b]a2 00LDX #$00; 0x00 == Lower byte. [d01d]8e 06 20STX a:PPUADDR; Write as the lower PPU address.
; ; Loop through 32 bytes of palette data and write to the PPU. ;
[d020]a0 20LDY #$20; Y = 32 (loop counter). [d022]@_paletteLoop: [d022]bd 93 02LDA a:Screen_PaletteData_Tiles,X; A = Palette byte at the index. [d025]8d 07 20STA a:PPUDATA; Write to the PPU. [d028]e8INX; Increment the PPU address. [d029]88DEY; Y-- (loop counter). [d02a]d0 f6BNE @_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]a9 3fLDA #$3f; 0x3F == Upper byte. [d02e]8d 06 20STA a:PPUADDR; Write as the upper PPU address. [d031]8c 06 20STY a:PPUADDR; Zero out the lower byte. [d034]8c 06 20STY a:PPUADDR; Now zero out the upper byte. [d037]8c 06 20STY a:PPUADDR; And the lower again. [d03a]60RTS
;============================================================================ ; 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]a8TAY; Y = A (palette index).
; ; Save the current bank and load bank 11 (palette data). ;
[d03c]ad 00 01LDA a:CurrentROMBank; Load the current ROM bank. [d03f]48PHA; Push it to the stack. [d040]a2 0bLDX #$0b [d042]20 1a ccJSR MMC1_UpdateROMBank; Switch to bank 11.
; ; Set the attribute data index for the HUD. ;
[d045]b9 f0 81LDA a:BYTE_81f0,Y; Load the HUD attribute data for this index. [d048]8d 8d 03STA a:UI_AttributeDataIndex; Set it for the HUD/textboxes.
; ; Restore our current bank. ;
[d04b]68PLA; Pop A (previous bank) from the stack. [d04c]aaTAX; X = A [d04d]20 1a ccJSR MMC1_UpdateROMBank; Switch back to the bank.
; ; Get the offset into the bank. ;
[d050]98TYA; A = Y (palette index). [d051]20 9b d0JSR Palette_IndexToROMOffset16; Convert to a relative offset into the bank. [d054]69 00ADC #$00; Add 0 + C (also 0) to the lower byte. [d056]85 08STA Temp_08; Store it. [d058]a5 09LDA Temp_09; Load the computed upper byte. [d05a]69 80ADC #$80; Add 0x80. [d05c]85 09STA Temp_09; Store it.
; ; Write that palette into the sprite palette. ;
[d05e]a0 0fLDY #$0f; Start writing into index 16 (sprite palette). [d060]d0 12BNE 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]8d d4 03STA 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]20 9b d0JSR Palette_IndexToROMOffset16; Convert the relative address for the palette. [d068]69 c0ADC #$c0; Add 0xC0 to the lower byte. [d06a]85 08STA Temp_08; Store it. [d06c]a5 09LDA Temp_09; Load the computed upper byte. [d06e]69 81ADC #$81; Add 0x81 to it. [d070]85 09STA Temp_09; Store it back.
; ; Save the previous bank and switch to bank 11 (palette data). ;
[d072]a0 1fLDY #$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:
; ; Save the current bank. ;
[d074]ad 00 01LDA a:CurrentROMBank; Load the current ROM bank. [d077]48PHA; Push it to the stack. [d078]a2 0bLDX #$0b [d07a]20 1a ccJSR MMC1_UpdateROMBank; Switch to bank 11.
; ; Copy 16 bytes of palette into ROM. ;
[d07d]98TYA; A = Y (palette data index) [d07e]aaTAX; X = A [d07f]a0 0fLDY #$0f; Y = 15 (loop counter) [d081]@_loadPaletteLoop: [d081]b1 08LDA (Temp_08),Y; Load the byte to copy. [d083]9d 93 02STA a:Screen_PaletteData_Tiles,X; Store as the palette data at the index. [d086]caDEX; X-- (destination index) [d087]88DEY; Y-- (loop counter) [d088]10 f7BPL @_loadPaletteLoop; If >= 0, loop.
; ; Switch bank to the previous bank. ;
[d08a]68PLA; Pop the previous bank. [d08b]aaTAX; X = A [d08c]20 1a ccJSR MMC1_UpdateROMBank; Switch to the bank. [d08f]60RTS
;============================================================================ ; 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]a9 00LDA #$00; A = 0 [d092]a6 20LDX PPUBuffer_WriteOffset; X = upper bounds of the buffer [d094]9d 00 05STA a:PPUBuffer,X; Store the 0 in the buffer at the upper bounds. [d097]e8INX; Increment the upper bounds. [d098]86 20STX PPUBuffer_WriteOffset [d09a]60RTS
;============================================================================ ; 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]a0 00LDY #$00; Y = 0 [d09d]84 09STY Temp_09; Store it temporarily. [d09f]0aASL A; Shift left, upper bit into Carry. [d0a0]26 09ROL Temp_09; Rotate left, carry into bit 0, bit 7 into carry. [d0a2]0aASL A; Repeat 3 more times, moving lower nibble into A, upper nibble into Temp_09. [d0a3]26 09ROL Temp_09 [d0a5]0aASL A [d0a6]26 09ROL Temp_09 [d0a8]0aASL A [d0a9]26 09ROL Temp_09 [d0ab]18CLC; Clear carry. [d0ac]60RTS; 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:
; ; Get an address for the provided palette in bank 11. ;
[d0ad]20 9b d0JSR Palette_IndexToROMOffset16; Convert the palette to a 16-bit integer. [d0b0]69 00ADC #$00; Add 0 and carry (which is also 0) to the lower byte. [d0b2]85 08STA Temp_08; Store temporarily as the lower byte. [d0b4]a5 09LDA Temp_09; Load it back into A. [d0b6]69 80ADC #$80; Add 0x80. [d0b8]85 09STA Temp_09; Store as the upper byte.
; ; Save the current bank and switch to bank 11. ;
[d0ba]ad 00 01LDA a:CurrentROMBank; Load the current bank. [d0bd]48PHA; Push it to the stack. [d0be]a2 0bLDX #$0b; 11 = Sprite info/palettes bank. [d0c0]20 1a ccJSR MMC1_UpdateROMBank; Switch to that bank. [d0c3]ae 30 04LDX 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]a0 0fLDY #$0f; Y = 15 (loop counter). [d0c8]@_loop: [d0c8]b1 08LDA (Temp_08),Y; Load the palette data at Y. [d0ca]38SEC; Set C = 1 so SBC won't subtract C. [d0cb]fd e0 d0SBC a:FADE_OUT_DELTA_TABLE,X; Subtract the value form the lookup table at the transition index. [d0ce]b0 02BCS @_setPaletteData; If > 0, jump to use this value as the palette.
; ; The subtraction yielded a palette value <= 0, so ; hard-code to 15. ;
[d0d0]a9 0fLDA #$0f; Set palette = 15. [d0d2]@_setPaletteData: [d0d2]99 93 02STA a:Screen_PaletteData_Tiles,Y; Set the palette data to A at index Y. [d0d5]88DEY; Y-- [d0d6]10 f0BPL @_loop; If >= 0, loop.
; ; Restore the previous bank. ;
[d0d8]68PLA; Pull the previous bank from the stack. [d0d9]aaTAX; Set in X. [d0da]20 1a ccJSR MMC1_UpdateROMBank; And switch to that bank. [d0dd]4c 90 d0JMP 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]10.byte $10; [0]: [d0e1]20.byte $20; [1]: [d0e2]30.byte $30; [2]: [d0e3]40.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]8d 36 04STA 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]8aTXA; Push X [d0e8]48PHA [d0e9]98TYA; Push Y [d0ea]48PHA [d0eb]ad 36 04LDA a:Temp_SoundIDToPlay; Load the sound ID we stored. [d0ee]20 6f f3JSR SoundEffect_SetCurrent; Play the sound.
; ; Pop X and Y from the stack and restore. ;
[d0f1]68PLA; Pop A into Y. [d0f2]a8TAY [d0f3]68PLA; Pop A into X. [d0f4]aaTAX [d0f5]60RTS
;============================================================================ ; TODO: Document Area_ScrollScreenRight ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Screen_SetupNew ;============================================================================
[d0f6]Area_ScrollScreenRight: [d0f6]a9 00LDA #$00 [d0f8]85 47STA Screen_Maybe_ScrollHorizDirection [d0fa]85 6dSTA MaybeUnused_006d [d0fc]85 47STA Screen_Maybe_ScrollHorizDirection [d0fe]a9 00LDA #$00 [d100]85 57STA PPU_ScrollY [d102]85 0cSTA PPU_ScrollX [d104]85 0dSTA PPU_ScrollScreen [d106]85 77STA Screen_NextScrollDataHandlerIndex [d108]85 73STA Screen_ScrollHandlersPending [d10a]85 74STA Screen_ScrollHorizBlocksLoaded [d10c]85 75STA Screen_MaybeUnused_0075 [d10e]85 76STA Screen_ScrollHorizAttrsLoaded [d110]a2 00LDX #$00 [d112]86 0cSTX PPU_ScrollX [d114]e8INX [d115]86 0dSTX PPU_ScrollScreen [d117]a2 01LDX #$01 [d119]20 27 d1JSR Area_LoadScrollData [d11c]@_loop: [d11c]20 e7 d2JSR Screen_HandleScroll [d11f]20 1d d6JSR Screen_RunWriteScrollDataHandler [d122]a5 54LDA Screen_ScrollDirection [d124]10 f6BPL @_loop [d126]60RTS
;============================================================================ ; TODO: Document Area_LoadScrollData ; ; INPUTS: ; X ; ; OUTPUTS: ; TODO ; ; XREFS: ; Area_ScrollScreenRight ; Area_ScrollTo ;============================================================================
[d127]Area_LoadScrollData: [d127]86 54STX Screen_ScrollDirection [d129]a5 0dLDA PPU_ScrollScreen [d12b]85 59STA PPU_ScrollScreenHoriz [d12d]a5 0cLDA PPU_ScrollX [d12f]85 45STA Screen_ScrollHorizLoadCounter [d131]ad 00 01LDA a:CurrentROMBank [d134]48PHA [d135]a2 03LDX #$03 [d137]20 1a ccJSR MMC1_UpdateROMBank [d13a]a9 00LDA #$00 [d13c]85 03STA Temp_Addr_U [d13e]a5 63LDA Area_CurrentScreen [d140]0aASL A [d141]26 03ROL Temp_Addr_U [d143]0aASL A [d144]26 03ROL Temp_Addr_U [d146]18CLC [d147]65 8aADC CurrentArea_ScrollingDataAddr [d149]85 02STA Temp_Addr_L [d14b]a5 03LDA Temp_Addr_U [d14d]65 8bADC ScrollingData_U [d14f]85 03STA Temp_Addr_U [d151]a0 00LDY #$00 [d153]@_loadScrollData: [d153]b1 02LDA (Temp_Addr_L),Y [d155]99 66 00STA a:Area_ScreenToTheLeft,Y [d158]c8INY [d159]c0 04CPY #$04 [d15b]90 f6BCC @_loadScrollData [d15d]68PLA [d15e]aaTAX [d15f]20 1a ccJSR MMC1_UpdateROMBank [d162]a6 8cLDX CurrentArea_ROMBank [d164]20 15 ccJSR MMC1_SaveROMBankAndUpdateTo
; ; Load blocks to the left of the screen. ;
[d167]a5 66LDA Area_ScreenToTheLeft [d169]20 f6 d1JSR Area_LoadBlocks [d16c]a2 0fLDX #$0f [d16e]a0 00LDY #$00 [d170]@_copyLastColumnLeftScreenLoop: [d170]bd 00 06LDA a:ScreenBuffer,X [d173]99 e6 03STA a:LastColumnLeftScreen,Y [d176]8aTXA [d177]18CLC [d178]69 10ADC #$10 [d17a]aaTAX [d17b]c8INY [d17c]c0 10CPY #$10 [d17e]90 f0BCC @_copyLastColumnLeftScreenLoop
; ; Load blocks to the right of the screen. ;
[d180]a5 67LDA Area_ScreenToTheRight [d182]20 f6 d1JSR Area_LoadBlocks [d185]a2 00LDX #$00 [d187]a0 00LDY #$00 [d189]@_copyRowScreenAboveLoop: [d189]bd 00 06LDA a:ScreenBuffer,X [d18c]99 f6 03STA a:FirstColumnInRightScreen,Y [d18f]8aTXA [d190]18CLC [d191]69 10ADC #$10 [d193]aaTAX [d194]c8INY [d195]c0 10CPY #$10 [d197]90 f0BCC @_copyRowScreenAboveLoop
; ; Load blocks from the screen above. ;
[d199]a5 68LDA Area_ScreenAbove [d19b]20 f6 d1JSR Area_LoadBlocks; Load blocks from screen above. [d19e]a2 f0LDX #$f0; X = 240 [d1a0]@_copyRowScreenBelowLoop: [d1a0]bd d0 05LDA a:$05d0,X [d1a3]9d 16 03STA a:$0316,X [d1a6]e8INX; X++ [d1a7]d0 f7BNE @_copyRowScreenBelowLoop
; ; Load blocks from the screen below. ;
[d1a9]a5 69LDA Area_ScreenBelow [d1ab]20 f6 d1JSR Area_LoadBlocks; Load blocks from screen below. [d1ae]a2 f0LDX #$f0 [d1b0]@LAB_PRG15_MIRROR__d1b0: [d1b0]bd 10 05LDA a:$0510,X [d1b3]9d 26 03STA a:$0326,X [d1b6]e8INX [d1b7]d0 f7BNE @LAB_PRG15_MIRROR__d1b0
; ; Load blocks for the current screen. ;
[d1b9]a5 63LDA Area_CurrentScreen [d1bb]20 f6 d1JSR Area_LoadBlocks [d1be]20 e7 ccJSR MMC1_RestorePrevROMBank [d1c1]a5 54LDA Screen_ScrollDirection [d1c3]c9 02CMP #$02 [d1c5]b0 0fBCS @_VerticalScroll [d1c7]aaTAX [d1c8]a9 00LDA #$00 [d1ca]85 b4STA Screen_ScrollPlayerTransitionCounter [d1cc]a5 a1LDA Player_PosY [d1ce]85 b3STA Maybe_PlayerY_ForScroll [d1d0]bd e7 d1LDA a:SCREEN_MAYBE_PLAYERX_FOR_SCROLL_MODE,X [d1d3]85 b2STA Maybe_PlayerX_ForScroll [d1d5]60RTS [d1d6]@_VerticalScroll: [d1d6]29 01AND #$01 [d1d8]aaTAX [d1d9]a5 9eLDA Player_PosX_Block [d1db]85 b2STA Maybe_PlayerX_ForScroll [d1dd]a9 00LDA #$00 [d1df]85 b4STA Screen_ScrollPlayerTransitionCounter [d1e1]bd e9 d1LDA a:SCREEN_MAYBE_PLAYERX_FOR_SCROLL_MODE_2_,X [d1e4]85 b3STA Maybe_PlayerY_ForScroll [d1e6]60RTS
; ; XREFS: ; Area_LoadScrollData ;
[d1e7]SCREEN_MAYBE_PLAYERX_FOR_SCROLL_MODE: [d1e7]00.byte $00; [0]: [d1e8]f0.byte $f0; [1]:
; ; XREFS: ; Area_LoadScrollData ;
[d1e9]SCREEN_MAYBE_PLAYERX_FOR_SCROLL_MODE_2_: [d1e9]00.byte $00; [2]: [d1ea]d0.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]a2 00LDX #$00; X = 0 [d1ed]a9 00LDA #$00; A = 0
; ; Begin writing empty blocks. ;
[d1ef]@_loop: [d1ef]9d 00 06STA a:ScreenBuffer,X; Write 0 to the screen buffer. [d1f2]e8INX; X = X + 1 [d1f3]d0 faBNE @_loop; If X hasn't wrapped back to 0, loop. [d1f5]60RTS
;============================================================================ ; 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_LoadScrollData ;============================================================================
[d1f6]Area_LoadBlocks: [d1f6]c9 ffCMP #$ff; Check if the screen index is set. [d1f8]f0 f1BEQ 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]0aASL A; Multiply screen index by 2 [d1fb]a8TAY; Y = New screen index offset value [d1fc]b1 7aLDA (Area_ScreenBlocksOffset),Y; Set the lower block data address for this screen index. [d1fe]85 08STA Temp_08; Store it. [d200]c8INY; Y++ [d201]b1 7aLDA (Area_ScreenBlocksOffset),Y; Set the upper block data address for this screen index. [d203]18CLC [d204]69 80ADC #$80; Add 0x80 to the upper byte. [d206]85 09STA Temp_09; Store this as our total CHR page count.
; ; Set our initial state for compressed block loading. ;
[d208]a9 00LDA #$00; A = 0 [d20a]85 5eSTA LoadCompressedScreenData_ByteOffset; Set byte offset to 0. [d20c]85 5dSTA Temp_LoadedBlocksCount; Set count to 0. [d20e]85 5fSTA LoadCompressedScreenData_BitOffset; Set bit offset to 0.
; ; Load the control bits for the block. This will dictate ; how loading will proceed. ;
[d210]@_nextBlock: [d210]a9 00LDA #$00 [d212]85 5cSTA 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]20 58 d2JSR Area_LoadNextCompressedScreenBit; Load next bit from compressed data. [d217]26 5cROL Temp_LoadedBlockValue; Shift our block to the left 1 and add the bit. [d219]20 58 d2JSR Area_LoadNextCompressedScreenBit; Load next bit from compressed data. [d21c]26 5cROL Temp_LoadedBlockValue; Shift our block to the left 1 and add the bit.
; ; Check what bits we just loaded. ;
[d21e]a5 5cLDA Temp_LoadedBlockValue; Load the current block value. [d220]29 03AND #$03; Check the 2 least-significant bits we loaded. [d222]aaTAX; X = Our loaded bits value [d223]e0 03CPX #$03; Is the value 3? [d225]f0 0dBEQ @_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]a5 5dLDA Temp_LoadedBlocksCount; Else, load our blocks count. [d229]18CLC [d22a]7d 73 d2ADC a:BLOCK_DATA_OFFSETS_FOR_BIT_VALUES,X; Add that to the value for this bit in the lookup table. [d22d]aaTAX; Set as X [d22e]bd 00 06LDA a:ScreenBuffer,X; Load the block value from the screen buffer at the offset for this bit value. [d231]4c 44 d2JMP @_storeBlock; Proceed to store the block.
; ; Control 0x03: Read an 8-bit block. ;
[d234]@_read8BitBlock: [d234]a2 08LDX #$08; X = 8 (total bits) [d236]a9 00LDA #$00; A = 0 (new block value) [d238]85 5cSTA 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]20 58 d2JSR Area_LoadNextCompressedScreenBit; Load next bit from compressed data. [d23d]26 5cROL Temp_LoadedBlockValue; Shift our block to the left 1 and add the bit. [d23f]caDEX; X-- (total bits) [d240]d0 f8BNE @_load8BitsOfBlockData; If we're not at 0, loop. [d242]a5 5cLDA Temp_LoadedBlockValue; Else, load the resulting block value.
; ; Store the block value in the screen buffer at the ; current offset. ;
[d244]@_storeBlock: [d244]a6 5dLDX Temp_LoadedBlocksCount; X = loaded block count, as our new offset [d246]9d 00 06STA a:ScreenBuffer,X; Store the byte in the screen buffer at that offset. [d249]e6 5dINC Temp_LoadedBlocksCount; Increment our block count. [d24b]d0 c3BNE @_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]a2 f0LDX #$f0; X = 0xF0 (offset into the screen buffer to fill) [d24f]a9 00LDA #$00; A = 0 (value to store)
; ; Fill the last line. ;
[d251]@_fillLastLine: [d251]9d 00 06STA a:ScreenBuffer,X; Store in the screen buffer. [d254]e8INX; X++ [d255]d0 faBNE @_fillLastLine; If we haven't hit 16 blocks (value 256 and wrapped around), then loop. [d257]60RTS
;============================================================================ ; 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]a5 5fLDA LoadCompressedScreenData_BitOffset; Load the current bit. [d25a]d0 06BNE @_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]a4 5eLDY LoadCompressedScreenData_ByteOffset; Get the offset of the byte we'll be processing. [d25e]b1 08LDA (Temp_08),Y; Load the next byte. [d260]85 60STA LoadCompressedScreenData_CurByte
; ; Shift the current byte left by 1 and increment the bit ; offset by 1, for comparison. ;
[d262]@_update: [d262]06 60ASL LoadCompressedScreenData_CurByte; Shift the current byte we're processing. [d264]08PHP; Save all flags. [d265]e6 5fINC 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]a5 5fLDA LoadCompressedScreenData_BitOffset; Check if we've hit the last bit in the byte. [d269]29 07AND #$07 [d26b]d0 04BNE @_return; If we have any data in bits 1-3... [d26d]85 5fSTA LoadCompressedScreenData_BitOffset; Reset the current bit to 0. [d26f]e6 5eINC LoadCompressedScreenData_ByteOffset; Increase the byte offset. [d271]@_return: [d271]28PLP; Restore all flags. [d272]60RTS
; ; XREFS: ; Area_LoadBlocks ;
[d273]BLOCK_DATA_OFFSETS_FOR_BIT_VALUES: [d273]ff.byte $ff; [0]: [d274]f0.byte $f0; [1]: [d275]ef.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:
; ; Switch to bank 3, where the level data is stored. ;
[d276]ad 00 01LDA a:CurrentROMBank [d279]48PHA [d27a]a2 03LDX #$03 [d27c]20 1a ccJSR MMC1_UpdateROMBank
; ; Prepare for the read loop. ;
[d27f]a0 00LDY #$00; Y = 0 [d281]a2 00LDX #$00; X = 0
; ; Load the lower nibble from the block properties. ;
[d283]@_nextBlockProperty: [d283]b1 88LDA (CurrentArea_BlockPropertiesAddr),Y; A = block property in the current area at offset Y. [d285]29 0fAND #$0f; Retain the lower nibble. [d287]85 00STA Temp_00; Store that for later lookup.
; ; Load the next block property as the upper nibble. ;
[d289]c8INY; Increment the index in the block properties table.; [d28a]b1 88LDA (CurrentArea_BlockPropertiesAddr),Y; Load it into A. [d28c]0aASL A; Move the lower nibble to the upper nibble. [d28d]0aASL A [d28e]0aASL A [d28f]0aASL A
; ; Combine the two nibbles for the new value and store it in ; BlockProperties. ;
[d290]05 00ORA Temp_00; OR it to the lower nibble we saved. [d292]9d 3c 04STA a:BlockProperties,X; Store it.
; ; Keep going while the index is positive. ;
[d295]c8INY; Y = Y + 1 [d296]e8INX; X = X + 1 [d297]10 eaBPL @_nextBlockProperty; If we haven't overflowed, loop.
; ; We're done. Switch back to our previous bank. ;
[d299]68PLA; Pull our saved bank. [d29a]aaTAX; Set to X and... [d29b]20 1a ccJSR MMC1_UpdateROMBank; Switch banks. [d29e]60RTS
;============================================================================ ; TODO: Document Screen_StopScrollAndLoadBlockProperties ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Screen_HandleScrollDown ; Screen_HandleScrollLeft ; Screen_HandleScrollRight ; Screen_HandleScrollUp ;============================================================================
[d29f]Screen_StopScrollAndLoadBlockProperties: [d29f]a9 ffLDA #$ff [d2a1]85 54STA Screen_ScrollDirection [d2a3]4c 76 d2JMP Area_LoadBlockProperties
;============================================================================ ; TODO: Document Screen_HandleScrollUp ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Screen_HandleScroll ;============================================================================
[d2a6]Screen_HandleScrollUp: [d2a6]a5 57LDA PPU_ScrollY [d2a8]d0 02BNE @LAB_PRG15_MIRROR__d2ac [d2aa]a9 d0LDA #$d0 [d2ac]@LAB_PRG15_MIRROR__d2ac: [d2ac]38SEC [d2ad]e9 01SBC #$01 [d2af]85 57STA PPU_ScrollY [d2b1]d0 05BNE @LAB_PRG15_MIRROR__d2b8 [d2b3]c6 58DEC PPU_ScrollScreenVert [d2b5]20 9f d2JSR Screen_StopScrollAndLoadBlockProperties [d2b8]@LAB_PRG15_MIRROR__d2b8: [d2b8]a5 57LDA PPU_ScrollY [d2ba]85 44STA Screen_ScrollVertLoadCounter [d2bc]ad 00 01LDA a:CurrentROMBank [d2bf]48PHA [d2c0]a2 03LDX #$03 [d2c2]20 1a ccJSR MMC1_UpdateROMBank [d2c5]20 93 d3JSR Screen_LoadDataUp [d2c8]68PLA [d2c9]aaTAX [d2ca]20 1a ccJSR MMC1_UpdateROMBank [d2cd]60RTS
;============================================================================ ; TODO: Document Screen_UpdateForScroll ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Game_UpdateForScroll ; Screen_UpdateForScroll ;============================================================================
[d2ce]Screen_UpdateForScroll: [d2ce]20 48 e0JSR Game_UpdatePlayerOnScroll; Update the player position during scroll. [d2d1]20 e7 d2JSR Screen_HandleScroll; Update the screen state/tiles on scroll. [d2d4]20 1d d6JSR Screen_RunWriteScrollDataHandler; Write data to the PPU based on the scroll direction. [d2d7]a5 54LDA Screen_ScrollDirection; Load the scroll direction. [d2d9]c9 02CMP #$02; Is it left or right? [d2db]b0 05BCS @_isLeftOrRight
; ; The screen is scrolling up, down, or not at all. ;
[d2dd]a5 0cLDA PPU_ScrollX; Load the scroll X delta. [d2df]d0 edBNE Screen_UpdateForScroll; If it's not 0, loop. [d2e1]60RTS
; ; The screen is scrolling left or right. ;
[d2e2]@_isLeftOrRight: [d2e2]a5 57LDA PPU_ScrollY; Load the scroll Y delta. [d2e4]d0 e8BNE Screen_UpdateForScroll; If it's not 0, loop.
; ; XREFS: ; Screen_HandleScroll ;
[d2e6]RETURN_D2E6: [d2e6]60RTS; Else, return.
;============================================================================ ; TODO: Document Screen_HandleScroll ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Area_ScrollScreenRight ; Game_MainLoop ; Screen_UpdateForScroll ;============================================================================
[d2e7]Screen_HandleScroll: [d2e7]a6 54LDX Screen_ScrollDirection; Load the scroll direction. [d2e9]f0 5fBEQ Screen_HandleScrollLeft; If 0, jump to scroll left. [d2eb]caDEX [d2ec]f0 30BEQ Screen_HandleScrollRight; If 1, jump to scroll right. [d2ee]caDEX [d2ef]f0 b5BEQ Screen_HandleScrollUp; If 2, jump to scroll up. [d2f1]caDEX [d2f2]d0 f2BNE RETURN_D2E6; If anything but 3 (down), return.
; ; v-- Fall through --v ;
;============================================================================ ; TODO: Document Screen_HandleScrollDown ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ;============================================================================
[d2f4]Screen_HandleScrollDown:
; ; The screen is scrolling down. ;
[d2f4]a5 57LDA PPU_ScrollY [d2f6]18CLC [d2f7]69 01ADC #$01 [d2f9]85 57STA PPU_ScrollY [d2fb]c9 d0CMP #$d0 [d2fd]90 09BCC @LAB_PRG15_MIRROR__d308 [d2ff]a9 00LDA #$00 [d301]85 57STA PPU_ScrollY [d303]e6 58INC PPU_ScrollScreenVert [d305]20 9f d2JSR Screen_StopScrollAndLoadBlockProperties [d308]@LAB_PRG15_MIRROR__d308: [d308]a5 57LDA PPU_ScrollY [d30a]85 44STA Screen_ScrollVertLoadCounter [d30c]ad 00 01LDA a:CurrentROMBank [d30f]48PHA [d310]a2 03LDX #$03 [d312]20 1a ccJSR MMC1_UpdateROMBank [d315]20 a6 d3JSR Screen_LoadBlocksDown [d318]68PLA [d319]aaTAX [d31a]20 1a ccJSR MMC1_UpdateROMBank [d31d]60RTS
;============================================================================ ; TODO: Document Screen_HandleScrollRight ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Screen_HandleScroll ;============================================================================
[d31e]Screen_HandleScrollRight:
; ; The screen is scrolling right. ;
[d31e]a5 0cLDA PPU_ScrollX [d320]18CLC [d321]69 01ADC #$01 [d323]85 0cSTA PPU_ScrollX [d325]08PHP [d326]a5 59LDA PPU_ScrollScreenHoriz [d328]69 00ADC #$00 [d32a]85 59STA PPU_ScrollScreenHoriz [d32c]85 0dSTA PPU_ScrollScreen [d32e]28PLP [d32f]90 03BCC @LAB_PRG15_MIRROR__d334 [d331]20 9f d2JSR Screen_StopScrollAndLoadBlockProperties [d334]@LAB_PRG15_MIRROR__d334: [d334]a5 0cLDA PPU_ScrollX [d336]85 45STA Screen_ScrollHorizLoadCounter [d338]ad 00 01LDA a:CurrentROMBank [d33b]48PHA [d33c]a2 03LDX #$03 [d33e]20 1a ccJSR MMC1_UpdateROMBank [d341]20 dc d4JSR Screen_LoadDataRight [d344]68PLA [d345]aaTAX [d346]20 1a ccJSR MMC1_UpdateROMBank [d349]60RTS
;============================================================================ ; TODO: Document Screen_HandleScrollLeft ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Screen_HandleScroll ;============================================================================
[d34a]Screen_HandleScrollLeft:
; ; The screen is scrolling left. ;
[d34a]a5 45LDA Screen_ScrollHorizLoadCounter; Load the screen blocks horizontal load counter. [d34c]c9 fcCMP #$fc; Is it >= 0xFC? [d34e]b0 14BCS @_checkLoadBlocks; If so, jump.
; ; We haven't loaded 252 columns yet. ; ; Start by reducing the scroll X counter by 1. ;
[d350]a5 0cLDA PPU_ScrollX; Load the scroll X counter. [d352]38SEC [d353]e9 01SBC #$01; Subtract 1. [d355]85 0cSTA PPU_ScrollX; And store it. [d357]08PHP; Push flags to the stack. [d358]a5 0dLDA PPU_ScrollScreen; Load the scroll screen we're using. [d35a]e9 00SBC #$00; Subtract carry from above. [d35c]85 0dSTA PPU_ScrollScreen; Store it. [d35e]28PLP; Pop flags from the stack. [d35f]d0 03BNE @_checkLoadBlocks; If scroll X > 0, jump.
; ; PPU_ScrollX hit 0, so we can stop scrolling and load ; the block properties for the new screen. ;
[d361]20 9f d2JSR Screen_StopScrollAndLoadBlockProperties; Stop scrolling and load the block properties. [d364]@_checkLoadBlocks: [d364]a5 45LDA Screen_ScrollHorizLoadCounter; Load the screen blocks horizontal load counter. [d366]38SEC [d367]e9 01SBC #$01; Subtract 1. [d369]85 45STA Screen_ScrollHorizLoadCounter; Store it. [d36b]a5 59LDA PPU_ScrollScreenHoriz; Load the index of the screen using for scrolling horizontally. [d36d]e9 00SBC #$00; Subtract carry, if wrapping above. [d36f]85 59STA PPU_ScrollScreenHoriz; Store it.
; ; Check if we're ready to load blocks to the left. ; ; This will only load if the screen index has changed or ; the scroll X is >= 4. ;
[d371]c5 0dCMP PPU_ScrollScreen; Is the horizontal scroll screen the same as the current screen? [d373]d0 07BNE @_loadBlocks; If not, jump to load blocks. [d375]a5 0cLDA PPU_ScrollX; Load the scroll X position. [d377]c9 04CMP #$04; Is it >= 4? [d379]b0 01BCS @_loadBlocks; If so, jump to load blocks. [d37b]60RTS; Else, we're done.
; ; Save the current ROM bank and switch to bank 3 (Area metadata). ;
[d37c]@_loadBlocks: [d37c]ad 00 01LDA a:CurrentROMBank; Load the current ROM bank. [d37f]48PHA; Push it to the stack. [d380]a2 03LDX #$03; Bank 3 = Area metadata. [d382]20 1a ccJSR MMC1_UpdateROMBank; Switch to it.
; ; Load data for the screen to the left of the current screen. ;
[d385]20 f0 d4JSR Screen_LoadDataLeft; Load data to the left of the current screen.
; ; Switch back to the saved bank. ;
[d388]68PLA; Pull the current bank from the stack. [d389]aaTAX; X = A (bank). [d38a]20 1a ccJSR MMC1_UpdateROMBank; Switch back to that bank. [d38d]60RTS
;============================================================================ ; 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]e6 6eINC Screen_ScrollLoadMode; Set load mode to 1 (load attributes) [d390]4c ba d3JMP 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]a2 00LDX #$00; 0 = Load Tiles mode [d395]86 6eSTX Screen_ScrollLoadMode; Set as the default mode. [d397]a5 44LDA Screen_ScrollVertLoadCounter; A = Vertical blocks counter [d399]29 0fAND #$0f; Keep the lower nibble. [d39b]c9 07CMP #$07; Is it 7 (every 8 blocks, starting at 7)? [d39d]f0 efBEQ Screen_LoadAttrsVert; If so, load attributes. [d39f]29 07AND #$07; Else, keep the lower 3 bits. [d3a1]c9 03CMP #$03; Is it 3 (every 8 blocks, starting at 3)? [d3a3]f0 15BEQ Screen_LoadBlockDataVert; If so, load tiles. [d3a5]60RTS; 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]a2 00LDX #$00; 0 = Load Tiles mode [d3a8]86 6eSTX Screen_ScrollLoadMode; Set as the default mode. [d3aa]e8INX; X++ (reuse as "is down" flag, set to 1). [d3ab]a5 44LDA Screen_ScrollVertLoadCounter; A = Vertical blocks counter [d3ad]29 0fAND #$0f; Keep the lower nibble. [d3af]c9 08CMP #$08; Is it 8 (every 16 blocks, starting at 8)? [d3b1]f0 dbBEQ Screen_LoadAttrsVert; If so, load attributes. [d3b3]29 07AND #$07; Else, keep the lower 3 bits. [d3b5]c9 04CMP #$04; Is it 4 (every 8 blocks, starting at 4)? [d3b7]f0 01BEQ Screen_LoadBlockDataVert; If so, load tiles. [d3b9]60RTS; Else, return.
;============================================================================ ; TODO ; ; direction: ; 1 = up ; 0 = down ; ; XREFS: ; Screen_LoadAttrsVert ; Screen_LoadBlocksDown ; Screen_LoadDataUp ;============================================================================
[d3ba]Screen_LoadBlockDataVert: [d3ba]a5 44LDA Screen_ScrollVertLoadCounter [d3bc]18CLC [d3bd]7d cb d4ADC a:BYTE_ARRAY_PRG15_MIRROR__d4cb,X [d3c0]85 00STA Temp_00 [d3c2]a5 58LDA PPU_ScrollScreenVert [d3c4]69 00ADC #$00 [d3c6]85 49STA Unused_Blocks_0049 [d3c8]a5 63LDA Area_CurrentScreen [d3ca]85 6dSTA MaybeUnused_006d [d3cc]a5 00LDA Temp_00 [d3ce]29 f0AND #$f0 [d3d0]85 48STA Temp_Blocks_0048 [d3d2]a9 00LDA #$00 [d3d4]18CLC [d3d5]65 48ADC Temp_Blocks_0048 [d3d7]85 08STA Temp_08 [d3d9]a9 06LDA #$06 [d3db]69 00ADC #$00 [d3dd]85 09STA Temp_09 [d3df]a5 6eLDA Screen_ScrollLoadMode [d3e1]f0 03BEQ @LAB_PRG15_MIRROR__d3e6 [d3e3]4c 45 d4JMP @LAB_PRG15_MIRROR__d445 [d3e6]@LAB_PRG15_MIRROR__d3e6: [d3e6]a0 00LDY #$00 [d3e8]a2 00LDX #$00 [d3ea]@LAB_PRG15_MIRROR__d3ea: [d3ea]84 06STY Temp_06 [d3ec]b1 08LDA (Temp_08),Y [d3ee]a8TAY [d3ef]b1 80LDA (CurrentArea_BlockData1StartAddr),Y [d3f1]85 6fSTA CurrentArea_BlockData1CurAddr [d3f3]b1 82LDA (CurrentArea_BlockData2StartAddr),Y [d3f5]85 70STA CurrentArea_BlockData2CurAddr [d3f7]b1 84LDA (CurrentArea_BlockData3StartAddr),Y [d3f9]85 71STA CurrentArea_BlockData3CurAddr [d3fb]b1 86LDA (CurrentArea_BlockData4StartAddr),Y [d3fd]85 72STA CurrentArea_BlockData4CurAddr [d3ff]a5 44LDA Screen_ScrollVertLoadCounter [d401]29 08AND #$08 [d403]4aLSR A [d404]4aLSR A [d405]a8TAY [d406]b9 6f 00LDA a:CurrentArea_BlockData1CurAddr,Y [d409]9d 20 02STA a:DataArray,X [d40c]b9 70 00LDA a:CurrentArea_BlockData2CurAddr,Y [d40f]9d 21 02STA a:DataArray_1_,X [d412]e8INX [d413]e8INX [d414]a4 06LDY Temp_06 [d416]c8INY [d417]c0 10CPY #$10 [d419]90 cfBCC @LAB_PRG15_MIRROR__d3ea [d41b]a9 00LDA #$00 [d41d]85 4fSTA Screen_ScrollVertBlocks_PPUTileMapAddr_U [d41f]a5 44LDA Screen_ScrollVertLoadCounter [d421]29 f8AND #$f8 [d423]0aASL A [d424]26 4fROL Screen_ScrollVertBlocks_PPUTileMapAddr_U [d426]0aASL A [d427]26 4fROL Screen_ScrollVertBlocks_PPUTileMapAddr_U [d429]18CLC [d42a]69 80ADC #$80 [d42c]85 4eSTA Screen_ScrollVertBlocks_PPUTileMapAddr_L [d42e]a5 4fLDA Screen_ScrollVertBlocks_PPUTileMapAddr_U [d430]69 00ADC #$00 [d432]85 4fSTA Screen_ScrollVertBlocks_PPUTileMapAddr_U [d434]a5 59LDA PPU_ScrollScreenHoriz [d436]29 01AND #$01 [d438]0aASL A [d439]0aASL A [d43a]09 20ORA #$20 [d43c]05 4fORA Screen_ScrollVertBlocks_PPUTileMapAddr_U [d43e]85 4fSTA Screen_ScrollVertBlocks_PPUTileMapAddr_U [d440]a9 01LDA #$01 [d442]85 73STA Screen_ScrollHandlersPending [d444]60RTS [d445]@LAB_PRG15_MIRROR__d445: [d445]a5 44LDA Screen_ScrollVertLoadCounter [d447]29 16AND #$16 [d449]4aLSR A [d44a]4aLSR A [d44b]4aLSR A [d44c]85 01STA Temp_01 [d44e]a0 f8LDY #$f8 [d450]a9 00LDA #$00 [d452]@LAB_PRG15_MIRROR__d452: [d452]99 92 01STA a:Screen_ScrollVert_ZeroData,Y [d455]c8INY [d456]d0 faBNE @LAB_PRG15_MIRROR__d452 [d458]@LAB_PRG15_MIRROR__d458: [d458]84 06STY Temp_06 [d45a]b1 08LDA (Temp_08),Y [d45c]a8TAY [d45d]b1 7eLDA (CurrentArea_BlockAttributesAddr),Y [d45f]85 00STA Temp_00 [d461]a5 06LDA Temp_06 [d463]29 01AND #$01 [d465]05 01ORA Temp_01 [d467]a8TAY [d468]a5 00LDA Temp_00 [d46a]39 d3 d4AND a:BLOCK_ATTRS_BITMASKS,Y [d46d]48PHA [d46e]a5 06LDA Temp_06 [d470]4aLSR A [d471]a8TAY [d472]68PLA [d473]19 8a 02ORA a:Screen_ScrollVertPPUAttrData,Y [d476]99 8a 02STA a:Screen_ScrollVertPPUAttrData,Y [d479]a4 06LDY Temp_06 [d47b]c8INY [d47c]c0 10CPY #$10 [d47e]90 d8BCC @LAB_PRG15_MIRROR__d458 [d480]a2 f0LDX #$f0 [d482]a5 44LDA Screen_ScrollVertLoadCounter [d484]29 10AND #$10 [d486]f0 02BEQ @LAB_PRG15_MIRROR__d48a [d488]a2 0fLDX #$0f [d48a]@LAB_PRG15_MIRROR__d48a: [d48a]86 06STX Temp_06 [d48c]a5 44LDA Screen_ScrollVertLoadCounter [d48e]29 e0AND #$e0 [d490]4aLSR A [d491]4aLSR A [d492]85 00STA Temp_00 [d494]18CLC [d495]69 08ADC #$08 [d497]a8TAY [d498]18CLC [d499]69 c0ADC #$c0 [d49b]85 52STA Screen_ScrollVertBlocks_PPUAttrAddr_L [d49d]a5 59LDA PPU_ScrollScreenHoriz [d49f]29 01AND #$01 [d4a1]aaTAX [d4a2]0aASL A [d4a3]0aASL A [d4a4]09 23ORA #$23 [d4a6]85 53STA Screen_ScrollVertBlocks_PPUAttrAddr_U [d4a8]bd cf d4LDA a:SET_BLOCKS_TILEMAP_OFFSETS_L,X [d4ab]85 08STA Temp_08 [d4ad]bd d1 d4LDA a:SET_BLOCKS_TILEMAP_OFFSETS_U,X [d4b0]85 09STA Temp_09 [d4b2]a2 00LDX #$00 [d4b4]@LAB_PRG15_MIRROR__d4b4: [d4b4]b1 08LDA (Temp_08),Y [d4b6]25 06AND Temp_06 [d4b8]1d 8a 02ORA a:Screen_ScrollVertPPUAttrData,X [d4bb]91 08STA (Temp_08),Y [d4bd]9d 8a 02STA a:Screen_ScrollVertPPUAttrData,X [d4c0]c8INY [d4c1]e8INX [d4c2]e0 08CPX #$08 [d4c4]90 eeBCC @LAB_PRG15_MIRROR__d4b4 [d4c6]a9 01LDA #$01 [d4c8]85 75STA Screen_MaybeUnused_0075 [d4ca]60RTS
; ; XREFS: ; Screen_LoadBlockDataVert ;
[d4cb]BYTE_ARRAY_PRG15_MIRROR__d4cb: [d4cb]00.byte $00; [0]: [d4cc]00.byte $00; [1]: [d4cd]00 08.byte $00,$08; byte
; ; XREFS: ; Area_SetBlocks_SetAttributes ; Screen_LoadBlockDataVert ; Screen_LoadBlocksHoriz ;
[d4cf]SET_BLOCKS_TILEMAP_OFFSETS_L: [d4cf]42.byte $42; [0]: [d4d0]42.byte $42; [1]:
; ; XREFS: ; Area_SetBlocks_SetAttributes ; Screen_LoadBlockDataVert ; Screen_LoadBlocksHoriz ;
[d4d1]SET_BLOCKS_TILEMAP_OFFSETS_U: [d4d1]02.byte $02; [0]: [d4d2]02.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]03.byte $03; [0]: Mask bits 0 and 1 [d4d4]0c.byte $0c; [1]: Mask bits 2 and 3 [d4d5]30.byte $30; [2]: Mask bits 4 and 5 [d4d6]c0.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]e6 6eINC Screen_ScrollLoadMode; Set load mode to 1 (load attributes). [d4d9]4c 03 d5JMP 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]a2 00LDX #$00; 0 = Load Tiles mode [d4de]86 6eSTX Screen_ScrollLoadMode; Set as the default mode. [d4e0]e8INX; X++ (reuse as "is right" flag, set to 1). [d4e1]a5 45LDA Screen_ScrollHorizLoadCounter; A = Horizontal blocks counter [d4e3]29 0fAND #$0f; Keep the lower nibble. [d4e5]c9 02CMP #$02; Is it 2 (every 4 blocks, starting at 2)? [d4e7]f0 eeBEQ Screen_LoadAttrsHoriz; If so, load attributes. [d4e9]29 07AND #$07; Else, keep the lower 3 bits. [d4eb]c9 01CMP #$01; Is it 1 (every 4 blocks, starting at 1)? [d4ed]f0 14BEQ Screen_LoadBlocksHoriz; If so, load tiles. [d4ef]60RTS; 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]a2 00LDX #$00; 0 = Load Tiles mode [d4f2]86 6eSTX Screen_ScrollLoadMode; Set as the default mode. [d4f4]a5 45LDA Screen_ScrollHorizLoadCounter; A = Horizontal blocks counter [d4f6]29 0fAND #$0f; Keep the lower nibble. [d4f8]c9 0fCMP #$0f; Is it 0xF (every 16, starting at 15)? [d4fa]f0 dbBEQ Screen_LoadAttrsHoriz; If so, load attributes. [d4fc]29 07AND #$07; Else, keep the lower 3 bits. [d4fe]c9 06CMP #$06; Is it 6 (every 8 blocks, starting at 6)? [d500]f0 01BEQ Screen_LoadBlocksHoriz; If so, load tiles. [d502]60RTS; Else, return.
;============================================================================ ; TODO: Document Screen_LoadBlocksHoriz ; ; INPUTS: ; X ; ; OUTPUTS: ; TODO ; ; XREFS: ; Screen_LoadAttrsHoriz ; Screen_LoadDataLeft ; Screen_LoadDataRight ;============================================================================
[d503]Screen_LoadBlocksHoriz:
; ; Store a copy of the load counter, and set carry if scrolling ; right. ; ; This will add from ; SCREEN_SCROLL_HORIZ_COUNTER_INCS, but this won't ; measurably change the value. It adds 0 when scrolling left ; (no-op) and 0xFF when scrolling right (no change, but sets ; carry). ;
[d503]a5 45LDA Screen_ScrollHorizLoadCounter; Load the scrolling counter. [d505]18CLC [d506]7d 19 d6ADC a:SCREEN_SCROLL_HORIZ_COUNTER_INCS,X; Add a value to increment by. This won't effectively change the value, but will set carry if scrolling right. [d509]85 00STA Temp_00; Store the load counter in temp.
; ; Update the horizontal screen scroll amount. ; ; This will add any carry to it from the counter. It also ; appears to add from a table, but the table is all 0 values, ; so that does nothing special. ;
[d50b]a5 59LDA PPU_ScrollScreenHoriz; Load the PPU horizontal screen offset. [d50d]7d 1b d6ADC a:SCREEN_SCROLL_HORIZ_DELTAS,X; Add carry (no value from the table -- all empty). [d510]85 47STA Screen_Maybe_ScrollHorizDirection; Store it. [d512]a5 63LDA Area_CurrentScreen; Load the current screen. [d514]85 6dSTA MaybeUnused_006d; DEADCODE: Store it (but this is never used).
; ; Load the scroll counter value, divide by 16, and set as ; the load offset. ;
[d516]a5 00LDA Temp_00; Load the counter value. [d518]4aLSR A; Divide by 16. [d519]4aLSR A [d51a]4aLSR A [d51b]4aLSR A [d51c]85 46STA Screen_ScrollHorizLoadOffset; Store as the new horizontal load offset.
; ; Set an address to read block data from. ; ; This will be $600. ;
[d51e]a9 00LDA #$00; Lower byte == 0. [d520]85 08STA Temp_08; Store it. [d522]a9 06LDA #$06; Upper byte == 6. [d524]85 09STA Temp_09; Store it.
; ; Choose the operation based on the current load mode: ; ; 0x00: Load block data. ; 0x01: Draw to the screen. ;
[d526]a5 6eLDA Screen_ScrollLoadMode; Check the load/draw mode. [d528]d0 5dBNE @_drawBlocks; If 0, jump to draw.
; ; We're in the block loading mode. ; ; Load 30 blocks to render in the next draw phase. ;
[d52a]a2 00LDX #$00; X = 0 (loop counter)
; ; Determine the offset for the blocks to load. ; ; This is data populated by Area_LoadBlocks. ;
[d52c]@_loadBlocksLoop: [d52c]a4 46LDY Screen_ScrollHorizLoadOffset; Y = Load offset. [d52e]b1 08LDA (Temp_08),Y; A = Block data at $600 + Y.
; ; Load the blocks information. ;
[d530]a8TAY; Y = A (block data). [d531]b1 80LDA (CurrentArea_BlockData1StartAddr),Y; A = Block data 1 address at Y. [d533]85 6fSTA CurrentArea_BlockData1CurAddr; Store it for processing. [d535]b1 84LDA (CurrentArea_BlockData3StartAddr),Y; A = Block data 3 address at Y. [d537]85 70STA CurrentArea_BlockData2CurAddr; Store it for processing. [d539]b1 82LDA (CurrentArea_BlockData2StartAddr),Y; A = Block data 2 address at Y. [d53b]85 71STA CurrentArea_BlockData3CurAddr; Store it for processing. [d53d]b1 86LDA (CurrentArea_BlockData4StartAddr),Y; A = Block data 4 address at Y. [d53f]85 72STA CurrentArea_BlockData4CurAddr; Store it for processing.
; ; Convert the block counter to a tile width and use it ; to determine which part of the block data to load. ; ; Every 8 ticks of the counter, blocks will load. This ; will convert to a value of 0 or 8, and then divide by ; 4 to convert to a block loading index of 0 or 2. ; ; That will be our offset into the loaded block data. ; ; We'll either be working off of block data lists (1, 2) ; or (3, 4). This loads a column of tiles in the block ; at a time. ;
[d541]a5 45LDA Screen_ScrollHorizLoadCounter; A = Horizontal load counter. [d543]29 08AND #$08; Every 8... [d545]4aLSR A; Divide by 4 (convert to 0 or 2). [d546]4aLSR A [d547]a8TAY; Y = result. [d548]b9 6f 00LDA a:CurrentArea_BlockData1CurAddr,Y; A = Block data 1 (top-left) or 3 (top-right). [d54b]9d 00 02STA a:Temp_0200,X; Queue for loading into the PPU. [d54e]b9 70 00LDA a:CurrentArea_BlockData2CurAddr,Y; A = Block data 2 (bottom-left) or 4 (bottom-right). [d551]9d 01 02STA a:Temp_0201,X; Queue for loading into the PPU. [d554]a5 08LDA Temp_08; Load the dest screen buffer address. [d556]18CLC [d557]69 10ADC #$10; Increment by 16. [d559]85 08STA Temp_08; Store the lower byte of the new address. [d55b]a5 09LDA Temp_09; Load the upper byte. [d55d]69 00ADC #$00; Add carry, if wrapped. [d55f]85 09STA Temp_09; Store the upper byte.
; ; Advance the loop counter by 2. ;
[d561]e8INX; X++ (loop counter). [d562]e8INX; X++ (loop counter). [d563]e0 1eCPX #$1e [d565]90 c5BCC @_loadBlocksLoop [d567]a9 01LDA #$01 [d569]85 74STA Screen_ScrollHorizBlocksLoaded [d56b]98TYA [d56c]4aLSR A [d56d]48PHA [d56e]a5 46LDA Screen_ScrollHorizLoadOffset [d570]0aASL A [d571]85 46STA Screen_ScrollHorizLoadOffset [d573]68PLA [d574]18CLC [d575]65 46ADC Screen_ScrollHorizLoadOffset [d577]18CLC [d578]69 80ADC #$80 [d57a]85 4cSTA Screen_ScrollHorizBlocks_PPUTileMapAddr_L [d57c]a5 47LDA Screen_Maybe_ScrollHorizDirection [d57e]29 01AND #$01 [d580]0aASL A [d581]0aASL A [d582]09 20ORA #$20 [d584]85 4dSTA Screen_ScrollHorizBlocks_PPUTileMapAddr_U [d586]60RTS
; ; 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]@_drawBlocks: [d587]a0 f8LDY #$f8; Y = 0xF8 (loop counter) [d589]a9 00LDA #$00; A = 0 (value to write) [d58b]@_clearLoop: [d58b]99 8a 01STA a:Screen_ScrollHoriz_ZeroData,Y; Write a 0 to this empty block. [d58e]c8INY; Y++ [d58f]d0 faBNE @_clearLoop; If != 0, loop.
; ; Begin loading PPU attribute data and scheduling to draw. ;
[d591]a2 00LDX #$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]a4 46LDY Screen_ScrollHorizLoadOffset; Y = Attr data load offset [d595]b1 08LDA (Temp_08),Y; A = Screen buffer data at offset (block attribute offset) [d597]a8TAY; Y = A [d598]b1 7eLDA (CurrentArea_BlockAttributesAddr),Y; A = block attribute at resulting screen buffer data offset value [d59a]85 00STA 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]a5 46LDA Screen_ScrollHorizLoadOffset; A = Attr data load offset [d59e]29 01AND #$01; Keep the least-significant bit (even/odd). [d5a0]85 01STA Temp_01; Store it temporarily. [d5a2]8aTXA; A = X (loop counter) [d5a3]29 01AND #$01; Keep the loop counter's least-significant bit (even/odd). [d5a5]0aASL A; A *= 2 [d5a6]05 01ORA Temp_01; OR with the load offset's even/odd bit. [d5a8]a8TAY; Y = A (result)
; ; Mask 2 bits of the block attributes based on that index. ;
[d5a9]a5 00LDA Temp_00; A = Loaded block attribute [d5ab]39 d3 d4AND a:BLOCK_ATTRS_BITMASKS,Y; AND with the bitmask based on our computed index. [d5ae]48PHA; Push the result to the stack.
; ; Generate the destination index into the PPU attribute data. ; ; This will be the loop counter / 2. ;
[d5af]8aTXA; A = X (loop counter) [d5b0]4aLSR A; A = A / 2 [d5b1]a8TAY; Y = A
; ; OR the result with the value already loaded at this loop ; index, and store it back. ;
[d5b2]68PLA; Pop the masked block attribute from stack. [d5b3]19 82 02ORA a:Screen_ScrollHorizPPUAttrData,Y; OR with the data already set. [d5b6]99 82 02STA a:Screen_ScrollHorizPPUAttrData,Y; And store it.
; ; Advance the screen buffer address by 16 bytes. ;
[d5b9]a5 08LDA Temp_08; A = Lower byte of Current screen buffer address. [d5bb]18CLC [d5bc]69 10ADC #$10; A += 16 [d5be]85 08STA Temp_08; Store as the new lower byte. [d5c0]a5 09LDA Temp_09; A = Upper byte of Current screen buffer address. [d5c2]69 00ADC #$00; A += Carry from lower increment [d5c4]85 09STA Temp_09; Store as the new upper byte.
; ; Advance the loop counter, and loop if < 15. ;
[d5c6]e8INX; X++ (loop counter) [d5c7]e0 0fCPX #$0f; Is this < 15? [d5c9]90 c8BCC @_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]a2 ccLDX #$cc; Default bitmask to enable bits 2-3, 6-7 (11001100) [d5cd]a5 46LDA Screen_ScrollHorizLoadOffset; Load the data load offset. [d5cf]29 01AND #$01; Is it even? [d5d1]f0 02BEQ @_updatePPUAttrAddr; If so, jump to skip. [d5d3]a2 33LDX #$33; Set bitmask to enable bits 0-1, 4-5 (00110011) [d5d5]@_updatePPUAttrAddr: [d5d5]86 06STX Temp_06; Store for later. [d5d7]a5 46LDA Screen_ScrollHorizLoadOffset; A = Data load offset. [d5d9]4aLSR A; A = A / 2 [d5da]18CLC [d5db]69 08ADC #$08; A += 8 [d5dd]a8TAY; Y = A (new screen buffer write offset) [d5de]18CLC [d5df]69 c0ADC #$c0; A += 0xC0 [d5e1]85 50STA Screen_ScrollHorizBlocks_PPUAttrAddr_L; Set as the lower byte of the PPU attr address. [d5e3]a5 47LDA Screen_Maybe_ScrollHorizDirection [d5e5]29 01AND #$01 [d5e7]aaTAX [d5e8]0aASL A [d5e9]0aASL A [d5ea]09 23ORA #$23 [d5ec]85 51STA Screen_ScrollHorizBlocks_PPUAttrAddr_U; Set as the upper byte of the PPU attr address. [d5ee]bd cf d4LDA a:SET_BLOCKS_TILEMAP_OFFSETS_L,X [d5f1]85 08STA Temp_08 [d5f3]bd d1 d4LDA a:SET_BLOCKS_TILEMAP_OFFSETS_U,X [d5f6]85 09STA 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]a2 00LDX #$00; X = 0 (loop counter) [d5fa]@_updatePPUAttrDataLoop: [d5fa]b1 08LDA (Temp_08),Y; Load the value from the screen buffer write offset at Y. [d5fc]25 06AND Temp_06; AND with the bitmask computed above. [d5fe]1d 82 02ORA a:Screen_ScrollHorizPPUAttrData,X; OR with the existing PPU attribute data [d601]9d 82 02STA a:Screen_ScrollHorizPPUAttrData,X; And store it. [d604]91 08STA (Temp_08),Y; Store it back in the screen buffer at write offset Y. [d606]e8INX; X++ (loop counter)
; ; The next three instructions are effectively unused. ; Legacy code? ;
[d607]8aTXA; <unused> [d608]29 07AND #$07; <unused> [d60a]8aTXA; <unused>
; ; Advance the earlier loop counter (which should start at 15) ; by 8 each loop iteration. ;
[d60b]98TYA; A = Y (loop counter from initial attribute loading) [d60c]18CLC [d60d]69 08ADC #$08; A += 8 [d60f]a8TAY; Y = A [d610]c0 40CPY #$40; Is it < 64? [d612]90 e6BCC @_updatePPUAttrDataLoop; If so, loop.
; ; Mark the attribute data as loaded. ;
[d614]a9 01LDA #$01 [d616]85 76STA Screen_ScrollHorizAttrsLoaded; Set attribute data loaded. [d618]60RTS
;============================================================================ ; A table mapping horizontal scroll directions to counter increments. ; ; When scrolling left, there's no increment. ; ; When scrolling right, it increments a full 0xFF, keeping ; the value the same but setting the carry flag. ; ; XREFS: ; Screen_LoadBlocksHoriz ;============================================================================
; ; XREFS: ; Screen_LoadBlocksHoriz ;
[d619]SCREEN_SCROLL_HORIZ_COUNTER_INCS: [d619]00.byte $00; [0]: [d61a]ff.byte $ff; [1]:
;============================================================================ ; A table of deltas to add to the horizontal scroll offset. ; ; NOTE: All values are 0. This may be a remnant of an earlier ; design. Changing values here largely seems to do ; nothing, but if you set to 0xFF you'll end up with ; some incorrect screens loaded. ; ; XREFS: ; Screen_LoadBlocksHoriz ;============================================================================
; ; XREFS: ; Screen_LoadBlocksHoriz ;
[d61b]SCREEN_SCROLL_HORIZ_DELTAS: [d61b]00.byte $00; [0]: [d61c]00.byte $00; [1]:
;============================================================================ ; Run a handler used for the next stage of screen scroll. ; ; This will check which handlers have been scheduled to run ; during screen scroll and run the handlers in the order ; found. ; ; Handlers are built to write a single row or column of ; tiles or set a row or column of PPU attributes during ; screen scroll. ; ; This is designed to store state from one call to another. ; Once a handler is found to run, state will be stored on ; the index of the next handler to check against the next ; time this is called. That reduces how many iterations are ; required during screen scroll. ; ; Callers set flags in a mapping table ; (Screen_ScrollHandlersPending) to mark which they need to ; run for this operation. Only these will be considered. ; ; INPUTS: ; Screen_ScrollHandlersPending: ; The table of scroll handlers the callers have ; requested to run for the scrolling process. ; ; Screen_NextScrollDataHandlerIndex: ; The index of the next scroll handler in the ; table to begin checking in the loop. ; ; SCREEN_WRITESCROLL_HANDLERS_U: ; SCREEN_WRITESCROLL_HANDLERS_L: ; The table of available scroll handlers. ; ; OUTPUTS: ; Temp_06: ; Clobbered. ; ; CALLS: ; The resulting handler. ; ; XREFS: ; Area_ScrollScreenRight ; PPU_HandleOnInterrupt ; Screen_UpdateForScroll ;============================================================================
[d61d]Screen_RunWriteScrollDataHandler:
; ; Begin looking through our handler tables from last handler run ; to last in the table. ; ; This will start at Screen_NextScrollDataHandlerIndex ; (which may have been set from a last call to this function) ; and begin checking the table from that point, wrapping the ; index back around to 0 as it gets to the end of the table if ; necessary. ; ; Up to 4 entries will be checked. ; ; If starting from index 0, this will end up running tile ; handlers before attribute handlers, if both are requested ; during a screen scroll. ;
[d61d]a9 03LDA #$03; 3 = Last entry in handler table. [d61f]85 06STA Temp_06; Set as our lookup index. [d621]a6 77LDX Screen_NextScrollDataHandlerIndex; Load the handler scheduled to check next during this process. [d623]@_loop: [d623]b5 73LDA Screen_ScrollHandlersPending,X; Check if this handler needs to be run. [d625]d0 0aBNE @_runHandler; If so, run the handler.
; ; Check the next entry in the tables. ;
[d627]e8INX; X++ (handler index). [d628]8aTXA; A = incremented handler index. [d629]29 03AND #$03; Cap to a valid entry in the tables (wrapping if needed). [d62b]aaTAX; X = resulting index. [d62c]c6 06DEC Temp_06; Decrement our lookup index. [d62e]10 f3BPL @_loop; If >= 0, loop. [d630]60RTS
; ; The handler needs to be run. Clear the pending state and ; queue running the handler. ;
[d631]@_runHandler: [d631]a9 00LDA #$00; A = 0 (clear the pending state). [d633]95 73STA Screen_ScrollHandlersPending,X; Clear the pending flag. [d635]86 06STX Temp_06; Store the handler index.
; ; Set the next handler in the table to check the next time ; this is called, so we don't have to loop through the ; whole table. ;
[d637]e8INX; X++ (next index). [d638]8aTXA; A = resulting index. [d639]29 03AND #$03; Cap to a valid entry in the tables. [d63b]aaTAX; X = A (resulting capped handler index). [d63c]86 77STX Screen_NextScrollDataHandlerIndex; Store as the currently-running handler index.
; ; Load the index of the current handler to run. ;
[d63e]a5 06LDA Temp_06; Load the handler index. [d640]29 03AND #$03; Cap to a valid entry in the table. [d642]aaTAX; X = resulting index.
; ; Load the address of the handler and put it on the stack ; to run it. ;
[d643]bd 50 d6LDA a:SCREEN_WRITESCROLL_HANDLERS_U,X; Load the upper byte of the handler address. [d646]48PHA; Push to the stack. [d647]bd 4c d6LDA a:SCREEN_WRITESCROLL_HANDLERS_L,X; Load the lower byte of the handler address. [d64a]48PHA; Push to the stack. [d64b]60RTS; And run it.
;============================================================================ ; Lower byte address table for screen scroll handlers. ; ; These cover both the block tile writes and the PPU attributes ; writes. ; ; XREFS: ; Screen_RunWriteScrollDataHandler ;============================================================================
; ; XREFS: ; Screen_RunWriteScrollDataHandler ;
[d64c]SCREEN_WRITESCROLL_HANDLERS_L: [d64c]53.byte <(Screen_WriteBlockTilesRowToPPU-1); [0]: [d64d]72.byte <(Screen_WriteBlockTilesColumnToPPU-1); [1]: [d64e]98.byte <(Screen_WriteBlockAttrsRowToPPU-1); [2]: [d64f]b0.byte <(Screen_WriteBlockAttrsColumnToPPU-1); [3]:
;============================================================================ ; Upper byte address table for screen scroll handlers. ; ; These cover both the block tile writes and the PPU attributes ; writes. ; ; XREFS: ; Screen_RunWriteScrollDataHandler ;============================================================================
; ; XREFS: ; Screen_RunWriteScrollDataHandler ;
[d650]SCREEN_WRITESCROLL_HANDLERS_U: [d650]d6.byte >(Screen_WriteBlockTilesRowToPPU-1); [0]: [d651]d6.byte >(Screen_WriteBlockTilesColumnToPPU-1); [1]: [d652]d6.byte >(Screen_WriteBlockAttrsRowToPPU-1); [2]: [d653]d6.byte >(Screen_WriteBlockAttrsColumnToPPU-1); [3]:
;============================================================================ ; Write a row of block tiles to the PPU. ; ; This will take the queued row of blocks starting at ; DataArray and begin writing them to the PPU, one at a ; time, progressing across the screen. ; ; INPUTS: ; PPU_ControlFlags: ; The current saved PPU control flags. ; ; Screen_ScrollVertBlocks_PPUTileMapAddr_U: ; Screen_ScrollVertBlocks_PPUTileMapAddr_L: ; The tilemap address to place the block tiles in. ; ; DataArray: ; The queued row of tiles to write. ; ; OUTPUTS: ; PPUADDR: ; PPUCTRL: ; PPUDATA: ; The written PPU state. ; ; XREFS: ; SCREEN_WRITESCROLL_HANDLERS_L ; [$PRG15_MIRROR::d64c] ; SCREEN_WRITESCROLL_HANDLERS_U ; [$PRG15_MIRROR::d650] ;============================================================================
[d654]Screen_WriteBlockTilesRowToPPU:
; ; Tell the PPU we want to write 1 byte at a time, going across. ;
[d654]a5 0aLDA PPU_ControlFlags; Load the current PPU control flags. [d656]29 fbAND #$fb; Switch to add 1, going across. [d658]8d 00 20STA a:PPUCTRL; Write it to the PPU.
; ; Set the address in the PPU where tiles should be written to. ;
[d65b]a5 4fLDA Screen_ScrollVertBlocks_PPUTileMapAddr_U; Load the upper byte of the tilemap address to write to. [d65d]8d 06 20STA a:PPUADDR; Write as the upper byte for the data. [d660]a5 4eLDA Screen_ScrollVertBlocks_PPUTileMapAddr_L; Load the lower byte. [d662]8d 06 20STA a:PPUADDR; Write as the lower byte.
; ; Begin our tile write loop. ; ; This will write up to 32 tiles (width of the screen). ;
[d665]a2 00LDX #$00; X = 0 (loop counter). [d667]@_loop: [d667]bd 20 02LDA a:DataArray,X; Load a tile from the queue. [d66a]8d 07 20STA a:PPUDATA; Write it to the PPU. [d66d]e8INX; X++ (loop counter). [d66e]e0 20CPX #$20; Are we under 32 columns? [d670]90 f5BCC @_loop; If so, loop. [d672]60RTS
;============================================================================ ; Write a column of block tiles to the PPU. ; ; This will take the queued column of blocks starting at ; Temp_0200 and begin writing them to the PPU, 2 tiles ; at a time, progressing down the screen. ; ; INPUTS: ; PPU_ControlFlags: ; The current saved PPU control flags. ; ; Screen_ScrollHorizBlocks_PPUTileMapAddr_U: ; Screen_ScrollHorizBlocks_PPUTileMapAddr_L: ; The tilemap address to place the block tiles in. ; ; Temp_0200: ; The queued column of tiles to write. ; ; OUTPUTS: ; PPUADDR: ; PPUCTRL: ; PPUDATA: ; The written PPU state. ; ; XREFS: ; SCREEN_WRITESCROLL_HANDLERS_L ; [$PRG15_MIRROR::d64d] ; SCREEN_WRITESCROLL_HANDLERS_U ; [$PRG15_MIRROR::d651] ;============================================================================
[d673]Screen_WriteBlockTilesColumnToPPU:
; ; Tell the PPU we want to write 32 bytes at a time, going down. ;
[d673]a5 0aLDA PPU_ControlFlags; Load the current PPU control flags. [d675]09 04ORA #$04; Switch to add 32, going down. [d677]8d 00 20STA a:PPUCTRL; Write it to the PPU.
; ; Set the address in the PPU where tiles should be written to. ;
[d67a]a5 4dLDA Screen_ScrollHorizBlocks_PPUTileMapAddr_U; Load the upper byte of the tilemap address to write to. [d67c]8d 06 20STA a:PPUADDR; Write as the upper byte for the data. [d67f]a5 4cLDA Screen_ScrollHorizBlocks_PPUTileMapAddr_L; Load the lower byte. [d681]8d 06 20STA a:PPUADDR; Write as the lower byte.
; ; Begin our tile write loop. ; ; This will write up to 26 tiles (height of the screen). ;
[d684]a2 00LDX #$00; X = 0 (loop counter). [d686]@_loop: [d686]bd 00 02LDA a:Temp_0200,X; Load a tile from the block queue. [d689]8d 07 20STA a:PPUDATA; Write it to the PPU. [d68c]e8INX; X++ (loop counter). [d68d]e0 1aCPX #$1a; Are we under 26 rows? [d68f]90 f5BCC @_loop; If so, loop.
; ; Restore the PPU control flags. ;
[d691]a5 0aLDA PPU_ControlFlags; Load the current PPU control flags. [d693]29 fbAND #$fb; Switch back to add 1, going across. [d695]8d 00 20STA a:PPUCTRL; Write it to the PPU. [d698]60RTS
;============================================================================ ; Write a row of attributes to the PPU. ; ; This will take the queued row of attributes starting at ; DataArray and begin writing them to the PPU, one at a ; time, progressing across the screen. ; ; INPUTS: ; Screen_ScrollVertBlocks_PPUAttrAddr_U: ; Screen_ScrollVertBlocks_PPUAttrAddr_L: ; The attribute address to write to in the PPU. ; ; Screen_ScrollVertPPUAttrData: ; The row of attributes to write. ; ; OUTPUTS: ; PPUADDR: ; PPUDATA: ; The written PPU state. ; ; XREFS: ; SCREEN_WRITESCROLL_HANDLERS_L ; [$PRG15_MIRROR::d64e] ; SCREEN_WRITESCROLL_HANDLERS_U ; [$PRG15_MIRROR::d652] ;============================================================================
[d699]Screen_WriteBlockAttrsRowToPPU:
; ; Set the address in the PPU where attributes should be ; written to. ;
[d699]a5 53LDA Screen_ScrollVertBlocks_PPUAttrAddr_U; Load the upper byte of the attribute address to write to. [d69b]8d 06 20STA a:PPUADDR; Write as the upper byte for the attributes. [d69e]a5 52LDA Screen_ScrollVertBlocks_PPUAttrAddr_L; Load the lower byte. [d6a0]8d 06 20STA a:PPUADDR; Write as the lower byte.
; ; Begin our attribute write loop. ; ; This will write up to 8 attributes in a single row. ;
[d6a3]a2 00LDX #$00; X = 0 (loop counter). [d6a5]@_loop: [d6a5]bd 8a 02LDA a:Screen_ScrollVertPPUAttrData,X; Load the attribute to write. [d6a8]8d 07 20STA a:PPUDATA; Write it to the PPU. [d6ab]e8INX; X++ (loop counter). [d6ac]e0 08CPX #$08; Are we under 8 attribute columns? [d6ae]90 f5BCC @_loop; If so, loop. [d6b0]60RTS
;============================================================================ ; Write a column of attributes to the PPU. ; ; This will take the queued column of attributes starting ; at and begin writing them to the PPU, one at a time, ; progressing down the screen. ; ; INPUTS: ; Screen_ScrollVertBlocks_PPUAttrAddr_U: ; Screen_ScrollVertBlocks_PPUAttrAddr_L: ; The attribute address to write to in the PPU. ; ; Screen_ScrollHorizPPUAttrData: ; The column of attributes to write. ; ; OUTPUTS: ; PPUADDR: ; PPUDATA: ; The written PPU state. ; ; XREFS: ; SCREEN_WRITESCROLL_HANDLERS_L ; [$PRG15_MIRROR::d64f] ; SCREEN_WRITESCROLL_HANDLERS_U ; [$PRG15_MIRROR::d653] ;============================================================================
[d6b1]Screen_WriteBlockAttrsColumnToPPU:
; ; Begin our attribute write loop. ; ; This will write up to 7 attributes one row at a time along ; the same column. ; ; The first row of attributes in the PPU is skipped, since it's ; not part Of screen data. ;
[d6b1]a2 00LDX #$00; X = 0 (loop counter). [d6b3]@_loop: [d6b3]a5 51LDA Screen_ScrollHorizBlocks_PPUAttrAddr_U; Load the upper byte of the attribute address to write to. [d6b5]8d 06 20STA a:PPUADDR; Write as the upper byte for the attributes. [d6b8]8aTXA; A = loop counter. [d6b9]0aASL A; Multiply by 8 (attributes per row) to get the target row. [d6ba]0aASL A [d6bb]0aASL A [d6bc]18CLC [d6bd]65 50ADC Screen_ScrollHorizBlocks_PPUAttrAddr_L; Add the lower byte of the PPU address to write to. [d6bf]8d 06 20STA a:PPUADDR; Write it. [d6c2]bd 82 02LDA a:Screen_ScrollHorizPPUAttrData,X; Load the attribute to write. [d6c5]8d 07 20STA a:PPUDATA; Write it. [d6c8]e8INX; X++ (loop counter). [d6c9]e0 07CPX #$07; Are we under 7 attribute rows? [d6cb]90 e6BCC @_loop; If so, loop. [d6cd]60RTS
;============================================================================ ; 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]86 00STX Temp_00; Temporarily store the block position. [d6d0]a2 00LDX #$00; X = 0 (loop counter) [d6d2]a5 b7LDA Blocks_Result; A = block type (value to search for) [d6d4]@_checkBlockLoop: [d6d4]dd ef d6CMP a:BREAKABLE_FLOOR_TRANSITIONS,X; Does the block match the table at this index? [d6d7]f0 07BEQ @_finishLoop; If so, jump. [d6d9]e8INX; Else, X++ [d6da]e0 03CPX #$03; Is X < 3? [d6dc]90 f6BCC @_checkBlockLoop; If so, loop. [d6de]b0 01BCS @_updateBlocks; Else, jump to update the block. [d6e0]@_finishLoop: [d6e0]e8INX; X++ (table index of block to set) [d6e1]@_updateBlocks: [d6e1]bd ef d6LDA a:BREAKABLE_FLOOR_TRANSITIONS,X; Load the block to set to. [d6e4]8d c9 03STA a:Arg_BlockAttributesIndex; DEADCODE: This is overridden in Area_SetBlocks [d6e7]a6 00LDX Temp_00; X = Block position (stored earlier) [d6e9]9d 00 06STA a:ScreenBuffer,X; Store in the screen buffer as the new block. [d6ec]4c c5 d7JMP 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]34.byte $34; [0]:
; ; XREFS: ; Area_HandleBreakableFloor ;
[d6f0]BREAKABLE_FLOOR_TRANSITIONS_1_: [d6f0]2c.byte $2c; [1]:
; ; XREFS: ; Area_HandleBreakableFloor ;
[d6f1]BREAKABLE_FLOOR_TRANSITIONS_2_: [d6f1]5c.byte $5c; [2]: [d6f2]13.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]20.byte $20; [0]: Screen 0 [d6f4]24.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:
; ; Push the stone covering block offset to the stack for later. ;
[d6f5]8aTXA; A = X [d6f6]48PHA; Push A to the stack.
; ; Clear the top stone covering. ;
[d6f7]a6 d5LDX PathToMascon_FountainCoverPos; X = Block position (in $YX form). [d6f9]ad 68 d7LDA a:MASCON_FOUNTAIN_BLOCK_1_AIR; A = Block ID to place. [d6fc]9d 00 06STA a:ScreenBuffer,X; Store it in the screen buffer at the target location. [d6ff]20 c5 d7JSR Area_SetBlocks; Update the block on the screen.
; ; Clear the bottom stone covering. ;
[d702]a5 d5LDA PathToMascon_FountainCoverPos; X = Block position (in $YX form). [d704]18CLC [d705]69 10ADC #$10; X += 16 (next row down; YPos += 1). [d707]aaTAX; X = A [d708]ad 69 d7LDA a:MASCON_FOUNTAIN_BLOCK_2_AIR; A = Block ID to place. [d70b]9d 00 06STA a:ScreenBuffer,X; Store it in the screen buffer at the target location. [d70e]20 c5 d7JSR Area_SetBlocks; Update the block on the screen.
; ; Pop the original stone covering block offset. ;
[d711]68PLA; Pull A from the stack (original block position) [d712]aaTAX; X = A [d713]bd 6c d7LDA a:GAME_LADDER_TO_MASCON_BLOCK_OFFSETS,X; A = Block position offset, based on the arguments. [d716]18CLC [d717]65 d5ADC PathToMascon_FountainCoverPos; A += Target block position. [d719]aaTAX; X = A [d71a]86 d5STX PathToMascon_FountainCoverPos; Update the block position.
; ; Place the top stone covering. ;
[d71c]ad 6a d7LDA a:MASCON_FOUNTAIN_BLOCK_1_STONE; A = Stone block. [d71f]9d 00 06STA a:ScreenBuffer,X; Place it in the screen buffer at the offset. [d722]20 c5 d7JSR Area_SetBlocks; Update the block on the screen.
; ; Place the bottom stone covering. ;
[d725]a5 d5LDA PathToMascon_FountainCoverPos; A = Block position. [d727]18CLC [d728]69 10ADC #$10; A += 16 (next row). [d72a]aaTAX; X = A [d72b]ad 6b d7LDA a:MASCON_FOUNTAIN_BLOCK_2_STONE; A = Stone block. [d72e]9d 00 06STA a:ScreenBuffer,X; Place it in the screen buffer. [d731]20 c5 d7JSR Area_SetBlocks; Update the block on the screen.
; ; Prepare to animate the dropping of the ladder. ;
[d734]a9 07LDA #$07 [d736]85 d9STA PathToMascon_LadderBlocksRemaining; Set the ladder block count to 7. [d738]a9 22LDA #$22 [d73a]85 d8STA 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]ad 2d 04LDA a:Quests; Load the completed quests. [d73f]29 20AND #$20; Is Path to Mascon completed? [d741]d0 08BNE @_clearCoverings; If it is, jump.
; ; Path to Mascon is not complete. Wait for 30 interrupts. ;
[d743]a2 1eLDX #$1e; X = 30 (loop counter) [d745]@_waitforInterruptLoop: [d745]20 2e caJSR WaitForInterrupt; Wait for an interrupt. [d748]caDEX; X--; [d749]d0 faBNE @_waitforInterruptLoop; If not 0, loop.
; ; Clear the top stone block on the fountain. ;
[d74b]@_clearCoverings: [d74b]a6 d5LDX PathToMascon_FountainCoverPos; X = Fountain cover block position. [d74d]ad 68 d7LDA a:MASCON_FOUNTAIN_BLOCK_1_AIR; A = Air block. [d750]9d 00 06STA a:ScreenBuffer,X; Set in the screen buffer at the cover position. [d753]20 c5 d7JSR Area_SetBlocks; Update the block on the screen.
; ; Clear the bottom stone block on the fountain. ;
[d756]a5 d5LDA PathToMascon_FountainCoverPos; A = Fountain cover block position. [d758]18CLC [d759]69 10ADC #$10; A += 16 (next row). [d75b]aaTAX; X = A [d75c]ad 69 d7LDA a:MASCON_FOUNTAIN_BLOCK_2_AIR; A = Air block. [d75f]9d 00 06STA a:ScreenBuffer,X; Set in the screen buffer at the cover position. [d762]20 c5 d7JSR Area_SetBlocks; Update the block on the screen.
; ; Drop the ladder to the path to Mascon. ;
[d765]4c 6e d7JMP Game_DropLadderToMascon; Drop the ladder.
;============================================================================ ; Two cleared fountain stone block coverings. ; ; XREFS: ; Game_OpenPathToMascon ;============================================================================
; ; XREFS: ; Game_OpenPathToMascon ;
[d768]MASCON_FOUNTAIN_BLOCK_1_AIR: [d768]42.byte $42; Air
; ; XREFS: ; Game_OpenPathToMascon ;
[d769]MASCON_FOUNTAIN_BLOCK_2_AIR: [d769]42.byte $42; Air
;============================================================================ ; Two set fountain stone block coverings. ; ; XREFS: ; Game_OpenPathToMascon ;============================================================================
; ; XREFS: ; Game_OpenPathToMascon ;
[d76a]MASCON_FOUNTAIN_BLOCK_1_STONE: [d76a]88.byte $88; Stone cover
; ; XREFS: ; Game_OpenPathToMascon ;
[d76b]MASCON_FOUNTAIN_BLOCK_2_STONE: [d76b]88.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]01.byte $01; [0]: X + 1 [d76d]ff.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:
; ; 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]ad 35 04LDA a:Area_Region; Load the current region. [d771]c9 01CMP #$01; Are we in Trunk? [d773]d0 0eBNE @_dropLadderLoop; If not, jump. [d775]a5 63LDA Area_CurrentScreen; Load the current screen. [d777]c9 28CMP #$28; Is it the screen with the blocked path? [d779]d0 08BNE @_dropLadderLoop; If not, jump.
; ; We're on the right screen. Mark the path as opened. ;
[d77b]ad 2d 04LDA a:Quests; Load the quests. [d77e]09 20ORA #$20; Mark the path to Mascon opened. [d780]8d 2d 04STA a:Quests; Store it.
; ; Wait for 4 interrupts. ;
[d783]@_dropLadderLoop: [d783]20 2e caJSR WaitForInterrupt [d786]20 2e caJSR WaitForInterrupt [d789]20 2e caJSR WaitForInterrupt [d78c]20 2e caJSR WaitForInterrupt
; ; Play the Drop Ladder sound effect. ;
[d78f]a9 17LDA #$17; 0x17 == Drop ladder sound effect. [d791]20 e4 d0JSR Sound_PlayEffect; Play the sound effect.
; ; Place the ladder block. ;
[d794]a6 d8LDX PathToMascon_LadderPos; X = Next ladder block position. [d796]ad af d7LDA a:DROP_LADDER_TO_MASCON_LADDER_BLOCK; A = Ladder block. [d799]9d 00 06STA a:ScreenBuffer,X; Store in the screen buffer at X. [d79c]20 c5 d7JSR Area_SetBlocks; Update blocks on the screen.
; ; Increment the ladder position, decrement the number of blocks ; to place, and loop. ;
[d79f]a5 d8LDA PathToMascon_LadderPos; A = Ladder position. [d7a1]18CLC [d7a2]69 10ADC #$10; A += 16 (next row) [d7a4]85 d8STA PathToMascon_LadderPos; Store as the new position. [d7a6]c6 d9DEC PathToMascon_LadderBlocksRemaining; Decrement the number of ladder blocks to place. [d7a8]d0 d9BNE @_dropLadderLoop; If there are still blocks remaining, loop.
; ; Clear the "Opening Path" flag. ;
[d7aa]a9 00LDA #$00 [d7ac]85 d4STA PathToMascon_Opening; Set opening to 0. [d7ae]60RTS
; ; XREFS: ; Game_DropLadderToMascon ;
[d7af]DROP_LADDER_TO_MASCON_LADDER_BLOCK: [d7af]20.byte $20; Ladder block
;============================================================================ ; TODO: Document SpriteBehavior_EnemyUnused18_SomethingSetBlocks ; ; INPUTS: ; X ; ; OUTPUTS: ; A ; ; XREFS: ; SpriteBehavior_EnemyUnused18__9991 ;============================================================================
[d7b0]SpriteBehavior_EnemyUnused18_SomethingSetBlocks: [d7b0]8aTXA [d7b1]48PHA [d7b2]98TYA [d7b3]48PHA [d7b4]ae cf 03LDX a:Something_UnusedSprite_ScreenBufferOffset [d7b7]ad ce 03LDA a:Something_UnusedSprite_BlockOffset [d7ba]9d 00 06STA a:ScreenBuffer,X [d7bd]20 c5 d7JSR Area_SetBlocks [d7c0]68PLA [d7c1]a8TAY [d7c2]68PLA [d7c3]aaTAX [d7c4]60RTS
;============================================================================ ; Put blocks on the screen. ; ; INPUTS: ; A: ; The 0-based index into the block data. ; ; X: ; The 0-based index of the block position to write. ; ; XREFS: ; Area_HandleBreakableFloor ; Game_DropLadderToMascon ; Game_OpenPathToMascon ; Player_UseMattock ; SpriteBehavior_EnemyUnused18_SomethingSetBlocks ;============================================================================
[d7c5]Area_SetBlocks: [d7c5]8d c9 03STA a:Arg_BlockAttributesIndex; Set the index of the block attribute to load. [d7c8]86 00STX Temp_00; Store the destination index of the block to write. [d7ca]8aTXA; A = X (write index). [d7cb]48PHA; Push it to the stack.
; ; Switch to the area metadata bank. ;
[d7cc]ad 00 01LDA a:CurrentROMBank; Load the current ROM bank. [d7cf]48PHA; Push it to the stack. [d7d0]a2 03LDX #$03; Bank 3 = Area metadata. [d7d2]20 1a ccJSR MMC1_UpdateROMBank; Switch to it.
; ; Restore our state for the block writes. ;
[d7d5]a5 00LDA Temp_00; Load the destination index. [d7d7]48PHA; Push it to the stack.
; ; Set the PPU address for the block. ;
[d7d8]aaTAX; X = Loaded destination index. [d7d9]20 ac d8JSR Area_SetPPUAddrForBlockIndex; Set the PPU address for this block. [d7dc]20 ff d7JSR Area_SetBlocks_WriteBlockDataColumn1; Write column 1 of the block's tiles. [d7df]ad e8 00LDA a:PPU_TargetAddr; Load the PPU target address we set before. [d7e2]18CLC [d7e3]69 20ADC #$20; Increment by 32 bytes (2 tiles). [d7e5]8d e8 00STA a:PPU_TargetAddr; Set as the new lower byte of the PPU address. [d7e8]ad e9 00LDA a:PPU_TargetAddr_U; Load the upper byte. [d7eb]69 00ADC #$00; Add carry, if the lower byte wrapped. [d7ed]8d e9 00STA a:PPU_TargetAddr_U; Set as the new upper byte. [d7f0]20 16 d8JSR Area_SetBlocks_WriteBlockDataColumn2; Write column 2 of the block's tiles.
; ; Begin updating the PPU attributes. ;
[d7f3]68PLA; Pop the write index from the stack. [d7f4]20 2d d8JSR Area_SetBlocks_SetAttributes; Write the PPU attributes for the block.
; ; Switch back to the previous bank. ;
[d7f7]68PLA; Pop the previous bank. [d7f8]aaTAX [d7f9]20 1a ccJSR MMC1_UpdateROMBank; Switch back to that bank. [d7fc]68PLA; Pop the write index from the stack. [d7fd]aaTAX; Set it back on X. [d7fe]60RTS; Return it.
;============================================================================ ; Write the first column of tiles of a block 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 top-left tile. ; ; CurrentArea_BlockData2StartAddr: ; The block data used for the bottom-left tile. ; ; Arg_BlockAttributesIndex: ; The caller-provided 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_WriteBlockDataColumn1:
; ; Set the tile length to 2. ;
[d7ff]a9 02LDA #$02 [d801]20 dc cfJSR PPUBuffer_QueueCommandOrLength; Write length of 2.
; ; Load the first block's tile and write to the PPU. ;
[d804]ac c9 03LDY a:Arg_BlockAttributesIndex; Load the requested block index within the area data. [d807]b1 80LDA (CurrentArea_BlockData1StartAddr),Y; Load the top-left tile for the block. [d809]9d 00 05STA a:PPUBuffer,X; Store it the PPU buffer.
; ; Load the second block's tile and write to the PPU. ;
[d80c]e8INX; X++ (PPU buffer offset). [d80d]b1 82LDA (CurrentArea_BlockData2StartAddr),Y; Load the bottom-left tile for the block. [d80f]9d 00 05STA a:PPUBuffer,X; Store it the PPU buffer. [d812]e8INX; X++ (PPU buffer offset).
; ; Set the new upper bounds of the buffer. ;
[d813]86 20STX PPUBuffer_WriteOffset; Set X as the new upper bounds of the PPU buffer. [d815]60RTS
;============================================================================ ; Write the second column of tiles of a block 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 top-right tile. ; ; CurrentArea_BlockData4StartAddr: ; The block data used for the bottom-right tile. ; ; Arg_BlockAttributesIndex: ; The caller-provided 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_WriteBlockDataColumn2:
; ; Set the tile length to 2. ;
[d816]a9 02LDA #$02 [d818]20 dc cfJSR PPUBuffer_QueueCommandOrLength; Write length of 2.
; ; Load the first block and write to the PPU. ;
[d81b]ac c9 03LDY a:Arg_BlockAttributesIndex; Load the requested block index within the area data. [d81e]b1 84LDA (CurrentArea_BlockData3StartAddr),Y; Load the top-right tile for the block. [d820]9d 00 05STA a:PPUBuffer,X; Store it the PPU buffer.
; ; Load the second block and write to the PPU. ;
[d823]e8INX; X++ (PPU buffer offset). [d824]b1 86LDA (CurrentArea_BlockData4StartAddr),Y; Load the bottom-right tile for the block. [d826]9d 00 05STA a:PPUBuffer,X; Store it the PPU buffer. [d829]e8INX; X++ (PPU buffer offset).
; ; Set the new upper bounds of the buffer. ;
[d82a]86 20STX PPUBuffer_WriteOffset; Set X as the new upper bounds of the PPU buffer. [d82c]60RTS
;============================================================================ ; Set the attributes for a block. ; ; INPUTS: ; A: ; The block position index. ; ; PPU_ScrollScreen ; The current scroll screen. ; ; SET_BLOCKS_TILEMAP_OFFSETS_U: ; SET_BLOCKS_TILEMAP_OFFSETS_L: ; A mapping table for computing offsets into ; a PPU screen tilemap address from a scroll ; screen index. ; ; SET_BLOCKS_SCREEN_TILEMAP_ADDRS_U: ; SET_BLOCKS_SCREEN_TILEMAP_ADDRS_U: ; A mapping table for PPU start addresses for ; updating blocks. ; ; SET_BLOCKS_TILE_CORNER_MASK_INVERT: ; A mapping table for masking all but a corner's ; bits. ; ; SET_BLOCKS_TILE_CORNER_MASK: ; A mapping table for masking a corner's bits. ; ; OUTPUTS: ; PPUBuffer: ; PPUBuffer_WriteOffset: ; PPU_TargetAddr: ; PPU_TargetAddr+1: ; The updated PPU buffer state. ; ; Temp_00: ; Temp_06: ; Clobbered. ; ; CALLS: ; PPUBuffer_QueueCommandOrLength ; ; XREFS: ; Area_SetBlocks ;============================================================================
[d82d]Area_SetBlocks_SetAttributes:
; ; 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]18CLC [d82e]69 20ADC #$20; A = index + 32 [d830]85 00STA 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]4aLSR A; A = A / 4 [d833]4aLSR A [d834]29 38AND #$38; Round down to a multiple of 8. [d836]8d e8 00STA 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]a5 00LDA Temp_00; A = normalized offset. [d83b]29 0fAND #$0f; A = lower nibble of A. [d83d]4aLSR A; A = A / 2 [d83e]0d e8 00ORA a:PPU_TargetAddr; OR with the floored offset. [d841]8d e8 00STA 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]a5 00LDA Temp_00; A = normalized offset. [d846]29 10AND #$10; Keep bit 4 (result = 0 or 16) [d848]4aLSR A; Convert to value 0 or 2. [d849]4aLSR A [d84a]4aLSR A [d84b]85 06STA Temp_06; Store the result. [d84d]a5 00LDA Temp_00; A = floored offset. [d84f]29 01AND #$01; Keep bit 0 (result = 0 or 1). [d851]05 06ORA Temp_06; OR with the value we just calculated (result = 0..3) [d853]85 06STA Temp_06; Store it.
; ; Load the block attributes for this block. ;
[d855]ac c9 03LDY a:Arg_BlockAttributesIndex; Y = block attributes index for the new block. [d858]b1 7eLDA (CurrentArea_BlockAttributesAddr),Y; A = block attributes for that index. [d85a]a6 06LDX Temp_06; X = corner (0..3 value from above). [d85c]3d a4 d8AND a:SET_BLOCKS_TILE_CORNER_MASK,X; A = block attributes for the given corner. [d85f]48PHA; Push it to the stack.
; ; Get the screen index we're drawing to. ;
[d860]a5 0dLDA PPU_ScrollScreen; A = Scroll screen to update. [d862]29 01AND #$01; Keep the right-most bit (0 or 1) [d864]aaTAX; 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]bd cf d4LDA a:SET_BLOCKS_TILEMAP_OFFSETS_L,X; A = lower byte of PPU address [d868]85 02STA Temp_Addr_L; Store it. [d86a]bd d1 d4LDA a:SET_BLOCKS_TILEMAP_OFFSETS_U,X; A = upper byte of PPU address [d86d]85 03STA Temp_Addr_U; Store it.
; ; Compute the upper starting byte for the PPU address. ;
[d86f]bd f3 d6LDA a:SET_BLOCKS_SCREEN_TILEMAP_ADDRS_U,X; A = tilemap address for the screen. [d872]8d e9 00STA 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]ac e8 00LDY a:PPU_TargetAddr; Y = lower byte of tilemap address computed above. [d878]b1 02LDA (Temp_Addr_L),Y; Load the data for this block. [d87a]a6 06LDX Temp_06; X = corner value (0..3) [d87c]3d a8 d8AND a:SET_BLOCKS_TILE_CORNER_MASK_INVERT,X; Invert the corner mask (keep all non-updated corner data). [d87f]85 06STA Temp_06; Store as the new value. [d881]68PLA; Pull the updated block attributes for the corner. [d882]05 06ORA Temp_06; OR it with the non-updating corner values. [d884]91 02STA (Temp_Addr_L),Y; And store it. [d886]48PHA; 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]ad e9 00LDA a:PPU_TargetAddr_U; Load the upper byte of the PPU address. [d88a]09 03ORA #$03; Ensure it starts at 0x03 (address >= $03XX). Realistically, $23C0 (we guarantee 0x20 or 0x24 above for the screen). [d88c]8d e9 00STA a:PPU_TargetAddr_U; Store as the new upper byte. [d88f]ad e8 00LDA a:PPU_TargetAddr; Load the lower byte. [d892]09 c0ORA #$c0; Ensure it starts at 0xC0 (address >= $XXC0) [d894]8d e8 00STA a:PPU_TargetAddr; Store it.
; ; Write the attribute data to the PPU buffer. ;
[d897]a9 01LDA #$01 [d899]20 dc cfJSR PPUBuffer_QueueCommandOrLength; Queue 1 byte. [d89c]68PLA; Pull the attribute data to write from stack. [d89d]9d 00 05STA a:PPUBuffer,X; Store it in the buffer at X. [d8a0]e8INX; X++ (new offset) [d8a1]86 20STX PPUBuffer_WriteOffset; Store it. [d8a3]60RTS
;============================================================================ ; A mapping table for masking a corner's bits. ; ; XREFS: ; Area_SetBlocks_SetAttributes ;============================================================================
; ; XREFS: ; Area_SetBlocks_SetAttributes ;
[d8a4]SET_BLOCKS_TILE_CORNER_MASK: [d8a4]03.byte $03; [0]: [d8a5]0c.byte $0c; [1]: [d8a6]30.byte $30; [2]: [d8a7]c0.byte $c0; [3]:
;============================================================================ ; A mapping table for masking all but a corner's bits. ; ; XREFS: ; Area_SetBlocks_SetAttributes ;============================================================================
; ; XREFS: ; Area_SetBlocks_SetAttributes ;
[d8a8]SET_BLOCKS_TILE_CORNER_MASK_INVERT: [d8a8]fc.byte $fc; [0]: [d8a9]f3.byte $f3; [1]: [d8aa]cf.byte $cf; [2]: [d8ab]3f.byte $3f; [3]:
;============================================================================ ; TODO: Document Area_SetPPUAddrForBlockIndex ; ; INPUTS: ; X ; ; OUTPUTS: ; TODO ; ; XREFS: ; Area_SetBlocks ;============================================================================
[d8ac]Area_SetPPUAddrForBlockIndex:
; ; Build an address based on the block index. ; ; The upper byte will always be 0. The lower byte will be ; the lower nibble * 2. ;
[d8ac]a9 00LDA #$00; 0 = Upper byte of address. [d8ae]8d e9 00STA a:PPU_TargetAddr_U; Set as the PPU target address.
; ; Compute the lower byte of the target PPU address based off ; the block index. ; ; This will be the lower nibble * 2. ;
[d8b1]8aTXA; A = Block index. [d8b2]29 0fAND #$0f; Keep the lower nibble. [d8b4]0aASL A; Multiply by 2. [d8b5]8d e8 00STA a:PPU_TargetAddr; Set as the lower byte of the PPU target address.
; ; Compute the upper byte of the target PPU address. ;
[d8b8]8aTXA; A = Block index. [d8b9]29 f0AND #$f0; Keep the upper nibble. [d8bb]0aASL A; Multiply by 2. [d8bc]2e e9 00ROL a:PPU_TargetAddr_U [d8bf]0aASL A [d8c0]2e e9 00ROL a:PPU_TargetAddr_U [d8c3]18CLC [d8c4]6d e8 00ADC a:PPU_TargetAddr; Add the lower byte of the PPU target address. [d8c7]8d e8 00STA a:PPU_TargetAddr; Store as the lower byte of the PPU target address. [d8ca]a5 0dLDA PPU_ScrollScreen; Load the scroll screen. [d8cc]29 01AND #$01; Convert to a 0 = Left, 1 = Right value. [d8ce]a8TAY; Y = result. [d8cf]ad e9 00LDA a:PPU_TargetAddr_U; Load the upper byte of the PPU target address. [d8d2]19 ea d8ORA a:AREA_SETPPUADDRFORBLOCK_U_BITS,Y; Apply bits to the value of the upper byte of the address. [d8d5]8d e9 00STA a:PPU_TargetAddr_U; Store as the new upper byte of the adress. [d8d8]ad e8 00LDA a:PPU_TargetAddr; Load the lower byte of the address. [d8db]18CLC [d8dc]69 80ADC #$80; Add 0x80 to it. [d8de]8d e8 00STA a:PPU_TargetAddr; Store as the new lower byte. [d8e1]ad e9 00LDA a:PPU_TargetAddr_U; Load the upper byte. [d8e4]69 00ADC #$00; Add carry, if the lower byte wrapped. [d8e6]8d e9 00STA a:PPU_TargetAddr_U; Store as the new upper byte. [d8e9]60RTS
; ; XREFS: ; Area_SetPPUAddrForBlockIndex ;
[d8ea]AREA_SETPPUADDRFORBLOCK_U_BITS: [d8ea]20.byte $20; [0]: Scroll screen 0 [d8eb]24.byte $24; [1]: Scroll screen 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]a2 ffLDX #$ff; X = 0xFF [d8ee]9aTXS; Set as stack pointer
; ; Switch to bank 14 (Logic) ;
[d8ef]a2 0eLDX #$0e [d8f1]20 1a ccJSR MMC1_UpdateROMBank; Switch to bank 14.
; ; Clear all player bits but the facing direction. ;
[d8f4]a5 a4LDA Player_Flags; Load the player's flags. [d8f6]29 40AND #$40; Remove all but the facing direction bit. [d8f8]85 a4STA Player_Flags; Store it back.
; ; Clear out more player state. ;
[d8fa]a9 00LDA #$00 [d8fc]85 a5STA Player_StatusFlag; Set player status to 0. [d8fe]85 a3STA Player_MovementTick; Set the movement tick to 0. [d900]85 adSTA Player_InvincibilityPhase; Set invincibility phase to 0.
; ; Push the inventory state to the stack. ;
[d902]ad bd 03LDA a:SelectedWeapon; Load the selected weapon. [d905]48PHA; Push to the stack. [d906]ad be 03LDA a:SelectedArmor; Load the selected armor. [d909]48PHA; Push to the stack. [d90a]ad bf 03LDA a:SelectedShield; Load the selected shield. [d90d]48PHA; Push to the stack. [d90e]ad c0 03LDA a:SelectedMagic; Load the selected magic. [d911]48PHA; Push to the stack. [d912]ad c1 03LDA a:SelectedItem; Load the selected item. [d915]48PHA; Push to the stack.
; ; Wait for interrupt and call other functions to ; reset state. ;
[d916]20 2e caJSR WaitForInterrupt; Wait for interrupt. [d919]20 47 cbJSR Screen_ResetSpritesForGamePlay; Reset animations. [d91c]20 bf b7JSR PlayerDeath_ResetSelectedItemState; Reset selected item state. [d91f]20 45 edJSR Player_DrawSprite; Update player sprite.
; ; Prepare state for screen transitions. ;
[d922]a9 00LDA #$00 [d924]8d db 03STA a:Screen_TransitionCounter; Clear the screen transition counter.
; ; Reset the player death state. ;
[d927]8d 38 04STA a:PlayerIsDead; Clear the dead flag. [d92a]a9 ffLDA #$ff [d92c]8d dd 03STA a:Player_DeathAnimationPhase; Clear the death animation phase. [d92f]8d de 03STA a:Player_DeathAnimationCounter; Clear the death animation counter. [d932]8d 30 04STA a:Screen_FadeOutStage; Clear the palette index.
; ; Clear the sprites and music. ;
[d935]20 30 c1JSR Screen_ClearSprites; Clear sprites from the screen. [d938]a9 00LDA #$00 [d93a]85 faSTA Music_Current; Clear the music.
; ; Play the death sound effect. ;
[d93c]a9 16LDA #$16; 0x16 == Player death sound. [d93e]20 e4 d0JSR Sound_PlayEffect; Play the sound effect.
; ; Begin our death animation loop. ; ; This will dissolve the user's sprite. ;
[d941]@_dissolvePlayerLoop: [d941]20 25 caJSR WaitForNextFrame; Wait for the next frame. [d944]20 47 cbJSR Screen_ResetSpritesForGamePlay; Reset animations. [d947]20 42 daJSR Screen_NextTransitionState; Prepare for the next screen state. [d94a]20 ee ebJSR Player_DrawBody; Draw the player. [d94d]20 d6 d9JSR Player_DrawDeathAnimation; Draw the next cycle of the death animation.
; ; Check whether to reset the animation state. ;
[d950]ad dd 03LDA a:Player_DeathAnimationPhase; Load the animation phase. [d953]c9 ffCMP #$ff; Is it 0xFF (complete)? [d955]d0 0fBNE @_prepareNextLoop; If not, prepare for the next loop. [d957]ad db 03LDA a:Screen_TransitionCounter; Load the screen transition counter. [d95a]c9 10CMP #$10; Is it >= 16? [d95c]90 08BCC @_prepareNextLoop; If so, prepare for next loop.
; ; Clear animation state. ;
[d95e]a9 00LDA #$00 [d960]8d dd 03STA a:Player_DeathAnimationPhase; Clear the animation phase. [d963]8d de 03STA a:Player_DeathAnimationCounter; Clear the animation counter. [d966]@_prepareNextLoop: [d966]ee db 03INC a:Screen_TransitionCounter; Increment the screen transition counter. [d969]d0 3eBNE @_continueDissolvePlayerLoop; If transition counter != 0, loop.
; ; We're past the player death animation. ; ; Load the palette and update the screen. ;
[d96b]ad d0 03LDA a:Screen_PaletteIndex [d96e]20 3b d0JSR Screen_LoadUIPalette; Load the palette. [d971]20 90 d0JSR PPUBuffer_WritePalette; Append 0 to the PPU buffer.
; ; Restore the player's inventory. ;
[d974]68PLA; Pop the selected item from the stack. [d975]8d c1 03STA a:SelectedItem; Set it. [d978]68PLA; Pop the magic from the stack. [d979]8d c0 03STA a:SelectedMagic; Set it. [d97c]68PLA; Pop the shield from the stack. [d97d]8d bf 03STA a:SelectedShield; Set it. [d980]68PLA; Pop the armor from the stack. [d981]8d be 03STA a:SelectedArmor; Set it. [d984]68PLA; Pop the weapon from the stack. [d985]8d bd 03STA a:SelectedWeapon; Set it.
; ; Set the death music. ;
[d988]a9 08LDA #$08; 0x08 == Death music. [d98a]85 faSTA Music_Current; Set as the current music.
; ; Clear the sprite loaded state. ;
[d98c]a9 ffLDA #$ff [d98e]85 0eSTA Screen_ReadyState; Set loaded state to 0xFF.
; ; Run IScript 0xFF via jump to IScripts_Begin. ;
[d990]20 59 f8JSR MMC1_LoadBankAndJump; Run the IScript: [d993]0c.byte BANK_12_LOGIC; Bank = 12 [d994]41 82pointer IScripts_Begin-1; Address = IScripts_Begin [d996]@_afterIScriptFarJump: [d996]a9 00LDA #$00; 0 = No music [d998]85 faSTA Music_Current; Set it.
; ; Set initial gold and experience via jump ; to Player_SetInitialExpAndGold. ;
[d99a]20 59 f8JSR MMC1_LoadBankAndJump; Run: [d99d]0c.byte BANK_12_LOGIC; Bank = 12 [d99e]b0 95pointer Player_SetInitialExpAndGold-1; Address = Player_SetInitialExpAndGold [d9a0]@_afterSetExpGoldFarJump: [d9a0]20 2f daJSR Screen_FadeToBlack; Fade the screen to black. [d9a3]20 7d daJSR Game_InitStateForSpawn; Reset state for spawn. [d9a6]4c 0a dbJMP Player_Spawn; Spawn the player. [d9a9]@_continueDissolvePlayerLoop: [d9a9]4c 41 d9JMP @_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:
; ; Clear all sprites from the screen. ;
[d9ac]20 30 c1JSR Screen_ClearSprites; Clear sprites.
; ; Wait 120 frames before beginning the fade-out. ;
[d9af]a2 78LDX #$78; X = 120 (loop counter) [d9b1]@_beforeFadeLoop: [d9b1]8aTXA; A = X [d9b2]48PHA; Push to the stack. [d9b3]20 25 caJSR WaitForNextFrame; Wait for the next frame. [d9b6]20 47 cbJSR Screen_ResetSpritesForGamePlay; Reset for this frame. [d9b9]20 46 dcJSR Game_DrawScreenInFrozenState; Draw the screen and player in a semi-paused state. [d9bc]68PLA; Pop our loop counter. [d9bd]aaTAX; X = A [d9be]caDEX; X-- [d9bf]d0 f0BNE @_beforeFadeLoop; If > 0, loop.
; ; Fade the screen to black. ;
[d9c1]20 2f daJSR Screen_FadeToBlack; Fade to black.
; ; Wait another 120 frames before switching screens. ;
[d9c4]a2 78LDX #$78; X = 120 (loop counter) [d9c6]@_beforeTransitionLoop: [d9c6]8aTXA; A = X (loop counter) [d9c7]48PHA; Push to the stack. [d9c8]20 25 caJSR WaitForNextFrame; Wait for the next frame. [d9cb]20 47 cbJSR Screen_ResetSpritesForGamePlay; Reset for this frame. [d9ce]68PLA; Pop our loop counter. [d9cf]aaTAX; X = A (loop counter) [d9d0]caDEX; X-- [d9d1]d0 f3BNE @_beforeTransitionLoop; If > 0, loop.
; ; Move the player to the King's room and begin the ; end-game dialogue sequence. ;
[d9d3]4c 04 dbJMP 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]ad dd 03LDA a:Player_DeathAnimationPhase; Load the death animation phase. [d9d9]c9 08CMP #$08; Is it >= 8? [d9db]b0 43BCS @_return; If so, return. [d9dd]ad de 03LDA a:Player_DeathAnimationCounter; Load the animation death counter. [d9e0]10 0bBPL @_dissolveSprite; If >= 0, jump to dissolve the sprite. [d9e2]a5 1aLDA InterruptCounter; Else, load the interrupt counter. [d9e4]29 03AND #$03; Every 3 out of 4 ticks... [d9e6]d0 38BNE @_return; Return. [d9e8]a9 00LDA #$00 [d9ea]8d de 03STA 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]a6 20LDX PPUBuffer_WriteOffset; X = PPU buffer upper bounds. [d9ef]a9 faLDA #$fa; A = 0xFA (Remove Vertical Lines draw command). [d9f1]9d 00 05STA a:PPUBuffer,X; Set that as the command to execute.
; ; Set the current player phase for the draw. ;
[d9f4]e8INX; X++ [d9f5]ad dd 03LDA a:Player_DeathAnimationPhase; Load the animation phase. [d9f8]9d 00 05STA a:PPUBuffer,X; Set that as the phase parameter.
; ; Set the upper byte of the address to 0. ;
[d9fb]e8INX; X++ [d9fc]a9 00LDA #$00 [d9fe]9d 00 05STA a:PPUBuffer,X; Set 0 as the upper address.
; ; Set the lower byte of the address based on the counter. ;
[da01]e8INX; X++ [da02]ac de 03LDY a:Player_DeathAnimationCounter; Load the animation counter. [da05]b9 21 daLDA a:DEATH_ANIMATION_DRAW_ADDR_LOWER,Y; Load the lower address for Y. [da08]9d 00 05STA a:PPUBuffer,X; Set it as the lower address.
; ; Store the new upper bounds of the PPU buffer. ;
[da0b]e8INX; X++ [da0c]86 20STX PPUBuffer_WriteOffset; Set as the new upper bounds for the PPU buffer.
; ; Update the animation counter. ;
[da0e]ee de 03INC a:Player_DeathAnimationCounter; Increment the animation counter.
; ; If >= 8, reset the counter and increment the phase. ;
[da11]ad de 03LDA a:Player_DeathAnimationCounter; Load the new animation counter. [da14]c9 08CMP #$08; Is it < 8? [da16]90 08BCC @_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]a9 ffLDA #$ff [da1a]8d de 03STA a:Player_DeathAnimationCounter; Set the animation counter to 0xFF. [da1d]ee dd 03INC a:Player_DeathAnimationPhase; Increment the animation phase. [da20]@_return: [da20]60RTS
;============================================================================ ; 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]00.byte $00; [0]: [da22]10.byte $10; [1]: [da23]20.byte $20; [2]: [da24]30.byte $30; [3]: [da25]40.byte $40; [4]: [da26]50.byte $50; [5]: [da27]60.byte $60; [6]: [da28]70.byte $70; [7]:
;============================================================================ ; DEADCODE: Clear the fade-out stage for the screen transitions. ;============================================================================
[da29]UNUSED_Screen_ClearFadeOutStage: [da29]a9 ffLDA #$ff [da2b]8d 30 04STA a:Screen_FadeOutStage [da2e]60RTS
;============================================================================ ; 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]a9 00LDA #$00; X = 0 (loop counter) [da31]8d 30 04STA a:Screen_FadeOutStage; Set as the index into the screen palette lookup. [da34]@_loop: [da34]20 2e caJSR WaitForInterrupt; Wait for the next interrupt. [da37]20 42 daJSR Screen_NextTransitionState; Update the screen for this palete. [da3a]ad 30 04LDA a:Screen_FadeOutStage; Load the palette index. [da3d]c9 04CMP #$04; Is it < 4? [da3f]90 f3BCC @_loop; If so, loop. [da41]60RTS
;============================================================================ ; 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]ad 30 04LDA a:Screen_FadeOutStage; Load the current palette index. [da45]c9 04CMP #$04; Is it >= 4? [da47]b0 14BCS @_doneFadeOut; If so, jump to finish up. [da49]ad dc 03LDA a:Screen_FadeOutCounter; A = fadeout counter. [da4c]18CLC [da4d]69 32ADC #$32; A += 0x32 (50) [da4f]8d dc 03STA a:Screen_FadeOutCounter; Store as the new value. [da52]90 09BCC @_doneFadeOut; If 0, don't alter the palette.
; ; Fade out to the next palette towards black. ;
[da54]ad d0 03LDA a:Screen_PaletteIndex; Load the screen palette index. [da57]20 ad d0JSR Screen_SetFadeOutPalette; Load the palette into memory. [da5a]ee 30 04INC a:Screen_FadeOutStage; Increment the palette index to the next transition state. [da5d]@_doneFadeOut: [da5d]ad db 03LDA a:Screen_TransitionCounter; Load the screen transition counter. [da60]c9 50CMP #$50; Is it < 80? [da62]d0 05BNE @_return; If so, return.
; ; We're doing player death, and have finished dissolving ; the player. We can now begin fading to black. ;
[da64]a9 00LDA #$00 [da66]8d 30 04STA a:Screen_FadeOutStage; Set the palette to 0. [da69]@_return: [da69]60RTS
;============================================================================ ; 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]a2 ffLDX #$ff; X = 0xFF [da6c]9aTXS; Copy to stack pointer. [da6d]a9 00LDA #$00; A = 0 [da6f]85 24STA Area_CurrentArea; Set as the current area (Eolis) [da71]8d 35 04STA a:Area_Region; Set as the current region (Eolis) [da74]20 ae b7JSR Player_InitInventoryState; Initialize the player inventory. [da77]20 7d daJSR Game_InitStateForSpawn; Initiate the spawn state. [da7a]4c 65 fcJMP Game_ShowStartScreen; Show the start screen.
;============================================================================ ; Initialize state when spawning the player. ; ; This will clear the player's death, magic, and other ; states, set up the screen for drawing, load sprites, ; and get the game ready to play again. ; ; INPUTS: ; None. ; ; OUTPUTS: ; PlayerIsDead: ; Set to alive. ; ; Screen_ReadyState: ; Maybe_Game_Ready: ; Set to ready. ; ; PPU_ScrollScreen: ; Set to screen 0. ; ; PPU_ScrollX: ; Set to 0 (no scrolling offset). ; ; PPUBuffer_WriteOffset ; PPUBuffer_ReadOffset: ; Cleared. ; ; CALLS: ; PPU_WaitUntilFlushed ; Player_ClearVisibleMagic ; Player_SetInitialState ; Sprites_LoadCommon ; ; XREFS: ; Game_InitStateForStartScreen ;============================================================================
[da7d]Game_InitStateForSpawn: [da7d]20 f7 caJSR PPU_WaitUntilFlushed; Wait until the PPU is flushed. [da80]a9 00LDA #$00 [da82]8d 38 04STA a:PlayerIsDead; Set the player as alive. [da85]85 20STA PPUBuffer_WriteOffset; Reset PPU buffer upper bounds to 0. [da87]85 1fSTA PPUBuffer_ReadOffset; Reset PPU buffer offset to 0. [da89]85 0eSTA Screen_ReadyState; Mark the screen as loaded and ready. [da8b]20 aa e0JSR Player_SetInitialState; Initialize the player's initial state. [da8e]20 55 baJSR Player_ClearVisibleMagic; Clear any visible magic on screen. [da91]20 80 ceJSR Sprites_LoadCommon; Load sprites from bank 8. [da94]a9 01LDA #$01 [da96]8d 2f 04STA a:Maybe_Game_Ready; Set that the game is ready to play. [da99]a9 00LDA #$00 [da9b]85 0cSTA PPU_ScrollX; Clear the scrolling screen pixel state. [da9d]85 0dSTA PPU_ScrollScreen; Clear the scrolling screen state. [da9f]60RTS
;============================================================================ ; 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]20 f7 caJSR PPU_WaitUntilFlushed; Wait until the PPU is flushed. [daa3]20 46 ddJSR Screen_Load; Load the screen state. [daa6]20 17 cbJSR Screen_ResetForGamePlay; Reset the screen and enable interrupts. [daa9]20 25 caJSR WaitForNextFrame; Wait for the next frame. [daac]4c 8d c2JMP 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]20 f7 caJSR PPU_WaitUntilFlushed; Wait until the PPU is flushed. [dab2]20 06 deJSR Game_EnterBuilding; Load state for the building. [dab5]20 17 cbJSR Screen_ResetForGamePlay; Reset the screen and enable interrupts. [dab8]20 25 caJSR WaitForNextFrame; Wait for the next frame. [dabb]4c 8d c2JMP 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]20 f7 caJSR PPU_WaitUntilFlushed; Wait until the PPU is flushed. [dac1]20 66 deJSR Game_ExitBuilding; Load state for the area outside the building. [dac4]20 17 cbJSR Screen_ResetForGamePlay; Reset the screen and enable interrupts. [dac7]20 25 caJSR WaitForNextFrame; Wait for the next frame. [daca]4c 8d c2JMP 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]20 f7 caJSR PPU_WaitUntilFlushed; Wait until the PPU is flushed. [dad0]20 64 dfJSR Game_EnterAreaHandler; Load state for the new area. [dad3]20 17 cbJSR Screen_ResetForGamePlay; Reset the screen and enable interrupts. [dad6]20 25 caJSR WaitForNextFrame; Wait for the next frame. [dad9]4c 8d c2JMP 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]20 f7 caJSR PPU_WaitUntilFlushed; Wait until the PPU is flushed. [dadf]ae 35 04LDX a:Area_Region; Load the region. [dae2]bd fe daLDA a:DOOR_OUTSIDE_REGION_INDEXES,X; Load the area of the game to place the player for this region. [dae5]85 24STA Area_CurrentArea; Store as the new area. [dae7]20 f5 deJSR Game_LoadCurrentArea; Load data for the area. [daea]20 17 cbJSR Screen_ResetForGamePlay; Reset the screen and enable interrupts. [daed]20 25 caJSR WaitForNextFrame; Wait for the next frame. [daf0]20 8d c2JSR GameLoop_LoadSpriteImages; Load the sprite images. [daf3]4c 45 dbJMP Game_MainLoop; Run the game mainloop.
;============================================================================ ; DEADCODE ; ; Would assign the area based on transitioning from another. ; ; This isn't even a complete function. It runs right into the ; data table below. ;============================================================================
[daf6]FUN_PRG15_MIRROR__daf6: [daf6]ae 35 04LDX a:Area_Region [daf9]bd fe daLDA a:DOOR_OUTSIDE_REGION_INDEXES,X [dafc]85 24STA Area_CurrentArea
; ; XREFS: ; FUN_PRG15_MIRROR__daf6 ; Game_SetupAndLoadOutsideArea ;
[dafe]DOOR_OUTSIDE_REGION_INDEXES: [dafe]00.byte AREA_EOLIS; [0]: Eolis [daff]01.byte AREA_TRUNK; [1]: Trunk [db00]02.byte AREA_MIST; [2]: Mist [db01]05.byte AREA_BRANCH; [3]: Branch [db02]06.byte AREA_DARTMOOR; [4]: Dartmoor [db03]07.byte AREA_ZENIS; [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]20 dd ddJSR EndGame_MoveToKingsRoom; Place the player in the King's room. [db07]4c ef dbJMP 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]a9 50LDA #$50 [db0c]8d 31 04STA a:Player_HP_U; Set the initial player HP to 80. [db0f]8d 9a 03STA a:Player_MP; Set the initial player MP to 80. [db12]ad 2d 04LDA a:Quests; Load the completed quests. [db15]29 efAND #$ef; Disable the Mattock barrier to Mascon quest. [db17]8d 2d 04STA a:Quests; Store it. [db1a]20 af c6JSR Game_ClearTimedItems; Cleared all timed items. [db1d]20 61 ddJSR Game_SpawnInTemple; Spawn in the temple. [db20]20 af daJSR Game_SetupEnterBuilding; Change the state to be inside a building. [db23]4c 45 dbJMP 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]20 af c6JSR Game_ClearTimedItems; Clear all timed items. [db29]20 f7 caJSR PPU_WaitUntilFlushed; Wait until everything is drawn to the screen. [db2c]20 a7 deJSR Game_LoadFirstLevel; Load the first level.
; ; Clear the player's MP. ;
[db2f]a9 00LDA #$00 [db31]8d 9a 03STA a:Player_MP; Set MP to 0.
; ; Clear the player's experience. ;
[db34]a9 00LDA #$00 [db36]8d 90 03STA a:Experience; Set lower byte of experience to 0. [db39]8d 91 03STA a:Experience_U; And upper byte.
; ; Prepare state for the first screen of Eolis, including the ; intro trigger sprite entity. ;
[db3c]20 17 cbJSR Screen_ResetForGamePlay; Reset the screen and enable interrupts. [db3f]20 25 caJSR WaitForNextFrame; Wait for the next frame. [db42]20 8d c2JSR 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]a2 ffLDX #$ff; X = 0xFF [db47]9aTXS; Transfer to the stack.
; ; Switch to bank 14 to run sprite logic. ;
[db48]a2 0eLDX #$0e; Bank 14 = Logic [db4a]20 1a ccJSR MMC1_UpdateROMBank; Switch to the bank.
; ; Prepare to draw sprites. ;
[db4d]20 25 caJSR WaitForNextFrame; Wait for the next frame. [db50]20 47 cbJSR Screen_ResetSpritesForGamePlay; Reset the sprite state for the screen. [db53]20 ca e0JSR GameLoop_UpdatePlayer [db56]20 82 b9JSR Player_DrawShield; Draw the player's shield sprite. [db59]20 ee ebJSR Player_DrawBody; Draw the player's body sprite. [db5c]20 d6 b7JSR Player_DrawWeapon; Draw the player's weapon sprite. [db5f]20 5b baJSR Player_CastMagic; Check and handle casting magic. [db62]20 78 c4JSR GameLoop_CheckUseCurrentItem; Active selected item? [db65]20 00 80JSR Sprites_UpdateAll; Update all sprites. [db68]20 8f c8JSR GameLoop_CountdownItems [db6b]20 c5 dfJSR Fog_OnTick [db6e]20 16 e0JSR GameLoop_CheckShowPlayerMenu [db71]20 4b efJSR GameLoop_RunScreenEventHandlers [db74]20 2b e0JSR GameLoop_CheckPauseGame [db77]ad 38 04LDA a:PlayerIsDead [db7a]f0 03BEQ @_playerIsNotDead [db7c]4c ec d8JMP Player_HandleDeath [db7f]@_playerIsNotDead: [db7f]a5 54LDA Screen_ScrollDirection [db81]30 c2BMI Game_MainLoop [db83]20 f4 cfJSR PPUBuffer_WaitUntilClear [db86]ad 2f 04LDA a:Maybe_Game_Ready [db89]f0 06BEQ @LAB_PRG15_MIRROR__db91 [db8b]a5 54LDA Screen_ScrollDirection [db8d]c9 02CMP #$02 [db8f]90 1eBCC @LAB_PRG15_MIRROR__dbaf [db91]@LAB_PRG15_MIRROR__db91: [db91]20 47 cbJSR Screen_ResetSpritesForGamePlay [db94]20 25 caJSR WaitForNextFrame [db97]20 30 c1JSR Screen_ClearSprites [db9a]20 b4 c1JSR Screen_LoadSpriteInfo [db9d]20 25 caJSR WaitForNextFrame [dba0]20 8d c2JSR GameLoop_LoadSpriteImages [dba3]20 f7 caJSR PPU_WaitUntilFlushed [dba6]20 0f ddJSR Game_UpdateForScroll [dba9]20 17 cbJSR Screen_ResetForGamePlay [dbac]4c 45 dbJMP Game_MainLoop [dbaf]@LAB_PRG15_MIRROR__dbaf: [dbaf]20 25 caJSR WaitForNextFrame [dbb2]20 47 cbJSR Screen_ResetSpritesForGamePlay [dbb5]20 82 b9JSR Player_DrawShield [dbb8]20 ee ebJSR Player_DrawBody [dbbb]20 d6 b7JSR Player_DrawWeapon [dbbe]20 30 c1JSR Screen_ClearSprites [dbc1]20 b4 c1JSR Screen_LoadSpriteInfo [dbc4]20 25 caJSR WaitForNextFrame [dbc7]20 8d c2JSR GameLoop_LoadSpriteImages [dbca]@_scrolling: [dbca]20 25 caJSR WaitForNextFrame [dbcd]20 47 cbJSR Screen_ResetSpritesForGamePlay [dbd0]20 48 e0JSR Game_UpdatePlayerOnScroll [dbd3]20 82 b9JSR Player_DrawShield [dbd6]20 ee ebJSR Player_DrawBody [dbd9]20 d6 b7JSR Player_DrawWeapon [dbdc]20 e7 d2JSR Screen_HandleScroll [dbdf]20 e7 d2JSR Screen_HandleScroll [dbe2]20 e7 d2JSR Screen_HandleScroll [dbe5]20 e7 d2JSR Screen_HandleScroll [dbe8]a5 54LDA Screen_ScrollDirection [dbea]10 deBPL @_scrolling [dbec]4c 45 dbJMP 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]a2 ffLDX #$ff; X = 0xFF (unused -- noop)
; ; Switch to bank 14. ;
[dbf1]a2 0eLDX #$0e [dbf3]20 1a ccJSR MMC1_UpdateROMBank; Set bank to 14 (Logic).
; ; Wait for a frame and prepare for renders. ;
[dbf6]20 25 caJSR WaitForNextFrame; Wait for the next frame. [dbf9]20 47 cbJSR Screen_ResetSpritesForGamePlay
; ; Move the player a step/jump toward the King, or begin ; interaction, depending on position. ;
[dbfc]20 23 dcJSR EndGame_MovePlayerTowardKing; Simulate the player's button press. [dbff]20 ca e0JSR GameLoop_UpdatePlayer; Update the player's position/state.
; ; Perform all the standard mainloop operations. ;
[dc02]20 82 b9JSR Player_DrawShield; Draw the shield. [dc05]20 ee ebJSR Player_DrawBody; Draw the armor. [dc08]20 d6 b7JSR Player_DrawWeapon; Draw the weapon. [dc0b]20 5b baJSR Player_CastMagic; No-op [dc0e]20 78 c4JSR GameLoop_CheckUseCurrentItem; No-op [dc11]20 00 80JSR Sprites_UpdateAll; Update sprites. [dc14]20 8f c8JSR GameLoop_CountdownItems; No-op [dc17]20 c5 dfJSR Fog_OnTick; No-op [dc1a]20 16 e0JSR GameLoop_CheckShowPlayerMenu; No-op [dc1d]20 4b efJSR GameLoop_RunScreenEventHandlers; No-op [dc20]4c ef dbJMP 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]a5 9eLDA Player_PosX_Block; A = player X. [dc25]c9 61CMP #$61; Is it >= 97? [dc27]b0 0fBCS @_setButtonsLeft; If so, jump to just press Left. [dc29]c9 50CMP #$50; Is it >= 80? [dc2b]b0 12BCS @_setJumpLeft; If so, jump to press Left+A. [dc2d]c9 44CMP #$44; Is it >= 68? [dc2f]b0 07BCS @_setButtonsLeft; If so, jump to press Left.
; ; Simulate pressing Up to engage with the King. ;
[dc31]a9 08LDA #$08; 0x08 == Up button bitmask. [dc33]85 16STA Joy1_ButtonMask; Set as the current button mask. [dc35]85 19STA Joy1_ChangedButtonMask; Set as the changed button mask. [dc37]60RTS
; ; Simulate pressing Left. ;
[dc38]@_setButtonsLeft: [dc38]a9 02LDA #$02; 0x02 == Left button bitmask. [dc3a]85 16STA Joy1_ButtonMask; Set as the current button mask. [dc3c]85 19STA Joy1_ChangedButtonMask; Set as the changed button mask. [dc3e]60RTS
; ; Simulate pressing Left+A to jump left. ;
[dc3f]@_setJumpLeft: [dc3f]a9 82LDA #$82; 0x82 == Left + A button bitmask. [dc41]85 16STA Joy1_ButtonMask; Set as the current button mask. [dc43]85 19STA Joy1_ChangedButtonMask; Set as the changed button mask.
; ; XREFS: ; Game_DrawScreenInFrozenState ;
[dc45]RETURN_DC45: [dc45]60RTS
;============================================================================ ; 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]a5 0eLDA Screen_ReadyState; Load the loaded state. [dc48]c9 ffCMP #$ff; Is it 0xFF? [dc4a]f0 f9BEQ RETURN_DC45; If so, then return.
; ; Switch to bank 14. ;
[dc4c]ad 00 01LDA a:CurrentROMBank; Load the current bank. [dc4f]48PHA; Push to the stack. [dc50]a2 0eLDX #$0e [dc52]20 1a ccJSR MMC1_UpdateROMBank; Switch to bank 14.
; ; Temporarily clear Wing Boots state. ;
[dc55]a5 a5LDA Player_StatusFlag; Load the player's status flag. [dc57]48PHA; Push to the stack so it can be restored later. [dc58]29 7fAND #$7f; Keep all but the Wing Boots flag. [dc5a]85 a5STA Player_StatusFlag; Store it.
; ; Draw the player. ;
[dc5c]20 82 b9JSR Player_DrawShield; Draw the shield. [dc5f]20 ee ebJSR Player_DrawBody; Draw the armor. [dc62]20 d6 b7JSR Player_DrawWeapon; Draw the weapon.
; ; Restore the player's status flags. ;
[dc65]68PLA; Pop the status flag. [dc66]85 a5STA Player_StatusFlag; Store it.
; ; Draw all the sprites, but with sprites paused. ; ; This flag will will disable all behavioral updates for ; sprites. ;
[dc68]a9 01LDA #$01; A = 1 [dc6a]8d 26 04STA a:Sprites_UpdatesPaused; Set as the paused state for sprites. [dc6d]20 70 80JSR Sprites_UpdateAllStates; Update all sprites. [dc70]a9 00LDA #$00; A = 0 [dc72]8d 26 04STA a:Sprites_UpdatesPaused; Clear the paused state. [dc75]4c d6 f3JMP MMC1_UpdatePRGBankToStackA; Restore the previous bank.
;============================================================================ ; Load the addresses of the area's metadata. ; ; This will load all the following information: ; ; * The address of the screen blocks data. ; * The address of the metadata table. ; * The address of the block properties. ; * The address of the blocks. ; * The address of the scrolling data. ; * The address of the door locations. ; * The address of the door destinations. ; ; INPUTS: ; A: ; The offset into the current bank's area blocks ; data. ; ; Area_CurrentArea: ; The current area. ; ; CurrentROMBank: ; The current ROM bank. ; ; OUTPUTS: ; Area_ScreenBlocksOffset: ; Area_ScreenBlocksOffset+1: ; The offset to the blocks data for the current ; area. ; ; CurrentArea_BlockAttributesAddr: ; The address of the block attributes. ; ; CurrentArea_BlockData1StartAddr: ; The address of the start of the block list #1. ; ; CurrentArea_BlockData2StartAddr: ; The address of the start of the block list #2. ; ; CurrentArea_BlockData3StartAddr: ; The address of the start of the block list #3. ; ; CurrentArea_BlockData4StartAddr: ; The address of the start of the block list #4. ; ; CurrentArea_BlockPropertiesAddr: ; CurrentArea_BlockPropertiesAddr+1: ; The address of the block properties. ; ; CurrentArea_DoorLocationsAddr: ; CurrentArea_DoorLocationsAddr+1: ; The address of the door locations. ; ; CurrentArea_DoorDestinationsAddr: ; CurrentArea_DoorDestinationsAddr+1: ; The address of the door destinations. ; ; CurrentArea_ScrollingDataAddr: ; ScrollingData_U: ; The address of the scrolling data. ; ; CurrentArea_TableAddr: ; CurrentArea_TableAddr+1: ; The address of the metadata table. ; ; CALLS: ; MMC1_UpdateROMBank ; ; XREFS: ; Game_EnterAreaHandler ; Game_EnterBuilding ; Game_ExitBuilding ; Game_LoadCurrentArea ; Game_LoadFirstLevel ;============================================================================
[dc78]Game_LoadAreaTable:
; ; Set the address for the area in the areas table. ; ; This will convert the offset to an address within the ; current areas bank by taking the given offset, multiplying ; by 2, and then using it as an index into the current bank ; to load 2 bytes. ; ; These 2 bytes form an upper and lower byte offset into ; bank 3. ;
[dc78]0aASL A; Normalize the offset to a word boundary. [dc79]a8TAY; Set as Y. [dc7a]b9 00 80LDA a:ROMBankStart,Y; Load as the lower byte in the current bank. [dc7d]85 7aSTA Area_ScreenBlocksOffset; Store that for lookup. [dc7f]b9 01 80LDA a:ROMBankStart+1,Y; Load the next byte as the upper byte in the current bank. [dc82]18CLC [dc83]69 80ADC #$80; Add 0x80, converting to an absolute address. [dc85]85 7bSTA Area_ScreenBlocksOffset_U; Store it.
; ; Save the current ROM bank and switch to bank 3, where the ; areas table lives. ;
[dc87]ad 00 01LDA a:CurrentROMBank; Load the current bank. [dc8a]48PHA; Push it to the stack. [dc8b]a2 03LDX #$03; X = bank 3 (area metadata). [dc8d]20 1a ccJSR MMC1_UpdateROMBank; Switch to it.
; ; Set the start of the area's pointer table in ; 0x{@address 007C}. ; ; This will be AREAS_TABLE_PTR in Bank 3. ;
[dc90]ad 00 80LDA a:AREAS_TABLE_PTR; A = first byte from the bank. [dc93]85 02STA Temp_Addr_L; Store it temporarily as a lower byte. [dc95]ad 01 80LDA a:AREAS_TABLE_PTR+1; Load the next byte. [dc98]18CLC [dc99]69 80ADC #$80; Add 0x80 (making it an absolute address). [dc9b]85 03STA Temp_Addr_U; Store it temporarily as an upper byte.
; ; Load the address of the current area's metadata table. ;
[dc9d]a5 24LDA Area_CurrentArea; A = Current area. [dc9f]0aASL A; Normalize to a word boundary. [dca0]a8TAY; Y = result. [dca1]b1 02LDA (Temp_Addr_L),Y; Load this offset from the area metadata table. [dca3]85 7cSTA CurrentArea_TableAddr; Store as the lower byte of the current area's metadata table address. [dca5]c8INY; Y++ (upper byte index). [dca6]b1 02LDA (Temp_Addr_L),Y; Load that offset. [dca8]18CLC [dca9]69 80ADC #$80; Add 0x80 to the address, making it an absolute address. [dcab]85 7dSTA CurrentArea_TableAddr_U; Store as the upper byte of the current area's metadata table address.
; ; Load the address of the block attributes pointer (not the ; block attributes data) from the metadata table. We'll read ; the first entry to get that address. ;
[dcad]a0 00LDY #$00; Y = 0. [dcaf]b1 7cLDA (CurrentArea_TableAddr),Y; Load the first byte from the current area's metadata table. [dcb1]85 02STA Temp_Addr_L; Store temporarily as the lower byte of an address. [dcb3]c8INY; Y++ [dcb4]b1 7cLDA (CurrentArea_TableAddr),Y; Load the upper byte. [dcb6]18CLC [dcb7]69 80ADC #$80; Normalize to an absolute address. [dcb9]85 03STA Temp_Addr_U; Store it.
; ; Store the addresses of the block attributes and 4 groups of ; block data in addresses {@address 007E}. This is done with ; a simple load loop, which corresponds to the order in which ; the data is represented in RAM. ;
[dcbb]a0 00LDY #$00; Y = 0. [dcbd]a2 05LDX #$05; X = 5. [dcbf]@_loadBlockInfoLoop: [dcbf]b1 02LDA (Temp_Addr_L),Y; Load the lower byte of the address at the current index. [dcc1]99 7e 00STA a:CurrentArea_BlockAttributesAddr,Y; Store it in RAM at the current index. [dcc4]c8INY; Y++ (upper byte). [dcc5]b1 02LDA (Temp_Addr_L),Y; Load the upper byte. [dcc7]18CLC [dcc8]69 80ADC #$80; Normalize to an absolute address. [dcca]99 7e 00STA a:CurrentArea_BlockAttributesAddr,Y; Store it in RAM. [dccd]c8INY; Y++ (next entry). [dcce]caDEX; X-- (loop counter). [dccf]d0 eeBNE @_loadBlockInfoLoop; If > 0, loop.
; ; Load the block properties (entry 2). ;
[dcd1]a0 02LDY #$02; Y = 2 (entry index). [dcd3]b1 7cLDA (CurrentArea_TableAddr),Y; Load the lower byte of the data address. [dcd5]85 88STA CurrentArea_BlockPropertiesAddr; Store it. [dcd7]c8INY; Y++ (upper byte). [dcd8]b1 7cLDA (CurrentArea_TableAddr),Y; Load the upper byte. [dcda]18CLC [dcdb]69 80ADC #$80; Normalize to an absolute address. [dcdd]85 89STA CurrentArea_BlockPropertiesAddr_U; Store it.
; ; Load the scrolling data (entry 4). ;
[dcdf]a0 04LDY #$04; Y = 4 (entry index). [dce1]b1 7cLDA (CurrentArea_TableAddr),Y; Load the lower byte of the data address. [dce3]85 8aSTA CurrentArea_ScrollingDataAddr; Store it. [dce5]c8INY; Y++ (upper byte). [dce6]b1 7cLDA (CurrentArea_TableAddr),Y; Load the upper byte. [dce8]18CLC [dce9]69 80ADC #$80; Normalize to an absolute address. [dceb]85 8bSTA ScrollingData_U; Store it.
; ; Load the door locations (entry 6). ;
[dced]a0 06LDY #$06; Y = 6 (entry index). [dcef]b1 7cLDA (CurrentArea_TableAddr),Y; Load the lower byte of the data address. [dcf1]85 8dSTA CurrentArea_DoorLocationsAddr; Store it. [dcf3]c8INY; Y++ (upper byte). [dcf4]b1 7cLDA (CurrentArea_TableAddr),Y; Load the upper byte. [dcf6]18CLC [dcf7]69 80ADC #$80; Normalize to an absolute address. [dcf9]85 8eSTA CurrentArea_DoorLocationsAddr_U; Store it.
; ; Load the door destinations (entry 8). ;
[dcfb]a0 08LDY #$08; Y = 8 (entry index). [dcfd]b1 7cLDA (CurrentArea_TableAddr),Y; Load the lower byte of the data address. [dcff]85 8fSTA CurrentArea_DoorDestinationsAddr; Store it. [dd01]c8INY; Y++ (upper byte). [dd02]b1 7cLDA (CurrentArea_TableAddr),Y; Load the upper byte. [dd04]18CLC [dd05]69 80ADC #$80; Normalize to an absolute address. [dd07]85 90STA CurrentArea_DoorDestinationsAddr_U; Store it.
; ; Switch back to the previous bank. ;
[dd09]68PLA; Restore the previous bank. [dd0a]aaTAX; X = result. [dd0b]20 1a ccJSR MMC1_UpdateROMBank; Switch to the bank. [dd0e]60RTS
;============================================================================ ; Update the scroll state during a mainloop iteration. ; ; INPUTS: ; None. ; ; OUTPUTS: ; None. ; ; CALLS: ; Screen_UpdateForScroll ; ; XREFS: ; Game_MainLoop ;============================================================================
[dd0f]Game_UpdateForScroll: [dd0f]20 ce d2JSR Screen_UpdateForScroll [dd12]60RTS
;============================================================================ ; 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]20 12 edJSR Player_DrawSpriteImmediately; Draw the player sprite. [dd16]20 80 ceJSR Sprites_LoadCommon; Load all common sprites. [dd19]a5 64LDA Area_LoadingScreenIndex [dd1b]85 63STA Area_CurrentScreen [dd1d]20 f6 d0JSR Area_ScrollScreenRight [dd20]20 eb f8JSR UI_DrawHUDSprites [dd23]a5 65LDA Screen_DestPaletteOrIndex [dd25]8d d0 03STA a:Screen_PaletteIndex [dd28]20 3b d0JSR Screen_LoadUIPalette
; ; Reset the screen scrolls. ;
[dd2b]a9 00LDA #$00 [dd2d]85 59STA PPU_ScrollScreenHoriz; Clear the horizontal scroll. [dd2f]85 58STA PPU_ScrollScreenVert; Clear the vertical scroll. [dd31]85 9fSTA Screen_Maybe_ScrollXCounter [dd33]85 a2STA Player_Something_ScrollPosY [dd35]a5 6cLDA Screen_StartPosYX [dd37]29 f0AND #$f0 [dd39]85 a1STA Player_PosY [dd3b]a5 6cLDA Screen_StartPosYX [dd3d]0aASL A [dd3e]0aASL A [dd3f]0aASL A [dd40]0aASL A [dd41]85 9eSTA Player_PosX_Block [dd43]4c 00 c0JMP 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]20 13 ddJSR Screen_SetupNew; Draw the HUD and prepare setup for the screen. [dd49]a9 00LDA #$00 [dd4b]20 62 d0JSR 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:
; ; 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]20 30 c1JSR Screen_ClearSprites; Clear current sprite data. [dd51]a5 0eLDA Screen_ReadyState; Get the screen state. [dd53]c9 01CMP #$01; Is it 1? (DEADCODE: It never will be). [dd55]f0 03BEQ @_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]20 b4 c1JSR Screen_LoadSpriteInfo; Load information for the screen.
; ; Mark the screen as ready, and write the palette. ;
[dd5a]@_setReady: [dd5a]a9 00LDA #$00 [dd5c]85 0eSTA Screen_ReadyState; Set the state to ready. [dd5e]4c 90 d0JMP 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:
; ; Set the starting area/screen/position information outside the ; temple. ;
[dd61]ae 39 04LDX a:TempleSpawnPoint; X = index of the temple spawn point. [dd64]bd d5 ddLDA a:START_SCREEN_FOR_TEMPLE_SPAWN,X; Set the current screen for outside the temple. [dd67]85 63STA Area_CurrentScreen [dd69]bd cd ddLDA a:START_MAYBE_NEXT_AREA_FOR_TEMPLE_SPAWN,X [dd6c]8d 35 04STA a:Area_Region [dd6f]bd b5 ddLDA a:START_PLAYERPOSX_FULL_FOR_TEMPLE_SPAWN,X [dd72]85 9eSTA Player_PosX_Block [dd74]bd bd ddLDA a:MAYBE_START_PLAYERPOSY_FOR_TEMPLE_SPAWN,X [dd77]85 a1STA Player_PosY [dd79]bd c5 ddLDA a:DEST_SCREEN_FOR_TEMPLE_SPAWN,X [dd7c]8d da 03STA a:Area_DestScreen [dd7f]bc ad ddLDY a:CURRENT_REGION_FOR_TEMPLE_SPAWN,X [dd82]84 24STY Area_CurrentArea [dd84]b9 4c dfLDA a:AREA_PALETTE_INDEXES,Y [dd87]8d d0 03STA a:Screen_PaletteIndex [dd8a]b9 5c dfLDA a:AREA_TO_MUSIC_TABLE,Y [dd8d]8d d2 03STA a:Area_Music_Outside [dd90]a9 12LDA #$12 [dd92]85 65STA Screen_DestPaletteOrIndex [dd94]a9 06LDA #$06 [dd96]8d d9 03STA a:Building_TilesIndex [dd99]a9 9eLDA #$9e [dd9b]85 6cSTA Screen_StartPosYX [dd9d]a9 0eLDA #$0e [dd9f]8d d1 03STA a:Areas_DefaultMusic [dda2]a9 01LDA #$01 [dda4]85 64STA Area_LoadingScreenIndex
; ; Set the player to face left inside the temple. ;
[dda6]a5 a4LDA Player_Flags [dda8]29 bfAND #$bf [ddaa]85 a4STA Player_Flags [ddac]60RTS
;============================================================================ ; A mapping of Temple spawn positions to regions. ; ; XREFS: ; Game_SpawnInTemple ;============================================================================
; ; XREFS: ; Game_SpawnInTemple ;
[ddad]CURRENT_REGION_FOR_TEMPLE_SPAWN: [ddad]00.byte REGION_EOLIS; [0]: First town [ddae]03.byte REGION_BRANCH; [1]: Apolune [ddaf]03.byte REGION_BRANCH; [2]: Forepaw [ddb0]02.byte REGION_MIST; [3]: Fog [ddb1]03.byte REGION_BRANCH; [4]: Victim [ddb2]03.byte REGION_BRANCH; [5]: Conflate [ddb3]03.byte REGION_BRANCH; [6]: Daybreak [ddb4]03.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]50.byte $50; [0]: First town [ddb6]50.byte $50; [1]: Apolune [ddb7]30.byte $30; [2]: Forepaw [ddb8]90.byte $90; [3]: Fog [ddb9]30.byte $30; [4]: Victim [ddba]90.byte $90; [5]: Conflate [ddbb]60.byte $60; [6]: Daybreak [ddbc]30.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]90.byte $90; [0]: First town [ddbe]90.byte $90; [1]: Apolune [ddbf]90.byte $90; [2]: Forepaw [ddc0]80.byte $80; [3]: Fog [ddc1]90.byte $90; [4]: Victim [ddc2]90.byte $90; [5]: Conflate [ddc3]90.byte $90; [6]: Daybreak [ddc4]90.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]02.byte $02; [0]: First town [ddc6]0b.byte $0b; [1]: Apolune [ddc7]10.byte $10; [2]: Forepaw [ddc8]1e.byte $1e; [3]: Fog [ddc9]23.byte $23; [4]: Victim [ddca]2b.byte $2b; [5]: Conflate [ddcb]33.byte $33; [6]: Daybreak [ddcc]3c.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]00.byte REGION_EOLIS; [0]: First town [ddce]01.byte REGION_TRUNK; [1]: Apolune [ddcf]01.byte REGION_TRUNK; [2]: Forepaw [ddd0]02.byte REGION_MIST; [3]: Fog [ddd1]02.byte REGION_MIST; [4]: Victim [ddd2]03.byte REGION_BRANCH; [5]: Conflate [ddd3]03.byte REGION_BRANCH; [6]: Daybreak [ddd4]04.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]02.byte $02; [0]: First town [ddd6]01.byte $01; [1]: Apolune [ddd7]03.byte $03; [2]: Forepaw [ddd8]06.byte $06; [3]: Fog [ddd9]06.byte $06; [4]: Victim [ddda]08.byte $08; [5]: Conflate [dddb]0b.byte $0b; [6]: Daybreak [dddc]0c.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:
; ; Move the player back to Eolis. ;
[dddd]a9 00LDA #$00 [dddf]85 64STA Area_LoadingScreenIndex; Set the screen index to 0. [dde1]8d 35 04STA a:Area_Region; Set the region to Eolis.
; ; Play the Temple music. ;
[dde4]a9 0dLDA #$0d [dde6]8d d1 03STA a:Areas_DefaultMusic; Play the Temple music.
; ; Move the player to the right of the room. ;
[dde9]a9 9dLDA #$9d [ddeb]85 6cSTA Screen_StartPosYX; Set the start position to X=13, Y=9.
; ; Load the palette for the King's room. ;
[dded]a9 11LDA #$11 [ddef]85 65STA Screen_DestPaletteOrIndex; Set the Palette for the King's room.
; ; Set the King's Room screen. ;
[ddf1]a9 44LDA #$44 [ddf3]85 63STA Area_CurrentScreen; Set the screen to 68. [ddf5]8d da 03STA a:Area_DestScreen [ddf8]a9 06LDA #$06 [ddfa]8d d9 03STA a:Building_TilesIndex; Set the tiles to 6.
; ; Face the player to the left. ;
[ddfd]a5 a4LDA Player_Flags; Load the player flags. [ddff]29 bfAND #$bf; Set to facing left. [de01]85 a4STA Player_Flags; Store it. [de03]4c af daJMP Game_SetupEnterBuilding; Trigger the Enter Building logic.
;============================================================================ ; TODO: Document Game_EnterBuilding ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Game_SetupEnterBuilding ;============================================================================
[de06]Game_EnterBuilding:
; ; Unset the player's current weapon. Players can't attack in ; buildings. ;
[de06]a9 ffLDA #$ff [de08]8d c8 03STA a:Player_CurWeapon; Clear the weapon.
; ; Set the music for this area. ;
[de0b]ad d1 03LDA a:Areas_DefaultMusic; Load the default music for the area. [de0e]85 faSTA Music_Current; Set as the current music.
; ; Push a buch of state to return to when exiting the door. ;
[de10]a5 24LDA Area_CurrentArea; Load the current area. [de12]8d d5 03STA a:Area_SavedArea; Save it for when exiting the building. [de15]a5 63LDA Area_CurrentScreen; Load the current screen in the area. [de17]8d d6 03STA a:Area_SavedScreen; Save it. [de1a]ad d0 03LDA a:Screen_PaletteIndex; Load the current palette. [de1d]8d d7 03STA a:Screen_SavedPalette; Save it.
; ; Save the player's X/Y position (rounded to the nearest ; block). ;
[de20]a5 a1LDA Player_PosY; Load the player's Y position. [de22]29 f0AND #$f0; Normalize to a block offset. [de24]8d d8 03STA a:Player_SavedPosXY; Save it. [de27]a5 9eLDA Player_PosX_Block; Load the player's block X position. [de29]29 f0AND #$f0; Normalize it to a block offset. [de2b]4aLSR A; Shift to the lower byte. [de2c]4aLSR A [de2d]4aLSR A [de2e]4aLSR A [de2f]0d d8 03ORA a:Player_SavedPosXY; Combine it with the saved X/Y position. [de32]8d d8 03STA a:Player_SavedPosXY; And save it.
; ; Set the new area to enter and load it. ;
[de35]a2 04LDX #$04 [de37]86 24STX Area_CurrentArea; Set the current area to 4 (inside buildings) [de39]bd 34 dfLDA a:AREA_TO_SCREEN_DATA_BANKS,X; Load the bank for this area. [de3c]85 8cSTA CurrentArea_ROMBank; Store as the current area ROM bank. [de3e]a6 8cLDX CurrentArea_ROMBank [de40]20 15 ccJSR MMC1_SaveROMBankAndUpdateTo; Push the current bank and switch to the new one. [de43]a6 24LDX Area_CurrentArea; Load the current area again. [de45]bd 3c dfLDA a:AREA_TO_BANK_OFFSET,X; Get the index into the screen data table in this bank. [de48]20 78 dcJSR Game_LoadAreaTable; Load the area table for the screen. [de4b]20 e7 ccJSR MMC1_RestorePrevROMBank; Restore the previous bank.
; ; Set the index into the tiles for this area and load them. ;
[de4e]ad d9 03LDA a:Building_TilesIndex [de51]85 95STA Area_TilesIndex [de53]20 b8 ceJSR Area_LoadTiles
; ; Load the palette. ;
[de56]a9 01LDA #$01 [de58]20 62 d0JSR Screen_LoadSpritePalette
; ; Set up the screen and the sprites. ;
[de5b]20 13 ddJSR Screen_SetupNew [de5e]ad da 03LDA a:Area_DestScreen [de61]85 63STA Area_CurrentScreen [de63]4c 4e ddJMP Screen_SetupSprites
;============================================================================ ; TODO: Document Game_ExitBuilding ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Game_SetupExitBuilding ;============================================================================
[de66]Game_ExitBuilding: [de66]ad bd 03LDA a:SelectedWeapon [de69]8d c8 03STA a:Player_CurWeapon [de6c]ad d1 03LDA a:Areas_DefaultMusic [de6f]85 faSTA Music_Current [de71]ae d5 03LDX a:Area_SavedArea [de74]86 24STX Area_CurrentArea [de76]bd 34 dfLDA a:AREA_TO_SCREEN_DATA_BANKS,X [de79]85 8cSTA CurrentArea_ROMBank [de7b]a6 8cLDX CurrentArea_ROMBank [de7d]20 15 ccJSR MMC1_SaveROMBankAndUpdateTo [de80]a6 24LDX Area_CurrentArea [de82]bd 3c dfLDA a:AREA_TO_BANK_OFFSET,X [de85]20 78 dcJSR Game_LoadAreaTable [de88]20 e7 ccJSR MMC1_RestorePrevROMBank [de8b]a6 24LDX Area_CurrentArea [de8d]bd 44 dfLDA a:AREA_TO_TILES_INDEXES,X [de90]85 95STA Area_TilesIndex [de92]20 b8 ceJSR Area_LoadTiles [de95]ad d6 03LDA a:Area_SavedScreen [de98]85 64STA Area_LoadingScreenIndex [de9a]ad d7 03LDA a:Screen_SavedPalette [de9d]85 65STA Screen_DestPaletteOrIndex [de9f]ad d8 03LDA a:Player_SavedPosXY [dea2]85 6cSTA Screen_StartPosYX [dea4]4c 46 ddJMP 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. ; ; AREA_TO_TILES_INDEXES: ; 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]a9 00LDA #$00; A = 0
; ; Set this as the current town. ;
[dea9]85 24STA 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]20 aa e0JSR Player_SetInitialState; Set the initial player state. [deae]a9 10LDA #$10 [deb0]8d 31 04STA a:Player_HP_U; Set the HP to 16. [deb3]a6 24LDX 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]bd 34 dfLDA a:AREA_TO_SCREEN_DATA_BANKS,X; A = PRG bank for the area. [deb8]85 8cSTA CurrentArea_ROMBank; Store as the current bank. [deba]a6 8cLDX CurrentArea_ROMBank; X = current bank. [debc]20 15 ccJSR 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]bd 3c dfLDA a:AREA_TO_BANK_OFFSET,X; A = sprite bank for the area. [dec2]20 78 dcJSR Game_LoadAreaTable; Load the area information. [dec5]20 e7 ccJSR MMC1_RestorePrevROMBank; Restore the previous bank.
; ; Start on the current screen. ;
[dec8]a6 24LDX Area_CurrentArea; X = current area. [deca]a9 00LDA #$00 [decc]85 63STA Area_CurrentScreen; Current screen = 0 [dece]85 64STA Area_LoadingScreenIndex; Screen index = 0
; ; Load the tiles and images for the area. ;
[ded0]a6 24LDX Area_CurrentArea; X = current area. [ded2]bd 44 dfLDA a:AREA_TO_TILES_INDEXES,X; A = tiles index. [ded5]85 95STA Area_TilesIndex; Store that. [ded7]20 b8 ceJSR Area_LoadTiles; Load the tileset pages for that index.
; ; Load the palette for the area. ;
[deda]a9 00LDA #$00 [dedc]20 62 d0JSR Screen_LoadSpritePalette; Load the palette for that index. [dedf]a6 24LDX Area_CurrentArea; X = current area. [dee1]bd 4c dfLDA a:AREA_PALETTE_INDEXES,X; Load the palette. [dee4]85 65STA Screen_DestPaletteOrIndex; And store it as the current one for the area.
; ; Load the music for this area. ;
[dee6]bd 5c dfLDA a:AREA_TO_MUSIC_TABLE,X; Load the music for the area. [dee9]85 faSTA Music_Current; And store it as the current music. [deeb]8d d1 03STA 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]a9 91LDA #$91 [def0]85 6cSTA Screen_StartPosYX; Set start position. [def2]4c 46 ddJMP 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. ; ; AREA_TO_TILES_INDEXES: ; 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]20 aa e0JSR Player_SetInitialState; Set the initial player state.
; ; Load the bank for this area. ;
[def8]a6 24LDX Area_CurrentArea; X = current area. [defa]bd 34 dfLDA a:AREA_TO_SCREEN_DATA_BANKS,X; Load the ROM bank for the area. [defd]85 8cSTA CurrentArea_ROMBank; Store that as the current bank. [deff]a6 8cLDX CurrentArea_ROMBank; X = current bank. [df01]20 15 ccJSR MMC1_SaveROMBankAndUpdateTo; Save the current bank and switch to it.
; ; Load the bank for the sprites for this area. ;
[df04]a6 24LDX Area_CurrentArea; X = current area. [df06]bd 3c dfLDA a:AREA_TO_BANK_OFFSET,X; A = bank for the sprites.
; ; Load information on the area. ;
[df09]20 78 dcJSR Game_LoadAreaTable; Load the table of information on this area. [df0c]20 e7 ccJSR MMC1_RestorePrevROMBank; Restore to the previous bank. [df0f]a5 64LDA Area_LoadingScreenIndex; A = current screen index. [df11]85 63STA Area_CurrentScreen; Store as the current screen.
; ; Load the tiles for the area. ;
[df13]a6 24LDX Area_CurrentArea; X = current area. [df15]bd 44 dfLDA a:AREA_TO_TILES_INDEXES,X; A = tiles index for the area. [df18]85 95STA Area_TilesIndex; Store as the current tiles index. [df1a]20 b8 ceJSR Area_LoadTiles; Load all tileset images for that index.
; ; Load the palette for the area. ;
[df1d]a9 00LDA #$00 [df1f]20 62 d0JSR Screen_LoadSpritePalette; Load palette 0. [df22]a6 24LDX Area_CurrentArea; X = current area. [df24]bd 4c dfLDA a:AREA_PALETTE_INDEXES,X; Load the palette for the area. [df27]85 65STA Screen_DestPaletteOrIndex; Store as the current palette.
; ; Load the music for the area. ;
[df29]bd 5c dfLDA a:AREA_TO_MUSIC_TABLE,X; Load the music for the area. [df2c]85 faSTA Music_Current; Set as the current music. [df2e]8d d1 03STA a:Areas_DefaultMusic; Store as the default. [df31]4c 46 ddJMP 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]00.byte BANK_0_AREA_DATA; [0]: Eolis [df35]01.byte BANK_1_AREA_DATA; [1]: Trunk [df36]00.byte BANK_0_AREA_DATA; [2]: Mist [df37]00.byte BANK_0_AREA_DATA; [3]: Towns
; ; XREFS: ; Game_EnterBuilding ;
[df38]AREA_TO_SCREEN_DATA_BANKS_4_: [df38]02.byte BANK_2_AREA_DATA; [4]: Buildings [df39]01.byte BANK_1_AREA_DATA; [5]: Branch [df3a]02.byte BANK_2_AREA_DATA; [6]: Dartmoor [df3b]02.byte BANK_2_AREA_DATA; [7]: Zenis
;============================================================================ ; A mapping of areas to the starting pointer offsets within the ROM banks ; above. ; ; XREFS: ; Game_EnterAreaHandler ; Game_ExitBuilding ; Game_LoadCurrentArea ; Game_LoadFirstLevel ;============================================================================
; ; XREFS: ; Game_EnterAreaHandler ; Game_ExitBuilding ; Game_LoadCurrentArea ; Game_LoadFirstLevel ;
[df3c]AREA_TO_BANK_OFFSET: [df3c]00.byte $00; [0]: Eolis [df3d]00.byte $00; [1]: Trunk [df3e]01.byte $01; [2]: Mist [df3f]02.byte $02; [3]: Towns
; ; XREFS: ; Game_EnterBuilding ;
[df40]AREA_TO_BANK_OFFSET_4_: [df40]01.byte $01; [4]: Buildings [df41]01.byte $01; [5]: Branch [df42]00.byte $00; [6]: Dartmoor [df43]02.byte $02; [7]: Zenis
;============================================================================ ; A mapping of area indexes to indexes into ; TILE_INDEX_TO_ADDR. ; ; XREFS: ; Game_EnterAreaHandler ; Game_ExitBuilding ; Game_LoadCurrentArea ; Game_LoadFirstLevel ;============================================================================
; ; XREFS: ; Game_EnterAreaHandler ; Game_ExitBuilding ; Game_LoadCurrentArea ; Game_LoadFirstLevel ;
[df44]AREA_TO_TILES_INDEXES: [df44]00.byte $00; [0]: Eolis [df45]02.byte $02; [1]: Trunk [df46]03.byte $03; [2]: Mist [df47]05.byte $05; [3]: Towns [df48]06.byte $06; [4]: Buildings [df49]01.byte $01; [5]: Branch [df4a]04.byte $04; [6]: Dartmoor [df4b]04.byte $04; [7]: Zenis / Evil Fortress
;============================================================================ ; 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]00.byte PALETTE_EOLIS; [0]: Eolis [df4d]06.byte PALETTE_OUTSIDE; [1]: Trunk [df4e]0a.byte PALETTE_MIST; [2]: Mist [df4f]1b.byte PALETTE_TOWN; [3]: Towns [df50]1b.byte PALETTE_TOWN; [4]: Buildings [df51]08.byte PALETTE_BRANCH; [5]: Branch [df52]0c.byte PALETTE_DARTMOOR; [6]: Dartmoor [df53]0f.byte PALETTE_EVIL_FORTRESS; [7]: Zenis / Evil Fortress
;============================================================================ ; MOD NOTES: 8 bytes of unused data. ;============================================================================
[df54]00 00 00 00 00 00 00 00.byte $00,$00,$00,$00,$00,$00,$00,$00; undefined
;============================================================================ ; Mapping of areas to music indexes. ; ; XREFS: ; Game_EnterAreaHandler ; Game_LoadCurrentArea ; Game_LoadFirstLevel ; Game_SpawnInTemple ;============================================================================
; ; XREFS: ; Game_EnterAreaHandler ; Game_LoadCurrentArea ; Game_LoadFirstLevel ; Game_SpawnInTemple ;
[df5c]AREA_TO_MUSIC_TABLE: [df5c]07.byte MUSIC_EOLIS; [0]: Eolis [df5d]03.byte MUSIC_APOLUNE; [1]: Trunk [df5e]05.byte MUSIC_FOREPAW; [2]: Mist [df5f]09.byte MUSIC_MASCON_VICTIM; [3]: Towns [df60]09.byte MUSIC_MASCON_VICTIM; [4]: Buildings [df61]04.byte MUSIC_CONFLATE; [5]: Branch [df62]02.byte MUSIC_DAYBREAK; [6]: Dartmoor [df63]10.byte MUSIC_EVIL_FORTRESS; [7]: Zenith / Evil Fortress
;============================================================================ ; Handle entering a new area. ; ; This will load the data for the area, including the metadata ; pointers, music, tiles, and palette. ; ; INPUTS: ; Area_CurrentArea: ; The area being entered. ; ; Area_LoadingScreenIndex: ; The index of the screen to enter. ; ; AREA_TO_BANK_OFFSET: ; The mapping of area to bank offset. ; ; AREA_TO_SCREEN_DATA_BANKS: ; The mapping of area to screen data banks. ; ; AREA_TO_TILES_INDEXES: ; The mapping of area to tile indexes. ; ; OUTPUTS: ; Area_CurrentScreen: ; The new screen index. ; ; Area_TilesIndex: ; The new index for tile data. ; ; Areas_DefaultMusic: ; The new default music to play on building exit. ; ; CurrentArea_ROMBank: ; The ROM bank for the current area. ; ; Music_Current: ; The new current music to play. ; ; CALLS: ; Area_LoadTiles ; Game_LoadAreaTable ; Screen_Load ; Screen_LoadSpritePalette ; MMC1_RestorePrevROMBank ; MMC1_SaveROMBankAndUpdateTo ; ; XREFS: ; Game_SetupNewArea ;============================================================================
[df64]Game_EnterAreaHandler:
; ; Load the bank containing the screen data for this area. ;
[df64]a6 24LDX Area_CurrentArea; Load the current area. [df66]bd 34 dfLDA a:AREA_TO_SCREEN_DATA_BANKS,X; Get the bank for the area's screen data. [df69]85 8cSTA CurrentArea_ROMBank; Store as the new area bank. [df6b]a6 8cLDX CurrentArea_ROMBank; Load that area bank back into X. [df6d]20 15 ccJSR MMC1_SaveROMBankAndUpdateTo; Push the bank to the stack and switch to it.
; ; Load the area table. ;
[df70]a6 24LDX Area_CurrentArea; Load the current area. [df72]bd 3c dfLDA a:AREA_TO_BANK_OFFSET,X; Load the starting pointer index in the loaded bank. [df75]20 78 dcJSR Game_LoadAreaTable; Load the area table at that index. [df78]20 e7 ccJSR MMC1_RestorePrevROMBank; Pop the previous bank.
; ; Set the current screen to the target. ;
[df7b]a5 64LDA Area_LoadingScreenIndex; Load the target screen index. [df7d]85 63STA Area_CurrentScreen; Set as the current screen.
; ; Load the music for the area. ;
[df7f]a6 24LDX Area_CurrentArea; Load the current area. [df81]bd 5c dfLDA a:AREA_TO_MUSIC_TABLE,X; Load the index of the music for this area. [df84]85 faSTA Music_Current; Store as the new current music. [df86]8d d1 03STA a:Areas_DefaultMusic; Store as the default music for this area.
; ; Load the tiles for the area. ;
[df89]bd 44 dfLDA a:AREA_TO_TILES_INDEXES,X; Load the index for the tile data for this area. [df8c]85 95STA Area_TilesIndex; Store as the current tiles index. [df8e]20 b8 ceJSR Area_LoadTiles; Load the tiles for the area into CHR RAM.
; ; Load the sprite palette. ;
[df91]a9 00LDA #$00 [df93]20 62 d0JSR Screen_LoadSpritePalette; Populate the sprite palette.
; ; Load the screen state. ;
[df96]4c 46 ddJMP Screen_Load; Load the screen data.
;============================================================================ ; 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]a5 17LDA Joy2_ButtonMask; Load the player 2 controller buttons. [df9b]29 0cAND #$0c; Is Up or Down pressed? [df9d]f0 3eBEQ RETURN_DFDD; If not, return.
; ; Figure out which region to switch to based on button press. ;
[df9f]29 04AND #$04; Is Down pressed? [dfa1]d0 11BNE @_nextArea; If so, switch to the next area.
; ; Up was pressed. Go forward a region. ;
[dfa3]ee 35 04INC a:Area_Region; Increment the region. [dfa6]ad 35 04LDA a:Area_Region; A = incremented region. [dfa9]c9 06CMP #$06; Is it in bounds? [dfab]90 11BCC @_switchArea; If so, switch to the area. [dfad]a9 00LDA #$00; Else... [dfaf]8d 35 04STA a:Area_Region; Set the region to (Eolis). [dfb2]f0 0aBEQ @_switchArea; And switch to it.
; ; Down was pressed. Go back a region. ;
[dfb4]@_nextArea: [dfb4]ce 35 04DEC a:Area_Region; Decrement the region. [dfb7]10 05BPL @_switchArea; If it's in bounds, switch to the previous area. [dfb9]a9 05LDA #$05; Else, set the region to the final fortress. [dfbb]8d 35 04STA a:Area_Region; Store as the new region.
; ; Set the current screen to 0 and load it. ;
[dfbe]@_switchArea: [dfbe]a9 00LDA #$00 [dfc0]85 63STA Area_CurrentScreen; Set current screen to 0. [dfc2]4c dc daJMP 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]a5 24LDA Area_CurrentArea; Load the current area. [dfc7]c9 02CMP #$02; Is it Forepaw? [dfc9]d0 46BNE RETURN_E011; If not, return. [dfcb]ad d0 03LDA a:Screen_PaletteIndex; Load the current palette. [dfce]c9 0aCMP #$0a; Is it Mist? [dfd0]d0 3fBNE RETURN_E011; If not, return. [dfd2]a5 1eLDA Fog_StateIndex; Load the fog index. [dfd4]4aLSR A; Is it an odd value? [dfd5]90 07BCC 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]c6 1dDEC Fog_TileIndex; Decrement the fog generator. [dfd9]d0 36BNE RETURN_E011; If != 0, return. [dfdb]e6 1eINC Fog_StateIndex; Else, increment the fog index.
; ; XREFS: ; Debug_ChooseArea ;
[dfdd]RETURN_DFDD: [dfdd]60RTS
;============================================================================ ; 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]a6 20LDX PPUBuffer_WriteOffset; Load the upper bounds of the fog generator.
; ; Queue drawing command "Rotate tiles right." ;
[dfe0]a9 fcLDA #$fc; 0xFC == Rotate Tiles Right draw command. [dfe2]9d 00 05STA a:PPUBuffer,X; Set as the command.
; ; Set the upper byte of the tile address to 0x18. ;
[dfe5]e8INX; X++ [dfe6]a9 18LDA #$18; 0x18 == Upper byte of the sprite tile address. [dfe8]9d 00 05STA a: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]e8INX; X++ [dfec]a5 1dLDA Fog_TileIndex; Load the fog generator value. [dfee]e6 1dINC Fog_TileIndex; Increment for later (not involved in the math). [dff0]0aASL A; Multiply the value we loaded by 16. [dff1]0aASL A [dff2]0aASL A [dff3]0aASL A [dff4]9d 00 05STA a: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]e8INX; X++ [dff8]86 20STX 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]e6 1dINC Fog_TileIndex; Increment the fog generator value. [dffc]a5 1dLDA Fog_TileIndex; Load the result. [dffe]29 07AND #$07; Keep it in range 0..7. [e000]85 1dSTA Fog_TileIndex; Store it back. [e002]d0 0dBNE 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]e6 1eINC Fog_StateIndex; Increment the fog index. [e006]a5 1eLDA Fog_StateIndex; Load it. [e008]4aLSR A; Divide by 2. [e009]29 03AND #$03; And convert to an index in the lookup table. [e00b]aaTAX; X = A [e00c]bd 12 e0LDA a:FOG_START_TILE_INDEXES,X; Load a starting fog generator value at that index. [e00f]85 1dSTA Fog_TileIndex; And set it.
; ; XREFS: ; Fog_OnTick ; Fog_UpdateTiles ;
[e011]RETURN_E011: [e011]60RTS
; ; XREFS: ; Fog_UpdateTiles ;
[e012]FOG_START_TILE_INDEXES: [e012]18.byte $18; [0]: [e013]06.byte $06; [1]: [e014]30.byte $30; [2]: [e015]0c.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:
; ; Check to make sure we're not outside the walls of Eolis. ;
[e016]a5 24LDA Area_CurrentArea; A = current area. [e018]d0 04BNE @_allowInventory; If it's > Eolis, the player can open the inventory. Jump. [e01a]a5 63LDA Area_CurrentScreen; A = current screen. [e01c]f0 0cBEQ RETURN_E02A; If it's 0, then we're outside the walls. No inventory allowed. Return. [e01e]@_allowInventory: [e01e]a5 19LDA Joy1_ChangedButtonMask; A = controller 1 button mask. [e020]29 20AND #$20; Is Select pressed? [e022]f0 06BEQ RETURN_E02A; If not, return.
; ; Open the inventory via jump to PlayerMenu_Show. ;
[e024]20 59 f8JSR MMC1_LoadBankAndJump; Open the Player Menu. [e027]0c.byte BANK_12_LOGIC; Bank = 12 [e028]92 8apointer PlayerMenu_Show-1; Address = PlayerMenu_Show
; ; XREFS: ; GameLoop_CheckPauseGame ; GameLoop_CheckShowPlayerMenu ;
[e02a]RETURN_E02A: [e02a]60RTS
;============================================================================ ; 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:
; ; Check if the player pressed Start. ;
[e02b]a5 19LDA Joy1_ChangedButtonMask; Load the button state. [e02d]29 10AND #$10; Check if the Pause button is pressed. [e02f]f0 f9BEQ RETURN_E02A; If paused...
; ; The player paused. Set the flag and wait until unpaused. ;
[e031]a9 01LDA #$01 [e033]8d 20 01STA a:Game_PausedState; Set the Paused flag. [e036]@_waitForUnpause: [e036]20 25 caJSR WaitForNextFrame; Wait... [e039]20 a8 cbJSR Sprites_FlipRanges
; ; Check if the player unpaused. ;
[e03c]a5 19LDA Joy1_ChangedButtonMask; Check if the Pause button was pressed again. [e03e]29 10AND #$10 [e040]f0 f4BEQ @_waitForUnpause; Until pause is pressed again, loop.
; ; Unpause the game. ;
[e042]a9 00LDA #$00; Clear the Paused flag. [e044]8d 20 01STA a:Game_PausedState [e047]60RTS
;============================================================================ ; 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:
; ; Preserve the previous pressed buttons while scrolling. ;
[e048]a5 18LDA Joy1_PrevButtonMask; Load the previous button mask. [e04a]85 16STA Joy1_ButtonMask; Set it as the current button mask. [e04c]20 6a e0JSR Game_MovePlayerOnScroll; Update the player position. [e04f]e6 b4INC Screen_ScrollPlayerTransitionCounter; Increment the screen scroll transition counter. [e051]60RTS
;============================================================================ ; TODO: Document Game_MovePlayerOnScrollLeft ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Game_MovePlayerOnScroll ;============================================================================
[e052]Game_MovePlayerOnScrollLeft:
; ; 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]a5 b4LDA Screen_ScrollPlayerTransitionCounter; Load the transition counter. [e054]c9 04CMP #$04; Is it >= 4? [e056]b0 0dBCS @_updateScrollX; If so, jump. [e058]a5 9eLDA Player_PosX_Block; Load the player X position. [e05a]38SEC [e05b]e9 04SBC #$04; Subtract 4. [e05d]85 9eSTA Player_PosX_Block; Store it. [e05f]a5 9fLDA Screen_Maybe_ScrollXCounter; Load our screen scroll counter. [e061]e9 00SBC #$00; Subtract carry. [e063]85 9fSTA Screen_Maybe_ScrollXCounter; Store it.
; ; Update Maybe_PlayerX_ForScroll based on the ; player's X position. ;
[e065]@_updateScrollX: [e065]a5 9eLDA Player_PosX_Block [e067]85 b2STA Maybe_PlayerX_ForScroll [e069]60RTS
;============================================================================ ; 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:
; ; Check which direction the screen is scrolling. ;
[e06a]a6 54LDX Screen_ScrollDirection; Load the screen scroll direction. [e06c]f0 e4BEQ Game_MovePlayerOnScrollLeft; If 0, jump to handle scrolling left. [e06e]caDEX [e06f]f0 21BEQ @_scrollRight; If 1, jump to handle scrolling right. [e071]caDEX [e072]f0 0fBEQ @_scrollUp; If 2, jump to handle scrolling up.
; ; The screen is scrolling to the screen below. ;
[e074]a5 b4LDA Screen_ScrollPlayerTransitionCounter; Load the transition counter. [e076]c9 08CMP #$08; Is it < 8? [e078]90 08BCC @_return1; If so, return. [e07a]a5 b3LDA Maybe_PlayerY_ForScroll; Load the player's Y position during scroll. [e07c]38SEC [e07d]e9 04SBC #$04; Subtract 4 [e07f]85 b3STA Maybe_PlayerY_ForScroll; Store it. [e081]38SEC [e082]@_return1: [e082]60RTS
; ; The screen is scrolling to the screen above. ;
[e083]@_scrollUp: [e083]a5 b4LDA Screen_ScrollPlayerTransitionCounter; Load the transition counter. [e085]c9 08CMP #$08; Is it < 8? [e087]90 08BCC @_return2; If so, return. [e089]a5 b3LDA Maybe_PlayerY_ForScroll; Load the player Y position during scroll. [e08b]18CLC [e08c]69 04ADC #$04; Add 4. [e08e]85 b3STA Maybe_PlayerY_ForScroll; Store it. [e090]38SEC [e091]@_return2: [e091]60RTS
; ; 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]a5 b4LDA Screen_ScrollPlayerTransitionCounter; Load the transition counter. [e094]c9 04CMP #$04; Is it >= 4? [e096]b0 0dBCS @_finishScrollRight; If so, jump. [e098]a5 9eLDA Player_PosX_Block; Load the player X position. [e09a]18CLC [e09b]69 04ADC #$04; Add 4. [e09d]85 9eSTA Player_PosX_Block; Store it. [e09f]a5 9fLDA Screen_Maybe_ScrollXCounter; Load our screen scroll counter. [e0a1]69 00ADC #$00; Add carry. [e0a3]85 9fSTA Screen_Maybe_ScrollXCounter; Store it.
; ; Update Maybe_PlayerX_ForScroll based on the ; player's X position. ;
[e0a5]@_finishScrollRight: [e0a5]a5 9eLDA Player_PosX_Block [e0a7]85 b2STA Maybe_PlayerX_ForScroll [e0a9]60RTS
;============================================================================ ; 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:
; ; Clear scroll state. ;
[e0aa]a9 00LDA #$00 [e0ac]85 9fSTA Screen_Maybe_ScrollXCounter [e0ae]85 a2STA Player_Something_ScrollPosY [e0b0]85 58STA PPU_ScrollScreenVert; DEADCODE: Overridden below. [e0b2]85 59STA PPU_ScrollScreenHoriz
; ; Set the starting speed, status, and iframes. ;
[e0b4]85 a9STA Player_MoveAcceleration; Set the player speed to 0. [e0b6]85 a5STA Player_StatusFlag; Set the player status to 0. [e0b8]85 adSTA Player_InvincibilityPhase; Set the invincibility phase to 0. [e0ba]85 58STA PPU_ScrollScreenVert; Set the vertical scroll to 0.
; ; Face the player to the right. ;
[e0bc]a9 40LDA #$40; 0x04 = Face player right bit. [e0be]85 a4STA Player_Flags; Set that on the player's flags.
; ; Set the accessory information. ;
[e0c0]a9 ffLDA #$ff [e0c2]85 a8STA Player_DrawTileReadOffset
; ; Unused value. This is written to but never read from. ;
[e0c4]a9 02LDA #$02 [e0c6]85 abSTA Player_Unused_00ab [e0c8]60RTS
; ; XREFS: ; GameLoop_UpdatePlayer ;
[e0c9]RETURN_E0C9: [e0c9]60RTS
;============================================================================ ; 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_CheckHandleVertMovement ; Player_CheckHandleJump ; Player_HandleIFrames ; Player_CheckHandleEnterDoor ; Player_CheckOnBreakableBlock ; Player_CheckPushingBlock ; Player_CheckSwitchScreen ; ; XREFS: ; EndGame_MainLoop ; Game_MainLoop ;============================================================================
[e0ca]GameLoop_UpdatePlayer: [e0ca]20 03 e1JSR Player_CheckHandleAttack; Check if the player has attacked and handle it. [e0cd]20 c8 e2JSR Player_CheckHandleVertMovement [e0d0]20 74 e1JSR Player_CheckHandleJump; Check if the player has jumped and handle it. [e0d3]20 e8 e0JSR Player_HandleIFrames; Handle any pending iframes.
; ; Check if screen scrolling is occurring. ;
[e0d6]a5 54LDA Screen_ScrollDirection; A = scrolling activity. [e0d8]c9 04CMP #$04; Is the screen scrolling in either direction? [e0da]90 edBCC RETURN_E0C9; If so, return.
; ; The screen is not scrolling. ;
[e0dc]20 26 e5JSR Player_CheckHandleEnterDoor; Check if the player is entering a door. [e0df]20 05 e9JSR Player_CheckOnBreakableBlock [e0e2]20 5d e9JSR Player_CheckPushingBlock; Check if the player is pushing the block to open a path to Mascon. [e0e5]4c c0 e9JMP 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]a5 adLDA Player_InvincibilityPhase; Check the current invincibility phase. [e0ea]f0 10BEQ @_removeIFrames; If the phase is now 1, remove iframe status.
; ; Invincibility is active. Reduce it by 1. ;
[e0ec]c6 adDEC 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]a5 adLDA Player_InvincibilityPhase; Check the invincibility phase. [e0f0]c9 39CMP #$39; Is it 0x39? [e0f2]f0 05BEQ @_resetSpeed; If so, reset the speed but don't change the player status. [e0f4]90 06BCC @_removeIFrames; If not over, update the player status.
; ; Set the knockback speed and return, leaving the knockback ; state intact. ;
[e0f6]4c 7f e2JMP Player_SetStandardAcceleration; Set standard acceleration.
; ; Set the knockback speed, but then clear the knockback. ;
[e0f9]@_resetSpeed: [e0f9]20 7f e2JSR Player_SetStandardAcceleration; Reset the player speed.
; ; Clear knockback from the player flags. ;
[e0fc]@_removeIFrames: [e0fc]a5 a5LDA Player_StatusFlag; Load the player's flags. [e0fe]29 fdAND #$fd; Clear the knockback bit. [e100]85 a5STA Player_StatusFlag; Save them. [e102]60RTS
;============================================================================ ; Check if the player is attacking and handle it. ; ; If the player is not yet attacking, and is triggering ; an attack ("B" button hit) while in a position to attack ; (not climbing), this will begin the attack phase. ; ; If the player is attacking, this will manage the current ; animation frame based on the attack phase (defined as ; PLAYER_ATTACK_ANIMATION_PHASES). ; ; INPUTS: ; Joy1_ChangedButtonMask: ; The controller 1 button mask. ; ; Player_Flags: ; The player's current flags. ; ; Player_StatusFlag: ; The player's status flags. ; ; PLAYER_ATTACK_ANIMATION_PHASES: ; The table of animation phases to timer lengths. ; ; OUTPUTS: ; Player_AttackPhaseTimer: ; The timer for the current attack phase. ; ; Player_AttackPhaseAnimFrame: ; The current animation frame for the phase. ; ; Player_Flags: ; Player flags with the attacking bit set/cleared. ; ; Player_StatusFlag: ; Player stauts flags with the attacking bit ; set/cleared. ; ; CALLS: ; Player_IsClimbing ; ; XREFS: ; GameLoop_UpdatePlayer ;============================================================================
[e103]Player_CheckHandleAttack: [e103]a5 a4LDA Player_Flags; Load the player's flags. [e105]30 2bBMI @_updateHitPhase; If already attacking, jump.
; ; The player is not yet attacking. Check first if the ; player is allowed to attack. ;
[e107]20 f6 ecJSR Player_IsClimbing; Is the player climbing? [e10a]b0 0dBCS @_clearAttacking; If so, clear any attacking state and return.
; ; The player isn't climbing. Check if they've attacked. ;
[e10c]a5 19LDA Joy1_ChangedButtonMask; Load the controller 1 button mask. [e10e]29 40AND #$40; Is the B button set? [e110]f0 07BEQ @_clearAttacking; If not, clear any attacking state and return. [e112]a5 a5LDA Player_StatusFlag; Load the player's status flags. [e114]29 01AND #$01; Was the player in attack mode? [e116]f0 08BEQ @_setAttacking; If so, set the attacking state. [e118]60RTS
; ; Clear the attacking state for the player. ;
[e119]@_clearAttacking: [e119]a5 a5LDA Player_StatusFlag; Load the player's flags. [e11b]29 feAND #$fe; Clear the Attacking bit. [e11d]85 a5STA Player_StatusFlag; Store it. [e11f]60RTS
; ; Set the attacking state for the player. ; ; This will update the flags and begin the animation ; phase timer. ;
[e120]@_setAttacking: [e120]a5 a4LDA Player_Flags; Load the player's flags. [e122]09 80ORA #$80; Set the Attacking bit. [e124]85 a4STA Player_Flags; Store it. [e126]a5 a5LDA Player_StatusFlag; Load the player's status flags. [e128]09 01ORA #$01; Set the Attacking bit. [e12a]85 a5STA Player_StatusFlag; Store it.
; ; Clear the phase state. ;
[e12c]a9 00LDA #$00 [e12e]85 acSTA Player_AttackPhaseTimer; Set phase timer to 0. [e130]85 aeSTA Player_AttackPhaseAnimFrame; Set phase counter to 0. [e132]@_updateHitPhase: [e132]e6 acINC Player_AttackPhaseTimer; Increment the current phase timer. [e134]a5 acLDA Player_AttackPhaseTimer; A = updated phase timer. [e136]a6 aeLDX Player_AttackPhaseAnimFrame; X = phase counter. [e138]dd 50 e1CMP a:PLAYER_ATTACK_ANIMATION_PHASES,X; Check against the phase lookup table. [e13b]90 0bBCC @_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]a9 00LDA #$00 [e13f]85 acSTA Player_AttackPhaseTimer; Reset the current phase's timer to 0. [e141]e8INX; Phase counter++ [e142]e0 03CPX #$03; Is it >= 3 (end of phases)? [e144]b0 03BCS @_finishAttacking; If so, finish the attacking state. [e146]86 aeSTX Player_AttackPhaseAnimFrame; Else, increment the phase counter for the next frame. [e148]@_return: [e148]60RTS [e149]@_finishAttacking: [e149]a5 a4LDA Player_Flags; Load the player's flags. [e14b]29 7fAND #$7f; Clear the Attacking flag. [e14d]85 a4STA Player_Flags; Store it.
; ; XREFS: ; Player_CheckHandleJump ;
[e14f]RETURN_E14F: [e14f]60RTS
;============================================================================ ; Mapping of player phase indexes to total animation frames. ; ; XREFS: ; Player_CheckHandleAttack ;============================================================================
; ; XREFS: ; Player_CheckHandleAttack ;
[e150]PLAYER_ATTACK_ANIMATION_PHASES: [e150]08.byte $08; [0]: 8 frames thrusting out weapon [e151]03.byte $03; [1]: 3 frames of waiting [e152]08.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:
; ; Temporarily store the player's current flags. ;
[e153]a5 a4LDA Player_Flags; Load the player's flags. [e155]29 40AND #$40; Take only the facing bit. [e157]48PHA; Push it to the stack.
; ; Flip the facing bit and knock back in the flipped direction. ;
[e158]49 40EOR #$40; Flip the facing bit temporarily for the purpose of knockback. [e15a]85 a4STA Player_Flags; Store as the new flags. [e15c]20 6b e1JSR Player_KnockbackHoriz; Move based on the facing bit.
; ; Restore the original flags, merging with any updated bits ; from the move. ;
[e15f]68PLA; Pop the original flags. [e160]85 00STA Temp_00; Store it. [e162]a5 a4LDA Player_Flags; Load the flags set above. [e164]29 bfAND #$bf; Clear the facing bit. [e166]05 00ORA Temp_00; OR with the original facing bit. [e168]85 a4STA Player_Flags; Store it. [e16a]60RTS
;============================================================================ ; 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]a5 a4LDA Player_Flags; Load the player's flags. [e16d]29 40AND #$40; Is the player facing right? [e16f]d0 5eBNE Player_TryMoveRight; If so, move right. [e171]4c 20 e2JMP Player_TryMoveLeft
;============================================================================ ; TODO: Document Player_CheckHandleJump ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; GameLoop_UpdatePlayer ;============================================================================
[e174]Player_CheckHandleJump:
; ; Check if the screen is scrolling. If so, bail. ;
[e174]a5 54LDA Screen_ScrollDirection; Load the scroll direction. [e176]10 d7BPL RETURN_E14F; If set, return.
; ; Check if knockback is active. Handle that if so. ;
[e178]a5 a5LDA Player_StatusFlag; Load the player's status flag. [e17a]29 02AND #$02; Check for knockback. [e17c]d0 d5BNE Player_HandleKnockback; If knockback is set, handle that and return.
; ; Check if the player's flying. ;
[e17e]a5 a5LDA Player_StatusFlag; Load the player's status flag. [e180]30 15BMI @_playerCanFly; If the player is flying, [e182]a5 a4LDA Player_Flags; Load the player's flags. [e184]29 05AND #$05 [e186]f0 0fBEQ @_playerCanFly [e188]a5 a4LDA Player_Flags [e18a]29 20AND #$20 [e18c]f0 1eBEQ @_playerNotMoving [e18e]a5 a4LDA Player_Flags [e190]29 40AND #$40 [e192]d0 3bBNE Player_TryMoveRight [e194]4c 20 e2JMP Player_TryMoveLeft [e197]@_playerCanFly: [e197]a5 a4LDA Player_Flags [e199]30 23BMI @LAB_PRG15_MIRROR__e1be [e19b]a5 16LDA Joy1_ButtonMask [e19d]29 03AND #$03 [e19f]f0 0bBEQ @_playerNotMoving [e1a1]e6 a3INC Player_MovementTick [e1a3]4aLSR A [e1a4]b0 29BCS Player_TryMoveRight [e1a6]4aLSR A [e1a7]90 03BCC @_playerNotMoving [e1a9]4c 20 e2JMP Player_TryMoveLeft [e1ac]@_playerNotMoving: [e1ac]a5 16LDA Joy1_ButtonMask [e1ae]29 0cAND #$0c [e1b0]f0 0cBEQ @LAB_PRG15_MIRROR__e1be [e1b2]a5 9eLDA Player_PosX_Block [e1b4]29 0fAND #$0f [e1b6]f0 06BEQ @LAB_PRG15_MIRROR__e1be [e1b8]a5 a4LDA Player_Flags [e1ba]29 08AND #$08 [e1bc]d0 07BNE @LAB_PRG15_MIRROR__e1c5 [e1be]@LAB_PRG15_MIRROR__e1be: [e1be]a5 a4LDA Player_Flags [e1c0]29 dfAND #$df [e1c2]85 a4STA Player_Flags [e1c4]60RTS [e1c5]@LAB_PRG15_MIRROR__e1c5: [e1c5]e6 a3INC Player_MovementTick [e1c7]a5 9eLDA Player_PosX_Block [e1c9]29 0fAND #$0f [e1cb]c9 08CMP #$08 [e1cd]90 51BCC 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:
; ; Check whether the player is moving. ;
[e1cf]a5 a4LDA Player_Flags; Load the player's flags. [e1d1]29 20AND #$20; Check the moving bit. [e1d3]d0 03BNE @_setMoving; If set, jump to handle movement.
; ; The player is not moving. Reset the movement speed. ;
[e1d5]20 7f e2JSR Player_SetStandardAcceleration; Start with the standard acceleration.
; ; Calculate movement speed and set the player to move to ; the right. ;
[e1d8]@_setMoving: [e1d8]20 91 e2JSR Player_UpdateAcceleration; Calculate the new walking speed. [e1db]a5 a4LDA Player_Flags; Load the player's flags. [e1dd]09 60ORA #$60; Set moving and to the right. [e1df]85 a4STA Player_Flags; Store as the new flags.
; ; Update the player's X position to factor in acceleration. ;
[e1e1]a5 9dLDA Player_PosX_Frac; Load the fractional X position. [e1e3]18CLC [e1e4]65 a9ADC Player_MoveAcceleration; Add the move acceleration. [e1e6]85 9dSTA Player_PosX_Frac; Store as the new fractional position. [e1e8]a5 9eLDA Player_PosX_Block; Load the full X position. [e1ea]65 aaADC Player_MoveAcceleration_U; Add the move acceleration. [e1ec]85 9eSTA Player_PosX_Block; And store it.
; ; Check if the block there is passable. ;
[e1ee]a2 01LDX #$01 [e1f0]20 c8 e6JSR Player_CheckIfPassable; Is the block to the right passable? [e1f3]f0 09BEQ @_checkAtRightEdge; If not, jump to check for an edge or transition boundary.
; ; The block is not passable. Cap the position. ;
[e1f5]a5 9eLDA Player_PosX_Block; Load the player X position. [e1f7]29 f0AND #$f0; Cap it. [e1f9]85 9eSTA Player_PosX_Block; And store it. [e1fb]4c 1a e2JMP @_return
; ; Check if the player is far enough to the right for ; any screen transition logic. ;
[e1fe]@_checkAtRightEdge: [e1fe]a5 9eLDA Player_PosX_Block; X = Player X position. [e200]c9 f1CMP #$f1; Is it too far left to perform a screen transition? [e202]90 16BCC @_return; If so, return.
; ; Check if the player is at the edge and can scroll. ;
[e204]a0 01LDY #$01 [e206]20 27 e6JSR Screen_IsEdge; Check if the screen is the right-most edge. [e209]b0 10BCS Player_SetPosAtRightEdge; If so, jump to set the position there.
; ; There's a screen to the right. Transition there. ;
[e20b]a5 67LDA Area_ScreenToTheRight; A = Neighboring screen to the right. [e20d]85 63STA Area_CurrentScreen; Store as the new current screen. [e20f]a2 01LDX #$01 [e211]20 e9 e8JSR Area_ScrollTo; Begin scrolling to the right. [e214]e6 9fINC Screen_Maybe_ScrollXCounter; Increment the scroll counter. [e216]a9 00LDA #$00 [e218]85 b4STA Screen_ScrollPlayerTransitionCounter; Clear the transition counter. [e21a]@_return: [e21a]60RTS
;============================================================================ ; 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]a9 f0LDA #$f0 [e21d]85 9eSTA Player_PosX_Block; Set the X position to 0xF0. [e21f]60RTS
;============================================================================ ; 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:
; ; Check whether the player is moving. ;
[e220]a5 a4LDA Player_Flags; Load the player's flags. [e222]29 20AND #$20; Check the moving bit. [e224]d0 03BNE @_setMoving; If set, jump to handle movement.
; ; The player is not moving. Reset the movement speed. ;
[e226]20 7f e2JSR Player_SetStandardAcceleration; Start with the standard acceleration.
; ; Calculate movement speed and set the player to move to ; the left. ;
[e229]@_setMoving: [e229]20 91 e2JSR Player_UpdateAcceleration; Calculate the new walking speed. [e22c]a5 a4LDA Player_Flags; Load the player's flags. [e22e]29 bfAND #$bf; Set the player to face left. [e230]09 20ORA #$20; Set the player to moving. [e232]85 a4STA Player_Flags; Store as the new flags.
; ; Update the player's X position to factor in acceleration. ;
[e234]a5 9dLDA Player_PosX_Frac; Load the fractional X position. [e236]38SEC [e237]e5 a9SBC Player_MoveAcceleration; Subtract the move acceleration. [e239]85 9dSTA Player_PosX_Frac; Store as the new fractional position. [e23b]a5 9eLDA Player_PosX_Block; Load the full X position. [e23d]e5 aaSBC Player_MoveAcceleration_U; Subtract the move acceleration. [e23f]08PHP; Push all flags. [e240]b0 02BCS @_saveFlags; If >= 0, jump. [e242]a9 00LDA #$00; Else, cap at 0, so it doesn't wrap. [e244]@_saveFlags: [e244]85 9eSTA Player_PosX_Block; Store the new X position.
; ; Check if the block there is passable. ;
[e246]a2 00LDX #$00 [e248]20 c8 e6JSR Player_CheckIfPassable; Is the block to the left passable? [e24b]f0 11BEQ @_checkAtLeftEdge; If not, jump to check for an edge or transition boundary.
; ; The block is not passable. Cap the position. ;
[e24d]28PLP; Pop the flags from the acceleration calculation. [e24e]a5 9eLDA Player_PosX_Block; Load the player X position. [e250]29 0fAND #$0f; Is this in the first 16 pixels? [e252]f0 09BEQ @_return1; If so, we're done. [e254]a5 9eLDA Player_PosX_Block; Load the player X position. [e256]29 f0AND #$f0; Keep all but pixels 0-16. [e258]18CLC [e259]69 10ADC #$10; Add 16 pixels. [e25b]85 9eSTA Player_PosX_Block; Store as the new position. [e25d]@_return1: [e25d]60RTS; And we're done.
; ; Check if the player is far enough to the left for ; any screen transition logic. ;
[e25e]@_checkAtLeftEdge: [e25e]28PLP; Pop the flags from the acceleration calculation. [e25f]b0 16BCS @_return2; If it didn't overflow off the left, return.
; ; Check if the player is at the edge and can scroll. ;
[e261]a0 00LDY #$00 [e263]20 27 e6JSR Screen_IsEdge; Check if the screen is the left-most edge. [e266]b0 10BCS @_setAtLeftEdge; If at the edge, jump to cap the position.
; ; There's a screen to the left. Transition there. ;
[e268]a5 66LDA Area_ScreenToTheLeft; A = Neighboring screen to the left. [e26a]85 63STA Area_CurrentScreen; Store as the new current screen. [e26c]a2 00LDX #$00 [e26e]20 e9 e8JSR Area_ScrollTo; Begin scrolling to the left. [e271]a9 00LDA #$00 [e273]85 9eSTA Player_PosX_Block; Set the X position to 0. [e275]85 b4STA Screen_ScrollPlayerTransitionCounter; Clear the transition counter. [e277]@_return2: [e277]60RTS
; ; The player is at the left-most edge, but didn't pass it. ;
[e278]@_setAtLeftEdge: [e278]a9 00LDA #$00 [e27a]85 9eSTA Player_PosX_Block; Set the X position to 0. [e27c]60RTS; And return. [e27d]00.byte $00; [0]: [e27e]0f.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]a9 c0LDA #$c0 [e281]85 a9STA Player_MoveAcceleration; Set the lower byte of movement acceleation to 0xC0. [e283]a9 00LDA #$00 [e285]85 aaSTA Player_MoveAcceleration_U; Set the upper byte to 0. [e287]60RTS
;============================================================================ ; 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:
; ; The player's in knockback state. Bump the player ; back a bunch. ;
[e288]a9 00LDA #$00 [e28a]85 a9STA Player_MoveAcceleration; Set lower acceleration byte to 0. [e28c]a9 08LDA #$08 [e28e]85 aaSTA Player_MoveAcceleration_U; Set upper to 8. [e290]60RTS
;============================================================================ ; Update the player's acceleration. ; ; This will determine if the player can accelerate and, if ; so, update the acceleration value based on their ; title/rank. ; ; If the player's in a knockback state, acceleration will ; stop and the position will be updated. ; ; If the player pressed Up or Down, or is in front of a ; climbable block, acceleration will reset. ; ; If the player's hit max acceleration (384), acceleration ; will not increase. ; ; Otherwise, acceleration will be updated based on their ; rank/title. Values are in 4 groups of 4 ranks each, ; meaning every 4 ranks, their acceleration will increase. ; ; INPUTS: ; Player_Flags: ; The player's current flags. ; ; Player_StatusFlag: ; The player's current status flags. ; ; PlayerTitle: ; The player's current title/rank. ; ; Joy1_ButtonMask: ; Controller 1's button mask. ; ; Player_MoveAcceleration: ; The current move acceleration. ; ; PLAYER_ACCELERATION_DELTA_BY_RANK_GROUP: ; The table of acceleration values by rank group. ; ; OUTPUTS: ; Player_MoveAcceleration: ; The updated move acceleration. ; ; CALLS: ; Player_SetStandardAcceleration ; Player_UpdatePosFromKnockback ; ; XREFS: ; Player_TryMoveLeft ; Player_TryMoveRight ;============================================================================
[e291]Player_UpdateAcceleration:
; ; First, check if the player is in a knockback state. If so, ; instead of accelerating, set their position from knockback. ;
[e291]a5 a5LDA Player_StatusFlag; Load the player's status flags. [e293]29 02AND #$02; Keep the knockback bit. [e295]d0 f1BNE Player_UpdatePosFromKnockback; If set, update the position from knockback and return.
; ; The player is not in a knockback state, so proceed with ; acceleration. ;
[e297]a5 16LDA Joy1_ButtonMask; Load the pressed buttons. [e299]29 0cAND #$0c; Check if Up or Down is pressed. [e29b]f0 06BEQ @_updateAcceleration; If not, jump to update acceleration. [e29d]a5 a4LDA Player_Flags; Load the player's flags. [e29f]29 08AND #$08; Check if the player can currently climb. [e2a1]d0 dcBNE Player_SetStandardAcceleration; If so, switch to standard acceleration and return.
; ; The player is moving and can accelerate. Calculate the ; acceleration. ; ; First, check if the acceleration >= 384, which I believe ; represents max acceleration. ;
[e2a3]@_updateAcceleration: [e2a3]a5 a9LDA Player_MoveAcceleration; Load the lower byte of the current acceleration. [e2a5]c9 80CMP #$80; If >= 128, C=1. Else, C=0. [e2a7]a5 aaLDA Player_MoveAcceleration_U; And load the upper byte. [e2a9]e9 01SBC #$01; If >= 128 above, subtract (1 + C). That equals 1 if C=0, 2 if C=1. [e2ab]b0 16BCS @_return; If acceleration >= 384 (-128 unsigned), return.
; ; Max acceleration wasn't hit, so calculate acceleration. ; ; This will be done based on the player's title and how much ; they've moved. ; ; Every 4 ranks, acceleration will change, so we'll take the ; current one and divide by 4 to get that grouping. ;
[e2ad]ad 37 04LDA a:PlayerTitle; Load the player's title. [e2b0]4aLSR A; Divide by 4 (acceleration changes every 4 ranks). [e2b1]4aLSR A [e2b2]29 03AND #$03; Cap to the size of the lookup table. [e2b4]aaTAX; X = resulting table index. [e2b5]a5 a9LDA Player_MoveAcceleration; A = Current acceleration (lower byte). [e2b7]18CLC [e2b8]7d c4 e2ADC a:PLAYER_ACCELERATION_DELTA_BY_RANK_GROUP,X; Add acceleration for the current rank. [e2bb]85 a9STA Player_MoveAcceleration; Store as the new lower byte value. [e2bd]a5 aaLDA Player_MoveAcceleration_U; Load the upper byte. [e2bf]69 00ADC #$00; Increment if lower byte wrapped. [e2c1]85 aaSTA Player_MoveAcceleration_U; Store it. [e2c3]@_return: [e2c3]60RTS
;============================================================================ ; Mapping table of rank groups to acceleration deltas. ; ; Each entry is indexed by a 4-span of ranks, mapping to ; a value that should be added to the player's acceleration ; every update tick. ; ; ; XREFS: ; Player_UpdateAcceleration ;============================================================================
; ; XREFS: ; Player_UpdateAcceleration ;
[e2c4]PLAYER_ACCELERATION_DELTA_BY_RANK_GROUP: [e2c4]02.byte $02; [0]: Novice - Aspirant [e2c5]04.byte $04; [1]: Adept - Warrior [e2c6]06.byte $06; [2]: Swordman - Myrmidon [e2c7]08.byte $08; [3]: Champion - Lord
;============================================================================ ; TODO: Document Player_CheckHandleVertMovement ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; GameLoop_UpdatePlayer ;============================================================================
[e2c8]Player_CheckHandleVertMovement:
; ; First, check if the Wing Boots are active or the A ; button is pressed. ;
[e2c8]a5 a5LDA Player_StatusFlag; A = Player status flags. [e2ca]10 0aBPL @_checkClimbLadder; If Wing Boots are not active, jump. [e2cc]a5 16LDA Joy1_ButtonMask; A = Controller 1 button mask. [e2ce]10 06BPL @_checkClimbLadder; If A is not pressed, jump.
; ; Wing Boots are active and the A button is pressed. ;
[e2d0]20 52 e7JSR Player_CheckIfOnLadder; Set state based on whether the user's in front of a ladder/climbable block. [e2d3]4c f4 e2JMP @_checkClimbUpDown [e2d6]@_checkClimbLadder: [e2d6]20 52 e7JSR Player_CheckIfOnLadder; Set state based on whether the user's in front of a ladder/climbable block. [e2d9]a5 a4LDA Player_Flags; Load the player's flags. [e2db]29 08AND #$08; Is the Can Climb flag set? [e2dd]f0 1fBEQ @_checkClimbSide; If not, jump to check climbing to the side. [e2df]a5 16LDA Joy1_ButtonMask; Load the controller 1 button mask. [e2e1]29 0cAND #$0c; Check if Up or Down is pressed. [e2e3]f0 19BEQ @_checkClimbSide; If not, check climbing to the side.
; ; The player is climbing. Set the state. ;
[e2e5]a5 a4LDA Player_Flags; Load the player's flags. [e2e7]09 10ORA #$10; Set the climbing bit. [e2e9]85 a4STA Player_Flags; Save it.
; ; Check if the player is evenly on a ladder (aligned with the ; sides of the ladder, and not in-between ladders or falling ; off). ; ; If evenly on a ladder, they'll climb up or down. If not, ; they'll be re-aligned first, or will fall off, depending ; on direction and nearby blocks. ;
[e2eb]a5 9eLDA Player_PosX_Block; Load the block X position the player is on. [e2ed]29 0fAND #$0f; Round it. [e2ef]f0 03BEQ @_checkClimbUpDown; If the player's evenly on the ladder, jump.
; ; The player is not evenly on the ladder. ;
[e2f1]4c 99 e3JMP Player_HandleVertLadderOrJump; Handle ladder movement.
; ; Check if the player is climbing up a ladder. ;
[e2f4]@_checkClimbUpDown: [e2f4]a5 16LDA Joy1_ButtonMask; Load the Controller 1 button mask. [e2f6]4aLSR A; Shift the "Down" bit into bit 0. [e2f7]4aLSR A [e2f8]4aLSR A [e2f9]b0 4eBCS Player_CheckHandleClimbDown; If set, climb down.
; ; Check if the player is climbing down. ;
[e2fb]4aLSR A; Else, shift "Up" into bit 0. [e2fc]b0 03BCS Player_CheckHandleClimbUp; If set, climb up. [e2fe]@_checkClimbSide: [e2fe]4c 99 e3JMP Player_HandleVertLadderOrJump; Else, handle general ladder movement.
;============================================================================ ; TODO: Document Player_CheckHandleClimbUp ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_CheckHandleVertMovement ;============================================================================
[e301]Player_CheckHandleClimbUp:
; ; Clear the flags for jumping, falling, and moving. ;
[e301]a5 a4LDA Player_Flags; Load the player's flags. [e303]29 daAND #$da; Clear the Jumping, Falling Off, and Moving flags. [e305]85 a4STA Player_Flags; Save it.
; ; Check if the player is able to climb up a block. ;
[e307]a2 02LDX #$02; X = 2 (Up direction). [e309]20 c8 e6JSR Player_CheckIfPassable; Check if the block up is passable. [e30c]d0 35BNE @_return; If not, return. [e30e]e6 a3INC Player_MovementTick; Increment the movement tick. [e310]a5 a5LDA Player_StatusFlag; Load the player's status flags. [e312]10 0aBPL @_wingBootsActive; If Wing Boots are active, jump.
; ; Wing Boots are not active, and the block is climbable, ; so move the player up 1. ;
[e314]a5 a1LDA Player_PosY; Load the player's Y position. [e316]38SEC [e317]e9 01SBC #$01; Subtract 1. [e319]85 a1STA Player_PosY; Save it. [e31b]4c 2b e3JMP @_checkScreenEdge
; ; Wing Boots are active. ;
[e31e]@_wingBootsActive: [e31e]a5 a0LDA Player_ClimbFlySpeedModifier [e320]38SEC [e321]e9 a0SBC #$a0 [e323]85 a0STA Player_ClimbFlySpeedModifier [e325]a5 a1LDA Player_PosY; Load the player's Y position. [e327]e9 00SBC #$00; Subtract carry from above calculation, if set. [e329]85 a1STA Player_PosY; Save it.
; ; Check the screen edge for wrapping. ; ; If subtracting 1 from the player's Y position did not wrap ; to negative, then there's no reason to check the edge, in ; which case we can bail early. ;
[e32b]@_checkScreenEdge: [e32b]b0 16BCS @_return; If subtraction above did not wrap, return. [e32d]a0 02LDY #$02; Y = 2 (Up direction). [e32f]20 27 e6JSR Screen_IsEdge; Check if there's a screen above or if this is the edge. [e332]b0 10BCS @_atEdge; If at the edge, jump.
; ; There's a screen above. ;
[e334]a5 68LDA Area_ScreenAbove; Load the index of the screen above. [e336]85 63STA Area_CurrentScreen; Set as the new current screen.
; ; Scroll up. ;
[e338]a2 02LDX #$02; X = 2 (Up direction). [e33a]20 e9 e8JSR Area_ScrollTo; Scroll up. [e33d]c6 a2DEC Player_Something_ScrollPosY; Decrement the scrolling Y position by 1.
; ; Move the player to the bottom of the new screen. ;
[e33f]a9 c0LDA #$c0; Set the player's Y position to the bottom of the screen. [e341]85 a1STA Player_PosY; Save it. [e343]@_return: [e343]60RTS
; ; The player's at the screen edge and can't go up, so cap ; the Y position to 0 so it doesn't wrap. ;
[e344]@_atEdge: [e344]a9 00LDA #$00; A = 0. [e346]85 a1STA Player_PosY; Set as the Y position. [e348]60RTS
;============================================================================ ; TODO: Document Player_CheckHandleClimbDown ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_CheckHandleVertMovement ;============================================================================
[e349]Player_CheckHandleClimbDown: [e349]a5 a4LDA Player_Flags [e34b]29 daAND #$da [e34d]85 a4STA Player_Flags [e34f]a2 03LDX #$03 [e351]20 c8 e6JSR Player_CheckIfPassable [e354]d0 3dBNE RETURN_E393 [e356]e6 a3INC Player_MovementTick [e358]a5 a5LDA Player_StatusFlag [e35a]10 10BPL @LAB_PRG15_MIRROR__e36c [e35c]a5 a0LDA Player_ClimbFlySpeedModifier [e35e]18CLC [e35f]69 80ADC #$80 [e361]85 a0STA Player_ClimbFlySpeedModifier [e363]a5 a1LDA Player_PosY [e365]69 01ADC #$01 [e367]85 a1STA Player_PosY [e369]4c 79 e3JMP Player_MoveDownScreen [e36c]@LAB_PRG15_MIRROR__e36c: [e36c]a5 a0LDA Player_ClimbFlySpeedModifier [e36e]18CLC [e36f]69 c0ADC #$c0 [e371]85 a0STA Player_ClimbFlySpeedModifier [e373]a5 a1LDA Player_PosY [e375]69 00ADC #$00 [e377]85 a1STA Player_PosY
;============================================================================ ; TODO: Document Player_MoveDownScreen ; ; INPUTS: ; A ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_CheckHandleClimbDown ;============================================================================
[e379]Player_MoveDownScreen: [e379]c9 c1CMP #$c1 [e37b]90 16BCC RETURN_E393 [e37d]a0 03LDY #$03 [e37f]20 27 e6JSR Screen_IsEdge [e382]b0 10BCS Player_SetPosAtBottomEdge [e384]a5 69LDA Area_ScreenBelow [e386]85 63STA Area_CurrentScreen [e388]a2 03LDX #$03 [e38a]20 e9 e8JSR Area_ScrollTo [e38d]e6 a2INC Player_Something_ScrollPosY [e38f]a9 00LDA #$00 [e391]85 a1STA Player_PosY
; ; XREFS: ; Player_CheckHandleClimbDown ; Player_MoveDownScreen ;
[e393]RETURN_E393: [e393]60RTS
;============================================================================ ; 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]a9 c0LDA #$c0 [e396]85 a1STA Player_PosY; Set player Y position to 0xC0 (192). [e398]60RTS
;============================================================================ ; TODO: Document Player_HandleVertLadderOrJump ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_CheckHandleVertMovement ;============================================================================
[e399]Player_HandleVertLadderOrJump:
; ; Check if there's a jump curve, and if it's in bounds. ;
[e399]a5 a6LDA Player_JumpCurveIndex; Load the jump curve index. [e39b]c9 20CMP #$20; Is it within bounds of the jump curve? [e39d]90 06BCC @_checkJumping; If so, check the jumping state.
; ; The jump curve is not in bounds. Clear the player's ; jumping state. ;
[e39f]a5 a4LDA Player_Flags; Load the player's flags. [e3a1]29 feAND #$fe; Clear the Jumping bit. [e3a3]85 a4STA Player_Flags; Save it back.
; ; Check the jumping state. ; ; If jumping, continue processing the jump. ; ; If not jumping, handle ladder movement. ;
[e3a5]@_checkJumping: [e3a5]a5 a4LDA Player_Flags; Load the player's flags. [e3a7]4aLSR A; Shift the Jumping bit into bit 0. [e3a8]90 03BCC @_checkDownPassable; If not jumping, check if the block below is passable.
; ; The player is jumping. Stay on the ladder. ;
[e3aa]4c 3a e4JMP Player_GrabClimbLadderOrJump; Else, stay on the ladder.
; ; BUG: This checks if a block in the Down direction is passable, ; but sets the wrong variable. It sets A instead of X. So ; instead, this ends up checking whatever X might be set to ; at this moment. ; ; Due to the way Player_CheckIfPassable ; works, this ; will check the block below unless X just happens to be 0, ; 1, or 2. Which is what "A" was going for above, checking ; the Down direction. So most likely, this will check Down, ; but it can mess up depending on what's in the X register. ;
[e3ad]@_checkDownPassable: [e3ad]a9 03LDA #$03; Should be LDX -- Fail to set the direction to Up. [e3af]20 c8 e6JSR Player_CheckIfPassable; Check if the block in whatever direction might be in X is passable. [e3b2]f0 03BEQ @_checkAdjacent; If it's passable, jump.
; ; The block below is impassable (well, in theory.. the bug). ; ; Stay on the ladder and continue. ;
[e3b4]4c 3a e4JMP Player_GrabClimbLadderOrJump
; ; Check if the player is moving left or right at the bottom of ; the screen and has a solid block in the direction they're ; moving. If so, keep the player firmly in place ; ; NOTE: ; has a solid block off-screen. If so, a counter ; will start ; ; NOTE: I'm ;
[e3b7]@_checkAdjacent: [e3b7]20 f6 e4JSR Area_CheckClimbAdjacentImpassable; Check if attempting to climb left/right to an impassable block. [e3ba]a5 b7LDA Blocks_Result; Check the result. [e3bc]f0 0fBEQ @_checkWingBoots; If passable, check if Wing Boots are active.
; ; The player tried climbing left or right, and the block there ; was impassable. ; ; This code is going to try to check what I think is a hang ; time to go from climbing to falling, but in practice, I'm ; not sure this is ever used. ; ; Seems we'd get here if moving left or right into an ; impassable block at the bottom of the screen, but I can't ; make this happen. I think other code paths get to that case ; first. ;
[e3be]a5 b1LDA Player_ClimbLadderHangTime [e3c0]c9 08CMP #$08 [e3c2]b0 05BCS @_resetHangTime [e3c4]e6 b1INC Player_ClimbLadderHangTime [e3c6]4c 44 e4JMP Player_UpdateClimbOrJump [e3c9]@_resetHangTime: [e3c9]a9 00LDA #$00 [e3cb]85 b1STA Player_ClimbLadderHangTime
; ; DEADCODE: This checks if Wing Boots are active, and then ; jumps to the very next line. It effectively does ; nothing. ;
[e3cd]@_checkWingBoots: [e3cd]a5 a5LDA Player_StatusFlag [e3cf]10 00BPL @_setFalling
; ; Mark the player as falling off the ladder. ;
[e3d1]@_setFalling: [e3d1]a5 a4LDA Player_Flags; Load the player's flags. [e3d3]09 04ORA #$04; Set the Falling bit. [e3d5]85 a4STA Player_Flags; Save it. [e3d7]a5 a4LDA Player_Flags; Load the player's flags again. [e3d9]29 08AND #$08; Check if the Can Climb bit is set. [e3db]d0 5dBNE Player_GrabClimbLadderOrJump; If so, stay on the ladder.
; ; The player can no longer climb. ; ; Update the Y position based on whether Wing Boots are ; being used or not. ;
[e3dd]a5 a5LDA Player_StatusFlag; Load the player's status flag. [e3df]10 14BPL @_climbDown; If Wing Boots are inactive, jump to climb down. [e3e1]a5 16LDA Joy1_ButtonMask; Load the controller 1 button mask. [e3e3]10 10BPL @_climbDown; If the A button isn't pressed, jump to climb down.
; ; Wing Boots are active, and "A" is pressed. ; ; ; DEADCODE: This next block doesn't do anything anymore. It ; loads the modifier, adds 0, and saves it back. It ; was probably used at one point to do the same kind ; of work that Player_CheckHandleClimbUp ; does. ;
[e3e5]a5 a0LDA Player_ClimbFlySpeedModifier [e3e7]18CLC [e3e8]69 00ADC #$00 [e3ea]85 a0STA Player_ClimbFlySpeedModifier
; ; Increase the Y position by 1. ;
[e3ec]a5 a1LDA Player_PosY; Load the player's Y position. [e3ee]69 01ADC #$01; Add 1. [e3f0]85 a1STA Player_PosY; Save it. [e3f2]4c fc e3JMP @_checkScrollScreenDown; Check if the screen should now scroll down.
; ; Move the player down 8 pixels. ;
[e3f5]@_climbDown: [e3f5]a5 a1LDA Player_PosY; Load the player's Y position. [e3f7]18CLC [e3f8]69 08ADC #$08; Add 8. [e3fa]85 a1STA Player_PosY; Save it.
; ; Check if the screen should scroll down. ;
[e3fc]@_checkScrollScreenDown: [e3fc]c9 c1CMP #$c1; Check if the player's hit the bottom of the screen. [e3fe]90 1eBCC @_checkCapFall; If not, check whether the player's fell on a block.
; ; The player hit the bottom of the screen. ; ; Check if there's a screen below. ;
[e400]a0 03LDY #$03; Y = 3 (Down direction). [e402]20 27 e6JSR Screen_IsEdge; Check if there's a screen or edge there. [e405]90 07BCC @_scrollScreenDown; If there's a screen, handle scrolling down.
; ; There's no screen below. Cap the player's position to the ; bottom of the screen. ;
[e407]a9 c0LDA #$c0; A = 192 (last Y position on the screen). [e409]85 a1STA Player_PosY; Set that as the player's Y position. [e40b]4c 2c e4JMP @_clearFalling; Clear the falling flag.
; ; Begin scrolling the screen down. ;
[e40e]@_scrollScreenDown: [e40e]a5 69LDA Area_ScreenBelow; Load the screen below. [e410]85 63STA Area_CurrentScreen; Store as the new current screen. [e412]a2 03LDX #$03; X = 3 (Down direction). [e414]20 e9 e8JSR Area_ScrollTo; Scroll there. [e417]e6 a2INC Player_Something_ScrollPosY; Increment the scroll Y position. [e419]a9 00LDA #$00; A = 0 (new player position, top of screen). [e41b]85 a1STA Player_PosY; Set as the player's new Y position. [e41d]60RTS
; ; Check if the block below is passable. If not, cap the Y ; position so the player doesn't fall through it. ;
[e41e]@_checkCapFall: [e41e]a2 03LDX #$03; X = 3 (Down direction). [e420]20 c8 e6JSR Player_CheckIfPassable; Check if there's a passable block there. [e423]f0 06BEQ @_return; If so, return.
; ; It's not passable, so we can cap now. ;
[e425]a5 a1LDA Player_PosY; Load the player's Y position. [e427]29 f0AND #$f0; Round to the top of the block. [e429]85 a1STA Player_PosY; Save it. [e42b]@_return: [e42b]60RTS
; ; Clear the falling state. ;
[e42c]@_clearFalling: [e42c]a5 a4LDA Player_Flags; Load the player's flags. [e42e]29 fbAND #$fb; Clear the falling bit. [e430]85 a4STA Player_Flags; Store it. [e432]60RTS
;============================================================================ ; Clear the player's Jumping and Holding to Climb flags. ; ; INPUTS: ; Player_Flags: ; The player's current flags. ; ; OUTPUTS: ; Player_Flags: ; The flags with Jumping and Holding To Climb cleared. ; ; XREFS: ; Player_UpdateClimbOrJump ;============================================================================
[e433]Player_ClearJumpingAndHoldingToClimb: [e433]a5 a4LDA Player_Flags; Load the player flags. [e435]29 fcAND #$fc; Clear the Jumping and Holding To Climb flags. [e437]85 a4STA Player_Flags; Save it.
; ; XREFS: ; Player_UpdateClimbOrJump ;
[e439]RETURN_E439: [e439]60RTS
;============================================================================ ; TODO: Document Player_GrabClimbLadderOrJump ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_HandleVertLadderOrJump ;============================================================================
[e43a]Player_GrabClimbLadderOrJump:
; ; Clear the Falling Off state. ;
[e43a]a5 a4LDA Player_Flags; Load the player's flags. [e43c]29 fbAND #$fb; Clear the Falling Off flag. [e43e]85 a4STA Player_Flags; Save it back.
; ; Reset the hang time. ;
[e440]a9 00LDA #$00 [e442]85 b1STA Player_ClimbLadderHangTime
; ; v-- Fall through --v ;
;============================================================================ ; TODO: Document Player_UpdateClimbOrJump ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_HandleVertLadderOrJump ;============================================================================
[e444]Player_UpdateClimbOrJump:
; ; Check if the player is jumping. ;
[e444]a5 a4LDA Player_Flags; Load the player's flags. [e446]4aLSR A; Shift the Jump flag into Carry. [e447]b0 1aBCS @_isJumping; If set (player is jumping), then jump (branch).
; ; The player is not jumping. ;
[e449]a6 19LDX Joy1_ChangedButtonMask; X = Controller 1 changed button mask. [e44b]10 e6BPL Player_ClearJumpingAndHoldingToClimb; If the player is holding down A, then check grabbing for a ladder and return.
; ; Check if the player is climbing. If so, return. ;
[e44d]20 f6 ecJSR Player_IsClimbing [e450]b0 e7BCS RETURN_E439
; ; Check if the block above is passable. If not, return. ;
[e452]a2 02LDX #$02 [e454]20 c8 e6JSR Player_CheckIfPassable [e457]d0 5dBNE @_return
; ; Set the Jumping and Holding To Climb flags. ;
[e459]a5 a4LDA Player_Flags; Load the player's flags. [e45b]09 03ORA #$03; Set the Jumping and Holding To Climb flags. [e45d]85 a4STA Player_Flags; Save it. [e45f]a9 00LDA #$00 [e461]85 a6STA Player_JumpCurveIndex [e463]@_isJumping: [e463]a6 a6LDX Player_JumpCurveIndex; Load the jump timer. [e465]e0 10CPX #$10; Check if we're in a rising (< 16) or falling (>= 16) state. [e467]90 02BCC @_jumpRising; Jump if rising. [e469]b0 37BCS @_jumpFalling; Jump if falling.
; ; Reduce the player's Y position based on the current position ; in the jump curve. ;
[e46b]@_jumpRising: [e46b]a5 a1LDA Player_PosY; Load the player's Y position. [e46d]38SEC [e46e]fd d6 e4SBC a:PLAYER_JUMP_CURVE,X; Subtract the delta from the jump curve table. [e471]85 a1STA Player_PosY; Store as the new Y position. [e473]b0 0cBCS @_checkBlockAbove; If Y >= 0, jump.
; ; The Y position wrapped around. Check if we should switch ; screens to the screen above. ;
[e475]a0 02LDY #$02; Y = 2 (Up direction). [e477]20 27 e6JSR Screen_IsEdge; Check if there's an edge above. [e47a]90 3bBCC @_scrollScreenUp; If not, scroll the screen up.
; ; We're not switching screens, so cap at Y=0. ;
[e47c]a9 00LDA #$00; A = 0 (new player Y position). [e47e]4c 9a e4JMP @_setJumpCurveMidpoint; Jump to set the position and wrap up.
; ; Check if there's an impassable block above the player. If not, ; the player's Y position will be capped below the block. ;
[e481]@_checkBlockAbove: [e481]a2 02LDX #$02; X = 2 (Up direction). [e483]20 c8 e6JSR Player_CheckIfPassable; Check if the block there is passable. [e486]f0 29BEQ @_continueJumpCurve; If impassable, continue the jump.
; ; The player hit against an impassable block. That means ; they've hit the top of the jump curve and can't go any ; higher. ; ; This will set the jump curve to the midpoint so they'll ; begin to fall, and cap the player's Y so they're just ; below the block they hit. Unless they hit the top of ; the screen, in which case they'll just be capped there. ;
[e488]a6 a6LDX Player_JumpCurveIndex; X = Jump curve index. [e48a]a5 a1LDA Player_PosY; A = Current player Y position. [e48c]29 0fAND #$0f; Cap it to <= the midpoint of the curve. [e48e]aaTAX; X = result. [e48f]a5 a1LDA Player_PosY; A = Current player Y position. [e491]29 f0AND #$f0; Cap it to a rounded block position. [e493]e0 00CPX #$00; Is it 0? [e495]f0 03BEQ @_setJumpCurveMidpoint; If so, jump to avoid updating the Y position. [e497]18CLC [e498]69 10ADC #$10; Shift down one block (below the obstacle hit).
; ; Set the jump curve index to 0xF, one just before the ; midpoint, to begin the process of putting the player ; into a downward arc. ; ; Just before the midpoint is used because after this, ; the index will advance by one. ;
[e49a]@_setJumpCurveMidpoint: [e49a]85 a1STA Player_PosY; Set the new player Y position. [e49c]a9 0fLDA #$0f; 0xF == Jump curve midpoint (- 1). [e49e]85 a6STA Player_JumpCurveIndex; Set to the midpoint to begin falling. [e4a0]d0 0fBNE @_continueJumpCurve; Continue the jump curve, advancing the index.
; ; Increase the player's Y position based on the current position ; in the jump curve. ;
[e4a2]@_jumpFalling: [e4a2]a5 a1LDA Player_PosY; Load the player's Y position. [e4a4]18CLC [e4a5]7d d6 e4ADC a:PLAYER_JUMP_CURVE,X; Add the delta from the jump curve table. [e4a8]85 a1STA Player_PosY; Store as the new Y position. [e4aa]a2 03LDX #$03; Y = 2 (Down direction). [e4ac]20 c8 e6JSR Player_CheckIfPassable; Check if the block there is passable. [e4af]d0 18BNE @_setLandedOnBlock; If impassable, land the player.
; ; Increase the index into the jump curve table. ;
[e4b1]@_continueJumpCurve: [e4b1]a6 a6LDX Player_JumpCurveIndex; Load the current jump timer. [e4b3]e8INX; Increase by 1. [e4b4]86 a6STX Player_JumpCurveIndex; Save it. [e4b6]@_return: [e4b6]60RTS
; ; Scroll to the screen above. ;
[e4b7]@_scrollScreenUp: [e4b7]a5 68LDA Area_ScreenAbove; Load the screen above. [e4b9]85 63STA Area_CurrentScreen; Set as the new screen. [e4bb]a2 02LDX #$02; X = 2 (Up direction). [e4bd]20 e9 e8JSR Area_ScrollTo; Scroll the screen up. [e4c0]c6 a2DEC Player_Something_ScrollPosY
; ; Place the player at the bottom of the screen and clear the ; jump state. ;
[e4c2]a9 c0LDA #$c0; Y = 0xC0 (first valid Y position at the bottom of a screen). [e4c4]85 a1STA Player_PosY; Set as the player's Y position. [e4c6]4c c9 e4JMP @_setLandedOnBlock; Mark the player as landed.
; ; Cap the player's Y position to the nearest rounded block ; position. ;
[e4c9]@_setLandedOnBlock: [e4c9]a5 a1LDA Player_PosY; Load the player's Y position. [e4cb]29 f0AND #$f0; Cap it to a rounded block position. [e4cd]85 a1STA Player_PosY; Set as the new Y position.
; ; Clear the Jumping flag. ;
[e4cf]a5 a4LDA Player_Flags; Load the player's flags. [e4d1]29 feAND #$fe; Clear the jumping flag. [e4d3]85 a4STA Player_Flags; Set the flags. [e4d5]60RTS
;============================================================================ ; A table mapping the jump curve. ; ; This maps a position into the jump curve ; (Player_JumpCurveIndex) to a Y delta to apply to the ; player's Y position. ; ; XREFS: ; Player_UpdateClimbOrJump ;============================================================================
; ; XREFS: ; Player_UpdateClimbOrJump ;
[e4d6]PLAYER_JUMP_CURVE: [e4d6]08.byte $08; [0]: Start of the jump. [e4d7]04.byte $04; [1]: [e4d8]04.byte $04; [2]: [e4d9]04.byte $04; [3]: [e4da]04.byte $04; [4]: [e4db]02.byte $02; [5]: [e4dc]02.byte $02; [6]: [e4dd]01.byte $01; [7]: [e4de]01.byte $01; [8]: [e4df]01.byte $01; [9]: [e4e0]01.byte $01; [10]: [e4e1]00.byte $00; [11]: [e4e2]00.byte $00; [12]: [e4e3]00.byte $00; [13]: [e4e4]00.byte $00; [14]: [e4e5]00.byte $00; [15]: [e4e6]00.byte $00; [16]: Midpoint of the jump. [e4e7]00.byte $00; [17]: [e4e8]00.byte $00; [18]: [e4e9]00.byte $00; [19]: [e4ea]00.byte $00; [20]: [e4eb]01.byte $01; [21]: [e4ec]01.byte $01; [22]: [e4ed]01.byte $01; [23]: [e4ee]01.byte $01; [24]: [e4ef]02.byte $02; [25]: [e4f0]02.byte $02; [26]: [e4f1]04.byte $04; [27]: [e4f2]04.byte $04; [28]: [e4f3]04.byte $04; [29]: [e4f4]04.byte $04; [30]: [e4f5]08.byte $08; [31]: End of the jump.
;============================================================================ ; TODO: Document Area_CheckClimbAdjacentImpassable ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_HandleVertLadderOrJump ;============================================================================
[e4f6]Area_CheckClimbAdjacentImpassable:
; ; Check if Left or Right is pressed. ;
[e4f6]a2 00LDX #$00; X = 0 (mapping table index). [e4f8]a5 19LDA Joy1_ChangedButtonMask; Load the controller 1 button mask. [e4fa]29 03AND #$03; If Left or Right pressed? [e4fc]f0 21BEQ @_returnFalse; If not, jump to return a false result. [e4fe]29 01AND #$01; Keep the Right Button bit. [e500]f0 01BEQ @_buildArgs [e502]e8INX; X = 1 (mapping table index).
; ; Determine an argument value for the X position to check. ; ; If pressing Left, this will be the player's rounded X ; block position. ; ; If pressing Right, this will be the next block to the ; right. ;
[e503]@_buildArgs: [e503]a5 9eLDA Player_PosX_Block; Load the player's X block position. [e505]18CLC [e506]7d 24 e5ADC a:CLIMB_ADJACENT_DIRECTION_TO_OFFSET,X; Add the offset based on the direction. No offset for Left, one block over for Right. [e509]85 b5STA Arg_PixelPosX; Store as the X position argument.
; ; Determine an argument for the Y position to check. ; ; This will be 2 blocks below the player's Y position, so ; the block under the player. ;
[e50b]a5 a1LDA Player_PosY; Load the player's Y position. [e50d]18CLC [e50e]69 20ADC #$20; Add 32 (2 blocks down). [e510]85 b6STA Arg_PixelPosY; Store as the Y position argument.
; ; Only perform the check if the result is the second row of ; blocks off-screen. ; ; NOTE: Which should not be possible. Let's break this down: ; ; 1. The last viable Y player position is 0xC0. Which, normalized ; with the + 0x20, is 0xE0. ; ; 2. Sticking the player's head just below the boundary of the ; screen would be 0xD0, or 0xF0 normalized. ; ; This is where the player would need to be, but... ; ; 3. The user would have already hit a screen transition ; in this case, or been capped at the boundary of the screen. ; ; So unless there's a particular case somewhere that allows the ; player to move under the screen (some kind of easter egg? Some ; kind of mechanic I can't think of?), this should never be able ; to pass the check. ; ; This makes me think that this boundary value was to turn off ; this logic as other climbing code was written. ;
[e512]c9 f0CMP #$f0; Is it < 0xF0? [e514]90 09BCC @_returnFalse; If so, return false.
; ; The player's within the sufficient bounds for a block check. ; Get the block and return the passable/impassable flag. ;
[e516]20 6c e8JSR Area_ConvertPixelsToBlockPos; Convert the argument X/Y position to a block position. [e519]20 7c e8JSR ScreenBuffer_IsBlockImpassable; Check if the block there is impassable. [e51c]85 b7STA Blocks_Result; Store it as the result to return. [e51e]60RTS [e51f]@_returnFalse: [e51f]a9 00LDA #$00; A = 0 (false). [e521]85 b7STA Blocks_Result; Store it as the result to return. [e523]60RTS
;============================================================================ ; Mapping of directions to block X offsets to check for adjacent climbing. ; ; XREFS: ; Area_CheckClimbAdjacentImpassable ;============================================================================
; ; XREFS: ; Area_CheckClimbAdjacentImpassable ;
[e524]CLIMB_ADJACENT_DIRECTION_TO_OFFSET: [e524]00.byte $00; [0]: Left
; ; XREFS: ; Area_CheckClimbAdjacentImpassable ;
[e525]CLIMB_ADJACENT_DIRECTION_TO_OFFSET_1_: [e525]0f.byte $0f; [1]: Right
;============================================================================ ; TODO: Document Player_CheckHandleEnterDoor ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; GameLoop_UpdatePlayer ;============================================================================
[e526]Player_CheckHandleEnterDoor: [e526]a5 19LDA Joy1_ChangedButtonMask [e528]29 08AND #$08 [e52a]f0 3cBEQ @_return [e52c]20 c5 e7JSR Area_SetStateFromDoorDestination; Set the new state based on the other end of the door. [e52f]a5 b7LDA Blocks_Result; Check the result of that. [e531]f0 35BEQ @_return [e533]a5 6bLDA Temp_BlockPos2 [e535]c9 feCMP #$fe [e537]b0 79BCS Player_EnterDoorToOutside
; ; The player is going inside. ;
[e539]20 2f ebJSR Game_RunDoorRequirementHandler [e53c]ad 2b 04LDA a:CurrentDoor_KeyRequirement [e53f]d0 27BNE @_return
; ; The door is not locked. ;
[e541]a5 6bLDA Temp_BlockPos2 [e543]c9 20CMP #$20 [e545]b0 30BCS Player_EnterDoorToInside [e547]20 2f daJSR Screen_FadeToBlack [e54a]a2 06LDX #$06 [e54c]@_paletteCheckLoop: [e54c]a5 65LDA Screen_DestPaletteOrIndex [e54e]dd 69 e5CMP a:Palette_ARRAY_PRG15_MIRROR__e569,X [e551]f0 05BEQ @_setupArea [e553]caDEX [e554]10 f6BPL @_paletteCheckLoop [e556]30 0dBMI @_enterScreen [e558]@_setupArea: [e558]bd 70 e5LDA a:Music_ARRAY_PRG15_MIRROR__e570,X [e55b]cd d1 03CMP a:Areas_DefaultMusic [e55e]f0 05BEQ @_enterScreen [e560]85 faSTA Music_Current [e562]8d d1 03STA a:Areas_DefaultMusic [e565]@_enterScreen: [e565]4c a0 daJMP Game_SetupEnterScreen [e568]@_return: [e568]60RTS
;============================================================================ ; 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. ; ; XREFS: ; Player_CheckHandleEnterDoor ;============================================================================
; ; XREFS: ; Player_CheckHandleEnterDoor ;
[e569]Palette_ARRAY_PRG15_MIRROR__e569: [e569]06.byte PALETTE_OUTSIDE; [0]: [e56a]07.byte PALETTE_TOWER; [1]: [e56b]0a.byte PALETTE_MIST; [2]: [e56c]0b.byte PALETTE_SUFFER; [3]: [e56d]0c.byte PALETTE_DARTMOOR; [4]: [e56e]0d.byte PALETTE_FRATERNAL; [5]: [e56f]0e.byte PALETTE_KING_GRIEVES_ROOM; [6]:
; ; XREFS: ; Player_CheckHandleEnterDoor ;
[e570]Music_ARRAY_PRG15_MIRROR__e570: [e570]03.byte MUSIC_APOLUNE; [0]: [e571]06.byte MUSIC_MAYBE_TOWER; [1]: [e572]05.byte MUSIC_FOREPAW; [2]: [e573]06.byte MUSIC_MAYBE_TOWER; [3]: [e574]02.byte MUSIC_DAYBREAK; [4]: [e575]06.byte MUSIC_MAYBE_TOWER; [5]: [e576]06.byte MUSIC_MAYBE_TOWER; [6]:
;============================================================================ ; TODO: Document Player_EnterDoorToInside ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_CheckHandleEnterDoor ;============================================================================
[e577]Player_EnterDoorToInside: [e577]a6 64LDX Area_LoadingScreenIndex [e579]8e da 03STX a:Area_DestScreen [e57c]a5 65LDA Screen_DestPaletteOrIndex [e57e]85 64STA Area_LoadingScreenIndex [e580]aaTAX [e581]bd 09 e6LDA a:BUILDING_PALETTES,X [e584]85 65STA Screen_DestPaletteOrIndex [e586]bd 13 e6LDA a:BUILDING_MAYBE_TILES_INDEXES,X [e589]8d d9 03STA a:Building_TilesIndex [e58c]bd 1d e6LDA a:BUILDING_START_POSITIONS,X [e58f]85 6cSTA Screen_StartPosYX [e591]ad d1 03LDA a:Areas_DefaultMusic [e594]8d d2 03STA a:Area_Music_Outside [e597]bd ff e5LDA a:BUILDING_MUSIC,X [e59a]8d d1 03STA a:Areas_DefaultMusic [e59d]a5 24LDA Area_CurrentArea
; ; Where does this door take the player? ;
[e59f]c9 04CMP #$04 [e5a1]d0 03BNE @LAB_PRG15_MIRROR__e5a6 [e5a3]4c 50 eaJMP Game_BeginExitBuilding [e5a6]@LAB_PRG15_MIRROR__e5a6: [e5a6]a5 a4LDA Player_Flags [e5a8]29 bfAND #$bf [e5aa]85 a4STA Player_Flags [e5ac]20 2f daJSR Screen_FadeToBlack [e5af]4c af daJMP Game_SetupEnterBuilding
;============================================================================ ; TODO: Document Player_EnterDoorToOutside ; ; INPUTS: ; A ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_CheckHandleEnterDoor ;============================================================================
[e5b2]Player_EnterDoorToOutside:
; ; The player opened a door back to the outside. ;
[e5b2]4aLSR A [e5b3]ad 35 04LDA a:Area_Region [e5b6]2aROL A [e5b7]48PHA [e5b8]a8TAY
; ; Check if the door is locked. ;
[e5b9]b9 db e5LDA a:DOOR_LOOKUP_REQUIREMENTS,Y [e5bc]8d 2b 04STA a:CurrentDoor_KeyRequirement [e5bf]20 2f ebJSR Game_RunDoorRequirementHandler [e5c2]68PLA [e5c3]a8TAY [e5c4]ad 2b 04LDA a:CurrentDoor_KeyRequirement [e5c7]d0 11BNE SUB_RETURN_E5DA
; ; This door is not locked. ;
[e5c9]b9 e7 e5LDA a:MAYBE_REGION_TRANSITION_MAP,Y [e5cc]8d 35 04STA a:Area_Region [e5cf]b9 f3 e5LDA a:MAYBE_SCREEN_TRANSITION_MAP,Y [e5d2]85 64STA Area_LoadingScreenIndex [e5d4]20 2f daJSR Screen_FadeToBlack [e5d7]4c dc daJMP Game_SetupAndLoadOutsideArea
; ; XREFS: ; Area_SetStateFromDoorDestination ; Player_EnterDoorToOutside ;
[e5da]SUB_RETURN_E5DA: [e5da]60RTS
; ; XREFS: ; Player_EnterDoorToOutside ;
[e5db]DOOR_LOOKUP_REQUIREMENTS: [e5db]00.byte $00; [0]: No key required [e5dc]04.byte $04; [1]: "J" key required [e5dd]00.byte $00; [2]: No key required [e5de]03.byte $03; [3]: "Q" key required [e5df]00.byte $00; [4]: No key required [e5e0]01.byte $01; [5]: "A" key required [e5e1]00.byte $00; [6]: No key required [e5e2]07.byte $07; [7]: Ring of Dworf required [e5e3]00.byte $00; [8]: No key required [e5e4]08.byte $08; [9]: Demon's Ring required [e5e5]00.byte $00; [10]: No key required [e5e6]00.byte $00; [11]: No key required
; ; XREFS: ; Player_EnterDoorToOutside ;
[e5e7]MAYBE_REGION_TRANSITION_MAP: [e5e7]00.byte REGION_EOLIS; [0]: [e5e8]01.byte REGION_TRUNK; [1]: [e5e9]00.byte REGION_EOLIS; [2]: [e5ea]02.byte REGION_MIST; [3]: [e5eb]01.byte REGION_TRUNK; [4]: [e5ec]03.byte REGION_BRANCH; [5]: [e5ed]02.byte REGION_MIST; [6]: [e5ee]04.byte REGION_DARTMOOR; [7]: [e5ef]03.byte REGION_BRANCH; [8]: [e5f0]05.byte REGION_EVIL_FORTRESS; [9]: [e5f1]04.byte REGION_DARTMOOR; [10]: [e5f2]05.byte REGION_EVIL_FORTRESS; [11]:
; ; XREFS: ; Player_EnterDoorToOutside ;
[e5f3]MAYBE_SCREEN_TRANSITION_MAP: [e5f3]00.byte $00; [0]: [e5f4]00.byte $00; [1]: [e5f5]08.byte $08; [2]: [e5f6]11.byte $11; [3]: [e5f7]28.byte $28; [4]: [e5f8]00.byte $00; [5]: [e5f9]1f.byte $1f; [6]: [e5fa]00.byte $00; [7]: [e5fb]27.byte $27; [8]: [e5fc]08.byte $08; [9]: [e5fd]0e.byte $0e; [10]: [e5fe]0e.byte $0e; [11]:
; ; XREFS: ; Player_EnterDoorToInside ;
[e5ff]BUILDING_MUSIC: [e5ff]0d.byte MUSIC_KINGS_ROOM; [0]: King's Room [e600]0e.byte MUSIC_TEMPLE; [1]: Temple [e601]0f.byte MUSIC_SHOP; [2]: Hospital [e602]0f.byte MUSIC_SHOP; [3]: Tavern [e603]0f.byte MUSIC_SHOP; [4]: Tool Shop [e604]0f.byte MUSIC_SHOP; [5]: Key Shop [e605]0f.byte MUSIC_SHOP; [6]: House [e606]0f.byte MUSIC_SHOP; [7]: Meat Shop [e607]0f.byte MUSIC_SHOP; [8]: Martial Arts [e608]0f.byte MUSIC_SHOP; [9]: Magic Shop
;============================================================================ ; Mapping of building indexes to palette indexes. ; ; XREFS: ; Player_EnterDoorToInside ;============================================================================
; ; XREFS: ; Player_EnterDoorToInside ;
[e609]BUILDING_PALETTES: [e609]11.byte PALETTE_KINGS_ROOM; [0]: King's Room [e60a]12.byte PALETTE_TEMPLE; [1]: Temple [e60b]13.byte PALETTE_HOSPITAL; [2]: Hospital [e60c]14.byte PALETTE_TAVERN; [3]: Tavern [e60d]15.byte PALETTE_TOOL_SHOP; [4]: Tool Shop [e60e]16.byte PALETTE_KEY_SHOP; [5]: Key Shop [e60f]17.byte PALETTE_HOUSE; [6]: House [e610]18.byte PALETTE_MEAT_SHOP; [7]: Meat Shop [e611]19.byte PALETTE_MARTIAL_ARTS; [8]: Martial Arts [e612]1a.byte PALETTE_MAGIC_SHOP; [9]: Magic Shop
; ; XREFS: ; Player_EnterDoorToInside ;
[e613]BUILDING_MAYBE_TILES_INDEXES: [e613]06.byte $06; [0]: King's Room [e614]06.byte $06; [1]: Temple [e615]06.byte $06; [2]: Hospital [e616]07.byte $07; [3]: Tavern [e617]07.byte $07; [4]: Tool Shop [e618]07.byte $07; [5]: Key Shop [e619]07.byte $07; [6]: House [e61a]07.byte $07; [7]: Meat Shop [e61b]08.byte $08; [8]: Martial Arts [e61c]08.byte $08; [9]: Magic Shop
; ; XREFS: ; Player_EnterDoorToInside ;
[e61d]BUILDING_START_POSITIONS: [e61d]9e.byte $9e; [0]: King's Room [e61e]9e.byte $9e; [1]: Temple [e61f]9e.byte $9e; [2]: Hospital [e620]8e.byte $8e; [3]: Tavern [e621]7e.byte $7e; [4]: Tool Shop [e622]7e.byte $7e; [5]: Key Shop [e623]7e.byte $7e; [6]: House [e624]7e.byte $7e; [7]: Meat Shop [e625]8e.byte $8e; [8]: Martial Arts [e626]8e.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: ; Player_CheckHandleClimbUp ; Player_HandleVertLadderOrJump ; Player_MoveDownScreen ; Player_TryMoveLeft ; Player_TryMoveRight ; Player_UpdateClimbOrJump ;============================================================================
[e627]Screen_IsEdge: [e627]b9 66 00LDA a:Area_ScreenToTheLeft,Y; Load the screen ID at the given neighbor. [e62a]c9 ffCMP #$ff; If 0xFF, C = 1. Else, C = 0 [e62c]60RTS
;============================================================================ ; TODO: Document Area_CanMoveUp ; ; INPUTS: ; None. ; ; OUTPUTS: ; A ; ; XREFS: ; Area_CanPlayerMoveRight ;============================================================================
[e62d]Area_CanMoveUp: [e62d]a5 a1LDA Player_PosY [e62f]4aLSR A [e630]4aLSR A [e631]4aLSR A [e632]4aLSR A [e633]aaTAX [e634]bc f6 03LDY a:FirstColumnInRightScreen,X [e637]20 7f e8JSR Area_IsBlockImpassable [e63a]85 b7STA Blocks_Result [e63c]e8INX [e63d]bc f6 03LDY a:FirstColumnInRightScreen,X [e640]20 7f e8JSR Area_IsBlockImpassable [e643]05 b7ORA Blocks_Result [e645]85 b7STA Blocks_Result [e647]a5 a1LDA Player_PosY [e649]29 0fAND #$0f [e64b]f0 0bBEQ @LAB_PRG15_MIRROR__e658 [e64d]e8INX [e64e]bc f6 03LDY a:FirstColumnInRightScreen,X [e651]20 7f e8JSR Area_IsBlockImpassable [e654]05 b7ORA Blocks_Result [e656]85 b7STA Blocks_Result [e658]@LAB_PRG15_MIRROR__e658: [e658]a5 b7LDA Blocks_Result [e65a]60RTS
;============================================================================ ; TODO: Document Area_CanPlayerMoveRight ; ; INPUTS: ; None. ; ; OUTPUTS: ; Z ; ; XREFS: ; Player_CheckIfPassable ;============================================================================
[e65b]Area_CanPlayerMoveRight: [e65b]a5 9eLDA Player_PosX_Block [e65d]18CLC [e65e]69 10ADC #$10 [e660]85 b5STA Arg_PixelPosX [e662]b0 c9BCS Area_CanMoveUp
; ; v-- Fall through --v ;
;============================================================================ ; TODO: Document Area_CanPlayerMoveAtY ; ; INPUTS: ; None. ; ; OUTPUTS: ; A ; ; XREFS: ; Area_CanPlayerMoveLeft ;============================================================================
[e664]Area_CanPlayerMoveAtY: [e664]a5 a1LDA Player_PosY [e666]85 b6STA Arg_PixelPosY [e668]20 6c e8JSR Area_ConvertPixelsToBlockPos [e66b]20 7c e8JSR ScreenBuffer_IsBlockImpassable [e66e]85 b7STA Blocks_Result [e670]8aTXA [e671]18CLC [e672]69 10ADC #$10 [e674]aaTAX [e675]20 7c e8JSR ScreenBuffer_IsBlockImpassable [e678]05 b7ORA Blocks_Result [e67a]85 b7STA Blocks_Result [e67c]a5 a1LDA Player_PosY [e67e]29 0fAND #$0f [e680]f0 0cBEQ @_returnResult [e682]8aTXA [e683]18CLC [e684]69 10ADC #$10 [e686]aaTAX [e687]20 7c e8JSR ScreenBuffer_IsBlockImpassable [e68a]05 b7ORA Blocks_Result [e68c]85 b7STA Blocks_Result [e68e]@_returnResult: [e68e]a5 b7LDA Blocks_Result [e690]60RTS
;============================================================================ ; TODO: Document Area_CanPlayerMoveLeft ; ; INPUTS: ; None. ; ; OUTPUTS: ; Z ; ; XREFS: ; Player_CheckIfPassable ;============================================================================
[e691]Area_CanPlayerMoveLeft: [e691]a5 9eLDA Player_PosX_Block [e693]38SEC [e694]e9 01SBC #$01 [e696]85 b5STA Arg_PixelPosX [e698]b0 caBCS Area_CanPlayerMoveAtY [e69a]a5 a1LDA Player_PosY [e69c]4aLSR A [e69d]4aLSR A [e69e]4aLSR A [e69f]4aLSR A [e6a0]aaTAX [e6a1]bc e6 03LDY a:LastColumnLeftScreen,X [e6a4]20 7f e8JSR Area_IsBlockImpassable [e6a7]85 b7STA Blocks_Result [e6a9]e8INX [e6aa]bc e6 03LDY a:LastColumnLeftScreen,X [e6ad]20 7f e8JSR Area_IsBlockImpassable [e6b0]05 b7ORA Blocks_Result [e6b2]85 b7STA Blocks_Result [e6b4]a5 a1LDA Player_PosY [e6b6]29 0fAND #$0f [e6b8]f0 0bBEQ @LAB_PRG15_MIRROR__e6c5 [e6ba]e8INX [e6bb]bc e6 03LDY a:LastColumnLeftScreen,X [e6be]20 7f e8JSR Area_IsBlockImpassable [e6c1]05 b7ORA Blocks_Result [e6c3]85 b7STA Blocks_Result [e6c5]@LAB_PRG15_MIRROR__e6c5: [e6c5]a5 b7LDA Blocks_Result [e6c7]60RTS
;============================================================================ ; TODO: Document Player_CheckIfPassable ; ; INPUTS: ; X ; ; OUTPUTS: ; Z ; ; XREFS: ; Player_CheckHandleClimbDown ; Player_CheckHandleClimbUp ; Player_HandleVertLadderOrJump ; Player_TryMoveLeft ; Player_TryMoveRight ; Player_UpdateClimbOrJump ;============================================================================
[e6c8]Player_CheckIfPassable: [e6c8]8aTXA [e6c9]f0 c6BEQ Area_CanPlayerMoveLeft [e6cb]caDEX [e6cc]f0 8dBEQ Area_CanPlayerMoveRight [e6ce]caDEX [e6cf]f0 53BEQ Area_CanPlayerMoveUp [e6d1]a5 a1LDA Player_PosY [e6d3]18CLC [e6d4]69 20ADC #$20 [e6d6]85 b6STA Arg_PixelPosY [e6d8]c9 d0CMP #$d0 [e6da]90 23BCC Area_CanMoveImmediatelyRight [e6dc]a5 9eLDA Player_PosX_Block [e6de]4aLSR A [e6df]4aLSR A [e6e0]4aLSR A [e6e1]4aLSR A [e6e2]aaTAX [e6e3]bc 16 04LDY a:FirstRowBelowScreen,X [e6e6]20 7f e8JSR Area_IsBlockImpassable [e6e9]85 b7STA Blocks_Result [e6eb]a5 9eLDA Player_PosX_Block [e6ed]29 0fAND #$0f [e6ef]f0 0bBEQ @_returnResult [e6f1]e8INX [e6f2]bc 16 04LDY a:FirstRowBelowScreen,X [e6f5]20 7f e8JSR Area_IsBlockImpassable [e6f8]05 b7ORA Blocks_Result [e6fa]85 b7STA Blocks_Result [e6fc]@_returnResult: [e6fc]a5 b7LDA Blocks_Result [e6fe]60RTS
;============================================================================ ; TODO: Document Area_CanMoveImmediatelyRight ; ; INPUTS: ; None. ; ; OUTPUTS: ; A ; ; XREFS: ; Area_CanPlayerMoveUp ; Player_CheckIfPassable ;============================================================================
[e6ff]Area_CanMoveImmediatelyRight: [e6ff]a5 9eLDA Player_PosX_Block [e701]18CLC [e702]69 04ADC #$04 [e704]85 b5STA Arg_PixelPosX [e706]20 6c e8JSR Area_ConvertPixelsToBlockPos [e709]20 7c e8JSR ScreenBuffer_IsBlockImpassable [e70c]85 b7STA Blocks_Result [e70e]a5 9eLDA Player_PosX_Block [e710]29 0fAND #$0f [e712]38SEC [e713]e9 04SBC #$04 [e715]c9 08CMP #$08 [e717]b0 08BCS @_returnResult [e719]e8INX [e71a]20 7c e8JSR ScreenBuffer_IsBlockImpassable [e71d]05 b7ORA Blocks_Result [e71f]85 b7STA Blocks_Result [e721]@_returnResult: [e721]a5 b7LDA Blocks_Result [e723]60RTS
;============================================================================ ; TODO: Document Area_CanPlayerMoveUp ; ; INPUTS: ; None. ; ; OUTPUTS: ; Z ; ; XREFS: ; Player_CheckIfPassable ;============================================================================
[e724]Area_CanPlayerMoveUp: [e724]a5 a1LDA Player_PosY [e726]38SEC [e727]e9 01SBC #$01 [e729]85 b6STA Arg_PixelPosY [e72b]c9 f0CMP #$f0 [e72d]90 d0BCC Area_CanMoveImmediatelyRight [e72f]a5 9eLDA Player_PosX_Block [e731]4aLSR A [e732]4aLSR A [e733]4aLSR A [e734]4aLSR A [e735]aaTAX [e736]bc 06 04LDY a:LastRowAboveScreen,X [e739]20 7f e8JSR Area_IsBlockImpassable [e73c]85 b7STA Blocks_Result [e73e]a5 9eLDA Player_PosX_Block [e740]29 0fAND #$0f [e742]f0 0bBEQ @_returnResult [e744]e8INX [e745]bc 06 04LDY a:LastRowAboveScreen,X [e748]20 7f e8JSR Area_IsBlockImpassable [e74b]05 b7ORA Blocks_Result [e74d]85 b7STA Blocks_Result [e74f]@_returnResult: [e74f]a5 b7LDA Blocks_Result [e751]60RTS
;============================================================================ ; TODO: Document Player_CheckIfOnLadder ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_CheckHandleVertMovement ;============================================================================
[e752]Player_CheckIfOnLadder:
; ; 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]a5 9eLDA Player_PosX_Block; A = player X position [e754]18CLC [e755]69 07ADC #$07; Add 7. [e757]85 b5STA Arg_PixelPosX; Store the player's new X pixel position [e759]a5 a1LDA Player_PosY; Load the player's Y position [e75b]85 b6STA Arg_PixelPosY; Store the player's Y pixel position
; ; Check if the block at this position is climbable. ;
[e75d]20 6c e8JSR Area_ConvertPixelsToBlockPos; Convert to a block position. [e760]bc 00 06LDY a:ScreenBuffer,X; Load the block at that position. [e763]20 c6 e8JSR Area_GetBlockProperty; Load the block property for that block. [e766]85 b7STA Blocks_Result; Store it. [e768]20 b2 e8JSR Area_IsBlockClimbable; Check if it's climbable. [e76b]b0 45BCS @_resetCanClimb
; ; The block is not climbable. Check if the player is ; overlapping a block to the right, and check that. ;
[e76d]a5 9eLDA Player_PosX_Block; A = player X position. [e76f]29 0fAND #$0f [e771]c9 08CMP #$08 [e773]d0 0eBNE @_checkBlockBelow [e775]e8INX [e776]bc 00 06LDY a:ScreenBuffer,X [e779]20 c6 e8JSR Area_GetBlockProperty [e77c]85 b7STA Blocks_Result [e77e]20 b2 e8JSR Area_IsBlockClimbable [e781]b0 2fBCS @_resetCanClimb [e783]@_checkBlockBelow: [e783]a5 9eLDA Player_PosX_Block [e785]18CLC [e786]69 07ADC #$07 [e788]85 b5STA Arg_PixelPosX [e78a]a5 a1LDA Player_PosY [e78c]18CLC [e78d]69 1fADC #$1f [e78f]85 b6STA Arg_PixelPosY [e791]20 6c e8JSR Area_ConvertPixelsToBlockPos [e794]bc 00 06LDY a:ScreenBuffer,X [e797]20 c6 e8JSR Area_GetBlockProperty [e79a]85 b7STA Blocks_Result [e79c]20 b2 e8JSR Area_IsBlockClimbable [e79f]b0 11BCS @_resetCanClimb [e7a1]a5 9eLDA Player_PosX_Block [e7a3]29 0fAND #$0f [e7a5]c9 08CMP #$08 [e7a7]d0 09BNE @_resetCanClimb [e7a9]e8INX [e7aa]bc 00 06LDY a:ScreenBuffer,X [e7ad]20 c6 e8JSR Area_GetBlockProperty [e7b0]85 b7STA Blocks_Result
; ; Reset the can-climb bit. ;
[e7b2]@_resetCanClimb: [e7b2]a5 a4LDA Player_Flags; Load the player's flags. [e7b4]29 f7AND #$f7; Clear the can-climb bit. [e7b6]20 b2 e8JSR Area_IsBlockClimbable; Is the current block climbable? [e7b9]90 05BCC @_clearBit5AndReturn; If not, jump.
; ; Set the can-climb bit. ;
[e7bb]09 08ORA #$08; Else, set the can-climbable bit. [e7bd]85 a4STA Player_Flags; Set the ladder flag bit [e7bf]60RTS [e7c0]@_clearBit5AndReturn: [e7c0]29 efAND #$ef; Clear bit 5. [e7c2]85 a4STA Player_Flags; Store it. [e7c4]60RTS
;============================================================================ ; 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:
; ; Convert the player position to a normalized block position. ;
[e7c5]a5 9eLDA Player_PosX_Block; Load the player's full X position. [e7c7]18CLC [e7c8]69 07ADC #$07; Add 7. [e7ca]85 b5STA Arg_PixelPosX; Store as the X pixel position argument. [e7cc]a5 a1LDA Player_PosY; Load the player's full Y position. [e7ce]85 b6STA Arg_PixelPosY; Store as the Y pixel position argument. [e7d0]20 6c e8JSR Area_ConvertPixelsToBlockPos; Conver to block positions.
; ; Load the block at this position from the screen and ; check what it is. ;
[e7d3]bc 00 06LDY a:ScreenBuffer,X; Load the block from the screen buffer. [e7d6]20 c6 e8JSR Area_GetBlockProperty; Get the block property. [e7d9]c9 03CMP #$03; Is it a door? [e7db]f0 18BEQ @_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]a5 9eLDA Player_PosX_Block; Load the player's full X position. [e7df]29 0fAND #$0f; Check the lower nibble. [e7e1]c9 08CMP #$08; Is it on a full block boundary? [e7e3]d0 0bBNE @_blockIsNotDoor; _blockIsAir [e7e5]e8INX; Increment the block position.
; ; We normalized to the next overlapped block. Now check ; to see if it's a door. ;
[e7e6]bc 00 06LDY a:ScreenBuffer,X; Load the block at that position. [e7e9]20 c6 e8JSR Area_GetBlockProperty; Load the block property. [e7ec]c9 03CMP #$03; Is it a door? [e7ee]f0 05BEQ @_blockIsDoor; If so, jump.
; ; The player was not standing in front of a door. ; Consider this block passable. ;
[e7f0]@_blockIsNotDoor: [e7f0]a9 00LDA #$00 [e7f2]85 b7STA Blocks_Result; Set the resulting block property as air. [e7f4]60RTS
; ; The player is standing in front of a door. ; ; Check to see where they are. ;
[e7f5]@_blockIsDoor: [e7f5]a5 24LDA Area_CurrentArea; Load the current region. [e7f7]c9 04CMP #$04; Is this the area around Victim(?) ? [e7f9]d0 03BNE @LAB_PRG15_MIRROR__e7fe; If not, jump and continue on.
; ; We won't be handling anything in this area. We're done here. ;
[e7fb]4c da e5JMP SUB_RETURN_E5DA; Else, we're done. [e7fe]@LAB_PRG15_MIRROR__e7fe: [e7fe]a9 01LDA #$01 [e800]85 b7STA Blocks_Result; Mark the resulting block as solid by default. [e802]86 6aSTX 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]a5 8dLDA CurrentArea_DoorLocationsAddr; Load the lower byte of the doors address. [e806]85 02STA Temp_Addr_L; Store it temporarily. [e808]a5 8eLDA CurrentArea_DoorLocationsAddr_U; Load the upper byte. [e80a]85 03STA Temp_Addr_U; Store it temporarily.
; ; Switch to Bank 3 (level data). ;
[e80c]ad 00 01LDA a:CurrentROMBank; Load the current ROM bank. [e80f]48PHA; Push it to the stack. [e810]a2 03LDX #$03 [e812]20 1a ccJSR 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]a0 00LDY #$00; Y = 0 [e817]b1 02LDA (Temp_Addr_L),Y; Load the first byte from the referenced address. [e819]c9 ffCMP #$ff; Is it 0xFF? [e81b]f0 45BEQ @_returnAirBlock; If so, consider this air and return. [e81d]c5 63CMP Area_CurrentScreen; Is it the current screen index? [e81f]d0 31BNE @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]c8INY; Y++ [e822]b1 02LDA (Temp_Addr_L),Y; Load the next byte from the referenced address. [e824]c5 6aCMP Temp_BlockPos; Is it the block position we stored? [e826]d0 2aBNE @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]c8INY; Y++ [e829]b1 02LDA (Temp_Addr_L),Y; Load the next byte from the referenced address. [e82b]85 6bSTA 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]c8INY; Y++ [e82e]b1 02LDA (Temp_Addr_L),Y; Load the next byte from the referenced address. [e830]85 6cSTA Screen_StartPosYX; Store as the area's starting X/Y position. [e832]a5 6bLDA 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]a4 24LDY Area_CurrentArea; Y = current area [e836]c0 03CPY #$03; Is the current area 3? [e838]d0 03BNE @LAB_PRG15_MIRROR__e83d; If not, jump.
; ; We'll need to normalize the block position. ;
[e83a]38SEC [e83b]e9 20SBC #$20; Reduce it by 32. [e83d]@LAB_PRG15_MIRROR__e83d: [e83d]0aASL A; Multiply the block position by 4. [e83e]0aASL A
; ; Load state from the door destination at this block. ; ; We'll load the screen index, palette, and the key ; requirement. ;
[e83f]a8TAY; Y = A (block position) [e840]b1 8fLDA (CurrentArea_DoorDestinationsAddr),Y; Load the screen index from the door destination. [e842]85 64STA Area_LoadingScreenIndex; Store it as the new screen index. [e844]c8INY; Y++ [e845]b1 8fLDA (CurrentArea_DoorDestinationsAddr),Y; Load the palette from the door destination. [e847]85 65STA Screen_DestPaletteOrIndex; Store it as the new palette. [e849]c8INY; Y++ [e84a]b1 8fLDA (CurrentArea_DoorDestinationsAddr),Y; Load the key requirement. [e84c]8d 2b 04STA a:CurrentDoor_KeyRequirement; Store it. [e84f]4c 66 e8JMP @_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]a5 02LDA Temp_Addr_L; Load the lower byte of the door destination address we stored. [e854]18CLC [e855]69 04ADC #$04; Add 4 (skip the information found on the other side of that door). [e857]85 02STA Temp_Addr_L; Store it back out. [e859]a5 03LDA Temp_Addr_U; Load the upper byte of the address. [e85b]69 00ADC #$00; Add the carry flag, if the lower byte overflowed. [e85d]85 03STA Temp_Addr_U; Store it. [e85f]4c 15 e8JMP @_doorCheckLoop
; ; Consider this air. ;
[e862]@_returnAirBlock: [e862]a9 00LDA #$00 [e864]85 b7STA Blocks_Result; Set the resulting block property to 0 (air).
; ; Restore the previous ROM bank and return. ;
[e866]@_restoreBankAndReturn: [e866]68PLA; Pop the previous bank from the stack. [e867]aaTAX; X = A (bank) [e868]20 1a ccJSR MMC1_UpdateROMBank; Update to the bank. [e86b]60RTS
;============================================================================ ; 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 ; Player_CalculateVisibility ; SpriteBehavior_NecronAides ; Sprites_CheckPlacementInvalidAtY ; Sprites_MoveRight__86c6 ; Area_CanMoveImmediatelyRight ; Area_CanPlayerMoveAtY ; Area_CheckClimbAdjacentImpassable ; Area_SetStateFromDoorDestination ; CastMagic_CalculateVisibility ; Player_CheckIfOnLadder ; Player_CheckOnBreakableBlock ; Player_CheckPushingBlock ; Player_CheckSwitchScreen ; Player_UseMattock ;============================================================================
[e86c]Area_ConvertPixelsToBlockPos: [e86c]a5 b6LDA Arg_PixelPosY; Load the stored Y coordinate for comparison [e86e]29 f0AND #$f0; Clear out the lower nibble [e870]85 00STA Temp_00 [e872]a5 b5LDA Arg_PixelPosX; Load the stored X coordinate for comparison [e874]4aLSR A; Convert X to block positions [e875]4aLSR A [e876]4aLSR A [e877]4aLSR A [e878]05 00ORA Temp_00; Include the Y block position [e87a]aaTAX; Store in X and return [e87b]60RTS
;============================================================================ ; 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_CheckClimbAdjacentImpassable ;============================================================================
[e87c]ScreenBuffer_IsBlockImpassable: [e87c]bc 00 06LDY a: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]20 c6 e8JSR 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]a8TAY; Y = A [e883]b9 d9 e8LDA a:BLOCK_PROPERTY_IMPASSABLE_MAP,Y; A = Impassable flag for the block property type. [e886]60RTS; 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: ; Sprites_CheckPlacementInvalidAtY ;============================================================================
[e887]Area_IsBlockImpassableOrLadder: [e887]bc 00 06LDY a:ScreenBuffer,X; Load the block property offset from the screen buffer at X. [e88a]20 c6 e8JSR Area_GetBlockProperty; Load the block property. [e88d]c9 02CMP #$02; Is it a ladder? [e88f]d0 f1BNE Area_GetFromImpassableMap; If not a ladder, look up from the passable map.
; ; This is a ladder. ;
[e891]a9 01LDA #$01; Return true. [e893]60RTS
;============================================================================ ; 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:
; ; Load the block property at this screen buffer offset. ;
[e894]bc 00 06LDY a:ScreenBuffer,X; Y = block property at screen buffer X [e897]20 c6 e8JSR Area_GetBlockProperty; Load the block property. [e89a]85 b7STA Blocks_Result; Store that in a temporary variable.
; ; Check if this is a solid block. ;
[e89c]c9 01CMP #$01; Is it solid? [e89e]f0 0dBEQ @_blockIsSolid; If so, then jump.
; ; Check if this is a ladder. ;
[e8a0]c9 02CMP #$02; Is it a ladder? [e8a2]f0 09BEQ @_blockIsSolid; If so, then jump.
; ; Check if this is a block transitioning to another screen. ;
[e8a4]c9 0aCMP #$0a; Is this a transition to a screen? [e8a6]f0 05BEQ @_blockIsSolid; If so, then jump.
; ; It's not solid. Store a result of 0 (air). ;
[e8a8]a9 00LDA #$00 [e8aa]85 b7STA Blocks_Result; Set result = 0. [e8ac]60RTS
; ; It is solid. Store a result of 1 (solid). ;
[e8ad]@_blockIsSolid: [e8ad]a9 01LDA #$01 [e8af]85 b7STA Blocks_Result; Set result = 1. [e8b1]60RTS
;============================================================================ ; 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]48PHA; Push A to the stack. [e8b3]a5 b7LDA Blocks_Result; Load the block property provided in the temp variable. [e8b5]c9 02CMP #$02; Is it a ladder? [e8b7]f0 04BEQ @_isLadder; If so, jump. [e8b9]c9 0aCMP #$0a; TODO: Is it... something else ladder-like? [e8bb]d0 03BNE @_isNotLadder; If not, jump.
; ; This is a ladder. ;
[e8bd]@_isLadder: [e8bd]68PLA; Pop A. [e8be]38SEC; Set carry to 1 as a truthy result. [e8bf]60RTS [e8c0]@_isNotLadder: [e8c0]68PLA; Pop A. [e8c1]18CLC; Set carry to 0 as a falsy result. [e8c2]60RTS
;============================================================================ ; 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]bc 00 06LDY a: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]98TYA; A = Y [e8c7]4aLSR A; Y = even block offset based on the index. [e8c8]a8TAY; Y = A [e8c9]90 08BCC @_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]b9 3c 04LDA a:BlockProperties,Y; Load the block property at the index. [e8ce]4aLSR A; Shift to the lower nibble. [e8cf]4aLSR A [e8d0]4aLSR A [e8d1]4aLSR A [e8d2]60RTS
; ; Load the block property, masking out the upper nibble. ;
[e8d3]@_isEven: [e8d3]b9 3c 04LDA a:BlockProperties,Y; Load the block property at the index. [e8d6]29 0fAND #$0f; Mask out the upper nibble, giving just the lower. [e8d8]60RTS
;============================================================================ ; Map of block property IDs to impassibility flags. ; ; XREFS: ; Area_GetFromImpassableMap ;============================================================================
; ; XREFS: ; Area_GetFromImpassableMap ;
[e8d9]BLOCK_PROPERTY_IMPASSABLE_MAP: [e8d9]00.byte $00; [0]: Air [e8da]01.byte $01; [1]: Solid [e8db]00.byte $00; [2]: Ladder [e8dc]00.byte $00; [3]: Door [e8dd]00.byte $00; [4]: Foreground [e8de]01.byte $01; [5]: Breakable floor [e8df]01.byte $01; [6]: Pushable [e8e0]01.byte $01; [7]: ?? [e8e1]01.byte $01; [8]: ?? [e8e2]00.byte $00; [9]: Maybe: Transition down [e8e3]00.byte $00; [10]: Maybe: Transition up [e8e4]01.byte $01; [11]: Breakable by Mattock [e8e5]00.byte $00; [12]: Area transition left-to-right [e8e6]00.byte $00; [13]: Area transition right-to-left [e8e7]00.byte $00; [14]: ?? [e8e8]00.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_LoadScrollData ; ; XREFS: ; Player_CheckHandleClimbUp ; Player_HandleVertLadderOrJump ; Player_MoveDownScreen ; Player_TryMoveLeft ; Player_TryMoveRight ; Player_UpdateClimbOrJump ;============================================================================
[e8e9]Area_ScrollTo: [e8e9]ad 2e 04LDA a:CurrentScreen_SpecialEventID; Load the current screen's event ID. [e8ec]29 7fAND #$7f; Keep the numeric ID. [e8ee]c9 01CMP #$01; Is it 1 (boss room)? [e8f0]d0 05BNE @_clearStates; If not, jump.
; ; The player is scrolling from a boss room. Reset the music ; back to the default music for the area. ;
[e8f2]ad d1 03LDA a:Areas_DefaultMusic; Load the default music for the area. [e8f5]85 faSTA Music_Current; Set as the current music. [e8f7]@_clearStates: [e8f7]a9 00LDA #$00; A = 0 [e8f9]85 a3STA Player_MovementTick; Set player movement tick to 0. [e8fb]85 1aSTA InterruptCounter; Set interrupt counter to 0.
; ; Clear any cast magic. ;
[e8fd]a9 ffLDA #$ff; A = 0xFF (unset) [e8ff]8d b3 02STA a:CastMagic_Type; Set as the magic type on screen. [e902]4c 27 d1JMP Area_LoadScrollData; 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:
; ; Only check for falling periodically. ;
[e905]a5 1aLDA InterruptCounter; Load the interrupt counter. [e907]29 07AND #$07; Are we ready to check for falling? [e909]d0 4fBNE @_return; If not, return.
; ; Begin checking against a block 32 pixels down. ;
[e90b]a5 a1LDA Player_PosY; A = Player's Y position. [e90d]18CLC [e90e]69 20ADC #$20; A += 32 [e910]85 b6STA Arg_PixelPosY; Store as the Y argument for block checks. [e912]c9 f0CMP #$f0; Is it >= 0xEF? (one block below the screen) [e914]b0 44BCS @_return; If so, stop falling.
; ; The player may be permitted to fall. Check the block ; it's on. ;
[e916]a5 a4LDA Player_Flags; Load the player's flags. [e918]29 40AND #$40; Keep only the facing bit. [e91a]2aROL A; Shift this into bit 0. [e91b]2aROL A [e91c]2aROL A [e91d]29 01AND #$01; Mask out everything else. [e91f]48PHA; Push it to the stack.
; ; Check the first block the player is overlapping. ;
[e920]aaTAX; X = facing value. [e921]a5 9eLDA Player_PosX_Block; A = Player X position. [e923]18CLC [e924]7d 5b e9ADC a:@_CHECK_BREAKABLE_BLOCK_X_OFFSETS,X; Load a block offset to round to the nearest overlapping blocks. [e927]85 b5STA Arg_PixelPosX; Store as the X position to check. [e929]20 6c e8JSR Area_ConvertPixelsToBlockPos; Convert it to a block position. [e92c]bc 00 06LDY a:ScreenBuffer,X; Load the block value from the screen. [e92f]84 b7STY Blocks_Result; Store as the block result. [e931]20 c6 e8JSR Area_GetBlockProperty; And get its corresponding property. [e934]c9 05CMP #$05; Is it 5 (breakable floor)? [e936]d0 04BNE @_checkNextBlock; If not, check the next overlapping block. [e938]68PLA; Pop the facing bit from the stack. [e939]4c ce d6JMP Area_HandleBreakableFloor; Handle breakable floor logic.
; ; Check the second block the player is overlapping. ;
[e93c]@_checkNextBlock: [e93c]68PLA; Pop the facing bit from the stack. [e93d]49 01EOR #$01; XOR the facing bit to check the other direction. [e93f]aaTAX; X = A [e940]a5 9eLDA Player_PosX_Block; A = Player X position. [e942]18CLC [e943]7d 5b e9ADC a:@_CHECK_BREAKABLE_BLOCK_X_OFFSETS,X; Load a block offset to round to the next overlapping blocks. [e946]85 b5STA Arg_PixelPosX; Store as the X position to check. [e948]20 6c e8JSR Area_ConvertPixelsToBlockPos; Convert it to a block position. [e94b]bc 00 06LDY a:ScreenBuffer,X; Load the block value from the screen. [e94e]84 b7STY Blocks_Result; Store as the block result. [e950]20 c6 e8JSR Area_GetBlockProperty [e953]c9 05CMP #$05; Is it 5 (breakable floor)? [e955]d0 03BNE @_return; If not, return. [e957]4c ce d6JMP Area_HandleBreakableFloor; Handle breakable floor logic. [e95a]@_return: [e95a]60RTS
;============================================================================ ; Map of facing bit values to block X offsets for checking breakable blocks. ; ; XREFS: ; Player_CheckOnBreakableBlock ;============================================================================
[e95b]@_CHECK_BREAKABLE_BLOCK_X_OFFSETS: [e95b]04.byte $04; [0]: Facing left [e95c]0c.byte $0c; [1]: Facing right
;============================================================================ ; TODO: Document Player_CheckPushingBlock ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; GameLoop_UpdatePlayer ;============================================================================
[e95d]Player_CheckPushingBlock: [e95d]a5 16LDA Joy1_ButtonMask [e95f]29 03AND #$03 [e961]f0 56BEQ @LAB_PRG15_MIRROR__e9b9 [e963]ad 2c 04LDA a:SpecialItems [e966]29 40AND #$40 [e968]f0 4fBEQ @LAB_PRG15_MIRROR__e9b9 [e96a]ad 2d 04LDA a:Quests [e96d]29 07AND #$07 [e96f]c9 07CMP #$07 [e971]d0 46BNE @LAB_PRG15_MIRROR__e9b9 [e973]a5 a4LDA Player_Flags [e975]29 40AND #$40 [e977]2aROL A [e978]2aROL A [e979]2aROL A [e97a]29 01AND #$01 [e97c]aaTAX [e97d]a5 a1LDA Player_PosY [e97f]85 b6STA Arg_PixelPosY [e981]a5 9eLDA Player_PosX_Block [e983]18CLC [e984]7d be e9ADC a:BYTE_ARRAY_PRG15_MIRROR__e9be,X [e987]85 b5STA Arg_PixelPosX
; ; Fetch the block property at this position. ;
[e989]20 6c e8JSR Area_ConvertPixelsToBlockPos [e98c]bc 00 06LDY a:ScreenBuffer,X [e98f]20 c6 e8JSR Area_GetBlockProperty
; ; Check if the block is pushable. If so, we'll handle it. ;
[e992]c9 06CMP #$06 [e994]d0 23BNE @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]e6 d7INC BlockPushCounter [e998]a5 d7LDA BlockPushCounter [e99a]c9 3fCMP #$3f [e99c]d0 05BNE @LAB_PRG15_MIRROR__e9a3 [e99e]a9 0fLDA #$0f [e9a0]20 e4 d0JSR Sound_PlayEffect [e9a3]@LAB_PRG15_MIRROR__e9a3: [e9a3]a5 d7LDA BlockPushCounter [e9a5]c9 60CMP #$60 [e9a7]90 0fBCC @_return [e9a9]a9 01LDA #$01 [e9ab]85 d4STA PathToMascon_Opening [e9ad]86 d5STX PathToMascon_FountainCoverPos [e9af]a5 16LDA Joy1_ButtonMask [e9b1]4aLSR A [e9b2]29 01AND #$01 [e9b4]aaTAX [e9b5]4c f5 d6JMP Game_OpenPathToMascon [e9b8]@_return: [e9b8]60RTS [e9b9]@LAB_PRG15_MIRROR__e9b9: [e9b9]a9 00LDA #$00 [e9bb]85 d7STA BlockPushCounter [e9bd]60RTS
; ; XREFS: ; Player_CheckPushingBlock ;
[e9be]BYTE_ARRAY_PRG15_MIRROR__e9be: [e9be]ff.byte $ff; [0]: [e9bf]10.byte $10; [1]:
;============================================================================ ; TODO: Document Player_CheckSwitchScreen ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; GameLoop_UpdatePlayer ;============================================================================
[e9c0]Player_CheckSwitchScreen:
; ; Check the current block the player is on. ;
[e9c0]a5 a1LDA Player_PosY [e9c2]85 b6STA Arg_PixelPosY [e9c4]a5 9eLDA Player_PosX_Block [e9c6]85 b5STA Arg_PixelPosX [e9c8]20 6c e8JSR Area_ConvertPixelsToBlockPos [e9cb]20 c3 e8JSR ScreenBuffer_LoadBlockProperty [e9ce]c9 09CMP #$09 [e9d0]f0 0cBEQ @LAB_PRG15_MIRROR__e9de [e9d2]c9 0aCMP #$0a [e9d4]f0 08BEQ @LAB_PRG15_MIRROR__e9de [e9d6]c9 0dCMP #$0d [e9d8]f0 04BEQ @LAB_PRG15_MIRROR__e9de [e9da]c9 0cCMP #$0c [e9dc]d0 58BNE @_return
; ; Check the next block. ;
[e9de]@LAB_PRG15_MIRROR__e9de: [e9de]a5 9eLDA Player_PosX_Block [e9e0]18CLC [e9e1]69 0fADC #$0f [e9e3]85 b5STA Arg_PixelPosX [e9e5]20 6c e8JSR Area_ConvertPixelsToBlockPos [e9e8]20 c3 e8JSR ScreenBuffer_LoadBlockProperty [e9eb]c9 09CMP #$09 [e9ed]f0 0cBEQ @LAB_PRG15_MIRROR__e9fb [e9ef]c9 0aCMP #$0a [e9f1]f0 08BEQ @LAB_PRG15_MIRROR__e9fb [e9f3]c9 0dCMP #$0d [e9f5]f0 04BEQ @LAB_PRG15_MIRROR__e9fb [e9f7]c9 0cCMP #$0c [e9f9]d0 3bBNE @_return
; ; Check if this is a horizontal transition block. If so, ; switch the area. ;
[e9fb]@LAB_PRG15_MIRROR__e9fb: [e9fb]c9 0cCMP #$0c [e9fd]f0 60BEQ Player_CheckSwitchScreen_SwitchAreaHoriz [e9ff]c9 0dCMP #$0d [ea01]f0 5cBEQ Player_CheckSwitchScreen_SwitchAreaHoriz
; ; This is a vertical transition block. Switch the ; area. ;
[ea03]a5 24LDA Area_CurrentArea [ea05]0aASL A [ea06]aaTAX [ea07]bd 37 eaLDA a:AREA_SCREEN_COMPARATORS,X [ea0a]85 02STA Temp_Addr_L [ea0c]bd 38 eaLDA a:AREA_SCREEN_COMPARATORS+1,X [ea0f]85 03STA Temp_Addr_U [ea11]a0 00LDY #$00 [ea13]@LAB_PRG15_MIRROR__ea13: [ea13]b1 02LDA (Temp_Addr_L),Y [ea15]c9 ffCMP #$ff [ea17]f0 1dBEQ @_return [ea19]c5 63CMP Area_CurrentScreen [ea1b]d0 12BNE @LAB_PRG15_MIRROR__ea2f [ea1d]c8INY [ea1e]b1 02LDA (Temp_Addr_L),Y [ea20]85 64STA Area_LoadingScreenIndex [ea22]c8INY [ea23]b1 02LDA (Temp_Addr_L),Y [ea25]85 6cSTA Screen_StartPosYX [ea27]c8INY [ea28]b1 02LDA (Temp_Addr_L),Y [ea2a]85 65STA Screen_DestPaletteOrIndex [ea2c]4c a0 daJMP Game_SetupEnterScreen [ea2f]@LAB_PRG15_MIRROR__ea2f: [ea2f]c8INY [ea30]c8INY [ea31]c8INY [ea32]c8INY [ea33]4c 13 eaJMP @LAB_PRG15_MIRROR__ea13 [ea36]@_return: [ea36]60RTS
; ; XREFS: ; Player_CheckSwitchScreen ;
[ea37]AREA_SCREEN_COMPARATORS: [ea37]4f eapointer AREA_SCREEN_COMPARATOR_END; Eolis [ea39]47 eapointer AREA_SCREEN_COMPARATOR_APOLUNE; Apolune [ea3b]4f eapointer AREA_SCREEN_COMPARATOR_END; Forepaw [ea3d]4f eapointer AREA_SCREEN_COMPARATOR_END; Mascon [ea3f]4f eapointer AREA_SCREEN_COMPARATOR_END; Victim [ea41]4f eapointer AREA_SCREEN_COMPARATOR_END; Conflate [ea43]4f eapointer AREA_SCREEN_COMPARATOR_END; Daybreak [ea45]4f eapointer AREA_SCREEN_COMPARATOR_END; Evil Fortress [ea47]AREA_SCREEN_COMPARATOR_APOLUNE: [ea47]0c.byte $0c; Current screen comparator [ea48]16.byte $16; New screen index [ea49]b3.byte $b3; Y, X [ea4a]06.byte $06; Area [ea4b]16.byte $16; Current screen comparator [ea4c]0c.byte $0c; New screen index [ea4d]2d.byte $2d; Y, X [ea4e]06.byte $06; Area
; ; XREFS: ; AREA_SCREEN_COMPARATORS ; [$PRG15_MIRROR::ea37] ;
[ea4f]AREA_SCREEN_COMPARATOR_END: [ea4f]ff.byte $ff; 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:
; ; Face the player to the right. ;
[ea50]a5 a4LDA Player_Flags; Load the player flags. [ea52]09 40ORA #$40; Face the player right. [ea54]85 a4STA Player_Flags; Store the flags. [ea56]ad d2 03LDA a:Area_Music_Outside; Load the music used outside the building. [ea59]8d d1 03STA a:Areas_DefaultMusic; Set as the music to play. [ea5c]4c be daJMP 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:
; ; Check if the player is in the area of Victim. ;
[ea5f]a5 24LDA Area_CurrentArea [ea61]c9 04CMP #$04 [ea63]f0 ebBEQ Game_BeginExitBuilding [ea65]0aASL A [ea66]a8TAY [ea67]b9 9c eaLDA a:AREA_TOWN_TRANSITIONS_DATA,Y [ea6a]85 02STA Temp_Addr_L [ea6c]b9 9d eaLDA a:AREA_TOWN_TRANSITIONS_DATA+1,Y [ea6f]85 03STA Temp_Addr_U [ea71]a0 00LDY #$00 [ea73]@LAB_PRG15_MIRROR__ea73: [ea73]b1 02LDA (Temp_Addr_L),Y [ea75]c9 ffCMP #$ff [ea77]f0 22BEQ @_return [ea79]c5 63CMP Area_CurrentScreen [ea7b]d0 17BNE @LAB_PRG15_MIRROR__ea94 [ea7d]c8INY [ea7e]b1 02LDA (Temp_Addr_L),Y [ea80]85 24STA Area_CurrentArea [ea82]c8INY [ea83]b1 02LDA (Temp_Addr_L),Y [ea85]85 64STA Area_LoadingScreenIndex [ea87]c8INY [ea88]b1 02LDA (Temp_Addr_L),Y [ea8a]85 6cSTA Screen_StartPosYX [ea8c]c8INY [ea8d]b1 02LDA (Temp_Addr_L),Y [ea8f]85 65STA Screen_DestPaletteOrIndex [ea91]4c cd daJMP Game_SetupNewArea [ea94]@LAB_PRG15_MIRROR__ea94: [ea94]98TYA [ea95]18CLC [ea96]69 05ADC #$05 [ea98]a8TAY [ea99]90 d8BCC @LAB_PRG15_MIRROR__ea73 [ea9b]@_return: [ea9b]60RTS
; ; XREFS: ; Player_CheckSwitchScreen_SwitchAreaHoriz ;
[ea9c]AREA_TOWN_TRANSITIONS_DATA: [ea9c]2e ebpointer TOWN_TRANSITIONS_EMPTY; [0]: Eolis [ea9e]ac eapointer TOWN_TRANSITIONS_TRUNK; [1]: Trunk [eaa0]03 ebpointer TOWN_TRANSITIONS_MIST; [2]: Mist [eaa2]c6 eapointer TOWN_TRANSITIONS_TOWNS; [3]: Towns [eaa4]2e ebpointer TOWN_TRANSITIONS_EMPTY; [4]: Buildings [eaa6]18 ebpointer TOWN_TRANSITIONS_BRANCH; [5]: Branch [eaa8]28 ebpointer TOWN_TRANSITIONS_DARTMOOR; [6]: Dartmoor [eaaa]2e ebpointer TOWN_TRANSITIONS_EMPTY; [7]: Zenis
; ; XREFS: ; AREA_TOWN_TRANSITIONS_DATA ; [$PRG15_MIRROR::ea9e] ;
[eaac]TOWN_TRANSITIONS_TRUNK: [eaac]00.byte $00; byte [eaad]03.byte AREA_TOWNS; Area [eaae]02 92.byte $02,$92; byte [eab0]1b.byte PALETTE_TOWN; Palette [eab1]07.byte $07; Entering Trunk from Trunk (screen 7) [eab2]03.byte AREA_TOWNS; |-> Switch to area 3 [eab3]00.byte $00; |-> Screen index 0 [eab4]92.byte $92; |-> Start position Y=9, X=2 [eab5]1b.byte PALETTE_TOWN; '-> Palette 27 [eab6]08.byte $08; byte [eab7]03.byte AREA_TOWNS; Area [eab8]01 9e.byte $01,$9e; byte [eaba]1b.byte PALETTE_TOWN; Palette [eabb]1a.byte $1a; Entering Trunk from Trunk (screen 26) [eabc]03.byte AREA_TOWNS; |-> Switch to area 3 [eabd]02.byte $02; |-> Screen index 2 [eabe]92.byte $92; |-> Start position X=2, Y=9 [eabf]1b.byte PALETTE_TOWN; '-> Palette 27 [eac0]1d.byte $1d; byte [eac1]03.byte AREA_TOWNS; Area [eac2]03 9e.byte $03,$9e; byte [eac4]1b.byte PALETTE_TOWN; Palette [eac5]ff.byte $ff; byte
; ; XREFS: ; AREA_TOWN_TRANSITIONS_DATA ; [$PRG15_MIRROR::eaa2] ;
[eac6]TOWN_TRANSITIONS_TOWNS: [eac6]00.byte $00; Exiting left from Apolune screen 0 [eac7]01.byte AREA_TRUNK; |-> Switch to area 1 [eac8]07.byte $07; |-> Screen index 6 [eac9]7e.byte $7e; |-> Start position Y=7, X=14 [eaca]06.byte PALETTE_OUTSIDE; '-> Palette 6 [eacb]01.byte $01; Exit right from Apolune screen 1 [eacc]01.byte AREA_TRUNK; |-> Switch to area 1 [eacd]08.byte $08; |-> Screen index 8 [eace]71.byte $71; |-> Start position Y=7, X=1 [eacf]06.byte PALETTE_OUTSIDE; '-> Palette 6 [ead0]02.byte $02; Exiting left from Forepaw to Trunk (screen 2) [ead1]01.byte AREA_TRUNK; |-> Switch to area 1 [ead2]1a.byte $1a; |-> Screen index 26 [ead3]7e.byte $7e; |-> Start position X=14, Y=7 [ead4]06.byte PALETTE_OUTSIDE; '-> Palette 6 [ead5]03.byte $03; Exiting right from Forepaw to Branch (screen 3) [ead6]01.byte AREA_TRUNK; |-> Switch to area 3 [ead7]1d.byte $1d; |-> Screen index 29 [ead8]71.byte $71; |-> Start position X=1, Y=7 [ead9]06.byte PALETTE_OUTSIDE; |-> Palette 6 [eada]04.byte $04; Exiting left from Mascon to Mist (screen 4) [eadb]02.byte AREA_MIST; |-> Switch to area 2 [eadc]09.byte $09; |-> Screen index 9 [eadd]9e.byte $9e; |-> Start position X=14, Y=9 [eade]0a.byte PALETTE_MIST; '-> Palette 10 [eadf]05.byte $05; Exiting right from Mascon to Mist (screen 5) [eae0]02.byte AREA_MIST; |-> Switch to area 2 [eae1]0c.byte $0c; |-> Screen index 12 [eae2]91.byte $91; |-> Start position X=1, Y=9 [eae3]0a.byte PALETTE_MIST; '-> Palette 10 [eae4]06.byte $06; Exit left from Victim to Mist from screen 6 [eae5]02.byte AREA_MIST; |-> Switch to area 2 [eae6]22.byte $22; |-> Screen index 34 [eae7]9e.byte $9e; |-> Start position X=14, Y=9 [eae8]0a.byte PALETTE_MIST; '-> Palette 10 [eae9]07.byte $07; Exit right from Victim to Mist on screen 7 [eaea]02.byte AREA_MIST; |-> Switch to area 7 [eaeb]25.byte $25; |-> Screen index = 37 [eaec]91.byte $91; |-> Start position X=1, Y=9 [eaed]0a.byte PALETTE_MIST; '-> Palette 10 [eaee]08.byte $08; Exit left from Conflate to Branch [eaef]05.byte AREA_BRANCH; |-> Switch to area 5 [eaf0]0d.byte $0d; |-> Screen index 13 [eaf1]7e.byte $7e; |-> Start position X=14, Y=7 [eaf2]08.byte PALETTE_BRANCH; '-> Palette 8 [eaf3]0a.byte $0a; Exit left from Daybreak to Branch [eaf4]05.byte AREA_BRANCH; |-> Switch to area 5 [eaf5]23.byte $23; |-> Screen index 35 [eaf6]7e.byte $7e; |-> Start position X=14, Y=7 [eaf7]08.byte PALETTE_BRANCH; '-> Palette 8 [eaf8]0b.byte $0b; Exit right from Daybreak to Branch [eaf9]05.byte AREA_BRANCH; |-> Switch to area 5 [eafa]24.byte $24; |-> Screen index 36 [eafb]71.byte $71; |-> Start position X=1, Y=7 [eafc]08.byte PALETTE_BRANCH; '-> Palette 8 [eafd]0c.byte $0c; Exit left from Dartmoor to Branch (screen 12) [eafe]06.byte AREA_DARTMOOR; |-> Switch to area 6 [eaff]03.byte $03; |-> Screen index 3 [eb00]7e.byte $7e; |-> Start position X=14, Y=7 [eb01]0c.byte PALETTE_DARTMOOR; '-> Palette 12 [eb02]ff.byte $ff; byte
; ; XREFS: ; AREA_TOWN_TRANSITIONS_DATA ; [$PRG15_MIRROR::eaa0] ;
[eb03]TOWN_TRANSITIONS_MIST: [eb03]09.byte $09; Entering Mascon from Mist (screen 9) [eb04]03.byte AREA_TOWNS; |-> Switch to area 3 [eb05]04.byte $04; |-> Screen index 4 [eb06]91.byte $91; |-> Start position X=1, Y=9 [eb07]1b.byte PALETTE_TOWN; '-> Palette 27 [eb08]0c.byte $0c; Entering left to Mascon from Mist [eb09]03.byte AREA_TOWNS; |-> Switch to area 3 [eb0a]05.byte $05; |-> Screen index 5 [eb0b]9e.byte $9e; |-> Start position X=14, Y=9 [eb0c]1b.byte PALETTE_TOWN; '-> Palette 27 [eb0d]22.byte $22; Enering right to Victim from Mist (screen 34) [eb0e]03.byte AREA_TOWNS; |-> Switch to area 3 [eb0f]06.byte $06; |-> Screen index 6 [eb10]91.byte $91; |-> Start position X=1, Y=9 [eb11]1b.byte PALETTE_TOWN; '-> Palette 27 [eb12]25.byte $25; Entering left to Victim from Mist (screen 37) [eb13]03.byte AREA_TOWNS; |-> Switch to area 3 [eb14]07.byte $07; |-> Screen index 7 [eb15]9e.byte $9e; |-> Start position X=14, Y=9 [eb16]1b.byte PALETTE_TOWN; '-> Palette 27 [eb17]ff.byte $ff; byte
; ; XREFS: ; AREA_TOWN_TRANSITIONS_DATA ; [$PRG15_MIRROR::eaa6] ;
[eb18]TOWN_TRANSITIONS_BRANCH: [eb18]0d.byte $0d; Entering right to Conflate from Branch (screen 13) [eb19]03.byte AREA_TOWNS; |-> Switch to area 3 [eb1a]08.byte $08; |-> Screen index 8 [eb1b]91.byte $91; |-> Start position X=1, Y=9 [eb1c]1b.byte PALETTE_TOWN; '-> Palette 27 [eb1d]23.byte $23; Entering Daybreak from Branch (screen 35) [eb1e]03.byte AREA_TOWNS; |-> Switch to area 3 [eb1f]0a.byte $0a; |-> Screen index 10 [eb20]92.byte $92; |-> Start position X=2, Y=9 [eb21]1b.byte PALETTE_TOWN; '-> Palette 27 [eb22]24.byte $24; Entering Daybreak from Branch (screen 36) [eb23]03.byte AREA_TOWNS; |-> Switch to area 3 [eb24]0b.byte $0b; |-> Screen index 11 [eb25]9e.byte $9e; |-> Start position X=14, Y=9 [eb26]1b.byte PALETTE_TOWN; '-> Palette 27 [eb27]ff.byte $ff; byte
; ; XREFS: ; AREA_TOWN_TRANSITIONS_DATA ; [$PRG15_MIRROR::eaa8] ;
[eb28]TOWN_TRANSITIONS_DARTMOOR: [eb28]03.byte $03; Entering right to Dartmoor from Branch (screen 3) [eb29]03.byte AREA_TOWNS; |-> Switch to area 3 [eb2a]0c.byte $0c; |-> Screen index 12 [eb2b]92.byte $92; |-> Start position X=2, Y=9 [eb2c]1b.byte PALETTE_TOWN; '-> Palette 27 [eb2d]ff.byte $ff; 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]ff.byte $ff; 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]ad 2b 04LDA a:CurrentDoor_KeyRequirement [eb32]f0 0aBEQ RETURN_EB3E [eb34]0aASL A [eb35]a8TAY [eb36]b9 40 ebLDA a:DOOR_REQUIREMENT_LOOKUP_FUNC_ADDRS+1,Y [eb39]48PHA [eb3a]b9 3f ebLDA a:DOOR_REQUIREMENT_LOOKUP_FUNC_ADDRS,Y [eb3d]48PHA
;============================================================================ ; TODO: Document RETURN_EB3E ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; DOOR_REQUIREMENT_LOOKUP_FUNC_ADDRS ; [$PRG15_MIRROR::eb3f] ; Game_RunDoorRequirementHandler ;============================================================================
[eb3e]RETURN_EB3E: [eb3e]60RTS
;============================================================================ ; A mapping of door requirement lookup function addresses. ; ; XREFS: ; Game_RunDoorRequirementHandler ;============================================================================
; ; XREFS: ; Game_RunDoorRequirementHandler ;
[eb3f]DOOR_REQUIREMENT_LOOKUP_FUNC_ADDRS: [eb3f]3d ebpointer RETURN_EB3E-1; [0]: [0]: No key, return [eb41]50 ebpointer Game_OpenDoorWithAKey-1; [0]: [1]: "A" Key [eb43]60 ebpointer Game_OpenDoorWithKKey-1; [0]: [2]: "K" Key [eb45]70 ebpointer Game_OpenDoorWithQKey-1; [0]: [3]: "Q" Key [eb47]80 ebpointer Game_OpenDoorWithJKey-1; [0]: [4]: "J" Key [eb49]90 ebpointer Game_OpenDoorWithJoKey-1; [0]: [5]: "Jo" Key [eb4b]a0 ebpointer Game_OpenDoorWithRingOfElf-1; [0]: [6]: Ring of Elf [eb4d]b0 ebpointer Game_OpenDoorWithRingOfDworf-1; [0]: [7]: Ring of Dworf [eb4f]c0 ebpointer Game_OpenDoorWithDemonsRing-1; [0]: [8]: Demon's Ring
;============================================================================ ; 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]ad c1 03LDA a:SelectedItem; Load the selected item. [eb54]c9 04CMP #$04; Is this the "A" Key? [eb56]f0 79BEQ Game_UnlockDoorWithUsableItem; If so, unlock the door and enter.
; ; Run IScript 0x7D (via jump to IScripts_Begin). ;
[eb58]a9 7dLDA #$7d; 0x7D == "A" Key required IScript. [eb5a]20 59 f8JSR MMC1_LoadBankAndJump; Run the IScript: [eb5d]0c.byte BANK_12_LOGIC; Bank = 12 [eb5e]41 82pointer IScripts_Begin-1; Address = IScripts_Begin [eb60]@_afterFarJump: [eb60]60RTS
;============================================================================ ; 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]ad c1 03LDA a:SelectedItem; Load the selected item. [eb64]c9 05CMP #$05; Is this the "K" Key? [eb66]f0 69BEQ Game_UnlockDoorWithUsableItem; If so, unlock the door and enter.
; ; Run IScript 0x7C (via jump to IScripts_Begin). ;
[eb68]a9 7cLDA #$7c; 0x7C == "K" Key required IScript. [eb6a]20 59 f8JSR MMC1_LoadBankAndJump; Run the IScript: [eb6d]0c.byte BANK_12_LOGIC; Bank = 12 [eb6e]41 82pointer IScripts_Begin-1; Address = IScripts_Begin [eb70]@_afterFarJump: [eb70]60RTS
;============================================================================ ; 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]ad c1 03LDA a:SelectedItem; Load the selected item. [eb74]c9 06CMP #$06; Is this the "Q" Key? [eb76]f0 59BEQ Game_UnlockDoorWithUsableItem; If so, unlock the door and enter.
; ; Run IScript 0x7B (via jump to IScripts_Begin). ;
[eb78]a9 7bLDA #$7b; 0x7B == "Q" Key required IScript. [eb7a]20 59 f8JSR MMC1_LoadBankAndJump; Run the IScript: [eb7d]0c.byte BANK_12_LOGIC; Bank = 12 [eb7e]41 82pointer IScripts_Begin-1; Address = IScripts_Begin [eb80]@_afterFarJump: [eb80]60RTS
;============================================================================ ; 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]ad c1 03LDA a:SelectedItem; Load the selected item. [eb84]c9 07CMP #$07; Is this the "J" Key? [eb86]f0 49BEQ Game_UnlockDoorWithUsableItem; If so, unlock the door and enter.
; ; Run IScript 0x7 (via jump to IScripts_Begin). ;
[eb88]a9 02LDA #$02; 0x02 == "J" Key required IScript. [eb8a]20 59 f8JSR MMC1_LoadBankAndJump; Run the IScript: [eb8d]0c.byte BANK_12_LOGIC; Bank = 12 [eb8e]41 82pointer IScripts_Begin-1; Address = IScripts_Begin [eb90]@_afterFarJump: [eb90]60RTS
;============================================================================ ; 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]ad c1 03LDA a:SelectedItem; Load the selected item. [eb94]c9 08CMP #$08; Is this the "Jo" Key? [eb96]f0 39BEQ Game_UnlockDoorWithUsableItem; If so, unlock the door and enter.
; ; Run IScript 0x7E (via jump to IScripts_Begin). ;
[eb98]a9 7eLDA #$7e; 0x7E == "Jo" Key required IScript. [eb9a]20 59 f8JSR MMC1_LoadBankAndJump; Run the IScript: [eb9d]0c.byte BANK_12_LOGIC; Bank = 12 [eb9e]41 82pointer IScripts_Begin-1; Address = IScripts_Begin [eba0]@_afterFarJump: [eba0]60RTS
;============================================================================ ; 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]ad 2c 04LDA a:SpecialItems; Load the special items. [eba4]29 80AND #$80; Does the player have the Ring of Elf? [eba6]d0 39BNE Game_UnlockDoor; If so, unlock the door and enter.
; ; Run IScript 0x7F (via jump to IScripts_Begin). ;
[eba8]a9 7fLDA #$7f; 0x7F == Ring required IScript. [ebaa]20 59 f8JSR MMC1_LoadBankAndJump; Run the IScript: [ebad]0c.byte BANK_12_LOGIC; Bank = 12 [ebae]41 82pointer IScripts_Begin-1; Address = IScripts_Begin [ebb0]@_afterFarJump: [ebb0]60RTS
;============================================================================ ; 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]ad 2c 04LDA a:SpecialItems; Load the special items. [ebb4]29 20AND #$20; Does the player have the Ring of Dworf? [ebb6]d0 29BNE Game_UnlockDoor; If so, unlock the door and enter.
; ; Run IScript 0x7F (via jump to IScripts_Begin). ;
[ebb8]a9 7fLDA #$7f; 0x7F == Ring required IScript. [ebba]20 59 f8JSR MMC1_LoadBankAndJump; Run the IScript: [ebbd]0c.byte BANK_12_LOGIC; Bank = 12 [ebbe]41 82pointer IScripts_Begin-1; Address = IScripts_Begin [ebc0]@_afterFarJump: [ebc0]60RTS
;============================================================================ ; 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]ad 2c 04LDA a:SpecialItems; Load the special items. [ebc4]29 10AND #$10; Does the player have the Demon's Ring? [ebc6]d0 19BNE Game_UnlockDoor; If so, unlock the door and enter.
; ; Run IScript 0x7F (via jump to IScripts_Begin). ;
[ebc8]a9 7fLDA #$7f; 0x7F == Ring required IScript. [ebca]20 59 f8JSR MMC1_LoadBankAndJump; Run the IScript: [ebcd]0c.byte BANK_12_LOGIC; Bank = 12 [ebce]41 82pointer IScripts_Begin-1; Address = IScripts_Begin [ebd0]@_afterFarJump: [ebd0]60RTS
;============================================================================ ; TODO: Document Game_UnlockDoorWithUsableItem ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Game_OpenDoorWithAKey ; Game_OpenDoorWithJKey ; Game_OpenDoorWithJoKey ; Game_OpenDoorWithKKey ; Game_OpenDoorWithQKey ;============================================================================
[ebd1]Game_UnlockDoorWithUsableItem:
; ; Run IScript 0x84 (via jump to IScripts_Begin). ;
[ebd1]a9 84LDA #$84; 0x84 == Used key IScript. [ebd3]20 59 f8JSR MMC1_LoadBankAndJump; Run the IScript: [ebd6]0c.byte BANK_12_LOGIC; Bank = 12 [ebd7]41 82pointer IScripts_Begin-1; Address = IScripts_Begin [ebd9]@_afterFarJump: [ebd9]a9 ffLDA #$ff [ebdb]8d c1 03STA a:SelectedItem [ebde]20 dc c8JSR UI_ClearSelectedItemPic
;============================================================================ ; TODO: Document Game_UnlockDoor ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Game_OpenDoorWithDemonsRing ; Game_OpenDoorWithRingOfDworf ; Game_OpenDoorWithRingOfElf ;============================================================================
[ebe1]Game_UnlockDoor: [ebe1]a9 00LDA #$00 [ebe3]8d 2b 04STA a:CurrentDoor_KeyRequirement [ebe6]a9 84LDA #$84 [ebe8]a9 06LDA #$06 [ebea]20 e4 d0JSR Sound_PlayEffect [ebed]60RTS
;============================================================================ ; TODO: Document Player_DrawBody ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; EndGame_MainLoop ; Game_DrawScreenInFrozenState ; Game_MainLoop ; Player_HandleDeath ;============================================================================
[ebee]Player_DrawBody: [ebee]ad c7 03LDA a:IScript_PortraitID [ebf1]30 01BMI @LAB_PRG15_MIRROR__ebf4 [ebf3]60RTS
; ; 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]a5 0eLDA Screen_ReadyState [ebf6]c9 01CMP #$01 [ebf8]f0 17BEQ @LAB_PRG15_MIRROR__ec11 [ebfa]c9 05CMP #$05 [ebfc]f0 13BEQ @LAB_PRG15_MIRROR__ec11
; ; Ths code path should always be hit when this function is ; called. ;
[ebfe]a5 9eLDA Player_PosX_Block [ec00]85 27STA Arg_DrawSprite_PosX [ec02]a5 9fLDA Screen_Maybe_ScrollXCounter [ec04]85 2bSTA Unused_Sprite_ScrollPosX [ec06]a5 a1LDA Player_PosY [ec08]85 28STA Arg_DrawSprite_PosY [ec0a]a5 a2LDA Player_Something_ScrollPosY [ec0c]85 2aSTA Unused_Sprite_ScrollPosY [ec0e]4c 21 ecJMP @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]a5 b2LDA Maybe_PlayerX_ForScroll [ec13]85 27STA Arg_DrawSprite_PosX [ec15]a5 59LDA PPU_ScrollScreenHoriz [ec17]85 2bSTA Unused_Sprite_ScrollPosX [ec19]a5 b3LDA Maybe_PlayerY_ForScroll [ec1b]85 28STA Arg_DrawSprite_PosY [ec1d]a5 58LDA PPU_ScrollScreenVert [ec1f]85 2aSTA Unused_Sprite_ScrollPosY [ec21]@LAB_PRG15_MIRROR__ec21: [ec21]20 ed b9JSR Player_CalculateVisibility [ec24]20 51 ecJSR Player_SetFacingLeft [ec27]20 ac ecJSR Player_GetBodySpriteFrameOffset [ec2a]48PHA [ec2b]ad be 03LDA a:SelectedArmor [ec2e]0aASL A [ec2f]85 00STA Temp_00 [ec31]ad bf 03LDA a:SelectedShield [ec34]c9 03CMP #$03 [ec36]a9 00LDA #$00 [ec38]2aROL A [ec39]49 01EOR #$01 [ec3b]05 00ORA Temp_00 [ec3d]aaTAX [ec3e]68PLA [ec3f]18CLC [ec40]7d 49 ecADC a:PLAYER_BODY_TILEINFO_START_OFFSETS,X [ec43]20 39 f0JSR Sprite_SetPlayerAppearanceAddr [ec46]4c 58 ecJMP FUN_PRG15_MIRROR__ec58
; ; XREFS: ; Player_DrawBody ;
[ec49]PLAYER_BODY_TILEINFO_START_OFFSETS: [ec49]00.byte $00; [0]: Leather Armor [ec4a]08.byte $08; [1]: Leather Armor + Shield [ec4b]10.byte $10; [2]: Studded Mail [ec4c]18.byte $18; [3]: Studded Mail + Shield [ec4d]20.byte $20; [4]: Full Plate [ec4e]28.byte $28; [5]: Full Plate + Shield [ec4f]30.byte $30; [6]: Battle Suit [ec50]38.byte $38; [7]: Battle Suit + Shield
;============================================================================ ; TODO: Document Player_SetFacingLeft ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_DrawBody ;============================================================================
[ec51]Player_SetFacingLeft: [ec51]a5 a4LDA Player_Flags [ec53]29 40AND #$40 [ec55]85 29STA CurrentSprite_FlipMask [ec57]60RTS
;============================================================================ ; TODO: Document FUN_PRG15_MIRROR__ec58 ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_DrawBody ;============================================================================
[ec58]FUN_PRG15_MIRROR__ec58: [ec58]a5 a4LDA Player_Flags [ec5a]10 45BPL @_return [ec5c]a5 aeLDA Player_AttackPhaseAnimFrame [ec5e]c9 02CMP #$02 [ec60]d0 3fBNE @_return [ec62]a0 00LDY #$00 [ec64]a5 a4LDA Player_Flags [ec66]29 40AND #$40 [ec68]f0 01BEQ @LAB_PRG15_MIRROR__ec6b [ec6a]c8INY [ec6b]@LAB_PRG15_MIRROR__ec6b: [ec6b]b9 a2 ecLDA a:BYTE_ARRAY_PRG15_MIRROR__eca2,Y [ec6e]20 80 b8JSR Player_CalcValueAndFFForNeg [ec71]a5 9eLDA Player_PosX_Block [ec73]18CLC [ec74]65 04ADC Temp_04 [ec76]85 27STA Arg_DrawSprite_PosX [ec78]a5 9eLDA Player_PosX_Block [ec7a]65 05ADC Temp_05 [ec7c]c5 9eCMP Player_PosX_Block [ec7e]d0 21BNE @_return [ec80]a5 a1LDA Player_PosY [ec82]85 28STA Arg_DrawSprite_PosY [ec84]20 ed b9JSR Player_CalculateVisibility [ec87]ad be 03LDA a:SelectedArmor [ec8a]0aASL A [ec8b]85 00STA Temp_00 [ec8d]a0 00LDY #$00 [ec8f]ad bf 03LDA a:SelectedShield [ec92]c9 03CMP #$03 [ec94]b0 01BCS @LAB_PRG15_MIRROR__ec97 [ec96]c8INY [ec97]@LAB_PRG15_MIRROR__ec97: [ec97]98TYA [ec98]05 00ORA Temp_00 [ec9a]a8TAY [ec9b]b9 a4 ecLDA a:BYTE_ARRAY_PRG15_MIRROR__eca4,Y [ec9e]4c 39 f0JMP Sprite_SetPlayerAppearanceAddr [eca1]@_return: [eca1]60RTS
; ; XREFS: ; FUN_PRG15_MIRROR__ec58 ;
[eca2]BYTE_ARRAY_PRG15_MIRROR__eca2: [eca2]f8.byte $f8; [0]:
; ; XREFS: ; FUN_PRG15_MIRROR__ec58 ;
[eca3]BYTE_ARRAY_PRG15_MIRROR__eca2_1_: [eca3]10.byte $10; [1]:
; ; XREFS: ; FUN_PRG15_MIRROR__ec58 ;
[eca4]BYTE_ARRAY_PRG15_MIRROR__eca4: [eca4]63.byte $63; [0]: Leather Armor [eca5]67.byte $67; [1]: Leather Armor + Shield [eca6]64.byte $64; [2]: Studded Mail [eca7]68.byte $68; [3]: Studded Mail + Shield [eca8]65.byte $65; [4]: Full Plate [eca9]69.byte $69; [5]: Full Plate + Shield [ecaa]66.byte $66; [6]: Battle Suit [ecab]6a.byte $6a; [7]: Battle Suit + Shield
;============================================================================ ; TODO: Document Player_GetBodySpriteFrameOffset ; ; INPUTS: ; None. ; ; OUTPUTS: ; A ; ; XREFS: ; Player_DrawBody ;============================================================================
[ecac]Player_GetBodySpriteFrameOffset: [ecac]a5 a4LDA Player_Flags [ecae]4aLSR A [ecaf]90 07BCC @LAB_PRG15_MIRROR__ecb8 [ecb1]a5 a4LDA Player_Flags [ecb3]30 17BMI @LAB_PRG15_MIRROR__eccc [ecb5]a9 03LDA #$03 [ecb7]60RTS [ecb8]@LAB_PRG15_MIRROR__ecb8: [ecb8]20 f6 ecJSR Player_IsClimbing [ecbb]90 0bBCC @LAB_PRG15_MIRROR__ecc8 [ecbd]a5 a3LDA Player_MovementTick [ecbf]29 10AND #$10 [ecc1]0aASL A [ecc2]0aASL A [ecc3]85 29STA CurrentSprite_FlipMask [ecc5]a9 07LDA #$07 [ecc7]60RTS [ecc8]@LAB_PRG15_MIRROR__ecc8: [ecc8]a5 a4LDA Player_Flags [ecca]10 06BPL @LAB_PRG15_MIRROR__ecd2 [eccc]@LAB_PRG15_MIRROR__eccc: [eccc]a6 aeLDX Player_AttackPhaseAnimFrame [ecce]bd f3 ecLDA a:BYTE_ARRAY_PRG15_MIRROR__ecf3,X [ecd1]60RTS [ecd2]@LAB_PRG15_MIRROR__ecd2: [ecd2]a5 a5LDA Player_StatusFlag [ecd4]10 07BPL @LAB_PRG15_MIRROR__ecdd [ecd6]a5 16LDA Joy1_ButtonMask [ecd8]10 03BPL @LAB_PRG15_MIRROR__ecdd [ecda]a9 03LDA #$03 [ecdc]60RTS [ecdd]@LAB_PRG15_MIRROR__ecdd: [ecdd]a5 a4LDA Player_Flags [ecdf]29 20AND #$20 [ece1]f0 07BEQ @LAB_PRG15_MIRROR__ecea [ece3]a5 a3LDA Player_MovementTick [ece5]4aLSR A [ece6]4aLSR A [ece7]4aLSR A [ece8]29 03AND #$03 [ecea]@LAB_PRG15_MIRROR__ecea: [ecea]aaTAX [eceb]bd ef ecLDA a:BYTE_ARRAY_PRG15_MIRROR__ecef,X [ecee]60RTS
; ; XREFS: ; Player_GetBodySpriteFrameOffset ;
[ecef]BYTE_ARRAY_PRG15_MIRROR__ecef: [ecef]00.byte $00; [0]: [ecf0]01.byte $01; [1]: [ecf1]02.byte $02; [2]: [ecf2]01.byte $01; [3]:
; ; XREFS: ; Player_GetBodySpriteFrameOffset ;
[ecf3]BYTE_ARRAY_PRG15_MIRROR__ecf3: [ecf3]04.byte $04; [0]: [ecf4]05.byte $05; [1]: [ecf5]06.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_GetBodySpriteFrameOffset ; Player_UpdateClimbOrJump ;============================================================================
[ecf6]Player_IsClimbing:
; ; Check if the player can currently climb an available ladder. ;
[ecf6]a5 a4LDA Player_Flags; Load the player's flags. [ecf8]29 08AND #$08; Can the player currently climb? [ecfa]f0 14BEQ @_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]a5 9eLDA Player_PosX_Block; Load the player's X position. [ecfe]29 0fAND #$0f; Is it on a block boundary? [ed00]f0 06BEQ @_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]a5 a4LDA Player_Flags; Load the player's flags. [ed04]29 04AND #$04; Is the player currently falling? [ed06]d0 08BNE @_returnFalse; If so, return false.
; ; Check if the player is currently climbing. ;
[ed08]@_checkClimbing: [ed08]a5 a4LDA Player_Flags; Load the player's flags. [ed0a]29 10AND #$10; Is the player climbing? [ed0c]f0 02BEQ @_returnFalse; If not, return false. [ed0e]38SEC; Set C = 1 to return true. [ed0f]60RTS [ed10]@_returnFalse: [ed10]18CLC; Set C = 0 to return false. [ed11]60RTS
;============================================================================ ; 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:
; ; Load and draw the player's sprite. ;
[ed12]20 72 edJSR Player_LoadArmorTilesToPPU; Load the armor sprite information. [ed15]@_drawArmorLoop: [ed15]20 a9 eeJSR Player_LoadArmorTile; Draw a tile of armor. [ed18]20 3c cfJSR PPUBuffer_Draw; Draw to the PPU. [ed1b]e6 a8INC Player_DrawTileReadOffset; Increment the read offset. [ed1d]c6 00DEC Temp_00; Decrement the tile count. [ed1f]10 f4BPL @_drawArmorLoop; If >= 0, loop.
; ; Load and draw the weapon sprite, if one is equipped. ;
[ed21]20 9d edJSR Player_LoadWeaponTilesToPPU; Load the weapon sprite information. [ed24]a5 a8LDA Player_DrawTileReadOffset; Get the tile read offset. [ed26]30 0cBMI @_loadShield; If < 0, skip weapon drawing.
; ; The player has a weapon equipped. Draw the tiles. ;
[ed28]@_drawWeaponLoop: [ed28]20 bf eeJSR Player_LoadWeaponTile; Draw a tile of the weapon. [ed2b]20 3c cfJSR PPUBuffer_Draw; Draw to the PPU. [ed2e]e6 a8INC Player_DrawTileReadOffset; Increment the tile read offset. [ed30]c6 00DEC Temp_00; Decrement the tile count. [ed32]10 f4BPL @_drawWeaponLoop; If >= 0, loop.
; ; Load and draw the shield sprite. ;
[ed34]@_loadShield: [ed34]20 cd edJSR Player_LoadShieldTilesToPPU; Load the shield sprite information. [ed37]a5 a8LDA Player_DrawTileReadOffset; Load the tile read offset. [ed39]30 09BMI @_return; If < 0 (unset), return. [ed3b]@_drawShieldLoop: [ed3b]20 93 eeJSR Player_LoadShieldTile; Draw a tile of the shield. [ed3e]e6 a8INC Player_DrawTileReadOffset; Increment the tile read offset. [ed40]c6 00DEC Temp_00; Decrement the tile count. [ed42]10 f7BPL @_drawShieldLoop; If >= 0, loop. [ed44]@_return: [ed44]60RTS
;============================================================================ ; 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:
; ; Load and draw the player's sprite. ;
[ed45]20 72 edJSR Player_LoadArmorTilesToPPU; Load the armor sprite information. [ed48]@_drawArmorLoop: [ed48]20 a9 eeJSR Player_LoadArmorTile; Draw a tile of armor. [ed4b]e6 a8INC Player_DrawTileReadOffset; Increment the read offset. [ed4d]c6 00DEC Temp_00; Decrement the tile count. [ed4f]10 f7BPL @_drawArmorLoop; If >= 0, loop.
; ; Load and draw the weapon sprite, if one is equipped. ;
[ed51]20 9d edJSR Player_LoadWeaponTilesToPPU; Load the weapon sprite information. [ed54]a5 a8LDA Player_DrawTileReadOffset; Get the tile read offset. [ed56]30 09BMI @_loadShield; If < 0, skip weapon drawing.
; ; The player has a weapon equipped. Draw the tiles. ;
[ed58]@_drawWeaponLoop: [ed58]20 bf eeJSR Player_LoadWeaponTile; Draw a tile of the weapon. [ed5b]e6 a8INC Player_DrawTileReadOffset; Increment the tile read offset. [ed5d]c6 00DEC Temp_00; Decrement the tile count. [ed5f]10 f7BPL @_drawWeaponLoop; If >= 0, loop.
; ; Load and draw the shield sprite. ;
[ed61]@_loadShield: [ed61]20 cd edJSR Player_LoadShieldTilesToPPU; Load the shield sprite information. [ed64]a5 a8LDA Player_DrawTileReadOffset; Load the tile read offset. [ed66]30 09BMI @_return; If < 0 (unset), return. [ed68]@_drawShieldLoop: [ed68]20 93 eeJSR Player_LoadShieldTile; Draw a tile of the shield. [ed6b]e6 a8INC Player_DrawTileReadOffset; Increment the tile read offset. [ed6d]c6 00DEC Temp_00; Decrement the tile count. [ed6f]10 f7BPL @_drawShieldLoop; If >= 0, loop. [ed71]@_return: [ed71]60RTS
;============================================================================ ; 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:
; ; Clear initial state for drawing the armor. ;
[ed72]a9 00LDA #$00 [ed74]85 a8STA Player_DrawTileReadOffset; Set the tile read offset to 0. [ed76]85 91STA Player_SpriteSegmentPPUAddr_L; Set the sprite segment PPU addresses to 0. [ed78]85 92STA 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]ad be 03LDA a:SelectedArmor; Load the selected armor. [ed7d]0aASL A; Convert to a word boundary. [ed7e]ac bf 03LDY a:SelectedShield; Load the selected shield. [ed81]c0 03CPY #$03; Is it the Battle Helmet? [ed83]b0 02BCS @_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]09 01ORA #$01; Offset the lookup index to include armor + shield tiles. [ed87]@_loadArmor: [ed87]85 a7STA Player_ArmorLookupIndex; Store the lookup index based on the armor word boundary and possibly the shield bit. [ed89]aaTAX; 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]bd 95 edLDA a:PLAYER_ARMOR_TILE_COUNTS,X; Load the tile count. [ed8d]85 00STA 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]8aTXA; A = Lookup index. [ed90]0aASL A; Multiply by 2 (so index considers armor and armor + shield entries). [ed91]a8TAY; Y = result. [ed92]4c 15 eeJMP 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]33.byte $33; [0]: Leather Armor (51 tiles) [ed96]27.byte $27; [1]: Leather Armor + Shield (39 tiles) [ed97]33.byte $33; [2]: Studded Mail (51 tiles) [ed98]27.byte $27; [3]: Studded Mail + Shield (39 tiles) [ed99]34.byte $34; [4]: Full Plate (52 tiles) [ed9a]28.byte $28; [5]: Full Plate + Shield (40 tiles) [ed9b]32.byte $32; [6]: Battle Helmet (50 tiles) [ed9c]32.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]a9 00LDA #$00 [ed9f]85 a8STA Player_DrawTileReadOffset; Set the tile read offset to 0. [eda1]ad c8 03LDA a:Player_CurWeapon; Load the selected weapon. [eda4]c9 ffCMP #$ff; Is it 0xFF (unset)? [eda6]d0 03BNE @_loadWeaponTiles; If not, jump to load tiles.
; ; No sword is equipped. Set the offset to 0xFF, to disable ; drawing. ;
[eda8]85 a8STA Player_DrawTileReadOffset; Set the read offset to 0xFF. [edaa]60RTS
; ; 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]aaTAX; X = weapon. [edac]bd c1 edLDA a:PLAYER_WEAPON_TILE_COUNTS,X; Load the tile count. [edaf]85 00STA Temp_00; Store it for the parent caller to use.
; ; Set the PPU address to load tiles into and load them. ;
[edb1]8aTXA; X = tile count. [edb2]0aASL A; Convert to a word boundary. [edb3]a8TAY; Y = resulting index. [edb4]b9 c5 edLDA a:PLAYER_WEAPON_TILE_PPU_ADDRS,Y; Load the lower byte of the target PPU address for weapons. [edb7]85 91STA Player_SpriteSegmentPPUAddr_L; Set it for load. [edb9]b9 c6 edLDA a:PLAYER_WEAPON_TILE_PPU_ADDRS+1,Y; Load the upper byte of the target PPU address for weapons. [edbc]85 92STA Player_SpriteSegmentPPUAddr_U; Set it for load. [edbe]4c 3f eeJMP 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]02.byte $02; [0]: Dagger [edc2]05.byte $05; [1]: Long Sword [edc3]06.byte $06; [2]: Giant Blade [edc4]08.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]80 03.word $0380; [0]: Dagger [edc7]80 03.word $0380; [1]: Long Sword [edc9]80 03.word $0380; [2]: Giant Blade [edcb]40 03.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]a9 00LDA #$00 [edcf]85 a8STA Player_DrawTileReadOffset; Set the tile read offset to 0. [edd1]ad bf 03LDA a:SelectedShield; Load the selected shield. [edd4]c9 03CMP #$03; Is it the Battle Helmet (or unset -- 0xFF)? [edd6]90 03BCC @_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]85 a8STA Player_DrawTileReadOffset; Set the read offset to the shield value. This will be 3 or 0xFF. [edda]60RTS; And return.
; ; An actual shield is equipped. Begin setting the lookup ; index for the shield and a tile count of 5. ;
[eddb]@_loadShieldTiles: [eddb]0aASL A; Convert the shield index to a word value for lookup. [eddc]a8TAY; Y = resulting index. [eddd]a9 05LDA #$05; 5 tiles. [eddf]85 00STA Temp_00; Set as the tile count for the load loop.
; ; Set the PPU address to load into to 0x0300. ;
[ede1]a9 00LDA #$00 [ede3]85 91STA Player_SpriteSegmentPPUAddr_L; Set the lower byte of the PPU address to 0. [ede5]a9 03LDA #$03 [ede7]85 92STA Player_SpriteSegmentPPUAddr_U; And upper to 3. [ede9]4c 69 eeJMP 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]48PHA; 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]a5 24LDA Area_CurrentArea; Set A = current area. [edef]c9 04CMP #$04; Is this the building? [edf1]f0 0cBEQ @_cannotEquip; If so, branch.
; ; The weapon can be equipped. Equip it and redraw the player. ;
[edf3]68PLA; Pop the weapon from the stack. [edf4]8d bd 03STA a:SelectedWeapon; Set as the current weapon in the inventory. [edf7]8d c8 03STA a:Player_CurWeapon; Store as the current weapon. [edfa]20 45 edJSR Player_DrawSprite; Draw the player sprite. [edfd]18CLC; Clear Carry. [edfe]60RTS
; ; The weapon cannot currently be equipped. Set the weapon ; but do not draw. ;
[edff]@_cannotEquip: [edff]68PLA; Pop the weapon from the stack. [ee00]8d bd 03STA a:SelectedWeapon; Set as the current weapon. [ee03]18CLC; Clear Carry. [ee04]60RTS
;============================================================================ ; 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]8d be 03STA a:SelectedArmor; Set the selected armor. [ee08]20 45 edJSR Player_DrawSprite; Draw the player sprite. [ee0b]18CLC; Clear Carry. [ee0c]60RTS
;============================================================================ ; 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]8d bf 03STA a:SelectedShield; Set the selected shield. [ee10]20 45 edJSR Player_DrawSprite; Draw the player sprite. [ee13]18CLC; Clear Carry. [ee14]60RTS
;============================================================================ ; 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:
; ; Save the current ROM bank and switch to bank 8 (sprites). ;
[ee15]ad 00 01LDA a:CurrentROMBank; Load the current ROM bank. [ee18]48PHA; Push the bank to the stack. [ee19]a2 08LDX #$08; 8 = Sprites bank. [ee1b]20 1a ccJSR MMC1_UpdateROMBank; Switch to the bank.
; ; Load the address of the index of armor types to tile IDs. ;
[ee1e]ad 00 80LDA a:TILES_ARMOR_ADDRS_INDEX_REF; Load the lower byte of the address of the armor index. [ee21]85 61STA Player_SpriteTileReadAddr_L; Set as the lower byte. [ee23]ad 01 80LDA a:TILES_ARMOR_ADDRS_INDEX_REF+1; Load the upper byte. [ee26]18CLC [ee27]69 80ADC #$80; Add 0x80 to the upper byte. [ee29]85 62STA Player_SpriteTileReadAddr_U; And set it as the upper byte.
; ; Load the tiles start address of the armor type from the index. ;
[ee2b]b1 61LDA (Player_SpriteTileReadAddr_L),Y; Read the lower byte of the tiles start address. [ee2d]48PHA; Push it to the stack. [ee2e]c8INY; Increment the offset. [ee2f]b1 61LDA (Player_SpriteTileReadAddr_L),Y; Read the upper byte of the tiles start address. [ee31]18CLC [ee32]69 80ADC #$80; Add 0x80 to it. [ee34]85 62STA Player_SpriteTileReadAddr_U; And store as the upper byte of the address. [ee36]68PLA; Pop the lower byte. [ee37]85 61STA Player_SpriteTileReadAddr_L; And store it as the lower byte of the address.
; ; Switch back to the saved bank. ;
[ee39]68PLA; Pop the saved ROM bank from the stack. [ee3a]aaTAX; Set it to X [ee3b]20 1a ccJSR MMC1_UpdateROMBank; And switch back to it. [ee3e]60RTS
;============================================================================ ; 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:
; ; Save the current ROM bank and switch to bank 8 (sprites). ;
[ee3f]ad 00 01LDA a:CurrentROMBank; Load the current ROM bank. [ee42]48PHA; Push the bank to the stack. [ee43]a2 08LDX #$08; 8 = Sprites bank. [ee45]20 1a ccJSR MMC1_UpdateROMBank; Switch to the bank.
; ; Load the address of the index of weapon types to tile IDs. ;
[ee48]ad 02 80LDA a:TILES_WEAPON_ADDRS_INDEX_REF; Load the lower byte of the address of the weapon index. [ee4b]85 61STA Player_SpriteTileReadAddr_L; Set as the lower byte. [ee4d]ad 03 80LDA a:TILES_WEAPON_ADDRS_INDEX_REF+1; Load the upper byte. [ee50]18CLC [ee51]69 80ADC #$80; Add 0x80 to the upper byte. [ee53]85 62STA Player_SpriteTileReadAddr_U; And set it as the upper byte.
; ; Load the tiles start address of the weapon type from the ; index. ;
[ee55]b1 61LDA (Player_SpriteTileReadAddr_L),Y; Read the lower byte of the tiles start address. [ee57]48PHA; Push it to the stack. [ee58]c8INY; Increment the offset. [ee59]b1 61LDA (Player_SpriteTileReadAddr_L),Y; Read the upper byte of the tiles start address. [ee5b]18CLC [ee5c]69 80ADC #$80; Add 0x80 to it. [ee5e]85 62STA Player_SpriteTileReadAddr_U; And store as the upper byte of the address. [ee60]68PLA; Pop the lower byte. [ee61]85 61STA Player_SpriteTileReadAddr_L; And store it as the lower byte of the address.
; ; Switch back to the saved bank. ;
[ee63]68PLA; Pop the saved ROM bank from the stack. [ee64]aaTAX; Set it to X [ee65]20 1a ccJSR MMC1_UpdateROMBank; And switch back to it. [ee68]60RTS
;============================================================================ ; 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:
; ; Save the current ROM bank and switch to bank 8 (sprites). ;
[ee69]ad 00 01LDA a:CurrentROMBank; Load the current ROM bank. [ee6c]48PHA; Push the bank to the stack. [ee6d]a2 08LDX #$08; 8 = Sprites bank. [ee6f]20 1a ccJSR MMC1_UpdateROMBank; Switch to the bank.
; ; Load the address of the index of shield types to tile IDs. ;
[ee72]ad 0c 80LDA a:TILES_SHIELDS_ADDRS_INDEX_REF; Load the lower byte of the address of the shield index. [ee75]85 61STA Player_SpriteTileReadAddr_L; Set as the lower byte. [ee77]ad 0d 80LDA a:TILES_SHIELDS_ADDRS_INDEX_REF+1; Load the upper byte. [ee7a]18CLC [ee7b]69 80ADC #$80; Add 0x80 to the upper byte. [ee7d]85 62STA Player_SpriteTileReadAddr_U; And set it as the upper byte.
; ; Load the tiles start address of the shield type from the index. ;
[ee7f]b1 61LDA (Player_SpriteTileReadAddr_L),Y; Read the lower byte of the tiles start address. [ee81]48PHA; Push it to the stack. [ee82]c8INY; Increment the offset. [ee83]b1 61LDA (Player_SpriteTileReadAddr_L),Y; Read the upper byte of the tiles start address. [ee85]18CLC [ee86]69 80ADC #$80; Add 0x80 to it. [ee88]85 62STA Player_SpriteTileReadAddr_U; And store as the upper byte of the address. [ee8a]68PLA; Pop the lower byte. [ee8b]85 61STA Player_SpriteTileReadAddr_L; And store it as the lower byte of the address.
; ; Switch back to the saved bank. ;
[ee8d]68PLA; Pop the saved ROM bank from the stack. [ee8e]aaTAX; Set it to X. [ee8f]20 1a ccJSR MMC1_UpdateROMBank; And switch back to it. [ee92]60RTS
;============================================================================ ; 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:
; ; Save the current ROM bank and switch to bank 8 (sprites). ;
[ee93]ad 00 01LDA a:CurrentROMBank; Load the current ROM bank. [ee96]48PHA; Push the bank to the stack. [ee97]a2 08LDX #$08; 8 = Sprites bank. [ee99]20 1a ccJSR MMC1_UpdateROMBank; Switch to the bank.
; ; Load the address for the start of the shield tile IDs. ;
[ee9c]ad 0a 80LDA a:TILES_SHIELDS_START_REF; Load the lower byte of the tile IDs start address. [ee9f]85 02STA Temp_Addr_L; Store as the lower byte of the address to read from. [eea1]ad 0b 80LDA a:TILES_SHIELDS_START_REF+1; Load the upper byte of the tile IDs start address. [eea4]85 03STA Temp_Addr_U; Store as the upper byte of the address to read from.
; ; Draw the shield tile. ;
[eea6]4c d2 eeJMP 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:
; ; Save the current ROM bank and switch to bank 8 (sprites). ;
[eea9]ad 00 01LDA a:CurrentROMBank; Load the current ROM bank. [eeac]48PHA; Push the bank to the stack. [eead]a2 08LDX #$08; 8 = Sprites bank. [eeaf]20 1a ccJSR MMC1_UpdateROMBank; Switch to the bank.
; ; Load the address for the start of the armor tile IDs. ;
[eeb2]ad 04 80LDA a:TILES_ARMOR_START_REF; Load the lower byte of the tile IDs start address. [eeb5]85 02STA Temp_Addr_L; Store as the lower byte of the address to read from. [eeb7]ad 05 80LDA a:TILES_ARMOR_START_REF+1; Load the upper byte of the tile IDs start address. [eeba]85 03STA Temp_Addr_U; Store as the upper byte of the address to read from.
; ; Draw the armor tile. ;
[eebc]4c d2 eeJMP 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:
; ; Save the current ROM bank and switch to bank 8 (sprites). ;
[eebf]ad 00 01LDA a:CurrentROMBank; Load the current ROM bank. [eec2]48PHA; Push the bank to the stack. [eec3]a2 08LDX #$08; 8 = Sprites bank. [eec5]20 1a ccJSR MMC1_UpdateROMBank; Switch to the bank.
; ; Load the address for the start of the weapon tile IDs. ;
[eec8]ad 06 80LDA a:TILES_WEAPONS_START_REF; Load the lower byte of the tile IDs start address. [eecb]85 02STA Temp_Addr_L; Store as the lower byte of the address to read from. [eecd]ad 07 80LDA a:TILES_WEAPONS_START_REF+1; Load the upper byte of the tile IDs start address. [eed0]85 03STA 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]68PLA; Pop the saved ROM bank from the stack. [eed3]aaTAX; Set it to X. [eed4]20 1a ccJSR MMC1_UpdateROMBank; And switch back to it.
; ; And then save it again and switch away again, back to ; bank 8. ;
[eed7]ad 00 01LDA a:CurrentROMBank; Load the current ROM bank. [eeda]48PHA; Push it to the stack. [eedb]a2 08LDX #$08; 8 = Sprites bank. [eedd]20 1a ccJSR MMC1_UpdateROMBank; Switch to the bank.
; ; Prepare the read address for the tile. ;
[eee0]a4 a8LDY Player_DrawTileReadOffset; Load the tile read offset. [eee2]a9 00LDA #$00; A = 0 [eee4]85 05STA 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]b1 61LDA (Player_SpriteTileReadAddr_L),Y; Load the tile ID at the offset. [eee8]0aASL A; Shift left, upper bit into Carry. [eee9]26 05ROL Temp_05; Rotate left, carry into bit 0, bit 7 into carry. [eeeb]0aASL A; Repeat 3 more times, moving lower nibble into A, upper nibble into Temp_05. [eeec]26 05ROL Temp_05 [eeee]0aASL A [eeef]26 05ROL Temp_05 [eef1]0aASL A [eef2]26 05ROL Temp_05; Final storage of the upper byte. [eef4]85 04STA Temp_04; Final storage of the lower byte.
; ; Restore the saved bank. ;
[eef6]68PLA; Pop the saved ROM bank from the stack. [eef7]aaTAX; Set it to X. [eef8]20 1a ccJSR 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]a5 04LDA Temp_04; Load the lower byte from the normalized tile ID. [eefd]18CLC [eefe]65 02ADC Temp_Addr_L; Add to the start of the tile IDs. [ef00]85 04STA Temp_04; Store as the new lower byte.
; ; Now handle the upper byte of the address. ;
[ef02]a5 05LDA Temp_05; Load the upper byte from the normalized tile ID. [ef04]65 03ADC Temp_Addr_U; Add the upper byte of the address. [ef06]18CLC [ef07]69 80ADC #$80; Add 0x80. [ef09]85 05STA Temp_05; Store as the new upper byte.
; ; Switch bank to bank 8 (where this probably should have ; stayed all along). ;
[ef0b]ad 00 01LDA a:CurrentROMBank; Load the current bank again. [ef0e]48PHA; Push it to the stack. [ef0f]a2 08LDX #$08; 8 = Sprites bank [ef11]20 1a ccJSR MMC1_UpdateROMBank; Switch to it again.
; ; Set the target PPU address to write the tiles to for the ; PPU buffer draw command. ;
[ef14]a5 92LDA Player_SpriteSegmentPPUAddr_U; Load the upper byte of the PPU address of the sprite data. [ef16]8d e9 00STA a:PPU_TargetAddr_U; Store as the upper byte to write to for the PPU draw command. [ef19]a5 91LDA Player_SpriteSegmentPPUAddr_L; Load the lower byte. [ef1b]8d e8 00STA a:PPU_TargetAddr; And store it.
; ; Queue 16 bytes (1 tile) to draw to the PPU. ;
[ef1e]a9 10LDA #$10; A = 16 bytes. [ef20]20 dc cfJSR PPUBuffer_QueueCommandOrLength; Queue it as the draw length.
; ; Prepare a loop for writing bytes for the tile. ;
[ef23]a0 00LDY #$00; Y = 0 (loop counter/read offset). [ef25]@_copyLoop: [ef25]b1 04LDA (Temp_04),Y; Load a byte of the tile. [ef27]9d 00 05STA a:PPUBuffer,X; Write it to the PPU buffer. [ef2a]e8INX; X++ (buffer position). [ef2b]c8INY; Y++ (loop counter). [ef2c]c0 10CPY #$10; Is it < 16? [ef2e]90 f5BCC @_copyLoop; If so, loop.
; ; 16 bytes are written. Update the PPU buffer write offset ; and restore the bank. ;
[ef30]86 20STX PPUBuffer_WriteOffset; Update the PPU buffer write offset. [ef32]68PLA; Pop the saved bank from the stack. [ef33]aaTAX; Set in X. [ef34]20 1a ccJSR MMC1_UpdateROMBank; And update to the bank.
; ; Increment the PPU address for the next tile. ;
[ef37]a5 91LDA Player_SpriteSegmentPPUAddr_L; Load the lower byte of the PPU address. [ef39]18CLC [ef3a]69 10ADC #$10; Add 16 (next tile). [ef3c]85 91STA Player_SpriteSegmentPPUAddr_L; Store it. [ef3e]a5 92LDA Player_SpriteSegmentPPUAddr_U; Load the upper byte. [ef40]69 00ADC #$00; Add Carry (if the lower byte wrapped). [ef42]85 92STA Player_SpriteSegmentPPUAddr_U; And store it. [ef44]60RTS
;============================================================================ ; DEADCODE: Unset the special event ID. ; ; This is not used in the game. ;============================================================================
[ef45]UNUSED_ClearScreenSpecialEventID: [ef45]a9 ffLDA #$ff [ef47]8d 2e 04STA a:CurrentScreen_SpecialEventID [ef4a]60RTS
;============================================================================ ; 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:
; ; Check if this screen has a special event ID. ;
[ef4b]ad 2e 04LDA a:CurrentScreen_SpecialEventID; Load the screen's special event ID. [ef4e]c9 ffCMP #$ff; Is it 0xFF? [ef50]f0 10BEQ @_return; If so, nothing to run. Return.
; ; Check if there's an entry in the table. ;
[ef52]29 7fAND #$7f; Take the lower 7 bits. [ef54]0aASL A; Convert to a word boundary for the lookup table. [ef55]c9 06CMP #$06; Is it >= 6 (out of bounds)? [ef57]b0 09BCS @_return; If so, return.
; ; Load the address of the handler and push as the new address ; to run. ;
[ef59]a8TAY; Y = A [ef5a]b9 64 efLDA a:SPECIAL_SCREEN_EVENT_LOOKUP_TABLE+1,Y; Load the lower byte of the handler address. [ef5d]48PHA; Push it to the stack. [ef5e]b9 63 efLDA a:SPECIAL_SCREEN_EVENT_LOOKUP_TABLE,Y; Load the upper byte. [ef61]48PHA; Push it. [ef62]@_return: [ef62]60RTS
;============================================================================ ; 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]68 efpointer ScreenEvents_HandlePathToMasconEvent-1; [0]: Handle pushable block on path to Mascon. [ef65]9d efpointer ScreenEvents_HandleBoss-1; [1]: Handle a standard boss battle. [ef67]d3 efpointer ScreenEvents_HandleFinalBossKilled-1; [2]: Handle end-game sequence after killing the final boss.
;============================================================================ ; 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:
; ; Check if the path to Mascon has been opened. ;
[ef69]ad 2d 04LDA a:Quests; Load the list of completed quests. [ef6c]29 20AND #$20; Is the Mascon path completed? [ef6e]f0 12BEQ @_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]a9 ffLDA #$ff [ef72]8d 2e 04STA a:CurrentScreen_SpecialEventID; Unset the special event ID. [ef75]a9 01LDA #$01 [ef77]85 d4STA PathToMascon_Opening; Set the path to Mascon as opened. [ef79]a9 56LDA #$56 [ef7b]85 d5STA PathToMascon_FountainCoverPos; Set the pushable block to X=6, Y=5. [ef7d]a2 00LDX #$00 [ef7f]4c f5 d6JMP 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]a9 ffLDA #$ff [ef84]8d 2e 04STA a:CurrentScreen_SpecialEventID; Unset the special event ID. [ef87]60RTS
;============================================================================ ; 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]85 00STA Temp_00 [ef8a]a2 07LDX #$07 [ef8c]@_loop: [ef8c]bd cc 02LDA a:CurrentSprites_Entities,X [ef8f]c9 ffCMP #$ff [ef91]f0 06BEQ @_prepareNextIter [ef93]c5 00CMP Temp_00 [ef95]f0 02BEQ @_prepareNextIter [ef97]38SEC [ef98]60RTS [ef99]@_prepareNextIter: [ef99]caDEX [ef9a]10 f0BPL @_loop [ef9c]18CLC [ef9d]60RTS
;============================================================================ ; 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:
; ; 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]ad 2e 04LDA a:CurrentScreen_SpecialEventID [efa1]30 0cBMI @_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]09 80ORA #$80; Set bit 7 to only run this conditional on the first game tick. [efa5]8d 2e 04STA a:CurrentScreen_SpecialEventID; Save it. [efa8]20 bf efJSR LimitItemDurations; Cap item durations within bounds. [efab]a9 0aLDA #$0a [efad]85 faSTA Music_Current; Set the music to the boss battle music.
; ; Check if there's a boss on screen. ;
[efaf]@_checkForBoss: [efaf]20 f8 efJSR Sprites_HasBoss; Check for a boss. [efb2]b0 0aBCS @_return; If there is one, we're done. Return.
; ; The boss has been defeated. Restore the game state. ;
[efb4]ad d1 03LDA a:Areas_DefaultMusic; Load the default music for the area. [efb7]85 faSTA Music_Current; Set it as the current music. [efb9]a9 ffLDA #$ff [efbb]8d 2e 04STA a:CurrentScreen_SpecialEventID; Clear the special event ID so this doesn't run again. [efbe]@_return: [efbe]60RTS
;============================================================================ ; 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]ad 27 04LDA a:DurationOintment; Load the Ointment duration. [efc2]30 05BMI @_checkHourGlass; If >= 0, move on to the Hour Glass. [efc4]a9 00LDA #$00 [efc6]8d 27 04STA a:DurationOintment; Set the Ointment duration to 0. [efc9]@_checkHourGlass: [efc9]ad 2a 04LDA a:DurationHourGlass; Load the Hour Glass duration. [efcc]30 05BMI @_return; If >= 0, we're done. [efce]a9 00LDA #$00 [efd0]8d 2a 04STA a:DurationHourGlass; Set the Hour Glass duration to 0. [efd3]@_return: [efd3]60RTS
;============================================================================ ; 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]20 0b f0JSR Sprites_HasCurrentSprites; Are there sprites on screen? [efd7]b0 0cBCS @_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]a9 07LDA #$07; 0x07 == Eolis music [efdb]85 faSTA Music_Current; Set as the current music.
; ; Play the "Player got hit" sound. ;
[efdd]a9 04LDA #$04; 0x04 == Player hit sound effect. [efdf]20 e4 d0JSR Sound_PlayEffect; Play it. [efe2]4c ac d9JMP EndGame_Begin; Begin the end-game sequence. [efe5]@_return: [efe5]60RTS
;============================================================================ ; 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]85 00STA Temp_00; Temp_00 = Sprite type for comparison. [efe8]a0 07LDY #$07; Y = 7
; ; Check the entity for this index in the current sprites list. ;
[efea]@_spriteLoop: [efea]b9 cc 02LDA a:CurrentSprites_Entities,Y; Load the entity of the sprite at loop index. [efed]c5 00CMP Temp_00; Is it the entity the caller is checking for? [efef]d0 02BNE @_prepareNextLoopIter; If not, prepare for the next loop iteration.
; ; The sprite entity is on the screen. Clear carry as the result. ;
[eff1]18CLC; Return false. [eff2]60RTS [eff3]@_prepareNextLoopIter: [eff3]88DEY; Y-- [eff4]10 f4BPL @_spriteLoop; If Y >= 0, loop.
; ; The sprite entity is not on the screen. Set carry as the ; result. ;
[eff6]38SEC; Return true. [eff7]60RTS
;============================================================================ ; 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]a2 07LDX #$07; X = 7 (loop counter).
; ; Check if this sprite is a boss. ;
[effa]@_loop: [effa]bc cc 02LDY a:CurrentSprites_Entities,X; Load the sprite entity at that index. [effd]b9 44 b5LDA a:SPRITE_CATEGORIES_BY_ENTITY,Y; Load the category for the entity. [f000]c9 07CMP #$07; Is it 7 (boss)? [f002]d0 02BNE @_notBoss; If not, jump.
; ; There is a boss on screen. Return true. ;
[f004]38SEC; Return true. [f005]60RTS [f006]@_notBoss: [f006]caDEX; X-- [f007]10 f1BPL @_loop; If X >= 0, loop.
; ; There is no boss on screen. Return false. ;
[f009]18CLC; Return false. [f00a]60RTS
;============================================================================ ; 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]a0 07LDY #$07; Y = 7 (loop counter). [f00d]@_loop: [f00d]b9 cc 02LDA a:CurrentSprites_Entities,Y; Load the sprite entity at that index. [f010]c9 ffCMP #$ff; Is it unset? [f012]f0 02BEQ @_prepareNextLoopIter; If yes, jump to prepare for the next loop iteration.
; ; A sprite entity was found. Return true. ;
[f014]38SEC; Return true. [f015]60RTS [f016]@_prepareNextLoopIter: [f016]88DEY; Y-- [f017]10 f4BPL @_loop; If Y >= 0, loop.
; ; A sprite entity was not found. Return false. ;
[f019]18CLC; Return false. [f01a]60RTS
;============================================================================ ; 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]a8TAY; Y = A (appearance offset).
; ; Save the current ROM bank and switch to bank 7. ;
[f01c]ad 00 01LDA a:CurrentROMBank; Load the current ROM bank. [f01f]48PHA; Push it to the stack. [f020]a2 07LDX #$07; Bank 7 = Sprites [f022]20 1a ccJSR 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]98TYA; A = Y (appearance offset). [f026]0aASL A; Convert to a word boundary for an index into the tileinfo table. [f027]a8TAY; Y = A (index). [f028]08PHP; Push flags to the stack. [f029]ad 0a 80LDA a:PORTRAIT_APPEARANCES_OFFSET; Load the lower byte of the portraits tileinfo address. [f02c]85 02STA Temp_Addr_L; Store as the lower byte in the read address. [f02e]ad 0b 80LDA a:PORTRAIT_APPEARANCES_OFFSET+1; Load the upper byte of the portraits tileinfo address. [f031]28PLP; Pop flags from the stack. [f032]69 80ADC #$80; Add 0x80 to the upper byte. [f034]85 03STA Temp_Addr_U; And store it. [f036]4c 72 f0JMP 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]a8TAY [f03a]ad 00 01LDA a:CurrentROMBank [f03d]48PHA [f03e]a2 07LDX #$07 [f040]20 1a ccJSR MMC1_UpdateROMBank [f043]98TYA
; ; 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]0aASL A [f045]a8TAY [f046]08PHP [f047]ad 08 80LDA a:PLAYER_APPEARANCES_OFFSET [f04a]85 02STA Temp_Addr_L [f04c]ad 09 80LDA a:PLAYER_APPEARANCES_OFFSET+1 [f04f]28PLP [f050]69 80ADC #$80 [f052]85 03STA Temp_Addr_U [f054]4c 72 f0JMP Sprite_Draw
;============================================================================ ; TODO: Document Sprite_SetAppearanceAddrFromOffset ; ; INPUTS: ; A ; ; OUTPUTS: ; TODO ; ; XREFS: ; Sprite_EnterNextAppearancePhase ; CastMagic_SetAppearance ; UI_DrawPromptInputSymbol ;============================================================================
[f057]Sprite_SetAppearanceAddrFromOffset:
; ; Switch to ROM bank 7 (PRG). ;
[f057]a8TAY; Y = A (offset) [f058]ad 00 01LDA a:CurrentROMBank; Load the current ROM bank. [f05b]48PHA; Push it to the stack. [f05c]a2 07LDX #$07 [f05e]20 1a ccJSR 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]98TYA; A = Y (restore offset) [f062]0aASL A; Multiply offset by 2, to get word boundaries in the lookup table. [f063]a8TAY; Y = A (updated offset) [f064]08PHP; Push flags and stack pointer. [f065]ad 06 80LDA a:SPRITE_APPEARANCES_OFFSET; Load the lower byte of the address to read from. [f068]85 02STA Temp_Addr_L; Store it for processing. [f06a]ad 07 80LDA a:SPRITE_APPEARANCES_OFFSET+1; Load the upper byte. [f06d]28PLP; Pop the flags and stack pointer. [f06e]69 80ADC #$80; Add 0x80 + carry to the upper byte. [f070]85 03STA 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:
; ; Load the offset within our starting address stored in ; Temp_Addr_L. ;
[f072]b1 02LDA (Temp_Addr_L),Y; Load the offset for the lower byte. [f074]85 3aSTA Sprite_TileInfo_ReadAddr_L; Store it. [f076]c8INY; Y++ (offset) [f077]b1 02LDA (Temp_Addr_L),Y; Load the upper byte. [f079]69 80ADC #$80; Add 0x80 + carry to it. [f07b]85 3bSTA 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]a0 00LDY #$00; Y = 0 (read offset) [f07f]b1 3aLDA (Sprite_TileInfo_ReadAddr_L),Y; Load the value at that address. [f081]29 0fAND #$0f; Discard the upper nibble. [f083]85 40STA Temp_Sprites_ColsRemaining; Store it. [f085]b1 3aLDA (Sprite_TileInfo_ReadAddr_L),Y; Load it again. [f087]4aLSR A; Move upper nibble to lower. [f088]4aLSR A [f089]4aLSR A [f08a]4aLSR A [f08b]85 41STA Temp_Sprites_RowsRemaining; Store it.
; ; Initialize Sprite_TileInfo_OffsetX and ; Sprite_TileInfo_OffsetY ; to 0 by default. ;
[f08d]a9 00LDA #$00; A = 0 [f08f]85 3eSTA Sprite_TileInfo_OffsetX; Set to 0. [f091]85 3fSTA 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]c8INY; Y++ (read offset) [f094]b1 3aLDA (Sprite_TileInfo_ReadAddr_L),Y; Load the X offset byte. [f096]10 02BPL @_calcX; If > 0, jump. [f098]c6 3eDEC Sprite_TileInfo_OffsetX; Set to 0xFF. This will set C=1 for the add below. [f09a]@_calcX: [f09a]18CLC [f09b]65 27ADC Arg_DrawSprite_PosX; Add Arg_DrawSprite_PosX to the X offset (which may be negative, extending left). [f09d]85 3cSTA Temp_Sprites_NormXPos; Store it.
; ; Calculate the new value for Sprite_TileInfo_OffsetX. ;
[f09f]a9 00LDA #$00; A = 0 [f0a1]65 3eADC Sprite_TileInfo_OffsetX; Add our stored value. [f0a3]85 3eSTA Sprite_TileInfo_OffsetX; Store it back out.
; ; Update the drawn X position to factor in any screen scrolling ; offsets. ;
[f0a5]a5 3cLDA Temp_Sprites_NormXPos; Load the new calculated X position. [f0a7]38SEC [f0a8]e5 0cSBC PPU_ScrollX; Subtract any scrolling offset that may be set. [f0aa]85 3cSTA 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]c8INY; Y++ (read offset) [f0ad]b1 3aLDA (Sprite_TileInfo_ReadAddr_L),Y; Load the Y offset byte. [f0af]10 02BPL @_calcY; If > 0, jump. [f0b1]c6 3fDEC Sprite_TileInfo_OffsetY; Set to 0xFF. This will set C=1 for the add below. [f0b3]@_calcY: [f0b3]18CLC [f0b4]65 28ADC Arg_DrawSprite_PosY; Add Arg_DrawSprite_PosY to the Y offset (which may be negative, extending up). [f0b6]85 3dSTA Temp_Sprites_NormYPos; Store it.
; ; Add Carry to the offset, if it's set. ;
[f0b8]a9 00LDA #$00 [f0ba]65 3fADC Sprite_TileInfo_OffsetY; A = Y offset + carry (from above). [f0bc]85 3fSTA Sprite_TileInfo_OffsetY; Store it.
; ; And then always offset by 32 pixels (the HUD height). ;
[f0be]a5 3dLDA Temp_Sprites_NormYPos; Load the normalized Y position. [f0c0]18CLC [f0c1]69 20ADC #$20; Add 32 (HUD height in pixels). [f0c3]85 3dSTA 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]a5 3fLDA Sprite_TileInfo_OffsetY; Load the Y offset. [f0c7]69 00ADC #$00; Add Carry, if set. [f0c9]85 3fSTA 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]c8INY; Y++ (read offset). [f0cc]b1 3aLDA (Sprite_TileInfo_ReadAddr_L),Y; Load the pivot point in pixels. [f0ce]85 02STA 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]a5 3aLDA Sprite_TileInfo_ReadAddr_L; Load the lower byte of the sprite address. [f0d2]18CLC [f0d3]69 04ADC #$04; Advance by 4 (skipping loaded state). [f0d5]85 3aSTA Sprite_TileInfo_ReadAddr_L; Set as the new lower byte of the address. [f0d7]a5 3bLDA Sprite_TileInfo_ReadAddr_U; Load the upper byte. [f0d9]69 00ADC #$00; Add the carry, if lower overflowed. [f0db]85 3bSTA Sprite_TileInfo_ReadAddr_U; Set it.
; ; We'll continue on based on whether the sprite is flipped. ;
[f0dd]a5 29LDA CurrentSprite_FlipMask; Load the flip mask. [f0df]29 c0AND #$c0; Keep only the horiz/vert flip bits. [f0e1]85 29STA CurrentSprite_FlipMask; Store as a new flip mask. [f0e3]a5 29LDA CurrentSprite_FlipMask; Load the resulting flip mask. [f0e5]29 40AND #$40; Check if it's flipped horizontally. [f0e7]f0 03BEQ Sprite_Draw_Standard; If not, draw in standard orientation. [f0e9]4c 81 f1JMP 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:
; ; The sprite is not flipped. ;
[f0ec]a9 00LDA #$00; A = 0. [f0ee]85 43STA Temp_Sprites_TileYOffset; Store it as the starting tiles Y offset. [f0f0]a8TAY; Y = A (0, loop counter). [f0f1]@_drawRowLoop: [f0f1]a5 40LDA Temp_Sprites_ColsRemaining; Load the number of tile columns remaining. [f0f3]48PHA; Push it to the stack. [f0f4]a9 00LDA #$00; A = 0. [f0f6]85 42STA Temp_Sprites_TileXOffset; Store as the start tiles X offset.
; ; Check if the current sprite data value is not 0xFF (unset). ;
[f0f8]@_drawColLoop: [f0f8]b1 3aLDA (Sprite_TileInfo_ReadAddr_L),Y; Load the value at the sprite data read address. [f0fa]c9 ffCMP #$ff; Is it 0xFF? [f0fc]f0 64BEQ @_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]a5 39LDA Sprites_SlotsFull; Can we add sprites this frame? [f100]d0 5fBNE @_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]a6 42LDX Temp_Sprites_TileXOffset; X = tile X offset. [f104]a5 3cLDA Temp_Sprites_NormXPos; A = normalized X position. [f106]7d 3d f2ADC a:SPRITE_TILE_BLOCK_POSITIONS,X; A += the offset from the lookup table at X. [f109]85 00STA Temp_00; Store it temporarily.
; ; Check if this tile would fit on screen in the X direction. ;
[f10b]a5 3eLDA Sprite_TileInfo_OffsetX; Load the default X position. [f10d]69 00ADC #$00; Add carry (from overflow), if set. [f10f]d0 50BNE @_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]a6 43LDX Temp_Sprites_TileYOffset; X = tile Y offset. [f113]a5 3dLDA Temp_Sprites_NormYPos; A = normalized Y position. [f115]7d 3d f2ADC a:SPRITE_TILE_BLOCK_POSITIONS,X; A += the offset from the lookup table at X. [f118]85 01STA Temp_01; Store it temporarily.
; ; Check if this tile would fit on screen in the Y direction. ;
[f11a]a5 3fLDA Sprite_TileInfo_OffsetY; Load the default Y position. [f11c]69 00ADC #$00; Add carry (from overflow), if set. [f11e]d0 41BNE @_endColLoop; If it doesn't fit on screen, jump.
; ; The sprite fits on screen. ; ; We'll begin writing the sprite to DMA. ;
[f120]98TYA; A = Y (loop counter) [f121]48PHA; Push our loop counter to the stack. [f122]a5 25LDA Sprites_SpriteSlot; Load the slot for this sprite. [f124]0aASL A; Multiply by 4 (for Y, tile ID, attributes, X bytes). [f125]0aASL A [f126]45 1cEOR Sprites_StartSlotRange
; ; Write the sprite Y position to DMA. ;
[f128]aaTAX; X = A (read offset) [f129]a5 01LDA Temp_01; Load the Y position. [f12b]9d 00 07STA a: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]e8INX; X++ (read offset) [f12f]b1 3aLDA (Sprite_TileInfo_ReadAddr_L),Y; Load the tile index. [f131]18CLC [f132]65 33ADC Sprites_PPUOffset; Add the PPU starting offset for the list of tiles. [f134]9d 00 07STA a: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]e8INX; X++ (read offset) [f138]c8INY; Y++ (loop counter) [f139]b1 3aLDA (Sprite_TileInfo_ReadAddr_L),Y; Load the attribute. [f13b]45 29EOR CurrentSprite_FlipMask; XOR with the flip mask. [f13d]85 01STA 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]a5 42LDA Temp_Sprites_TileXOffset; Load the tile X offset. [f141]29 01AND #$01; Convert to an even/odd value for the visibility lookup table. [f143]a8TAY; Y = result. [f144]a5 26LDA MovingSpriteVisibility; Load the moving visibility of the sprite. [f146]39 24 f2AND a:DRAW_SPRITE_VISIBILITY_MASK,Y; AND with the lookup table, determining priority. [f149]f0 06BEQ @_storeAttribute; If non-0, keep foreground priority. Else...
; ; The tile is obscured. Set the sprite to be a ; background sprite. ;
[f14b]a5 01LDA Temp_01; Load the attribute calculated above. [f14d]09 20ORA #$20; Set background priority. [f14f]85 01STA Temp_01; And store it.
; ; Write the tile attribute to DMA. ;
[f151]@_storeAttribute: [f151]a5 01LDA Temp_01; Load the calculated attribute. [f153]9d 00 07STA a:SPRITE_0_RANGE_1_START,X; Store the tile attributes to DMA.
; ; Write the sprite X position to DMA. ;
[f156]e8INX; X++ (read offset) [f157]a5 00LDA Temp_00; Load the X position. [f159]9d 00 07STA a:SPRITE_0_RANGE_1_START,X; Write the X coordinate to DMA.
; ; Prepare for the next column. ;
[f15c]20 28 f2JSR Sprites_IncNextSpriteSlot; Finish up with this sprite's state. [f15f]68PLA; Pull A (loop counter) from stack. [f160]a8TAY; Y = A (loop counter) [f161]@_endColLoop: [f161]c8INY; Y++ (loop counter) [f162]@_prepareNextCol: [f162]c8INY; Y++ (loop counter) [f163]e6 42INC Temp_Sprites_TileXOffset; Increment the tile X offset. [f165]c6 40DEC Temp_Sprites_ColsRemaining; Decrement the number of columns remaining. [f167]10 8fBPL @_drawColLoop; If there are columns remaining, loop.
; ; We're on the next row. Reset the column count and advance ; the row counter. ;
[f169]68PLA; Pop the original total columns remaining. [f16a]85 40STA Temp_Sprites_ColsRemaining; Store as the new columns count for the next row. [f16c]e6 43INC Temp_Sprites_TileYOffset; Increment the tile Y offset. [f16e]c6 41DEC Temp_Sprites_RowsRemaining; Decrement the number of rows remaining. [f170]30 03BMI Sprite_Draw_Finish; If there are no more rows, finish the drawing. [f172]4c f1 f0JMP @_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]68PLA [f176]aaTAX [f177]20 1a ccJSR MMC1_UpdateROMBank [f17a]a9 00LDA #$00 [f17c]85 33STA Sprites_PPUOffset [f17e]85 26STA MovingSpriteVisibility [f180]60RTS
;============================================================================ ; TODO: Document Sprite_Draw_FlippedHoriz ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; Sprite_Draw ;============================================================================
[f181]Sprite_Draw_FlippedHoriz:
; ; The sprite is flipped. ;
[f181]a9 00LDA #$00 [f183]85 05STA Temp_05; Set Temp_05 = 0. [f185]a4 40LDY Temp_Sprites_ColsRemaining; Load the number of columns remaining for a row. [f187]c8INY; Y++ (convert from 0-based count) [f188]98TYA; A = result [f189]0aASL A; Multiply the columns by 8. [f18a]0aASL A [f18b]0aASL A [f18c]85 04STA Temp_04; Store for temporary use. [f18e]a5 02LDA Temp_Addr_L; A = 4th byte from the sprite information. [f190]0aASL A; A = A * 2 [f191]38SEC [f192]e5 04SBC Temp_04; A = A - normalized column value. [f194]10 02BPL @LAB_PRG15_MIRROR__f198; If negative, jump. [f196]c6 05DEC Temp_05; Else, it's positive. Decrement Temp_05. [f198]@LAB_PRG15_MIRROR__f198: [f198]18CLC [f199]65 3cADC Temp_Sprites_NormXPos; A = A + normalized X position [f19b]85 3cSTA Temp_Sprites_NormXPos; Store as the new position. [f19d]a5 3eLDA Sprite_TileInfo_OffsetX [f19f]65 05ADC Temp_05 [f1a1]85 3eSTA Sprite_TileInfo_OffsetX [f1a3]a9 00LDA #$00 [f1a5]85 43STA Temp_Sprites_TileYOffset [f1a7]a8TAY [f1a8]@LAB_PRG15_MIRROR__f1a8: [f1a8]a5 40LDA Temp_Sprites_ColsRemaining [f1aa]85 42STA Temp_Sprites_TileXOffset [f1ac]@LAB_PRG15_MIRROR__f1ac: [f1ac]b1 3aLDA (Sprite_TileInfo_ReadAddr_L),Y [f1ae]c9 ffCMP #$ff [f1b0]f0 64BEQ @LAB_PRG15_MIRROR__f216 [f1b2]a5 39LDA Sprites_SlotsFull [f1b4]d0 5fBNE @LAB_PRG15_MIRROR__f215 [f1b6]a6 42LDX Temp_Sprites_TileXOffset [f1b8]a5 3cLDA Temp_Sprites_NormXPos [f1ba]7d 3d f2ADC a:SPRITE_TILE_BLOCK_POSITIONS,X [f1bd]85 00STA Temp_00 [f1bf]a5 3eLDA Sprite_TileInfo_OffsetX [f1c1]69 00ADC #$00 [f1c3]d0 50BNE @LAB_PRG15_MIRROR__f215 [f1c5]a6 43LDX Temp_Sprites_TileYOffset [f1c7]a5 3dLDA Temp_Sprites_NormYPos [f1c9]7d 3d f2ADC a:SPRITE_TILE_BLOCK_POSITIONS,X [f1cc]85 01STA Temp_01 [f1ce]a5 3fLDA Sprite_TileInfo_OffsetY [f1d0]69 00ADC #$00 [f1d2]d0 41BNE @LAB_PRG15_MIRROR__f215 [f1d4]98TYA [f1d5]48PHA [f1d6]a5 25LDA Sprites_SpriteSlot [f1d8]0aASL A [f1d9]0aASL A [f1da]45 1cEOR Sprites_StartSlotRange [f1dc]aaTAX [f1dd]a5 01LDA Temp_01 [f1df]9d 00 07STA a:SPRITE_0_RANGE_1_START,X [f1e2]e8INX [f1e3]b1 3aLDA (Sprite_TileInfo_ReadAddr_L),Y [f1e5]18CLC [f1e6]65 33ADC Sprites_PPUOffset [f1e8]9d 00 07STA a:SPRITE_0_RANGE_1_START,X [f1eb]e8INX [f1ec]c8INY [f1ed]b1 3aLDA (Sprite_TileInfo_ReadAddr_L),Y [f1ef]45 29EOR CurrentSprite_FlipMask [f1f1]85 01STA Temp_01 [f1f3]a5 42LDA Temp_Sprites_TileXOffset [f1f5]29 01AND #$01 [f1f7]a8TAY [f1f8]a5 26LDA MovingSpriteVisibility [f1fa]39 26 f2AND a:BYTE_ARRAY_PRG15_MIRROR__f226,Y [f1fd]f0 06BEQ @LAB_PRG15_MIRROR__f205 [f1ff]a5 01LDA Temp_01 [f201]09 20ORA #$20 [f203]85 01STA Temp_01 [f205]@LAB_PRG15_MIRROR__f205: [f205]a5 01LDA Temp_01 [f207]9d 00 07STA a:SPRITE_0_RANGE_1_START,X [f20a]e8INX [f20b]a5 00LDA Temp_00 [f20d]9d 00 07STA a:SPRITE_0_RANGE_1_START,X [f210]20 28 f2JSR Sprites_IncNextSpriteSlot [f213]68PLA [f214]a8TAY [f215]@LAB_PRG15_MIRROR__f215: [f215]c8INY [f216]@LAB_PRG15_MIRROR__f216: [f216]c8INY [f217]c6 42DEC Temp_Sprites_TileXOffset [f219]10 91BPL @LAB_PRG15_MIRROR__f1ac [f21b]e6 43INC Temp_Sprites_TileYOffset [f21d]c6 41DEC Temp_Sprites_RowsRemaining [f21f]10 87BPL @LAB_PRG15_MIRROR__f1a8 [f221]4c 75 f1JMP Sprite_Draw_Finish
; ; XREFS: ; Sprite_Draw_Standard ;
[f224]DRAW_SPRITE_VISIBILITY_MASK: [f224]01.byte $01; [0]: Trailing visibility [f225]02.byte $02; [1]: Leading visibility
; ; XREFS: ; Sprite_Draw_FlippedHoriz ;
[f226]BYTE_ARRAY_PRG15_MIRROR__f226: [f226]02.byte $02; [0]: [f227]01.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:
; ; Alternate between sprites 0-31 and sprites 32-63. ; ; The bit being flipped is bit 5, value 32. ;
[f228]a5 25LDA Sprites_SpriteSlot; Load the sprite slot ID. [f22a]49 20EOR #$20; Cap the value to < 32. [f22c]85 25STA 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]29 20AND #$20; Check if we're in 32-63. [f230]d0 0aBNE @_return; If so, return.
; ; It's in sprite range 0-31. ;
[f232]e6 25INC Sprites_SpriteSlot; Increase the sprite slot. [f234]a5 25LDA Sprites_SpriteSlot; Load the new slot. [f236]c9 20CMP #$20; Are there slots left? [f238]90 02BCC @_return; If so, return.
; ; The slots are full. Mark it. ;
[f23a]e6 39INC Sprites_SlotsFull; Mark slots as full. [f23c]@_return: [f23c]60RTS
; ; XREFS: ; Sprite_Draw_FlippedHoriz ; Sprite_Draw_Standard ;
[f23d]SPRITE_TILE_BLOCK_POSITIONS: [f23d]00.byte $00; [0]: [f23e]08.byte $08; [1]: [f23f]10.byte $10; [2]: [f240]18.byte $18; [3]: [f241]20.byte $20; [4]: [f242]28.byte $28; [5]: [f243]30.byte $30; [6]: [f244]38.byte $38; [7]: [f245]40.byte $40; [8]: [f246]48.byte $48; [9]: [f247]50.byte $50; [10]: [f248]58.byte $58; [11]: [f249]60.byte $60; [12]: [f24a]68.byte $68; [13]: [f24b]70.byte $70; [14]: [f24c]78.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:
; ; Check if a portrait ID is set. ;
[f24d]8d c7 03STA a:IScript_PortraitID; Set the current portrait ID. [f250]aaTAX; X = result. [f251]30 2aBMI @_return; If unset, return.
; ; The portrait ID is set. Load the address and wait for an ; OAM reset. ;
[f253]20 e3 f2JSR IScripts_LoadPortraitTilesAddress; Load the tiles address for this portrait ID. [f256]20 9a cbJSR Game_WaitForOAMReset; Wait for OAM reset.
; ; Save the sprite palette and switch to palette 2 for the ; portrait. ;
[f259]ad d4 03LDA a:Palette_SpritePaletteIndex; Load the current sprite palette index [f25c]8d d3 03STA a:Palette_SavedIndex; And save it for later. [f25f]a9 02LDA #$02 [f261]20 62 d0JSR Screen_LoadSpritePalette; Load palette 2. [f264]20 90 d0JSR PPUBuffer_WritePalette; Write the palette to the PPU.
; ; Set the target PPU address to draw the portrait to. ; This will be $0900. ;
[f267]a9 09LDA #$09; Upper byte = 0x09. [f269]85 e9STA PPU_TargetAddr_U; Set it. [f26b]a9 00LDA #$00; Lower byte = 0x00. [f26d]85 e8STA PPU_TargetAddr; Set it.
; ; Begin drawing the portrait. This will loop up to ; 256 times, drawing 16 tiles each. ;
[f26f]a0 00LDY #$00; Y = 0 (loop counter). [f271]@_drawLoop: [f271]98TYA; A = Y (loop counter). [f272]48PHA; Push it to the stack. [f273]20 16 f3JSR IScripts_DrawPortraitTileToPPU; Draw a 16-byte tile to the PPU. [f276]b0 06BCS @_finishDrawing [f278]68PLA; Pop the loop counter from the stack. [f279]a8TAY; Set back in Y. [f27a]c8INY; Y++ [f27b]d0 f4BNE @_drawLoop; If not wrapped to 0, loop. [f27d]@_return: [f27d]60RTS
; ; The portrait is finished. ;
[f27e]@_finishDrawing: [f27e]68PLA; Pop the loop counter from the stack. [f27f]a8TAY; Set back in Y. [f280]60RTS
;============================================================================ ; 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]a9 ffLDA #$ff [f283]8d c7 03STA a:IScript_PortraitID; Clear the portrait ID. [f286]20 9a cbJSR Game_WaitForOAMReset; Wait for OAM reset. [f289]ad d3 03LDA a:Palette_SavedIndex; Load the saved sprite palette ID. [f28c]20 62 d0JSR Screen_LoadSpritePalette; Load that palette's data. [f28f]20 90 d0JSR PPUBuffer_WritePalette; Write to the PPU buffer.
; ; Switch to bank 14 and run ; GameLoop_LoadSpriteImages ; (from bank 15). ;
[f292]20 59 f8JSR MMC1_LoadBankAndJump; Load sprite imagesin bank 14. [f295]0e.byte BANK_14_LOGIC; Bank = 14 Address = GameLoop_LoadSpriteImages [f296]8c c2pointer GameLoop_LoadSpriteImages-1; GameLoop_LoadSpriteImages [f298]@_afterFarJump: [f298]60RTS
; ; The portrait ID is unset. There's nothing to do. ; ; ; XREFS: ; IScripts_DrawPortraitAnimationFrame ;
[f299]IScripts_DrawPortraitAnimationFrame_Unset: [f299]68PLA; Clean up the stack, popping the pushed frame. [f29a]60RTS
;============================================================================ ; 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]48PHA; Push the frame to the stack. [f29c]ad c7 03LDA a:IScript_PortraitID; Load the current portrait ID. [f29f]30 f8BMI IScripts_DrawPortraitAnimationFrame_Unset; If 0xFF, jump to finish.
; ; There's an active portrait. Begin preparing by clearing ; the flip mask state. ;
[f2a1]a9 00LDA #$00 [f2a3]85 29STA CurrentSprite_FlipMask; Set the flip mask to 0 (not flipped).
; ; Update the general portrait image. ;
[f2a5]a9 90LDA #$90 [f2a7]85 33STA Sprites_PPUOffset; Set the PPU offset to $0090. [f2a9]20 d5 f2JSR IScripts_GetPortraitOffset; Get the portrait offset into the tileinfo for the ID. [f2ac]20 1b f0JSR Sprite_DrawPortraitPartAppearance; Draw as the portrait.
; ; Update the eye animation. ; ; This will switch to new tiles every frame. ;
[f2af]a9 90LDA #$90 [f2b1]85 33STA Sprites_PPUOffset; Set the PPU offset to $0090. [f2b3]68PLA; Pull the frame from the stack. [f2b4]48PHA; Push it again. We still have it in A. [f2b5]29 01AND #$01; Generate an even/odd index from it. [f2b7]aaTAX; X = result. [f2b8]20 d5 f2JSR IScripts_GetPortraitOffset; Get the offset into the table for the portrait ID. [f2bb]18CLC [f2bc]7d df f2ADC a:PORTRAIT_EYE_APPEARANCE_OFFSETS,X; Add the eyes animation offset for this even/odd frame. [f2bf]20 1b f0JSR Sprite_DrawPortraitPartAppearance; Draw as the eyes.
; ; Update the mouth animation. ; ; This will switch to new tiles every other frame. ;
[f2c2]a9 90LDA #$90 [f2c4]85 33STA Sprites_PPUOffset; Set the PPU offset to $0090. [f2c6]68PLA; Pull the frame from the stack. [f2c7]4aLSR A; Divide the frame by 2 (reducing the animation rate to switch every other frame). [f2c8]29 01AND #$01; Convert that to an even/odd value. [f2ca]aaTAX; X = result. [f2cb]20 d5 f2JSR IScripts_GetPortraitOffset; Get the offset into the table for the portrait ID. [f2ce]18CLC [f2cf]7d e1 f2ADC a:PORTRAIT_MOUTH_APPEARANCE_OFFSETS,X; Add the mouth animation offset for this even/odd frame. [f2d2]4c 1b f0JMP 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]ad c7 03LDA a:IScript_PortraitID; Load the current portrait ID. [f2d8]0aASL A; Multiply by 4. [f2d9]0aASL A [f2da]18CLC [f2db]6d c7 03ADC a:IScript_PortraitID; And add the ID again (effectively multiplying by a total of 5). [f2de]60RTS; Return it as A.
;============================================================================ ; Offsets for the portrait eye blinking animation frames. ; ; XREFS: ; IScripts_DrawPortraitAnimationFrame ;============================================================================
; ; XREFS: ; IScripts_DrawPortraitAnimationFrame ;
[f2df]PORTRAIT_EYE_APPEARANCE_OFFSETS: [f2df]01.byte $01; [0]: [f2e0]02.byte $02; [1]:
;============================================================================ ; Offsets for the portrait mouth movement animation frames. ; ; XREFS: ; IScripts_DrawPortraitAnimationFrame ;============================================================================
; ; XREFS: ; IScripts_DrawPortraitAnimationFrame ;
[f2e1]PORTRAIT_MOUTH_APPEARANCE_OFFSETS: [f2e1]03.byte $03; [0]: [f2e2]04.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:
; ; Switch to bank 8 for the portrait sprites. ;
[f2e3]ad 00 01LDA a:CurrentROMBank; Load the current ROM bank. [f2e6]48PHA; Push it to the stack. [f2e7]a2 08LDX #$08; Bank 8 = Sprites [f2e9]20 1a ccJSR MMC1_UpdateROMBank; Switch to the bank.
; ; Convert a portrait ID to a word boundary for later lookup. ;
[f2ec]ad c7 03LDA a:IScript_PortraitID; Load the portrait ID. [f2ef]0aASL A; Convert to a word boundary. [f2f0]a8TAY; Y = result
; ; Load the address for the portrait tiles table. ;
[f2f1]ad 0e 80LDA a:TILES_PORTRAITS_ADDRS_INDEX_REF; Load the lower byte of the start address for the portrait tiles. [f2f4]85 02STA Temp_Addr_L; Store as the lower byte. [f2f6]ad 0f 80LDA a:TILES_PORTRAITS_ADDRS_INDEX_REF+1; Load the upper byte of the portrait address. [f2f9]18CLC [f2fa]69 80ADC #$80; Add 0x80 to it. [f2fc]85 03STA 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]b1 02LDA (Temp_Addr_L),Y; Load the lower byte of the address based on the potrait ID. [f300]48PHA; Push it to the stack. [f301]c8INY; Y++ (next offset) [f302]b1 02LDA (Temp_Addr_L),Y; Load the upper byte of the tile. [f304]18CLC [f305]69 80ADC #$80; Add 0x80 to it. [f307]85 03STA Temp_Addr_U; Store as the new upper byte. [f309]68PLA; Pop the lower byte. [f30a]85 02STA Temp_Addr_L; Store as the lower byte.
; ; Switch back to the stored bank. ;
[f30c]4c d6 f3JMP 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]68PLA; Pop the saved bank from the stack. [f310]aaTAX; X = A (bank) [f311]20 1a ccJSR MMC1_UpdateROMBank; Switch to it. [f314]38SEC; Set C=1 for the result. [f315]60RTS; 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:
; ; Save the current bank and switch to bank 8. ;
[f316]ad 00 01LDA a:CurrentROMBank; Load the current ROM bank. [f319]48PHA; Push it to the stack. [f31a]a2 08LDX #$08; Bank 8 = Portrait sprites [f31c]20 1a ccJSR MMC1_UpdateROMBank; Switch to the bank.
; ; Load the tile and see if it's set or if it's 0xFF. ;
[f31f]b1 02LDA (Temp_Addr_L),Y; Load the tile index from the tiles address. [f321]c9 ffCMP #$ff; Is it 0xFF? [f323]f0 eaBEQ 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]a9 00LDA #$00; A = 0 (offset). [f327]85 05STA Temp_05 [f329]b1 02LDA (Temp_Addr_L),Y [f32b]0aASL A [f32c]26 05ROL Temp_05 [f32e]0aASL A [f32f]26 05ROL Temp_05 [f331]0aASL A [f332]26 05ROL Temp_05 [f334]0aASL A [f335]26 05ROL Temp_05 [f337]18CLC
; ; Make the above relative to the address stored in ; TILES_PORTRAITS_START_REF (+ $8000). ;
[f338]6d 10 80ADC a:TILES_PORTRAITS_START_REF; Add the tile ID to the lower byte. [f33b]85 04STA Temp_04; Store in Temp_04. [f33d]a5 05LDA Temp_05; Load the calculated upper byte. [f33f]6d 11 80ADC a:TILES_PORTRAITS_START_REF+1; Add the upper byte for the relative start address. [f342]18CLC [f343]69 80ADC #$80; Add 0x80. [f345]85 05STA Temp_05; And store as a new upper byte.
; ; Begin writing to the PPU buffer. ;
[f347]a9 10LDA #$10; Length = 16 (tile bytes). [f349]20 dc cfJSR PPUBuffer_QueueCommandOrLength; Queue that as the PPU buffer length.
; ; Begin populating the buffer with the tile's bytes. ;
[f34c]a0 00LDY #$00; Y = 0 (loop counter). [f34e]@_drawLoop: [f34e]b1 04LDA (Temp_04),Y; Load the next byte for the tile. [f350]9d 00 05STA a:PPUBuffer,X; Store it in the PPU buffer. [f353]e8INX; X++ (destination). [f354]c8INY; Y++ (loop counter). [f355]c0 10CPY #$10; Have we hit 16 bytes? [f357]90 f5BCC @_drawLoop; If not, loop.
; ; All tiles have been drawn. Update the write offset and ; advance the PPU address. ;
[f359]86 20STX PPUBuffer_WriteOffset; Store the new PPU buffer write offset. [f35b]a5 e8LDA PPU_TargetAddr; Load the lower byte of the target address. [f35d]18CLC [f35e]69 10ADC #$10; Advance by 16 bytes. [f360]85 e8STA PPU_TargetAddr; Store it. [f362]a5 e9LDA PPU_TargetAddr_U; Load the upper byte of the target address. [f364]69 00ADC #$00; Add carry, if lower wrapped. [f366]85 e9STA PPU_TargetAddr_U; Store it.
; ; Restore the previous bank. ;
[f368]68PLA; Pop the bank from the stack. [f369]aaTAX; X = A (bank). [f36a]20 1a ccJSR MMC1_UpdateROMBank; Switch to it. [f36d]18CLC; Set C=0 for the result. [f36e]60RTS
;============================================================================ ; 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]c9 1dCMP #$1d; Length of sound IDs array [f371]b0 14BCS @_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]aaTAX; X = new sound ID [f374]ac 21 01LDY a:SoundEffect_Unused_PriorityID; Y = current sound ID [f377]b9 88 f3LDA a:SOUND_PRIORITIES,Y; A = Sound type for current sound from map [f37a]dd 88 f3CMP a:SOUND_PRIORITIES,X; Is this already the current sound? [f37d]f0 06BEQ @_queueNext; If yes, then jump [f37f]90 04BCC @_queueNext; If it's a lower priority (larger number), then jump. [f381]8e 21 01STX a:SoundEffect_Unused_PriorityID; Set as the current sound. [f384]60RTS
; ; Set the sound effect to play at the next hardware interrupt. ;
[f385]@_queueNext: [f385]86 fbSTX SoundEffect_Current; Queue this up as the next effect. [f387]@_return: [f387]60RTS
;============================================================================ ; 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]00.byte $00; [0]: [f389]08.byte $08; [1]: [f38a]15.byte $15; [2]: Hit enemy with weapon [f38b]14.byte $14; [3]: Enemy is dead [f38c]04.byte $04; [4]: Player hit [f38d]19.byte $19; [5]: Magic [f38e]0b.byte $0b; [6]: Open door [f38f]0d.byte $0d; [7]: [f390]10.byte $10; [8]: Item picked up [f391]11.byte $11; [9]: Touched coin [f392]17.byte $17; [10]: Cast magic hit an obstacle [f393]09.byte $09; [11]: Cursor moved [f394]13.byte $13; [12]: Text input [f395]0a.byte $0a; [13]: [f396]07.byte $07; [14]: Password character entered [f397]0c.byte $0c; [15]: Block pushed [f398]12.byte $12; [16]: Coin dropped [f399]0f.byte $0f; [17]: [f39a]0e.byte $0e; [18]: [f39b]06.byte $06; [19]: Filling HP or MP [f39c]18.byte $18; [20]: Tilte magic cast [f39d]04.byte $04; [21]: Player taking step [f39e]01.byte $01; [22]: [f39f]02.byte $02; [23]: [f3a0]03.byte $03; [24]: [f3a1]05.byte $05; [25]: Gold changed [f3a2]03.byte $03; [26]: Item used [f3a3]10.byte $10; [27]: Touched meat [f3a4]02.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:
; ; Save the current bank and switch to bank 13 (strings). ;
[f3a5]ad 00 01LDA a:CurrentROMBank; Get the current ROM bank. [f3a8]48PHA; Push it to the stack. [f3a9]a2 0dLDX #$0d; 13 = Strings bank. [f3ab]20 1a ccJSR MMC1_UpdateROMBank; Switch to the bank.
; ; Set the starting address to load from ($8000). ;
[f3ae]a9 00LDA #<TILE_GLYPHS_START; Lower byte of the starting address. [f3b0]85 ecSTA Temp_Int24; Store it. [f3b2]a9 80LDA #$80; Upper byte of the starting address. [f3b4]85 edSTA Temp_Int24_M
; ; Set the starting PPU address to write glyphs to. ;
[f3b6]a9 12LDA #$12; Lower byte of the PPU address. [f3b8]8d 06 20STA a:PPUADDR; Set it. [f3bb]a9 00LDA #$00; Upper byte of the PPU address. [f3bd]8d 06 20STA a:PPUADDR; Set it. [f3c0]a2 60LDX #$60 [f3c2]@_loadGlyphsLoop: [f3c2]20 dc f3JSR PPU_WriteGlyphTile; Write the glyph as plane 1. [f3c5]20 dc f3JSR PPU_WriteGlyphTile; Write the glyph as plane 2. [f3c8]a5 ecLDA Temp_Int24; Load the lower byte of the load address. [f3ca]18CLC [f3cb]69 08ADC #$08; Increment by 8. [f3cd]90 02BCC @_prepareNextLoop; If it didn't wrap, jump to avoid setting the upper byte. [f3cf]e6 edINC Temp_Int24_M; Increment the upper byte of the load address. [f3d1]@_prepareNextLoop: [f3d1]85 ecSTA Temp_Int24; Store as the new lower byte of the load address. [f3d3]caDEX; X-- [f3d4]d0 ecBNE @_loadGlyphsLoop; If X > 0, loop.
; ; v-- Fall through --v ;
;============================================================================ ; 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]68PLA; Pop the bank from the stack. [f3d7]aaTAX; X = Resulting bank. [f3d8]20 1a ccJSR MMC1_UpdateROMBank; Switch to the bank. [f3db]60RTS
;============================================================================ ; 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]a0 00LDY #$00; Y = 0 [f3de]@_loop: [f3de]b1 ecLDA (Temp_Int24),Y; Load the byte from the temp string address. [f3e0]8d 07 20STA a:PPUDATA; Write to the PPU. [f3e3]c8INY; Y++ [f3e4]c0 08CPY #$08; Have we written 8 bytes? [f3e6]d0 f6BNE @_loop; If not, loop. [f3e8]60RTS
;============================================================================ ; 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]20 f5 f3JSR Messages_Load; Load the message. [f3ec]@_loop: [f3ec]20 7d f4JSR TextBox_LoadAndShowMessage; Show the next character from the message. [f3ef]ad 13 02LDA a:MessageID; Is it 0xFF (end of message)? [f3f2]d0 f8BNE @_loop; If not, loop. [f3f4]60RTS
;============================================================================ ; 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]8d 13 02STA a:MessageID
; ; Save our current bank. ;
[f3f8]ad 00 01LDA a:CurrentROMBank; A = Our current bank [f3fb]48PHA; Push A to the stack.
; ; Switch to Bank 13 for the strings. ;
[f3fc]a2 0dLDX #$0d; Update to Bank 13. [f3fe]20 1a ccJSR MMC1_UpdateROMBank
; ; Set the start of the messages string (MESSAGE_STRINGS). ;
[f401]a2 00LDX #<MESSAGE_STRINGS; Set the lower byte of the address for the strings. [f403]86 ecSTX Temp_Int24 [f405]a2 83LDX #>MESSAGE_STRINGS; Set the upper byte of the address for the strings. [f407]86 edSTX Temp_Int24_M
; ; Reset the display timer. ;
[f409]a0 00LDY #$00; Y = 0 (start of our lookup offset) [f40b]8c 1d 02STY a:TextBox_Timer; Set the timer counter 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]ae 13 02LDX a:MessageID; X = MessageID [f411]caDEX; X-- [f412]f0 0eBEQ @_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]b1 ecLDA (Temp_Int24),Y; A = Character at Y from the current string. [f416]c8INY; Y++
; ; If Y == 0, increment our upper byte position. This ; will happen when Y wraps around from 0xFF to 0. ;
[f417]d0 02BNE @_checkChar; If Y != 0, jump. [f419]e6 edINC 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]c9 ffCMP #$ff; Is A (the character) 0xFF? [f41d]d0 f5BNE @_messageLoop; If not 0xFF, loop. [f41f]caDEX; A is 0xFF. X-- to the previous message ID. [f420]d0 f2BNE @_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]8c 14 02STY a:Message_ProcessedLength; Message length = Y [f425]a5 ecLDA Temp_Int24; Load the new lower byte of the string position. [f427]8d 18 02STA a:Message_StartAddr; Store it. [f42a]a5 edLDA Temp_Int24_M; Load the new upper byte of the string position. [f42c]8d 19 02STA a:Message_StartAddr_U; Store it.
; ; Restore our previous bank. ;
[f42f]68PLA; Pop A (our previous bank) off the stack. [f430]aaTAX; X = A [f431]20 1a ccJSR 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]a9 00LDA #$00 [f436]8d 1a 02STA a:TextBox_LineScrollOffset; Set line scroll offset to 0. [f439]8d 16 02STA a:TextBox_CharPosForLine; Set character position in line to 0. [f43c]8d 17 02STA a:TextBox_DrawnLineNum; Set the drawn line number to 0. [f43f]8d 15 02STA a:TextBox_MessagePaused; Clear the message paused state. [f442]8d 1c 02STA a:Textbox_TitleCharOffset; Set the title character offset to 0. [f445]a9 04LDA #$04 [f447]8d 1b 02STA 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:
; ; Set the PPU target address to $1400. These are the ; text tiles. ;
[f44a]a9 14LDA #$14; 0x14 == upper byte of the PPU address. [f44c]85 e9STA PPU_TargetAddr_U; Store it. [f44e]a9 00LDA #$00; 0x00 == lower byte. [f450]85 e8STA PPU_TargetAddr; Store it. [f452]a0 20LDY #$20; Y = 32 (loop counter).
; ; Write the value 0x00 for bytes $1400 through $1800. ;
[f454]@_writeLoop: [f454]98TYA; A = Y (loop counter) [f455]48PHA; Push to the stack. [f456]a9 00LDA #$00; A = 0 [f458]a0 20LDY #$20; Y = 32 [f45a]20 4a f8JSR PPUBuffer_WriteValueMany; Write 0x00 32 times to the PPU buffer. [f45d]20 26 f8JSR PPU_IncrementAddrBy32; Address += 32 [f460]68PLA; Pull A from the stack. [f461]a8TAY; Y = A (loop counter) [f462]88DEY; Y-- [f463]d0 efBNE @_writeLoop; If > 0, loop.
; ; XREFS: ; TextBox_ShowNextChar_IfReady ;
[f465]TextBox_ClearShopSizeAtOffset_return: [f465]60RTS
;============================================================================ ; 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 ; TextBox_ShowNextChar_IfReady ; ; XREFS: ; IScriptAction_OpenShop ; IScriptAction_ShowMessage ; IScriptAction_ShowQuestionMessage ; IScriptAction_ShowQuestionMessageCheckIfDismissed ; IScriptAction_ShowSellMenu ; IScriptAction_ShowUnskippableMessage ; IScripts_ShowFinalMessage ;============================================================================
[f466]TextBox_ShowNextChar:
; ; Save the current bank and switch to bank 13 (strings). ;
[f466]ad 00 01LDA a:CurrentROMBank; A = current bank [f469]48PHA; Push it to the stack. [f46a]a2 0dLDX #$0d; X = 13 (bank) [f46c]20 1a ccJSR MMC1_UpdateROMBank; Switch to bank 13.
; ; Show the next character. ;
[f46f]ee 1d 02INC a:TextBox_Timer; Increment the timer delay. [f472]a9 01LDA #$01; A = 1 [f474]8d 12 02STA a:TextBox_PlayTextSound; Store it. [f477]20 91 f4JSR TextBox_ShowNextChar_IfReady; Show this character in the message.
; ; Restore the bank and return. ;
[f47a]4c d6 f3JMP MMC1_UpdatePRGBankToStackA; Restore the pushed bank.
;============================================================================ ; TODO: Document TextBox_LoadAndShowMessage ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; IScripts_LoadAndShowMessage ;============================================================================
[f47d]TextBox_LoadAndShowMessage:
; ; Save the current ROM. ;
[f47d]ad 00 01LDA a:CurrentROMBank [f480]48PHA
; ; Switch to Bank 13, where the strings are. ;
[f481]a2 0dLDX #$0d [f483]20 1a ccJSR MMC1_UpdateROMBank [f486]a9 00LDA #$00 [f488]8d 12 02STA a:TextBox_PlayTextSound [f48b]20 a2 f4JSR TextBox_DisplayMessage
; ; Restore the previous bank. ;
[f48e]4c d6 f3JMP MMC1_UpdatePRGBankToStackA
;============================================================================ ; Show the next character in a message if the state allows it. ; ; A character will be shown if: ; ; 1. A message is loaded. ; 2. The message is not paused. ; 3. The delay timer is a multiple of 3. ; ; This falls through to TextBox_DisplayMessage. ; ; INPUTS: ; MessageID: ; The loaded message ID. ; ; TextBox_MessagePaused: ; Whether the message is currently paused. ; ; TextBox_Timer: ; The character display timer. ; ; OUTPUTS: ; None. ; ; XREFS: ; TextBox_ShowNextChar ;============================================================================
[f491]TextBox_ShowNextChar_IfReady: [f491]ad 13 02LDA a:MessageID; Load the message ID to show. [f494]f0 cfBEQ TextBox_ClearShopSizeAtOffset_return; If unset, return. [f496]ad 15 02LDA a:TextBox_MessagePaused; Is the message paused? [f499]d0 caBNE TextBox_ClearShopSizeAtOffset_return; If so, return. [f49b]ad 1d 02LDA a:TextBox_Timer; Load the character display timer. [f49e]29 03AND #$03; Is it 3? [f4a0]d0 c3BNE TextBox_ClearShopSizeAtOffset_return; If not, we're not ready to display the next character. Return.
; ; v-- Fall through --v ;
;============================================================================ ; TODO: Document TextBox_DisplayMessage ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; TextBox_LoadAndShowMessage ;============================================================================
[f4a2]TextBox_DisplayMessage: [f4a2]ad 1c 02LDA a:Textbox_TitleCharOffset [f4a5]f0 21BEQ TextBox_ShowMessage_LoadMessage [f4a7]c9 90CMP #$90 [f4a9]f0 18BEQ @LAB_PRG15_MIRROR__f4c3 [f4ab]29 0fAND #$0f [f4ad]85 ecSTA Temp_Int24
; ; Convert the player's title to an index in the string list. ;
[f4af]ad 37 04LDA a:PlayerTitle; Load the player's title. [f4b2]0aASL A; Shift left 4, converting to an index. [f4b3]0aASL A [f4b4]0aASL A [f4b5]0aASL A [f4b6]05 ecORA Temp_Int24 [f4b8]aaTAX; X = A [f4b9]bd 49 f6LDA a:PLAYER_TITLE_STRINGS,X; Look up the string. [f4bc]ee 1c 02INC a:Textbox_TitleCharOffset [f4bf]c9 20CMP #$20 [f4c1]d0 30BNE TextBox_ShowMessageWithSound [f4c3]@LAB_PRG15_MIRROR__f4c3: [f4c3]a9 00LDA #$00 [f4c5]8d 1c 02STA 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]ad 18 02LDA a:Message_StartAddr [f4cb]85 ecSTA Temp_Int24 [f4cd]ad 19 02LDA a:Message_StartAddr_U [f4d0]85 edSTA Temp_Int24_M [f4d2]ac 14 02LDY a:Message_ProcessedLength [f4d5]ee 14 02INC a:Message_ProcessedLength [f4d8]d0 03BNE @LAB_PRG15_MIRROR__f4dd [f4da]ee 19 02INC a:Message_StartAddr_U [f4dd]@LAB_PRG15_MIRROR__f4dd: [f4dd]b1 ecLDA (Temp_Int24),Y [f4df]c9 ffCMP #$ff [f4e1]f0 75BEQ TextBox_ShowMessage_EndOfMessage [f4e3]c9 feCMP #$fe [f4e5]f0 3cBEQ TextBox_ShowMessage_Newline [f4e7]c9 fdCMP #$fd [f4e9]f0 17BEQ TextBox_ShowMessage_HandleSpaceOrEndOfLine [f4eb]c9 fbCMP #$fb [f4ed]f0 6fBEQ TextBox_ShowMessage_ShowPlayerTitle [f4ef]c9 fcCMP #$fc [f4f1]f0 4cBEQ 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]ae 12 02LDX a:TextBox_PlayTextSound [f4f6]f0 07BEQ TextBox_ShowMessage
; ; Play the message sound effect. ;
[f4f8]48PHA; Push A to the stack. [f4f9]a9 01LDA #$01; 0x01 == Message sound effect. [f4fb]20 e4 d0JSR Sound_PlayEffect; Play it. [f4fe]68PLA; Pop A.
; ; v-- Fall through --v ;
;============================================================================ ; TODO: Document TextBox_ShowMessage ; ; INPUTS: ; A ; ; OUTPUTS: ; TODO ; ; XREFS: ; PasswordScreen_ShowNextChar ; TextBox_ShowMessageWithSound ;============================================================================
[f4ff]TextBox_ShowMessage: [f4ff]20 f3 f5JSR TextBox_WriteChar
; ; v-- Fall through --v ;
;============================================================================ ; TODO: Document TextBox_ShowMessage_HandleSpaceOrEndOfLine ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; TextBox_ShowMessage_LoadMessage ;============================================================================
[f502]TextBox_ShowMessage_HandleSpaceOrEndOfLine:
; ; If it's 0xFD, it's a space. ;
[f502]ee 16 02INC a:TextBox_CharPosForLine [f505]ad 16 02LDA a:TextBox_CharPosForLine [f508]c9 10CMP #$10 [f50a]90 4bBCC TextBox_ShowMessage_Return
; ; Load the next character in the message string. ;
[f50c]ad 18 02LDA a:Message_StartAddr [f50f]85 ecSTA Temp_Int24 [f511]ad 19 02LDA a:Message_StartAddr_U [f514]85 edSTA Temp_Int24_M [f516]ac 14 02LDY a:Message_ProcessedLength [f519]b1 ecLDA (Temp_Int24),Y
; ; If it's 0xFF, the message ends. ;
[f51b]c9 ffCMP #$ff [f51d]f0 39BEQ TextBox_ShowMessage_EndOfMessage
; ; If it's 0xFC, finish this part of the message. ;
[f51f]c9 fcCMP #$fc [f521]f0 34BEQ TextBox_ShowMessage_Return
; ; v-- Fall through --v ;
;============================================================================ ; TODO: Document TextBox_ShowMessage_Newline ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; TextBox_ShowMessage_LoadMessage ;============================================================================
[f523]TextBox_ShowMessage_Newline:
; ; If it's 0xFE, it's a newline. ;
[f523]ad 16 02LDA a:TextBox_CharPosForLine [f526]f0 a0BEQ TextBox_ShowMessage_LoadMessage
; ; v-- Fall through --v ;
;============================================================================ ; TODO: Document TextBox_ShowMessage_IncLineAndReset ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; TextBox_ShowMessage_Prepare4Lines ;============================================================================
[f528]TextBox_ShowMessage_IncLineAndReset:
; ; Increment the line and reset the character position. ;
[f528]a9 00LDA #$00; A = 0 [f52a]8d 16 02STA a:TextBox_CharPosForLine; Set as the character position. [f52d]ac 17 02LDY a:TextBox_DrawnLineNum; Y = line number [f530]c8INY; Y++
; ; Check if we've reached the end of the textbox. ; If so, prepare the state for the next 4 lines. ;
[f531]c0 04CPY #$04; Is the line number 4? [f533]f0 3cBEQ TextBox_ShowMessage_Fill4Lines; If so, jump.
; ; We're not at the end of the textbox. Increment and return. ;
[f535]8c 17 02STY a:TextBox_DrawnLineNum; Els, set as the number of drawn lines. [f538]60RTS
;============================================================================ ; 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:
; ; Check if anything's ready to be written for this line. ;
[f539]ad 16 02LDA a:TextBox_CharPosForLine; Load the character position (1-based; 0 for not ready). [f53c]d0 eaBNE TextBox_ShowMessage_IncLineAndReset; If > 0, the line can be drawn. [f53e]60RTS
;============================================================================ ; 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]ad 18 02LDA a:Message_StartAddr; Load the lower byte of the message string address. [f542]85 ecSTA Temp_Int24; Store it for length calculation. [f544]ad 19 02LDA a:Message_StartAddr_U; Load the upper byte. [f547]85 edSTA Temp_Int24_M; Store it.
; ; Check if the next byte is 0xFF, in which case we just want ; to end the message immediately. ;
[f549]ac 14 02LDY a:Message_ProcessedLength; Load the message length. [f54c]b1 ecLDA (Temp_Int24),Y; Load the next byte of the message. [f54e]c9 ffCMP #$ff; Is it 0xFF (end of message)? [f550]f0 06BEQ TextBox_ShowMessage_EndOfMessage; If true, then end message.
; ; The message isn't ending. We're paused instead. ;
[f552]a9 ffLDA #$ff [f554]8d 15 02STA a:TextBox_MessagePaused; Set the message as paused.
; ; XREFS: ; TextBox_ShowMessage_HandleSpaceOrEndOfLine ;
[f557]TextBox_ShowMessage_Return: [f557]60RTS
; ; This is the end of the message. ; ; ; XREFS: ; TextBox_ShowMessage_HandleSpaceOrEndOfLine ; TextBox_ShowMessage_LoadMessage ; TextBox_ShowMessage_Pause ;
[f558]TextBox_ShowMessage_EndOfMessage: [f558]a9 00LDA #$00; A = 0 [f55a]8d 13 02STA a:MessageID; Store as the message ID (terminating message). [f55d]60RTS
;============================================================================ ; 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:
; ; If it's 0xFB, the title rank will be inserted. ;
[f55e]a9 81LDA #$81; A = 0x81 [f560]8d 1c 02STA a:Textbox_TitleCharOffset; Store it. [f563]ad 37 04LDA a:PlayerTitle; Load the player's title. [f566]0aASL A; Convert to an index in the Player Titles table. [f567]0aASL A [f568]0aASL A [f569]0aASL A [f56a]aaTAX; X = A [f56b]bd 49 f6LDA a:PLAYER_TITLE_STRINGS,X; Load the title string at X. [f56e]4c f3 f4JMP 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:
; ; Set the start position where text will be drawn. ;
[f571]ad 08 02LDA a:TextBox_X; X = textbox tile X coordinate. [f574]18CLC [f575]69 02ADC #$02; X += 2 [f577]85 eaSTA TextBox_ContentsX; Store as the text X start position. [f579]ad 09 02LDA a:TextBox_Y; Y = textbox tile Y coordinate. [f57c]18CLC [f57d]69 02ADC #$02; Y += 2 [f57f]85 ebSTA 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]20 04 f8JSR PPU_SetAddrForTextBoxPos; Set the PPU address for the text position. [f584]ae 1a 02LDX a:TextBox_LineScrollOffset; X = scroll line offset [f587]20 cd f5JSR TextBox_FillPlaceholderTextForLineCapped; Write a line and advance. [f58a]20 cd f5JSR TextBox_FillPlaceholderTextForLineCapped; Write a line and advance. [f58d]20 cd f5JSR TextBox_FillPlaceholderTextForLineCapped; Write a line and advance.
; ; Clear the final line. ;
[f590]a9 00LDA #$00; A = 0 (start offset) [f592]a0 10LDY #$10; Y = 16 (line length) [f594]20 4a f8JSR 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]a9 00LDA #$00; A = 0 [f599]85 e8STA PPU_TargetAddr; Store A as the lower byte of the PPU target address. [f59b]ad 1a 02LDA a:TextBox_LineScrollOffset; A = scroll-aware line offset. [f59e]18CLC [f59f]69 14ADC #$14; A += 20 [f5a1]85 e9STA PPU_TargetAddr_U; Store as the upper byte of the PPU target addres.
; ; Clear out each of the lines that were just written. ;
[f5a3]a2 10LDX #$10; X = 16 [f5a5]@LAB_PRG15_MIRROR__f5a5: [f5a5]8aTXA; A = X [f5a6]48PHA; Push A to the stack. [f5a7]a9 00LDA #$00; A = 0 [f5a9]a0 10LDY #$10; Y = 16 [f5ab]20 4a f8JSR PPUBuffer_WriteValueMany; Write '0' 16 times, clearing the line. [f5ae]20 1e f8JSR PPU_IncrementAddrBy16; Increment the offset by 16. [f5b1]68PLA; Pop A from the stack (now 16). [f5b2]aaTAX; X = A [f5b3]caDEX; X-- [f5b4]d0 efBNE @LAB_PRG15_MIRROR__f5a5; If X > 0, loop.
; ; Increase the Y position to draw to by 3, after these lines. ;
[f5b6]e6 ebINC TextBox_ContentsY; TextBox_ContentsY += 3 [f5b8]e6 ebINC TextBox_ContentsY [f5ba]e6 ebINC TextBox_ContentsY
; ; Draw a line of text to fill the address. ; ; This will be: ; ; PQRSTUVWXYZ[\]^_ ;
[f5bc]20 04 f8JSR PPU_SetAddrForTextBoxPos; Set the text draw position back. [f5bf]ad 1a 02LDA a:TextBox_LineScrollOffset; A = line scroll offset. [f5c2]20 d1 f5JSR TextBox_FillPlaceholderTextForLine; Write a line of text.
; ; Store the new line number offset. ;
[f5c5]e8INX; X++ [f5c6]8aTXA; A = X [f5c7]29 03AND #$03; Cap the line number from 0..4. [f5c9]8d 1a 02STA a:TextBox_LineScrollOffset; Store it. [f5cc]60RTS
;============================================================================ ; 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]e8INX; lineNum++ [f5ce]8aTXA; A = X
; ; Cap the linenum to lines 0-4. ;
[f5cf]29 03AND #$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]aaTAX; X = A
; ; Calculate a character offset for the line number. ; Lines are 16 characters per line. ;
[f5d2]0aASL A; Multiply by 16. [f5d3]0aASL A [f5d4]0aASL A [f5d5]0aASL A
; ; Add 64, giving us a value of "A". ;
[f5d6]69 40ADC #$40; Add 64 (character code of "A"). [f5d8]a8TAY; 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]8aTXA; A = X (offset) [f5da]48PHA; Push A to the stack.
; ; Set the write length to 16 characters (max for a line). ;
[f5db]a9 10LDA #$10; A = 16 [f5dd]20 dc cfJSR PPUBuffer_QueueCommandOrLength; Queue this as the write length. [f5e0]98TYA; A = Y
; ; Begin the write loop. ;
[f5e1]a0 10LDY #$10; Y = 16 [f5e3]@_loop: [f5e3]20 45 f8JSR PPUBuffer_Set; Write the value to the buffer. [f5e6]18CLC [f5e7]69 01ADC #$01; A++ (next character value) [f5e9]88DEY; Y-- [f5ea]d0 f7BNE @_loop; If Y > 0, loop. [f5ec]86 20STX PPUBuffer_WriteOffset; Store the new offset as the PPU upper bounds. [f5ee]68PLA; Pop A from the stack. [f5ef]aaTAX; X = A [f5f0]4c 26 f8JMP 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:
; ; Normalize the ASCII value into an index in the characters ; table. ;
[f5f3]38SEC; C = 1 [f5f4]e9 20SBC #$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]a2 00LDX #$00; X = 0 [f5f8]86 edSTX Temp_Int24_M; Set middle byte of string address to 0. [f5fa]0aASL A; A = A * 8 (each glyph is 8 bytes) [f5fb]0aASL A [f5fc]26 edROL Temp_Int24_M; Rotate upper byte left and add carry. [f5fe]0aASL A [f5ff]26 edROL Temp_Int24_M [f601]69 00ADC #$00 [f603]85 ecSTA Temp_Int24
; ; Make the address relative to $8000. ;
[f605]a5 edLDA Temp_Int24_M; Load the upper byte of of the address. [f607]69 80ADC #$80; Add 0x80 to it. [f609]85 edSTA Temp_Int24_M; Store it back.
; ; Compute the PPU target address. ;
[f60b]a9 00LDA #$00; A = 0 [f60d]85 e9STA PPU_TargetAddr_U; Store as the starting upper byte of the PPU target address. [f60f]ad 16 02LDA a:TextBox_CharPosForLine; A = character position. [f612]0aASL A; Convert to a 16 byte address boundary. [f613]0aASL A [f614]0aASL A [f615]0aASL A [f616]85 e8STA PPU_TargetAddr; Store as the lower byte of the PPU target address. [f618]ad 17 02LDA a:TextBox_DrawnLineNum; A = drawn line number. [f61b]18CLC [f61c]6d 1a 02ADC a:TextBox_LineScrollOffset; A += line scroll offset [f61f]29 03AND #$03; Cap to a 0..4 line number. [f621]69 14ADC #$14; A += 20 [f623]85 e9STA 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]ad 00 01LDA a:CurrentROMBank; A = current bank [f628]48PHA; Push A to the stack. [f629]a2 0dLDX #$0d; A = bank 13 [f62b]20 1a ccJSR MMC1_UpdateROMBank; Switch to it.
; ; Queue up the drawn line. ;
[f62e]a9 10LDA #$10; A = 16 (line length) [f630]20 dc cfJSR PPUBuffer_QueueCommandOrLength; Queue as the line length. [f633]20 3e f6JSR TextBox_Write8BytesFromTemp; Write the first half of the line. [f636]20 3e f6JSR TextBox_Write8BytesFromTemp; Write the second half.
; ; Finish up and restore the bank. ;
[f639]86 20STX PPUBuffer_WriteOffset; Store the new upper bounds for the PPU buffer. [f63b]4c d6 f3JMP 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]a0 00LDY #$00; Y = 0 [f640]@_loop: [f640]20 42 f8JSR PPUBuffer_WriteFromTemp; Write a value from temp. [f643]98TYA; A = Y [f644]29 07AND #$07; Consider values 0-127. [f646]d0 f8BNE @_loop; If not 0, loop. [f648]60RTS
; ; XREFS: ; TextBox_DisplayMessage ; TextBox_ShowMessage_ShowPlayerTitle ;
[f649]PLAYER_TITLE_STRINGS: [f649]4e 6f 76 69 63 65 20 20.byte "Novice "; char [f651]20 20 20 20 20 20 20 20.byte " "; char [f659]41 73 70 69 72 61 6e 74.byte "Aspirant"; char [f661]20 20 20 20 20 20 20 20.byte " "; char [f669]42 61 74 74 6c 65 72 20.byte "Battler "; char [f671]20 20 20 20 20 20 20 20.byte " "; char [f679]46 69 67 68 74 65 72 20.byte "Fighter "; char [f681]20 20 20 20 20 20 20 20.byte " "; char [f689]41 64 65 70 74 20 20 20.byte "Adept "; char [f691]20 20 20 20 20 20 20 20.byte " "; char [f699]43 68 65 76 61 6c 69 65.byte "Chevalie"; char [f6a1]72 20 20 20 20 20 20 20.byte "r "; char [f6a9]56 65 74 65 72 61 6e 20.byte "Veteran "; char [f6b1]20 20 20 20 20 20 20 20.byte " "; char [f6b9]57 61 72 72 69 6f 72 20.byte "Warrior "; char [f6c1]20 20 20 20 20 20 20 20.byte " "; char [f6c9]53 77 6f 72 64 6d 61 6e.byte "Swordman"; char [f6d1]20 20 20 20 20 20 20 20.byte " "; char [f6d9]48 65 72 6f 20 20 20 20.byte "Hero "; char [f6e1]20 20 20 20 20 20 20 20.byte " "; char [f6e9]53 6f 6c 64 69 65 72 20.byte "Soldier "; char [f6f1]20 20 20 20 20 20 20 20.byte " "; char [f6f9]4d 79 72 6d 69 64 6f 6e.byte "Myrmidon"; char [f701]20 20 20 20 20 20 20 20.byte " "; char [f709]43 68 61 6d 70 69 6f 6e.byte "Champion"; char [f711]20 20 20 20 20 20 20 20.byte " "; char [f719]53 75 70 65 72 68 65 72.byte "Superher"; char [f721]6f 20 20 20 20 20 20 20.byte "o "; char [f729]50 61 6c 61 64 69 6e 20.byte "Paladin "; char [f731]20 20 20 20 20 20 20 20.byte " "; char [f739]4c 6f 72 64 20 20 20 20.byte "Lord "; char [f741]20 20 20 20 20 20.byte " "; char
; ; XREFS: ; Player_SetInitialExpAndGold ;
[f747]PLAYER_TITLE_STRINGS_15__14_: [f747]20.byte " "; char
; ; XREFS: ; Player_SetInitialExpAndGold ;
[f748]PLAYER_TITLE_STRINGS_15__15_: [f748]20.byte " "; char
;============================================================================ ; Experience requirements for each title. ; ; XREFS: ; PlayerMenu_ShowStatusMenu ; Player_CheckReachedNextTitle ;============================================================================
; ; XREFS: ; PlayerMenu_ShowStatusMenu ; Player_CheckReachedNextTitle ;
[f749]PLAYER_TITLE_EXP_NEEDED: [f749]e8 03.word $03e8; [0]: Aspirant (1,000 exp) [f74b]98 08.word $0898; [1]: Battler (2,200 exp) [f74d]ac 0d.word $0dac; [2]: Fighter (3,500 exp) [f74f]c0 12.word $12c0; [3]: Adept (4,800 exp) [f751]38 18.word $1838; [4]: Chevalier (6,200 exp) [f753]40 1f.word $1f40; [5]: Veteran (8,000 exp) [f755]10 27.word $2710; [6]: Warrior (10,000 exp) [f757]d4 30.word $30d4; [7]: Swordman (12,500 exp) [f759]98 3a.word $3a98; [8]: Hero (15,000 exp) [f75b]50 46.word $4650; [9]: Soldier (18,000 exp) [f75d]f0 55.word $55f0; [10]: Myrmidon (22,000 exp) [f75f]90 65.word $6590; [11]: Champion (26,000 exp) [f761]30 75.word $7530; [12]: Superhero (30,000 exp) [f763]b8 88.word $88b8; [13]: Paladin (35,000 exp)
; ; XREFS: ; Player_SetInitialExpAndGold ;
[f765]PLAYER_TITLE_EXP_NEEDED_14_: [f765]c8 af.word $afc8; [14]: Lord (45,000 exp)
;============================================================================ ; Starting gold at each player title. ;============================================================================
[f767]PLAYER_TITLE_GOLD: [f767]f4 01.word $01f4; [0]: [f769]20 03.word $0320; [1]: [f76b]b0 04.word $04b0; [2]: [f76d]40 06.word $0640; [3]: [f76f]34 08.word $0834; [4]: [f771]f0 0a.word $0af0; [5]: [f773]ac 0d.word $0dac; [6]: [f775]cc 10.word $10cc; [7]: [f777]50 14.word $1450; [8]: [f779]38 18.word $1838; [9]: [f77b]4c 1d.word $1d4c; [10]: [f77d]28 23.word $2328; [11]: [f77f]04 29.word $2904; [12]: [f781]c8 32.word $32c8; [13]: [f783]98 3a.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]4aLSR A; Shift right 5, turning inventory bits into an index. [f786]4aLSR A [f787]4aLSR A [f788]4aLSR A [f789]4aLSR A [f78a]60RTS
;============================================================================ ; 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]0aASL A; Shift left 5 (here and the next 4 after falling through).
;============================================================================ ; Multiply a value by 16. ; ; This if often used for 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]0aASL A; Shift left 4. [f78d]0aASL A [f78e]0aASL A [f78f]0aASL A [f790]60RTS
;============================================================================ ; 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:
; ; Preserve X and Y on the stack. ;
[f791]98TYA; A = Y [f792]48PHA; Push it to the stack. [f793]8aTXA; A = X [f794]48PHA; Push it to the stack.
; ; Preserve the current ROM bank. ;
[f795]ad 00 01LDA a:CurrentROMBank; A = Current ROM bank. [f798]48PHA; Push it to the stack.
; ; Switch to bank 3 (area metadata). ;
[f799]a2 03LDX #$03; X = 3 (Area metadata bank) [f79b]20 1a ccJSR MMC1_UpdateROMBank; Switch to the bank. [f79e]ad eb 00LDA a:TextBox_ContentsY; A = Tile Y position for the text in the textbox. [f7a1]20 8c f7JSR Math_MultiplyBy16; Convert to an index in the block attributes data. [f7a4]29 f0AND #$f0; Keep the upper nibble. [f7a6]85 ecSTA Temp_Int24; Store for the final result. [f7a8]0d ea 00ORA a:TextBox_ContentsX; OR the index with the tile X position for the text. [f7ab]38SEC [f7ac]e9 20SBC #$20; Subtract 32.
; ; Load the data attribute from the screen buffer at this index. ;
[f7ae]aaTAX; X = A (screen buffer lookup index) [f7af]bc 00 06LDY a:ScreenBuffer,X; Y = Screen buffer data at the index. [f7b2]b1 7eLDA (CurrentArea_BlockAttributesAddr),Y; Load the block attributes at the given offset. [f7b4]4c f6 f7JMP textBox_GetAreaBehindTextBox_restoreAndReturn; Jump to finish loading.
;============================================================================ ; TODO: Document Textbox_Maybe_GetAreaBehindTextbox ; ; INPUTS: ; Y ; ; OUTPUTS: ; A ; ; XREFS: ; TextBox_Close ;============================================================================
[f7b7]Textbox_Maybe_GetAreaBehindTextbox: [f7b7]98TYA [f7b8]48PHA [f7b9]8aTXA [f7ba]48PHA [f7bb]ad 00 01LDA a:CurrentROMBank [f7be]48PHA [f7bf]a2 03LDX #$03 [f7c1]20 1a ccJSR MMC1_UpdateROMBank [f7c4]ad eb 00LDA a:TextBox_ContentsY [f7c7]0aASL A [f7c8]0aASL A [f7c9]0aASL A [f7ca]29 f0AND #$f0 [f7cc]85 ecSTA Temp_Int24 [f7ce]ad ea 00LDA a:TextBox_ContentsX [f7d1]4aLSR A [f7d2]05 ecORA Temp_Int24 [f7d4]38SEC [f7d5]e9 20SBC #$20 [f7d7]aaTAX [f7d8]bc 00 06LDY a:ScreenBuffer,X [f7db]ad ea 00LDA a:TextBox_ContentsX [f7de]29 01AND #$01 [f7e0]85 ecSTA Temp_Int24 [f7e2]ad eb 00LDA a:TextBox_ContentsY [f7e5]0aASL A [f7e6]29 02AND #$02 [f7e8]05 ecORA Temp_Int24 [f7ea]0aASL A [f7eb]aaTAX [f7ec]b5 80LDA CurrentArea_BlockData1StartAddr,X [f7ee]85 ecSTA Temp_Int24 [f7f0]b5 81LDA CurrentArea_BlockData1StartAddr+1,X [f7f2]85 edSTA Temp_Int24_M [f7f4]b1 ecLDA (Temp_Int24),Y
; ; XREFS: ; TextBox_GetBackingAttributeData ;
[f7f6]textBox_GetAreaBehindTextBox_restoreAndReturn: [f7f6]85 ecSTA Temp_Int24; Store the result temporarily.
; ; Restore the previous ROM bank. ;
[f7f8]68PLA; Pull the save ROM bank from the stack. [f7f9]aaTAX; X = A (ROM bank). [f7fa]20 1a ccJSR MMC1_UpdateROMBank; Restore the bank.
; ; Restore the original X and Y registers from the stack. ;
[f7fd]68PLA; Pull the original X from the stack. [f7fe]aaTAX; Set back as X. [f7ff]68PLA; Pull the original Y from the stack. [f800]a8TAY; Set back as Y.
; ; Load the result we calculated and return it. ;
[f801]a5 ecLDA Temp_Int24; Load the calculated result. [f803]60RTS; 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]a5 0dLDA PPU_ScrollScreen; A = Scroll screen used for drawing. [f806]29 01AND #$01; Convert to 0/1 (even/odd). [f808]09 08ORA #$08; A += 8 [f80a]85 e9STA PPU_TargetAddr_U; Store as the PPU target address. [f80c]ad eb 00LDA a:TextBox_ContentsY; Load the text Y position. [f80f]0aASL A; A = A * 16 [f810]0aASL A [f811]0aASL A [f812]0aASL A [f813]26 e9ROL PPU_TargetAddr_U; Rotate the upper byte of the target address left. [f815]0aASL A; A << 1 [f816]26 e9ROL PPU_TargetAddr_U; Rotate the upper byte of the target address left. [f818]0d ea 00ORA a:TextBox_ContentsX; OR it with the X position. [f81b]85 e8STA PPU_TargetAddr; Store as the lower byte of the target address. [f81d]60RTS
;============================================================================ ; 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]a9 10LDA #$10; A = 16 [f820]d0 06BNE 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]a9 08LDA #$08; A = 8 [f824]d0 02BNE 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]a9 20LDA #$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]18CLC [f829]65 e8ADC PPU_TargetAddr; A += PPU_TargetAddr [f82b]90 02BCC @_storeAndReturn; If it overflowed, jump. No need to increment the upper byte. [f82d]e6 e9INC PPU_TargetAddr_U; Else, increment the upper byte by 1. [f82f]@_storeAndReturn: [f82f]85 e8STA PPU_TargetAddr; Set the new target address. [f831]60RTS
;============================================================================ ; 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]ad 0a 02LDA a:TextBox_Width; A = Textbox width [f835]a8TAY; Y = A [f836]4c dc cfJMP 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]20 45 f8JSR PPUBuffer_Set; Set the value in the buffer at the given offset. [f83c]88DEY; Y-- [f83d]c0 02CPY #$02; Is it 2? [f83f]d0 f8BNE TextBox_FillPPUBufferForTextWidth; If not, loop. [f841]60RTS
;============================================================================ ; 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]b1 ecLDA (Temp_Int24),Y; Load the value from Temp_Int24 at offset Y. [f844]c8INY; 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_SetHUDBarValue ;============================================================================
[f845]PPUBuffer_Set: [f845]9d 00 05STA a:PPUBuffer,X; Store A in the PPU buffer at X. [f848]e8INX; X++ [f849]60RTS
;============================================================================ ; 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]48PHA; Push A to the stack [f84b]98TYA; A = Y (length) [f84c]20 dc cfJSR PPUBuffer_QueueCommandOrLength; Write the length to the buffer. [f84f]68PLA; Pop A from the stack [f850]@_loop: [f850]20 45 f8JSR PPUBuffer_Set; Set the value in the PPU buffer. [f853]88DEY; Y-- [f854]d0 faBNE @_loop; If Y > 0, loop [f856]86 20STX PPUBuffer_WriteOffset; Set the upper bounds to write. [f858]60RTS; 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:
; ; Save out the X, Y, and Z values to temporary variables. ;
[f859]85 deSTA BankedCallSetup_SavedA; Set BankedCallSetup_SavedA = A [f85b]86 dfSTX BankedCallSetup_SavedX; Set BankedCallSetup_SavedX = X [f85d]84 e0STY 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]68PLA; Pop A from the stack. [f860]85 ecSTA Temp_Int24; Lower byte of Temp_Int24 = A [f862]68PLA; Pull A from the stack. [f863]85 edSTA Temp_Int24_M; Middle byte of Temp_Int24 = A
; ; Save A to X. We'll increment this below if we overflow ; the add. ;
[f865]aaTAX; X = A
; ; Compute a new return low = Temp_Int24.L + 3. ;
[f866]a5 ecLDA Temp_Int24; A = lower byte of Temp_Int24. [f868]18CLC; C = 0 [f869]69 03ADC #$03; A = A + 3 [f86b]85 eeSTA Temp_Int24_U; Upper byte of Temp_Int24 = A
; ; If the upper byte overflowed, increment X. ;
[f86d]90 01BCC @_storeCurrentAddress; If carry is cleared, jump. [f86f]e8INX; X = X + 1
; ; Preserve the target address and ROM bank on the stack. ;
[f870]@_storeCurrentAddress: [f870]8aTXA; A = X (our adjusted offset) [f871]48PHA; Push A to stack [f872]a5 eeLDA Temp_Int24_U; A = upper byte of Temp_Int24 [f874]48PHA; Push A to stack [f875]ad 00 01LDA a:CurrentROMBank; A = CurrentROMBank [f878]48PHA; Push A to stack
; ; Store the loaded address and bank to jump to. ;
[f879]a0 03LDY #$03; Y = 3 [f87b]b1 ecLDA (Temp_Int24),Y; A = Upper byte of the address to jump to. [f87d]85 efSTA Temp_0xEF; Temp_0xEF = A [f87f]88DEY; Y-- (2) [f880]b1 ecLDA (Temp_Int24),Y; A = Lower byte of the address to jump to. [f882]85 eeSTA Temp_Int24_U; Upper byte of Temp_Int24 = A [f884]88DEY; Y-- (1) [f885]b1 ecLDA (Temp_Int24),Y; A = Bank to load [f887]aaTAX; X = A [f888]20 1a ccJSR MMC1_UpdateROMBank; Update the ROM bank to X.
; ; Push the trampoline address (MMC1_PopBank). ;
[f88b]a9 f8LDA #>MMC1_PopBank; Upper byte of the trampoline address. . [f88d]48PHA; Push A to stack [f88e]a9 c5LDA #<MMC1_PopBank-1; Upper byte of the trampoline address. . [f890]48PHA; Push A to stack
; ; Push the target address for the trampoline. ;
[f891]a5 efLDA Temp_0xEF; A = Temp_0xEF [f893]48PHA; Push A to stack [f894]a5 eeLDA Temp_Int24_U; A = upper byte of Temp_Int24. [f896]48PHA; Push A to stack
; ; Restore the original values for A, X, and Y. ;
[f897]a5 deLDA BankedCallSetup_SavedA; A = BankedCallSetup_SavedA [f899]a6 dfLDX BankedCallSetup_SavedX; X = BankedCallSetup_SavedX [f89b]a4 e0LDY BankedCallSetup_SavedY; Y = BankedCallSetup_SavedY [f89d]60RTS
;============================================================================ ; TODO: Document PPU_WriteTilesFromCHRRAM ; ; INPUTS: ; X ; Y ; ; OUTPUTS: ; TODO ; ; XREFS: ; SplashAnimation_DrawScenery ; StartScreen_Draw ; UI_DrawHUDSprites ;============================================================================
[f89e]PPU_WriteTilesFromCHRRAM: [f89e]ad 00 01LDA a:CurrentROMBank [f8a1]48PHA [f8a2]20 1a ccJSR MMC1_UpdateROMBank [f8a5]84 ecSTY Temp_Int24 [f8a7]a5 e9LDA PPU_TargetAddr_U [f8a9]8d 06 20STA a:PPUADDR [f8ac]a5 e8LDA PPU_TargetAddr [f8ae]8d 06 20STA a:PPUADDR [f8b1]a0 00LDY #$00 [f8b3]@LAB_PRG15_MIRROR__f8b3: [f8b3]a2 10LDX #$10 [f8b5]@LAB_PRG15_MIRROR__f8b5: [f8b5]b1 dbLDA (IScriptOrCHRAddr),Y [f8b7]8d 07 20STA a:PPUDATA [f8ba]c8INY [f8bb]d0 02BNE @LAB_PRG15_MIRROR__f8bf [f8bd]e6 dcINC IScriptOrCHRAddr_U [f8bf]@LAB_PRG15_MIRROR__f8bf: [f8bf]caDEX [f8c0]d0 f3BNE @LAB_PRG15_MIRROR__f8b5 [f8c2]c6 ecDEC Temp_Int24 [f8c4]d0 edBNE @LAB_PRG15_MIRROR__f8b3
; ; v-- Fall through --v ;
;============================================================================ ; TODO: Document MMC1_PopBank ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ;============================================================================
[f8c6]MMC1_PopBank: [f8c6]68PLA [f8c7]aaTAX [f8c8]4c 1a ccJMP MMC1_UpdateROMBank
;============================================================================ ; PPU address locations where statuc symbols are positioned. ; ; XREFS: ; UI_DrawHUDSprites ;============================================================================
; ; XREFS: ; UI_DrawHUDSprites ;
[f8cb]UI_STATUS_SYMBOL_PPU_ADDR_L: [f8cb]41.byte $41; [0]: 0x2041 -- "M" (Magic)
; ; XREFS: ; UI_DrawHUDSprites ;
[f8cc]UI_STATUS_SYMBOL_PPU_ADDR_L_1_: [f8cc]61.byte $61; [1]: 0x2061 -- "P" (Power) [f8cd]6e.byte $6e; [2]: 0x206E -- "G" (Gold) [f8ce]4e.byte $4e; [3]: 0x204E -- "E" (Experience) [f8cf]56.byte $56; [4]: 0x2056 -- "T" (Time) [f8d0]5b.byte $5b; [5]: 0x205B -- Top-left of "[" (Selected item) [f8d1]7b.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]1c.byte $1c; [0]: "M" (Magic)
; ; XREFS: ; UI_DrawHUDSprites ;
[f8d3]UI_STATUS_TILES_1_: [f8d3]0a.byte $0a; [1]: ":"
; ; XREFS: ; UI_DrawHUDSprites ;
[f8d4]UI_STATUS_TILES_2_: [f8d4]00.byte $00; [2]: [f8d5]1f.byte $1f; [3]: "P" (Power) [f8d6]0a.byte $0a; [4]: ":" [f8d7]00.byte $00; [5]: [f8d8]16.byte $16; [6]: "G" (Gold) [f8d9]3a.byte $3a; [7]: ":" [f8da]00.byte $00; [8]: [f8db]14.byte $14; [9]: "E" (Experience) [f8dc]3a.byte $3a; [10]: ":" [f8dd]00.byte $00; [11]: [f8de]23.byte $23; [12]: "T" (Time) [f8df]3a.byte $3a; [13]: ":" [f8e0]00.byte $00; [14]: [f8e1]2c.byte $2c; [15]: Top of "[" (current item) [f8e2]3c.byte $3c; [16]: <blank space> [f8e3]3d.byte $3d; [17]: <blank space> [f8e4]2e.byte $2e; [18]: Top of "]" (current item) [f8e5]00.byte $00; [19]: [f8e6]2d.byte $2d; [20]: Bottom of "[" (current item) [f8e7]3e.byte $3e; [21]: <blank space> [f8e8]3f.byte $3f; [22]: <blank space> [f8e9]2f.byte $2f; [23]: Bottom of "]" (current item) [f8ea]00.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]a9 0aLDA #$0a [f8ed]8d ee 04STA a:UI_MPAndHPBarWidth
; ; Fill the entire status area with 0 (blank). ; This is 4 rows of tiles. ;
[f8f0]a9 20LDA #$20; Set the upper byte for the draw position. [f8f2]8d 06 20STA a:PPUADDR; Store it. [f8f5]a9 00LDA #$00; Set the lower byte for the draw position. [f8f7]8d 06 20STA a:PPUADDR; Store it. [f8fa]a0 80LDY #$80; Set Y = 128 (number of tiles to draw) [f8fc]a9 00LDA #$00; Set the value to draw (blank tile). [f8fe]20 b2 fcJSR 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]a2 00LDX #$00; Set X = 0 (start index in UI_STATUS_SYMBOL_PPU_ADDR_L. [f903]a0 00LDY #$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]a9 20LDA #$20 [f907]8d 06 20STA a:PPUADDR; Store the upper draw position byte as 0x20. [f90a]bd cb f8LDA a:UI_STATUS_SYMBOL_PPU_ADDR_L,X; Load the lower byte from the lookup table based on the index. [f90d]8d 06 20STA 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]b9 d2 f8LDA a:UI_STATUS_TILES,Y; Load the tile to draw at the current tile index. [f913]f0 06BEQ @_advanceIndexes; If this is 0x00, we've reached the end of the run. Advance indexes. [f915]8d 07 20STA a:PPUDATA; Else, write the tile. [f918]c8INY; tileIndex++ [f919]d0 f5BNE @_drawTiles; Loop.
; ; Advance to the next set of tiles and the next lower address ; (draw position) in the lookup tables. ;
[f91b]@_advanceIndexes: [f91b]c8INY; tileIndex++ [f91c]e8INX; position++
; ; Check if we've completed the lookup table. ; ; There's only 7 addresses/positions to work with. ;
[f91d]e0 07CPX #$07; Have we reached the end (draw position 7)? [f91f]d0 e4BNE @_goToNextPosition; If not, loop. [f921]20 af fbJSR UI_DrawSelectedItem; Draw the selected item.
; ; Set the location in bank 10 to load HUD tiles from ; (HUD_TILES_START). ;
[f924]a9 40LDA #<HUD_TILES_START; Lower byte of the start of HUD tiles. [f926]85 dbSTA IScriptOrCHRAddr; Set it for reading. [f928]a9 81LDA #>HUD_TILES_START; Upper byte of the start of HUD tiles. [f92a]85 dcSTA IScriptOrCHRAddr_U; Set it for reading.
; ; Set the address in the PPU to write tiles to ($1000). ;
[f92c]a9 00LDA #$00; Lower byte of PPU address. [f92e]85 e8STA PPU_TargetAddr; Set it for writing. [f930]a9 10LDA #$10; Upper byte of PPU address. [f932]85 e9STA PPU_TargetAddr_U; Set it for writing.
; ; Begin writing 60 tiles from bank 10. ;
[f934]a2 0aLDX #$0a; X = bank 10. [f936]a0 3cLDY #$3c; Y = number of tiles to write. [f938]20 9e f8JSR PPU_WriteTilesFromCHRRAM [f93b]60RTS
;============================================================================ ; 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]ad ed 04LDA a:NextPlayerTitle [f93f]c9 0fCMP #$0f; Have we hit the max title? (There are 15) [f941]f0 13BEQ @_return; If yes, exit. [f943]0aASL A; Convert to an index in the lookup table.
; ; Load the 16-bit experience level for the title. ;
[f944]aaTAX [f945]ad 90 03LDA a:Experience; Load the lower byte from the lookup table. [f948]dd 49 f7CMP a:PLAYER_TITLE_EXP_NEEDED,X; Compare it. [f94b]ad 91 03LDA a:Experience_U; Load the upper byte from the lookup table. [f94e]fd 4a f7SBC a:PLAYER_TITLE_EXP_NEEDED+1,X; Compare it. [f951]90 03BCC @_return; If we're under, exit.
; ; The player has met the next title's requirements. ;
[f953]ee ed 04INC a:NextPlayerTitle; We have enough. Increase the player's next title. [f956]@_return: [f956]60RTS
;============================================================================ ; Update experience for the player. ; ; This will update from upper and lower values in RAM. ; ; INPUTS: ; Temp_Int24: ; Temp_Int24+1: ; The experience to add. ; ; Experience: ; Experience_U: ; The existing experience. ; ; OUTPUTS: ; Experience: ; The updated experience. ; ; PPU_TargetAddr: ; PPU_TargetAddr+1: ; Temp_Int24: ; $ee: ; $ee: ; Clobbered. ; ; CALLS: ; Player_CheckReachedNextTitle ; UI_DrawDigitsZeroPadded ; ; XREFS: ; Player_AddExperienceFromSprite ; Player_Add100XP ;============================================================================
[f957]Player_UpdateExperience:
; ; Update the lower byte of experience. ;
[f957]ad 90 03LDA a:Experience; Load the current lower byte of experience. [f95a]18CLC [f95b]65 ecADC Temp_Int24 [f95d]8d 90 03STA a:Experience; Set the new lower byte of experience.
; ; Update the upper byte of experience. ;
[f960]ad 91 03LDA a:Experience_U; Load the current upper byte of experience. [f963]65 edADC Temp_Int24_M [f965]8d 91 03STA a:Experience_U; Set the new upper byte of experience. [f968]90 08BCC @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]a9 ffLDA #$ff [f96c]8d 90 03STA a:Experience; Set lower byte of experience to max. [f96f]8d 91 03STA a:Experience_U; Set upper byte of experience to max. [f972]@LAB_PRG15_MIRROR__f972: [f972]20 3c f9JSR 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:
; ; Set the address of the first digit of the experience to write ; (0x2050). ;
[f975]a9 20LDA #$20 [f977]85 e9STA PPU_TargetAddr_U; Store 0x20 as the upper byte. [f979]a9 50LDA #$50 [f97b]85 e8STA PPU_TargetAddr; Store 0x50 as the upper byte.
; ; Store the new experience back out to Temp_Int24 and ; Temp_Int24+1. ;
[f97d]ad 90 03LDA a:Experience; Load the lower byte of the experience. [f980]85 ecSTA Temp_Int24; Save to Temp_Int24. [f982]ad 91 03LDA a:Experience_U; Load the upper byte of the experience. [f985]85 edSTA Temp_Int24_M; Save to Temp_Int24+1. [f987]a9 00LDA #$00 [f989]85 eeSTA Temp_Int24_U [f98b]a0 05LDY #$05 [f98d]4c 06 faJMP 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:
; ; Set the number of seconds remaining as a 24-bit integer. ; ; Only the lower byte is used. The rest are 0'd out. ;
[f990]85 ecSTA Temp_Int24; Set number of seconds as the lower byte. [f992]a9 00LDA #$00 [f994]85 edSTA Temp_Int24_M; Set middle byte = 0. [f996]85 eeSTA Temp_Int24_U; Set upper byte = 0.
; ; Set the draw position (first digit of time remaining). ; This is 0x2058. ;
[f998]a9 20LDA #$20 [f99a]85 e9STA PPU_TargetAddr_U; Set high byte of position as 0x20. [f99c]a9 58LDA #$58 [f99e]85 e8STA PPU_TargetAddr; Set high byte of position as 0x58.
; ; Draw the digits with zero-padding. ;
[f9a0]a0 02LDY #$02; Set the number of digits to draw. [f9a2]4c 06 faJMP 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:
; ; Subtract from the lower byte. ;
[f9a5]ad 92 03LDA a:Gold; Load the lower byte of the current amount. [f9a8]38SEC [f9a9]e5 ecSBC Temp_Int24; Subtract the provided value. [f9ab]8d 92 03STA a:Gold; Store as the new lower byte.
; ; Subtract from the middle byte. ;
[f9ae]ad 93 03LDA a:Gold_M; Load the middle byte of the current amount. [f9b1]e5 edSBC Temp_Int24_M; Subtract the provided value and the carry. [f9b3]8d 93 03STA a:Gold_M; Store as the new middle byte.
; ; Subtract from the upper byte. ;
[f9b6]ad 94 03LDA a:Gold_U; Load the upper byte of the current amount. [f9b9]e9 00SBC #$00; Subtract the carry. [f9bb]8d 94 03STA a:Gold_U; Store as the new upper byte.
; ; Draw the new amount. ;
[f9be]4c e7 f9JMP 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:
; ; Add to the lower byte. ;
[f9c1]ad 92 03LDA a:Gold; Load the lower byte of the current amount. [f9c4]18CLC [f9c5]65 ecADC Temp_Int24; Add the new amount. [f9c7]8d 92 03STA a:Gold; Store as the new lower byte.
; ; Add to the middle byte. ;
[f9ca]ad 93 03LDA a:Gold_M; Load the middle byte of the current amount. [f9cd]65 edADC Temp_Int24_M; Add the new amount + carry. [f9cf]8d 93 03STA a:Gold_M; Store as the new middle byte.
; ; Add to the upper byte. ;
[f9d2]ad 94 03LDA a:Gold_U; Load the upper byte of the current amount. [f9d5]69 00ADC #$00; Add the carry. [f9d7]8d 94 03STA 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]90 0bBCC UI_DrawGoldValue; If no carry, draw the current amount.
; ; Carry was set. We're maxed. Make it official. ;
[f9dc]a9 ffLDA #$ff [f9de]8d 92 03STA a:Gold; Set lower to max. [f9e1]8d 93 03STA a:Gold_M; Set middle to max. [f9e4]8d 94 03STA 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:
; ; Set the draw position for the first digit of gold. ; This is 0x2070. ;
[f9e7]a9 20LDA #$20 [f9e9]85 e9STA PPU_TargetAddr_U; Set the upper byte to 0x20. [f9eb]a9 70LDA #$70 [f9ed]85 e8STA PPU_TargetAddr; Set the lower byte to 0x70.
; ; Load the gold into the 24-bit temporary integer used for ; drawing digits. ;
[f9ef]ad 92 03LDA a:Gold; Load the lower byte of the gold value. [f9f2]85 ecSTA Temp_Int24; Set a the lower byte for the digits to draw. [f9f4]ad 93 03LDA a:Gold_M; Load the middle byte of the gold value. [f9f7]85 edSTA Temp_Int24_M; Set a the middle byte for the digits to draw. [f9f9]ad 94 03LDA a:Gold_U; Load the upper byte of the gold value. [f9fc]85 eeSTA Temp_Int24_U; Set a the upper byte for the digits to draw.
; ; Draw the new amount as 7 digits, zero-padded. ;
[f9fe]a0 07LDY #$07 [fa00]4c 06 faJMP 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]20 04 f8JSR 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]98TYA; A = Y (number of digits). [fa07]48PHA; Push it to the stack. [fa08]20 47 faJSR 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]68PLA; Pull the number of digits from the stack. [fa0c]a8TAY; Y = A [fa0d]20 dc cfJSR PPUBuffer_QueueCommandOrLength; Set as the number of tiles to draw in the PPU buffer. [fa10]84 ecSTY Temp_Int24; Store the number temporarily. [fa12]a9 07LDA #$07; A = 7 (maximum number of digits). [fa14]38SEC [fa15]e5 ecSBC Temp_Int24; Subtract the number of tiles to draw. [fa17]a8TAY; Y = result (loop counter). [fa18]@_drawDigitsLoop: [fa18]b9 38 02LDA a:UI_DigitsToRender,Y; Load the digit to draw at this index. [fa1b]20 45 f8JSR PPUBuffer_Set; Set it in the PPU buffer. [fa1e]c8INY; Y++ [fa1f]c0 07CPY #$07; Is it 7 (max)? [fa21]d0 f5BNE @_drawDigitsLoop; If not, loop. [fa23]86 20STX PPUBuffer_WriteOffset; Store the resulting PPU buffer write offset. [fa25]60RTS
;============================================================================ ; 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]20 04 f8JSR PPU_SetAddrForTextBoxPos; Set the PPU position address for the text. [fa29]98TYA; A = Y (number of digits) [fa2a]48PHA; Push it to the stack. [fa2b]20 31 faJSR UI_PopulateDigitsNoLeadingZeroes; Populate the digits with no leading zeros. [fa2e]4c 0b faJMP 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]20 47 faJSR UI_PopulateDigits; Populate the ASCII digits. [fa34]e8INX; Start at the provided index + 1.
; ; Check if the digit displays "0". If not, we're done. ;
[fa35]@_loop: [fa35]bd 38 02LDA a:UI_DigitsToRender,X; Load the current ASCII digit. [fa38]c9 30CMP #$30; Is it an ASCII "0"? [fa3a]d0 0aBNE @_return; If not, we're done.
; ; Clear out the digit entirely. ;
[fa3c]a9 00LDA #$00; NUL out this digit. [fa3e]9d 38 02STA a:UI_DigitsToRender,X; Store that as the new digit value. [fa41]e8INX; i++ [fa42]e0 06CPX #$06; Are we at the end of the loop? [fa44]d0 efBNE @_loop; If not, loop again. [fa46]@_return: [fa46]60RTS
;============================================================================ ; 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:
; ; Prepare to loop 7 times (the max number of digits). ;
[fa47]a2 06LDX #$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]20 55 faJSR UI_GetValueForDigit; Get the value for this digit. [fa4c]09 30ORA #$30; Normalize the value to an ASCII digit. [fa4e]9d 38 02STA a:UI_DigitsToRender,X; Store in the target position in RAM. [fa51]caDEX; i-- [fa52]10 f5BPL @_loop; Loop if we're not done. [fa54]60RTS
;============================================================================ ; 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]a0 18LDY #$18; Prepare to loop over the 24 bits. [fa57]a9 00LDA #$00; Set A (our value) = 0. [fa59]@_loop: [fa59]06 ecASL Temp_Int24; Shift the low byte << 1; Set C as the out going bit. [fa5b]26 edROL Temp_Int24_M; Set mid byte = (mid << 1) | C [fa5d]26 eeROL Temp_Int24_U; Set high byte = (high << 1) | C [fa5f]2aROL 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]c9 0aCMP #$0a; Is A >= 10? [fa62]90 04BCC @_finishLoopIter; Branch if not. [fa64]e9 0aSBC #$0a; A >= 10, so subtract 10. [fa66]e6 ecINC Temp_Int24; Set quotient bit to 1. [fa68]@_finishLoopIter: [fa68]88DEY; i-- (process next bit) [fa69]d0 eeBNE @_loop; Loop until we hit 0. [fa6b]60RTS
; ; XREFS: ; UI_SetHUDBarValue ;
[fa6c]STATUS_BAR_PPU_ADDR_L: [fa6c]63.byte $63; [0]: Power bar [fa6d]43.byte $43; [1]: Magic bar
; ; XREFS: ; UI_SetHUDBarValue ;
[fa6e]STATUS_BAR_FILL_TILE_INDEXES: [fa6e]08.byte $08; [0]: Power bar [fa6f]09.byte $09; [1]: Magic bar
; ; XREFS: ; UI_SetHUDBarValue ;
[fa70]STATUS_BAR_PARTIAL_TILE_INDEXES: [fa70]0c.byte $0c; [0]: Power bar [fa71]0d.byte $0d; [1]: Magic bar [fa72]STATUS_BAR_PARTIAL_PPU_ADDR_L: [fa72]c0.byte $c0; [0]: Power bar [fa73]d0.byte $d0; [1]: Magic bar [fa74]60.byte $60; DEADCODE: Leftover byte. RTS? Extra bar type?
;============================================================================ ; TODO: Document UI_DrawPlayerHPValue ; ; INPUTS: ; A ; ; OUTPUTS: ; TODO ; ; XREFS: ; IScriptAction_AddHP ; Player_AddHP ; UI_DrawPlayerHP ;============================================================================
[fa75]UI_DrawPlayerHPValue:
; ; Cap the amount of HP to 80, if over. ;
[fa75]c9 51CMP #$51 [fa77]90 02BCC @LAB_PRG15_MIRROR__fa7b [fa79]a9 50LDA #$50 [fa7b]@LAB_PRG15_MIRROR__fa7b: [fa7b]8d 99 03STA a:Temp_AddedHPValue [fa7e]8d 31 04STA a:Player_HP_U [fa81]a0 00LDY #$00 [fa83]f0 0bBEQ UI_SetHUDBarValue
; ; v-- Fall through --v ;
;============================================================================ ; Set the MP value for the player. ; ; Once set, the magic bar will be drawn to reflect the new amount. ; ; INPUT: ; A: ; The MP to set. ; ; OUTPUT: ; None ; ; CALLS: ; UI_SetHUDBarValue ; ; XREFS: ; IScriptAction_AddMP ; Player_AddMP ; Player_ReduceMP ; UI_DrawHUD ;============================================================================
[fa85]Player_SetMP:
; ; Cap the MP to 80. ;
[fa85]c9 51CMP #$51; Check if over 80 MP. [fa87]90 02BCC @_setMP; If not over, we'll store what was passed in. [fa89]a9 50LDA #$50; Cap to 80 MP.
; ; Proceed to set the MP on the player and then draw the ; Magic bar. ;
[fa8b]@_setMP: [fa8b]8d 9a 03STA a:Player_MP; Set the player's current MP. [fa8e]a0 01LDY #$01; Set 1 = Magic Bar and fall through to update.
; ; v-- Fall through --v ;
;============================================================================ ; Set the Power or Magic bar's value. ; ; This fills up the bar to the specified value. It's done by ; considering three types of tiles: ; ; 1. Zero or more "fill" tiles ; 2. An optional single "partial fill" tile ; 3. Zero or more "empty" tiles ; ; The number of each type of tile needed is calculated. Any ; fill tiles will be drawn across the bar up to the maximum ; width, followed by a partial tile (if the value isn't at ; an even tile boundary), followed by any empty tiles needed ; to fill up the bar. ; ; There's a "partial fill" tile for each possible value, ; listed immediately after this function. ; ; INPUTS: ; A: ; The value to set for the bar. ; ; This will always be capped to the maximum value ; allowed by the bar. ; ; Y: ; The index of the bar. ; ; 0 = Power Bar ; 1 = Magic Bar ; ; UI_MPAndHPBarWidth: ; The width of the MP/HP bar in pixels. ; ; STATUS_BAR_PPU_ADDR_L: ; Table of lower PPU address bytes for drawing, by ; bar type. ; ; STATUS_BAR_FILL_TILE_INDEXES: ; Indexes of the fill tiles in the PPU to set for ; drawing, by bar type. ; ; STATUS_BAR_PARTIAL_TILE_INDEXES: ; Indexes of the partial fill tiles in the PPU to ; set for drawing, by bar type. ; ; STATUS_BAR_PARTIAL_PPU_ADDR_L: ; Table of lower PPU address bytes for partial ; tiles, by bar type. ; ; STATUS_BAR_PARTIAL_PPU_TILES: ; Start of the list of partial fill tiles. ; ; OUTPUTS: ; PPUBuffer_WriteOffset: ; Next offset into the PPU buffer for writing. ; ; PPU_TargetAddr: ; PPU_TargetAddr+1: ; Temp_Int24: ; $ee: ; Temp_0xEF: ; Clobbered. ; ; CALLS: ; Math_MultiplyBy16 ; PPUBuffer_QueueCommandOrLength ; PPUBuffer_Set ; ; XREFS: ; UI_DrawPlayerHPValue ;============================================================================
[fa90]UI_SetHUDBarValue:
; ; Cap the value to be <= the max for the bar. ; ; The cap is the bar's width * 8. ;
[fa90]85 eeSTA Temp_Int24_U; Store the value temporarily. [fa92]ad ee 04LDA a:UI_MPAndHPBarWidth; Load the total width of the bar. [fa95]0aASL A; Multiply by 8, yielding the max available tile-bound value. [fa96]0aASL A [fa97]0aASL A [fa98]c5 eeCMP Temp_Int24_U; Is the passed-in value less than the max? [fa9a]b0 02BCS @_setBar; If so, jump to draw. [fa9c]85 eeSTA Temp_Int24_U; Else, cap to the max.
; ; 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. ;
[fa9e]@_setBar: [fa9e]84 efSTY Temp_0xEF; Set the bar index in a temp variable [faa0]a9 20LDA #$20; Upper byte of the PPU address. [faa2]85 e9STA PPU_TargetAddr_U; Set it. [faa4]b9 6c faLDA a:STATUS_BAR_PPU_ADDR_L,Y; Lower byte of the PPU address, based on the bar index. [faa7]85 e8STA PPU_TargetAddr; Set it.
; ; Set the number of tiles to draw as the bar width + 1. ;
[faa9]ae ee 04LDX a:UI_MPAndHPBarWidth; Load the width of the bar. [faac]e8INX; Increment by 1. [faad]8aTXA; A = result. [faae]20 dc cfJSR PPUBuffer_QueueCommandOrLength; Queue that as the number of tiles to draw.
; ; Calculate the numer of pixels to draw. ;
[fab1]a5 eeLDA Temp_Int24_U; Load the value to draw. [fab3]4aLSR A; Multiply by 8 to get a tile-bound value. [fab4]4aLSR A [fab5]4aLSR A [fab6]f0 0eBEQ @_drawPartialTile; If 0, jump. [fab8]48PHA; Push the normalized value to the stack.
; ; Begin the loop for drawing fill tiles. ;
[fab9]85 ecSTA Temp_Int24; Store it as a temp variable. [fabb]b9 6e faLDA a:STATUS_BAR_FILL_TILE_INDEXES,Y; Load the fill tile index to draw for this bar. [fabe]@_fillTileLoop: [fabe]20 45 f8JSR PPUBuffer_Set; Queue drawing the tile. [fac1]c6 ecDEC Temp_Int24; Decrement the loop counter. [fac3]d0 f9BNE @_fillTileLoop; If > 0, loop.
; ; The loop is complete. ; ; See if the bar is full. If not, the rest will need to be ; filled in. ;
[fac5]68PLA; Pop the normalized value. [fac6]@_drawPartialTile: [fac6]cd ee 04CMP a:UI_MPAndHPBarWidth; Was the bar maxed out? [fac9]f0 17BEQ @_drawEndBarDeco; If so, jump.
; ; The bar was not maxed out. Draw a partial tile before ; the empty tiles. ; ; A partial tile has the fill in the left and empty space ; in the right. ;
[facb]48PHA; Else, push the normalized value back to the stack. [facc]b9 70 faLDA a:STATUS_BAR_PARTIAL_TILE_INDEXES,Y; Load the partial tile index to draw for this bar. [facf]20 45 f8JSR PPUBuffer_Set; Queue drawing the tile. [fad2]68PLA; Pop the normalized value from the stack. [fad3]a8TAY; Y = result.
; ; Now fill in the rest with empty tiles. ; ; This will be tile index 7 for either bar. ;
[fad4]a9 07LDA #$07 [fad6]@_emptyTileLoop: [fad6]c8INY; Y++ (next value for the bar). [fad7]cc ee 04CPY a:UI_MPAndHPBarWidth; Would this max out the bar? [fada]f0 06BEQ @_drawEndBarDeco; If so, break out of the loop. [fadc]20 45 f8JSR PPUBuffer_Set; Else, queue drawing the tile. [fadf]4c d6 faJMP @_emptyTileLoop; Loop.
; ; Draw the ending decoration for the bar. ;
[fae2]@_drawEndBarDeco: [fae2]a9 0bLDA #$0b; 0x0B == Ending bar decoration. [fae4]20 45 f8JSR PPUBuffer_Set; Queue drawing it. [fae7]86 20STX PPUBuffer_WriteOffset; X = resulting write offset.
; ; Next, the partial tile will be updated to fill in the ; number of pixels remaining in the bar value. ;
[fae9]a5 eeLDA Temp_Int24_U; Load the raw value to set for the bar. [faeb]29 07AND #$07; Convert to a partial tile index below. [faed]20 8c f7JSR Math_MultiplyBy16; Multiply by 16 (2 tiles). [faf0]a8TAY; Y = result.
; ; Compute the PPU address where the partial tile resides ; for this bar. ;
[faf1]a9 10LDA #$10; Upper byte of PPU address. [faf3]85 e9STA PPU_TargetAddr_U; Set it. [faf5]a6 efLDX Temp_0xEF; Load the bar index. [faf7]bd 72 faLDA a:STATUS_BAR_PARTIAL_PPU_ADDR_L,X; Lower byte of the PPU address, based on bar index. [fafa]85 e8STA PPU_TargetAddr; Set it. [fafc]a9 10LDA #$10; A = Length of 16 tiles (2 pattern draws). [fafe]20 dc cfJSR PPUBuffer_QueueCommandOrLength; Set as the draw length.
; ; Figure out which bar is being drawn, so the appropriate ; partial tile can be updated. ;
[fb01]a5 efLDA Temp_0xEF; Load the bar index. [fb03]d0 0fBNE @_updateMagicBarPartialTilePattern1Loop; If not the Power bar, jump.
; ; This is the Power bar. Queue drawing each tile in range. ;
[fb05]@_updatePowerBarPartialTileLoop: [fb05]b9 2f fbLDA a:STATUS_BAR_PARTIAL_PPU_TILES,Y; Load the partial tile to set. [fb08]c8INY; Y++ (next partial tile to draw). [fb09]20 45 f8JSR PPUBuffer_Set; Queue drawing the loaded tile. [fb0c]98TYA; A = Next tile index. [fb0d]29 0fAND #$0f; Cap to 16. [fb0f]d0 f4BNE @_updatePowerBarPartialTileLoop; If the tile index is within bounds (> 0, < 16), loop.
; ; We're done. Finish up and exit. ;
[fb11]86 20STX PPUBuffer_WriteOffset; Store the resulting PPU buffer write offset. [fb13]60RTS; And return.
; ; This is the Magic bar. Queue drawing each tile in range. ; ; Start with drawing pattern 1 based on pattern 2 in the ; selected tile. This is half-way through the tile. (Tiles ; consist of 2 patterns). ; ; Note that the second half of all these bar tiles are the same. ;
[fb14]@_updateMagicBarPartialTilePattern1Loop: [fb14]b9 37 fbLDA a:STATUS_BAR_PARTIAL_PPU_TILES+8,Y; Load the partial tile to set. [fb17]c8INY; Y++ (next partial tile to draw). [fb18]20 45 f8JSR PPUBuffer_Set; Queue drawing the loaded tile. [fb1b]98TYA; A = Next tile index. [fb1c]29 07AND #$07; Cap to 8 (pattern length). [fb1e]d0 f4BNE @_updateMagicBarPartialTilePattern1Loop; If the tile index is within bounds (> 0, < 8), loop. [fb20]@_updateMagicBarPartialTilePattern2Loop: [fb20]b9 27 fbLDA a:STATUS_BAR_PARTIAL_PPU_TILES-8,Y; Load the first half as pattern 1. [fb23]c8INY; Y++ (next partial tile to draw). [fb24]20 45 f8JSR PPUBuffer_Set; Queue drawing the loaded tile. [fb27]@LAB_PRG15_MIRROR__fb27: [fb27]98TYA; A = loop counter. [fb28]29 07AND #$07; Cap to 8 (pattern length). [fb2a]d0 f4BNE @_updateMagicBarPartialTilePattern2Loop; If the tile index is within bounds (> 0, < 8), loop.
; ; We're done. Finish up and exit. ;
[fb2c]86 20STX PPUBuffer_WriteOffset; Store the resulting PPU buffer write offset. [fb2e]60RTS; And return.
;============================================================================ ; The collection of partial tiles for the Power/Magic bars. ; ; When drawing the Power bar, the tiles are used as normal. ; The first half is pattern 1, the second half is pattern 2. ; ; When drawing the Magic bar, that's reversed. Pattern 1 ; comes from the second half, and pattern 2 comes from the ; first half. ; ; XREFS: ; _updatePowerBarPartialTileLoop ; [$PRG15_MIRROR::fb05] ;============================================================================
; ; ........ ; ████████ ; ........ ; ........ ; ........ ; ........ ; ████████ ; ........ ; ; XREFS: ; _updatePowerBarPartialTileLoop ; [$PRG15_MIRROR::fb05] ;
[fb2f]STATUS_BAR_PARTIAL_PPU_TILES: [fb2f]00 ff 00 00 00 00 ff 00.byte $00,$ff,$00,$00,$00,$00,$ff,$00; byte
; ; XREFS: ; _updateMagicBarPartialTilePattern1Loop ; [$PRG15_MIRROR::fb14] ;
[fb37]STATUS_BAR_PARTIAL_PPU_TILES_row0_1: [fb37]00 ff 00 00 00 00 ff 00.byte $00,$ff,$00,$00,$00,$00,$ff,$00; byte
; ; ........ ; ████████ ; ░....... ; ░....... ; ░....... ; ░....... ; ████████ ; ........ ;
[fb3f]00.byte $00; ........ [fb40]ff 80 80 80 80 ff 00 00.byte $ff,$80,$80,$80,$80,$ff,$00,$00; byte [fb48]ff 00 00 00 00 ff 00.byte $ff,$00,$00,$00,$00,$ff,$00; byte
; ; ........ ; ████████ ; ░░...... ; ░░...... ; ░░...... ; ░░...... ; ████████ ; ........ ;
[fb4f]00 ff c0 c0 c0 c0 ff 00.byte $00,$ff,$c0,$c0,$c0,$c0,$ff,$00; byte [fb57]00 ff 00 00 00 00 ff 00.byte $00,$ff,$00,$00,$00,$00,$ff,$00; byte
; ; ........ ; ████████ ; ░░░..... ; ░░░..... ; ░░░..... ; ░░░..... ; ████████ ; ........ ;
[fb5f]00 ff e0 e0 e0 e0 ff 00.byte $00,$ff,$e0,$e0,$e0,$e0,$ff,$00; byte [fb67]00 ff 00 00 00 00 ff 00.byte $00,$ff,$00,$00,$00,$00,$ff,$00; byte
; ; ........ ; ████████ ; ░░░░.... ; ░░░░.... ; ░░░░.... ; ░░░░.... ; ████████ ; ........ ;
[fb6f]00 ff f0 f0 f0 f0 ff 00.byte $00,$ff,$f0,$f0,$f0,$f0,$ff,$00; byte [fb77]00 ff 00 00 00 00 ff 00.byte $00,$ff,$00,$00,$00,$00,$ff,$00; byte
; ; ........ ; ████████ ; ░░░░░... ; ░░░░░... ; ░░░░░... ; ░░░░░... ; ████████ ; ........ ;
[fb7f]00 ff f8 f8 f8 f8 ff 00.byte $00,$ff,$f8,$f8,$f8,$f8,$ff,$00; byte [fb87]00 ff 00 00 00 00 ff 00.byte $00,$ff,$00,$00,$00,$00,$ff,$00; byte
; ; ........ ; ████████ ; ░░░░░░.. ; ░░░░░░.. ; ░░░░░░.. ; ░░░░░░.. ; ████████ ; ........ ;
[fb8f]00 ff fc fc fc fc ff 00.byte $00,$ff,$fc,$fc,$fc,$fc,$ff,$00; byte [fb97]00 ff 00 00 00 00 ff 00.byte $00,$ff,$00,$00,$00,$00,$ff,$00; byte
; ; ........ ; ████████ ; ░░░░░░░. ; ░░░░░░░. ; ░░░░░░░. ; ░░░░░░░. ; ████████ ; ........ ;
[fb9f]00 ff fe fe fe fe ff 00.byte $00,$ff,$fe,$fe,$fe,$fe,$ff,$00; byte [fba7]00 ff 00 00 00 00 ff 00.byte $00,$ff,$00,$00,$00,$00,$ff,$00; byte
;============================================================================ ; TODO: Document UI_DrawSelectedItem ; ; INPUTS: ; None. ; ; OUTPUTS: ; TODO ; ; XREFS: ; UI_DrawHUDSprites ;============================================================================
[fbaf]UI_DrawSelectedItem: [fbaf]a9 13LDA #$13 [fbb1]8d 06 20STA a:PPUADDR [fbb4]a9 c0LDA #$c0 [fbb6]8d 06 20STA a:PPUADDR [fbb9]ad c1 03LDA a:SelectedItem [fbbc]10 07BPL @LAB_PRG15_MIRROR__fbc5 [fbbe]a0 40LDY #$40 [fbc0]a9 00LDA #$00 [fbc2]4c b2 fcJMP PPU_FillData [fbc5]@LAB_PRG15_MIRROR__fbc5: [fbc5]0aASL A [fbc6]0aASL A [fbc7]a8TAY [fbc8]ad 00 01LDA a:CurrentROMBank [fbcb]48PHA [fbcc]a2 0aLDX #$0a [fbce]20 1a ccJSR MMC1_UpdateROMBank [fbd1]@LAB_PRG15_MIRROR__fbd1: [fbd1]b9 e4 b4LDA a:ITEM_TILEMAP_INDEXES,Y [fbd4]20 f0 fbJSR UI_Maybe_GetItemSpritePPUTileAddr [fbd7]98TYA [fbd8]48PHA [fbd9]a0 00LDY #$00 [fbdb]@LAB_PRG15_MIRROR__fbdb: [fbdb]b1 ecLDA (Temp_Int24),Y [fbdd]8d 07 20STA a:PPUDATA [fbe0]c8INY [fbe1]c0 10CPY #$10 [fbe3]d0 f6BNE @LAB_PRG15_MIRROR__fbdb [fbe5]68PLA [fbe6]a8TAY [fbe7]c8INY [fbe8]98TYA [fbe9]29 03AND #$03 [fbeb]d0 e4BNE @LAB_PRG15_MIRROR__fbd1 [fbed]4c d6 f3JMP MMC1_UpdatePRGBankToStackA
;============================================================================ ; TODO: Document UI_Maybe_GetItemSpritePPUTileAddr ; ; INPUTS: ; A ; ; OUTPUTS: ; TODO ; ; XREFS: ; TextBox_LoadItemSourceTiles ; UI_DrawSelectedItem ;============================================================================
[fbf0]UI_Maybe_GetItemSpritePPUTileAddr: [fbf0]85 edSTA Temp_Int24_M [fbf2]a9 00LDA #$00 [fbf4]46 edLSR Temp_Int24_M [fbf6]6aROR A [fbf7]46 edLSR Temp_Int24_M [fbf9]6aROR A [fbfa]46 edLSR Temp_Int24_M [fbfc]6aROR A [fbfd]46 edLSR Temp_Int24_M [fbff]6aROR A [fc00]69 00ADC #$00 [fc02]85 ecSTA Temp_Int24 [fc04]a5 edLDA Temp_Int24_M [fc06]69 85ADC #$85 [fc08]85 edSTA Temp_Int24_M [fc0a]60RTS
;============================================================================ ; TODO: Document Player_SetItem ; ; INPUTS: ; A ; ; OUTPUTS: ; TODO ; ; XREFS: ; Player_Equip ;============================================================================
[fc0b]Player_SetItem: [fc0b]8d c1 03STA a:SelectedItem [fc0e]a2 13LDX #$13 [fc10]86 e9STX PPU_TargetAddr_U [fc12]a2 c0LDX #$c0 [fc14]86 e8STX PPU_TargetAddr [fc16]09 80ORA #$80
; ; v-- Fall through --v ;
;============================================================================ ; TODO: Document TextBox_LoadItemSourceTiles ; ; INPUTS: ; A ; ; OUTPUTS: ; TODO ; ; XREFS: ; TextBox_DrawItemImage ;============================================================================
[fc18]TextBox_LoadItemSourceTiles: [fc18]48PHA [fc19]20 85 f7JSR Player_GetInventoryIndexForItem [fc1c]aaTAX [fc1d]bd 5b fcLDA a:FUN_PRG15_MIRROR__fc0b__LOWER_ADDR_TABLE,X [fc20]85 eeSTA Temp_Int24_U [fc22]bd 60 fcLDA a:FUN_PRG15_MIRROR__fc0b__UPPER_ADDR_TABLE,X [fc25]85 efSTA Temp_0xEF [fc27]68PLA [fc28]29 1fAND #$1f [fc2a]0aASL A [fc2b]0aASL A [fc2c]a8TAY [fc2d]ad 00 01LDA a:CurrentROMBank [fc30]48PHA [fc31]a2 0aLDX #$0a [fc33]20 1a ccJSR MMC1_UpdateROMBank [fc36]@LAB_PRG15_MIRROR__fc36: [fc36]b1 eeLDA (Temp_Int24_U),Y [fc38]20 f0 fbJSR UI_Maybe_GetItemSpritePPUTileAddr [fc3b]98TYA [fc3c]48PHA [fc3d]a9 10LDA #$10 [fc3f]20 dc cfJSR PPUBuffer_QueueCommandOrLength [fc42]a0 00LDY #$00 [fc44]@LAB_PRG15_MIRROR__fc44: [fc44]20 42 f8JSR PPUBuffer_WriteFromTemp [fc47]c0 10CPY #$10 [fc49]d0 f9BNE @LAB_PRG15_MIRROR__fc44 [fc4b]86 20STX PPUBuffer_WriteOffset [fc4d]20 1e f8JSR PPU_IncrementAddrBy16 [fc50]68PLA [fc51]a8TAY [fc52]c8INY [fc53]98TYA [fc54]29 03AND #$03 [fc56]d0 deBNE @LAB_PRG15_MIRROR__fc36 [fc58]4c d6 f3JMP MMC1_UpdatePRGBankToStackA
; ; XREFS: ; TextBox_LoadItemSourceTiles ;
[fc5b]FUN_PRG15_MIRROR__fc0b__LOWER_ADDR_TABLE: [fc5b]a0.byte $a0; [0]: [fc5c]b0.byte $b0; [1]: [fc5d]c0.byte $c0; [2]: [fc5e]d0.byte $d0; [3]: [fc5f]e4.byte $e4; [4]:
; ; XREFS: ; TextBox_LoadItemSourceTiles ;
[fc60]FUN_PRG15_MIRROR__fc0b__UPPER_ADDR_TABLE: [fc60]b4.byte $b4; [0]: [fc61]b4.byte $b4; [1]: [fc62]b4.byte $b4; [2]: [fc63]b4.byte $b4; [3]: [fc64]b4.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]a2 ffLDX #$ff; X = 0xFF [fc67]9aTXS; Store X in memory.
; ; Switch to bank 12 and run StartScreen_Draw. ;
[fc68]20 59 f8JSR MMC1_LoadBankAndJump; Jump to: [fc6b]0c.byte BANK_12_LOGIC; Bank = 12 [fc6c]20 9epointer StartScreen_Draw-1; Address = StartScreen_Draw
; ; Wait for a choice at the game's start screen. ;
[fc6e]@_waitForInput: [fc6e]20 25 caJSR WaitForNextFrame; Wait for the next frame. [fc71]20 4f cbJSR Screen_ResetSpritesForNonGame
; ; Switch to bank 12 and run StartScreen_CheckHandleInput. ;
[fc74]20 59 f8JSR MMC1_LoadBankAndJump; Jump to: [fc77]0c.byte BANK_12_LOGIC; Bank = 12 [fc78]43 9fpointer StartScreen_CheckHandleInput-1; Address = StartScreen_CheckHandleInput [fc7a]@_afterCheckHandleInputFarJump: [fc7a]a5 19LDA Joy1_ChangedButtonMask; Check the changed controller 1 button mask. [fc7c]29 10AND #$10; Was the Start button pressed? [fc7e]f0 eeBEQ @_waitForInput; If not, loop.
; ; The player pressed Start. Check what they chose. ; ; But first, play the titlescreen music. ;
[fc80]a9 08LDA #$08 [fc82]85 faSTA Music_Current [fc84]ad 87 06LDA a:DAT_0687; Check the chosen option. [fc87]f0 0fBEQ @_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]20 59 f8JSR MMC1_LoadBankAndJump; Jump to: [fc8c]0c.byte BANK_12_LOGIC; Bank = 12 [fc8d]9c 90pointer PasswordScreen_Show-1; Address = PasswordScreen_Show
; ; Switch to bank 12 and run Player_SetInitialExpAndGold. ;
[fc8f]@_afterPasswordScreenShow: [fc8f]20 59 f8JSR MMC1_LoadBankAndJump; Jump to: [fc92]0c.byte BANK_12_LOGIC; Bank = 12 [fc93]b0 95pointer Player_SetInitialExpAndGold-1; Address = Player_SetInitialExpAndGold [fc95]@_afterSetExpGoldFarJump: [fc95]4c 0a dbJMP Player_Spawn
; ; The player chose to start the game. ; ; Switch to bank 12 and run SplashAnimation_RunIntro. ;
[fc98]@_startGame: [fc98]20 59 f8JSR MMC1_LoadBankAndJump; Jump to: [fc9b]0c.byte BANK_12_LOGIC; Bank = 12 [fc9c]99 a7pointer SplashAnimation_RunIntro-1; Address = SplashAnimation_RunIntro
; ; Switch to bank 12 and run Player_SetStartGameState. ;
[fc9e]@_afterRunIntroFarJump: [fc9e]20 59 f8JSR MMC1_LoadBankAndJump; Jump to: [fca1]0c.byte BANK_12_LOGIC; Bank = 12 [fca2]6f 95pointer Player_SetStartGameState-1; Address = Player_SetStartGameState
; ; Begin the game. ;
[fca4]@_afterSetStartGameStateFarJump: [fca4]4c 26 dbJMP 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]86 27STX Arg_DrawSprite_PosX; Set the X argument. [fca9]84 28STY Arg_DrawSprite_PosY; Set the Y argument. [fcab]a2 00LDX #$00 [fcad]86 29STX CurrentSprite_FlipMask; Clear the flip mask. [fcaf]4c 57 f0JMP 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]8d 07 20STA a:PPUDATA; Store the value in PPUDATA. [fcb5]88DEY; Y-- [fcb6]d0 faBNE PPU_FillData; If not 0, loop. [fcb8]60RTS
;============================================================================ ; 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:
; ; Set the start position to the top-left ($2000). ;
[fcb9]a9 20LDA #$20 [fcbb]8d 06 20STA a:PPUADDR [fcbe]a9 00LDA #$00 [fcc0]8d 06 20STA a:PPUADDR
; ; Prepare the value and loop counter. ;
[fcc3]a8TAY; Y = 0 (value to write) [fcc4]a2 04LDX #$04; X = 4 (loop counter)
; ; Begin the loop. ;
[fcc6]@_loop: [fcc6]20 b2 fcJSR PPU_FillData; Fill 256 bytes of data. [fcc9]caDEX; X-- [fcca]d0 faBNE @_loop; If not 0, loop. [fccc]60RTS [fccd]bd.byte $bd; [0]: [fcce]ff.byte $ff; [1]: [fccf]ff.byte $ff; [2]: [fcd0]ff.byte $ff; [3]: [fcd1]ff.byte $ff; [4]: [fcd2]ff.byte $ff; [5]: [fcd3]ff.byte $ff; [6]: [fcd4]ff.byte $ff; [7]: [fcd5]ff.byte $ff; [8]: [fcd6]ff.byte $ff; [9]: [fcd7]ff.byte $ff; [10]: [fcd8]ff.byte $ff; [11]: [fcd9]ff.byte $ff; [12]: [fcda]ff.byte $ff; [13]: [fcdb]ff.byte $ff; [14]: [fcdc]ff.byte $ff; [15]: [fcdd]ff.byte $ff; [16]: [fcde]ff.byte $ff; [17]: [fcdf]ff.byte $ff; [18]: [fce0]ff.byte $ff; [19]: [fce1]ff.byte $ff; [20]: [fce2]ff.byte $ff; [21]: [fce3]ff.byte $ff; [22]: [fce4]ff.byte $ff; [23]: [fce5]ff.byte $ff; [24]: [fce6]ff.byte $ff; [25]: [fce7]ff.byte $ff; [26]: [fce8]ff.byte $ff; [27]: [fce9]ff.byte $ff; [28]: [fcea]ff.byte $ff; [29]: [fceb]ff.byte $ff; [30]: [fcec]ff.byte $ff; [31]: [fced]ff.byte $ff; [32]: [fcee]ff.byte $ff; [33]: [fcef]ff.byte $ff; [34]: [fcf0]ff.byte $ff; [35]: [fcf1]ff.byte $ff; [36]: [fcf2]ff.byte $ff; [37]: [fcf3]ff.byte $ff; [38]: [fcf4]ff.byte $ff; [39]: [fcf5]ff.byte $ff; [40]: [fcf6]ff.byte $ff; [41]: [fcf7]ff.byte $ff; [42]: [fcf8]ff.byte $ff; [43]: [fcf9]ff.byte $ff; [44]: [fcfa]ff.byte $ff; [45]: [fcfb]ff.byte $ff; [46]: [fcfc]ff.byte $ff; [47]: [fcfd]ff.byte $ff; [48]: [fcfe]ff.byte $ff; [49]: [fcff]ff.byte $ff; [50]: [fd00]ff.byte $ff; [51]: [fd01]ff.byte $ff; [52]: [fd02]ff.byte $ff; [53]: [fd03]ff.byte $ff; [54]: [fd04]ff.byte $ff; [55]: [fd05]ff.byte $ff; [56]: [fd06]ff.byte $ff; [57]: [fd07]ff.byte $ff; [58]: [fd08]ff.byte $ff; [59]: [fd09]ff.byte $ff; [60]: [fd0a]ff.byte $ff; [61]: [fd0b]ff.byte $ff; [62]: [fd0c]ff.byte $ff; [63]: [fd0d]ff.byte $ff; [64]: [fd0e]ff.byte $ff; [65]: [fd0f]ff.byte $ff; [66]: [fd10]ff.byte $ff; [67]: [fd11]ff.byte $ff; [68]: [fd12]ff.byte $ff; [69]: [fd13]ff.byte $ff; [70]: [fd14]ff.byte $ff; [71]: [fd15]ff.byte $ff; [72]: [fd16]ff.byte $ff; [73]: [fd17]ff.byte $ff; [74]: [fd18]ff.byte $ff; [75]: [fd19]ff.byte $ff; [76]: [fd1a]ff.byte $ff; [77]: [fd1b]ff.byte $ff; [78]: [fd1c]ff.byte $ff; [79]: [fd1d]ff.byte $ff; [80]: [fd1e]ff.byte $ff; [81]: [fd1f]ff.byte $ff; [82]: [fd20]ff.byte $ff; [83]: [fd21]ff.byte $ff; [84]: [fd22]ff.byte $ff; [85]: [fd23]ff.byte $ff; [86]: [fd24]ff.byte $ff; [87]: [fd25]ff.byte $ff; [88]: [fd26]ff.byte $ff; [89]: [fd27]ff.byte $ff; [90]: [fd28]ff.byte $ff; [91]: [fd29]ff.byte $ff; [92]: [fd2a]ff.byte $ff; [93]: [fd2b]ff.byte $ff; [94]: [fd2c]ff.byte $ff; [95]: [fd2d]ff.byte $ff; [96]: [fd2e]ff.byte $ff; [97]: [fd2f]ff.byte $ff; [98]: [fd30]ff.byte $ff; [99]: [fd31]ff.byte $ff; [100]: [fd32]ff.byte $ff; [101]: [fd33]ff.byte $ff; [102]: [fd34]ff.byte $ff; [103]: [fd35]ff.byte $ff; [104]: [fd36]ff.byte $ff; [105]: [fd37]ff.byte $ff; [106]: [fd38]ff.byte $ff; [107]: [fd39]ff.byte $ff; [108]: [fd3a]ff.byte $ff; [109]: [fd3b]ff.byte $ff; [110]: [fd3c]ff.byte $ff; [111]: [fd3d]ff.byte $ff; [112]: [fd3e]ff.byte $ff; [113]: [fd3f]ff.byte $ff; [114]: [fd40]ff.byte $ff; [115]: [fd41]ff.byte $ff; [116]: [fd42]ff.byte $ff; [117]: [fd43]ff.byte $ff; [118]: [fd44]ff.byte $ff; [119]: [fd45]ff.byte $ff; [120]: [fd46]ff.byte $ff; [121]: [fd47]ff.byte $ff; [122]: [fd48]ff.byte $ff; [123]: [fd49]ff.byte $ff; [124]: [fd4a]ff.byte $ff; [125]: [fd4b]ff.byte $ff; [126]: [fd4c]ff.byte $ff; [127]: [fd4d]ff.byte $ff; [128]: [fd4e]ff.byte $ff; [129]: [fd4f]ff.byte $ff; [130]: [fd50]ff.byte $ff; [131]: [fd51]ff.byte $ff; [132]: [fd52]ff.byte $ff; [133]: [fd53]ff.byte $ff; [134]: [fd54]ff.byte $ff; [135]: [fd55]ff.byte $ff; [136]: [fd56]ff.byte $ff; [137]: [fd57]ff.byte $ff; [138]: [fd58]ff.byte $ff; [139]: [fd59]ff.byte $ff; [140]: [fd5a]ff.byte $ff; [141]: [fd5b]ff.byte $ff; [142]: [fd5c]ff.byte $ff; [143]: [fd5d]ff.byte $ff; [144]: [fd5e]ff.byte $ff; [145]: [fd5f]ff.byte $ff; [146]: [fd60]ff.byte $ff; [147]: [fd61]ff.byte $ff; [148]: [fd62]ff.byte $ff; [149]: [fd63]ff.byte $ff; [150]: [fd64]ff.byte $ff; [151]: [fd65]ff.byte $ff; [152]: [fd66]ff.byte $ff; [153]: [fd67]ff.byte $ff; [154]: [fd68]ff.byte $ff; [155]: [fd69]ff.byte $ff; [156]: [fd6a]ff.byte $ff; [157]: [fd6b]ff.byte $ff; [158]: [fd6c]ff.byte $ff; [159]: [fd6d]ff.byte $ff; [160]: [fd6e]ff.byte $ff; [161]: [fd6f]ff.byte $ff; [162]: [fd70]ff.byte $ff; [163]: [fd71]ff.byte $ff; [164]: [fd72]ff.byte $ff; [165]: [fd73]ff.byte $ff; [166]: [fd74]ff.byte $ff; [167]: [fd75]ff.byte $ff; [168]: [fd76]ff.byte $ff; [169]: [fd77]ff.byte $ff; [170]: [fd78]ff.byte $ff; [171]: [fd79]ff.byte $ff; [172]: [fd7a]ff.byte $ff; [173]: [fd7b]ff.byte $ff; [174]: [fd7c]ff.byte $ff; [175]: [fd7d]ff.byte $ff; [176]: [fd7e]ff.byte $ff; [177]: [fd7f]ff.byte $ff; [178]: [fd80]ff.byte $ff; [179]: [fd81]ff.byte $ff; [180]: [fd82]ff.byte $ff; [181]: [fd83]ff.byte $ff; [182]: [fd84]ff.byte $ff; [183]: [fd85]ff.byte $ff; [184]: [fd86]ff.byte $ff; [185]: [fd87]ff.byte $ff; [186]: [fd88]ff.byte $ff; [187]: [fd89]ff.byte $ff; [188]: [fd8a]ff.byte $ff; [189]: [fd8b]ff.byte $ff; [190]: [fd8c]ff.byte $ff; [191]: [fd8d]ff.byte $ff; [192]: [fd8e]ff.byte $ff; [193]: [fd8f]ff.byte $ff; [194]: [fd90]ff.byte $ff; [195]: [fd91]ff.byte $ff; [196]: [fd92]ff.byte $ff; [197]: [fd93]ff.byte $ff; [198]: [fd94]ff.byte $ff; [199]: [fd95]ff.byte $ff; [200]: [fd96]ff.byte $ff; [201]: [fd97]ff.byte $ff; [202]: [fd98]ff.byte $ff; [203]: [fd99]ff.byte $ff; [204]: [fd9a]ff.byte $ff; [205]: [fd9b]ff.byte $ff; [206]: [fd9c]ff.byte $ff; [207]: [fd9d]ff.byte $ff; [208]: [fd9e]ff.byte $ff; [209]: [fd9f]ff.byte $ff; [210]: [fda0]ff.byte $ff; [211]: [fda1]ff.byte $ff; [212]: [fda2]ff.byte $ff; [213]: [fda3]ff.byte $ff; [214]: [fda4]ff.byte $ff; [215]: [fda5]ff.byte $ff; [216]: [fda6]ff.byte $ff; [217]: [fda7]ff.byte $ff; [218]: [fda8]ff.byte $ff; [219]: [fda9]ff.byte $ff; [220]: [fdaa]ff.byte $ff; [221]: [fdab]ff.byte $ff; [222]: [fdac]ff.byte $ff; [223]: [fdad]ff.byte $ff; [224]: [fdae]ff.byte $ff; [225]: [fdaf]ff.byte $ff; [226]: [fdb0]ff.byte $ff; [227]: [fdb1]ff.byte $ff; [228]: [fdb2]ff.byte $ff; [229]: [fdb3]ff.byte $ff; [230]: [fdb4]ff.byte $ff; [231]: [fdb5]ff.byte $ff; [232]: [fdb6]ff.byte $ff; [233]: [fdb7]ff.byte $ff; [234]: [fdb8]ff.byte $ff; [235]: [fdb9]ff.byte $ff; [236]: [fdba]ff.byte $ff; [237]: [fdbb]ff.byte $ff; [238]: [fdbc]ff.byte $ff; [239]: [fdbd]ff.byte $ff; [240]: [fdbe]ff.byte $ff; [241]: [fdbf]ff.byte $ff; [242]: [fdc0]ff.byte $ff; [243]: [fdc1]ff.byte $ff; [244]: [fdc2]ff.byte $ff; [245]: [fdc3]ff.byte $ff; [246]: [fdc4]ff.byte $ff; [247]: [fdc5]ff.byte $ff; [248]: [fdc6]ff.byte $ff; [249]: [fdc7]ff.byte $ff; [250]: [fdc8]ff.byte $ff; [251]: [fdc9]ff.byte $ff; [252]: [fdca]ff.byte $ff; [253]: [fdcb]ff.byte $ff; [254]: [fdcc]ff.byte $ff; [255]: [fdcd]ff.byte $ff; [256]: [fdce]ff.byte $ff; [257]: [fdcf]ff.byte $ff; [258]: [fdd0]ff.byte $ff; [259]: [fdd1]ff.byte $ff; [260]: [fdd2]ff.byte $ff; [261]: [fdd3]ff.byte $ff; [262]: [fdd4]ff.byte $ff; [263]: [fdd5]ff.byte $ff; [264]: [fdd6]ff.byte $ff; [265]: [fdd7]ff.byte $ff; [266]: [fdd8]ff.byte $ff; [267]: [fdd9]ff.byte $ff; [268]: [fdda]ff.byte $ff; [269]: [fddb]ff.byte $ff; [270]: [fddc]ff.byte $ff; [271]: [fddd]ff.byte $ff; [272]: [fdde]ff.byte $ff; [273]: [fddf]ff.byte $ff; [274]: [fde0]ff.byte $ff; [275]: [fde1]ff.byte $ff; [276]: [fde2]ff.byte $ff; [277]: [fde3]ff.byte $ff; [278]: [fde4]ff.byte $ff; [279]: [fde5]ff.byte $ff; [280]: [fde6]ff.byte $ff; [281]: [fde7]ff.byte $ff; [282]: [fde8]ff.byte $ff; [283]: [fde9]ff.byte $ff; [284]: [fdea]ff.byte $ff; [285]: [fdeb]ff.byte $ff; [286]: [fdec]ff.byte $ff; [287]: [fded]ff.byte $ff; [288]: [fdee]ff.byte $ff; [289]: [fdef]ff.byte $ff; [290]: [fdf0]ff.byte $ff; [291]: [fdf1]ff.byte $ff; [292]: [fdf2]ff.byte $ff; [293]: [fdf3]ff.byte $ff; [294]: [fdf4]ff.byte $ff; [295]: [fdf5]ff.byte $ff; [296]: [fdf6]ff.byte $ff; [297]: [fdf7]ff.byte $ff; [298]: [fdf8]ff.byte $ff; [299]: [fdf9]ff.byte $ff; [300]: [fdfa]ff.byte $ff; [301]: [fdfb]ff.byte $ff; [302]: [fdfc]ff.byte $ff; [303]: [fdfd]ff.byte $ff; [304]: [fdfe]ff.byte $ff; [305]: [fdff]ff.byte $ff; [306]: [fe00]ff.byte $ff; [307]: [fe01]ff.byte $ff; [308]: [fe02]ff.byte $ff; [309]: [fe03]ff.byte $ff; [310]: [fe04]ff.byte $ff; [311]: [fe05]ff.byte $ff; [312]: [fe06]ff.byte $ff; [313]: [fe07]ff.byte $ff; [314]: [fe08]ff.byte $ff; [315]: [fe09]ff.byte $ff; [316]: [fe0a]ff.byte $ff; [317]: [fe0b]ff.byte $ff; [318]: [fe0c]ff.byte $ff; [319]: [fe0d]ff.byte $ff; [320]: [fe0e]ff.byte $ff; [321]: [fe0f]ff.byte $ff; [322]: [fe10]ff.byte $ff; [323]: [fe11]ff.byte $ff; [324]: [fe12]ff.byte $ff; [325]: [fe13]ff.byte $ff; [326]: [fe14]ff.byte $ff; [327]: [fe15]ff.byte $ff; [328]: [fe16]ff.byte $ff; [329]: [fe17]ff.byte $ff; [330]: [fe18]ff.byte $ff; [331]: [fe19]ff.byte $ff; [332]: [fe1a]ff.byte $ff; [333]: [fe1b]ff.byte $ff; [334]: [fe1c]ff.byte $ff; [335]: [fe1d]ff.byte $ff; [336]: [fe1e]ff.byte $ff; [337]: [fe1f]ff.byte $ff; [338]: [fe20]ff.byte $ff; [339]: [fe21]ff.byte $ff; [340]: [fe22]ff.byte $ff; [341]: [fe23]ff.byte $ff; [342]: [fe24]ff.byte $ff; [343]: [fe25]ff.byte $ff; [344]: [fe26]ff.byte $ff; [345]: [fe27]ff.byte $ff; [346]: [fe28]ff.byte $ff; [347]: [fe29]ff.byte $ff; [348]: [fe2a]ff.byte $ff; [349]: [fe2b]ff.byte $ff; [350]: [fe2c]ff.byte $ff; [351]: [fe2d]ff.byte $ff; [352]: [fe2e]ff.byte $ff; [353]: [fe2f]ff.byte $ff; [354]: [fe30]ff.byte $ff; [355]: [fe31]ff.byte $ff; [356]: [fe32]ff.byte $ff; [357]: [fe33]ff.byte $ff; [358]: [fe34]ff.byte $ff; [359]: [fe35]ff.byte $ff; [360]: [fe36]ff.byte $ff; [361]: [fe37]ff.byte $ff; [362]: [fe38]ff.byte $ff; [363]: [fe39]ff.byte $ff; [364]: [fe3a]ff.byte $ff; [365]: [fe3b]ff.byte $ff; [366]: [fe3c]ff.byte $ff; [367]: [fe3d]ff.byte $ff; [368]: [fe3e]ff.byte $ff; [369]: [fe3f]ff.byte $ff; [370]: [fe40]ff.byte $ff; [371]: [fe41]ff.byte $ff; [372]: [fe42]ff.byte $ff; [373]: [fe43]ff.byte $ff; [374]: [fe44]ff.byte $ff; [375]: [fe45]ff.byte $ff; [376]: [fe46]ff.byte $ff; [377]: [fe47]ff.byte $ff; [378]: [fe48]ff.byte $ff; [379]: [fe49]ff.byte $ff; [380]: [fe4a]ff.byte $ff; [381]: [fe4b]ff.byte $ff; [382]: [fe4c]ff.byte $ff; [383]: [fe4d]ff.byte $ff; [384]: [fe4e]ff.byte $ff; [385]: [fe4f]ff.byte $ff; [386]: [fe50]ff.byte $ff; [387]: [fe51]ff.byte $ff; [388]: [fe52]ff.byte $ff; [389]: [fe53]ff.byte $ff; [390]: [fe54]ff.byte $ff; [391]: [fe55]ff.byte $ff; [392]: [fe56]ff.byte $ff; [393]: [fe57]ff.byte $ff; [394]: [fe58]ff.byte $ff; [395]: [fe59]ff.byte $ff; [396]: [fe5a]ff.byte $ff; [397]: [fe5b]ff.byte $ff; [398]: [fe5c]ff.byte $ff; [399]: [fe5d]ff.byte $ff; [400]: [fe5e]ff.byte $ff; [401]: [fe5f]ff.byte $ff; [402]: [fe60]ff.byte $ff; [403]: [fe61]ff.byte $ff; [404]: [fe62]ff.byte $ff; [405]: [fe63]ff.byte $ff; [406]: [fe64]ff.byte $ff; [407]: [fe65]ff.byte $ff; [408]: [fe66]ff.byte $ff; [409]: [fe67]ff.byte $ff; [410]: [fe68]ff.byte $ff; [411]: [fe69]ff.byte $ff; [412]: [fe6a]ff.byte $ff; [413]: [fe6b]ff.byte $ff; [414]: [fe6c]ff.byte $ff; [415]: [fe6d]ff.byte $ff; [416]: [fe6e]ff.byte $ff; [417]: [fe6f]ff.byte $ff; [418]: [fe70]ff.byte $ff; [419]: [fe71]ff.byte $ff; [420]: [fe72]ff.byte $ff; [421]: [fe73]ff.byte $ff; [422]: [fe74]ff.byte $ff; [423]: [fe75]ff.byte $ff; [424]: [fe76]ff.byte $ff; [425]: [fe77]ff.byte $ff; [426]: [fe78]ff.byte $ff; [427]: [fe79]ff.byte $ff; [428]: [fe7a]ff.byte $ff; [429]: [fe7b]ff.byte $ff; [430]: [fe7c]ff.byte $ff; [431]: [fe7d]ff.byte $ff; [432]: [fe7e]ff.byte $ff; [433]: [fe7f]ff.byte $ff; [434]: [fe80]ff.byte $ff; [435]: [fe81]ff.byte $ff; [436]: [fe82]ff.byte $ff; [437]: [fe83]ff.byte $ff; [438]: [fe84]ff.byte $ff; [439]: [fe85]ff.byte $ff; [440]: [fe86]ff.byte $ff; [441]: [fe87]ff.byte $ff; [442]: [fe88]ff.byte $ff; [443]: [fe89]ff.byte $ff; [444]: [fe8a]ff.byte $ff; [445]: [fe8b]ff.byte $ff; [446]: [fe8c]ff.byte $ff; [447]: [fe8d]ff.byte $ff; [448]: [fe8e]ff.byte $ff; [449]: [fe8f]ff.byte $ff; [450]: [fe90]ff.byte $ff; [451]: [fe91]ff.byte $ff; [452]: [fe92]ff.byte $ff; [453]: [fe93]ff.byte $ff; [454]: [fe94]ff.byte $ff; [455]: [fe95]ff.byte $ff; [456]: [fe96]ff.byte $ff; [457]: [fe97]ff.byte $ff; [458]: [fe98]ff.byte $ff; [459]: [fe99]ff.byte $ff; [460]: [fe9a]ff.byte $ff; [461]: [fe9b]ff.byte $ff; [462]: [fe9c]ff.byte $ff; [463]: [fe9d]ff.byte $ff; [464]: [fe9e]ff.byte $ff; [465]: [fe9f]ff.byte $ff; [466]: [fea0]ff.byte $ff; [467]: [fea1]ff.byte $ff; [468]: [fea2]ff.byte $ff; [469]: [fea3]ff.byte $ff; [470]: [fea4]ff.byte $ff; [471]: [fea5]ff.byte $ff; [472]: [fea6]ff.byte $ff; [473]: [fea7]ff.byte $ff; [474]: [fea8]ff.byte $ff; [475]: [fea9]ff.byte $ff; [476]: [feaa]ff.byte $ff; [477]: [feab]ff.byte $ff; [478]: [feac]ff.byte $ff; [479]: [fead]ff.byte $ff; [480]: [feae]ff.byte $ff; [481]: [feaf]ff.byte $ff; [482]: [feb0]ff.byte $ff; [483]: [feb1]ff.byte $ff; [484]: [feb2]ff.byte $ff; [485]: [feb3]ff.byte $ff; [486]: [feb4]ff.byte $ff; [487]: [feb5]ff.byte $ff; [488]: [feb6]ff.byte $ff; [489]: [feb7]ff.byte $ff; [490]: [feb8]ff.byte $ff; [491]: [feb9]ff.byte $ff; [492]: [feba]ff.byte $ff; [493]: [febb]ff.byte $ff; [494]: [febc]ff.byte $ff; [495]: [febd]ff.byte $ff; [496]: [febe]ff.byte $ff; [497]: [febf]ff.byte $ff; [498]: [fec0]ff.byte $ff; [499]: [fec1]ff.byte $ff; [500]: [fec2]ff.byte $ff; [501]: [fec3]ff.byte $ff; [502]: [fec4]ff.byte $ff; [503]: [fec5]ff.byte $ff; [504]: [fec6]ff.byte $ff; [505]: [fec7]ff.byte $ff; [506]: [fec8]ff.byte $ff; [507]: [fec9]ff.byte $ff; [508]: [feca]ff.byte $ff; [509]: [fecb]ff.byte $ff; [510]: [fecc]ff.byte $ff; [511]: [fecd]ff.byte $ff; [512]: [fece]ff.byte $ff; [513]: [fecf]ff.byte $ff; [514]: [fed0]ff.byte $ff; [515]: [fed1]ff.byte $ff; [516]: [fed2]ff.byte $ff; [517]: [fed3]ff.byte $ff; [518]: [fed4]ff.byte $ff; [519]: [fed5]ff.byte $ff; [520]: [fed6]ff.byte $ff; [521]: [fed7]ff.byte $ff; [522]: [fed8]ff.byte $ff; [523]: [fed9]ff.byte $ff; [524]: [feda]ff.byte $ff; [525]: [fedb]ff.byte $ff; [526]: [fedc]ff.byte $ff; [527]: [fedd]ff.byte $ff; [528]: [fede]ff.byte $ff; [529]: [fedf]ff.byte $ff; [530]: [fee0]ff.byte $ff; [531]: [fee1]ff.byte $ff; [532]: [fee2]ff.byte $ff; [533]: [fee3]ff.byte $ff; [534]: [fee4]ff.byte $ff; [535]: [fee5]ff.byte $ff; [536]: [fee6]ff.byte $ff; [537]: [fee7]ff.byte $ff; [538]: [fee8]ff.byte $ff; [539]: [fee9]ff.byte $ff; [540]: [feea]ff.byte $ff; [541]: [feeb]ff.byte $ff; [542]: [feec]ff.byte $ff; [543]: [feed]ff.byte $ff; [544]: [feee]ff.byte $ff; [545]: [feef]ff.byte $ff; [546]: [fef0]ff.byte $ff; [547]: [fef1]ff.byte $ff; [548]: [fef2]ff.byte $ff; [549]: [fef3]ff.byte $ff; [550]: [fef4]ff.byte $ff; [551]: [fef5]ff.byte $ff; [552]: [fef6]ff.byte $ff; [553]: [fef7]ff.byte $ff; [554]: [fef8]ff.byte $ff; [555]: [fef9]ff.byte $ff; [556]: [fefa]ff.byte $ff; [557]: [fefb]ff.byte $ff; [558]: [fefc]ff.byte $ff; [559]: [fefd]ff.byte $ff; [560]: [fefe]ff.byte $ff; [561]: [feff]ff.byte $ff; [562]: [ff00]ff.byte $ff; [563]: [ff01]ff.byte $ff; [564]: [ff02]ff.byte $ff; [565]: [ff03]ff.byte $ff; [566]: [ff04]ff.byte $ff; [567]: [ff05]ff.byte $ff; [568]: [ff06]ff.byte $ff; [569]: [ff07]ff.byte $ff; [570]: [ff08]ff.byte $ff; [571]: [ff09]ff.byte $ff; [572]: [ff0a]ff.byte $ff; [573]: [ff0b]ff.byte $ff; [574]: [ff0c]ff.byte $ff; [575]: [ff0d]ff.byte $ff; [576]: [ff0e]ff.byte $ff; [577]: [ff0f]ff.byte $ff; [578]: [ff10]ff.byte $ff; [579]: [ff11]ff.byte $ff; [580]: [ff12]ff.byte $ff; [581]: [ff13]ff.byte $ff; [582]: [ff14]ff.byte $ff; [583]: [ff15]ff.byte $ff; [584]: [ff16]ff.byte $ff; [585]: [ff17]ff.byte $ff; [586]: [ff18]ff.byte $ff; [587]: [ff19]ff.byte $ff; [588]: [ff1a]ff.byte $ff; [589]: [ff1b]ff.byte $ff; [590]: [ff1c]ff.byte $ff; [591]: [ff1d]ff.byte $ff; [592]: [ff1e]ff.byte $ff; [593]: [ff1f]ff.byte $ff; [594]: [ff20]ff.byte $ff; [595]: [ff21]ff.byte $ff; [596]: [ff22]ff.byte $ff; [597]: [ff23]ff.byte $ff; [598]: [ff24]ff.byte $ff; [599]: [ff25]ff.byte $ff; [600]: [ff26]ff.byte $ff; [601]: [ff27]ff.byte $ff; [602]: [ff28]ff.byte $ff; [603]: [ff29]ff.byte $ff; [604]: [ff2a]ff.byte $ff; [605]: [ff2b]ff.byte $ff; [606]: [ff2c]ff.byte $ff; [607]: [ff2d]ff.byte $ff; [608]: [ff2e]ff.byte $ff; [609]: [ff2f]ff.byte $ff; [610]: [ff30]ff.byte $ff; [611]: [ff31]ff.byte $ff; [612]: [ff32]ff.byte $ff; [613]: [ff33]ff.byte $ff; [614]: [ff34]ff.byte $ff; [615]: [ff35]ff.byte $ff; [616]: [ff36]ff.byte $ff; [617]: [ff37]ff.byte $ff; [618]: [ff38]ff.byte $ff; [619]: [ff39]ff.byte $ff; [620]: [ff3a]ff.byte $ff; [621]: [ff3b]ff.byte $ff; [622]: [ff3c]ff.byte $ff; [623]: [ff3d]ff.byte $ff; [624]: [ff3e]ff.byte $ff; [625]: [ff3f]ff.byte $ff; [626]: [ff40]ff.byte $ff; [627]: [ff41]ff.byte $ff; [628]: [ff42]ff.byte $ff; [629]: [ff43]ff.byte $ff; [630]: [ff44]ff.byte $ff; [631]: [ff45]ff.byte $ff; [632]: [ff46]ff.byte $ff; [633]: [ff47]ff.byte $ff; [634]: [ff48]ff.byte $ff; [635]: [ff49]ff.byte $ff; [636]: [ff4a]ff.byte $ff; [637]: [ff4b]ff.byte $ff; [638]: [ff4c]ff.byte $ff; [639]: [ff4d]ff.byte $ff; [640]: [ff4e]ff.byte $ff; [641]: [ff4f]ff.byte $ff; [642]: [ff50]ff.byte $ff; [643]: [ff51]ff.byte $ff; [644]: [ff52]ff.byte $ff; [645]: [ff53]ff.byte $ff; [646]: [ff54]ff.byte $ff; [647]: [ff55]ff.byte $ff; [648]: [ff56]ff.byte $ff; [649]: [ff57]ff.byte $ff; [650]: [ff58]ff.byte $ff; [651]: [ff59]ff.byte $ff; [652]: [ff5a]ff.byte $ff; [653]: [ff5b]ff.byte $ff; [654]: [ff5c]ff.byte $ff; [655]: [ff5d]ff.byte $ff; [656]: [ff5e]ff.byte $ff; [657]: [ff5f]ff.byte $ff; [658]: [ff60]ff.byte $ff; [659]: [ff61]ff.byte $ff; [660]: [ff62]ff.byte $ff; [661]: [ff63]ff.byte $ff; [662]: [ff64]ff.byte $ff; [663]: [ff65]ff.byte $ff; [664]: [ff66]ff.byte $ff; [665]: [ff67]ff.byte $ff; [666]: [ff68]ff.byte $ff; [667]: [ff69]ff.byte $ff; [668]: [ff6a]ff.byte $ff; [669]: [ff6b]ff.byte $ff; [670]: [ff6c]ff.byte $ff; [671]: [ff6d]ff.byte $ff; [672]: [ff6e]ff.byte $ff; [673]: [ff6f]ff.byte $ff; [674]: [ff70]ff.byte $ff; [675]: [ff71]ff.byte $ff; [676]: [ff72]ff.byte $ff; [677]: [ff73]ff.byte $ff; [678]: [ff74]ff.byte $ff; [679]: [ff75]ff.byte $ff; [680]: [ff76]ff.byte $ff; [681]: [ff77]ff.byte $ff; [682]: [ff78]ff.byte $ff; [683]: [ff79]ff.byte $ff; [684]: [ff7a]ff.byte $ff; [685]: [ff7b]ff.byte $ff; [686]: [ff7c]ff.byte $ff; [687]: [ff7d]ff.byte $ff; [688]: [ff7e]ff.byte $ff; [689]: [ff7f]ff.byte $ff; [690]: [ff80]ff.byte $ff; [691]: [ff81]ff.byte $ff; [692]: [ff82]ff.byte $ff; [693]: [ff83]ff.byte $ff; [694]: [ff84]ff.byte $ff; [695]: [ff85]ff.byte $ff; [696]: [ff86]ff.byte $ff; [697]: [ff87]ff.byte $ff; [698]: [ff88]ff.byte $ff; [699]: [ff89]ff.byte $ff; [700]: [ff8a]ff.byte $ff; [701]: [ff8b]ff.byte $ff; [702]: [ff8c]ff.byte $ff; [703]: [ff8d]ff.byte $ff; [704]: [ff8e]ff.byte $ff; [705]: [ff8f]ff.byte $ff; [706]: [ff90]ff.byte $ff; [707]: [ff91]ff.byte $ff; [708]: [ff92]ff.byte $ff; [709]: [ff93]ff.byte $ff; [710]: [ff94]ff.byte $ff; [711]: [ff95]ff.byte $ff; [712]: [ff96]ff.byte $ff; [713]: [ff97]ff.byte $ff; [714]: [ff98]ff.byte $ff; [715]: [ff99]ff.byte $ff; [716]: [ff9a]ff.byte $ff; [717]: [ff9b]ff.byte $ff; [718]: [ff9c]ff.byte $ff; [719]: [ff9d]ff.byte $ff; [720]: [ff9e]ff.byte $ff; [721]: [ff9f]ff.byte $ff; [722]: [ffa0]ff.byte $ff; [723]: [ffa1]ff.byte $ff; [724]: [ffa2]ff.byte $ff; [725]: [ffa3]ff.byte $ff; [726]: [ffa4]ff.byte $ff; [727]: [ffa5]ff.byte $ff; [728]: [ffa6]ff.byte $ff; [729]: [ffa7]ff.byte $ff; [730]: [ffa8]ff.byte $ff; [731]: [ffa9]ff.byte $ff; [732]: [ffaa]ff.byte $ff; [733]: [ffab]ff.byte $ff; [734]: [ffac]ff.byte $ff; [735]: [ffad]ff.byte $ff; [736]: [ffae]ff.byte $ff; [737]: [ffaf]ff.byte $ff; [738]: [ffb0]ff.byte $ff; [739]: [ffb1]ff.byte $ff; [740]: [ffb2]ff.byte $ff; [741]: [ffb3]ff.byte $ff; [742]: [ffb4]ff.byte $ff; [743]: [ffb5]ff.byte $ff; [744]: [ffb6]ff.byte $ff; [745]: [ffb7]ff.byte $ff; [746]: [ffb8]ff.byte $ff; [747]: [ffb9]ff.byte $ff; [748]: [ffba]ff.byte $ff; [749]: [ffbb]ff.byte $ff; [750]: [ffbc]ff.byte $ff; [751]: [ffbd]ff.byte $ff; [752]: [ffbe]ff.byte $ff; [753]: [ffbf]ff.byte $ff; [754]: [ffc0]ff.byte $ff; [755]: [ffc1]ff.byte $ff; [756]: [ffc2]ff.byte $ff; [757]: [ffc3]ff.byte $ff; [758]: [ffc4]ff.byte $ff; [759]: [ffc5]ff.byte $ff; [760]: [ffc6]ff.byte $ff; [761]: [ffc7]ff.byte $ff; [762]: [ffc8]ff.byte $ff; [763]: [ffc9]ff.byte $ff; [764]: [ffca]ff.byte $ff; [765]: [ffcb]ff.byte $ff; [766]: [ffcc]ff.byte $ff; [767]: [ffcd]ff.byte $ff; [768]: [ffce]ff.byte $ff; [769]: [ffcf]ff.byte $ff; [770]: [ffd0]ff.byte $ff; [771]: [ffd1]ff.byte $ff; [772]: [ffd2]ff.byte $ff; [773]: [ffd3]ff.byte $ff; [774]: [ffd4]ff.byte $ff; [775]: [ffd5]ff.byte $ff; [776]: [ffd6]ff.byte $ff; [777]: [ffd7]ff.byte $ff; [778]: [ffd8]ff.byte $ff; [779]: [ffd9]ff.byte $ff; [780]: [ffda]ff.byte $ff; [781]: [ffdb]ff.byte $ff; [782]: [ffdc]ff.byte $ff; [783]: [ffdd]ff.byte $ff; [784]: [ffde]ff.byte $ff; [785]: [ffdf]ff.byte $ff; [786]: