/*
 * 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, PlayState* play);
void EnTite_Destroy(Actor* thisx, PlayState* play);
void EnTite_Update(Actor* thisx, PlayState* play);
void EnTite_Draw(Actor* thisx, PlayState* play);

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, PlayState* play);
void EnTite_Attack(EnTite* this, PlayState* play);
void EnTite_TurnTowardPlayer(EnTite* this, PlayState* play);
void EnTite_MoveTowardPlayer(EnTite* this, PlayState* play);
void EnTite_Recoil(EnTite* this, PlayState* play);
void EnTite_Stunned(EnTite* this, PlayState* play);
void EnTite_DeathCry(EnTite* this, PlayState* play);
void EnTite_FallApart(EnTite* this, PlayState* play);
void EnTite_FlipOnBack(EnTite* this, PlayState* play);
void EnTite_FlipUpright(EnTite* this, PlayState* play);

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, PlayState* play) {
    EnTite* this = (EnTite*)thisx;

    Actor_ProcessInitChain(thisx, sInitChain);
    thisx->targetMode = 3;
    Actor_SetScale(thisx, 0.01f);
    SkelAnime_Init(play, &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(play, &this->collider);
    Collider_SetJntSph(play, &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, PlayState* play) {
    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(play, &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, PlayState* play) {
    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, PlayState* play) {
    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(play, &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(play, &this->frontLeftFootPos);
                    func_800355B8(play, &this->frontRightFootPos);
                    func_800355B8(play, &this->backRightFootPos);
                    func_800355B8(play, &this->backLeftFootPos);
                }
            }
            if (!(this->collider.base.atFlags & AT_HIT) && (this->actor.flags & ACTOR_FLAG_6)) {
                CollisionCheck_SetAT(play, &play->colChkCtx, &this->collider.base);
            } else {
                Player* player = GET_PLAYER(play);
                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(play, &this->frontLeftFootPos, 1.0f, 2, 80, 15, 1);
            func_80033480(play, &this->frontRightFootPos, 1.0f, 2, 80, 15, 1);
            func_80033480(play, &this->backRightFootPos, 1.0f, 2, 80, 15, 1);
            func_80033480(play, &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, PlayState* play) {
    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(play)->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, PlayState* play) {
    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(play, &this->frontLeftFootPos, 1.0f, 2, 80, 15, 1);
            func_80033480(play, &this->frontRightFootPos, 1.0f, 2, 80, 15, 1);
            func_80033480(play, &this->backRightFootPos, 1.0f, 2, 80, 15, 1);
            func_80033480(play, &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(play, &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(play, &this->frontLeftFootPos);
                func_800355B8(play, &this->frontRightFootPos);
                func_800355B8(play, &this->backRightFootPos);
                func_800355B8(play, &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, PlayState* play) {
    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(play, &this->frontLeftFootPos, 1.0f, 2, 80, 15, 1);
            func_80033480(play, &this->frontRightFootPos, 1.0f, 2, 80, 15, 1);
            func_80033480(play, &this->backRightFootPos, 1.0f, 2, 80, 15, 1);
            func_80033480(play, &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, PlayState* play) {
    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(play, &this->frontLeftFootPos, 1.0f, 2, 80, 15, 1);
            func_80033480(play, &this->frontRightFootPos, 1.0f, 2, 80, 15, 1);
            func_80033480(play, &this->backRightFootPos, 1.0f, 2, 80, 15, 1);
            func_80033480(play, &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, PlayState* play) {
    EffectSsDeadSound_SpawnStationary(play, &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, play);
}

/**
 * Spawn EnPart and drop items
 */
