DoPlayerMovement:: call .GetDPad ld a, movement_step_sleep ld [wMovementAnimation], a xor a ld [wWalkingIntoEdgeWarp], a call .TranslateIntoMovement ld c, a ld a, [wMovementAnimation] ld [wPlayerNextMovement], a ret .GetDPad: ldh a, [hCurBoardEvent] cp BOARDEVENT_HANDLE_BOARD jr nz, .not_auto_in_board ; compute direction according to space layout and save to wCurInput farcall StepTowardsNextSpace ret .not_auto_in_board ldh a, [hJoyDown] ld [wCurInput], a ; ignore standing downhill if in View Map mode ldh a, [hCurBoardEvent] cp BOARDEVENT_VIEW_MAP_MODE ret z ; Otherwise, standing downhill instead moves down. ld hl, wBikeFlags bit BIKEFLAGS_DOWNHILL_F, [hl] ret z ld c, a and D_PAD ret nz ld a, c or D_DOWN ld [wCurInput], a ret .TranslateIntoMovement: ldh a, [hCurBoardEvent] cp BOARDEVENT_VIEW_MAP_MODE jp z, .ViewMapMode ld a, [wPlayerState] cp PLAYER_NORMAL jr z, .Normal cp PLAYER_SURF jr z, .Surf cp PLAYER_SURF_PIKA jr z, .Surf cp PLAYER_BIKE jr z, .Normal cp PLAYER_SKATE jr z, .Ice .Normal: call .CheckForced call .GetAction call .CheckTile ret c call .CheckTurning ret c call .TryStep ret c call .TryJump ret c call .CheckWarp ret c jr .NotMoving .Surf: call .CheckForced call .GetAction call .CheckTile ret c call .CheckTurning ret c call .TrySurf ret c jr .NotMoving .Ice: call .CheckForced call .GetAction call .CheckTile ret c call .CheckTurning ret c call .TryStep ret c call .TryJump ret c call .CheckWarp ret c ld a, [wWalkingDirection] cp STANDING jr z, .HitWall call .BumpSound .HitWall: call .StandInPlace xor a ret .NotMoving: ld a, [wWalkingDirection] cp STANDING jr z, .Standing ; Walking into an edge warp won't bump. ld a, [wWalkingIntoEdgeWarp] and a jr nz, .CantMove call .BumpSound .CantMove: call ._WalkInPlace xor a ret .Standing: call .StandInPlace xor a ret .BumpSound: call CheckSFX ret c ld de, SFX_BUMP call PlaySFX ret .ViewMapMode: ; if View Map mode, ignore regular collisions but account for going off-limits or off-range call .GetAction call .ViewMapMode_CheckCollision ; perform a normal step (Stand in place if wWalkingDirection is STANDING) ld a, STEP_WALK call .DoStep scf ret .ViewMapMode_CheckCollision: ; return STANDING into wWalkingDirection if trying to walk off-limits or off-range ; wTileDown, wTileUp, wTileLeft, and wTileRight are borrowed in this mode ; to signal valid directions to InitSecondarySprites. xor a ld hl, wTileDown ld [hli], a ; wTileDown ld [hli], a ; wTileUp ld [hli], a ; wTileLeft ld [hl], a ; wTileRight ; for each direction, check if next tile is COLL_OUT_OF_BOUNDS, which is the only impassable collision in View Map mode xor a ld [wTilePermissions], a ld a, [wPlayerMapX] ld d, a ld a, [wPlayerMapY] ld e, a push de inc e call GetCoordTileCollision cp COLL_OUT_OF_BOUNDS jr nz, .next1 ld [wTileDown], a .next1 pop de dec e call GetCoordTileCollision cp COLL_OUT_OF_BOUNDS jr nz, .next2 ld [wTileUp], a .next2 ld a, [wPlayerMapX] ld d, a ld a, [wPlayerMapY] ld e, a push de dec d call GetCoordTileCollision cp COLL_OUT_OF_BOUNDS jr nz, .next3 ld [wTileLeft], a .next3 pop de inc d call GetCoordTileCollision cp COLL_OUT_OF_BOUNDS jr nz, .next4 ld [wTileRight], a .next4 ; for each direction, check if trying to go off-limits ld hl, wYCoord ld de, wMapHeight ld bc, wSouthConnectedMapGroup ld a, DOWN call .ViewMapMode_CheckDirectionOffLimits ld hl, wYCoord ld de, .TopLimit ld bc, wNorthConnectedMapGroup ld a, UP call .ViewMapMode_CheckDirectionOffLimits ld hl, wXCoord ld de, wMapWidth ld bc, wEastConnectedMapGroup ld a, RIGHT call .ViewMapMode_CheckDirectionOffLimits ld hl, wXCoord ld de, .LeftLimit ld bc, wWestConnectedMapGroup ld a, LEFT call .ViewMapMode_CheckDirectionOffLimits ; for each direction, check if trying to go off-range ld hl, wViewMapModeRange ld de, wViewMapModeDisplacementY ld bc, wTileDown ld a, [de] cp [hl] jr nz, .next5 ld a, COLL_OUT_OF_BOUNDS ld [bc], a .next5 inc bc ; wTileUp ld a, [de] xor $ff inc a cp [hl] jr nz, .next6 ld a, COLL_OUT_OF_BOUNDS ld [bc], a .next6 ld de, wViewMapModeDisplacementX inc bc ; wTileLeft ld a, [de] xor $ff inc a cp [hl] jr nz, .next7 ld a, COLL_OUT_OF_BOUNDS ld [bc], a .next7 inc bc ; wTileRight ld a, [de] cp [hl] jr nz, .next8 ld a, COLL_OUT_OF_BOUNDS ld [bc], a .next8 ; finally return STANDING into wWalkingDirection in the current direction at ; wWalkingDirection has had its corresponding wTile* address set to $ff. ld a, [wWalkingDirection] cp STANDING ret z ld hl, wTileDown ld c, a ld b, 0 add hl, bc ld a, [hl] cp COLL_OUT_OF_BOUNDS ret nz ld a, STANDING ld [wWalkingDirection], a ret .TopLimit: .LeftLimit: db 0 ; check if player would be walking off-limits in this direction .ViewMapMode_CheckDirectionOffLimits: push af ; connection strips that aren't as large as the map height/width are not accounted for. ; to avoid viewing map off-limits when there is a partial map connection, ; use COLL_OUT_OF_BOUNDS in out of bound tiles. ld a, [bc] ; connected map group inc a jr nz, .valid ; it's ok if there's any connected map in this direction ld a, [de] ; map dimension size (in metatiles), or 0 if moving up/left add a and a jr z, .ok dec a ; if e.g. size is 0x10, limit is at coord=0x1f .ok cp [hl] ; player coord (in half-metatiles) jr nz, .valid ; not valid: set wTile* to $ff pop af ld hl, wTileDown ld c, a ld b, 0 add hl, bc ld [hl], $ff ret .valid pop af ret .CheckTile: ; Tiles such as waterfalls and warps move the player ; in a given direction, overriding input. ld a, [wPlayerTileCollision] ld c, a call CheckWhirlpoolTile jr c, .not_whirlpool ld a, PLAYERMOVEMENT_FORCE_TURN scf ret .not_whirlpool and $f0 cp HI_NYBBLE_CURRENT jr z, .water cp HI_NYBBLE_WALK jr z, .land1 cp HI_NYBBLE_WALK_ALT jr z, .land2 cp HI_NYBBLE_WARPS jr z, .warps jr .no_walk .water ld a, c maskbits NUM_DIRECTIONS ld c, a ld b, 0 ld hl, .water_table add hl, bc ld a, [hl] ld [wWalkingDirection], a jr .continue_fast_slide .water_table db RIGHT ; COLL_WATERFALL_RIGHT db LEFT ; COLL_WATERFALL_LEFT db UP ; COLL_WATERFALL_UP db DOWN ; COLL_WATERFALL .land1 ld a, c and 7 ld c, a ld b, 0 ld hl, .land1_table add hl, bc ld a, [hl] cp STANDING jr z, .no_walk ld [wWalkingDirection], a jr .continue_walk .land1_table db STANDING ; COLL_BRAKE db RIGHT ; COLL_WALK_RIGHT db LEFT ; COLL_WALK_LEFT db UP ; COLL_WALK_UP db DOWN ; COLL_WALK_DOWN db STANDING ; COLL_BRAKE_45 db STANDING ; COLL_BRAKE_46 db STANDING ; COLL_BRAKE_47 .land2 ld a, c and 7 ld c, a ld b, 0 ld hl, .land2_table add hl, bc ld a, [hl] cp STANDING jr z, .no_walk ld [wWalkingDirection], a jr .continue_walk .land2_table db RIGHT ; COLL_WALK_RIGHT_ALT db LEFT ; COLL_WALK_LEFT_ALT db UP ; COLL_WALK_UP_ALT db DOWN ; COLL_WALK_DOWN_ALT db STANDING ; COLL_BRAKE_ALT db STANDING ; COLL_BRAKE_55 db STANDING ; COLL_BRAKE_56 db STANDING ; COLL_BRAKE_57 .warps ld a, c cp COLL_DOOR jr z, .down cp COLL_DOOR_79 jr z, .down cp COLL_STAIRCASE jr z, .down cp COLL_CAVE jr nz, .no_walk .down ld a, DOWN ld [wWalkingDirection], a jr .continue_walk .no_walk xor a ret .continue_fast_slide ld a, STEP_ICE jr .continue_step .continue_walk ld a, STEP_WALK ; fallthrough .continue_step call .DoStep ld a, PLAYERMOVEMENT_CONTINUE scf ret .CheckTurning: ; If the player is turning, change direction first. This also lets ; the player change facing without moving by tapping a direction. ld a, [wPlayerTurningDirection] cp 0 jr nz, .not_turning ld a, [wWalkingDirection] cp STANDING jr z, .not_turning ld e, a ld a, [wPlayerDirection] rrca rrca maskbits NUM_DIRECTIONS cp e jr z, .not_turning ld a, STEP_TURN call .DoStep ld a, PLAYERMOVEMENT_TURN scf ret .not_turning xor a ret .TryStep: ; Surfing actually calls .TrySurf directly instead of passing through here. ld a, [wPlayerState] cp PLAYER_SURF jr z, .TrySurf cp PLAYER_SURF_PIKA jr z, .TrySurf call .CheckLandPerms jr c, .bump call .CheckNPC and a jr z, .bump cp 2 jr z, .bump ld a, [wPlayerTileCollision] call CheckIceTile jr nc, .ice ; Downhill riding is slower when not moving down. call .BikeCheck jr nz, .walk ld hl, wBikeFlags bit BIKEFLAGS_DOWNHILL_F, [hl] jr z, .fast ld a, [wWalkingDirection] cp DOWN jr z, .fast ld a, STEP_WALK call .DoStep scf ret .fast ld a, STEP_BIKE call .DoStep scf ret .walk ld a, STEP_WALK call .DoStep scf ret .ice ld a, STEP_ICE call .DoStep scf ret .bump xor a ret .TrySurf: call .CheckSurfPerms ld [wWalkingIntoLand], a jr c, .surf_bump call .CheckNPC ld [wWalkingIntoNPC], a and a jr z, .surf_bump cp 2 jr z, .surf_bump ld a, [wWalkingIntoLand] and a jr nz, .ExitWater ld a, STEP_WALK call .DoStep scf ret .ExitWater: call .GetOutOfWater call PlayMapMusic ld a, STEP_WALK call .DoStep ld a, PLAYERMOVEMENT_EXIT_WATER scf ret .surf_bump xor a ret .TryJump: ld a, [wPlayerTileCollision] ld e, a and $f0 cp HI_NYBBLE_LEDGES jr nz, .DontJump ld a, e and 7 ld e, a ld d, 0 ld hl, .ledge_table add hl, de ld a, [wFacingDirection] and [hl] jr z, .DontJump ld de, SFX_JUMP_OVER_LEDGE call PlaySFX ld a, STEP_LEDGE call .DoStep ld a, PLAYERMOVEMENT_JUMP scf ret .DontJump: xor a ret .ledge_table db FACE_RIGHT ; COLL_HOP_RIGHT db FACE_LEFT ; COLL_HOP_LEFT db FACE_UP ; COLL_HOP_UP db FACE_DOWN ; COLL_HOP_DOWN db FACE_RIGHT | FACE_DOWN ; COLL_HOP_DOWN_RIGHT db FACE_DOWN | FACE_LEFT ; COLL_HOP_DOWN_LEFT db FACE_UP | FACE_RIGHT ; COLL_HOP_UP_RIGHT db FACE_UP | FACE_LEFT ; COLL_HOP_UP_LEFT .CheckWarp: ld a, [wWalkingDirection] cp STANDING jr z, .not_warp ld e, a ld d, 0 ld hl, .EdgeWarps add hl, de ld a, [wPlayerTileCollision] cp [hl] jr nz, .not_warp ld a, TRUE ld [wWalkingIntoEdgeWarp], a ld a, [wWalkingDirection] ld e, a ld a, [wPlayerDirection] rrca rrca maskbits NUM_DIRECTIONS cp e jr nz, .not_warp call WarpCheck jr nc, .not_warp call .StandInPlace scf ld a, PLAYERMOVEMENT_WARP ret .not_warp xor a ; PLAYERMOVEMENT_NORMAL ret .EdgeWarps: db COLL_WARP_CARPET_DOWN db COLL_WARP_CARPET_UP db COLL_WARP_CARPET_LEFT db COLL_WARP_CARPET_RIGHT .DoStep: ld e, a ld d, 0 ld hl, .Steps add hl, de add hl, de ld a, [hli] ld h, [hl] ld l, a ld a, [wWalkingDirection] ld e, a cp STANDING jp z, .StandInPlace add hl, de ld a, [hl] ld [wMovementAnimation], a ld hl, .FinishFacing add hl, de ld a, [hl] ld [wPlayerTurningDirection], a ld a, PLAYERMOVEMENT_FINISH ret .Steps: ; entries correspond to STEP_* constants (see constants/map_object_constants.asm) table_width 2, DoPlayerMovement.Steps dw .SlowStep dw .NormalStep dw .FastStep dw .JumpStep dw .SlideStep dw .TurningStep dw .BackJumpStep dw .FinishFacing assert_table_length NUM_STEPS .SlowStep: slow_step DOWN slow_step UP slow_step LEFT slow_step RIGHT .NormalStep: step DOWN step UP step LEFT step RIGHT .FastStep: big_step DOWN big_step UP big_step LEFT big_step RIGHT .JumpStep: jump_step DOWN jump_step UP jump_step LEFT jump_step RIGHT .SlideStep: fast_slide_step DOWN fast_slide_step UP fast_slide_step LEFT fast_slide_step RIGHT .BackJumpStep: jump_step UP jump_step DOWN jump_step RIGHT jump_step LEFT .TurningStep: turn_step DOWN turn_step UP turn_step LEFT turn_step RIGHT .FinishFacing: db $80 | DOWN db $80 | UP db $80 | LEFT db $80 | RIGHT .StandInPlace: ld a, 0 ld [wPlayerTurningDirection], a ld a, movement_step_sleep ld [wMovementAnimation], a xor a ret ._WalkInPlace: ld a, 0 ld [wPlayerTurningDirection], a ld a, movement_step_bump ld [wMovementAnimation], a xor a ret .CheckForced: ; When sliding on ice, input is forced to remain in the same direction. call CheckStandingOnIce ret nc ld a, [wPlayerTurningDirection] cp 0 ret z maskbits NUM_DIRECTIONS ld e, a ld d, 0 ld hl, .forced_dpad add hl, de ld a, [wCurInput] and BUTTONS or [hl] ld [wCurInput], a ret .forced_dpad db D_DOWN, D_UP, D_LEFT, D_RIGHT .GetAction: ; Poll player input and update movement info. ld hl, .action_table ld de, .action_table_1_end - .action_table_1 ld a, [wCurInput] bit D_DOWN_F, a jr nz, .d_down bit D_UP_F, a jr nz, .d_up bit D_LEFT_F, a jr nz, .d_left bit D_RIGHT_F, a jr nz, .d_right ; Standing jr .update .d_down add hl, de .d_up add hl, de .d_left add hl, de .d_right add hl, de .update ld a, [hli] ld [wWalkingDirection], a ld a, [hli] ld [wFacingDirection], a ld a, [hli] ld [wWalkingX], a ld a, [hli] ld [wWalkingY], a ld a, [hli] ld h, [hl] ld l, a ld a, [hl] ld [wWalkingTileCollision], a ret MACRO player_action ; walk direction, facing, x movement, y movement, tile collision pointer db \1, \2, \3, \4 dw \5 ENDM .action_table: .action_table_1 player_action STANDING, FACE_CURRENT, 0, 0, wPlayerTileCollision .action_table_1_end player_action RIGHT, FACE_RIGHT, 1, 0, wTileRight player_action LEFT, FACE_LEFT, -1, 0, wTileLeft player_action UP, FACE_UP, 0, -1, wTileUp player_action DOWN, FACE_DOWN, 0, 1, wTileDown .CheckNPC: ; Returns 0 if there is an NPC in front that you can't move ; Returns 1 if there is no NPC in front ; Returns 2 if there is a movable NPC in front. The game actually treats ; this the same as an NPC in front (bump). ld a, 0 ldh [hMapObjectIndex], a ; Load the next X coordinate into d ld a, [wPlayerMapX] ld d, a ld a, [wWalkingX] add d ld d, a ; Load the next Y coordinate into e ld a, [wPlayerMapY] ld e, a ld a, [wWalkingY] add e ld e, a ; Find an object struct with coordinates equal to d,e ld bc, wObjectStructs ; redundant farcall IsNPCAtCoord jr nc, .no_npc call .CheckStrengthBoulder jr c, .no_bump xor a ; bump ret .no_npc ld a, 1 ret .no_bump ld a, 2 ret .CheckStrengthBoulder: ld hl, wBikeFlags bit BIKEFLAGS_STRENGTH_ACTIVE_F, [hl] jr z, .not_boulder ld hl, OBJECT_WALKING add hl, bc ld a, [hl] cp STANDING jr nz, .not_boulder ld hl, OBJECT_PALETTE add hl, bc bit STRENGTH_BOULDER_F, [hl] jr z, .not_boulder ld hl, OBJECT_FLAGS2 add hl, bc set 2, [hl] ld a, [wWalkingDirection] ld d, a ld hl, OBJECT_RANGE add hl, bc ld a, [hl] and %11111100 or d ld [hl], a scf ret .not_boulder xor a ret .CheckLandPerms: ; Return 0 if walking onto land and tile permissions allow it. ; Otherwise, return carry. ld a, [wTilePermissions] ld d, a ld a, [wFacingDirection] and d jr nz, .NotWalkable ld a, [wWalkingTileCollision] call .CheckWalkable jr c, .NotWalkable xor a ret .NotWalkable: scf ret .CheckSurfPerms: ; Return 0 if moving in water, or 1 if moving onto land. ; Otherwise, return carry. ld a, [wTilePermissions] ld d, a ld a, [wFacingDirection] and d jr nz, .NotSurfable ld a, [wWalkingTileCollision] call .CheckSurfable jr c, .NotSurfable and a ret .NotSurfable: scf ret .BikeCheck: ld a, [wPlayerState] cp PLAYER_BIKE ret z cp PLAYER_SKATE ret .CheckWalkable: ; Return 0 if tile a is land. Otherwise, return carry. call GetTilePermission and a ; LAND_TILE ret z cp SPACE_TILE ret z scf ret .CheckSurfable: ; Return 0 if tile a is water, or 1 if land. ; Otherwise, return carry. call GetTilePermission cp WATER_TILE jr z, .Water ; because this is called during PLAYER_SURF or PLAYER_SURF_PIKA state, ; SPACE_TILE is considered still water. Do not stop surfing on a space tile. cp SPACE_TILE jr z, .Water ; Can walk back onto land from water. and a ; LAND_TILE jr z, .Land jr .Neither .Water: xor a ret .Land: ld a, 1 and a ret .Neither: scf ret .GetOutOfWater: push bc ld a, PLAYER_NORMAL ld [wPlayerState], a call UpdatePlayerSprite ; UpdateSprites pop bc ret CheckStandingOnIce:: ld a, [wPlayerTurningDirection] cp 0 jr z, .not_ice cp $f0 jr z, .not_ice ld a, [wPlayerTileCollision] call CheckIceTile jr nc, .yep ld a, [wPlayerState] cp PLAYER_SKATE jr nz, .not_ice .yep scf ret .not_ice and a ret StopPlayerForEvent:: ld hl, wPlayerNextMovement ld a, movement_step_sleep cp [hl] ret z ld [hl], a ld a, 0 ld [wPlayerTurningDirection], a ret