/*
 * File: z_en_partner.c
 * Overlay: ovl_En_Partner
 * Description: Navi Coop Partner
 */

#include "z_en_partner.h"
#include "objects/gameplay_keep/gameplay_keep.h"
#include <overlays/actors/ovl_En_Arrow/z_en_arrow.h>
#include "overlays/actors/ovl_En_Si/z_en_si.h"
#include <objects/object_link_child/object_link_child.h>
#include <overlays/actors/ovl_En_Bom/z_en_bom.h>
#include <overlays/actors/ovl_Obj_Switch/z_obj_switch.h>
#include "soh/OTRGlobals.h"
#include "soh/ResourceManagerHelpers.h"

#define FLAGS (ACTOR_FLAG_UPDATE_CULLING_DISABLED | ACTOR_FLAG_DRAW_CULLING_DISABLED | ACTOR_FLAG_HOOKSHOT_PULLS_PLAYER | ACTOR_FLAG_CAN_PRESS_SWITCHES)

void EnPartner_Init(Actor* thisx, PlayState* play);
void EnPartner_Destroy(Actor* thisx, PlayState* play);
void EnPartner_Update(Actor* thisx, PlayState* play);
void EnPartner_Draw(Actor* thisx, PlayState* play);
void EnPartner_SpawnSparkles(EnPartner* this, PlayState* play, s32 sparkleLife);

void func_808328EC(Player* this, u16 sfxId);
void Player_RequestQuake(PlayState* play, s32 speed, s32 y, s32 countdown);
s32 spawn_boomerang_ivan(EnPartner* this, PlayState* play);

static InitChainEntry sInitChain[] = {
    ICHAIN_VEC3F_DIV1000(scale, 8, ICHAIN_STOP),
};

static Color_RGBAf sInnerColors[] = {
    { 255.0f, 255.0f, 255.0f, 255.0f },
};

static Color_RGBAf sOuterColors[] = {
    { 0.0f, 255.0f, 0.0f, 255.0f },
};

static ColliderCylinderInit sCylinderInit = {
    {
        COLTYPE_NONE,
        AT_ON | AT_TYPE_PLAYER,
        AC_ON | AC_TYPE_PLAYER,
        OC1_ON | OC1_TYPE_ALL,
        OC2_TYPE_2,
        COLSHAPE_CYLINDER,
    },
    {
        ELEMTYPE_UNK0,
        { 0x00020002, 0x00, 0x01 },
        { 0x4FC00748, 0x00, 0x00 },
        TOUCH_ON | TOUCH_SFX_NORMAL,
        BUMP_ON,
        OCELEM_ON,
    },
    { 12, 27, 0, { 0, 0, 0 } },
};

static CollisionCheckInfoInit sCCInfoInit = { 0, 12, 60, MASS_HEAVY };

void EnPartner_Init(Actor* thisx, PlayState* play) {
    EnPartner* this = (EnPartner*)thisx;
    s32 pad;
    Player* player = GET_PLAYER(play);
    s32 i;

    this->usedItem = 0xFF;
    this->canMove = 1;
    this->shouldDraw = 1;
    this->hookshotTarget = NULL;
    GET_PLAYER(play)->ivanFloating = 0;

    this->innerColor.r = 255.0f;
    this->innerColor.g = 255.0f;
    this->innerColor.b = 255.0f;
    this->innerColor.a = 255.0f;

    this->outerColor.r = 0.0f;
    this->outerColor.g = 255.0f;
    this->outerColor.b = 0.0f;
    this->outerColor.a = 255.0f;

    this->usedItemButton = 0xFF;

    Collider_InitCylinder(play, &this->collider);
    Collider_SetCylinder(play, &this->collider, &this->actor, &sCylinderInit);
    Collider_UpdateCylinder(&this->actor, &this->collider);
    CollisionCheck_SetInfo(&this->actor.colChkInfo, NULL, &sCCInfoInit);
    this->actor.colChkInfo.mass = MASS_HEAVY;
    this->collider.base.ocFlags1 |= OC1_TYPE_PLAYER;
    this->collider.info.toucher.damage = 1;
    GET_PLAYER(play)->ivanDamageMultiplier = 1;

    Actor_ProcessInitChain(thisx, sInitChain);
    SkelAnime_Init(play, &this->skelAnime, &gFairySkel, &gFairyAnim, this->jointTable, this->morphTable, 15);
    ActorShape_Init(&thisx->shape, 1000.0f, ActorShadow_DrawCircle, 15.0f);
    thisx->shape.shadowAlpha = 0xFF;

    Lights_PointGlowSetInfo(&this->lightInfoGlow, thisx->world.pos.x, thisx->world.pos.y, thisx->world.pos.z, 200, 255,
                            200, 0);
    this->lightNodeGlow = LightContext_InsertLight(play, &play->lightCtx, &this->lightInfoGlow);

    Lights_PointNoGlowSetInfo(&this->lightInfoNoGlow, thisx->world.pos.x, thisx->world.pos.y, thisx->world.pos.z, 200,
                              255, 200, 0);
    this->lightNodeNoGlow = LightContext_InsertLight(play, &play->lightCtx, &this->lightInfoNoGlow);

	thisx->room = -1;
}

