/* * File: z_en_tite.c * Overlay: ovl_En_Tite * Description: Tektite */ #include "z_en_tite.h" #include "overlays/actors/ovl_En_Encount1/z_en_encount1.h" #include "overlays/effects/ovl_Effect_Ss_Dead_Sound/z_eff_ss_dead_sound.h" #include "vt.h" #include "objects/object_tite/object_tite.h" #define FLAGS (ACTOR_FLAG_0 | ACTOR_FLAG_2 | ACTOR_FLAG_4) // EnTite_Idle #define vIdleTimer actionVar1 // EnTite_Attack (vQueuedJumps also used by EnTite_MoveTowardPlayer) #define vAttackState actionVar1 #define vQueuedJumps actionVar2 // EnTite_FlipOnBack #define vOnBackTimer actionVar1 #define vLegTwitchTimer actionVar2 typedef enum { /* 0x0 */ TEKTITE_DEATH_CRY, /* 0x1 */ TEKTITE_UNK_1, /* 0x2 */ TEKTITE_UNK_2, /* 0x3 */ TEKTITE_RECOIL, /* 0x4 */ TEKTITE_UNK_4, /* 0x5 */ TEKTITE_FALL_APART, /* 0x6 */ TEKTITE_IDLE, /* 0x7 */ TEKTITE_STUNNED, /* 0x8 */ TEKTITE_UNK_8, /* 0x9 */ TEKTITE_ATTACK, /* 0xA */ TEKTITE_TURN_TOWARD_PLAYER, /* 0xB */ TEKTITE_UNK9, /* 0xC */ TEKTITE_MOVE_TOWARD_PLAYER } EnTiteAction; typedef enum { /* 0x0 */ TEKTITE_BEGIN_LUNGE, /* 0x1 */ TEKTITE_MID_LUNGE, /* 0x2 */ TEKTITE_LANDED, /* 0x2 */ TEKTITE_SUBMERGED } EnTiteAttackState; typedef enum { /* 0x0 */ TEKTITE_INITIAL, /* 0x1 */ TEKTITE_UNFLIPPED, /* 0x2 */ TEKTITE_FLIPPED } EnTiteFlipState; void EnTite_Init(Actor* thisx, GlobalContext* globalCtx); void EnTite_Destroy(Actor* thisx, GlobalContext* globalCtx); void EnTite_Update(Actor* thisx, GlobalContext* globalCtx); void EnTite_Draw(Actor* thisx, GlobalContext* globalCtx); void EnTite_SetupIdle(EnTite* this); void EnTite_SetupTurnTowardPlayer(EnTite* this); void EnTite_SetupMoveTowardPlayer(EnTite* this); void EnTite_SetupDeathCry(EnTite* this); void EnTite_SetupFlipUpright(EnTite* this); void EnTite_Idle(EnTite* this, GlobalContext* globalCtx); void EnTite_Attack(EnTite* this, GlobalContext* globalCtx); void EnTite_TurnTowardPlayer(EnTite* this, GlobalContext* globalCtx); void EnTite_MoveTowardPlayer(EnTite* this, GlobalContext* globalCtx); void EnTite_Recoil(EnTite* this, GlobalContext* globalCtx); void EnTite_Stunned(EnTite* this, GlobalContext* globalCtx); void EnTite_DeathCry(EnTite* this, GlobalContext* globalCtx); void EnTite_FallApart(EnTite* this, GlobalContext* globalCtx); void EnTite_FlipOnBack(EnTite* this, GlobalContext* globalCtx); void EnTite_FlipUpright(EnTite* this, GlobalContext* globalCtx); const ActorInit En_Tite_InitVars = { ACTOR_EN_TITE, ACTORCAT_ENEMY, FLAGS, OBJECT_TITE, sizeof(EnTite), (ActorFunc)EnTite_Init, (ActorFunc)EnTite_Destroy, (ActorFunc)EnTite_Update, (ActorFunc)EnTite_Draw, NULL, }; static ColliderJntSphElementInit sJntSphElementsInit[1] = { { { ELEMTYPE_UNK0, { 0xFFCFFFFF, 0x00, 0x08 }, { 0xFFCFFFFF, 0x00, 0x00 }, TOUCH_ON | TOUCH_SFX_NORMAL, BUMP_ON | BUMP_HOOKABLE, OCELEM_ON, }, { 0, { { 0, 1500, 0 }, 20 }, 100 }, }, }; static ColliderJntSphInit sJntSphInit = { { COLTYPE_HIT6, AT_ON | AT_TYPE_ENEMY, AC_ON | AC_TYPE_PLAYER, OC1_ON | OC1_TYPE_ALL, OC2_TYPE_1, COLSHAPE_JNTSPH, }, 1, sJntSphElementsInit, }; static DamageTable sDamageTable[] = { /* Deku nut */ DMG_ENTRY(0, 0x1), /* Deku stick */ DMG_ENTRY(2, 0x0), /* Slingshot */ DMG_ENTRY(1, 0x0), /* Explosive */ DMG_ENTRY(2, 0x0), /* Boomerang */ DMG_ENTRY(0, 0x1), /* Normal arrow */ DMG_ENTRY(2, 0x0), /* Hammer swing */ DMG_ENTRY(2, 0x0), /* Hookshot */ DMG_ENTRY(0, 0x1), /* Kokiri sword */ DMG_ENTRY(1, 0x0), /* Master sword */ DMG_ENTRY(2, 0x0), /* Giant's Knife */ DMG_ENTRY(4, 0x0), /* Fire arrow */ DMG_ENTRY(2, 0x0), /* Ice arrow */ DMG_ENTRY(4, 0xF), /* Light arrow */ DMG_ENTRY(2, 0x0), /* Unk arrow 1 */ DMG_ENTRY(2, 0x0), /* Unk arrow 2 */ DMG_ENTRY(2, 0x0), /* Unk arrow 3 */ DMG_ENTRY(2, 0x0), /* Fire magic */ DMG_ENTRY(0, 0xE), /* Ice magic */ DMG_ENTRY(3, 0xF), /* Light magic */ DMG_ENTRY(0, 0xE), /* Shield */ DMG_ENTRY(0, 0x0), /* Mirror Ray */ DMG_ENTRY(0, 0x0), /* Kokiri spin */ DMG_ENTRY(1, 0x0), /* Giant spin */ DMG_ENTRY(4, 0x0), /* Master spin */ DMG_ENTRY(2, 0x0), /* Kokiri jump */ DMG_ENTRY(2, 0x0), /* Giant jump */ DMG_ENTRY(8, 0x0), /* Master jump */ DMG_ENTRY(4, 0x0), /* Unknown 1 */ DMG_ENTRY(0, 0x0), /* Unblockable */ DMG_ENTRY(0, 0x0), /* Hammer jump */ DMG_ENTRY(4, 0x0), /* Unknown 2 */ DMG_ENTRY(0, 0x0), }; static InitChainEntry sInitChain[] = { ICHAIN_S8(naviEnemyId, 0x45, ICHAIN_CONTINUE), ICHAIN_F32(targetArrowOffset, 2000, ICHAIN_CONTINUE), ICHAIN_F32(minVelocityY, -40, ICHAIN_CONTINUE), ICHAIN_F32_DIV1000(gravity, -1000, ICHAIN_STOP), }; static AnimationHeader* D_80B1B634[] = { &object_tite_Anim_00083C, &object_tite_Anim_0004F8, &object_tite_Anim_00069C, NULL, NULL, NULL, }; // Some kind of offset for the position of each tektite foot static Vec3f sFootOffset = { 2800.0f, -200.0f, 0.0f }; // Relative positions to spawn ice chunks when tektite is frozen static Vec3f sIceChunks[12] = { { 20.0f, 20.0f, 0.0f }, { 10.0f, 40.0f, 10.0f }, { -10.0f, 40.0f, 10.0f }, { -20.0f, 20.0f, 0.0f }, { 10.0f, 40.0f, -10.0f }, { -10.0f, 40.0f, -10.0f }, { 0.0f, 20.0f, -20.0f }, { 10.0f, 0.0f, 10.0f }, { 10.0f, 0.0f, -10.0f }, { 0.0f, 20.0f, 20.0f }, { -10.0f, 0.0f, 10.0f }, { -10.0f, 0.0f, -10.0f }, }; void EnTite_SetupAction(EnTite* this, EnTiteActionFunc actionFunc) { this->actionFunc = actionFunc; } void EnTite_Init(Actor* thisx, GlobalContext* globalCtx) { EnTite* this = (EnTite*)thisx; Actor_ProcessInitChain(thisx, sInitChain); thisx->targetMode = 3; Actor_SetScale(thisx, 0.01f); SkelAnime_Init(globalCtx, &this->skelAnime, &object_tite_Skel_003A20, &object_tite_Anim_0012E4, this->jointTable, this->morphTable, 25); ActorShape_Init(&thisx->shape, -200.0f, ActorShadow_DrawCircle, 70.0f); this->flipState = TEKTITE_INITIAL; thisx->colChkInfo.damageTable = sDamageTable; this->actionVar1 = 0; this->bodyBreak.val = BODYBREAK_STATUS_FINISHED; thisx->focus.pos = thisx->world.pos; thisx->focus.pos.y += 20.0f; thisx->colChkInfo.health = 2; thisx->colChkInfo.mass = MASS_HEAVY; Collider_InitJntSph(globalCtx, &this->collider); Collider_SetJntSph(globalCtx, &this->collider, thisx, &sJntSphInit, &this->colliderItem); this->unk_2DC = 0x1D; if (this->actor.params == TEKTITE_BLUE) { this->unk_2DC |= 0x40; // Don't use the actor engine's ripple spawning code thisx->colChkInfo.health = 4; thisx->naviEnemyId += 1; } EnTite_SetupIdle(this); } void EnTite_Destroy(Actor* thisx, GlobalContext* globalCtx) { EnTite* this = (EnTite*)thisx; EnEncount1* spawner; if (thisx->parent != NULL) { spawner = (EnEncount1*)thisx->parent; if (spawner->curNumSpawn > 0) { spawner->curNumSpawn--; } osSyncPrintf("\n\n"); // "Number of simultaneous occurrences" osSyncPrintf(VT_FGCOL(GREEN) "☆☆☆☆☆ 同時発生数 ☆☆☆☆☆%d\n" VT_RST, spawner->curNumSpawn); osSyncPrintf("\n\n"); } Collider_DestroyJntSph(globalCtx, &this->collider); } void EnTite_SetupIdle(EnTite* this) { Animation_MorphToLoop(&this->skelAnime, &object_tite_Anim_0012E4, 4.0f); this->action = TEKTITE_IDLE; this->vIdleTimer = Rand_S16Offset(15, 30); this->actor.speedXZ = 0.0f; EnTite_SetupAction(this, EnTite_Idle); } void EnTite_Idle(EnTite* this, GlobalContext* globalCtx) { SkelAnime_Update(&this->skelAnime); Math_SmoothStepToF(&this->actor.speedXZ, 0.0f, 1.0f, 0.5f, 0.0f); if (this->actor.params == TEKTITE_BLUE) { if (this->actor.bgCheckFlags & 0x20) { // Float on water surface this->actor.gravity = 0.0f; Math_SmoothStepToF(&this->actor.velocity.y, 0.0f, 1.0f, 2.0f, 0.0f); Math_SmoothStepToF(&this->actor.world.pos.y, this->actor.world.pos.y + this->actor.yDistToWater, 1.0f, 2.0f, 0.0f); } else { this->actor.gravity = -1.0f; } } if ((this->actor.bgCheckFlags & 3) && (this->actor.velocity.y <= 0.0f)) { this->actor.velocity.y = 0.0f; } if (this->vIdleTimer > 0) { this->vIdleTimer--; } else if ((this->actor.xzDistToPlayer < 300.0f) && (this->actor.yDistToPlayer <= 80.0f)) { EnTite_SetupTurnTowardPlayer(this); } } void EnTite_SetupAttack(EnTite* this) { Animation_PlayOnce(&this->skelAnime, &object_tite_Anim_00083C); this->action = TEKTITE_ATTACK; this->vAttackState = TEKTITE_BEGIN_LUNGE; this->vQueuedJumps = Rand_S16Offset(1, 3); this->actor.speedXZ = 0.0f; this->actor.velocity.y = 0.0f; this->actor.world.rot.y = this->actor.shape.rot.y; EnTite_SetupAction(this, EnTite_Attack); } void EnTite_Attack(EnTite* this, GlobalContext* globalCtx) { s16 angleToPlayer; s32 attackState; Vec3f ripplePos; if (SkelAnime_Update(&this->skelAnime) != 0) { attackState = this->vAttackState; // for deciding whether to change animation switch (this->vAttackState) { case TEKTITE_BEGIN_LUNGE: // Snap to ground or water, then lunge into the air with some initial speed this->vAttackState = TEKTITE_MID_LUNGE; if ((this->actor.params != TEKTITE_BLUE) || !(this->actor.bgCheckFlags & 0x20)) { if (this->actor.floorHeight > BGCHECK_Y_MIN) { this->actor.world.pos.y = this->actor.floorHeight; } Audio_PlayActorSound2(&this->actor, NA_SE_EN_STAL_JUMP); } else { this->actor.world.pos.y += this->actor.yDistToWater; Audio_PlayActorSound2(&this->actor, NA_SE_EN_TEKU_JUMP_WATER); } this->actor.velocity.y = 8.0f; this->actor.gravity = -1.0f; this->actor.speedXZ = 4.0f; break; case TEKTITE_MID_LUNGE: // Continue trajectory until tektite has negative velocity and has landed on ground/water surface // Snap to ground/water surface, or if falling fast dip into the water and slow fall speed this->actor.flags |= ACTOR_FLAG_24; if ((this->actor.bgCheckFlags & 3) || ((this->actor.params == TEKTITE_BLUE) && (this->actor.bgCheckFlags & 0x20))) { if (this->actor.velocity.y <= 0.0f) { this->vAttackState = TEKTITE_LANDED; if ((this->actor.params != TEKTITE_BLUE) || !(this->actor.bgCheckFlags & 0x20)) { if (BGCHECK_Y_MIN < this->actor.floorHeight) { this->actor.world.pos.y = this->actor.floorHeight; } this->actor.velocity.y = 0.0f; this->actor.speedXZ = 0.0f; } else { this->actor.gravity = 0.0f; if (this->actor.velocity.y < -8.0f) { ripplePos = this->actor.world.pos; ripplePos.y += this->actor.yDistToWater; this->vAttackState++; // TEKTITE_SUBMERGED this->actor.velocity.y *= 0.75f; attackState = this->vAttackState; EffectSsGRipple_Spawn(globalCtx, &ripplePos, 0, 500, 0); } else { this->actor.velocity.y = 0.0f; this->actor.speedXZ = 0.0f; } } this->actor.world.rot.y = this->actor.shape.rot.y; } } break; case TEKTITE_LANDED: // Get ready to begin another lunge if more lunges are queued, otherwise start turning if (this->vQueuedJumps != 0) { this->vQueuedJumps--; this->vAttackState = TEKTITE_BEGIN_LUNGE; this->collider.base.atFlags &= ~AT_HIT; } else { EnTite_SetupTurnTowardPlayer(this); } break; case TEKTITE_SUBMERGED: // Check if floated to surface if (this->actor.yDistToWater == 0.0f) { this->vAttackState = TEKTITE_LANDED; attackState = this->vAttackState; } break; } // If switching attack state, change animation (unless tektite is switching between submerged and landed) if (attackState != this->vAttackState) { Animation_PlayOnce(&this->skelAnime, D_80B1B634[this->vAttackState]); } } switch (this->vAttackState) { case TEKTITE_BEGIN_LUNGE: // Slightly turn to player and switch to turning/idling action if the player is too far Math_SmoothStepToS(&this->actor.world.rot.y, this->actor.yawTowardsPlayer, 1, 1000, 0); this->actor.shape.rot.y = this->actor.world.rot.y; angleToPlayer = this->actor.yawTowardsPlayer - this->actor.shape.rot.y; if ((this->actor.xzDistToPlayer > 300.0f) && (this->actor.yDistToPlayer > 80.0f)) { EnTite_SetupIdle(this); } else if (ABS(angleToPlayer) >= 9000) { EnTite_SetupTurnTowardPlayer(this); } break; case TEKTITE_MID_LUNGE: // Generate sparkles at feet upon landing, set jumping animation and hurtbox and check if hit player if (this->actor.velocity.y >= 5.0f) { if (this->actor.bgCheckFlags & 1) { func_800355B8(globalCtx, &this->frontLeftFootPos); func_800355B8(globalCtx, &this->frontRightFootPos); func_800355B8(globalCtx, &this->backRightFootPos); func_800355B8(globalCtx, &this->backLeftFootPos); } } if (!(this->collider.base.atFlags & AT_HIT) && (this->actor.flags & ACTOR_FLAG_6)) { CollisionCheck_SetAT(globalCtx, &globalCtx->colChkCtx, &this->collider.base); } else { Player* player = GET_PLAYER(globalCtx); this->collider.base.atFlags &= ~AT_HIT; Animation_MorphToLoop(&this->skelAnime, &object_tite_Anim_0012E4, 4.0f); this->actor.speedXZ = -6.0f; this->actor.world.rot.y = this->actor.yawTowardsPlayer; if (&player->actor == this->collider.base.at) { if (!(this->collider.base.atFlags & AT_BOUNCED)) { Audio_PlayActorSound2(&player->actor, NA_SE_PL_BODY_HIT); } } EnTite_SetupAction(this, EnTite_Recoil); } break; case TEKTITE_LANDED: // Slightly turn to player Math_SmoothStepToS(&this->actor.world.rot.y, this->actor.yawTowardsPlayer, 1, 1500, 0); break; case TEKTITE_SUBMERGED: // Float up to water surface Math_SmoothStepToF(&this->actor.velocity.y, 0.0f, 1.0f, 2.0f, 0.0f); Math_SmoothStepToF(&this->actor.speedXZ, 0.0f, 1.0f, 0.5f, 0.0f); Math_SmoothStepToF(&this->actor.world.pos.y, this->actor.world.pos.y + this->actor.yDistToWater, 1.0f, 2.0f, 0.0f); break; } // Create ripples on water surface where tektite feet landed if (this->actor.bgCheckFlags & 2) { if (!(this->actor.bgCheckFlags & 0x20)) { func_80033480(globalCtx, &this->frontLeftFootPos, 1.0f, 2, 80, 15, 1); func_80033480(globalCtx, &this->frontRightFootPos, 1.0f, 2, 80, 15, 1); func_80033480(globalCtx, &this->backRightFootPos, 1.0f, 2, 80, 15, 1); func_80033480(globalCtx, &this->backLeftFootPos, 1.0f, 2, 80, 15, 1); } } // if landed, kill XZ speed and play appropriate sounds if (this->actor.params == TEKTITE_BLUE) { if (this->actor.bgCheckFlags & 0x40) { this->actor.speedXZ = 0.0f; if (this->vAttackState == TEKTITE_SUBMERGED) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_TEKU_LAND_WATER); } else { Audio_PlayActorSound2(&this->actor, NA_SE_EN_TEKU_LAND_WATER2); } this->actor.bgCheckFlags &= ~0x40; } else if (this->actor.bgCheckFlags & 2) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_DODO_M_GND); } } else if (this->actor.bgCheckFlags & 2) { this->actor.speedXZ = 0.0f; Audio_PlayActorSound2(&this->actor, NA_SE_EN_DODO_M_GND); } } void EnTite_SetupTurnTowardPlayer(EnTite* this) { Animation_PlayLoop(&this->skelAnime, &object_tite_Anim_000A14); this->action = TEKTITE_TURN_TOWARD_PLAYER; if ((this->actor.bgCheckFlags & 3) || ((this->actor.params == TEKTITE_BLUE) && (this->actor.bgCheckFlags & 0x20))) { if (this->actor.velocity.y <= 0.0f) { this->actor.gravity = 0.0f; this->actor.velocity.y = 0.0f; this->actor.speedXZ = 0.0f; } } EnTite_SetupAction(this, EnTite_TurnTowardPlayer); } void EnTite_TurnTowardPlayer(EnTite* this, GlobalContext* globalCtx) { s16 angleToPlayer; s16 turnVelocity; if (((this->actor.bgCheckFlags & 3) || ((this->actor.params == TEKTITE_BLUE) && (this->actor.bgCheckFlags & 0x20))) && (this->actor.velocity.y <= 0.0f)) { this->actor.gravity = 0.0f; this->actor.velocity.y = 0.0f; this->actor.speedXZ = 0.0f; } // Calculate turn velocity and animation speed based on angle to player if ((this->actor.params == TEKTITE_BLUE) && (this->actor.bgCheckFlags & 0x20)) { this->actor.world.pos.y += this->actor.yDistToWater; } angleToPlayer = Actor_WorldYawTowardActor(&this->actor, &GET_PLAYER(globalCtx)->actor) - this->actor.world.rot.y; if (angleToPlayer > 0) { turnVelocity = (angleToPlayer / 42.0f) + 10.0f; this->actor.world.rot.y += (turnVelocity * 2); } else { turnVelocity = (angleToPlayer / 42.0f) - 10.0f; this->actor.world.rot.y += (turnVelocity * 2); } if (angleToPlayer > 0) { this->skelAnime.playSpeed = turnVelocity * 0.01f; } else { this->skelAnime.playSpeed = turnVelocity * 0.01f; } /** * Play sounds once every animation cycle */ SkelAnime_Update(&this->skelAnime); if (((s16)this->skelAnime.curFrame & 7) == 0) { if ((this->actor.params == TEKTITE_BLUE) && (this->actor.bgCheckFlags & 0x20)) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_TEKU_WALK_WATER); } else { Audio_PlayActorSound2(&this->actor, NA_SE_EN_TEKU_WALK); } } // Idle if player is far enough away from the tektite, move or attack if almost facing player this->actor.shape.rot.y = this->actor.world.rot.y; if ((this->actor.xzDistToPlayer > 300.0f) && (this->actor.yDistToPlayer > 80.0f)) { EnTite_SetupIdle(this); } else if (Actor_IsFacingPlayer(&this->actor, 3640)) { if ((this->actor.xzDistToPlayer <= 180.0f) && (this->actor.yDistToPlayer <= 80.0f)) { EnTite_SetupAttack(this); } else { EnTite_SetupMoveTowardPlayer(this); } } } void EnTite_SetupMoveTowardPlayer(EnTite* this) { Animation_PlayLoop(&this->skelAnime, &object_tite_Anim_000C70); this->action = TEKTITE_MOVE_TOWARD_PLAYER; this->actor.velocity.y = 10.0f; this->actor.gravity = -1.0f; this->actor.speedXZ = 4.0f; this->vQueuedJumps = Rand_S16Offset(1, 3); if ((this->actor.params == TEKTITE_BLUE) && (this->actor.bgCheckFlags & 0x20)) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_TEKU_JUMP_WATER); } else { Audio_PlayActorSound2(&this->actor, NA_SE_EN_STAL_JUMP); } EnTite_SetupAction(this, EnTite_MoveTowardPlayer); } /** * Jumping toward player as a method of travel (different from attacking, has no hitbox) */ void EnTite_MoveTowardPlayer(EnTite* this, GlobalContext* globalCtx) { Math_SmoothStepToF(&this->actor.speedXZ, 0.0f, 0.1f, 1.0f, 0.0f); SkelAnime_Update(&this->skelAnime); if (this->actor.bgCheckFlags & 0x42) { if (!(this->actor.bgCheckFlags & 0x40)) { func_80033480(globalCtx, &this->frontLeftFootPos, 1.0f, 2, 80, 15, 1); func_80033480(globalCtx, &this->frontRightFootPos, 1.0f, 2, 80, 15, 1); func_80033480(globalCtx, &this->backRightFootPos, 1.0f, 2, 80, 15, 1); func_80033480(globalCtx, &this->backLeftFootPos, 1.0f, 2, 80, 15, 1); Audio_PlayActorSound2(&this->actor, NA_SE_EN_DODO_M_GND); } else { Audio_PlayActorSound2(&this->actor, NA_SE_EN_TEKU_LAND_WATER); } } if ((this->actor.bgCheckFlags & 2) || ((this->actor.params == TEKTITE_BLUE) && (this->actor.bgCheckFlags & 0x40))) { if (this->vQueuedJumps != 0) { this->vQueuedJumps--; } else { EnTite_SetupIdle(this); } } if (((this->actor.bgCheckFlags & 3) || (this->actor.params == TEKTITE_BLUE && (this->actor.bgCheckFlags & 0x60))) && (this->actor.velocity.y <= 0.0f)) { // slightly turn toward player upon landing and snap to ground or water. this->actor.speedXZ = 0.0f; Math_SmoothStepToS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer, 1, 4000, 0); this->actor.world.rot.y = this->actor.shape.rot.y; if ((this->actor.params != TEKTITE_BLUE) || !(this->actor.bgCheckFlags & 0x20)) { if (this->actor.floorHeight > BGCHECK_Y_MIN) { this->actor.world.pos.y = this->actor.floorHeight; } } else if (this->actor.bgCheckFlags & 0x40) { Vec3f ripplePos = this->actor.world.pos; this->actor.bgCheckFlags &= ~0x40; ripplePos.y += this->actor.yDistToWater; this->actor.gravity = 0.0f; this->actor.velocity.y *= 0.75f; EffectSsGRipple_Spawn(globalCtx, &ripplePos, 0, 500, 0); return; } else { // If submerged, float to surface Math_SmoothStepToF(&this->actor.velocity.y, 0.0f, 1.0f, 2.0f, 0.0f); Math_SmoothStepToF(&this->actor.world.pos.y, this->actor.world.pos.y + this->actor.yDistToWater, 1.0f, 2.0f, 0.0f); if (this->actor.yDistToWater != 0.0f) { // Do not change state until tekite has floated to surface return; } } // Idle or turn if player is too far away, otherwise keep jumping if (((this->actor.xzDistToPlayer > 300.0f) && (this->actor.yDistToPlayer > 80.0f))) { EnTite_SetupIdle(this); } else if (((this->actor.xzDistToPlayer <= 180.0f)) && ((this->actor.yDistToPlayer <= 80.0f))) { if (this->vQueuedJumps <= 0) { EnTite_SetupTurnTowardPlayer(this); } else { this->actor.velocity.y = 10.0f; this->actor.speedXZ = 4.0f; this->actor.flags |= ACTOR_FLAG_24; this->actor.gravity = -1.0f; if ((this->actor.params == TEKTITE_BLUE) && (this->actor.bgCheckFlags & 0x20)) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_TEKU_JUMP_WATER); } else { Audio_PlayActorSound2(&this->actor, NA_SE_EN_STAL_JUMP); } } } else { this->actor.velocity.y = 10.0f; this->actor.speedXZ = 4.0f; this->actor.flags |= ACTOR_FLAG_24; this->actor.gravity = -1.0f; if ((this->actor.params == TEKTITE_BLUE) && (this->actor.bgCheckFlags & 0x20)) { Audio_PlayActorSound2(&this->actor, NA_SE_EN_TEKU_JUMP_WATER); } else { Audio_PlayActorSound2(&this->actor, NA_SE_EN_STAL_JUMP); } } // If in midair: } else { // Turn slowly toward player this->actor.flags |= ACTOR_FLAG_24; Math_SmoothStepToS(&this->actor.shape.rot.y, this->actor.yawTowardsPlayer, 1, 1000, 0); if (this->actor.velocity.y >= 6.0f) { if (this->actor.bgCheckFlags & 1) { func_800355B8(globalCtx, &this->frontLeftFootPos); func_800355B8(globalCtx, &this->frontRightFootPos); func_800355B8(globalCtx, &this->backRightFootPos); func_800355B8(globalCtx, &this->backLeftFootPos); } } } } void EnTite_SetupRecoil(EnTite* this) { this->action = TEKTITE_RECOIL; Animation_MorphToLoop(&this->skelAnime, &object_tite_Anim_0012E4, 4.0f); this->actor.speedXZ = -6.0f; this->actor.world.rot.y = this->actor.yawTowardsPlayer; this->actor.gravity = -1.0f; EnTite_SetupAction(this, EnTite_Recoil); } /** * After tektite hits or gets hit, recoils backwards and slides a bit upon landing */ void EnTite_Recoil(EnTite* this, GlobalContext* globalCtx) { s16 angleToPlayer; // Snap to ground or water surface upon landing Math_SmoothStepToF(&this->actor.speedXZ, 0.0f, 1.0f, 0.5f, 0.0f); if (((this->actor.bgCheckFlags & 3) || (this->actor.params == TEKTITE_BLUE && (this->actor.bgCheckFlags & 0x20))) && (this->actor.velocity.y <= 0.0f)) { if ((this->actor.params != TEKTITE_BLUE) || !(this->actor.bgCheckFlags & 0x20)) { if (this->actor.floorHeight > BGCHECK_Y_MIN) { this->actor.world.pos.y = this->actor.floorHeight; } } else { this->actor.velocity.y = 0.0f; this->actor.gravity = 0.0f; this->actor.world.pos.y += this->actor.yDistToWater; } } // play sound and generate ripples if (this->actor.bgCheckFlags & 0x42) { if (!(this->actor.bgCheckFlags & 0x40)) { func_80033480(globalCtx, &this->frontLeftFootPos, 1.0f, 2, 80, 15, 1); func_80033480(globalCtx, &this->frontRightFootPos, 1.0f, 2, 80, 15, 1); func_80033480(globalCtx, &this->backRightFootPos, 1.0f, 2, 80, 15, 1); func_80033480(globalCtx, &this->backLeftFootPos, 1.0f, 2, 80, 15, 1); Audio_PlayActorSound2(&this->actor, NA_SE_EN_DODO_M_GND); } else { this->actor.bgCheckFlags &= ~0x40; Audio_PlayActorSound2(&this->actor, NA_SE_EN_TEKU_LAND_WATER2); } } // If player is far away, idle. Otherwise attack or move angleToPlayer = (this->actor.yawTowardsPlayer - this->actor.shape.rot.y); if ((this->actor.speedXZ == 0.0f) && ((this->actor.bgCheckFlags & 1) || ((this->actor.params == TEKTITE_BLUE) && (this->actor.bgCheckFlags & 0x20)))) { this->actor.world.rot.y = this->actor.shape.rot.y; this->collider.base.atFlags &= ~AT_HIT; if ((this->actor.xzDistToPlayer > 300.0f) && (this->actor.yDistToPlayer > 80.0f) && (ABS(this->actor.shape.rot.x) < 4000) && (ABS(this->actor.shape.rot.z) < 4000) && ((this->actor.bgCheckFlags & 1) || ((this->actor.params == TEKTITE_BLUE) && (this->actor.bgCheckFlags & 0x20)))) { EnTite_SetupIdle(this); } else if ((this->actor.xzDistToPlayer < 180.0f) && (this->actor.yDistToPlayer <= 80.0f) && (ABS(angleToPlayer) <= 6000)) { EnTite_SetupAttack(this); } else { EnTite_SetupMoveTowardPlayer(this); } } SkelAnime_Update(&this->skelAnime); } void EnTite_SetupStunned(EnTite* this) { Animation_Change(&this->skelAnime, &object_tite_Anim_0012E4, 0.0f, 0.0f, (f32)Animation_GetLastFrame(&object_tite_Anim_0012E4), ANIMMODE_LOOP, 4.0f); this->action = TEKTITE_STUNNED; this->actor.speedXZ = -6.0f; this->actor.world.rot.y = this->actor.yawTowardsPlayer; if (this->damageEffect == 0xF) { this->spawnIceTimer = 48; } Audio_PlayActorSound2(&this->actor, NA_SE_EN_GOMA_JR_FREEZE); EnTite_SetupAction(this, EnTite_Stunned); } /** * stunned or frozen */ void EnTite_Stunned(EnTite* this, GlobalContext* globalCtx) { s16 angleToPlayer; Math_SmoothStepToF(&this->actor.speedXZ, 0.0f, 1.0f, 0.5f, 0.0f); // Snap to ground or water if (((this->actor.bgCheckFlags & 3) || ((this->actor.params == TEKTITE_BLUE) && (this->actor.bgCheckFlags & 0x20))) && (this->actor.velocity.y <= 0.0f)) { if (((this->actor.params != TEKTITE_BLUE) || !(this->actor.bgCheckFlags & 0x20))) { if (this->actor.floorHeight > BGCHECK_Y_MIN) { this->actor.world.pos.y = this->actor.floorHeight; } } else { this->actor.velocity.y = 0.0f; this->actor.gravity = 0.0f; this->actor.world.pos.y += this->actor.yDistToWater; } } // Play sounds and spawn dirt effects upon landing if (this->actor.bgCheckFlags & 0x42) { if (!(this->actor.bgCheckFlags & 0x40)) { func_80033480(globalCtx, &this->frontLeftFootPos, 1.0f, 2, 80, 15, 1); func_80033480(globalCtx, &this->frontRightFootPos, 1.0f, 2, 80, 15, 1); func_80033480(globalCtx, &this->backRightFootPos, 1.0f, 2, 80, 15, 1); func_80033480(globalCtx, &this->backLeftFootPos, 1.0f, 2, 80, 15, 1); Audio_PlayActorSound2(&this->actor, NA_SE_EN_DODO_M_GND); } else { this->actor.bgCheckFlags &= ~0x40; Audio_PlayActorSound2(&this->actor, NA_SE_EN_TEKU_LAND_WATER2); } } // Decide on next action based on health, flip state and player distance angleToPlayer = this->actor.yawTowardsPlayer - this->actor.shape.rot.y; if (((this->actor.colorFilterTimer == 0) && (this->actor.speedXZ == 0.0f)) && ((this->actor.bgCheckFlags & 1) || ((this->actor.params == TEKTITE_BLUE) && (this->actor.bgCheckFlags & 0x20)))) { this->actor.world.rot.y = this->actor.shape.rot.y; if (this->actor.colChkInfo.health == 0) { EnTite_SetupDeathCry(this); } else if (this->flipState == TEKTITE_FLIPPED) { EnTite_SetupFlipUpright(this); } else if (((this->actor.xzDistToPlayer > 300.0f) && (this->actor.yDistToPlayer > 80.0f) && (ABS(this->actor.shape.rot.x) < 4000) && (ABS(this->actor.shape.rot.z) < 4000)) && ((this->actor.bgCheckFlags & 1) || ((this->actor.params == TEKTITE_BLUE) && (this->actor.bgCheckFlags & 0x20)))) { EnTite_SetupIdle(this); } else if ((this->actor.xzDistToPlayer < 180.0f) && (this->actor.yDistToPlayer <= 80.0f) && (ABS(angleToPlayer) <= 6000)) { EnTite_SetupAttack(this); } else { EnTite_SetupMoveTowardPlayer(this); } } SkelAnime_Update(&this->skelAnime); } void EnTite_SetupDeathCry(EnTite* this) { this->action = TEKTITE_DEATH_CRY; this->actor.colorFilterTimer = 0; this->actor.speedXZ = 0.0f; EnTite_SetupAction(this, EnTite_DeathCry); } /** * First frame of death. Scream in pain and allocate memory for EnPart data */ void EnTite_DeathCry(EnTite* this, GlobalContext* globalCtx) { EffectSsDeadSound_SpawnStationary(globalCtx, &this->actor.projectedPos, NA_SE_EN_TEKU_DEAD, true, DEADSOUND_REPEAT_MODE_OFF, 40); this->action = TEKTITE_FALL_APART; EnTite_SetupAction(this, EnTite_FallApart); BodyBreak_Alloc(&this->bodyBreak, 24, globalCtx); } /** * Spawn EnPart and drop items */ void EnTite_FallApart(EnTite* this, GlobalContext* globalCtx) { if (BodyBreak_SpawnParts(&this->actor, &this->bodyBreak, globalCtx, this->actor.params + 0xB)) { if (this->actor.params == TEKTITE_BLUE) { Item_DropCollectibleRandom(globalCtx, &this->actor, &this->actor.world.pos, 0xE0); } else { Item_DropCollectibleRandom(globalCtx, &this->actor, &this->actor.world.pos, 0x40); } Actor_Kill(&this->actor); } } void EnTite_SetupFlipOnBack(EnTite* this) { Animation_PlayLoopSetSpeed(&this->skelAnime, &object_tite_Anim_000A14, 1.5f); Audio_PlayActorSound2(&this->actor, NA_SE_EN_TEKU_REVERSE); this->flipState = TEKTITE_FLIPPED; this->vOnBackTimer = 500; this->actor.speedXZ = 0.0f; this->actor.gravity = -1.0f; this->vLegTwitchTimer = (Rand_ZeroOne() * 50.0f); this->actor.velocity.y = 11.0f; EnTite_SetupAction(this, EnTite_FlipOnBack); } /** * During the flip animation and also while idling on back */ void EnTite_FlipOnBack(EnTite* this, GlobalContext* globalCtx) { Math_SmoothStepToS(&this->actor.shape.rot.z, 0x7FFF, 1, 4000, 0); // randomly reset the leg wiggling animation whenever timer reaches 0 to give illusion of twitching legs this->vLegTwitchTimer--; if (this->vLegTwitchTimer == 0) { this->vLegTwitchTimer = Rand_ZeroOne() * 30.0f; this->skelAnime.curFrame = Rand_ZeroOne() * 5.0f; } SkelAnime_Update(&this->skelAnime); if (this->actor.bgCheckFlags & 3) { // Upon landing, spawn dust and make noise if (this->actor.bgCheckFlags & 2) { Actor_SpawnFloorDustRing(globalCtx, &this->actor, &this->actor.world.pos, 20.0f, 11, 4.0f, 0, 0, false); Audio_PlayActorSound2(&this->actor, NA_SE_EN_DODO_M_GND); } this->vOnBackTimer--; if (this->vOnBackTimer == 0) { EnTite_SetupFlipUpright(this); } } else { // Gradually increase y offset during flip so that the actor position is at tektite's back instead of feet if (this->actor.shape.yOffset < 2800.0f) { this->actor.shape.yOffset += 400.0f; } } } void EnTite_SetupFlipUpright(EnTite* this) { this->flipState = TEKTITE_UNFLIPPED; this->actionVar1 = 1000; // value unused here and overwritten in SetupIdle //! @bug flying tektite: water sets gravity to 0 so y velocity will never decrease from 13 this->actor.velocity.y = 13.0f; Audio_PlayActorSound2(&this->actor, NA_SE_EN_TEKU_REVERSE); EnTite_SetupAction(this, EnTite_FlipUpright); } void EnTite_FlipUpright(EnTite* this, GlobalContext* globalCtx) { Math_SmoothStepToS(&this->actor.shape.rot.z, 0, 1, 0xFA0, 0); SkelAnime_Update(&this->skelAnime); //! @bug flying tektite: the following condition is never met and tektite stays stuck in this action forever if (this->actor.bgCheckFlags & 2) { func_80033480(globalCtx, &this->frontLeftFootPos, 1.0f, 2, 80, 15, 1); func_80033480(globalCtx, &this->frontRightFootPos, 1.0f, 2, 80, 15, 1); func_80033480(globalCtx, &this->backRightFootPos, 1.0f, 2, 80, 15, 1); func_80033480(globalCtx, &this->backLeftFootPos, 1.0f, 2, 80, 15, 1); this->actor.shape.yOffset = 0.0f; this->actor.world.pos.y = this->actor.floorHeight; Audio_PlayActorSound2(&this->actor, NA_SE_EN_DODO_M_GND); EnTite_SetupIdle(this); } } void EnTite_CheckDamage(Actor* thisx, GlobalContext* globalCtx) { EnTite* this = (EnTite*)thisx; if ((this->collider.base.acFlags & AC_HIT) && (this->action >= TEKTITE_IDLE)) { this->collider.base.acFlags &= ~AC_HIT; if (thisx->colChkInfo.damageEffect != 0xE) { // Immune to fire magic this->damageEffect = thisx->colChkInfo.damageEffect; Actor_SetDropFlag(thisx, &this->collider.elements[0].info, 0); // Stun if Tektite hit by nut, boomerang, hookshot, ice arrow or ice magic if ((thisx->colChkInfo.damageEffect == 1) || (thisx->colChkInfo.damageEffect == 0xF)) { if (this->action != TEKTITE_STUNNED) { Actor_SetColorFilter(thisx, 0, 0x78, 0, 0x50); Actor_ApplyDamage(thisx); EnTite_SetupStunned(this); } // Otherwise apply damage and handle death where necessary } else { if ((thisx->colorFilterTimer == 0) || ((thisx->colorFilterParams & 0x4000) == 0)) { Actor_SetColorFilter(thisx, 0x4000, 0xFF, 0, 8); Actor_ApplyDamage(thisx); } if (thisx->colChkInfo.health == 0) { EnTite_SetupDeathCry(this); } else { // Flip tektite back up if it's on its back Audio_PlayActorSound2(thisx, NA_SE_EN_TEKU_DAMAGE); if (this->flipState != TEKTITE_FLIPPED) { EnTite_SetupRecoil(this); } else { EnTite_SetupFlipUpright(this); } } } } // If hammer has recently hit the floor and player is close to tektite, flip over } else if ((thisx->colChkInfo.health != 0) && (globalCtx->actorCtx.unk_02 != 0) && (thisx->xzDistToPlayer <= 400.0f) && (thisx->bgCheckFlags & 1)) { if (this->flipState == TEKTITE_FLIPPED) { EnTite_SetupFlipUpright(this); } else if ((this->action >= TEKTITE_IDLE) || (this->action >= TEKTITE_IDLE)) { EnTite_SetupFlipOnBack(this); } } } void EnTite_Update(Actor* thisx, GlobalContext* globalCtx) { EnTite* this = (EnTite*)thisx; char pad[0x4]; CollisionPoly* floorPoly; WaterBox* waterBox; f32 waterSurfaceY; EnTite_CheckDamage(thisx, globalCtx); // Stay still if hit by immunity damage type this frame if (thisx->colChkInfo.damageEffect != 0xE) { this->actionFunc(this, globalCtx); Actor_MoveForward(thisx); Actor_UpdateBgCheckInfo(globalCtx, thisx, 25.0f, 40.0f, 20.0f, this->unk_2DC); // If on water, snap feet to surface and spawn ripples if ((this->actor.params == TEKTITE_BLUE) && (thisx->bgCheckFlags & 0x20)) { floorPoly = thisx->floorPoly; if ((((globalCtx->gameplayFrames % 8) == 0) || (thisx->velocity.y < 0.0f)) && (WaterBox_GetSurfaceImpl(globalCtx, &globalCtx->colCtx, this->backRightFootPos.x, this->backRightFootPos.z, &waterSurfaceY, &waterBox)) && (this->backRightFootPos.y <= waterSurfaceY)) { this->backRightFootPos.y = waterSurfaceY; EffectSsGRipple_Spawn(globalCtx, &this->backRightFootPos, 0, 220, 0); } if (((((globalCtx->gameplayFrames + 2) % 8) == 0) || (thisx->velocity.y < 0.0f)) && (WaterBox_GetSurfaceImpl(globalCtx, &globalCtx->colCtx, this->backLeftFootPos.x, this->backLeftFootPos.z, &waterSurfaceY, &waterBox)) && (this->backLeftFootPos.y <= waterSurfaceY)) { this->backLeftFootPos.y = waterSurfaceY; EffectSsGRipple_Spawn(globalCtx, &this->backLeftFootPos, 0, 220, 0); } if (((((globalCtx->gameplayFrames + 4) % 8) == 0) || (thisx->velocity.y < 0.0f)) && (WaterBox_GetSurfaceImpl(globalCtx, &globalCtx->colCtx, this->frontLeftFootPos.x, this->frontLeftFootPos.z, &waterSurfaceY, &waterBox)) && (this->frontLeftFootPos.y <= waterSurfaceY)) { this->frontLeftFootPos.y = waterSurfaceY; EffectSsGRipple_Spawn(globalCtx, &this->frontLeftFootPos, 0, 220, 0); } if (((((globalCtx->gameplayFrames + 1) % 8) == 0) || (thisx->velocity.y < 0.0f)) && (WaterBox_GetSurfaceImpl(globalCtx, &globalCtx->colCtx, this->frontRightFootPos.x, this->frontRightFootPos.z, &waterSurfaceY, &waterBox)) && (this->frontRightFootPos.y <= waterSurfaceY)) { this->frontRightFootPos.y = waterSurfaceY; EffectSsGRipple_Spawn(globalCtx, &this->frontRightFootPos, 0, 220, 0); } thisx->floorPoly = floorPoly; } // If on ground and currently flipped over, set tektite to be fully upside-down if (thisx->bgCheckFlags & 3) { func_800359B8(thisx, thisx->shape.rot.y, &thisx->shape.rot); if (this->flipState >= TEKTITE_FLIPPED) { thisx->shape.rot.z += 0x7FFF; } // Otherwise ensure the tektite is rotating back upright } else { Math_SmoothStepToS(&thisx->shape.rot.x, 0, 1, 1000, 0); if (this->flipState <= TEKTITE_UNFLIPPED) { Math_SmoothStepToS(&thisx->shape.rot.z, 0, 1, 1000, 0); if (thisx->shape.yOffset > 0) { thisx->shape.yOffset -= 400.0f; } } } } thisx->focus.pos = thisx->world.pos; thisx->focus.pos.y += 20.0f; CollisionCheck_SetAC(globalCtx, &globalCtx->colChkCtx, &this->collider.base); CollisionCheck_SetOC(globalCtx, &globalCtx->colChkCtx, &this->collider.base); } void EnTite_PostLimbDraw(GlobalContext* globalCtx, s32 limbIndex, Gfx** limbDList, Vec3s* rot, void* thisx) { EnTite* this = (EnTite*)thisx; switch (limbIndex) { case 8: Matrix_MultVec3f(&sFootOffset, &this->backRightFootPos); break; case 13: Matrix_MultVec3f(&sFootOffset, &this->frontRightFootPos); break; case 18: Matrix_MultVec3f(&sFootOffset, &this->backLeftFootPos); break; case 23: Matrix_MultVec3f(&sFootOffset, &this->frontLeftFootPos); break; } BodyBreak_SetInfo(&this->bodyBreak, limbIndex, 0, 24, 24, limbDList, BODYBREAK_OBJECT_DEFAULT); } void EnTite_Draw(Actor* thisx, GlobalContext* globalCtx) { EnTite* this = (EnTite*)thisx; OPEN_DISPS(globalCtx->state.gfxCtx); func_80093D18(globalCtx->state.gfxCtx); Collider_UpdateSpheres(0, &this->collider); if (this->actor.params == TEKTITE_BLUE) { gSPSegment(POLY_OPA_DISP++, 0x08, SEGMENTED_TO_VIRTUAL(object_tite_Tex_001300)); gSPSegment(POLY_OPA_DISP++, 0x09, SEGMENTED_TO_VIRTUAL(object_tite_Tex_001700)); gSPSegment(POLY_OPA_DISP++, 0x0A, SEGMENTED_TO_VIRTUAL(object_tite_Tex_001900)); } else { gSPSegment(POLY_OPA_DISP++, 0x08, SEGMENTED_TO_VIRTUAL(object_tite_Tex_001B00)); gSPSegment(POLY_OPA_DISP++, 0x09, SEGMENTED_TO_VIRTUAL(object_tite_Tex_001F00)); gSPSegment(POLY_OPA_DISP++, 0x0A, SEGMENTED_TO_VIRTUAL(object_tite_Tex_002100)); } SkelAnime_DrawOpa(globalCtx, this->skelAnime.skeleton, this->skelAnime.jointTable, NULL, EnTite_PostLimbDraw, thisx); CLOSE_DISPS(globalCtx->state.gfxCtx); if (this->spawnIceTimer != 0) { // Spawn chunks of ice all over the tektite's body thisx->colorFilterTimer++; this->spawnIceTimer--; if ((this->spawnIceTimer & 3) == 0) { Vec3f iceChunk; s32 idx = this->spawnIceTimer >> 2; iceChunk.x = thisx->world.pos.x + sIceChunks[idx].x; iceChunk.y = thisx->world.pos.y + sIceChunks[idx].y; iceChunk.z = thisx->world.pos.z + sIceChunks[idx].z; EffectSsEnIce_SpawnFlyingVec3f(globalCtx, &this->actor, &iceChunk, 150, 150, 150, 250, 235, 245, 255, 1.0f); } } }