aboutsummaryrefslogtreecommitdiff
;;;
;; The same as in `level.s` but at the bottom of the screen we have a "This is a
;; message" being shown. This message is part of the background but it does not
;; scroll like the rest of the screen, but it stays at the same coordinates all
;; the time. This is done via the MMC3 chip, and the same technique is further
;; developed in `roulette.s`. In short, this mapper chip implements a bunch of
;; features, and one of them is the ability to instruct the chip to send an IRQ
;; on a given scanline. This is then done to mess with the scroll value and
;; obtain different effects.
;;
;; This example shows the most basic usage of it, and it's what games like Super
;; Mario Bros. 3 and Kirby's Adventure did: implement a status bar at the bottom
;; of the screen in a way that is reliable and less CPU consuming than sprite 0
;; hit detection as it's done in `sprite0.s`. That is, instead of wasting CPU
;; cycles waiting for a sprite 0 hit, during VBlank we configure the chip to
;; send us an IRQ for a given scanline. Once the PPU arrives at this scanline,
;; then it sends us an IRQ in which we reset the scroll value. This scroll value
;; will then be configured again during VBlank.
;;
;; The code is a mix between `level.s` and `fx/blink.s`, so consider these two
;; examples as previous work and get to know them before jumping into this one.
;; In here I have just written comments which are specific to this example.

;; Just like `fx/blink.s`.
.segment "HEADER"
    .byte 'N', 'E', 'S', $1A
    .byte $10, $10
    .byte $42, $08
    .res 8, 0

.segment "VECTORS"
    .addr nmi, reset, irq

;; Just like `fx/blink.s`: empty on purpose as this is a simple example.

.segment "PRG0_00"
.byte $FF
.segment "PRG0_01"
.byte $FF
.segment "PRG0_02"
.byte $FF
.segment "PRG0_03"
.byte $FF
.segment "PRG0_04"
.byte $FF
.segment "PRG0_05"
.byte $FF
.segment "PRG0_06"
.byte $FF
.segment "PRG0_07"
.byte $FF
.segment "PRG0_08"
.byte $FF
.segment "PRG0_09"
.byte $FF
.segment "PRG0_0A"
.byte $FF
.segment "PRG0_0B"
.byte $FF
.segment "PRG0_0C"
.byte $FF
.segment "PRG0_0D"
.byte $FF
.segment "PRG0_0E"
.byte $FF
.segment "PRG1_00"
.byte $FF
.segment "PRG1_01"
.byte $FF
.segment "PRG1_02"
.byte $FF
.segment "PRG1_03"
.byte $FF
.segment "PRG1_04"
.byte $FF
.segment "PRG1_05"
.byte $FF
.segment "PRG1_06"
.byte $FF
.segment "PRG1_07"
.byte $FF
.segment "PRG1_08"
.byte $FF
.segment "PRG1_09"
.byte $FF
.segment "PRG1_0A"
.byte $FF
.segment "PRG1_0B"
.byte $FF
.segment "PRG1_0C"
.byte $FF
.segment "PRG1_0D"
.byte $FF
.segment "PRG1_0E"
.byte $FF
.segment "FIXED"
.byte $FF

;; Everything happens on this segment. Check the `config/mmc3.cfg` for more
;; information on where it is placed in the end.
.segment "TAIL"

;; Just like with `sprite0.s`, the engine in `include/` can be configured to a
;; degree. In this case we instruct it to never go over the `$0D` row as this
;; will be the one being used for showing the status bar.
BACKGROUND_ROW_MAX = $0D
.include "include/all.s"
.include "../shared/mmc3.s"

;; Clear out the a rows of tiles from the high byte for the PPU address as given
;; in the `x` register, and the low byte as given on the `y` register.
.proc clear_row_x_y
    bit PPU::STATUS

    stx PPU::ADDRESS
    sty PPU::ADDRESS

    lda #$00
    ldx #$20
@loop:
    sta PPU::DATA
    dex
    bne @loop

    rts
.endproc

;; Similar to `show_hud` in `sprite0.s`, we want to allocate this text in the
;; background where the engine will not touch it.
.proc show_status
    ;; Clear out the space in which we want to allocate or status bar.
    ldx #$23
    ldy #$40
    jsr clear_row_x_y
    ldx #$23
    ldy #$60
    jsr clear_row_x_y
    ldx #$23
    ldy #$80
    jsr clear_row_x_y
    ldx #$27
    ldy #$40
    jsr clear_row_x_y
    ldx #$27
    ldy #$60
    jsr clear_row_x_y
    ldx #$27
    ldy #$80
    jsr clear_row_x_y

    ;; Just like with `sprite0.s`, the message has to be repeated over the
    ;; nametable on $2400 as the engine will flip the base nametable address
    ;; whenever the scroll wraps around.

    ;; This
    WRITE_PPU_DATA $2368, $23
    WRITE_PPU_DATA $2369, $17
    WRITE_PPU_DATA $236A, $18
    WRITE_PPU_DATA $236B, $22
    WRITE_PPU_DATA $2768, $23
    WRITE_PPU_DATA $2769, $17
    WRITE_PPU_DATA $276A, $18
    WRITE_PPU_DATA $276B, $22

    ;; is
    WRITE_PPU_DATA $236D, $18
    WRITE_PPU_DATA $236E, $22
    WRITE_PPU_DATA $276D, $18
    WRITE_PPU_DATA $276E, $22

    ;; a
    WRITE_PPU_DATA $2370, $10
    WRITE_PPU_DATA $2770, $10

    ;; message
    WRITE_PPU_DATA $2372, $1C
    WRITE_PPU_DATA $2373, $14
    WRITE_PPU_DATA $2374, $22
    WRITE_PPU_DATA $2375, $22
    WRITE_PPU_DATA $2376, $10
    WRITE_PPU_DATA $2377, $16
    WRITE_PPU_DATA $2378, $14
    WRITE_PPU_DATA $2772, $1C
    WRITE_PPU_DATA $2773, $14
    WRITE_PPU_DATA $2774, $22
    WRITE_PPU_DATA $2775, $22
    WRITE_PPU_DATA $2776, $10
    WRITE_PPU_DATA $2777, $16
    WRITE_PPU_DATA $2778, $14

    ;; NOTE: in stark contrast with `sprite0.s`, there's no need to waste a
    ;; sprite for this purpose.

    rts