void EnPartner_Destroy(Actor* thisx, PlayState* play) {
    s32 pad;
    EnPartner* this = (EnPartner*)thisx;

    LightContext_RemoveLight(play, &play->lightCtx, this->lightNodeGlow);
    LightContext_RemoveLight(play, &play->lightCtx, this->lightNodeNoGlow);

    Collider_DestroyCylinder(play, &this->collider);

    ResourceMgr_UnregisterSkeleton(&this->skelAnime);
}

void EnPartner_UpdateLights(EnPartner* this, PlayState* play) {
    s16 glowLightRadius = 100;
    s16 lightRadius = 200;

    if (this->shouldDraw == 0) {
        glowLightRadius = 0;
        lightRadius = 0;
    }

    Player* player;

    player = GET_PLAYER(play);
    Lights_PointNoGlowSetInfo(&this->lightInfoNoGlow, player->actor.world.pos.x, (s16)(player->actor.world.pos.y) + 69,
                              player->actor.world.pos.z, 200, 255, 200, lightRadius);

    Lights_PointGlowSetInfo(&this->lightInfoGlow, this->actor.world.pos.x, this->actor.world.pos.y + 9,
                            this->actor.world.pos.z, 200, 255, 200, glowLightRadius);

    Actor_SetScale(&this->actor, this->actor.scale.x);
}

void EnPartner_SpawnSparkles(EnPartner* this, PlayState* play, s32 sparkleLife) {
    static Vec3f sparkleVelocity = { 0.0f, -0.05f, 0.0f };
    static Vec3f sparkleAccel = { 0.0f, -0.025f, 0.0f };
    s32 pad;
    Vec3f sparklePos;
    Color_RGBA8 primColor;
    Color_RGBA8 envColor;

    sparklePos.x = Rand_CenteredFloat(6.0f) + this->actor.world.pos.x;
    sparklePos.y = (Rand_ZeroOne() * 6.0f) + this->actor.world.pos.y + 5;
    sparklePos.z = Rand_CenteredFloat(6.0f) + this->actor.world.pos.z;

    primColor.r = this->innerColor.r;
    primColor.g = this->innerColor.g;
    primColor.b = this->innerColor.b;

    envColor.r = this->outerColor.r;
    envColor.g = this->outerColor.g;
    envColor.b = this->outerColor.b;

    EffectSsKiraKira_SpawnDispersed(play, &sparklePos, &sparkleVelocity, &sparkleAccel, &primColor, &envColor,
                                    1500, sparkleLife);
}

Vec3f Vec3fNormalize(Vec3f vec) {
    f32 norm = sqrt((vec.x * vec.x) + (vec.y * vec.y) + (vec.z * vec.z));

    if (norm != 0.0f) {
        vec.x /= norm;
        vec.y /= norm;
        vec.z /= norm;
    } else {
        vec.x = vec.y = vec.z = 0.0f;
    }

    return vec;
}

void CenterIvanOnLink(Actor* thisx, PlayState* play) {
    EnPartner* this = (EnPartner*)thisx;
    this->actor.world.pos = GET_PLAYER(play)->actor.world.pos;
    this->actor.world.pos.y += Player_GetHeight(GET_PLAYER(play)) + 5.0f;
}

static u8 magicArrowCosts[] = { 0, 4, 4, 8 };

void UseBow(Actor* thisx, PlayState* play, u8 started, u8 arrowType) {
    EnPartner* this = (EnPartner*)thisx;

    if (started == 1) {
        func_808328EC(this, NA_SE_PL_CHANGE_ARMS);
        this->canMove = 0;
    } else if (started == 0) {
        if (this->itemTimer <= 0) {
            if (AMMO(ITEM_BOW) > 0) {
                if (arrowType >= 1 && !Magic_RequestChange(play, magicArrowCosts[arrowType], MAGIC_CONSUME_NOW)) {
                    Sfx_PlaySfxCentered(NA_SE_SY_ERROR);
                    this->canMove = 1;
                    return;
                }

                this->itemTimer = 10;

                Actor* newarrow = Actor_SpawnAsChild(
                    &play->actorCtx, &this->actor, play, ACTOR_EN_ARROW, this->actor.world.pos.x,
                    this->actor.world.pos.y + 7, this->actor.world.pos.z, 0, this->actor.world.rot.y, 0, ARROW_NORMAL);

                switch (arrowType) {
                    case 1:
                        newarrow->params = ARROW_FIRE;
                        break;
                    case 2:
                        newarrow->params = ARROW_ICE;
                        break;
                    case 3:
                        newarrow->params = ARROW_LIGHT;
                        break;
                }

                GET_PLAYER(play)->unk_A73 = 4;
                newarrow->parent = NULL;
                Inventory_ChangeAmmo(ITEM_BOW, -1);
            }
        }
    }
}

