aboutsummaryrefslogtreecommitdiff
path: root/src/items.s
diff options
context:
space:
mode:
Diffstat (limited to 'src/items.s')
-rw-r--r--src/items.s583
1 files changed, 583 insertions, 0 deletions
diff --git a/src/items.s b/src/items.s
new file mode 100644
index 0000000..c66e293
--- /dev/null
+++ b/src/items.s
@@ -0,0 +1,583 @@
+.segment "CODE"
+
+;; Assuming that the 'x' register indexes an item on its pool, increment the
+;; register as many times as to point to the next one. Bound checking is not
+;; performed, it's up to the caller to implement that.
+.macro NEXT_ITEM_INDEX_X
+ inx
+ inx
+ inx
+.endmacro
+
+.scope Items
+ ;; Maximum amount of items allowed on screen at the same time.
+ POOL_CAPACITY = 3
+
+ ;; The amount of bytes each pool item takes.
+ SIZEOF_POOL_ITEM = 3
+
+ ;; The capacity of the items pool in bytes.
+ POOL_CAPACITY_BYTES = POOL_CAPACITY * SIZEOF_POOL_ITEM
+
+ ;; 1. State: $FF for invalid, otherwise:
+ ;; |PFD- CKKK|; where:
+ ;; |
+ ;; |- P: following the player
+ ;; |- 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)
+ ;;
+ ;; 2. Y coordinate.
+ ;; 3. X coordinate.
+ zp_pool_base = $C0 ; asan:reserve POOL_CAPACITY_BYTES
+
+ ;; Preserves the index on 'zp_pool_base' in Items::update().
+ zp_pool_index = $C9
+
+ ;; TODO: stabilize and document.
+ ;;
+ ;; Y tile | X tile | palette
+ zp_current_tiles = $E7 ; asan:reserve POOL_CAPACITY_BYTES
+
+ ;;
+ ;; TODO: stabilize and document.
+ ;;
+ ;; |G--- FFAA|
+ ;; |
+ ;; |- G: the player is grabbing an item
+ ;; |- F: number of falling items.
+ ;; |- A: number of active items.
+ zp_state = $CA
+
+ ;; Number of shuttle parts (or fuel tanks) that have been collected so far.
+ zp_collected = $CB
+
+ ;; Coordinate where the dropping of items takes place. This comes in two
+ ;; versions, as the "collision" is done in the tile coordinates so to give
+ ;; some leeway to the player; but the dropping itself has to fall from the
+ ;; exact screen coordinates or otherwise the dropping would feel weird.z
+ DROPPING_SCREEN_X = $A8
+ DROPPING_TILE_X = $15
+
+ ;; Y screen coordinates in order for various parts to be considered as
+ ;; "collected".
+ MID_SHUTTLE_Y = $A7
+ HIGH_SHUTTLE_Y = $97
+ FUEL_SHUTTLE_Y = $C7
+
+ .proc init
+ lda Globals::zp_level_kind
+ bne @other_screens
+ JAL Items::init_first_screen
+
+ @other_screens:
+ ;; TODO
+
+ rts
+ .endproc
+
+ ;; TODO: this is only to be done for the first time we enter. Otherwise this
+ ;; will be reset every time.
+ .proc init_first_screen
+ ;; We are going to allocate two shuttle parts, and hence two items.
+ lda #2
+ sta Items::zp_state
+
+ ;; We haven't collected anything yet, but it's convenient for us to mock
+ ;; that the ship part on the right side of the screen is actually
+ ;; collected.
+ lda #1
+ sta Items::zp_collected
+
+ ;; State of the top part of the shuttle.
+ ldx #0
+ ldy #0
+ sty Items::zp_pool_base, x
+
+ ;; Screen and tile coordinates for the top part of the shuttle.
+ lda #$4F
+ sta Items::zp_pool_base + 1, x
+ lsr
+ lsr
+ lsr
+ sta Items::zp_current_tiles, x
+ lda #$29
+ sta Items::zp_pool_base + 2, x
+ lsr
+ lsr
+ lsr
+ sta Items::zp_current_tiles + 1, x
+
+ ;; State of the middle part of the shuttle.
+ iny
+ sty Items::zp_pool_base + 3, x
+
+ ;; Screen and tile coordinates for the middle part of the shuttle.
+ lda #$67
+ sta Items::zp_pool_base + 4, x
+ lsr
+ lsr
+ lsr
+ sta Items::zp_current_tiles + 3, x
+ lda #$81
+ sta Items::zp_pool_base + 5, x
+ lsr
+ lsr
+ lsr
+ sta Items::zp_current_tiles + 4, x
+
+ ;; Invalidte the third item.
+ ldy #$FF
+ sty Items::zp_pool_base + 6, x
+
+ ;; Palettes.
+ lda #0
+ sta Items::zp_current_tiles + 2, x
+ sta Items::zp_current_tiles + 6, x
+
+ rts
+ .endproc
+
+ ;; Allocate an item indexed by 'x' from the `zp_pool_base` buffer, and set
+ ;; it to OAM-reserved space indexed via 'y'.
+ ;;
+ ;; The 'y' register will be updated by increasing its value by 16,
+ ;; indicating the amount of bytes allocated in OAM space.
+ ;;
+ ;; The 'x' register will be _preserved_.
+ ;;
+ ;; NOTE: this function assumes that the item is in a valid state. That's up
+ ;; to the caller to check before calling this function.
+ .proc allocate_x_y
+ lda Items::zp_pool_base, x
+ and #$07
+
+ ;; Should we allocate a part from the shuttle?
+ bne @try_next_shuttle
+ lda #$04
+ JAL allocate_shuttle_x_y
+ @try_next_shuttle:
+ cmp #$01
+ bne @try_fuel
+ lda #$06
+ JAL allocate_shuttle_x_y
+
+ @try_fuel:
+ ;; TODO: validate whether we need to save/restore the 'x' register.
+ stx Globals::zp_tmp3
+ ;; TODO
+ ldx Globals::zp_tmp3
+ rts
+ .endproc
+
+ ;; Allocate a shuttle part on the same terms as
+ ;; Items::allocate_shuttle_x_y().
+ .proc allocate_shuttle_x_y
+ sta Globals::zp_tmp0
+
+ ;; Y coordinates
+ lda Items::zp_pool_base + 1, x
+ sta OAM::m_sprites, y ; top left
+ sta OAM::m_sprites + 4, y ; top right
+ clc
+ adc #8
+ sta OAM::m_sprites + 8, y ; bottom left
+ sta OAM::m_sprites + 12, y ; bottom right
+
+ ;; Tile IDs
+ lda Globals::zp_tmp0
+ sta OAM::m_sprites + 1, y ; top left
+ clc
+ adc #1
+ sta OAM::m_sprites + 5, y ; top right
+
+ lda Globals::zp_tmp0
+ clc
+ adc #$10
+ sta OAM::m_sprites + 9, y ; bottom left
+ clc
+ adc #1
+ sta OAM::m_sprites + 13, y ; bottom right
+
+ ;; Attributes
+ lda #0
+ sta OAM::m_sprites + 2, y ; top left
+ sta OAM::m_sprites + 6, y ; top right
+ sta OAM::m_sprites + 10, y ; bottom left
+ sta OAM::m_sprites + 14, y ; bottom right
+
+ ;; X coordinates.
+ lda Items::zp_pool_base + 2, x ; top left
+ sta OAM::m_sprites + 3, y
+ sta OAM::m_sprites + 11, y ; bottom left
+ clc
+ adc #8
+ sta OAM::m_sprites + 7, y ; top right
+ sta OAM::m_sprites + 15, y ; bottom right
+
+ ;; And update the 'y' register.
+ tya
+ clc
+ adc #16
+ tay
+
+ rts
+ .endproc
+
+ .proc update
+ ldx #0
+
+ ldy #POOL_CAPACITY
+ sty Globals::zp_idx
+
+ ;; The player's coordinates are cached into arguments in memory so they
+ ;; can be used for collision checking. Note that we are targetting for
+ ;; the center of the player, which feels at a fair point for item
+ ;; interactions.
+ lda Player::zp_screen_y
+ clc
+ adc #Player::PLAYER_WAIST
+ lsr
+ lsr
+ lsr
+ sta Globals::zp_arg0
+ lda Player::zp_screen_x
+ lsr
+ lsr
+ lsr
+ clc
+ adc #1
+ sta Globals::zp_arg1
+
+ @loop:
+ ;; TODO: check how relevant this really is.
+ stx Items::zp_pool_index
+
+ ;; Is it valid?
+ lda Items::zp_pool_base, x
+ cmp #$FF
+ bne @check_status
+ jmp @next
+
+ @check_status:
+ ;; If it's resting, then just check for collision. Otherwise, we either
+ ;; fall/drop or follow the player.
+ and #$C0
+ beq @check_collision
+ cmp #$40
+ beq @do_fall
+
+ ;;;
+ ;; Follow the player.
+
+ ;; Neither of the above. Then, just follow the player.
+ lda Player::zp_screen_y
+ clc
+ adc #8
+ sta Items::zp_pool_base + 1, x
+ lda Player::zp_screen_x
+ sta Items::zp_pool_base + 2, x
+
+ ;; Are we at the zone where we must drop items?
+ ldy Globals::zp_arg1
+ dey
+ cpy #DROPPING_TILE_X
+ beq @drop
+ jmp @next
+
+ @drop:
+ ;; Yeah! Then the item stops being in 'following player' mode and is
+ ;; dropped (F & D set).
+ lda Items::zp_pool_base, x
+ and #$7F
+ ora #%01100000
+ sta Items::zp_pool_base, x
+
+ ;; Unset the 'grabbing' bit and increase the number of falling items.
+ lda Items::zp_state
+ and #$7F
+ clc
+ adc #$04
+ sta Items::zp_state
+
+ ;; And we force the item to be on the exact X screen position so to
+ ;; adjust from the player's subpixel movement.
+ lda #DROPPING_SCREEN_X
+ sta Items::zp_pool_base + 2, x
+
+ jmp @next
+
+ ;;;
+ ;; Fall/drop.
+
+ @do_fall:
+ ;; Update the Y coordinate so the item is falling.
+ inc Items::zp_pool_base + 1, x
+
+ ;; Is the item being dropped? If not, then we just check for collision.
+ lda Items::zp_pool_base, x
+ and #$20
+ beq @check_collision
+
+ ;; This is a fuel tank or a shuttle part that is aligned with the
+ ;; shuttle platform. We will load in 'a' the exact screen coordinates
+ ;; where each part should stop.
+ lda Items::zp_pool_base, x
+ and #$07
+ beq @high_shuttle
+ cmp #1
+ beq @mid_shuttle
+ lda #FUEL_SHUTTLE_Y
+ bne @drop_check
+ @mid_shuttle:
+ lda #MID_SHUTTLE_Y
+ bne @drop_check
+ @high_shuttle:
+ lda #HIGH_SHUTTLE_Y
+
+ @drop_check:
+ ;; Does this item reach its dropping limit? If not just go to the next
+ ;; item.
+ ;; TODO: It should also work for "greater than".
+ cmp Items::zp_pool_base + 1, x
+ bne @next
+
+ ;; Enable the 'ppu' and the 'shuttle' flags.
+ lda Globals::zp_flags
+ ora #%01100000
+ sta Globals::zp_flags
+
+ ;; Decrease the number of falling/active items.
+ lda Items::zp_state
+ sec
+ sbc #$05 ; NOTE: $04 (falling) + $01 (active)
+ sta Items::zp_state
+
+ ;; Increase the number of collected items.
+ inc Items::zp_collected
+
+ ;; And invalidate this item.
+ lda #$FF
+ sta Items::zp_pool_base, x
+
+ ;; All collision checks that were needed for 'collision' mode have been
+ ;; done. We can just move to the next item.
+ jmp @next
+
+ ;;;
+ ;; Collision checks.
+
+ @check_collision:
+ ;; Collision with the player.
+ jsr Items::collides_with_player
+ beq @next
+ ;; TODO: background collision (when the item is not grabbed): if it
+ ;; happens, then the P, F, D are set to 0. The number of falling items
+ ;; is also decreased.
+
+ ;; A collision happened! Get collected or follow the player (if possible).
+ lda Items::zp_pool_base, x
+ tay
+ and #$08
+ beq @try_to_follow_player
+ jsr Items::collect
+ jmp @next
+
+ @try_to_follow_player:
+ ;; If the player is already grabbing another item, don't even try it.
+ bit Items::zp_state
+ bmi @next
+
+ ;; We don't need extra precautions except when the level kind is the
+ ;; first one. In that case we must guarantee the right shuttle order.
+ lda Globals::zp_level_kind
+ bne @do_follow_player
+
+ ;; Is this the first shuttle part to be collected?
+ lda Items::zp_collected
+ cmp #1
+ bne @do_follow_player
+
+ ;; Yes! Then it _must_ be the middle part.
+ lda Items::zp_pool_base, x
+ and #$07
+ cmp #$01
+ bne @next
+
+ @do_follow_player:
+ ;; TODO: If F was set, unset it and subtract the number of falling items.
+
+ ;; Mark this item to be in 'following' mode.
+ tya
+ ora #$80
+ sta Items::zp_pool_base, x
+
+ ;; Mark the player's to be already grabbing an item.
+ lda Items::zp_state
+ ora #$80
+ sta Items::zp_state
+
+ @next:
+ NEXT_ITEM_INDEX_X
+ dec Globals::zp_idx
+ beq @end
+ jmp @loop
+
+ @end:
+ rts
+ .endproc
+
+ ;; TODO: this assumes a 4-sprite item
+ .proc collides_with_player
+ ldx Items::zp_pool_index
+ lda Items::zp_current_tiles, x
+ cmp #$FF
+ beq @no
+
+ ;; Check for the Y tile coordinate. If it's not the same on either the
+ ;; upper or the bottom parts of the item, then it's a no.
+ cmp Globals::zp_arg0
+ beq @check_x
+ clc
+ adc #1
+ cmp Globals::zp_arg0
+ bne @no
+
+ @check_x:
+ ;; If the Y tile coordinate checks out, let's narrow it down to the X
+ ;; coordinate.
+ lda Items::zp_current_tiles + 1, x
+ cmp Globals::zp_arg1
+ beq @yes
+ clc
+ adc #1
+ cmp Globals::zp_arg1
+ bne @no
+
+ @yes:
+ lda #1
+ rts
+ @no:
+ lda #0
+ rts
+ .endproc
+
+ ;; TODO: guarantee 'x' and 'y' safety
+ .proc collect
+ ;; TODO
+ rts
+ .endproc
+
+ ;; Prepare the background scenary for items. Namely, the rocket parts which
+ ;; belong to the background.
+ ;;
+ ;; NOTE: this has to be called with the PPU disabled.
+ .proc prepare_scene
+ ;; The low part of the rocket.
+ bit PPU::m_status
+ lda #$2A
+ sta PPU::m_address
+ lda #$F5
+ sta PPU::m_address
+ ldx #$0C
+ stx PPU::m_data
+ inx
+ stx PPU::m_data
+
+ lda #$2B
+ sta PPU::m_address
+ lda #$15
+ sta PPU::m_address
+ inx
+ stx PPU::m_data
+ inx
+ stx PPU::m_data
+
+ lda Globals::zp_level_kind
+ beq @end
+
+ @rest_of_the_rocket:
+ jsr draw_high_part_shuttle
+ jsr draw_middle_part_shuttle
+
+ @end:
+ rts
+ .endproc
+
+ ;; Update the background scenary for the shuttle.
+ ;;
+ ;; NOTE: this has to be called with the PPU disabled.
+ .proc update_shuttle
+ lda Globals::zp_level_kind
+ bne @fuel
+
+ ;; Update the shuttle.
+ lda Items::zp_collected
+ cmp #3
+ bne @mid_shuttle
+ jsr draw_high_part_shuttle
+ @mid_shuttle:
+ jsr draw_middle_part_shuttle
+ rts
+
+ @fuel:
+ ;; TODO
+
+ rts
+ .endproc
+
+ ;; Update the background scenary to show the middle part of the shuttle.
+ ;;
+ ;; NOTE: this has to be called with the PPU disabled.
+ .proc draw_middle_part_shuttle
+ ldx #$08
+ ldy #$2A
+
+ bit PPU::m_status
+ sty PPU::m_address
+ lda #$B5
+ sta PPU::m_address
+ stx PPU::m_data
+ inx
+ stx PPU::m_data
+
+ bit PPU::m_status
+ sty PPU::m_address
+ lda #$D5
+ sta PPU::m_address
+ inx
+ stx PPU::m_data
+ inx
+ stx PPU::m_data
+
+ rts
+ .endproc
+
+ ;; Update the background scenary to show the high part of the shuttle.
+ ;;
+ ;; NOTE: this has to be called with the PPU disabled.
+ .proc draw_high_part_shuttle
+ ldx #$04
+ ldy #$2A
+
+ bit PPU::m_status
+ sty PPU::m_address
+ lda #$75
+ sta PPU::m_address
+ stx PPU::m_data
+ inx
+ stx PPU::m_data
+
+ bit PPU::m_status
+ sty PPU::m_address
+ lda #$95
+ sta PPU::m_address
+ inx
+ stx PPU::m_data
+ inx
+ stx PPU::m_data
+
+ rts
+ .endproc
+.endscope