diff options
| author | Miquel Sabaté Solà <mssola@mssola.com> | 2026-03-20 09:54:25 +0100 |
|---|---|---|
| committer | Miquel Sabaté Solà <mssola@mssola.com> | 2026-03-20 12:48:17 +0100 |
| commit | 8f7b8ce3335404badc276445d899372839cdde71 (patch) | |
| tree | c5422f80bc9e13f927965bfa655dd29d6bec5942 | |
| parent | 01f92b866eb4610b8d12008ea3389ac56052d20a (diff) | |
| download | jetpac.nes-8f7b8ce3335404badc276445d899372839cdde71.tar.gz jetpac.nes-8f7b8ce3335404badc276445d899372839cdde71.zip | |
Add support for the SUSE coin
This is a coin that appears after going through a first cycle of levels
and that allows the player to get a different game over screen than
usual.
Signed-off-by: Miquel Sabaté Solà <mssola@mssola.com>
| -rw-r--r-- | .nasm/segments.txt | 2 | ||||
| -rw-r--r-- | CHANGELOG.md | 2 | ||||
| -rw-r--r-- | include/asm.s | 5 | ||||
| -rw-r--r-- | src/assets.s | 2 | ||||
| -rw-r--r-- | src/driver.s | 8 | ||||
| -rw-r--r-- | src/items.s | 110 | ||||
| -rw-r--r-- | src/jetpac.s | 1 | ||||
| -rw-r--r-- | src/over.s | 58 |
8 files changed, 152 insertions, 36 deletions
diff --git a/.nasm/segments.txt b/.nasm/segments.txt index 403b691..ffbe519 100644 --- a/.nasm/segments.txt +++ b/.nasm/segments.txt @@ -1,4 +1,4 @@ - HEADER: 16/16 (100%) -- ROM0: 8066/32762 (24.62%) +- ROM0: 8238/32762 (25.14%) - ROMV: 6/6 (100%) - ROM2: 8192/8192 (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0fbde8..11057c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,7 +24,7 @@ adapted to the reality of the NES/Famicom. This means: hard to do and probably not worth it. In the end: different machine, different rules. Hence, bullets are handled in a similar way as other games for the NES/Famicom, even if it's not particularly close to the original. -- As an homage to Donkey Kong 64, you can **collect a coin** after completing 16 +- As an homage to Donkey Kong 64, you can **collect a coin** after completing 8 stages. This coin features a chameleon as a reference to SUSE, since I originally bootstrapped this project during [Hackweek 23](https://hackweek.opensuse.org/projects/port-the-jetpac-game-to-the-nes). diff --git a/include/asm.s b/include/asm.s index e44aedf..09229f9 100644 --- a/include/asm.s +++ b/include/asm.s @@ -1,8 +1,3 @@ -;; Sanity check for the 'LEVEL' build parameter. -.if !(LEVEL >= 0 && LEVEL < 8) - .error "You have defined a bad 'LEVEL' value, it should be between 0 and 7 (both included)" -.endif - ;; Jump And Link: jump to subroutine but use the return address that the caller ;; had whenever the given subroutine runs `rts`. In other words, "link" the ;; return address from the caller to the callee. diff --git a/src/assets.s b/src/assets.s index cfcddbf..26f6546 100644 --- a/src/assets.s +++ b/src/assets.s @@ -180,7 +180,7 @@ ;; 2: enemy 2, fuel & bonuses .byte $0F, $16, $24, $28 ;; 3: SUSE easter egg - .byte $0F, $16, $10, $2B + .byte $0F, $16, $1B, $2B .endproc ;; Having 2KB for screen data is quite wasteful, but since it's such a diff --git a/src/driver.s b/src/driver.s index e1c2eb7..bfa6294 100644 --- a/src/driver.s +++ b/src/driver.s @@ -323,12 +323,16 @@ and #%00000110 bne @reset_timer - ;; No! Toggle the game over bit. - ;; TODO: missing the coin game over. + ;; No! Set the game over bit (with or without coin). lda Globals::zp_flags ora #%00000010 sta Globals::zp_flags + lda Items::zp_state + and #$04 + beq @invalidate_items + inc Globals::zp_flags + @invalidate_items: ;; Invalidate items, which were skipped on move_sprites_out() on purpose ;; to keep them after each death. But since we are about to go to the ;; title screen, now they are no longer useful. diff --git a/src/items.s b/src/items.s index 731756d..de0a28e 100644 --- a/src/items.s +++ b/src/items.s @@ -44,7 +44,7 @@ ;; |- F: falling. ;; |- D: dropping: together with 'falling', but the player cannot re-grab it. ;; |- C: 1: collectable (i.e. disappears on collision); 0: part (i.e. follows the player) - ;; |- K: object kind (00: high shuttle; 01: mid shuttle; 10: fuel; 11: regular item; 100: coin) + ;; |- K: object kind (000: high shuttle; 001: mid shuttle; 010: fuel; 011: regular item; 100: coin) ;; ;; 2. Y coordinate. ;; 3. X coordinate. @@ -72,11 +72,12 @@ ;; Bitmap which holds different boolean values for the state of items in ;; general. ;; - ;; |GNS- --FF| + ;; |GNS- -CFF| ;; | ;; |- G: the player is Grabbing an item ;; |- N: a fuel tank is Needed. ;; |- S: there is a fuel tank on Screen. + ;; |- C: SUSE's Coin has been collected. ;; |- F: number of Falling items. zp_state = $CA @@ -128,7 +129,9 @@ lda Globals::zp_level_kind bne @other_screens - lda #0 + ;; Zero out the state except for the 'C' bit. + lda Items::zp_state + and #$04 sta Items::zp_state ;; We haven't collected anything yet, but it's convenient for us to mock @@ -179,13 +182,16 @@ ;; Palettes. lda #0 - sta Items::zp_current_tiles + 6, x + sta Items::zp_current_tiles + 5, x - beq @invalidate_third + beq @coin_or_invalidate_third @other_screens: - ;; Fuel tanks are needed, that's all. - lda #%01000000 + ;; Zero out the state except for the 'C' bit. The 'N' bit needs to be + ;; set always. + lda Items::zp_state + and #$04 + ora #$40 sta Items::zp_state ;; Shuttle parts are counted as "collected". This makes the @@ -197,6 +203,46 @@ lda #$FF sta Items::zp_pool_base, x sta Items::zp_pool_base + 3, x + bne @invalidate_third + + @coin_or_invalidate_third: + ;; If we have finished the game once, and we have not collected the + ;; SUSE's coin yet, let it be. + lda Globals::zp_level + cmp #8 + bne @invalidate_third + lda Items::zp_state + and #$04 + bne @invalidate_third + + ;; SUSE's coin will fall from the sky. Hence, compute this new item as a + ;; falling one. + inc Items::zp_state + + ;; SUSE's coin can be Collected, is identified by the '100' kind, and is + ;; in a Falling state. + lda #%01001100 + sta Items::zp_pool_base + 6, x + + ;; Falling from the sky at the right platform. + lda #Background::UPPER_MARGIN_Y_COORD + sta Items::zp_pool_base + 7, x + lsr + lsr + lsr + sta Items::zp_current_tiles + 6, x + lda #$D0 + sta Items::zp_pool_base + 8, x + lsr + lsr + lsr + sta Items::zp_current_tiles + 7, x + + ;; Default palette, the tile ID is simply ignored. + lda #0 + sta Items::zp_current_tiles + 8, x + + rts @invalidate_third: ;; Always invalidate the third item. @@ -248,26 +294,23 @@ cmp #$01 bne @do_fuel_or_regular lda #$06 - bne @no_attributes + beq @do_fuel_or_regular + + @no_attributes: + sta Globals::zp_arg0 + lda #0 + sta Globals::zp_arg1 + JAL allocate_metasprite_x_y @do_fuel_or_regular: - ;; Is it a fuel tank? + ;; Switch statement for the kind of item (regular, fuel, coin). lda Items::zp_pool_base, x - and #$03 + and #$07 cmp #2 - bne @regular + beq @fuel cmp #4 beq @coin - ;; Then just pick the tile from the fuel tank and pick the right - ;; palette. - lda #$0C - sta Globals::zp_arg0 - lda #2 - sta Globals::zp_arg1 - JAL allocate_metasprite_x_y - - @regular: ;; This is a regular item lda Items::zp_current_tiles + 2, x lsr @@ -284,12 +327,19 @@ sta Globals::zp_arg0 JAL allocate_metasprite_x_y + @fuel: + ;; Then just pick the tile from the fuel tank and pick the right + ;; palette. + lda #$0C + sta Globals::zp_arg0 + lda #2 + sta Globals::zp_arg1 + JAL allocate_metasprite_x_y + @coin: lda #$0A - - @no_attributes: sta Globals::zp_arg0 - lda #0 + lda #3 sta Globals::zp_arg1 JAL allocate_metasprite_x_y @@ -709,8 +759,6 @@ ;; We start by generating a new state. If the state is asking for a fuel ;; tank, let it be. Otherwise it will be a regular item. - ;; - ;; TODO: coin support. lda Items::zp_state and #$40 beq @regular @@ -862,6 +910,18 @@ .proc collect ldx Items::zp_pool_index + ;; Are we collecting the one and only SUSE coin?! + lda Items::zp_pool_base, x + and #$07 + cmp #4 + bne @check_fall + + ;; Hell, yeah! + lda Items::zp_state + ora #$04 + sta Items::zp_state + + @check_fall: ;; If the collected item was actually falling down, decrease the number ;; of falling items. lda Items::zp_pool_base, x diff --git a/src/jetpac.s b/src/jetpac.s index 2d397ac..f6a53a9 100644 --- a/src/jetpac.s +++ b/src/jetpac.s @@ -129,6 +129,7 @@ sta Joypad::zp_buttons sta Joypad::zp_prev sta Player::zp_state + sta Items::zp_state ;; Initialize the level. We allow the build system to pass its own value for ;; this in `LEVEL`, just in case we want to debug the enemy of a specific @@ -67,9 +67,16 @@ @do_render: jsr Over::clear_out_screen - ;; TODO: coin game over. + lda Globals::zp_flags + and #$03 + cmp #2 + beq @regular + jsr Over::render_coin_game_over + jmp @next + @regular: jsr Over::render_regular_game_over + @next: ;; Enable back the PPU (only background). lda #%00001110 sta PPU::zp_mask @@ -234,4 +241,53 @@ ;; "OVER" .byte $29, $30, $1F, $2C, $FF .endproc + + ;; Render the "Game over" in the case the player has collected SUSE's coin. + ;; TODO: see how much it can be merged + ;; TODO: not centered nor fully realized. + .proc render_coin_game_over + ;; Set the position. + bit PPU::m_status + ldx #$29 + stx PPU::m_address + ldx #$6C + stx PPU::m_address + + ;; And just iterate over the "message" until we reach the end of string + ;; $FF character. + ldx #0 + @message_loop: + lda message, x + cmp #$FF + beq @out + sta PPU::m_data + inx + bne @message_loop + + @out: + ;; Reset attributes for the end of the message. + bit PPU::m_status + ldx #$2B + stx PPU::m_address + ldx #$D5 + stx PPU::m_address + lda #0 + sta PPU::m_data + sta PPU::m_data + sta PPU::m_data + + rts + + message: + ;; "YOU " + .byte $33, $29, $2F, $00 + ;; "ARE " + .byte $1B, $2C, $1F, $00 + ;; "A " + .byte $1B, $00 + ;; "SUPER " + .byte $2D, $2F, $2A, $1F, $2C, $00 + ;; "PLAYER!" TODO + .byte $2A, $26, $1B, $33, $1F, $2C, $FF + .endproc .endscope |