void UseSlingshot(Actor* thisx, PlayState* play, u8 started) {
    EnPartner* this = (EnPartner*)thisx;

    if (started == 1) {
        func_808328EC(this, NA_SE_PL_CHANGE_ARMS);
        this->canMove = 0;
    } else if (started == 0) {
        if (this->itemTimer <= 0) {
            if (AMMO(ITEM_SLINGSHOT) > 0) {
                this->itemTimer = 10;
                Actor* newarrow = Actor_SpawnAsChild(
                    &play->actorCtx, &this->actor, play, ACTOR_EN_ARROW, this->actor.world.pos.x,
                    this->actor.world.pos.y + 7, this->actor.world.pos.z, 0, this->actor.world.rot.y, 0, ARROW_SEED);
                GET_PLAYER(play)->unk_A73 = 4;
                newarrow->parent = NULL;
                Inventory_ChangeAmmo(ITEM_SLINGSHOT, -1);
            } else {
                Sfx_PlaySfxCentered(NA_SE_SY_ERROR);
            }
        }
    }
}

void UseBombs(Actor* thisx, PlayState* play, u8 started) {
    EnPartner* this = (EnPartner*)thisx;

    if (this->itemTimer <= 0) {
        if (started == 1) {
            if (AMMO(ITEM_BOMB) > 0 && play->actorCtx.actorLists[ACTORCAT_EXPLOSIVE].length < 3) {
                this->itemTimer = 10;
                Actor_Spawn(&play->actorCtx, play, ACTOR_EN_BOM, this->actor.world.pos.x, this->actor.world.pos.y + 7,
                            this->actor.world.pos.z, 0, 0, 0, 0, false);
                Inventory_ChangeAmmo(ITEM_BOMB, -1);
            } else {
                Sfx_PlaySfxCentered(NA_SE_SY_ERROR);
            }
        }
    }
}

static Vec3f D_80854A40 = { 0.0f, 40.0f, 45.0f };

void UseHammer(Actor* thisx, PlayState* play, u8 started) {
    EnPartner* this = (EnPartner*)thisx;

    if (this->itemTimer <= 0) {
        if (started == 1) {
            this->itemTimer = 10;
            static Vec3f zeroVec = { 0.0f, 0.0f, 0.0f };
            Vec3f shockwavePos = this->actor.world.pos;

            Player_RequestQuake(play, 27767, 7, 20);
            Player_PlaySfx(&this->actor, NA_SE_IT_HAMMER_HIT);

            EffectSsBlast_SpawnWhiteShockwave(play, &shockwavePos, &zeroVec, &zeroVec);

            if (this->actor.xzDistToPlayer < 100.0f && this->actor.yDistToPlayer < 35.0f) {
                func_8002F71C(play, &this->actor, 8.0f, this->actor.yawTowardsPlayer, 8.0f);
            }
        }
    }
}

void UseBombchus(Actor* thisx, PlayState* play, u8 started) {
    EnPartner* this = (EnPartner*)thisx;

    if (this->itemTimer <= 0) {
        if (started == 1) {
            if (AMMO(ITEM_BOMBCHU) > 0) {
                this->itemTimer = 10;
                EnBom* bomb = Actor_Spawn(&play->actorCtx, play, ACTOR_EN_BOM, this->actor.world.pos.x, this->actor.world.pos.y + 7,
                            this->actor.world.pos.z, 0, 0, 0, 0, false);
                bomb->timer = 0;
                Inventory_ChangeAmmo(ITEM_BOMBCHU, -1);
            } else {
                Sfx_PlaySfxCentered(NA_SE_SY_ERROR);
            }
        }
    }
}

static Vec3f D_808547A4 = { 0.0f, 0.5f, 0.0f };
static Vec3f D_808547B0 = { 0.0f, 0.5f, 0.0f };

static Color_RGBA8 D_808547BC = { 255, 255, 100, 255 };
static Color_RGBA8 D_808547C0 = { 255, 50, 0, 0 };

