aboutsummaryrefslogtreecommitdiff
path: root/src/player.s
blob: cee79cf3672de65abe5ebf104c1a9888fc197a89 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
.segment "CODE"

;; Functions and variables that keep up with the player's sprite. That is,
;; movement, heading, animation, collision with the environment, etc.
.scope Player
    ;;;
    ;; *Movement* is done with subpixel precision. Hence, we need to handle
    ;; things via fixed-point arithemetic. In particular, the `zp_screen_*`
    ;; variables refer to pure screen coordinates, and they are the only uint8_t
    ;; variables. As for the rest:
    ;;
    ;;  - Velocity (actual and target) are 4.4 fixed-point signed values. Hence,
    ;;    the high nibble represents the signed integer part, and the low nibble
    ;;    the fractional one.
    ;;  - Each Position is a 16-bit signed fixed-point value in little-endian
    ;;    format like so: |llll/ffff| - |0000/hhhh|. That is, the low byte is
    ;;    split with the fractional part and the low nibble of the integer part,
    ;;    and the high byte only contains the high nibble of the signed integer
    ;;    part (and the high nibble of that high byte is discarded).
    ;;
    ;; This has the following properties:
    ;;
    ;;  - Adding the velocity to a position is just a matter of performing an
    ;;    `adc` between the low byte of the position and the velocity.
    ;;  - Translating positions into screen coordinates it's a matter of simply
    ;;    rolling the low nibble of the high byte into the low byte.

    INIT_Y_POSITION_LO = $80
    INIT_Y_POSITION_HI = $0C
    INIT_Y_VELOCITY    = $08

    INIT_X_POSITION_LO = $00
    INIT_X_POSITION_HI = $04

    THROTTLE  = $D8
    BLAST_OFF = $F8
    GRAVITY   = $28

    UPPER_LIMIT  = 10
    GROUND_LIMIT = 200

    zp_screen_y          = $40
    zp_position_y        = $41  ; NOTE: 16-bit.
    zp_target_velocity_y = $43  ; TODO: needed?
    zp_velocity_y        = $44

    zp_screen_x          = $45
    zp_position_x        = $46  ; NOTE: 16-bit.
    zp_target_velocity_x = $48  ; TODO: needed?
    zp_velocity_x        = $49

    ;; Initialize the player's sprite. Note that for the sprite to look
    ;; correctly on screen you still need to call `Player::update` afterwards.
    .proc init
        ;; Reset velocity
        lda #$00
        sta zp_target_velocity_y
        sta zp_velocity_y
        sta zp_target_velocity_x
        sta zp_velocity_x

        ;; Set position, and the screen coordinates will be updated upong
        ;; calling `update`, which on initialization will happen right after.
        lda #INIT_Y_POSITION_LO
        sta zp_position_y
        lda #INIT_Y_POSITION_HI
        sta zp_position_y + 1
        lda #INIT_X_POSITION_LO
        sta zp_position_x
        lda #INIT_X_POSITION_HI
        sta zp_position_x + 1

        ;;;
        ;; TODO: this should really just go away

        lda #$00
        sta $201
        lda #$00
        sta $202

        lda #$01
        sta $205
        lda #$00
        sta $206

        lda #$10
        sta $209
        lda #$00
        sta $20A

        lda #$11
        sta $20D
        lda #$00
        sta $20E

        lda #$20
        sta $211
        lda #$00
        sta $212

        lda #$21
        sta $215
        lda #$00
        sta $216

        rts
    .endproc

    .proc update
        jsr update_vertical_position

        ;; TODO: horizontal

        ;; At this point all positions are clear, transform them into screen
        ;; coordinates, eject out from boundaries and platforms, and update the
        ;; sprite with the new state.
        jsr position_to_screen
        jsr bound_check
        JAL update_sprite
    .endproc

    ;; Updates the `zp_velocity_y` and the `zp_position_y` depending on whether
    ;; the player is throttling or gravity should just apply.
    .proc update_vertical_position
        ;; Check if the player is asking to throttle, otherwise apply gravity.
        lda #(Joypad::BUTTON_UP | Joypad::BUTTON_A)
        and Joypad::zp_buttons1
        beq @set_gravity
        lda zp_velocity_y
        beq @blast_off
        lda #THROTTLE
        bne @compute_vertical
    @set_gravity:
        lda #GRAVITY

    @compute_vertical:
        sta Globals::zp_tmp0

        ;; Check the difference between the given target velocity and what we
        ;; have now. If it equals to zero, then we change nothing in regards to
        ;; the velocity.
        lda zp_velocity_y
        sec
        sbc Globals::zp_tmp0
        beq @apply_velocity

        ;; Increase or decrease depending on what we have now.
        ;; TODO: inc/dec might not quite cut it in NTSC vs PAL
        bmi @down
        dec zp_velocity_y
        jmp @apply_velocity
    @down:
        inc zp_velocity_y
        jmp @apply_velocity

    @blast_off:
        lda #BLAST_OFF
        sta zp_velocity_y
        jmp @going_up

    @apply_velocity:
        lda zp_velocity_y
        bmi @going_up

        ;; The velocity is positive, so it's just a 16-bit addition.
        clc
        adc zp_position_y
        sta zp_position_y
        lda #0
        adc zp_position_y + 1
        sta zp_position_y + 1
        rts

    @going_up:
        ;; Negative velocity, we need to go up. This is probably not optimal,
        ;; but we just invert the number and subtract with that.
        lda #0
        sec
        sbc zp_velocity_y
        sta Globals::zp_tmp0
        lda zp_position_y
        sec
        sbc Globals::zp_tmp0
        sta zp_position_y
        lda zp_position_y + 1
        sbc #0
        sta zp_position_y + 1

        rts
    .endproc

    ;; Convert the positions with subpixel precision into mere screen
    ;; coordinates. That is, update the values on `zp_screen_{x,y}` given the
    ;; current values of `zp_position_{x,y}`.
    .proc position_to_screen
        ;; We save the high byte into a temporary value, and we load the low
        ;; byte into the accumulator.
        lda zp_position_y + 1
        sta Globals::zp_tmp0
        lda zp_position_y

        ;; And now it's a matter of rotating the high byte into the low one to
        ;; match a full byte.
        lsr Globals::zp_tmp0
        ror
        lsr Globals::zp_tmp0
        ror
        lsr Globals::zp_tmp0
        ror
        lsr Globals::zp_tmp0
        ror

        ;; Ecce Y coordinates.
        sta zp_screen_y

        ;; And the same for the X coordinates.
        lda zp_position_x + 1
        sta Globals::zp_tmp0
        lda zp_position_x

        ;; And rolling...
        lsr Globals::zp_tmp0
        ror
        lsr Globals::zp_tmp0
        ror
        lsr Globals::zp_tmp0
        ror
        lsr Globals::zp_tmp0
        ror

        ;; Ecce X coordinates.
        sta zp_screen_x

        rts
    .endproc

    ;; Check on whether the player is out of bounds in any way and provide an
    ;; ejection logic for each situation.
    .proc bound_check
        ;; Are we at the top?
        ;; TODO: actually buggy, but nevermind for now
        lda zp_screen_y
        cmp #UPPER_LIMIT
        beq @too_high_icarus
        bcs @check_ground
    @too_high_icarus:
        lda #0
        sta zp_velocity_y
        lda #UPPER_LIMIT
        sta zp_screen_y
        rts

        ;; Nope, are we at the ground?
    @check_ground:
        cmp #(GROUND_LIMIT - 24)
        bcc @above_ground

        ;; We appear to be either at the ground or below it (e.g. we are
        ;; standing still but initial gravity is pulling us down). In this case,
        ;; just reset the Y velocity and the Y position.
        lda #0
        sta zp_velocity_y
        lda #(GROUND_LIMIT - 24)
        sta zp_screen_y
        lda #$F0
        and zp_position_y
        sta zp_position_y
        rts

        ;; Nope, let's check for the platforms.
    @above_ground:
        ;; TODO: notice how ground and top are just cases on the general
        ;; "collision up/down". Next commits will merge these logics.

        rts
    .endproc

    .proc update_sprite
        ;; TODO:
        ;;   - Update heading
        ;;   - Update motion state
        ;;   - Update tiles

        jsr update_sprite_coordinates

        rts
    .endproc

    ;; Update the coordinate for the six sprites that make up the player.
    .proc update_sprite_coordinates
        ;; Y axis.
        lda zp_screen_y
        sta $0200
        sta $0204
        clc
        adc #8
        sta $0208
        sta $020C
        clc
        adc #8
        sta $0210
        sta $0214

        ;; X axis.
        lda zp_screen_x
        sta $0203
        sta $020B
        sta $0213
        clc
        adc #8
        sta $0207
        sta $020F
        sta $0217

        rts
    .endproc
.endscope