pokered/engine/overworld/sprite_collisions.asm
2023-11-20 17:53:19 -05:00

345 lines
8.4 KiB
NASM

_UpdateSprites::
ld h, $c1
inc h
ld a, SPRITESTATEDATA2_IMAGEBASEOFFSET
.spriteLoop
ld l, a
sub SPRITESTATEDATA2_IMAGEBASEOFFSET
ld c, a
ldh [hCurrentSpriteOffset], a
ld a, [hl]
and a
jr z, .skipSprite ; tests SPRITESTATEDATA2_IMAGEBASEOFFSET
push hl
push de
push bc
call .updateCurrentSprite
pop bc
pop de
pop hl
.skipSprite
ld a, l
add $10 ; move to next sprite
cp SPRITESTATEDATA2_IMAGEBASEOFFSET ; test for overflow (back at beginning)
jr nz, .spriteLoop
ret
.updateCurrentSprite
cp $1
jp nz, UpdateNonPlayerSprite
jp UpdatePlayerSprite
UpdateNonPlayerSprite:
dec a
swap a
ldh [hTilePlayerStandingOn], a ; $10 * sprite#
ld a, [wNPCMovementScriptSpriteOffset] ; some sprite offset?
ld b, a
ldh a, [hCurrentSpriteOffset]
cp b
jr nz, .unequal
jp DoScriptedNPCMovement
.unequal
jp UpdateNPCSprite
; This detects if the current sprite (whose offset is at hCurrentSpriteOffset)
; is going to collide with another sprite by looping over the other sprites.
; The current sprite's offset will be labelled with i (e.g. i#SPRITESTATEDATA1_PICTUREID).
; The loop sprite's offset will labelled with j (e.g. j#SPRITESTATEDATA1_PICTUREID).
;
; Note that the Y coordinate of the sprite (in [k#SPRITESTATEDATA1_YPIXELS])
; is one of the following 9 values when the sprite is aligned with the grid:
; $fc, $0c, $1c, $2c, ..., $7c.
; The reason that 4 is added below to the coordinate is to make it align with a
; multiple of $10 to make comparisons easier.
DetectCollisionBetweenSprites:
nop
ld h, HIGH(wSpriteStateData1)
ldh a, [hCurrentSpriteOffset]
add LOW(wSpriteStateData1)
ld l, a
ld a, [hl] ; a = [i#SPRITESTATEDATA1_PICTUREID] (0 if slot is unused)
and a ; is this sprite slot slot used?
ret z ; return if not used
ld a, l
add 3
ld l, a
ld a, [hli] ; a = [i#SPRITESTATEDATA1_YSTEPVECTOR] (-1, 0, or 1)
call SetSpriteCollisionValues
ld a, [hli] ; a = [i#SPRITESTATEDATA1_YPIXELS]
add 4 ; align with multiple of $10
; The effect of the following 3 lines is to
; add 7 to a if moving south or
; subtract 7 from a if moving north.
add b
and $f0
or c
ldh [hCollidingSpriteTempYValue], a ; y adjusted for direction of movement
ld a, [hli] ; a = [i#SPRITESTATEDATA1_XSTEPVECTOR] (-1, 0, or 1)
call SetSpriteCollisionValues
ld a, [hl] ; a = [i#SPRITESTATEDATA1_XPIXELS]
; The effect of the following 3 lines is to
; add 7 to a if moving east or
; subtract 7 from a if moving west.
add b
and $f0
or c
ldh [hCollidingSpriteTempXValue], a ; x adjusted for direction of movement
ld a, l
add 7
ld l, a
xor a
ld [hld], a ; zero [i#SPRITESTATEDATA1_0D] XXX what's this for?
ld [hld], a ; zero [i#SPRITESTATEDATA1_COLLISIONDATA]
ldh a, [hCollidingSpriteTempXValue]
ld [hld], a ; [i#SPRITESTATEDATA1_XADJUSTED]
ldh a, [hCollidingSpriteTempYValue]
ld [hl], a ; [i#SPRITESTATEDATA1_YADJUSTED]
xor a ; zero the loop counter
.loop
ldh [hCollidingSpriteOffset], a
swap a
ld e, a
ldh a, [hCurrentSpriteOffset]
cp e ; does the loop sprite match the current sprite?
jp z, .next ; go to the next sprite if they match
ld d, h
ld a, [de] ; a = [j#SPRITESTATEDATA1_PICTUREID] (0 if slot is unused)
and a ; is this sprite slot slot used?
jp z, .next ; go the next sprite if not used
inc e
inc e
ld a, [de] ; a = [j#SPRITESTATEDATA1_IMAGEINDEX] ($ff means the sprite is offscreen)
inc a
jp z, .next ; go the next sprite if offscreen
ldh a, [hCurrentSpriteOffset]
add 10
ld l, a
inc e
ld a, [de] ; a = [j#SPRITESTATEDATA1_YSTEPVECTOR]
call SetSpriteCollisionValues
inc e
ld a, [de] ; a = [j#SPRITESTATEDATA1_YPIXELS]
add 4 ; align with multiple of $10
; The effect of the following 3 lines is to
; add 7 to a if moving south or
; subtract 7 from a if moving north.
add b
and $f0
or c
sub [hl] ; subtract [i#SPRITESTATEDATA1_YADJUSTED] from [j#SPRITESTATEDATA1_YADJUSTED]
; calculate the absolute value of the difference to get the distance
jr nc, .noCarry1
cpl
inc a
.noCarry1
ldh [hCollidingSpriteTempYValue], a ; store the distance between the two sprites' adjusted Y values
; Use the carry flag set by the above subtraction to determine which sprite's
; Y coordinate is larger. This information is used later to set
; [i#SPRITESTATEDATA1_COLLISIONDATA].
; The following 5 lines set the lowest 2 bits of c, which are later shifted left by 2.
; If sprite i's Y is larger, set lowest 2 bits of c to 10.
; If sprite j's Y is larger or both are equal, set lowest 2 bits of c to 01.
push af
rl c
pop af
ccf
rl c
; If sprite i's delta Y is 0, then b = 7, else b = 9.
ld b, 7
ld a, [hl] ; a = [i#SPRITESTATEDATA1_YADJUSTED]
and $f
jr z, .next1
ld b, 9
.next1
ldh a, [hCollidingSpriteTempYValue] ; a = distance between adjusted Y coordinates
sub b
ldh [hCollidingSpriteAdjustedDistance], a
ld a, b
ldh [hCollidingSpriteTempYValue], a ; store 7 or 9 depending on sprite i's delta Y
jr c, .checkXDistance
; If sprite j's delta Y is 0, then b = 7, else b = 9.
ld b, 7
dec e
ld a, [de] ; a = [j#SPRITESTATEDATA1_YSTEPVECTOR]
inc e
and a
jr z, .next2
ld b, 9
.next2
ldh a, [hCollidingSpriteAdjustedDistance]
sub b ; adjust distance using sprite j's direction
jr z, .checkXDistance
jr nc, .next ; go to next sprite if distance is still positive after both adjustments
.checkXDistance
inc e
inc l
ld a, [de] ; a = [j#SPRITESTATEDATA1_XSTEPVECTOR]
push bc
call SetSpriteCollisionValues
inc e
ld a, [de] ; a = [j#SPRITESTATEDATA1_XPIXELS]
; The effect of the following 3 lines is to
; add 7 to a if moving east or
; subtract 7 from a if moving west.
add b
and $f0
or c
pop bc
sub [hl] ; subtract [i#SPRITESTATEDATA1_XADJUSTED] from [j#SPRITESTATEDATA1_XADJUSTED]
; calculate the absolute value of the difference to get the distance
jr nc, .noCarry2
cpl
inc a
.noCarry2
ldh [hCollidingSpriteTempXValue], a ; store the distance between the two sprites' adjusted X values
; Use the carry flag set by the above subtraction to determine which sprite's
; X coordinate is larger. This information is used later to set
; [i#SPRITESTATEDATA1_COLLISIONDATA].
; The following 5 lines set the lowest 2 bits of c.
; If sprite i's X is larger, set lowest 2 bits of c to 10.
; If sprite j's X is larger or both are equal, set lowest 2 bits of c to 01.
push af
rl c
pop af
ccf
rl c
; If sprite i's delta X is 0, then b = 7, else b = 9.
ld b, 7
ld a, [hl] ; a = [i#SPRITESTATEDATA1_XADJUSTED]
and $f
jr z, .next3
ld b, 9
.next3
ldh a, [hCollidingSpriteTempXValue] ; a = distance between adjusted X coordinates
sub b
ldh [hCollidingSpriteAdjustedDistance], a
ld a, b
ldh [hCollidingSpriteTempXValue], a ; store 7 or 9 depending on sprite i's delta X
jr c, .collision
; If sprite j's delta X is 0, then b = 7, else b = 9.
ld b, 7
dec e
ld a, [de] ; a = [j#SPRITESTATEDATA1_XSTEPVECTOR]
inc e
and a
jr z, .next4
ld b, 9
.next4
ldh a, [hCollidingSpriteAdjustedDistance]
sub b ; adjust distance using sprite j's direction
jr z, .collision
jr nc, .next ; go to next sprite if distance is still positive after both adjustments
.collision
ldh a, [hCollidingSpriteTempXValue] ; a = 7 or 9 depending on sprite i's delta X
ld b, a
ldh a, [hCollidingSpriteTempYValue] ; a = 7 or 9 depending on sprite i's delta Y
inc l
; If delta X isn't 0 and delta Y is 0, then b = %0011, else b = %1100.
; (note that normally if delta X isn't 0, then delta Y must be 0 and vice versa)
cp b
jr c, .next5
ld b, %1100
jr .next6
.next5
ld b, %0011
.next6
ld a, c ; c has 2 bits set (one of bits 0-1 is set for the X axis and one of bits 2-3 for the Y axis)
and b ; we select either the bit in bits 0-1 or bits 2-3 based on the calculation immediately above
or [hl] ; or with existing collision direction bits in [i#SPRITESTATEDATA1_COLLISIONDATA]
ld [hl], a ; store new value
ld a, c ; useless code because a is overwritten before being used again
; set bit in [i#SPRITESTATEDATA1_0E] or [i#SPRITESTATEDATA1_0F]
; to indicate which sprite the collision occurred with
inc l
inc l
ldh a, [hCollidingSpriteOffset]
ld de, SpriteCollisionBitTable
add a
add e
ld e, a
jr nc, .noCarry3
inc d
.noCarry3
ld a, [de]
or [hl]
ld [hli], a
inc de
ld a, [de]
or [hl]
ld [hl], a
.next
ldh a, [hCollidingSpriteOffset]
inc a
cp $10
jp nz, .loop
ret
; takes delta X or delta Y in a
; b = delta X/Y
; c = 0 if delta X/Y is 0
; c = 7 if delta X/Y is 1
; c = 9 if delta X/Y is -1
SetSpriteCollisionValues:
and a
ld b, 0
ld c, 0
jr z, .done
ld c, 9
cp -1
jr z, .ok
ld c, 7
ld a, 0
.ok
ld b, a
.done
ret
SpriteCollisionBitTable:
FOR n, $10
bigdw 1 << n
ENDR