void UseDekuStick(Actor* thisx, PlayState* play, u8 started) {
    EnPartner* this = (EnPartner*)thisx;

    if (this->itemTimer <= 0) {
        if (started == 1) {
            if (AMMO(ITEM_STICK) > 0) {
                func_808328EC(this, NA_SE_EV_FLAME_IGNITION);
            } else {
                Sfx_PlaySfxCentered(NA_SE_SY_ERROR);
            }
        }

        if (started == 2) {
            if (AMMO(ITEM_STICK) > 0) {
                this->stickWeaponInfo.tip = this->actor.world.pos;
                this->stickWeaponInfo.tip.y += 7.0f;

                func_8002836C(play, &this->stickWeaponInfo.tip, &D_808547A4, &D_808547B0, &D_808547BC, &D_808547C0,
                              200.0f, 0, 8);

                CollisionCheck_SetAT(play, &play->colChkCtx, &this->collider.base);

                if (this->damageTimer <= 0) {
                    Inventory_ChangeAmmo(ITEM_STICK, -1);
                    this->damageTimer = 20;
                } else {
                    this->damageTimer--;
                }
            }
        }
    }
}

void UseNuts(Actor* thisx, PlayState* play, u8 started) {
    EnPartner* this = (EnPartner*)thisx;

    if (this->itemTimer <= 0) {
        if (started == 1) {
            if (AMMO(ITEM_NUT) > 0) {
                this->itemTimer = 10;
                Actor_Spawn(&play->actorCtx, play, ACTOR_EN_ARROW, this->actor.world.pos.x, this->actor.world.pos.y + 7,
                            this->actor.world.pos.z, 0x1000, this->actor.world.rot.y, 0, ARROW_NUT, false);
                Inventory_ChangeAmmo(ITEM_NUT, -1);
            } else {
                Sfx_PlaySfxCentered(NA_SE_SY_ERROR);
            }
        }
    }
}

void UseHookshot(Actor* thisx, PlayState* play, u8 started) {
    EnPartner* this = (EnPartner*)thisx;

    if (this->itemTimer <= 0) {
        if (started == 1) {
            func_808328EC(this, NA_SE_PL_CHANGE_ARMS);
            this->canMove = 0;
            this->hookshotTarget =
                Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_OBJ_HSBLOCK, this->actor.world.pos.x,
                                   this->actor.world.pos.y + 7.5f, this->actor.world.pos.z, this->actor.world.rot.x,
                                   this->actor.world.rot.y, this->actor.world.rot.z, 2);
            this->hookshotTarget->scale.x = 0.05f;
            this->hookshotTarget->scale.y = 0.05f;
            this->hookshotTarget->scale.z = 0.05f;
        } else if (started == 0) {
            Actor_Kill(this->hookshotTarget);
            this->hookshotTarget = NULL;
            func_808328EC(this, NA_SE_PL_CHANGE_ARMS);
            this->canMove = 1;
        } else if (started == 2) {
            this->hookshotTarget->shape.rot.y = this->actor.world.rot.y;
        }
    }
}

void UseOcarina(Actor* thisx, PlayState* play, u8 started) {
    EnPartner* this = (EnPartner*)thisx;

    if (this->itemTimer <= 0) {
        if (started == 1) {
            Audio_PlaySoundTransposed(&this->actor.projectedPos, NA_SE_VO_NA_HELLO_2, -6);
        }
    }
}

void UseBoomerang(Actor* thisx, PlayState* play, u8 started) {
    EnPartner* this = (EnPartner*)thisx;

    if (this->itemTimer <= 0) {
        if (started == 1) {
            this->itemTimer = 20;
            spawn_boomerang_ivan(&this->actor, play);
        }
    }
}

void UseLens(Actor* thisx, PlayState* play, u8 started) {
    EnPartner* this = (EnPartner*)thisx;

    if (this->itemTimer <= 0) {
        if (started == 1) {
            Sfx_PlaySfxCentered(NA_SE_SY_GLASSMODE_ON);
            this->shouldDraw = 0;
        }

        if (started == 0) {
            Sfx_PlaySfxCentered(NA_SE_SY_GLASSMODE_OFF);
            this->shouldDraw = 1;
        }
    }
}

void UseBeans(Actor* thisx, PlayState* play, u8 started) {
    EnPartner* this = (EnPartner*)thisx;

    if (this->itemTimer <= 0) {
        if (started == 1) {
            this->entry = ItemTable_Retrieve(GI_BEAN);
            if (play->actorCtx.titleCtx.alpha <= 0) {
                if (gSaveContext.rupees >= 100 && GiveItemEntryWithoutActor(play, this->entry)) {
                    Rupees_ChangeBy(-100);
                } else {
                    Sfx_PlaySfxCentered(NA_SE_SY_ERROR);
                }
            }
        }
    }
}

