/** * @file npc5.c * @ingroup NPCs * * @brief Unused Zelda follower NPC. */ #include "collision.h" #include "functions.h" #include "hitbox.h" #include "message.h" #include "npc.h" #include "tiles.h" #define kFollowDistance 32 // distance to follow player #define kPoiDistance 4 // point of interest distance #define kGravity Q_8_8(32.0) #define kCloseDistance 48 // distance to player to walk slowly #define kFarDistance 80 // distance to player to walk fast #define kCloseSpeed 0x120 // speed when close to player #define kMidSpeed 0x160 // speed when mid distance from player #define kFarSpeed 0x220 // speed when far from player #define kNavigateSpeed 0x1e0 // speed when navigating #define FLAG_FLINCHING 0x1 #define FLAG_GOTO_PLAYER 0x2 #define FLAG_GOTO_JUMPED 0x4 #define FLAG_NAVIGATE 0x8 typedef enum { ZELDA_STATE_INIT, ZELDA_STATE_IDLE, ZELDA_STATE_FOLLOW, ZELDA_STATE_LOST, ZELDA_STATE_ANIM_SCRIPTED, ZELDA_STATE_WALK_PRE_JUMP, ZELDA_STATE_JUMP, ZELDA_STATE_LAND, } ZeldaState; #define HEAP ((ZeldaData*)super->myHeap) typedef struct { /*0x00*/ Entity base; /*0x68*/ u16* messageData; /*0x6c*/ u8 baseAnimation; /*0x6d*/ u8 unused1; /*0x6e*/ u16 linear_move_dist; /*0x70*/ u8 unused2[4]; /*0x74*/ u16 currentRoom; /*0x76*/ u8 unused3[2]; /*0x78*/ Entity* interactEntity; } NPC5Entity; typedef struct { u8 flags; // u8 u8 followDistance; // u8 u16 playerX; // u16 u16 playerY; // u16 u16 destX; u16 destY; u16 playerJumpedX; // u16 u16 playerJumpedY; // u16 u16 navX; // u16 u16 navY; // u16 } ZeldaData; void ZeldaSetAnim(NPC5Entity*, u32); u32 CheckIsFlinching(NPC5Entity*); u32 ZeldaAtDestination(Entity*); void ZeldaUpdateIdleAnim(NPC5Entity*); void ZeldaCalcWalkSpeed(NPC5Entity*, u32, u32); bool32 CheckDirectPathUnblocked(Entity*, u32, u32); void ZeldaInitNavigate(NPC5Entity*, u32, u32); void ZeldaCalcWalkAnim(NPC5Entity*, u32, u32, u32); bool32 ZeldaProcessMovement(NPC5Entity*); void ZeldaDoLostAnim(NPC5Entity*); void ZeldaUpdateAnim(Entity*); void ZeldaType0Init(NPC5Entity*); void ZeldaType1Init(NPC5Entity*); void ZeldaType2Init(NPC5Entity*); void ZeldaType3Init(NPC5Entity*); void ZeldaInitAction(NPC5Entity*); void ZeldaIdleAction(NPC5Entity*); void ZeldaFollowAction(NPC5Entity*); void ZeldaLostAction(NPC5Entity*); void ZeldaAnimScripted(NPC5Entity*); void ZeldaWalkPreJump(NPC5Entity*); void ZeldaJumpAction(NPC5Entity*); void ZeldaLandAction(NPC5Entity*); void sub_08061ACC(NPC5Entity*); void sub_08061B18(NPC5Entity*); u32 PointInsideRadius(s32, s32, s32); u32 CalcJumpDirection(Entity*); extern u32 sub_08079FD4(Entity*, u32); extern void UpdateCollisionLayer(Entity*); bool32 TryNavRightFromAbove(NPC5Entity*, s32, s32, s32); bool32 TryNavUpFromRight(NPC5Entity*, s32, s32, s32); bool32 TryNavLeftFromAbove(NPC5Entity*, s32, s32, s32); bool32 TryNavBelowFromRight(NPC5Entity*, s32, s32, s32); bool32 TryNavLeftFromBelow(NPC5Entity*, s32, s32, s32); bool32 TryNavBelowFromLeft(NPC5Entity*, s32, s32, s32); bool32 TryNavRightFromBelow(NPC5Entity*, s32, s32, s32); bool32 TryNavUpFromLeft(NPC5Entity*, s32, s32, s32); bool32 CheckPathRight(u8*, s32, s32, s32); bool32 CheckPathLeft(u8*, s32, s32, s32); bool32 CheckPathUp(u8*, s32, s32, s32); bool32 CheckPathBelow(u8*, s32, s32, s32); void sub_08061AFC(NPC5Entity*); extern u16* gZeldaFollowerText[8]; void CreateZeldaFollower(void) { Entity* npc; if (CheckGlobalFlag(ZELDA_CHASE) != 0) { npc = CreateNPC(ZELDA_FOLLOWER, 0, 0); if (npc != NULL) { CopyPosition(&gPlayerEntity.base, npc); npc->flags |= ENT_PERSIST; npc->animationState = GetAnimationState(npc); } } } // UNUSED zelda follower, probably because it was too resource heavy void NPC5(NPC5Entity* this) { static void (*const gUnk_0810AC1C[])(NPC5Entity*) = { ZeldaType0Init, ZeldaType1Init, ZeldaType2Init, ZeldaType3Init, }; gUnk_0810AC1C[super->type](this); } void ZeldaType0Init(NPC5Entity* this) { static void (*const Npc5_Actions[])(NPC5Entity*) = { ZeldaInitAction, ZeldaIdleAction, ZeldaFollowAction, ZeldaLostAction, ZeldaAnimScripted, ZeldaWalkPreJump, ZeldaJumpAction, ZeldaLandAction, }; u32 tmp; if (gPlayerState.jump_status & 0x80) { if (super->action != ZELDA_STATE_INIT) { if ((HEAP->flags & FLAG_GOTO_JUMPED) == 0) { HEAP->flags |= FLAG_GOTO_JUMPED; HEAP->playerJumpedX = (gPlayerEntity.base.x.HALF.HI & 0xfff0) + 8; HEAP->playerJumpedY = (gPlayerEntity.base.y.HALF.HI & 0xfff0) + 8; } } } if ((super->action == ZELDA_STATE_INIT) || (super->spriteSettings.draw != 0)) { Npc5_Actions[super->action](this); } if (super->action != ZELDA_STATE_INIT) { HEAP->playerX = gPlayerEntity.base.x.HALF.HI; HEAP->playerY = gPlayerEntity.base.y.HALF.HI; } if (this->currentRoom != gRoomControls.room) { this->currentRoom = gRoomControls.room; CopyPosition(&gPlayerEntity.base, super); super->action = ZELDA_STATE_IDLE; super->spriteSettings.draw = 1; super->speed = kCloseSpeed; tmp = gRoomControls.scroll_direction; super->animationState = tmp * 2; InitAnimationForceUpdate(super, tmp << 0x19 >> 0x19); // TODO some conversion between u8 and u32? super->frameDuration = (Random() & 0x7f) + 0x80; HEAP->flags &= ~FLAG_GOTO_JUMPED; } } void ZeldaInitAction(NPC5Entity* this) { ZeldaData* heapObj; Entity* otherNpc; heapObj = (ZeldaData*)zMalloc(sizeof(ZeldaData)); if (heapObj != NULL) { super->myHeap = (u32*)heapObj; heapObj->followDistance = kFollowDistance; super->action = ZELDA_STATE_IDLE; COLLISION_ON(super); super->animationState &= 3; super->collisionFlags = 7; super->hurtType = 0x48; super->hitType = 0x49; super->collisionMask = 3; super->hitbox = (Hitbox*)&gHitbox_0; super->followerFlag &= ~1; this->baseAnimation = 0xff; ZeldaSetAnim(this, super->animationState); otherNpc = CreateNPC(NPC_UNK_5, 2, 0); if (otherNpc != NULL) { otherNpc->parent = super; this->interactEntity = otherNpc; } } } void ZeldaIdleAction(NPC5Entity* this) { if (CheckIsFlinching(this)) { return; } if (!ZeldaAtDestination(super) && DirectionNormalize(GetFacingDirection(super, &gPlayerEntity.base) + (super->animationState * -4) + 4) < 9) { super->action = ZELDA_STATE_FOLLOW; super->subtimer = 0; return; } ZeldaUpdateIdleAnim(this); } void ZeldaFollowAction(NPC5Entity* this) { Entity* r5; //! @bug: r5 is uninitialized if (CheckIsFlinching(this)) { return; } if (HEAP->flags & FLAG_GOTO_JUMPED) { // goto position where player jumped if (HEAP->flags & FLAG_NAVIGATE) { // navigate to jump position super->speed = kNavigateSpeed; ZeldaCalcWalkAnim(this, HEAP->navX, HEAP->navY, 0xc); ZeldaProcessMovement(this); if (EntityWithinDistance(super, HEAP->navX, HEAP->navY, kPoiDistance)) { // reached navigation position HEAP->flags &= ~FLAG_NAVIGATE; } } else if (CheckDirectPathUnblocked(super, HEAP->playerJumpedX, HEAP->playerJumpedY)) { // At jump location, begin jumping if (EntityWithinDistance(super, HEAP->playerJumpedX, HEAP->playerJumpedY, kPoiDistance)) { HEAP->flags &= ~FLAG_GOTO_JUMPED; super->action = ZELDA_STATE_WALK_PRE_JUMP; super->direction = r5->direction; super->speed = kMidSpeed; ZeldaSetAnim(this, 8); } else { // walk to jump location super->speed = kNavigateSpeed; ZeldaCalcWalkAnim(this, r5->x.HALF.HI, r5->y.HALF.HI, 0xc); ZeldaProcessMovement(this); } } else { // navigate to jump location (bugged) ZeldaInitNavigate(this, r5->x.HALF.HI, r5->y.HALF.HI); } } else if (CheckDirectPathUnblocked(super, gPlayerEntity.base.x.HALF.HI, gPlayerEntity.base.y.HALF.HI)) { // walk directly to player ZeldaCalcWalkSpeed(this, gPlayerEntity.base.x.HALF.HI, gPlayerEntity.base.y.HALF.HI); ZeldaProcessMovement(this); HEAP->flags &= ~(FLAG_NAVIGATE | FLAG_GOTO_PLAYER); } else if (HEAP->flags & FLAG_NAVIGATE) { // navigating to a position super->speed = kNavigateSpeed; ZeldaCalcWalkAnim(this, HEAP->navX, HEAP->navY, 0xc); ZeldaProcessMovement(this); if (EntityWithinDistance(super, HEAP->navX, HEAP->navY, kPoiDistance)) { // reached navigation position HEAP->flags &= ~FLAG_NAVIGATE; } } else { // player not found and no position set to navigate to if ((HEAP->flags & FLAG_GOTO_PLAYER) == 0) { // get player position HEAP->flags |= FLAG_GOTO_PLAYER; HEAP->destX = HEAP->playerX; HEAP->destY = HEAP->playerY; } if (CheckDirectPathUnblocked(super, HEAP->destX, HEAP->destY)) { // can walk directly to player super->speed = kNavigateSpeed; ZeldaCalcWalkAnim(this, HEAP->destX, HEAP->destY, 0xc); ZeldaProcessMovement(this); if (EntityWithinDistance(super, HEAP->destX, HEAP->destY, kPoiDistance)) { // reached player position HEAP->flags &= ~FLAG_GOTO_PLAYER; } } else { // try to navigate to player HEAP->flags &= ~FLAG_GOTO_PLAYER; ZeldaInitNavigate(this, gPlayerEntity.base.x.HALF.HI, gPlayerEntity.base.y.HALF.HI); } } if (ZeldaAtDestination(super)) { super->action = ZELDA_STATE_IDLE; HEAP->flags &= ~FLAG_GOTO_JUMPED; ZeldaSetAnim(this, 0); } } void ZeldaLostAction(NPC5Entity* this) { ZeldaDoLostAnim(this); // wait to be found if (!ZeldaAtDestination(super)) { return; } if ((u32)super->animIndex - 0x20 < 0x10) { if ((super->frame & 7) != 0) { super->frameDuration = 1; UpdateAnimationSingleFrame(super); } super->animationState = super->frame & 0x18; this->baseAnimation = 0xff; } super->action = ZELDA_STATE_IDLE; ZeldaSetAnim(this, 0); } void ZeldaAnimScripted(NPC5Entity* this) { UpdateAnimationSingleFrame(super); if (super->frame & ANIM_DONE) { super->action = ZELDA_STATE_IDLE; ZeldaSetAnim(this, 0); } } void ZeldaWalkPreJump(NPC5Entity* this) { ZeldaProcessMovement(this); } void ZeldaJumpAction(NPC5Entity* this) { LinearMoveUpdate(super); ZeldaUpdateAnim(super); if (GravityUpdate(super, kGravity) == 0) { super->action = ZELDA_STATE_LAND; super->collisionLayer = 1; UpdateSpriteForCollisionLayer(super); ZeldaSetAnim(this, 0x1c); } } void ZeldaLandAction(NPC5Entity* this) { UpdateAnimationSingleFrame(super); if (super->frame & ANIM_DONE) { super->action = ZELDA_STATE_FOLLOW; super->animationState = DirectionToAnimationState(GetFacingDirection(super, &gPlayerEntity.base)) * 2; ZeldaSetAnim(this, 8); } } void ZeldaSetAnim(NPC5Entity* this, u32 param) { u32 tmp = param + super->animationState / 2; if (tmp != super->animIndex) { this->baseAnimation = param; InitAnimationForceUpdate(super, tmp); } } void ZeldaUpdateAnim(Entity* this) { if (((*(u32*)&this->animIndex & 0x80ff00) == 0x800100) && (this->animIndex < 4)) { InitAnimationForceUpdate(this, (this->animationState >> 1)); this->frameDuration = (Random() & 0x7f) + 0x80; } else { UpdateAnimationSingleFrame(this); } } void ZeldaUpdateIdleAnim(NPC5Entity* this) { s32 tmp; if (((u32)super->animIndex - 0x20 < 0x10) && ((super->frame & ANIM_DONE) == 0)) { UpdateAnimationSingleFrame(super); return; } tmp = GetFacingDirection(super, &gPlayerEntity.base) + super->animationState * -4; if (((tmp + 3) & 0x1f) > 6) { if ((tmp & 0x1f) < 0x10) { InitAnimationForceUpdate(super, super->animationState + 0x20); super->animationState = (super->animationState + 1) & 7; } else { InitAnimationForceUpdate(super, super->animationState + 0x28); super->animationState = (super->animationState - 1) & 7; } return; } if ((super->animationState & 1) == 0) { if ((super->frame & ANIM_DONE) && (0xf >= (u32)super->animIndex - 0x20)) { ZeldaSetAnim(this, 0); } else { ZeldaUpdateAnim(super); } } } u32 ZeldaAtDestination(Entity* this) { if (CheckDirectPathUnblocked(this, (s32)gPlayerEntity.base.x.HALF.HI, (s32)gPlayerEntity.base.y.HALF.HI) == 0) { return 0; } ((ZeldaData*)this->myHeap)->flags &= ~FLAG_GOTO_JUMPED; if (PointInsideRadius(gPlayerEntity.base.x.HALF.HI - this->x.HALF.HI, gPlayerEntity.base.y.HALF.HI - this->y.HALF.HI, ((ZeldaData*)this->myHeap)->followDistance)) { return 1; } return 0; } bool32 CheckDirectPathUnblocked(Entity* this, u32 target_x, u32 target_y) { s32 dx; s32 dy; int angle; int x; int y; u8* layer; const int col_check_length = 6; x = this->x.HALF.HI; y = this->y.HALF.HI; angle = CalculateDirectionFromOffsets(target_x - x, target_y - y); x <<= 8; y <<= 8; // get vector to target dx = gSineTable[angle] * col_check_length; dy = gSineTable[(angle + 0x40)] * col_check_length; if (this->collisionLayer != 2) { layer = gMapBottom.collisionData; } else { layer = gMapTop.collisionData; } while (1) { if (IsTileCollision(layer, x / 0x100, y / 0x100, col_check_length)) { return 0; } if (((target_x - (x / 0x100)) + col_check_length > col_check_length * 2) || ((target_y - (y / 0x100)) + col_check_length > col_check_length * 2)) { x += dx; y -= dy; continue; } return 1; } } void ZeldaCalcWalkSpeed(NPC5Entity* this, u32 a, u32 b) { s32 xDist; s32 yDist; s32 sqrDist; u32 tmp; xDist = gPlayerEntity.base.x.HALF.HI - super->x.HALF.HI; yDist = gPlayerEntity.base.y.HALF.HI - super->y.HALF.HI; sqrDist = (xDist * xDist) + (yDist * yDist); if (sqrDist < kCloseDistance * kCloseDistance) { super->speed = kCloseSpeed; } else { if (sqrDist < kFarDistance * kFarDistance) { super->speed = ((sqrDist - (kCloseDistance * kCloseDistance)) >> 4) + kCloseSpeed; } else { super->speed = kFarSpeed; } } if (super->speed == kCloseSpeed) { tmp = 4; } else if (super->speed < kMidSpeed) { tmp = 8; } else { tmp = 0xc; } ZeldaCalcWalkAnim(this, a, b, tmp); } void ZeldaCalcWalkAnim(NPC5Entity* this, u32 target_x, u32 target_y, u32 anim) { super->direction = CalculateDirectionTo(super->x.HALF.HI, super->y.HALF.HI, target_x, target_y); if ((anim != this->baseAnimation) || (10 < ((super->direction + super->animationState * -4 + 5) & 0x1f))) { super->animationState = Direction8ToAnimationState(DirectionRoundUp(super->direction)); ZeldaSetAnim(this, anim); } } bool32 ZeldaProcessMovement(NPC5Entity* this) { u32 direction; u32 tmp; UpdateAnimationSingleFrame(super); if (ProcessMovement6(super) == 0) { direction = CalcJumpDirection(super); if (direction != 0xff) { super->action = ZELDA_STATE_JUMP; tmp = (sub_08079FD4(super, 1)); tmp <<= 4; tmp -= 4; tmp = tmp << 0xc; super->zVelocity = tmp; super->speed = 0x100; super->direction = direction; super->animationState = direction >> 2; if (tmp >> 0x10 != 0) { ZeldaSetAnim(this, 0x14); } else { ZeldaSetAnim(this, 0x18); } } return FALSE; } else { UpdateCollisionLayer(super); return TRUE; } } // TODO: this relies on tiles 0x2a - 0x2d, do these exist in the game? u32 CalcJumpDirection(Entity* this) { static const struct { s8 unk_0; s8 unk_1; } PACKED sOffsets[] = { { 0, -8 }, { 8, 0 }, { 0, 3 }, { -8, 0 }, }; static const u8 sTable[] = { // actTile, animationState ACT_TILE_43, DirectionSouth, ACT_TILE_42, DirectionNorth, ACT_TILE_45, DirectionEast, ACT_TILE_44, DirectionWest, 0x0, }; u32 actTile; u32 x; s32 x_offset; s32 y_offset; s8* ptr; const u8* ptr2; x = AnimationStateIdle(this->animationState); ptr = (s8*)sOffsets; x_offset = ptr[x]; y_offset = ptr[x + 1]; actTile = GetActTileRelativeToEntity(this, x_offset, y_offset); ptr2 = sTable; do { if (*ptr2 != actTile || this->animationState != (ptr2[1] >> 2)) { continue; } ++this->subtimer; if (this->subtimer < 8) { return 0xff; } return ptr2[1]; } while (ptr2 += 2, *ptr2 != 0); this->subtimer = 0; return 0xff; } u32 CheckIsFlinching(NPC5Entity* this) { if ((HEAP->flags & FLAG_FLINCHING) == 0) { if (super->contactFlags & CONTACT_NOW) { switch (super->contactFlags & 0x7f) { case 0: case 1: case 2: case 3: case 0xf: case 0x13: case 0x1b: case 0x1e: case 0x1f: break; default: HEAP->flags |= FLAG_FLINCHING; InitAnimationForceUpdate(super, (super->animationState >> 1) + 0x40); return 1; } } } else { UpdateAnimationSingleFrame(super); if ((super->frame & ANIM_DONE) == 0) { return 1; } HEAP->flags &= ~FLAG_FLINCHING; InitAnimationForceUpdate(super, this->baseAnimation + (super->animationState >> 1)); } super->contactFlags &= 0x7f; if (super->iframes != 0) { super->iframes++; } return 0; } void ZeldaDoLostAnim(NPC5Entity* this) { static const u8 gUnk_0810AC5D[] = { 0x30, 0x31, 0x38, 0x39, 0x32, 0x33, 0x3a, 0x3b, 0x34, 0x35, 0x3c, 0x3d, 0x36, 0x37, 0x3e, 0x3f, 0x0, 0x0, 0x0, }; u32 uVar2; u32 bVar4; switch (super->subAction) { case 0: UpdateAnimationSingleFrame(super); if ((super->frame & ANIM_DONE) == 0) { return; } super->subAction = 1; super->timer = 15; ZeldaSetAnim(this, 0); break; case 1: super->timer--; if (super->timer != 0) { return; } uVar2 = Random(); bVar4 = uVar2; if ((uVar2 & 1) == 0) { super->subAction = 3; super->timer = (bVar4 & 0x18) + 30; ZeldaSetAnim(this, 4); return; } super->subAction = 2; InitAnimationForceUpdate(super, gUnk_0810AC5D[(u32)super->animationState * 2 + ((uVar2 >> 4) & 3)]); break; case 2: UpdateAnimationSingleFrame(super); if ((super->frame & ANIM_DONE) == 0) { return; } super->animationState = ((super->frame & 0x18) >> 2); if ((Random() & 1)) { super->subAction = 3; super->timer = (bVar4 & 0x18) + 30; ZeldaSetAnim(this, 4); return; } super->subAction = 0; ZeldaSetAnim(this, 0x10); break; case 3: if (ZeldaProcessMovement(this) == 0) { super->subAction = 2; //! @bug bVar4 (r6) is uninitialized. InitAnimationForceUpdate(super, gUnk_0810AC5D[super->animationState * 2 + (bVar4 >> 4 & 3)]); return; } if (--super->timer != 0) { return; } super->subAction = 0; ZeldaSetAnim(this, 0x10); break; } } void ZeldaInitNavigate(NPC5Entity* this, u32 tgt_x, u32 tgt_y) { s32 x; s32 y; x = super->x.HALF.HI; y = super->y.HALF.HI; switch (((CalculateDirectionTo(super->x.HALF.HI, super->y.HALF.HI, tgt_x, tgt_y) + 2) & 0x1c) >> 2) { case 0: this->linear_move_dist = tgt_y; if (super->x.HALF.HI > (s32)tgt_x) { TryNavRightFromAbove(this, x, y + -8, tgt_x); break; } TryNavLeftFromAbove(this, x, y + -8, tgt_x); break; case 1: this->linear_move_dist = tgt_x; if (TryNavUpFromRight(this, x + 8, y, tgt_y) != 0) break; this->linear_move_dist = tgt_y; TryNavLeftFromAbove(this, x, y + -8, tgt_x); break; case 2: this->linear_move_dist = tgt_x; if (super->y.HALF.HI > (s32)tgt_y) { TryNavUpFromRight(this, x + 8, y, tgt_y); } else { TryNavBelowFromRight(this, x + 8, y, tgt_y); } break; case 3: this->linear_move_dist = tgt_x; if (TryNavBelowFromRight(this, x + 8, y, tgt_y) != 0) break; this->linear_move_dist = tgt_y; TryNavLeftFromBelow(this, x, y + 8, tgt_x); break; case 4: this->linear_move_dist = tgt_y; if (super->x.HALF.HI > (s32)tgt_x) { TryNavRightFromBelow(this, x, y + 8, tgt_x); break; } TryNavLeftFromBelow(this, x, y + 8, tgt_x); break; case 5: this->linear_move_dist = tgt_x; if (TryNavBelowFromLeft(this, x + -8, y, tgt_y) != 0) break; this->linear_move_dist = tgt_y; TryNavRightFromBelow(this, x, y + 8, tgt_x); break; case 6: this->linear_move_dist = tgt_x; if (super->y.HALF.HI > (s32)tgt_y) { TryNavUpFromLeft(this, x + -8, y, tgt_y); } else { TryNavBelowFromLeft(this, x + -8, y, tgt_y); } break; case 7: this->linear_move_dist = tgt_x; if (TryNavUpFromLeft(this, x + -8, y, tgt_y) == 0) { this->linear_move_dist = tgt_y; TryNavRightFromAbove(this, x, y + -8, tgt_x); } } if ((HEAP->flags & FLAG_NAVIGATE) == 0) { super->action = ZELDA_STATE_LOST; super->subAction = 0; } } bool32 TryNavRightFromAbove(NPC5Entity* this, s32 x, s32 y, s32 param) { u32 param_y = y; u8* layer = (super->collisionLayer == 2) ? gMapTop.collisionData : gMapBottom.collisionData; while (!IsTileCollision(layer, x, y, 6)) { if (CheckPathRight(layer, x, y, param)) { HEAP->navX = x; HEAP->navY = param_y; HEAP->flags |= FLAG_NAVIGATE; if (this->linear_move_dist >= y) { return TRUE; } } y -= 8; } return FALSE; } bool32 TryNavLeftFromAbove(NPC5Entity* this, s32 x, s32 y, s32 param) { u32 param_y = y; u8* layer = (super->collisionLayer == 2) ? gMapTop.collisionData : gMapBottom.collisionData; while (!IsTileCollision(layer, x, y, 6)) { if (CheckPathLeft(layer, x, y, param)) { HEAP->navX = x; HEAP->navY = param_y; HEAP->flags |= FLAG_NAVIGATE; if (this->linear_move_dist >= y) { return TRUE; } } y -= 8; } return FALSE; } bool32 TryNavUpFromRight(NPC5Entity* this, s32 x, s32 y, s32 param) { u32 param_x = x; u8* layer = (super->collisionLayer == 2) ? gMapTop.collisionData : gMapBottom.collisionData; while (!IsTileCollision(layer, x, y, 6)) { if (CheckPathUp(layer, x, y, param)) { HEAP->navX = param_x; HEAP->navY = y; HEAP->flags |= FLAG_NAVIGATE; if (this->linear_move_dist <= x) { return TRUE; } } x += 8; } return FALSE; } bool32 TryNavBelowFromRight(NPC5Entity* this, s32 x, s32 y, s32 param) { u32 param_x = x; u8* layer = (super->collisionLayer == 2) ? gMapTop.collisionData : gMapBottom.collisionData; while (!IsTileCollision(layer, x, y, 6)) { if (CheckPathBelow(layer, x, y, param)) { HEAP->navX = param_x; HEAP->navY = y; HEAP->flags |= FLAG_NAVIGATE; if (this->linear_move_dist <= x) { return TRUE; } } x += 8; } return FALSE; } bool32 TryNavRightFromBelow(NPC5Entity* this, s32 x, s32 y, s32 param) { u32 param_y = y; u8* layer = (super->collisionLayer == 2) ? gMapTop.collisionData : gMapBottom.collisionData; while (!IsTileCollision(layer, x, y, 6)) { if (CheckPathRight(layer, x, y, param)) { HEAP->navX = x; HEAP->navY = param_y; HEAP->flags |= FLAG_NAVIGATE; if (this->linear_move_dist <= y) { return TRUE; } } y += 8; } return FALSE; } bool32 TryNavLeftFromBelow(NPC5Entity* this, s32 x, s32 y, s32 param) { u32 param_y = y; u8* layer = (super->collisionLayer == 2) ? gMapTop.collisionData : gMapBottom.collisionData; while (!IsTileCollision(layer, x, y, 6)) { if (CheckPathLeft(layer, x, y, param)) { HEAP->navX = x; HEAP->navY = param_y; HEAP->flags |= FLAG_NAVIGATE; if (this->linear_move_dist <= y) { return TRUE; } } y += 8; } return FALSE; } bool32 TryNavUpFromLeft(NPC5Entity* this, s32 x, s32 y, s32 param) { u32 param_x = x; u8* layer = (super->collisionLayer == 2) ? gMapTop.collisionData : gMapBottom.collisionData; while (!IsTileCollision(layer, x, y, 6)) { if (CheckPathUp(layer, x, y, param)) { HEAP->navX = param_x; HEAP->navY = y; HEAP->flags |= FLAG_NAVIGATE; if (this->linear_move_dist >= x) { return TRUE; } } x -= 8; } return FALSE; } bool32 TryNavBelowFromLeft(NPC5Entity* this, s32 x, s32 y, s32 param) { u32 param_x = x; u8* layer = (super->collisionLayer == 2) ? gMapTop.collisionData : gMapBottom.collisionData; while (!IsTileCollision(layer, x, y, 6)) { if (CheckPathBelow(layer, x, y, param)) { HEAP->navX = param_x; HEAP->navY = y; HEAP->flags |= FLAG_NAVIGATE; if (this->linear_move_dist >= x) { return TRUE; } } x -= 8; } return FALSE; } bool32 CheckPathUp(u8* layer, s32 x, s32 y, s32 param) { while (param <= y) { if (IsTileCollision(layer, x, y, 6) != 0) { return FALSE; } y -= 8; } return TRUE; } bool32 CheckPathLeft(u8* layer, s32 x, s32 y, s32 param) { while (param >= x) { if (IsTileCollision(layer, x, y, 6) != 0) { return FALSE; } x += 8; } return TRUE; } bool32 CheckPathBelow(u8* layer, s32 x, s32 y, s32 param) { while (param >= y) { if (IsTileCollision(layer, x, y, 6) != 0) { return FALSE; } y += 8; } return TRUE; } bool32 CheckPathRight(u8* layer, s32 x, s32 y, s32 param) { while (param <= x) { if (IsTileCollision(layer, x, y, 6) != 0) { return FALSE; } x -= 8; } return TRUE; } void ZeldaType1Init(NPC5Entity* this) { DeleteThisEntity(); } void ZeldaType2Init(NPC5Entity* this) { static void (*const gUnk_0810AC70[])(NPC5Entity*) = { sub_08061ACC, sub_08061B18, }; gUnk_0810AC70[super->action](this); CopyPosition(super->parent, super); } void sub_08061ACC(NPC5Entity* this) { super->flags |= ENT_PERSIST; super->action = 1; super->subAction = 0xff; super->timer = 0; super->followerFlag = super->followerFlag & ~1; AddInteractableWhenBigObject(super); sub_08061AFC(this); } void sub_08061AFC(NPC5Entity* this) { u32 tmp = 0; if (super->subAction != 0) { super->subAction = tmp; this->messageData = gZeldaFollowerText[0]; super->timer = 0; } } void sub_08061B18(NPC5Entity* this) { u16* puVar2; switch (super->interactType) { case INTERACTION_NONE: break; case INTERACTION_TALK: super->interactType = INTERACTION_NONE; sub_08061AFC(this); puVar2 = this->messageData; puVar2 += (super->timer++); if (puVar2[1] == 0) { super->timer = 0; } MessageNoOverlap(puVar2[0], super); break; } } void ZeldaType3Init(NPC5Entity* this) { if (super->action == 0) { super->action = 1; InitAnimationForceUpdate(super, 2); } if (gRoomTransition.entity_update_type == 2) { UpdateAnimationSingleFrame(super); } sub_0806FD3C(super); }