summaryrefslogtreecommitdiff
path: root/en/engine/overworld/movement.asm
diff options
context:
space:
mode:
Diffstat (limited to 'en/engine/overworld/movement.asm')
-rw-r--r--en/engine/overworld/movement.asm880
1 files changed, 880 insertions, 0 deletions
diff --git a/en/engine/overworld/movement.asm b/en/engine/overworld/movement.asm
new file mode 100644
index 00000000..bbfbbc39
--- /dev/null
+++ b/en/engine/overworld/movement.asm
@@ -0,0 +1,880 @@
+UpdatePlayerSprite:
+ ld a, [wSpriteStateData2]
+ and a
+ jr z, .checkIfTextBoxInFrontOfSprite
+ cp $ff
+ jr z, .disableSprite
+ dec a
+ ld [wSpriteStateData2], a
+ jr .disableSprite
+; check if a text box is in front of the sprite by checking if the lower left
+; background tile the sprite is standing on is greater than $5F, which is
+; the maximum number for map tiles
+.checkIfTextBoxInFrontOfSprite
+ aCoord 8, 9
+ ld [hTilePlayerStandingOn], a
+ cp $60
+ jr c, .lowerLeftTileIsMapTile
+.disableSprite
+ ld a, $ff
+ ld [wSpriteStateData1 + 2], a
+ ret
+.lowerLeftTileIsMapTile
+ call DetectCollisionBetweenSprites
+ ld h, wSpriteStateData1 / $100
+ ld a, [wWalkCounter]
+ and a
+ jr nz, .moving
+ ld a, [wPlayerMovingDirection]
+; check if down
+ bit PLAYER_DIR_BIT_DOWN, a
+ jr z, .checkIfUp
+ xor a ; ld a, SPRITE_FACING_DOWN
+ jr .next
+.checkIfUp
+ bit PLAYER_DIR_BIT_UP, a
+ jr z, .checkIfLeft
+ ld a, SPRITE_FACING_UP
+ jr .next
+.checkIfLeft
+ bit PLAYER_DIR_BIT_LEFT, a
+ jr z, .checkIfRight
+ ld a, SPRITE_FACING_LEFT
+ jr .next
+.checkIfRight
+ bit PLAYER_DIR_BIT_RIGHT, a
+ jr z, .notMoving
+ ld a, SPRITE_FACING_RIGHT
+ jr .next
+.notMoving
+; zero the animation counters
+ xor a
+ ld [wSpriteStateData1 + 7], a
+ ld [wSpriteStateData1 + 8], a
+ jr .calcImageIndex
+.next
+ ld [wSpriteStateData1 + 9], a ; facing direction
+ ld a, [wFontLoaded]
+ bit 0, a
+ jr nz, .notMoving
+.moving
+ ld a, [wd736]
+ bit 7, a ; is the player sprite spinning due to a spin tile?
+ jr nz, .skipSpriteAnim
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $7
+ ld l, a
+ ld a, [hl]
+ inc a
+ ld [hl], a
+ cp 4
+ jr nz, .calcImageIndex
+ xor a
+ ld [hl], a
+ inc hl
+ ld a, [hl]
+ inc a
+ and $3
+ ld [hl], a
+.calcImageIndex
+ ld a, [wSpriteStateData1 + 8]
+ ld b, a
+ ld a, [wSpriteStateData1 + 9]
+ add b
+ ld [wSpriteStateData1 + 2], a
+.skipSpriteAnim
+; If the player is standing on a grass tile, make the player's sprite have
+; lower priority than the background so that it's partially obscured by the
+; grass. Only the lower half of the sprite is permitted to have the priority
+; bit set by later logic.
+ ld a, [hTilePlayerStandingOn]
+ ld c, a
+ ld a, [wGrassTile]
+ cp c
+ ld a, $0
+ jr nz, .next2
+ ld a, $80
+.next2
+ ld [wSpriteStateData2 + 7], a
+ ret
+
+UnusedReadSpriteDataFunction:
+ push bc
+ push af
+ ld a, [H_CURRENTSPRITEOFFSET]
+ ld c, a
+ pop af
+ add c
+ ld l, a
+ pop bc
+ ret
+
+UpdateNPCSprite:
+ ld a, [H_CURRENTSPRITEOFFSET]
+ swap a
+ dec a
+ add a
+ ld hl, wMapSpriteData
+ add l
+ ld l, a
+ ld a, [hl] ; read movement byte 2
+ ld [wCurSpriteMovement2], a
+ ld h, $c1
+ ld a, [H_CURRENTSPRITEOFFSET]
+ ld l, a
+ inc l
+ ld a, [hl] ; c1x1
+ and a
+ jp z, InitializeSpriteStatus
+ call CheckSpriteAvailability
+ ret c ; if sprite is invisible, on tile >=$60, in grass or player is currently walking
+ ld h, $c1
+ ld a, [H_CURRENTSPRITEOFFSET]
+ ld l, a
+ inc l
+ ld a, [hl] ; c1x1
+ bit 7, a ; is the face player flag set?
+ jp nz, MakeNPCFacePlayer
+ ld b, a
+ ld a, [wFontLoaded]
+ bit 0, a
+ jp nz, notYetMoving
+ ld a, b
+ cp $2
+ jp z, UpdateSpriteMovementDelay ; c1x1 == 2
+ cp $3
+ jp z, UpdateSpriteInWalkingAnimation ; c1x1 == 3
+ ld a, [wWalkCounter]
+ and a
+ ret nz ; don't do anything yet if player is currently moving (redundant, already tested in CheckSpriteAvailability)
+ call InitializeSpriteScreenPosition
+ ld h, $c2
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $6
+ ld l, a
+ ld a, [hl] ; c2x6: movement byte 1
+ inc a
+ jr z, .randomMovement ; value $FF
+ inc a
+ jr z, .randomMovement ; value $FE
+; scripted movement
+ dec a
+ ld [hl], a ; increment movement byte 1 (movement data index)
+ dec a
+ push hl
+ ld hl, wNPCNumScriptedSteps
+ dec [hl] ; decrement wNPCNumScriptedSteps
+ pop hl
+ ld de, wNPCMovementDirections
+ call LoadDEPlusA ; a = [wNPCMovementDirections + movement byte 1]
+ cp $e0
+ jp z, ChangeFacingDirection
+ cp STAY
+ jr nz, .next
+; reached end of wNPCMovementDirections list
+ ld [hl], a ; store $ff in movement byte 1, disabling scripted movement
+ ld hl, wd730
+ res 0, [hl]
+ xor a
+ ld [wSimulatedJoypadStatesIndex], a
+ ld [wWastedByteCD3A], a
+ ret
+.next
+ cp WALK
+ jr nz, .determineDirection
+; current NPC movement data is $fe. this seems buggy
+ ld [hl], $1 ; set movement byte 1 to $1
+ ld de, wNPCMovementDirections
+ call LoadDEPlusA ; a = [wNPCMovementDirections + $fe] (?)
+ jr .determineDirection
+.randomMovement
+ call GetTileSpriteStandsOn
+ call Random
+.determineDirection
+ ld b, a
+ ld a, [wCurSpriteMovement2]
+ cp $d0
+ jr z, .moveDown ; movement byte 2 = $d0 forces down
+ cp $d1
+ jr z, .moveUp ; movement byte 2 = $d1 forces up
+ cp $d2
+ jr z, .moveLeft ; movement byte 2 = $d2 forces left
+ cp $d3
+ jr z, .moveRight ; movement byte 2 = $d3 forces right
+ ld a, b
+ cp $40 ; a < $40: down (or left)
+ jr nc, .notDown
+ ld a, [wCurSpriteMovement2]
+ cp $2
+ jr z, .moveLeft ; movement byte 2 = $2 only allows left or right
+.moveDown
+ ld de, 2*SCREEN_WIDTH
+ add hl, de ; move tile pointer two rows down
+ lb de, 1, 0
+ lb bc, 4, SPRITE_FACING_DOWN
+ jr TryWalking
+.notDown
+ cp $80 ; $40 <= a < $80: up (or right)
+ jr nc, .notUp
+ ld a, [wCurSpriteMovement2]
+ cp $2
+ jr z, .moveRight ; movement byte 2 = $2 only allows left or right
+.moveUp
+ ld de, -2*SCREEN_WIDTH
+ add hl, de ; move tile pointer two rows up
+ lb de, -1, 0
+ lb bc, 8, SPRITE_FACING_UP
+ jr TryWalking
+.notUp
+ cp $c0 ; $80 <= a < $c0: left (or up)
+ jr nc, .notLeft
+ ld a, [wCurSpriteMovement2]
+ cp $1
+ jr z, .moveUp ; movement byte 2 = $1 only allows up or down
+.moveLeft
+ dec hl
+ dec hl ; move tile pointer two columns left
+ lb de, 0, -1
+ lb bc, 2, SPRITE_FACING_LEFT
+ jr TryWalking
+.notLeft ; $c0 <= a: right (or down)
+ ld a, [wCurSpriteMovement2]
+ cp $1
+ jr z, .moveDown ; movement byte 2 = $1 only allows up or down
+.moveRight
+ inc hl
+ inc hl ; move tile pointer two columns right
+ lb de, 0, 1
+ lb bc, 1, SPRITE_FACING_RIGHT
+ jr TryWalking
+
+; changes facing direction by zeroing the movement delta and calling TryWalking
+ChangeFacingDirection:
+ ld de, $0
+ ; fall through
+
+; b: direction (1,2,4 or 8)
+; c: new facing direction (0,4,8 or $c)
+; d: Y movement delta (-1, 0 or 1)
+; e: X movement delta (-1, 0 or 1)
+; hl: pointer to tile the sprite would walk onto
+; set carry on failure, clears carry on success
+TryWalking:
+ push hl
+ ld h, $c1
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $9
+ ld l, a
+ ld [hl], c ; c1x9 (update facing direction)
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $3
+ ld l, a
+ ld [hl], d ; c1x3 (update Y movement delta)
+ inc l
+ inc l
+ ld [hl], e ; c1x5 (update X movement delta)
+ pop hl
+ push de
+ ld c, [hl] ; read tile to walk onto
+ call CanWalkOntoTile
+ pop de
+ ret c ; cannot walk there (reinitialization of delay values already done)
+ ld h, $c2
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $4
+ ld l, a
+ ld a, [hl] ; c2x4: Y position
+ add d
+ ld [hli], a ; update Y position
+ ld a, [hl] ; c2x5: X position
+ add e
+ ld [hl], a ; update X position
+ ld a, [H_CURRENTSPRITEOFFSET]
+ ld l, a
+ ld [hl], $10 ; c2x0=16: walk animation counter
+ dec h
+ inc l
+ ld [hl], $3 ; c1x1: set movement status to walking
+ jp UpdateSpriteImage
+
+; update the walking animation parameters for a sprite that is currently walking
+UpdateSpriteInWalkingAnimation:
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $7
+ ld l, a
+ ld a, [hl] ; c1x7 (counter until next walk animation frame)
+ inc a
+ ld [hl], a ; c1x7 += 1
+ cp $4
+ jr nz, .noNextAnimationFrame
+ xor a
+ ld [hl], a ; c1x7 = 0
+ inc l
+ ld a, [hl] ; c1x8 (walk animation frame)
+ inc a
+ and $3
+ ld [hl], a ; advance to next animation frame every 4 ticks (16 ticks total for one step)
+.noNextAnimationFrame
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $3
+ ld l, a
+ ld a, [hli] ; c1x3 (movement Y delta)
+ ld b, a
+ ld a, [hl] ; c1x4 (screen Y position)
+ add b
+ ld [hli], a ; update screen Y position
+ ld a, [hli] ; c1x5 (movement X delta)
+ ld b, a
+ ld a, [hl] ; c1x6 (screen X position)
+ add b
+ ld [hl], a ; update screen X position
+ ld a, [H_CURRENTSPRITEOFFSET]
+ ld l, a
+ inc h
+ ld a, [hl] ; c2x0 (walk animation counter)
+ dec a
+ ld [hl], a ; update walk animation counter
+ ret nz
+ ld a, $6 ; walking finished, update state
+ add l
+ ld l, a
+ ld a, [hl] ; c2x6 (movement byte 1)
+ cp $fe
+ jr nc, .initNextMovementCounter ; values $fe and $ff
+ ld a, [H_CURRENTSPRITEOFFSET]
+ inc a
+ ld l, a
+ dec h
+ ld [hl], $1 ; c1x1 = 1 (movement status ready)
+ ret
+.initNextMovementCounter
+ call Random
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $8
+ ld l, a
+ ld a, [hRandomAdd]
+ and $7f
+ ld [hl], a ; c2x8: set next movement delay to a random value in [0,$7f]
+ dec h ; note that value 0 actually makes the delay $100 (bug?)
+ ld a, [H_CURRENTSPRITEOFFSET]
+ inc a
+ ld l, a
+ ld [hl], $2 ; c1x1 = 2 (movement status)
+ inc l
+ inc l
+ xor a
+ ld b, [hl] ; c1x3 (movement Y delta)
+ ld [hli], a ; reset movement Y delta
+ inc l
+ ld c, [hl] ; c1x5 (movement X delta)
+ ld [hl], a ; reset movement X delta
+ ret
+
+; update delay value (c2x8) for sprites in the delayed state (c1x1)
+UpdateSpriteMovementDelay:
+ ld h, $c2
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $6
+ ld l, a
+ ld a, [hl] ; c2x6: movement byte 1
+ inc l
+ inc l
+ cp $fe
+ jr nc, .tickMoveCounter ; values $fe or $ff
+ ld [hl], $0
+ jr .moving
+.tickMoveCounter
+ dec [hl] ; c2x8: frame counter until next movement
+ jr nz, notYetMoving
+.moving
+ dec h
+ ld a, [H_CURRENTSPRITEOFFSET]
+ inc a
+ ld l, a
+ ld [hl], $1 ; c1x1 = 1 (mark as ready to move)
+notYetMoving:
+ ld h, wSpriteStateData1 / $100
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $8
+ ld l, a
+ ld [hl], $0 ; c1x8 = 0 (walk animation frame)
+ jp UpdateSpriteImage
+
+MakeNPCFacePlayer:
+; Make an NPC face the player if the player has spoken to him or her.
+
+; Check if the behaviour of the NPC facing the player when spoken to is
+; disabled. This is only done when rubbing the S.S. Anne captain's back.
+ ld a, [wd72d]
+ bit 5, a
+ jr nz, notYetMoving
+ res 7, [hl]
+ ld a, [wPlayerDirection]
+ bit PLAYER_DIR_BIT_UP, a
+ jr z, .notFacingDown
+ ld c, SPRITE_FACING_DOWN
+ jr .facingDirectionDetermined
+.notFacingDown
+ bit PLAYER_DIR_BIT_DOWN, a
+ jr z, .notFacingUp
+ ld c, SPRITE_FACING_UP
+ jr .facingDirectionDetermined
+.notFacingUp
+ bit PLAYER_DIR_BIT_LEFT, a
+ jr z, .notFacingRight
+ ld c, SPRITE_FACING_RIGHT
+ jr .facingDirectionDetermined
+.notFacingRight
+ ld c, SPRITE_FACING_LEFT
+.facingDirectionDetermined
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $9
+ ld l, a
+ ld [hl], c ; c1x9: set facing direction
+ jr notYetMoving
+
+InitializeSpriteStatus:
+ ld [hl], $1 ; $c1x1: set movement status to ready
+ inc l
+ ld [hl], $ff ; $c1x2: set sprite image to $ff (invisible/off screen)
+ inc h
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $2
+ ld l, a
+ ld a, $8
+ ld [hli], a ; $c2x2: set Y displacement to 8
+ ld [hl], a ; $c2x3: set X displacement to 8
+ ret
+
+; calculates the sprite's screen position form its map position and the player position
+InitializeSpriteScreenPosition:
+ ld h, wSpriteStateData2 / $100
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $4
+ ld l, a
+ ld a, [wYCoord]
+ ld b, a
+ ld a, [hl] ; c2x4 (Y position + 4)
+ sub b ; relative to player position
+ swap a ; * 16
+ sub $4 ; - 4
+ dec h
+ ld [hli], a ; c1x4 (screen Y position)
+ inc h
+ ld a, [wXCoord]
+ ld b, a
+ ld a, [hli] ; c2x6 (X position + 4)
+ sub b ; relative to player position
+ swap a ; * 16
+ dec h
+ ld [hl], a ; c1x6 (screen X position)
+ ret
+
+; tests if sprite is off screen or otherwise unable to do anything
+CheckSpriteAvailability:
+ predef IsObjectHidden
+ ld a, [$ffe5]
+ and a
+ jp nz, .spriteInvisible
+ ld h, wSpriteStateData2 / $100
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $6
+ ld l, a
+ ld a, [hl] ; c2x6: movement byte 1
+ cp $fe
+ jr c, .skipXVisibilityTest ; movement byte 1 < $fe (i.e. the sprite's movement is scripted)
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $4
+ ld l, a
+ ld b, [hl] ; c2x4: Y pos (+4)
+ ld a, [wYCoord]
+ cp b
+ jr z, .skipYVisibilityTest
+ jr nc, .spriteInvisible ; above screen region
+ add $8 ; screen is 9 tiles high
+ cp b
+ jr c, .spriteInvisible ; below screen region
+.skipYVisibilityTest
+ inc l
+ ld b, [hl] ; c2x5: X pos (+4)
+ ld a, [wXCoord]
+ cp b
+ jr z, .skipXVisibilityTest
+ jr nc, .spriteInvisible ; left of screen region
+ add $9 ; screen is 10 tiles wide
+ cp b
+ jr c, .spriteInvisible ; right of screen region
+.skipXVisibilityTest
+; make the sprite invisible if a text box is in front of it
+; $5F is the maximum number for map tiles
+ call GetTileSpriteStandsOn
+ ld d, $60
+ ld a, [hli]
+ cp d
+ jr nc, .spriteInvisible ; standing on tile with ID >=$60 (bottom left tile)
+ ld a, [hld]
+ cp d
+ jr nc, .spriteInvisible ; standing on tile with ID >=$60 (bottom right tile)
+ ld bc, -20
+ add hl, bc ; go back one row of tiles
+ ld a, [hli]
+ cp d
+ jr nc, .spriteInvisible ; standing on tile with ID >=$60 (top left tile)
+ ld a, [hl]
+ cp d
+ jr c, .spriteVisible ; standing on tile with ID >=$60 (top right tile)
+.spriteInvisible
+ ld h, wSpriteStateData1 / $100
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $2
+ ld l, a
+ ld [hl], $ff ; c1x2
+ scf
+ jr .done
+.spriteVisible
+ ld c, a
+ ld a, [wWalkCounter]
+ and a
+ jr nz, .done ; if player is currently walking, we're done
+ call UpdateSpriteImage
+ inc h
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $7
+ ld l, a
+ ld a, [wGrassTile]
+ cp c
+ ld a, $0
+ jr nz, .notInGrass
+ ld a, $80
+.notInGrass
+ ld [hl], a ; c2x7
+ and a
+.done
+ ret
+
+UpdateSpriteImage:
+ ld h, $c1
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $8
+ ld l, a
+ ld a, [hli] ; c1x8: walk animation frame
+ ld b, a
+ ld a, [hl] ; c1x9: facing direction
+ add b
+ ld b, a
+ ld a, [$ff93] ; current sprite offset
+ add b
+ ld b, a
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $2
+ ld l, a
+ ld [hl], b ; c1x2: sprite to display
+ ret
+
+; tests if sprite can walk the specified direction
+; b: direction (1,2,4 or 8)
+; c: ID of tile the sprite would walk onto
+; d: Y movement delta (-1, 0 or 1)
+; e: X movement delta (-1, 0 or 1)
+; set carry on failure, clears carry on success
+CanWalkOntoTile:
+ ld h, wSpriteStateData2 / $100
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $6
+ ld l, a
+ ld a, [hl] ; c2x6 (movement byte 1)
+ cp $fe
+ jr nc, .notScripted ; values $fe and $ff
+; always allow walking if the movement is scripted
+ and a
+ ret
+.notScripted
+ ld a, [wTilesetCollisionPtr]
+ ld l, a
+ ld a, [wTilesetCollisionPtr+1]
+ ld h, a
+.tilePassableLoop
+ ld a, [hli]
+ cp $ff
+ jr z, .impassable
+ cp c
+ jr nz, .tilePassableLoop
+ ld h, $c2
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $6
+ ld l, a
+ ld a, [hl] ; $c2x6 (movement byte 1)
+ inc a
+ jr z, .impassable ; if $ff, no movement allowed (however, changing direction is)
+ ld h, wSpriteStateData1 / $100
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $4
+ ld l, a
+ ld a, [hli] ; c1x4 (screen Y pos)
+ add $4 ; align to blocks (Y pos is always 4 pixels off)
+ add d ; add Y delta
+ cp $80 ; if value is >$80, the destination is off screen (either $81 or $FF underflow)
+ jr nc, .impassable ; don't walk off screen
+ inc l
+ ld a, [hl] ; c1x6 (screen X pos)
+ add e ; add X delta
+ cp $90 ; if value is >$90, the destination is off screen (either $91 or $FF underflow)
+ jr nc, .impassable ; don't walk off screen
+ push de
+ push bc
+ call DetectCollisionBetweenSprites
+ pop bc
+ pop de
+ ld h, wSpriteStateData1 / $100
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $c
+ ld l, a
+ ld a, [hl] ; c1xc (directions in which sprite collision would occur)
+ and b ; check against chosen direction (1,2,4 or 8)
+ jr nz, .impassable ; collision between sprites, don't go there
+ ld h, wSpriteStateData2 / $100
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $2
+ ld l, a
+ ld a, [hli] ; c2x2 (sprite Y displacement, initialized at $8, keep track of where a sprite did go)
+ bit 7, d ; check if going upwards (d=$ff)
+ jr nz, .upwards
+ add d
+ cp $5
+ jr c, .impassable ; if c2x2+d < 5, don't go ;bug: this tests probably were supposed to prevent sprites
+ jr .checkHorizontal ; from walking out too far, but this line makes sprites get stuck
+.upwards ; whenever they walked upwards 5 steps
+ sub $1 ; on the other hand, the amount a sprite can walk out to the
+ jr c, .impassable ; if d2x2 == 0, don't go ; right of bottom is not limited (until the counter overflows)
+.checkHorizontal
+ ld d, a
+ ld a, [hl] ; c2x3 (sprite X displacement, initialized at $8, keep track of where a sprite did go)
+ bit 7, e ; check if going left (e=$ff)
+ jr nz, .left
+ add e
+ cp $5 ; compare, but no conditional jump like in the vertical check above (bug?)
+ jr .passable
+.left
+ sub $1
+ jr c, .impassable ; if d2x3 == 0, don't go
+.passable
+ ld [hld], a ; update c2x3
+ ld [hl], d ; update c2x2
+ and a ; clear carry (marking success)
+ ret
+.impassable
+ ld h, $c1
+ ld a, [H_CURRENTSPRITEOFFSET]
+ inc a
+ ld l, a
+ ld [hl], $2 ; c1x1 = 2 (set movement status to delayed)
+ inc l
+ inc l
+ xor a
+ ld [hli], a ; c1x3 = 0 (clear Y movement delta)
+ inc l
+ ld [hl], a ; c1x5 = 0 (clear X movement delta)
+ inc h
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $8
+ ld l, a
+ call Random
+ ld a, [hRandomAdd]
+ and $7f
+ ld [hl], a ; c2x8: set next movement delay to a random value in [0,$7f] (again with delay $100 if value is 0)
+ scf ; set carry (marking failure to walk)
+ ret
+
+; calculates the tile pointer pointing to the tile the current sprite stands on
+; this is always the lower left tile of the 2x2 tile blocks all sprites are snapped to
+; hl: output pointer
+GetTileSpriteStandsOn:
+ ld h, wSpriteStateData1 / $100
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $4
+ ld l, a
+ ld a, [hli] ; c1x4: screen Y position
+ add $4 ; align to 2*2 tile blocks (Y position is always off 4 pixels to the top)
+ and $f0 ; in case object is currently moving
+ srl a ; screen Y tile * 4
+ ld c, a
+ ld b, $0
+ inc l
+ ld a, [hl] ; c1x6: screen Y position
+ srl a
+ srl a
+ srl a ; screen X tile
+ add SCREEN_WIDTH ; screen X tile + 20
+ ld d, $0
+ ld e, a
+ coord hl, 0, 0
+ add hl, bc
+ add hl, bc
+ add hl, bc
+ add hl, bc
+ add hl, bc
+ add hl, de ; wTileMap + 20*(screen Y tile + 1) + screen X tile
+ ret
+
+; loads [de+a] into a
+LoadDEPlusA:
+ add e
+ ld e, a
+ jr nc, .noCarry
+ inc d
+.noCarry
+ ld a, [de]
+ ret
+
+DoScriptedNPCMovement:
+; This is an alternative method of scripting an NPC's movement and is only used
+; a few times in the game. It is used when the NPC and player must walk together
+; in sync, such as when the player is following the NPC somewhere. An NPC can't
+; be moved in sync with the player using the other method.
+ ld a, [wd730]
+ bit 7, a
+ ret z
+ ld hl, wd72e
+ bit 7, [hl]
+ set 7, [hl]
+ jp z, InitScriptedNPCMovement
+ ld hl, wNPCMovementDirections2
+ ld a, [wNPCMovementDirections2Index]
+ add l
+ ld l, a
+ jr nc, .noCarry
+ inc h
+.noCarry
+ ld a, [hl]
+; check if moving up
+ cp NPC_MOVEMENT_UP
+ jr nz, .checkIfMovingDown
+ call GetSpriteScreenYPointer
+ ld c, SPRITE_FACING_UP
+ ld a, -2
+ jr .move
+.checkIfMovingDown
+ cp NPC_MOVEMENT_DOWN
+ jr nz, .checkIfMovingLeft
+ call GetSpriteScreenYPointer
+ ld c, SPRITE_FACING_DOWN
+ ld a, 2
+ jr .move
+.checkIfMovingLeft
+ cp NPC_MOVEMENT_LEFT
+ jr nz, .checkIfMovingRight
+ call GetSpriteScreenXPointer
+ ld c, SPRITE_FACING_LEFT
+ ld a, -2
+ jr .move
+.checkIfMovingRight
+ cp NPC_MOVEMENT_RIGHT
+ jr nz, .noMatch
+ call GetSpriteScreenXPointer
+ ld c, SPRITE_FACING_RIGHT
+ ld a, 2
+ jr .move
+.noMatch
+ cp $ff
+ ret
+.move
+ ld b, a
+ ld a, [hl]
+ add b
+ ld [hl], a
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $9
+ ld l, a
+ ld a, c
+ ld [hl], a ; facing direction
+ call AnimScriptedNPCMovement
+ ld hl, wScriptedNPCWalkCounter
+ dec [hl]
+ ret nz
+ ld a, 8
+ ld [wScriptedNPCWalkCounter], a
+ ld hl, wNPCMovementDirections2Index
+ inc [hl]
+ ret
+
+InitScriptedNPCMovement:
+ xor a
+ ld [wNPCMovementDirections2Index], a
+ ld a, 8
+ ld [wScriptedNPCWalkCounter], a
+ jp AnimScriptedNPCMovement
+
+GetSpriteScreenYPointer:
+ ld a, $4
+ ld b, a
+ jr GetSpriteScreenXYPointerCommon
+
+GetSpriteScreenXPointer:
+ ld a, $6
+ ld b, a
+
+GetSpriteScreenXYPointerCommon:
+ ld hl, wSpriteStateData1
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add l
+ add b
+ ld l, a
+ ret
+
+AnimScriptedNPCMovement:
+ ld hl, wSpriteStateData2
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $e
+ ld l, a
+ ld a, [hl] ; VRAM slot
+ dec a
+ swap a
+ ld b, a
+ ld hl, wSpriteStateData1
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $9
+ ld l, a
+ ld a, [hl] ; facing direction
+ cp SPRITE_FACING_DOWN
+ jr z, .anim
+ cp SPRITE_FACING_UP
+ jr z, .anim
+ cp SPRITE_FACING_LEFT
+ jr z, .anim
+ cp SPRITE_FACING_RIGHT
+ jr z, .anim
+ ret
+.anim
+ add b
+ ld b, a
+ ld [hSpriteVRAMSlotAndFacing], a
+ call AdvanceScriptedNPCAnimFrameCounter
+ ld hl, wSpriteStateData1
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $2
+ ld l, a
+ ld a, [hSpriteVRAMSlotAndFacing]
+ ld b, a
+ ld a, [hSpriteAnimFrameCounter]
+ add b
+ ld [hl], a
+ ret
+
+AdvanceScriptedNPCAnimFrameCounter:
+ ld a, [H_CURRENTSPRITEOFFSET]
+ add $7
+ ld l, a
+ ld a, [hl] ; intra-animation frame counter
+ inc a
+ ld [hl], a
+ cp 4
+ ret nz
+ xor a
+ ld [hl], a ; reset intra-animation frame counter
+ inc l
+ ld a, [hl] ; animation frame counter
+ inc a
+ and $3
+ ld [hl], a
+ ld [hSpriteAnimFrameCounter], a
+ ret