void UseSpell(Actor* thisx, PlayState* play, u8 started, u8 spellType) {
    EnPartner* this = (EnPartner*)thisx;

    if (gSaveContext.magic > 0) {
        if (this->itemTimer <= 0 && this->usedSpell == 0) {
            if (started == 1) {
                this->usedSpell = spellType;
            }
        }

        if (started == 0 && this->usedSpell != 0) {
            this->itemTimer = 10;
            gSaveContext.magicState = MAGIC_STATE_RESET;

            switch (this->usedSpell) {
                case 1:
                    GET_PLAYER(play)->ivanDamageMultiplier = 1;
                    break;
                case 3:
                    GET_PLAYER(play)->ivanFloating = 0;
                    break;
            }
            
            this->usedSpell = 0;
        }

        if (started == 2) {
            if (this->usedSpell != 0) {
                Vec3f spE4[3];
                Vec3f newBasePos[3];

                switch (this->usedSpell) {
                    case 1: //Din's
                        GET_PLAYER(play)->ivanDamageMultiplier = 2;
                        break;
                    case 2: //Nayru's
                        GET_PLAYER(play)->invincibilityTimer = -10;
                        break;
                    case 3: //Farore's
                        GET_PLAYER(play)->hoverBootsTimer = 10;
                        GET_PLAYER(play)->ivanFloating = 1;
                        break;
                }

                gSaveContext.magicState = MAGIC_STATE_METER_FLASH_1;
                this->magicTimer--;
                if (this->magicTimer <= 0) {
                    gSaveContext.magic--;
                    this->magicTimer = 20;
                    if (gSaveContext.magic <= 0) {
                        gSaveContext.magic = 0;

                        this->itemTimer = 10;
                        this->usedSpell = 0;
                        gSaveContext.magicState = MAGIC_STATE_RESET;
                    }
                }
            }
        }
    }
}

void UseItem(uint8_t usedItem, u8 started, Actor* thisx, PlayState* play) {
    EnPartner* this = (EnPartner*)thisx;

    if (this->usedItem != 0xFF && this->itemTimer <= 0) {
        switch (usedItem) {
            case ITEM_STICK:
                UseDekuStick(this, play, started);
                break;
            case ITEM_BOMB:
                UseBombs(this, play, started);
                break;
            case ITEM_BOMBCHU:
                UseBombchus(this, play, started);
                break;
            case ITEM_NUT:
                UseNuts(this, play, started);
                break;
            case ITEM_BOW:
                UseBow(this, play, started, 0);
                break;
            case ITEM_ARROW_FIRE:
                UseBow(this, play, started, 1);
                break;
            case ITEM_ARROW_ICE:
                UseBow(this, play, started, 2);
                break;
            case ITEM_ARROW_LIGHT:
                UseBow(this, play, started, 3);
                break;
            case ITEM_SLINGSHOT:
                UseSlingshot(this, play, started);
                break;
            case ITEM_OCARINA_FAIRY:
            case ITEM_OCARINA_TIME:
                UseOcarina(this, play, started);
                break;
            case ITEM_HOOKSHOT:
            case ITEM_LONGSHOT:
                UseHookshot(this, play, started);
                break;
            case ITEM_DINS_FIRE:
                UseSpell(this, play, started, 1);
                break;
            case ITEM_NAYRUS_LOVE:
                UseSpell(this, play, started, 2);
                break;
            case ITEM_FARORES_WIND:
                UseSpell(this, play, started, 3);
                break;
            case ITEM_HAMMER:
                UseHammer(this, play, started);
                break;
            case ITEM_BOOMERANG:
                UseBoomerang(this, play, started);
                break;
            case ITEM_LENS:
                UseLens(this, play, started);
                break;
            case ITEM_BEAN:
                UseBeans(this, play, started);
                break;
        }
    }

    if (started == 0) {
        this->usedItem = 0xFF;
    }
}