.endproc

.proc main
    ;; Setup the MMC3 chip. Note that this is better suited in the `reset`
    ;; function, but the engine already provides one and I didn't want to start
    ;; messing with `.ifdef` and the likes.
    ;;
    ;; NOTE: this is a copy-paste from `fx/blink.s`, so refer to that example on
    ;; what any of the code below means.

    lda #$00
    sta MMC3::MIRRORING
    sta MMC3::IRQ_DISABLE

    lda #$80
    sta MMC3::RAM_PROTECT

    BANK_REGISTER_SET 0, 0
    BANK_REGISTER_SET 1, 2
    BANK_REGISTER_SET 2, 4
    BANK_REGISTER_SET 3, 5
    BANK_REGISTER_SET 4, 6
    BANK_REGISTER_SET 5, 7
    BANK_REGISTER_SET 6, 0
    BANK_REGISTER_SET 7, 1

    ;; MMC3 configured, now go on as usual.

    lda #$00
    sta PPU::zp_mask
    sta PPU::MASK

    jsr Palettes::init
    jsr Metatile::init
    jsr Driver::init
    jsr Background::init
    jsr Player::init

    ;; Show the status bar down below.
    jsr show_status

    cli

    lda #%10001000
    sta PPU::zp_control
    sta PPU::CONTROL

@main_game_loop:
    READ_JOYPAD1
    jsr Player::update
    jsr Driver::update

    lda #%10000000
    ora Globals::zp_flags
    sta Globals::zp_flags

@wait_for_render:
    bit Globals::zp_flags
    bmi @wait_for_render

    jmp @main_game_loop
.endproc

;; The basics are laid out in `basics/sprite.s`. Read the comments below for
;; more info.
.proc nmi
    bit Globals::zp_flags
    bpl @next

    pha
    txa
    pha
    tya
    pha

    ;; Just like in `fx/blink.s`, acknowledge any previous IRQ as a safety
    ;; measure. But anyways set the next IRQ to happen on scanline 210.

    ldx #$00
    stx MMC3::IRQ_DISABLE

    lda #210
    sta MMC3::IRQ_LATCH
    sta MMC3::IRQ_RELOAD
    sta MMC3::IRQ_ENABLE

    ;; From here on as in `level.s`.

    jsr Player::update_sprite

    OAM_WRITE_SPRITES

    FLUSH_PENDING_VRAM_BUFFER

    bit Globals::zp_flags
    bvc @after_ppu

    lda #%10111111
    and Globals::zp_flags
    sta Globals::zp_flags

    bit PPU::STATUS

    lda PPU::zp_control
    sta PPU::CONTROL
    lda PPU::zp_mask
    sta PPU::MASK

@after_ppu:
    ;; NOTE: scroll is updated always. This is in contrast with `level.s`, but
    ;; if we don't do that the scroll would be lost if the player stops moving
    ;; the scroll position.
    lda Background::zp_scroll
    sta PPU::SCROLL
    lda #$00
    sta PPU::SCROLL

    ;; And as usual again.

    lda #%01111111
    and Globals::zp_flags
    sta Globals::zp_flags

    pla
    tay
    pla
    tax
    pla
@next:
    rti
.endproc

;; This is a much simpler version of IRQ handling as you can see in examples
;; such as `roulette.s`.
.proc irq
    ;; Save current context.
    pha
    txa
    pha
    tya
    pha

    ;; Disable IRQs and acknowledge the current one.
    ldx #$00
    stx MMC3::IRQ_DISABLE

    ;; Reset the scroll so the status bar is kept in place. The scroll will be
    ;; kept like this until VBlank happens, when the `nmi` function will set the
    ;; scroll value to what's perceived by the player.
    bit PPU::STATUS
    lda #$00
    sta $2005
    sta $2005

    ;; Restore previous context.
    pla
    tay
    pla
    tax
    pla

    rti
.endproc

.segment "CHARS"
;; Similar to `fx/blink.s` but here we really needed something for the
;; background :)
.incbin "../assets/diskun-background.chr"
.incbin "../assets/diskun0.chr"
.incbin "../assets/diskun1.chr"

;; The 15 other 8KB portions are left empty.
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00
.res $2000, $00