void EnTite_FallApart(EnTite* this, PlayState* play) {
    if (BodyBreak_SpawnParts(&this->actor, &this->bodyBreak, play, this->actor.params + 0xB)) {
        if (this->actor.params == TEKTITE_BLUE) {
            Item_DropCollectibleRandom(play, &this->actor, &this->actor.world.pos, 0xE0);
            gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_TEKTITE_BLUE]++;
        } else {
            Item_DropCollectibleRandom(play, &this->actor, &this->actor.world.pos, 0x40);
            gSaveContext.sohStats.count[COUNT_ENEMIES_DEFEATED_TEKTITE_RED]++;
        }
        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, PlayState* play) {
    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(play, &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, PlayState* play) {
    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(play, &this->frontLeftFootPos, 1.0f, 2, 80, 15, 1);
        func_80033480(play, &this->frontRightFootPos, 1.0f, 2, 80, 15, 1);
        func_80033480(play, &this->backRightFootPos, 1.0f, 2, 80, 15, 1);
        func_80033480(play, &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, PlayState* play) {
    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) && (play->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, PlayState* play) {
    EnTite* this = (EnTite*)thisx;
    char pad[0x4];
    CollisionPoly* floorPoly;
    WaterBox* waterBox;
    f32 waterSurfaceY;

    EnTite_CheckDamage(thisx, play);
    // Stay still if hit by immunity damage type this frame
    if (thisx->colChkInfo.damageEffect != 0xE) {
        this->actionFunc(this, play);
        Actor_MoveForward(thisx);
        Actor_UpdateBgCheckInfo(play, 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 ((((play->gameplayFrames % 8) == 0) || (thisx->velocity.y < 0.0f)) &&
                (WaterBox_GetSurfaceImpl(play, &play->colCtx, this->backRightFootPos.x,
                                         this->backRightFootPos.z, &waterSurfaceY, &waterBox)) &&
                (this->backRightFootPos.y <= waterSurfaceY)) {
                this->backRightFootPos.y = waterSurfaceY;
                EffectSsGRipple_Spawn(play, &this->backRightFootPos, 0, 220, 0);
            }
            if (((((play->gameplayFrames + 2) % 8) == 0) || (thisx->velocity.y < 0.0f)) &&
                (WaterBox_GetSurfaceImpl(play, &play->colCtx, this->backLeftFootPos.x,
                                         this->backLeftFootPos.z, &waterSurfaceY, &waterBox)) &&
                (this->backLeftFootPos.y <= waterSurfaceY)) {
                this->backLeftFootPos.y = waterSurfaceY;
                EffectSsGRipple_Spawn(play, &this->backLeftFootPos, 0, 220, 0);
            }
            if (((((play->gameplayFrames + 4) % 8) == 0) || (thisx->velocity.y < 0.0f)) &&
                (WaterBox_GetSurfaceImpl(play, &play->colCtx, this->frontLeftFootPos.x,
                                         this->frontLeftFootPos.z, &waterSurfaceY, &waterBox)) &&
                (this->frontLeftFootPos.y <= waterSurfaceY)) {
                this->frontLeftFootPos.y = waterSurfaceY;
                EffectSsGRipple_Spawn(play, &this->frontLeftFootPos, 0, 220, 0);
            }
            if (((((play->gameplayFrames + 1) % 8) == 0) || (thisx->velocity.y < 0.0f)) &&
                (WaterBox_GetSurfaceImpl(play, &play->colCtx, this->frontRightFootPos.x,
                                         this->frontRightFootPos.z, &waterSurfaceY, &waterBox)) &&
                (this->frontRightFootPos.y <= waterSurfaceY)) {
                this->frontRightFootPos.y = waterSurfaceY;
                EffectSsGRipple_Spawn(play, &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(play, &play->colChkCtx, &this->collider.base);
    CollisionCheck_SetOC(play, &play->colChkCtx, &this->collider.base);
}

void EnTite_PostLimbDraw(PlayState* play, 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, PlayState* play) {
    EnTite* this = (EnTite*)thisx;

    OPEN_DISPS(play->state.gfxCtx);
    Gfx_SetupDL_25Opa(play->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(play, this->skelAnime.skeleton, this->skelAnime.jointTable, NULL, EnTite_PostLimbDraw,
                      thisx);
    CLOSE_DISPS(play->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(play, &this->actor, &iceChunk, 150, 150, 150, 250, 235, 245, 255, 1.0f);
        }
    }
}