void EnPartner_Update(Actor* thisx, PlayState* play) {
    s32 pad;
    EnPartner* this = (EnPartner*)thisx;

    Input sControlInput = play->state.input[this->actor.params];

    f32 relX = sControlInput.cur.stick_x / 10.0f * (CVarGetInteger(CVAR_ENHANCEMENT("MirroredWorld"), 0) ? -1 : 1);
    f32 relY = sControlInput.cur.stick_y / 10.0f;

    Vec3f camForward = { GET_ACTIVE_CAM(play)->at.x - GET_ACTIVE_CAM(play)->eye.x, 0.0f,
                         GET_ACTIVE_CAM(play)->at.z - GET_ACTIVE_CAM(play)->eye.z };
    camForward = Vec3fNormalize(camForward);

    Vec3f camRight = { -camForward.z, 0.0f, camForward.x };

    this->actor.velocity.x = 0;
    this->actor.velocity.y = 0;
    this->actor.velocity.z = 0;

    this->actor.velocity.x += camRight.x * relX;
    this->actor.velocity.z += camRight.z * relX;
    this->actor.velocity.x += camForward.x * relY;
    this->actor.velocity.z += camForward.z * relY;

    if (this->actor.velocity.x != 0 || this->actor.velocity.z != 0) {
        int16_t finalDir = Math_Atan2S(-this->actor.velocity.x, this->actor.velocity.z) - 0x4000;
        Math_SmoothStepToS(&this->actor.world.rot.y, finalDir, 2, 10000, 0);
        Math_SmoothStepToS(&this->actor.shape.rot.y, finalDir, 2, 10000, 0);
    }

    if (!this->canMove) {
        relX = 0;
        relY = 0;
    }

    Math_SmoothStepToF(&this->actor.speedXZ, sqrtf(SQ(relX) + SQ(relY)), 1.0f, 1.3f, 0.0f);

    if (this->shouldDraw == 1) {
        thisx->shape.shadowAlpha = 0xFF;
        EnPartner_SpawnSparkles(this, play, 12);
    } else {
        thisx->shape.shadowAlpha = 0;
    }

    if (CHECK_BTN_ALL(sControlInput.cur.button, BTN_A) && this->canMove) {
        Math_SmoothStepToF(&this->yVelocity, 6.0f, 1.0f, 1.5f, 0.0f);
    } else if (CHECK_BTN_ALL(sControlInput.cur.button, BTN_B) && this->canMove) {
        Math_SmoothStepToF(&this->yVelocity, -6.0f, 1.0f, 1.5f, 0.0f);
    } else {
        Math_SmoothStepToF(&this->yVelocity, 0.0f, 1.0f, 1.5f, 0.0f);
    }

    this->actor.gravity = this->yVelocity;

    if (this->canMove == 1) {
        Actor_MoveXZGravity(&this->actor);
        Actor_UpdateBgCheckInfo(play, &this->actor, 19.0f, 20.0f, 0.0f, 5);
    }

    if (this->usedSpell != 0) {
        func_8002F974(thisx, NA_SE_PL_MAGIC_SOUL_NORMAL - SFX_FLAG);
    }

    if (!Player_InCsMode(play)) {
        // Collect drops & rupees
        Actor* itemActor = play->actorCtx.actorLists[ACTORCAT_MISC].head;
        while (itemActor != NULL) {
            if (itemActor->id == ACTOR_EN_ITEM00) {
                if (itemActor->params == ITEM00_RUPEE_GREEN || itemActor->params == ITEM00_RUPEE_BLUE ||
                    itemActor->params == ITEM00_RUPEE_RED || itemActor->params == ITEM00_RUPEE_PURPLE ||
                    itemActor->params == ITEM00_RUPEE_ORANGE || itemActor->params == ITEM00_HEART ||
                    itemActor->params == ITEM00_BOMBS_A || itemActor->params == ITEM00_BOMBS_B ||
                    itemActor->params == ITEM00_ARROWS_SINGLE || itemActor->params == ITEM00_ARROWS_SMALL ||
                    itemActor->params == ITEM00_ARROWS_MEDIUM || itemActor->params == ITEM00_ARROWS_LARGE ||
                    itemActor->params == ITEM00_BOMBCHU || itemActor->params == ITEM00_MAGIC_SMALL ||
                    itemActor->params == ITEM00_MAGIC_LARGE || itemActor->params == ITEM00_NUTS ||
                    itemActor->params == ITEM00_STICK) {
                    f32 distanceToObject = Actor_WorldDistXYZToActor(&this->actor, itemActor);
                    if (distanceToObject <= 20.0f) {
                        itemActor->world.pos = GET_PLAYER(play)->actor.world.pos;
                        break;
                    }
                }
            }
            itemActor = itemActor->next;
        }

        itemActor = play->actorCtx.actorLists[ACTORCAT_ITEMACTION].head;
        while (itemActor != NULL) {
            if (itemActor->id == ACTOR_EN_SI) {
                f32 distanceToObject = Actor_WorldDistXYZToActor(&this->actor, itemActor);
                if (distanceToObject <= 20.0f) {
                    EnSi* ensi = (EnSi*)itemActor;
                    ensi->collider.base.ocFlags2 = OC2_HIT_PLAYER;
                    break;
                }
            }
            itemActor = itemActor->next;
        }
    }

    if (this->itemTimer > 0) {
        this->itemTimer--;
        if (this->itemTimer <= 0) {
            this->canMove = 1;
        }
    }

    if (!Player_InCsMode(play)) {
        uint8_t pressed = 0;
        uint8_t released = 0;
        uint8_t current = 0;

        uint16_t partnerButtons[7] = { BTN_CLEFT, BTN_CDOWN, BTN_CRIGHT, BTN_DUP, BTN_DDOWN, BTN_DLEFT, BTN_DRIGHT};
        uint8_t buttonMax = 3;
        if (CVarGetInteger(CVAR_ENHANCEMENT("DpadEquips"), 0) != 0) {
            buttonMax = ARRAY_COUNT(gSaveContext.equips.cButtonSlots);
        }

        if (this->usedItem == 0xFF && this->itemTimer <= 0) {
            for (uint8_t i = 0; i < buttonMax; i++) {
                if (CHECK_BTN_ALL(sControlInput.press.button, partnerButtons[i])) {
                    this->usedItem = gSaveContext.equips.buttonItems[i+1];
                    this->usedItemButton = i;
                    pressed = 1;
                }
            }
        }

        if (this->usedItem != 0xFF) {
            for (uint8_t i = 0; i < buttonMax; i++) {
                if (CHECK_BTN_ALL(sControlInput.cur.button, partnerButtons[i]) && this->usedItemButton == i) {
                    current = 1;
                }
            }
        }

        if (this->usedItem != 0xFF) {
            for (uint8_t i = 0; i < buttonMax; i++) {
                if (CHECK_BTN_ALL(sControlInput.rel.button, partnerButtons[i]) && this->usedItemButton == i) {
                    released = 1;
                }
            }
        }

        if (pressed == 1) {
            UseItem(this->usedItem, 1, this, play);
        } else if (released == 1) {
            UseItem(this->usedItem, 0, this, play);
            this->usedItemButton = 0xFF;
        } else if (current == 1) {
            UseItem(this->usedItem, 2, this, play);
        }
    } else {
        UseItem(this->usedItem, 0, this, play);
        this->usedItem = 0xFF;
        this->itemTimer = 10;
    }

    if (CHECK_BTN_ALL(sControlInput.press.button, BTN_Z) && this->canMove) {
        Audio_PlayActorSound2(&this->actor, NA_SE_EV_FAIRY_DASH);
    }

    if (CHECK_BTN_ALL(sControlInput.cur.button, BTN_Z) && this->canMove) {
        CenterIvanOnLink(this, play);
    } else if (this->canMove == 1 && this->hookshotTarget == NULL) {
        Collider_UpdateCylinder(&this->actor, &this->collider);
        CollisionCheck_SetAC(play, &play->colChkCtx, &this->collider.base);
        CollisionCheck_SetOC(play, &play->colChkCtx, &this->collider.base);
    }

    if (CVarGetInteger(CVAR_COSMETIC("Ivan.IdlePrimary.Changed"), 0)) {
        Color_RGB8 ivanColor1 = CVarGetColor24(CVAR_COSMETIC("Ivan.IdlePrimary.Value"), (Color_RGB8){ 255, 255, 255 });
        this->innerColor.r = ivanColor1.r;
        this->innerColor.g = ivanColor1.g;
        this->innerColor.b = ivanColor1.b;
    } else {
        this->innerColor.r = 255;
        this->innerColor.g = 255;
        this->innerColor.b = 255;
    }

    if (CVarGetInteger(CVAR_COSMETIC("Ivan.IdleSecondary.Changed"), 0)) {
        Color_RGB8 ivanColor2 = CVarGetColor24(CVAR_COSMETIC("Ivan.IdleSecondary.Value"), (Color_RGB8){ 0, 255, 0 });
        this->outerColor.r = ivanColor2.r;
        this->outerColor.g = ivanColor2.g;
        this->outerColor.b = ivanColor2.b;
    } else {
        this->outerColor.r = 0;
        this->outerColor.g = 255;
        this->outerColor.b = 0;
    }

    SkelAnime_Update(&this->skelAnime);

    EnPartner_UpdateLights(this, play);
}

s32 EnPartner_OverrideLimbDraw(PlayState* play, s32 limbIndex, Gfx** dList, Vec3f* pos, Vec3s* rot, void* thisx,
                           Gfx** gfx) {
    static Vec3f zeroVec = { 0.0f, 0.0f, 0.0f };
    s32 pad;
    f32 scale;
    Vec3f mtxMult;
    EnPartner* this = (EnPartner*)thisx;

    if (limbIndex == 8) {
        scale = ((Math_SinS(4096) * 0.1f) + 1.0f) * 0.012f;
        scale *= (this->actor.scale.x * 124.99999f);
        Matrix_MultVec3f(&zeroVec, &mtxMult);
        Matrix_Translate(mtxMult.x, mtxMult.y, mtxMult.z, MTXMODE_NEW);
        Matrix_Scale(scale, scale, scale, MTXMODE_APPLY);
    }

    return false;
}

void DrawOrb(Actor* thisx, PlayState* play, u8 color) {
    EnPartner* this = (EnPartner*)thisx;
    Vec3f pos;
    Player* player = GET_PLAYER(play);
    s32 pad;
    f32 sp6C = play->state.frames & 0x1F;

    pos = this->actor.world.pos;
    pos.y += 5.0f;

    pos.x -= (this->actor.scale.x * 300.0f * Math_SinS(Camera_GetCamDirYaw(GET_ACTIVE_CAM(play))) *
              Math_CosS(Camera_GetCamDirPitch(GET_ACTIVE_CAM(play))));
    pos.y -= (this->actor.scale.x * 300.0f * Math_SinS(Camera_GetCamDirPitch(GET_ACTIVE_CAM(play))));
    pos.z -= (this->actor.scale.x * 300.0f * Math_CosS(Camera_GetCamDirYaw(GET_ACTIVE_CAM(play))) *
              Math_CosS(Camera_GetCamDirPitch(GET_ACTIVE_CAM(play))));

    OPEN_DISPS(play->state.gfxCtx);

    Gfx_SetupDL_25Xlu(play->state.gfxCtx);

    gDPSetPrimColor(POLY_XLU_DISP++, 0, 0x80, 255, 255, 255, 255);

    switch (color) {
        case 1:
            gDPSetEnvColor(POLY_XLU_DISP++, 255, 0, 0, 255);
            break;
        case 2:
            gDPSetEnvColor(POLY_XLU_DISP++, 0, 0, 255, 255);
            break;
        case 3:
            gDPSetEnvColor(POLY_XLU_DISP++, 0, 255, 0, 255);
            break;
    }

    Matrix_Translate(pos.x, pos.y, pos.z, MTXMODE_NEW);
    Matrix_Scale(this->actor.scale.x * 3.0f, this->actor.scale.y * 3.0f, this->actor.scale.z * 3.0f, MTXMODE_APPLY);
    Matrix_Mult(&play->billboardMtxF, MTXMODE_APPLY);
    Matrix_Push();
    gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
    Matrix_RotateZ(sp6C * (M_PI / 32), MTXMODE_APPLY);
    gSPDisplayList(POLY_XLU_DISP++, gEffFlash1DL);
    Matrix_Pop();
    Matrix_RotateZ(-sp6C * (M_PI / 32), MTXMODE_APPLY);
    gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
    gSPDisplayList(POLY_XLU_DISP++, gEffFlash1DL);

    CLOSE_DISPS(play->state.gfxCtx);
}


void EnPartner_Draw(Actor* thisx, PlayState* play) {
    s32 pad;
    f32 alphaScale;
    s32 envAlpha;
    EnPartner* this = (EnPartner*)thisx;
    s32 pad1;
    Gfx* dListHead;
    Player* player = GET_PLAYER(play);

    if (play->pauseCtx.state != 0 && this->usedItem != 0xFF) {
        UseItem(this->usedItem, 0, this, play);
        this->usedItem = 0xFF;
    }

    if (this->shouldDraw == 0) {
        return;
    }

    dListHead = Graph_Alloc(play->state.gfxCtx, sizeof(Gfx) * 4);

    OPEN_DISPS(play->state.gfxCtx);

    Gfx_SetupDL_27Xlu(play->state.gfxCtx);

    envAlpha = (50) & 0x1FF;
    envAlpha = (envAlpha > 255) ? 511 - envAlpha : envAlpha;

    alphaScale = 1.0f;

    gSPSegment(POLY_XLU_DISP++, 0x08, dListHead);
    gDPPipeSync(dListHead++);
    gDPSetPrimColor(dListHead++, 0, 0x01, (u8)this->innerColor.r, (u8)this->innerColor.g, (u8)this->innerColor.b,
                    (u8)(this->innerColor.a * alphaScale));

    gDPSetRenderMode(dListHead++, G_RM_PASS, G_RM_ZB_CLD_SURF2);

    gSPEndDisplayList(dListHead++);
    gDPSetEnvColor(POLY_XLU_DISP++, (u8)this->outerColor.r, (u8)this->outerColor.g, (u8)this->outerColor.b,
                   (u8)(envAlpha * alphaScale));
    POLY_XLU_DISP = SkelAnime_DrawSkeleton2(play, &this->skelAnime,
                                   EnPartner_OverrideLimbDraw, NULL, this, POLY_XLU_DISP);

    CLOSE_DISPS(play->state.gfxCtx);

    if (this->usedSpell > 0) {
        DrawOrb(this, play, this->usedSpell);
    }
}
