#include "global.h"
#include "vt.h"

#include "overlays/actors/ovl_Arms_Hook/z_arms_hook.h"
#include "overlays/actors/ovl_En_Arrow/z_en_arrow.h"
#include "overlays/actors/ovl_En_Part/z_en_part.h"
#include "objects/gameplay_keep/gameplay_keep.h"
#include "objects/gameplay_dangeon_keep/gameplay_dangeon_keep.h"
#include "objects/object_bdoor/object_bdoor.h"
#include "soh/frame_interpolation.h"
#include "soh/Enhancements/cosmetics/cosmeticsTypes.h"
#include "soh/Enhancements/enemyrandomizer.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/Enhancements/nametag.h"

#include "soh/ActorDB.h"
#include "soh/OTRGlobals.h"

#include <string.h>
#include <stdlib.h>
#include <assert.h>

#if defined(_MSC_VER) || defined(__GNUC__)
#include "textures/place_title_cards/g_pn_49.h"
#include "textures/place_title_cards/g_pn_01.h"
#include "textures/place_title_cards/g_pn_02.h"
#include "textures/place_title_cards/g_pn_03.h"
#include "textures/place_title_cards/g_pn_04.h"
#include "textures/place_title_cards/g_pn_05.h"
#include "textures/place_title_cards/g_pn_06.h"
#include "textures/place_title_cards/g_pn_07.h"
#include "textures/place_title_cards/g_pn_08.h"
#include "textures/place_title_cards/g_pn_09.h"
#include "textures/place_title_cards/g_pn_10.h"
#include "textures/place_title_cards/g_pn_11.h"
#include "textures/place_title_cards/g_pn_12.h"
#include "textures/place_title_cards/g_pn_13.h"
#include "textures/place_title_cards/g_pn_14.h"
#include "textures/place_title_cards/g_pn_15.h"
#include "textures/place_title_cards/g_pn_16.h"
#include "textures/place_title_cards/g_pn_17.h"
#include "textures/place_title_cards/g_pn_18.h"
#include "textures/place_title_cards/g_pn_19.h"
#include "textures/place_title_cards/g_pn_20.h"
#include "textures/place_title_cards/g_pn_21.h"
#include "textures/place_title_cards/g_pn_22.h"
#include "textures/place_title_cards/g_pn_23.h"
#include "textures/place_title_cards/g_pn_24.h"
#include "textures/place_title_cards/g_pn_25.h"
#include "textures/place_title_cards/g_pn_26.h"
#include "textures/place_title_cards/g_pn_27.h"
#include "textures/place_title_cards/g_pn_28.h"
#include "textures/place_title_cards/g_pn_29.h"
#include "textures/place_title_cards/g_pn_30.h"
#include "textures/place_title_cards/g_pn_31.h"
#include "textures/place_title_cards/g_pn_32.h"
#include "textures/place_title_cards/g_pn_33.h"
#include "textures/place_title_cards/g_pn_34.h"
#include "textures/place_title_cards/g_pn_35.h"
#include "textures/place_title_cards/g_pn_36.h"
#include "textures/place_title_cards/g_pn_37.h"
#include "textures/place_title_cards/g_pn_38.h"
#include "textures/place_title_cards/g_pn_39.h"
#include "textures/place_title_cards/g_pn_40.h"
#include "textures/place_title_cards/g_pn_41.h"
#include "textures/place_title_cards/g_pn_42.h"
#include "textures/place_title_cards/g_pn_43.h"
#include "textures/place_title_cards/g_pn_44.h"
#include "textures/place_title_cards/g_pn_45.h"
#include "textures/place_title_cards/g_pn_46.h"
#include "textures/place_title_cards/g_pn_47.h"
#include "textures/place_title_cards/g_pn_48.h"
#include "textures/place_title_cards/g_pn_50.h"
#include "textures/place_title_cards/g_pn_51.h"
#include "textures/place_title_cards/g_pn_52.h"
#include "textures/place_title_cards/g_pn_53.h"
#include "textures/place_title_cards/g_pn_54.h"
#include "textures/place_title_cards/g_pn_55.h"
#include "textures/place_title_cards/g_pn_56.h"
#include "textures/place_title_cards/g_pn_57.h"
#endif

static CollisionPoly* sCurCeilingPoly;
static s32 sCurCeilingBgId;

// Used for animating the ice trap on the "Get Item" model.
f32 iceTrapScale;

// For Link's voice pitch SFX modifier
static f32 freqMultiplier = 1;

void ActorShape_Init(ActorShape* shape, f32 yOffset, ActorShadowFunc shadowDraw, f32 shadowScale) {
    shape->yOffset = yOffset;
    shape->shadowDraw = shadowDraw;
    shape->shadowScale = shadowScale;
    shape->shadowAlpha = 255;
}

void ActorShadow_Draw(Actor* actor, Lights* lights, PlayState* play, Gfx* dlist, Color_RGBA8* color) {
    f32 temp1;
    f32 temp2;
    MtxF sp60;

    if (actor->floorPoly != NULL) {
        temp1 = actor->world.pos.y - actor->floorHeight;

        if (temp1 >= -50.0f && temp1 < 500.0f) {
            OPEN_DISPS(play->state.gfxCtx);

            POLY_OPA_DISP = Gfx_SetupDL(POLY_OPA_DISP, 0x2C);

            gDPSetCombineLERP(POLY_OPA_DISP++, 0, 0, 0, PRIMITIVE, TEXEL0, 0, PRIMITIVE, 0, 0, 0, 0, COMBINED, 0, 0, 0,
                              COMBINED);

            temp1 = (temp1 < 0.0f) ? 0.0f : ((temp1 > 150.0f) ? 150.0f : temp1);
            temp2 = 1.0f - (temp1 * (1.0f / 350));

            if (color != NULL) {
                gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, color->r, color->g, color->b,
                                (u32)(actor->shape.shadowAlpha * temp2) & 0xFF);
            } else {
                gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 0, 0, 0, (u32)(actor->shape.shadowAlpha * temp2) & 0xFF);
            }

            func_80038A28(actor->floorPoly, actor->world.pos.x, actor->floorHeight, actor->world.pos.z, &sp60);
            Matrix_Put(&sp60);

            if (dlist != gCircleShadowDL) {
                Matrix_RotateY(actor->shape.rot.y * (M_PI / 0x8000), MTXMODE_APPLY);
            }

            temp2 = (1.0f - (temp1 * (1.0f / 350))) * actor->shape.shadowScale;
            Matrix_Scale(actor->scale.x * temp2, 1.0f, actor->scale.z * temp2, MTXMODE_APPLY);

            gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx),
                      G_MTX_MODELVIEW | G_MTX_LOAD);
            gSPDisplayList(POLY_OPA_DISP++, dlist);

            CLOSE_DISPS(play->state.gfxCtx);
        }
    }
}

void ActorShadow_DrawCircle(Actor* actor, Lights* lights, PlayState* play) {
    ActorShadow_Draw(actor, lights, play, gCircleShadowDL, NULL);
}

void ActorShadow_DrawWhiteCircle(Actor* actor, Lights* lights, PlayState* play) {
    static Color_RGBA8 white = { 255, 255, 255, 255 };

    ActorShadow_Draw(actor, lights, play, gCircleShadowDL, &white);
}

void ActorShadow_DrawHorse(Actor* actor, Lights* lights, PlayState* play) {
    ActorShadow_Draw(actor, lights, play, gHorseShadowDL, NULL);
}

void ActorShadow_DrawFoot(PlayState* play, Light* light, MtxF* arg2, s32 arg3, f32 arg4, f32 arg5, f32 arg6) {
    s32 pad1;
    f32 sp58;
    s32 pad2[2];

    OPEN_DISPS(play->state.gfxCtx);

    gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 0, 0, 0,
                    (u32)(((arg3 * 0.00005f) > 1.0f ? 1.0f : (arg3 * 0.00005f)) * arg4) & 0xFF);

    sp58 = Math_FAtan2F(light->l.dir[0], light->l.dir[2]);
    arg6 *= (4.5f - (light->l.dir[1] * 0.035f));
    arg6 = (arg6 < 1.0f) ? 1.0f : arg6;
    Matrix_Put(arg2);
    Matrix_RotateY(sp58, MTXMODE_APPLY);
    Matrix_Scale(arg5, 1.0f, arg5 * arg6, MTXMODE_APPLY);

    gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx),
              G_MTX_MODELVIEW | G_MTX_LOAD);
    gSPDisplayList(POLY_OPA_DISP++, gFootShadowDL);

    CLOSE_DISPS(play->state.gfxCtx);
}

void ActorShadow_DrawFeet(Actor* actor, Lights* lights, PlayState* play) {
    f32 distToFloor = actor->world.pos.y - actor->floorHeight;

    if (distToFloor > 20.0f) {
        f32 shadowScale = actor->shape.shadowScale;
        u8 shadowAlpha = actor->shape.shadowAlpha;
        f32 alphaRatio;

        actor->shape.shadowScale *= 0.3f;
        alphaRatio = (distToFloor - 20.0f) * 0.02f;
        actor->shape.shadowAlpha = (f32)actor->shape.shadowAlpha * CLAMP_MAX(alphaRatio, 1.0f);
        ActorShadow_DrawCircle(actor, lights, play);
        actor->shape.shadowScale = shadowScale;
        actor->shape.shadowAlpha = shadowAlpha;
    }

    if (distToFloor < 200.0f) {
        MtxF floorMtx;
        f32 floorHeight[2]; // One for each foot
        f32 distToFloor;
        f32 shadowAlpha;
        f32 shadowScaleX;
        f32 shadowScaleZ;
        Light* lightPtr;
        s32 lightNum;
        s32 lightNumMax;
        s32 i;
        s32 j;
        s32 numLights = lights->numLights - 2;
        Light* firstLightPtr = &lights->l.l[0];
        Vec3f* feetPosPtr = actor->shape.feetPos;
        f32* floorHeightPtr = floorHeight;

        OPEN_DISPS(play->state.gfxCtx);

        POLY_OPA_DISP = Gfx_SetupDL(POLY_OPA_DISP, 0x2C);

        actor->shape.feetFloorFlags = 0;

        for (i = 0; i < 2; i++) {
            feetPosPtr->y += 50.0f;
            *floorHeightPtr = func_800BFCB8(play, &floorMtx, feetPosPtr);
            feetPosPtr->y -= 50.0f;
            actor->shape.feetFloorFlags <<= 1;
            distToFloor = feetPosPtr->y - *floorHeightPtr;

            if ((-1.0f <= distToFloor) && (distToFloor < 500.0f)) {
                if (distToFloor <= 0.0f) {
                    actor->shape.feetFloorFlags++;
                }
                distToFloor = CLAMP_MAX(distToFloor, 30.0f);
                shadowAlpha = (f32)actor->shape.shadowAlpha * (1.0f - (distToFloor * (1.0f / 30.0f)));
                distToFloor = CLAMP_MAX(distToFloor, 30.0f);
                shadowScaleZ = 1.0f - (distToFloor * (1.0f / (30.0f + 40.0f)));
                shadowScaleX = shadowScaleZ * actor->shape.shadowScale * actor->scale.x;
                lightNumMax = 0;
                lightPtr = firstLightPtr;

                for (j = 0; j < numLights; j++) {
                    if (lightPtr->l.dir[1] > 0) {
                        lightNum =
                            (lightPtr->l.col[0] + lightPtr->l.col[1] + lightPtr->l.col[2]) * ABS(lightPtr->l.dir[1]);
                        if (lightNum > 0) {
                            lightNumMax += lightNum;
                            ActorShadow_DrawFoot(play, lightPtr, &floorMtx, lightNum, shadowAlpha, shadowScaleX,
                                                 shadowScaleZ);
                        }
                    }
                    lightPtr++;
                }

                for (j = 0; j < 2; j++) {
                    if (lightPtr->l.dir[1] > 0) {
                        lightNum =
                            ((lightPtr->l.col[0] + lightPtr->l.col[1] + lightPtr->l.col[2]) * ABS(lightPtr->l.dir[1])) -
                            (lightNumMax * 8);
                        if (lightNum > 0) {
                            ActorShadow_DrawFoot(play, lightPtr, &floorMtx, lightNum, shadowAlpha, shadowScaleX,
                                                 shadowScaleZ);
                        }
                    }
                    lightPtr++;
                }
            }
            feetPosPtr++;
            floorHeightPtr++;
        }

        if (!(actor->bgCheckFlags & 1)) {
            actor->shape.feetFloorFlags = 0;
        } else if (actor->shape.feetFloorFlags == 3) {
            f32 footDistY = actor->shape.feetPos[FOOT_LEFT].y - actor->shape.feetPos[FOOT_RIGHT].y;

            if ((floorHeight[0] + footDistY) < (floorHeight[1] - footDistY)) {
                actor->shape.feetFloorFlags = 2;
            } else {
                actor->shape.feetFloorFlags = 1;
            }
        }

        CLOSE_DISPS(play->state.gfxCtx);
    }
}

void Actor_SetFeetPos(Actor* actor, s32 limbIndex, s32 leftFootIndex, Vec3f* leftFootPos, s32 rightFootIndex,
                      Vec3f* rightFootPos) {
    if (limbIndex == leftFootIndex) {
        Matrix_MultVec3f(leftFootPos, &actor->shape.feetPos[FOOT_LEFT]);
    } else if (limbIndex == rightFootIndex) {
        Matrix_MultVec3f(rightFootPos, &actor->shape.feetPos[FOOT_RIGHT]);
    }
}

void func_8002BE04(PlayState* play, Vec3f* arg1, Vec3f* arg2, f32* arg3) {
    SkinMatrix_Vec3fMtxFMultXYZW(&play->viewProjectionMtxF, arg1, arg2, arg3);
    *arg3 = (*arg3 < 1.0f) ? 1.0f : (1.0f / *arg3);
}

typedef struct {
    /* 0x00 */ Color_RGBA8 inner;
    /* 0x04 */ Color_RGBA8 outer;
} NaviColor; // size = 0x8

NaviColor sNaviColorList[] = {
    { { 0, 255, 0, 255 }, { 0, 255, 0, 0 } },         { { 0, 255, 0, 255 }, { 0, 255, 0, 0 } },
    { { 255, 255, 255, 255 }, { 0, 0, 255, 0 } },     { { 0, 255, 0, 255 }, { 0, 255, 0, 0 } },
    { { 150, 150, 255, 255 }, { 150, 150, 255, 0 } }, { { 255, 255, 0, 255 }, { 200, 155, 0, 0 } },
    { { 0, 255, 0, 255 }, { 0, 255, 0, 0 } },         { { 0, 255, 0, 255 }, { 0, 255, 0, 0 } },
    { { 0, 255, 0, 255 }, { 0, 255, 0, 0 } },         { { 255, 255, 0, 255 }, { 200, 155, 0, 0 } },
    { { 0, 255, 0, 255 }, { 0, 255, 0, 0 } },         { { 0, 255, 0, 255 }, { 0, 255, 0, 0 } },
    { { 0, 255, 0, 255 }, { 0, 255, 0, 0 } },
};

static Color_RGBA8 defaultIdlePrimaryColor = { 255, 255, 255, 255 };
static Color_RGBA8 defaultIdleSecondaryColor = { 0, 0, 255, 0 };
static Color_RGBA8 defaultNPCPrimaryColor = { 150, 150, 255, 255 };
static Color_RGBA8 defaultNPCSecondaryColor = { 150, 150, 255, 0 };
static Color_RGBA8 defaultEnemyPrimaryColor = { 255, 255, 0, 255 };
static Color_RGBA8 defaultEnemySecondaryColor = { 200, 155, 0, 0 };
static Color_RGBA8 defaultPropsPrimaryColor = { 0, 255, 0, 255 };
static Color_RGBA8 defaultPropsSecondaryColor = { 0, 255, 0, 0 };

// unused
Gfx D_80115FF0[] = {
    gsSPEndDisplayList(),
};

void func_8002BE64(TargetContext* targetCtx, s32 index, f32 arg2, f32 arg3, f32 arg4) {
    targetCtx->arr_50[index].pos.x = arg2;
    targetCtx->arr_50[index].pos.y = arg3;
    targetCtx->arr_50[index].pos.z = arg4;
    targetCtx->arr_50[index].unk_0C = targetCtx->unk_44;
}

void func_8002BE98(TargetContext* targetCtx, s32 actorCategory, PlayState* play) {
    TargetContextEntry* entry;
    NaviColor* naviColor;
    s32 i;

    Math_Vec3f_Copy(&targetCtx->targetCenterPos, &play->view.eye);
    targetCtx->unk_44 = 500.0f;
    targetCtx->unk_48 = 0x100;

    naviColor = &sNaviColorList[actorCategory];

    entry = &targetCtx->arr_50[0];
    for (i = 0; i < ARRAY_COUNT(targetCtx->arr_50); i++) {
        func_8002BE64(targetCtx, i, 0.0f, 0.0f, 0.0f);
        entry->color.r = naviColor->inner.r;
        entry->color.g = naviColor->inner.g;
        entry->color.b = naviColor->inner.b;
        entry++;
    }
}

void func_8002BF60(TargetContext* targetCtx, Actor* actor, s32 actorCategory, PlayState* play) {
    if (CVarGetInteger(CVAR_COSMETIC("Navi.IdlePrimary.Changed"), 0)) {
        sNaviColorList[ACTORCAT_PLAYER].inner = CVarGetColor(CVAR_COSMETIC("Navi.IdlePrimary.Value"), defaultIdlePrimaryColor);
    } else {
        sNaviColorList[ACTORCAT_PLAYER].inner = defaultIdlePrimaryColor;
    }
    if (CVarGetInteger(CVAR_COSMETIC("Navi.IdleSecondary.Changed"), 0)) {
        sNaviColorList[ACTORCAT_PLAYER].outer = CVarGetColor(CVAR_COSMETIC("Navi.IdleSecondary.Value"), defaultIdleSecondaryColor);
    } else {
        sNaviColorList[ACTORCAT_PLAYER].outer = defaultIdleSecondaryColor;
    }

    if (CVarGetInteger(CVAR_COSMETIC("Navi.NPCPrimary.Changed"), 0)) {
        sNaviColorList[ACTORCAT_NPC].inner = CVarGetColor(CVAR_COSMETIC("Navi.NPCPrimary.Value"), defaultNPCPrimaryColor);
    } else {
        sNaviColorList[ACTORCAT_NPC].inner = defaultNPCPrimaryColor;
    }
    if (CVarGetInteger(CVAR_COSMETIC("Navi.NPCSecondary.Changed"), 0)) {
        sNaviColorList[ACTORCAT_NPC].outer = CVarGetColor(CVAR_COSMETIC("Navi.NPCSecondary.Value"), defaultNPCSecondaryColor);
    } else {
        sNaviColorList[ACTORCAT_NPC].outer = defaultNPCSecondaryColor;
    }

    if (CVarGetInteger(CVAR_COSMETIC("Navi.EnemyPrimary.Changed"), 0)) {
        sNaviColorList[ACTORCAT_ENEMY].inner = CVarGetColor(CVAR_COSMETIC("Navi.EnemyPrimary.Value"), defaultEnemyPrimaryColor);
        sNaviColorList[ACTORCAT_BOSS].inner = CVarGetColor(CVAR_COSMETIC("Navi.EnemyPrimary.Value"), defaultEnemyPrimaryColor);
    } else {
        sNaviColorList[ACTORCAT_ENEMY].inner = defaultEnemyPrimaryColor;
        sNaviColorList[ACTORCAT_BOSS].inner = defaultEnemyPrimaryColor;
    }
    if (CVarGetInteger(CVAR_COSMETIC("Navi.EnemySecondary.Changed"), 0)) {
        sNaviColorList[ACTORCAT_ENEMY].outer = CVarGetColor(CVAR_COSMETIC("Navi.EnemySecondary.Value"), defaultEnemySecondaryColor);
        sNaviColorList[ACTORCAT_BOSS].outer = CVarGetColor(CVAR_COSMETIC("Navi.EnemySecondary.Value"), defaultEnemySecondaryColor);
    } else {
        sNaviColorList[ACTORCAT_ENEMY].outer = defaultEnemySecondaryColor;
        sNaviColorList[ACTORCAT_BOSS].outer = defaultEnemySecondaryColor;
    }

    if (CVarGetInteger(CVAR_COSMETIC("Navi.PropsPrimary.Changed"), 0)) {
        sNaviColorList[ACTORCAT_PROP].inner = CVarGetColor(CVAR_COSMETIC("Navi.PropsPrimary.Value"), defaultPropsPrimaryColor);
    } else {
        sNaviColorList[ACTORCAT_PROP].inner = defaultPropsPrimaryColor;
    }
    if (CVarGetInteger(CVAR_COSMETIC("Navi.PropsSecondary.Changed"), 0)) {
        sNaviColorList[ACTORCAT_PROP].outer = CVarGetColor(CVAR_COSMETIC("Navi.PropsSecondary.Value"), defaultPropsSecondaryColor);
    } else {
        sNaviColorList[ACTORCAT_PROP].outer = defaultPropsSecondaryColor;
    }

    NaviColor* naviColor = &sNaviColorList[actorCategory];
    targetCtx->naviRefPos.x = actor->focus.pos.x;
    targetCtx->naviRefPos.y = actor->focus.pos.y + (actor->targetArrowOffset * actor->scale.y);
    targetCtx->naviRefPos.z = actor->focus.pos.z;
    targetCtx->naviInner.r = naviColor->inner.r;
    targetCtx->naviInner.g = naviColor->inner.g;
    targetCtx->naviInner.b = naviColor->inner.b;
    targetCtx->naviInner.a = naviColor->inner.a;
    targetCtx->naviOuter.r = naviColor->outer.r;
    targetCtx->naviOuter.g = naviColor->outer.g;
    targetCtx->naviOuter.b = naviColor->outer.b;
    targetCtx->naviOuter.a = naviColor->outer.a;
}

void func_8002C0C0(TargetContext* targetCtx, Actor* actor, PlayState* play) {
    targetCtx->arrowPointedActor = NULL;
    targetCtx->targetedActor = NULL;
    targetCtx->unk_40 = 0.0f;
    targetCtx->unk_8C = NULL;
    targetCtx->bgmEnemy = NULL;
    targetCtx->unk_4B = 0;
    targetCtx->unk_4C = 0;
    func_8002BF60(targetCtx, actor, actor->category, play);
    func_8002BE98(targetCtx, actor->category, play);
}

void func_8002C124(TargetContext* targetCtx, PlayState* play) {
    Actor* actor = targetCtx->targetedActor;

    OPEN_DISPS(play->state.gfxCtx);

    if (targetCtx->unk_48 != 0) {
        TargetContextEntry* entry;
        Player* player;
        s16 spCE;
        f32 temp1;
        Vec3f spBC;
        s32 spB8;
        f32 spB4;
        s32 spB0;
        s32 spAC;
        f32 var1;
        f32 var2;
        s32 i;

        FrameInterpolation_RecordOpenChild(actor, 0);
        player = GET_PLAYER(play);

        spCE = 0xFF;
        var1 = 1.0f;

        if (targetCtx->unk_4B != 0) {
            spB8 = 1;
        } else {
            spB8 = 3;
        }

        if (actor != NULL) {
            Math_Vec3f_Copy(&targetCtx->targetCenterPos, &actor->focus.pos);
            var1 = (500.0f - targetCtx->unk_44) / 420.0f;
        } else {
            targetCtx->unk_48 -= 120;
            if (targetCtx->unk_48 < 0) {
                targetCtx->unk_48 = 0;
            }
            spCE = targetCtx->unk_48;
        }

        func_8002BE04(play, &targetCtx->targetCenterPos, &spBC, &spB4);

        spBC.x = (160 * (spBC.x * spB4)) * var1;
        spBC.x = CLAMP(spBC.x, -320.0f, 320.0f);

        spBC.y = (120 * (spBC.y * spB4)) * var1;
        spBC.y = CLAMP(spBC.y, -240.0f, 240.0f);

        spBC.z = spBC.z * var1;

        targetCtx->unk_4C--;
        if (targetCtx->unk_4C < 0) {
            targetCtx->unk_4C = 2;
        }

        func_8002BE64(targetCtx, targetCtx->unk_4C, spBC.x, spBC.y, spBC.z);

        if ((!(player->stateFlags1 & PLAYER_STATE1_TALKING)) || (actor != player->focusActor)) {
            OVERLAY_DISP = Gfx_SetupDL(OVERLAY_DISP, 0x39);

            for (spB0 = 0, spAC = targetCtx->unk_4C; spB0 < spB8; spB0++, spAC = (spAC + 1) % 3) {
                entry = &targetCtx->arr_50[spAC];

                if (entry->unk_0C < 500.0f) {
                    if (entry->unk_0C <= 120.0f) {
                        var2 = 0.15f;
                    } else {
                        var2 = ((entry->unk_0C - 120.0f) * 0.001f) + 0.15f;
                    }

                    Matrix_Translate(entry->pos.x, entry->pos.y, 0.0f, MTXMODE_NEW);
                    Matrix_Scale(var2, 0.15f, 1.0f, MTXMODE_APPLY);

                    gDPSetPrimColor(OVERLAY_DISP++, 0, 0, entry->color.r, entry->color.g, entry->color.b, (u8)spCE);

                    Matrix_RotateZ((targetCtx->unk_4B & 0x7F) * (M_PI / 64), MTXMODE_APPLY);

                    for (i = 0; i < 4; i++) {
                        Matrix_RotateZ(M_PI / 2, MTXMODE_APPLY);
                        Matrix_Push();
                        Matrix_Translate(entry->unk_0C, entry->unk_0C, 0.0f, MTXMODE_APPLY);
                        gSPMatrix(OVERLAY_DISP++, MATRIX_NEWMTX(play->state.gfxCtx),
                                  G_MTX_MODELVIEW | G_MTX_LOAD);
                        gSPDisplayList(OVERLAY_DISP++, gZTargetLockOnTriangleDL);
                        Matrix_Pop();
                    }
                }

                spCE -= 0xFF / 3;
                if (spCE < 0) {
                    spCE = 0;
                }
            }
        }
        FrameInterpolation_RecordCloseChild();
    }

    actor = targetCtx->unk_94;
    if ((actor != NULL) && !(actor->flags & ACTOR_FLAG_LOCK_ON_DISABLED)) {
        FrameInterpolation_RecordOpenChild(actor, 1);
        NaviColor* naviColor = &sNaviColorList[actor->category];

        POLY_XLU_DISP = Gfx_SetupDL(POLY_XLU_DISP, 0x7);

        Matrix_Translate(actor->focus.pos.x, actor->focus.pos.y + (actor->targetArrowOffset * actor->scale.y) + 17.0f,
                         actor->focus.pos.z, MTXMODE_NEW);
        Matrix_RotateY((f32)((u16)(play->gameplayFrames * 3000)) * (M_PI / 0x8000), MTXMODE_APPLY);
        Matrix_Scale((iREG(27) + 35) / 1000.0f, (iREG(28) + 60) / 1000.0f, (iREG(29) + 50) / 1000.0f, MTXMODE_APPLY);

        gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, naviColor->inner.r, naviColor->inner.g, naviColor->inner.b, 255);
        gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(play->state.gfxCtx),
                  G_MTX_MODELVIEW | G_MTX_LOAD);
        gSPDisplayList(POLY_XLU_DISP++, gZTargetArrowDL);
        FrameInterpolation_RecordCloseChild();
    }

    CLOSE_DISPS(play->state.gfxCtx);
}

void func_8002C7BC(TargetContext* targetCtx, Player* player, Actor* actorArg, PlayState* play) {
    s32 pad;
    Actor* unkActor;
    s32 actorCategory;
    Vec3f sp50;
    f32 sp4C;
    f32 temp1;
    f32 temp2;
    f32 temp3;
    f32 temp4;
    f32 temp5;
    f32 temp6;
    s32 lockOnSfxId;

    unkActor = NULL;

    if ((player->focusActor != NULL) && (player->controlStickDirections[player->controlStickDataIndex] == 2)) {
        targetCtx->unk_94 = NULL;
    } else {
        func_80032AF0(play, &play->actorCtx, &unkActor, player);
        targetCtx->unk_94 = unkActor;
    }

    if (targetCtx->unk_8C != NULL) {
        unkActor = targetCtx->unk_8C;
        targetCtx->unk_8C = NULL;
    } else if (actorArg != NULL) {
        unkActor = actorArg;
    }

    if (unkActor != NULL) {
        actorCategory = unkActor->category;
    } else {
        actorCategory = player->actor.category;
    }

    if ((unkActor != targetCtx->arrowPointedActor) || (actorCategory != targetCtx->activeCategory)) {
        targetCtx->arrowPointedActor = unkActor;
        targetCtx->activeCategory = actorCategory;
        targetCtx->unk_40 = 1.0f;
    }

    if (unkActor == NULL) {
        unkActor = &player->actor;
    }

    if (Math_StepToF(&targetCtx->unk_40, 0.0f, 0.25f) == 0) {
        temp1 = 0.25f / targetCtx->unk_40;
        temp2 = unkActor->world.pos.x - targetCtx->naviRefPos.x;
        temp3 = (unkActor->world.pos.y + (unkActor->targetArrowOffset * unkActor->scale.y)) - targetCtx->naviRefPos.y;
        temp4 = unkActor->world.pos.z - targetCtx->naviRefPos.z;
        targetCtx->naviRefPos.x += temp2 * temp1;
        targetCtx->naviRefPos.y += temp3 * temp1;
        targetCtx->naviRefPos.z += temp4 * temp1;
    } else {
        func_8002BF60(targetCtx, unkActor, actorCategory, play);
    }

    if ((actorArg != NULL) && (targetCtx->unk_4B == 0)) {
        func_8002BE04(play, &actorArg->focus.pos, &sp50, &sp4C);
        if (((sp50.z <= 0.0f) || (1.0f <= fabsf(sp50.x * sp4C))) || (1.0f <= fabsf(sp50.y * sp4C))) {
            actorArg = NULL;
        }
    }

    if (actorArg != NULL) {
        if (actorArg != targetCtx->targetedActor) {
            func_8002BE98(targetCtx, actorArg->category, play);
            targetCtx->targetedActor = actorArg;

            if (actorArg->id == ACTOR_EN_BOOM) {
                targetCtx->unk_48 = 0;
            }

            lockOnSfxId = CHECK_FLAG_ALL(actorArg->flags, ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE) ? NA_SE_SY_LOCK_ON
                              : NA_SE_SY_LOCK_ON_HUMAN;
            Sfx_PlaySfxCentered(lockOnSfxId);
        }

        targetCtx->targetCenterPos.x = actorArg->world.pos.x;
        targetCtx->targetCenterPos.y = actorArg->world.pos.y - (actorArg->shape.yOffset * actorArg->scale.y);
        targetCtx->targetCenterPos.z = actorArg->world.pos.z;

        if (targetCtx->unk_4B == 0) {
            temp5 = (500.0f - targetCtx->unk_44) * 3.0f;
            temp6 = (temp5 < 30.0f) ? 30.0f : ((100.0f < temp5) ? 100.0f : temp5);
            if (Math_StepToF(&targetCtx->unk_44, 80.0f, temp6) != 0) {
                targetCtx->unk_4B++;
            }
        } else {
            targetCtx->unk_4B = (targetCtx->unk_4B + 3) | 0x80;
            targetCtx->unk_44 = 120.0f;
        }
    } else {
        targetCtx->targetedActor = NULL;
        Math_StepToF(&targetCtx->unk_44, 500.0f, 80.0f);
    }
}

/**
 * Tests if current scene switch flag is set.
 */
s32 Flags_GetSwitch(PlayState* play, s32 flag) {
    if (flag < 0x20) {
        return play->actorCtx.flags.swch & (1 << flag);
    } else {
        return play->actorCtx.flags.tempSwch & (1 << (flag - 0x20));
    }
}

/**
 * Sets current scene switch flag.
 */
void Flags_SetSwitch(PlayState* play, s32 flag) {
    u8 previouslyOff = !Flags_GetSwitch(play, flag);
    if (flag < 0x20) {
        play->actorCtx.flags.swch |= (1 << flag);
    } else {
        play->actorCtx.flags.tempSwch |= (1 << (flag - 0x20));
    }
    if (previouslyOff) {
        LUSLOG_INFO("Switch Flag Set - %#x", flag);
        GameInteractor_ExecuteOnSceneFlagSet(play->sceneNum, FLAG_SCENE_SWITCH, flag);
    }
}

/**
 * Unsets current scene switch flag.
 */
void Flags_UnsetSwitch(PlayState* play, s32 flag) {
    u8 previouslyOn = Flags_GetSwitch(play, flag);
    if (flag < 0x20) {
        play->actorCtx.flags.swch &= ~(1 << flag);
    } else {
        play->actorCtx.flags.tempSwch &= ~(1 << (flag - 0x20));
    }
    if (previouslyOn) {
        LUSLOG_INFO("Switch Flag Unset - %#x", flag);
        GameInteractor_ExecuteOnSceneFlagUnset(play->sceneNum, FLAG_SCENE_SWITCH, flag);
    }
}

/**
 * Tests if unknown flag is set.
 */
s32 Flags_GetUnknown(PlayState* play, s32 flag) {
    if (flag < 0x20) {
        return play->actorCtx.flags.unk0 & (1 << flag);
    } else {
        return play->actorCtx.flags.unk1 & (1 << (flag - 0x20));
    }
}

/**
 * Sets unknown flag.
 */
void Flags_SetUnknown(PlayState* play, s32 flag) {
    if (flag < 0x20) {
        play->actorCtx.flags.unk0 |= (1 << flag);
    } else {
        play->actorCtx.flags.unk1 |= (1 << (flag - 0x20));
    }
}

/**
 * Unsets unknown flag.
 */
void Flags_UnsetUnknown(PlayState* play, s32 flag) {
    if (flag < 0x20) {
        play->actorCtx.flags.unk0 &= ~(1 << flag);
    } else {
        play->actorCtx.flags.unk1 &= ~(1 << (flag - 0x20));
    }
}

/**
 * Tests if current scene chest flag is set.
 */
s32 Flags_GetTreasure(PlayState* play, s32 flag) {
    return play->actorCtx.flags.chest & (1 << flag);
}

/**
 * Sets current scene chest flag.
 */
void Flags_SetTreasure(PlayState* play, s32 flag) {
    u8 previouslyOff = !Flags_GetTreasure(play, flag);
    play->actorCtx.flags.chest |= (1 << flag);
    if (previouslyOff) {
        LUSLOG_INFO("Treasure Flag Set - %#x", flag);
        GameInteractor_ExecuteOnSceneFlagSet(play->sceneNum, FLAG_SCENE_TREASURE, flag);
    }
}

/**
 * Tests if current scene clear flag is set.
 */
s32 Flags_GetClear(PlayState* play, s32 flag) {
    return play->actorCtx.flags.clear & (1 << flag);
}

/**
 * Sets current scene clear flag.
 */
void Flags_SetClear(PlayState* play, s32 flag) {
    u8 previouslyOff = !Flags_GetClear(play, flag);
    play->actorCtx.flags.clear |= (1 << flag);
    if (previouslyOff) {
        LUSLOG_INFO("Clear Flag Set - %#x", flag);
        GameInteractor_ExecuteOnSceneFlagSet(play->sceneNum, FLAG_SCENE_CLEAR, flag);
    }
}

/**
 * Unsets current scene clear flag.
 */
void Flags_UnsetClear(PlayState* play, s32 flag) {
    u8 previouslyOn = Flags_GetClear(play, flag);
    play->actorCtx.flags.clear &= ~(1 << flag);
    if (previouslyOn) {
        LUSLOG_INFO("Clear Flag Unset - %#x", flag);
        GameInteractor_ExecuteOnSceneFlagUnset(play->sceneNum, FLAG_SCENE_CLEAR, flag);
    }
}

/**
 * Tests if current scene temp clear flag is set.
 */
s32 Flags_GetTempClear(PlayState* play, s32 flag) {
    return play->actorCtx.flags.tempClear & (1 << flag);
}

/**
 * Sets current scene temp clear flag.
 */
void Flags_SetTempClear(PlayState* play, s32 flag) {
    play->actorCtx.flags.tempClear |= (1 << flag);
}

/**
 * Unsets current scene temp clear flag.
 */
void Flags_UnsetTempClear(PlayState* play, s32 flag) {
    play->actorCtx.flags.tempClear &= ~(1 << flag);
}

/**
 * Tests if current scene collectible flag is set.
 */
s32 Flags_GetCollectible(PlayState* play, s32 flag) {
    if (flag < 0x20) {
        return play->actorCtx.flags.collect & (1 << flag);
    } else {
        return play->actorCtx.flags.tempCollect & (1 << (flag - 0x20));
    }
}

/**
 * Sets current scene collectible flag.
 */
void Flags_SetCollectible(PlayState* play, s32 flag) {
    u8 previouslyOff = !Flags_GetCollectible(play, flag);
    if (flag != 0) {
        if (flag < 0x20) {
            play->actorCtx.flags.collect |= (1 << flag);
        } else {
            play->actorCtx.flags.tempCollect |= (1 << (flag - 0x20));
        }
    }
    if (previouslyOff) {
        LUSLOG_INFO("Collectible Flag Set - %#x", flag);
        GameInteractor_ExecuteOnSceneFlagSet(play->sceneNum, FLAG_SCENE_COLLECTIBLE, flag);
    }
}

void func_8002CDE4(PlayState* play, TitleCardContext* titleCtx) {
    titleCtx->durationTimer = titleCtx->delayTimer = titleCtx->intensityR = titleCtx->alpha = 0;
    titleCtx->durationTimer = titleCtx->delayTimer = titleCtx->intensityG = titleCtx->alpha = 0;
    titleCtx->durationTimer = titleCtx->delayTimer = titleCtx->intensityB = titleCtx->alpha = 0;
}

void TitleCard_InitBossName(PlayState* play, TitleCardContext* titleCtx, void* texture, s16 x, s16 y, u8 width,
                            u8 height, s16 hasTranslation) {
    static char newName[512];

    if (gSaveContext.language != LANGUAGE_ENG) {
        size_t length = strlen(texture);
        strcpy(newName, texture);
        if (gSaveContext.language == LANGUAGE_FRA) {
            newName[length - 6] = 'F';
            newName[length - 5] = 'R';
            newName[length - 4] = 'A';
        } else if (gSaveContext.language == LANGUAGE_GER) {
            newName[length - 6] = 'G';
            newName[length - 5] = 'E';
            newName[length - 4] = 'R';
        }
        texture = newName;
    }

    titleCtx->texture = texture;
    titleCtx->isBossCard = true;
    titleCtx->hasTranslation = hasTranslation;
    titleCtx->x = x;
    titleCtx->y = y;
    titleCtx->width = width;
    titleCtx->height = height;
    titleCtx->durationTimer = 80;
    titleCtx->delayTimer = 0;
}

void TitleCard_InitPlaceName(PlayState* play, TitleCardContext* titleCtx, void* texture, s32 x, s32 y,
                             s32 width, s32 height, s32 delay) {
    SceneTableEntry* loadedScene = play->loadedScene;
    //  size_t size = loadedScene->titleFile.vromEnd - loadedScene->titleFile.vromStart;
    switch (play->sceneNum) {
        case SCENE_DEKU_TREE:
            texture = gDekuTreeTitleCardENGTex;
            break;
        case SCENE_DODONGOS_CAVERN:
            texture = gDodongosCavernTitleCardENGTex;
            break;
        case SCENE_JABU_JABU:
            texture = gJabuJabuTitleCardENGTex;
            break;
        case SCENE_FOREST_TEMPLE:
            texture = gForestTempleTitleCardENGTex;
            break;
        case SCENE_FIRE_TEMPLE:
            texture = gFireTempleTitleCardENGTex;
            break;
        case SCENE_WATER_TEMPLE:
            texture = gWaterTempleTitleCardENGTex;
            break;
        case SCENE_SPIRIT_TEMPLE:
            texture = gSpiritTempleTitleCardENGTex;
            break;
        case SCENE_SHADOW_TEMPLE:
            texture = gShadowTempleTitleCardENGTex;
            break;
        case SCENE_BOTTOM_OF_THE_WELL:
            texture = gBottomOfTheWellTitleCardENGTex;
            break;
        case SCENE_ICE_CAVERN:
            texture = gIceCavernTitleCardENGTex;
            break;
        case SCENE_GERUDO_TRAINING_GROUND:
            texture = gGERudoTrainingGroundTitleCardENGTex;
            break;
        case SCENE_THIEVES_HIDEOUT:
            texture = gThievesHideoutTitleCardENGTex;
            break;
        case SCENE_OUTSIDE_GANONS_CASTLE:
            texture = gGanonsCastleTitleCardENGTex;
            break;
        case SCENE_INSIDE_GANONS_CASTLE:
            texture = gInsideGanonsCastleTitleCardENGTex;
            break;
        case SCENE_TREASURE_BOX_SHOP:
            texture = gTreasureBoxShopTitleCardENGTex;
            break;
        case SCENE_BACK_ALLEY_DAY:
        case SCENE_BACK_ALLEY_NIGHT:
            texture = gBackAlleyTitleCardENGTex;
            break;
        case SCENE_MARKET_DAY:
        case SCENE_MARKET_NIGHT:
        case SCENE_MARKET_RUINS:
            texture = gMarketTitleCardENGTex;
            break;
        case SCENE_BAZAAR:
            texture = gBazaarTitleCardENGTex;
            break;
        case SCENE_KOKIRI_SHOP:
            texture = gKokiriShopTitleCardENGTex;
            break;
        case SCENE_GORON_SHOP:
            texture = gGoronShopTitleCardENGTex;
            break;
        case SCENE_ZORA_SHOP:
            texture = gZoraShopTitleCardENGTex;
            break;
        case SCENE_BOMBCHU_SHOP:
            texture = gBombchuShopTitleCardENGTex;
            break;
        case SCENE_POTION_SHOP_KAKARIKO:
        case SCENE_POTION_SHOP_GRANNY:
        case SCENE_POTION_SHOP_MARKET:
            texture = gPotionShopTitleCardENGTex;
            break;
        case SCENE_HAPPY_MASK_SHOP:
            texture = gHappyMaskShopTitleCardENGTex;
            break;
        case SCENE_STABLE:
            texture = gStableTitleCardENGTex;
            break;
        case SCENE_LAKESIDE_LABORATORY:
            texture = gLakesideLaboratoryTitleCardENGTex;
            break;
        case SCENE_GRAVEKEEPERS_HUT:
            texture = gGravekeepersHutTitleCardENGTex;
            break;
        case SCENE_GREAT_FAIRYS_FOUNTAIN_MAGIC:
        case SCENE_GREAT_FAIRYS_FOUNTAIN_SPELLS:
            texture = gGreatFairysFountainTitleCardENGTex;
            break;
        case SCENE_FAIRYS_FOUNTAIN:
            texture = gFairysFountainTitleCardENGTex;
            break;
        case SCENE_ROYAL_FAMILYS_TOMB:
            texture = gRoyalFamilysTombTitleCardENGTex;
            break;
        case SCENE_SHOOTING_GALLERY:
            texture = gShootingGalleryTitleCardENGTex;
            break;
        case SCENE_TEMPLE_OF_TIME:
            texture = gTempleOfTimeTitleCardENGTex;
            break;
        case SCENE_CHAMBER_OF_THE_SAGES:
            texture = gChamberOfTheSagesTitleCardENGTex;
            break;
        case SCENE_CASTLE_COURTYARD_GUARDS_DAY:
        case SCENE_CASTLE_COURTYARD_GUARDS_NIGHT:
        case SCENE_CASTLE_COURTYARD_ZELDA:
        case SCENE_HAIRAL_NIWA2:
            texture = gCastleCourtyardTitleCardENGTex;
            break;
        case SCENE_WINDMILL_AND_DAMPES_GRAVE:
            texture = gQuestionMarkTitleCardENGTex;
            break;
        case SCENE_FISHING_POND:
            texture = gFishingPondTitleCardENGTex;
            break;
        case SCENE_BOMBCHU_BOWLING_ALLEY:
            texture = gBombchuBowlingAlleyCardENGTex;
            break;
        case SCENE_HOUSE_OF_SKULLTULA:
            texture = gHouseOfSkulltulaTitleCardENGTex;
            break;
        case SCENE_HYRULE_FIELD:
            texture = gHyruleFieldTitleCardENGTex;
            break;
        case SCENE_KAKARIKO_VILLAGE:
            texture = gKakarikoVillageTitleCardENGTex;
            break;
        case SCENE_GRAVEYARD:
            texture = gGraveyardTitleCardENGTex;
            break;
        case SCENE_ZORAS_RIVER:
            texture = gZorasRiverTitleCardENGTex;
            break;
        case SCENE_KOKIRI_FOREST:
            texture = gKokiriForestTitleCardENGTex;
            break;
        case SCENE_SACRED_FOREST_MEADOW:
            texture = gSacredForestMeadowTitleCardENGTex;
            break;
        case SCENE_LAKE_HYLIA:
            texture = gLakeHyliaTitleCardENGTex;
            break;
        case SCENE_ZORAS_DOMAIN:
            texture = gZorasDomainTitleCardENGTex;
            break;
        case SCENE_ZORAS_FOUNTAIN:
            texture = gZorasFountainTitleCardENGTex;
            break;
        case SCENE_GERUDO_VALLEY:
            texture = gGERudoValleyTitleCardENGTex;
            break;
        case SCENE_LOST_WOODS:
            texture = gLostWoodsTitleCardENGTex;
            break;
        case SCENE_DESERT_COLOSSUS:
            texture = gDesertColossusTitleCardENGTex;
            break;
        case SCENE_GERUDOS_FORTRESS:
            texture = gGERudosFortressTitleCardENGTex;
            break;
        case SCENE_HAUNTED_WASTELAND:
            texture = gHauntedWastelandTitleCardENGTex;
            break;
        case SCENE_HYRULE_CASTLE:
            texture = gHyruleCastleTitleCardENGTex;
            break;
        case SCENE_DEATH_MOUNTAIN_TRAIL:
            texture = gDeathMountainTrailTitleCardENGTex;
            break;
        case SCENE_DEATH_MOUNTAIN_CRATER:
            texture = gDeathMountainCraterTitleCardENGTex;
            break;
        case SCENE_GORON_CITY:
            texture = gGoronCityTitleCardENGTex;
            break;
        case SCENE_LON_LON_RANCH:
            texture = gLonLonRanchTitleCardENGTex;
            break;
        default:
            titleCtx->texture = NULL;
            return;

    }

    static char newName[512];

    if (gSaveContext.language != LANGUAGE_ENG) {
        size_t length = strlen(texture);
        strcpy(newName, texture);
        if (gSaveContext.language == LANGUAGE_FRA) {
            newName[length - 6] = 'F';
            newName[length - 5] = 'R';
            newName[length - 4] = 'A';
        }
        else if (gSaveContext.language == LANGUAGE_GER) {
            newName[length - 6] = 'G';
            newName[length - 5] = 'E';
            newName[length - 4] = 'R';
        }
        texture = newName;
    }

    titleCtx->texture = texture;
    titleCtx->isBossCard = false;
    titleCtx->hasTranslation = false;
    titleCtx->x = x;
    titleCtx->y = y;
    titleCtx->width = width;
    titleCtx->height = height;
    titleCtx->durationTimer = 80;
    titleCtx->delayTimer = delay;
}

void TitleCard_Update(PlayState* play, TitleCardContext* titleCtx) {
    Color_RGB8 TitleCard_Colors = { 255, 255, 255 };
    if (titleCtx->isBossCard && CVarGetInteger(CVAR_COSMETIC("HUD.TitleCard.Boss.Changed"), 0) == 1) {
        TitleCard_Colors = CVarGetColor24(CVAR_COSMETIC("HUD.TitleCard.Boss.Value"), TitleCard_Colors);
    } else if (!titleCtx->isBossCard && CVarGetInteger(CVAR_COSMETIC("HUD.TitleCard.Map.Changed"), 0) == 1) {
        TitleCard_Colors = CVarGetColor24(CVAR_COSMETIC("HUD.TitleCard.Map.Value"), TitleCard_Colors);
    }

    if (DECR(titleCtx->delayTimer) == 0) {
        if (titleCtx->durationTimer == 80) {
            GameInteractor_ExecuteOnPresentTitleCard();
        }

        if (DECR(titleCtx->durationTimer) == 0) {
            Math_StepToS(&titleCtx->alpha, 0, 30);
            Math_StepToS(&titleCtx->intensityR, 0, 70);
            Math_StepToS(&titleCtx->intensityG, 0, 70);
            Math_StepToS(&titleCtx->intensityB, 0, 70);
        } else {
            Math_StepToS(&titleCtx->alpha, 255, 10);
            Math_StepToS(&titleCtx->intensityR, TitleCard_Colors.r, 20);
            Math_StepToS(&titleCtx->intensityG, TitleCard_Colors.g, 20);
            Math_StepToS(&titleCtx->intensityB, TitleCard_Colors.b, 20);
        }
    }
}

void TitleCard_Draw(PlayState* play, TitleCardContext* titleCtx) {
    s32 width;
    s32 height;
    s32 titleX;
    s32 doubleWidth;
    s32 titleY;

    if (titleCtx->alpha != 0) {
        width = titleCtx->width;
        height = titleCtx->height;
        s16 TitleCard_PosX_Modifier = (titleCtx->isBossCard ? CVarGetInteger(CVAR_COSMETIC("TitleCard.Boss.PosX"), 0) : CVarGetInteger(CVAR_COSMETIC("TitleCard.Map.PosX"), 0));
        s16 TitleCard_PosY_Modifier = (titleCtx->isBossCard ? CVarGetInteger(CVAR_COSMETIC("TitleCard.Boss.PosY"), 0) : CVarGetInteger(CVAR_COSMETIC("TitleCard.Map.PosY"), 0));
        s16 TitleCard_PosType_Checker = (titleCtx->isBossCard ? CVarGetInteger(CVAR_COSMETIC("TitleCard.Boss.PosType"), 0) : CVarGetInteger(CVAR_COSMETIC("TitleCard.Map.PosType"), 0));
        s16 TitleCard_Margin_Checker = (titleCtx->isBossCard ? CVarGetInteger(CVAR_COSMETIC("TitleCard.Boss.UseMargins"), 0) : CVarGetInteger(CVAR_COSMETIC("TitleCard.Map.UseMargins"), 0));
        s16 TitleCard_MarginX = 0;
        s16 TitleCard_PosX = titleCtx->x;
        s16 TitleCard_PosY = titleCtx->y;
        if (TitleCard_PosType_Checker != 0) {
            TitleCard_PosY = TitleCard_PosY_Modifier;
            if (TitleCard_PosType_Checker == ANCHOR_LEFT) {
                if (TitleCard_Margin_Checker != 0) {TitleCard_MarginX = CVarGetInteger(CVAR_COSMETIC("HUD.Margin.L"), 0) * -1;};
                TitleCard_PosX = OTRGetDimensionFromLeftEdge(TitleCard_PosX_Modifier + TitleCard_MarginX) - 11;
            } else if (TitleCard_PosType_Checker == ANCHOR_RIGHT) {
                if (TitleCard_Margin_Checker != 0) {TitleCard_MarginX = CVarGetInteger(CVAR_COSMETIC("HUD.Margin.R"), 0);};
                TitleCard_PosX = OTRGetDimensionFromRightEdge(TitleCard_PosX_Modifier + TitleCard_MarginX);
            } else if (TitleCard_PosType_Checker == ANCHOR_NONE) {
                TitleCard_PosX = TitleCard_PosX_Modifier;
            } else if (TitleCard_PosType_Checker == HIDDEN) {
                TitleCard_PosX = -9999;
            }
        }

        titleX = (TitleCard_PosX * 4) - (width * 2);
        titleY = (TitleCard_PosY * 4) - (height * 2);
        doubleWidth = width * 2;

        OPEN_DISPS(play->state.gfxCtx);

        OVERLAY_DISP = Gfx_SetupDL_52NoCD(OVERLAY_DISP);

        gDPSetPrimColor(OVERLAY_DISP++, 0, 0, (u8)titleCtx->intensityR, (u8)titleCtx->intensityG, (u8)titleCtx->intensityB,
                        (u8)titleCtx->alpha);

        gDPLoadTextureBlock(OVERLAY_DISP++, (uintptr_t)titleCtx->texture, G_IM_FMT_IA,
                            G_IM_SIZ_8b,
                            width, height, 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK,
                            G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD);
        gSPWideTextureRectangle(OVERLAY_DISP++, titleX, titleY, ((doubleWidth * 2) + titleX) - 4, titleY + (height * 4),
                                G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10);

        height = titleCtx->height - height;

        CLOSE_DISPS(play->state.gfxCtx);
    }
}

s32 func_8002D53C(PlayState* play, TitleCardContext* titleCtx) {
    if ((play->actorCtx.titleCtx.delayTimer != 0) || (play->actorCtx.titleCtx.alpha != 0)) {
        titleCtx->durationTimer = 0;
        titleCtx->delayTimer = 0;
        return false;
    }

    return true;
}

void Actor_Kill(Actor* actor) {
    GameInteractor_ExecuteOnActorKill(actor);
    actor->draw = NULL;
    actor->update = NULL;
    actor->flags &= ~ACTOR_FLAG_ATTENTION_ENABLED;
}

void Actor_SetWorldToHome(Actor* actor) {
    actor->world = actor->home;
}

void Actor_SetFocus(Actor* actor, f32 yOffset) {
    actor->focus.pos.x = actor->world.pos.x;
    actor->focus.pos.y = actor->world.pos.y + yOffset;
    actor->focus.pos.z = actor->world.pos.z;

    actor->focus.rot.x = actor->world.rot.x;
    actor->focus.rot.y = actor->world.rot.y;
    actor->focus.rot.z = actor->world.rot.z;
}

void Actor_SetWorldRotToShape(Actor* actor) {
    actor->world.rot = actor->shape.rot;
}

void Actor_SetShapeRotToWorld(Actor* actor) {
    actor->shape.rot = actor->world.rot;
}

void Actor_SetScale(Actor* actor, f32 scale) {
    actor->scale.z = scale;
    actor->scale.y = scale;
    actor->scale.x = scale;
}

void Actor_SetObjectDependency(PlayState* play, Actor* actor) {
    gSegments[6] = VIRTUAL_TO_PHYSICAL(play->objectCtx.status[actor->objBankIndex].segment);
}

void Actor_Init(Actor* actor, PlayState* play) {
    Actor_SetWorldToHome(actor);
    Actor_SetShapeRotToWorld(actor);
    Actor_SetFocus(actor, 0.0f);
    Math_Vec3f_Copy(&actor->prevPos, &actor->world.pos);
    Actor_SetScale(actor, 0.01f);
    actor->targetMode = 3;
    actor->minVelocityY = -20.0f;
    actor->xyzDistToPlayerSq = FLT_MAX;
    actor->naviEnemyId = 0xFF;
    actor->uncullZoneForward = 1000.0f;
    actor->uncullZoneScale = 350.0f;
    actor->uncullZoneDownward = 700.0f;
    CollisionCheck_InitInfo(&actor->colChkInfo);
    actor->floorBgId = BGCHECK_SCENE;
    ActorShape_Init(&actor->shape, 0.0f, NULL, 0.0f);
    if (Object_IsLoaded(&play->objectCtx, actor->objBankIndex)) {
        Actor_SetObjectDependency(play, actor);
        actor->init(actor, play);
        actor->init = NULL;

        GameInteractor_ExecuteOnActorInit(actor);

        // For enemy health bar we need to know the max health during init
        if (actor->category == ACTORCAT_ENEMY) {
            actor->maximumHealth = actor->colChkInfo.health;
        }
    }
}

void Actor_Destroy(Actor* actor, PlayState* play) {
    if (actor->destroy != NULL) {
        actor->destroy(actor, play);
        actor->destroy = NULL;
    } else {
        // "No Actor class destruct [%s]"
        osSyncPrintf("Ａｃｔｏｒクラス デストラクトがありません [%s]\n" VT_RST, ActorDB_Retrieve(actor->id)->name);
    }

    NameTag_RemoveAllForActor(actor);
}

void Actor_UpdatePos(Actor* actor) {
    f32 speedRate = R_UPDATE_RATE * 0.5f;

    actor->world.pos.x += (actor->velocity.x * speedRate) + actor->colChkInfo.displacement.x;
    actor->world.pos.y += (actor->velocity.y * speedRate) + actor->colChkInfo.displacement.y;
    actor->world.pos.z += (actor->velocity.z * speedRate) + actor->colChkInfo.displacement.z;
}

void Actor_UpdateVelocityXZGravity(Actor* actor) {
    actor->velocity.x = Math_SinS(actor->world.rot.y) * actor->speedXZ;
    actor->velocity.z = Math_CosS(actor->world.rot.y) * actor->speedXZ;

    actor->velocity.y += actor->gravity;
    if (actor->velocity.y < actor->minVelocityY) {
        actor->velocity.y = actor->minVelocityY;
    }
}

void Actor_MoveXZGravity(Actor* actor) {
    Actor_UpdateVelocityXZGravity(actor);
    Actor_UpdatePos(actor);
}

void Actor_UpdateVelocityXYZ(Actor* actor) {
    f32 sp24 = Math_CosS(actor->world.rot.x) * actor->speedXZ;

    actor->velocity.x = Math_SinS(actor->world.rot.y) * sp24;
    actor->velocity.y = Math_SinS(actor->world.rot.x) * actor->speedXZ;
    actor->velocity.z = Math_CosS(actor->world.rot.y) * sp24;
}

void Actor_MoveXYZ(Actor* actor) {
    Actor_UpdateVelocityXYZ(actor);
    Actor_UpdatePos(actor);
}

void Actor_SetProjectileSpeed(Actor* actor, f32 arg1) {
    actor->speedXZ = Math_CosS(actor->world.rot.x) * arg1;
    actor->velocity.y = -Math_SinS(actor->world.rot.x) * arg1;
}

void Actor_UpdatePosByAnimation(Actor* actor, SkelAnime* skelAnime) {
    Vec3f sp1C;

    SkelAnime_UpdateTranslation(skelAnime, &sp1C, actor->shape.rot.y);
    actor->world.pos.x += sp1C.x * actor->scale.x;
    actor->world.pos.y += sp1C.y * actor->scale.y;
    actor->world.pos.z += sp1C.z * actor->scale.z;
}

s16 Actor_WorldYawTowardActor(Actor* actorA, Actor* actorB) {
    return Math_Vec3f_Yaw(&actorA->world.pos, &actorB->world.pos);
}

s16 Actor_FocusYawTowardActor(Actor* actorA, Actor* actorB) {
    return Math_Vec3f_Yaw(&actorA->focus.pos, &actorB->focus.pos);
}

s16 Actor_WorldYawTowardPoint(Actor* actor, Vec3f* refPoint) {
    return Math_Vec3f_Yaw(&actor->world.pos, refPoint);
}

s16 Actor_WorldPitchTowardActor(Actor* actorA, Actor* actorB) {
    return Math_Vec3f_Pitch(&actorA->world.pos, &actorB->world.pos);
}

s16 Actor_FocusPitchTowardActor(Actor* actorA, Actor* actorB) {
    return Math_Vec3f_Pitch(&actorA->focus.pos, &actorB->focus.pos);
}

s16 Actor_WorldPitchTowardPoint(Actor* actor, Vec3f* refPoint) {
    return Math_Vec3f_Pitch(&actor->world.pos, refPoint);
}

f32 Actor_WorldDistXYZToActor(Actor* actorA, Actor* actorB) {
    return Math_Vec3f_DistXYZ(&actorA->world.pos, &actorB->world.pos);
}

f32 Actor_WorldDistXYZToPoint(Actor* actor, Vec3f* refPoint) {
    return Math_Vec3f_DistXYZ(&actor->world.pos, refPoint);
}

f32 Actor_WorldDistXZToActor(Actor* actorA, Actor* actorB) {
    return Math_Vec3f_DistXZ(&actorA->world.pos, &actorB->world.pos);
}

f32 Actor_WorldDistXZToPoint(Actor* actor, Vec3f* refPoint) {
    return Math_Vec3f_DistXZ(&actor->world.pos, refPoint);
}

void Actor_WorldToActorCoords(Actor* actor, Vec3f* dest, Vec3f* pos) {
    f32 cosY;
    f32 sinY;
    f32 deltaX;
    f32 deltaZ;

    cosY = Math_CosS(actor->shape.rot.y);
    sinY = Math_SinS(actor->shape.rot.y);
    deltaX = pos->x - actor->world.pos.x;
    deltaZ = pos->z - actor->world.pos.z;

    dest->x = (deltaX * cosY) - (deltaZ * sinY);
    dest->z = (deltaX * sinY) + (deltaZ * cosY);
    dest->y = pos->y - actor->world.pos.y;
}

f32 Actor_HeightDiff(Actor* actorA, Actor* actorB) {
    return actorB->world.pos.y - actorA->world.pos.y;
}

f32 Player_GetHeight(Player* player) {
    f32 offset = (player->stateFlags1 & PLAYER_STATE1_ON_HORSE) ? 32.0f : 0.0f;

    if (LINK_IS_ADULT) {
        return offset + 68.0f;
    } else {
        return offset + 44.0f;
    }
}

f32 func_8002DCE4(Player* player) {
    if (player->stateFlags1 & PLAYER_STATE1_ON_HORSE) {
        return 8.0f;
    } else if (player->stateFlags1 & PLAYER_STATE1_IN_WATER) {
        return (R_RUN_SPEED_LIMIT / 100.0f) * 0.6f;
    } else {
        return R_RUN_SPEED_LIMIT / 100.0f;
    }
}

s32 func_8002DD6C(Player* player) {
    return player->stateFlags1 & PLAYER_STATE1_ITEM_IN_HAND;
}

s32 func_8002DD78(Player* player) {
    return func_8002DD6C(player) && player->unk_834;
}

s32 func_8002DDA8(PlayState* play) {
    Player* player = GET_PLAYER(play);

    return (player->stateFlags1 & PLAYER_STATE1_CARRYING_ACTOR) || func_8002DD78(player);
}

s32 func_8002DDE4(PlayState* play) {
    Player* player = GET_PLAYER(play);

    return player->stateFlags2 & PLAYER_STATE2_FOOTSTEP;
}

s32 func_8002DDF4(PlayState* play) {
    Player* player = GET_PLAYER(play);

    return player->stateFlags2 & PLAYER_STATE2_STATIONARY_LADDER;
}

void func_8002DE04(PlayState* play, Actor* actorA, Actor* actorB) {
    ArmsHook* hookshot = (ArmsHook*)Actor_Find(&play->actorCtx, ACTOR_ARMS_HOOK, ACTORCAT_ITEMACTION);

    hookshot->grabbed = actorB;
    hookshot->grabbedDistDiff.x = 0.0f;
    hookshot->grabbedDistDiff.y = 0.0f;
    hookshot->grabbedDistDiff.z = 0.0f;
    actorB->flags |= ACTOR_FLAG_HOOKSHOT_ATTACHED;
    actorA->flags &= ~ACTOR_FLAG_HOOKSHOT_ATTACHED;
}

void func_8002DE74(PlayState* play, Player* player) {
    if ((play->roomCtx.curRoom.behaviorType1 != ROOM_BEHAVIOR_TYPE1_4) && func_800C0CB8(play)) {
        Camera_ChangeSetting(Play_GetCamera(play, MAIN_CAM), CAM_SET_HORSE);
    }
}

void Actor_MountHorse(PlayState* play, Player* player, Actor* horse) {
    player->rideActor = horse;
    player->stateFlags1 |= PLAYER_STATE1_ON_HORSE;
    horse->child = &player->actor;
}

s32 func_8002DEEC(Player* player) {
    return (player->stateFlags1 & (PLAYER_STATE1_DEAD | PLAYER_STATE1_IN_CUTSCENE)) || (player->csAction != 0);
}

void func_8002DF18(PlayState* play, Player* player) {
    func_8006DC68(play, player);
}

s32 func_8002DF38(PlayState* play, Actor* actor, u8 csAction) {
    Player* player = GET_PLAYER(play);

    player->csAction = csAction;
    player->csActor = actor;
    player->cv.haltActorsDuringCsAction = false;

    return true;
}

s32 Player_SetCsActionWithHaltedActors(PlayState* play, Actor* actor, u8 csAction) {
    Player* player = GET_PLAYER(play);

    func_8002DF38(play, actor, csAction);
    player->cv.haltActorsDuringCsAction = true;

    return true;
}

void func_8002DF90(DynaPolyActor* dynaActor) {
    dynaActor->unk_154 = 0.0f;
    dynaActor->unk_150 = 0.0f;
}

void func_8002DFA4(DynaPolyActor* dynaActor, f32 arg1, s16 arg2) {
    dynaActor->unk_150 += arg1;
    dynaActor->unk_158 = arg2;
}

/**
 * Chcek if the player is facing the specified actor.
 * The maximum angle difference that qualifies as "facing" is specified by `maxAngle`.
 */
s32 Player_IsFacingActor(Actor* actor, s16 maxAngle, PlayState* play) {
    Player* player = GET_PLAYER(play);
    s16 yawDiff = (s16)(actor->yawTowardsPlayer + 0x8000) - player->actor.shape.rot.y;

    if (ABS(yawDiff) < maxAngle) {
        return true;
    }

    return false;
}

/**
 * Chcek if `actorB` is facing `actorA`.
 * The maximum angle difference that qualifies as "facing" is specified by `maxAngle`.
 *
 * This function is unused in the original game.
 */
s32 Actor_ActorBIsFacingActorA(Actor* actorA, Actor* actorB, s16 maxAngle) {
    s16 yawDiff = (s16)(Actor_WorldYawTowardActor(actorA, actorB) + 0x8000) - actorB->shape.rot.y;

    if (ABS(yawDiff) < maxAngle) {
        return true;
    }

    return false;
}

/**
 * Chcek if the specified actor is facing the player.
 * The maximum angle difference that qualifies as "facing" is specified by `maxAngle`.
 */
s32 Actor_IsFacingPlayer(Actor* actor, s16 maxAngle) {
    s16 yawDiff = actor->yawTowardsPlayer - actor->shape.rot.y;

    if (ABS(yawDiff) < maxAngle) {
        return true;
    }

    return false;
}

/**
 * Chcek if `actorA` is facing `actorB`.
 * The maximum angle difference that qualifies as "facing" is specified by `maxAngle`.
 *
 * This function is unused in the original game.
 */
s32 Actor_ActorAIsFacingActorB(Actor* actorA, Actor* actorB, s16 maxAngle) {
    s16 yawDiff = Actor_WorldYawTowardActor(actorA, actorB) - actorA->shape.rot.y;

    if (ABS(yawDiff) < maxAngle) {
        return true;
    }

    return false;
}

/**
 * Chcek if the specified actor is facing the player and is nearby.
 * The maximum angle difference that qualifies as "facing" is specified by `maxAngle`.
 * The minimum distance that qualifies as "nearby" is specified by `range`.
 */
s32 Actor_IsFacingAndNearPlayer(Actor* actor, f32 range, s16 maxAngle) {
    s16 yawDiff = actor->yawTowardsPlayer - actor->shape.rot.y;

    if (ABS(yawDiff) < maxAngle) {
        f32 xyzDistanceFromLink = sqrtf(SQ(actor->xzDistToPlayer) + SQ(actor->yDistToPlayer));

        if (xyzDistanceFromLink < range) {
            return true;
        }
    }

    return false;
}

/**
 * Chcek if `actorA` is facing `actorB` and is nearby.
 * The maximum angle difference that qualifies as "facing" is specified by `maxAngle`.
 * The minimum distance that qualifies as "nearby" is specified by `range`.
 */
s32 Actor_ActorAIsFacingAndNearActorB(Actor* actorA, Actor* actorB, f32 range, s16 maxAngle) {
    if (Actor_WorldDistXYZToActor(actorA, actorB) < range) {
        s16 yawDiff = Actor_WorldYawTowardActor(actorA, actorB) - actorA->shape.rot.y;

        if (ABS(yawDiff) < maxAngle) {
            return true;
        }
    }

    return false;
}

s32 func_8002E234(Actor* actor, f32 arg1, s32 arg2) {
    if ((actor->bgCheckFlags & 0x1) && (arg1 < -11.0f)) {
        actor->bgCheckFlags &= ~0x1;
        actor->bgCheckFlags |= 0x4;

        if ((actor->velocity.y < 0.0f) && (arg2 & 0x10)) {
            actor->velocity.y = 0.0f;
        }

        return false;
    }

    return true;
}

s32 func_8002E2AC(PlayState* play, Actor* actor, Vec3f* arg2, s32 arg3) {
    f32 floorHeightDiff;
    s32 floorBgId;

    arg2->y += 50.0f;

    actor->floorHeight =
        BgCheck_EntityRaycastFloor5(play, &play->colCtx, &actor->floorPoly, &floorBgId, actor, arg2);
    actor->bgCheckFlags &= ~0x0086;

    if (actor->floorHeight <= BGCHECK_Y_MIN) {
        return func_8002E234(actor, BGCHECK_Y_MIN, arg3);
    }

    floorHeightDiff = actor->floorHeight - actor->world.pos.y;
    actor->floorBgId = floorBgId;

    if (floorHeightDiff >= 0.0f) { // actor is on or below the ground
        actor->bgCheckFlags |= 0x80;

        if (actor->bgCheckFlags & 0x10) {
            if (floorBgId != sCurCeilingBgId) {
                if (floorHeightDiff > 15.0f) {
                    actor->bgCheckFlags |= 0x100;
                }
            } else {
                actor->world.pos.x = actor->prevPos.x;
                actor->world.pos.z = actor->prevPos.z;
            }
        }

        actor->world.pos.y = actor->floorHeight;

        if (actor->velocity.y <= 0.0f) {
            if (!(actor->bgCheckFlags & 0x1)) {
                actor->bgCheckFlags |= 0x2;
            } else if ((arg3 & 0x8) && (actor->gravity < 0.0f)) {
                actor->velocity.y = -4.0f;
            } else {
                actor->velocity.y = 0.0f;
            }

            actor->bgCheckFlags |= 0x1;
            func_80043334(&play->colCtx, actor, actor->floorBgId);
        }
    } else { // actor is above ground
        if ((actor->bgCheckFlags & 0x1) && (floorHeightDiff >= -11.0f)) {
            func_80043334(&play->colCtx, actor, actor->floorBgId);
        }

        return func_8002E234(actor, floorHeightDiff, arg3);
    }

    return true;
}

void Actor_UpdateBgCheckInfo(PlayState* play, Actor* actor, f32 wallCheckHeight, f32 wallCheckRadius,
                             f32 ceilingCheckHeight, s32 flags) {
    f32 sp74;
    s32 pad;
    Vec3f sp64;
    s32 bgId;
    CollisionPoly* wallPoly;
    f32 sp58;
    WaterBox* waterBox;
    f32 waterBoxYSurface;
    Vec3f ripplePos;

    sp74 = actor->world.pos.y - actor->prevPos.y;

    if ((actor->floorBgId != BGCHECK_SCENE) && (actor->bgCheckFlags & 1)) {
        func_800433A4(&play->colCtx, actor->floorBgId, actor);
    }

    if (flags & 1) {
        if ((!(flags & 0x80) &&
             BgCheck_EntitySphVsWall3(&play->colCtx, &sp64, &actor->world.pos, &actor->prevPos, wallCheckRadius,
                                      &actor->wallPoly, &bgId, actor, wallCheckHeight)) ||
            ((flags & 0x80) &&
             BgCheck_EntitySphVsWall4(&play->colCtx, &sp64, &actor->world.pos, &actor->prevPos, wallCheckRadius,
                                      &actor->wallPoly, &bgId, actor, wallCheckHeight))) {
            wallPoly = actor->wallPoly;
            Math_Vec3f_Copy(&actor->world.pos, &sp64);
            actor->wallYaw = Math_Atan2S(wallPoly->normal.z, wallPoly->normal.x);
            actor->bgCheckFlags |= 8;
            actor->wallBgId = bgId;
        } else {
            actor->bgCheckFlags &= ~8;
        }
    }

    sp64.x = actor->world.pos.x;
    sp64.z = actor->world.pos.z;

    if (flags & 2) {
        sp64.y = actor->prevPos.y + 10.0f;
        if (BgCheck_EntityCheckCeiling(&play->colCtx, &sp58, &sp64, (ceilingCheckHeight + sp74) - 10.0f,
                                       &sCurCeilingPoly, &sCurCeilingBgId, actor)) {
            actor->bgCheckFlags |= 0x10;
            actor->world.pos.y = (sp58 + sp74) - 10.0f;
        } else {
            actor->bgCheckFlags &= ~0x10;
        }
    }

    if (flags & 4) {
        sp64.y = actor->prevPos.y;
        func_8002E2AC(play, actor, &sp64, flags);
        waterBoxYSurface = actor->world.pos.y;
        if (WaterBox_GetSurface1(play, &play->colCtx, actor->world.pos.x, actor->world.pos.z,
                                 &waterBoxYSurface, &waterBox)) {
            actor->yDistToWater = waterBoxYSurface - actor->world.pos.y;
            if (actor->yDistToWater < 0.0f) {
                actor->bgCheckFlags &= ~0x60;
            } else {
                if (!(actor->bgCheckFlags & 0x20)) {
                    actor->bgCheckFlags |= 0x40;
                    if (!(flags & 0x40)) {
                        ripplePos.x = actor->world.pos.x;
                        ripplePos.y = waterBoxYSurface;
                        ripplePos.z = actor->world.pos.z;
                        EffectSsGRipple_Spawn(play, &ripplePos, 100, 500, 0);
                        EffectSsGRipple_Spawn(play, &ripplePos, 100, 500, 4);
                        EffectSsGRipple_Spawn(play, &ripplePos, 100, 500, 8);
                    }
                }
                actor->bgCheckFlags |= 0x20;
            }
        } else {
            actor->bgCheckFlags &= ~0x60;
            actor->yDistToWater = BGCHECK_Y_MIN;
        }
    }
}

Mtx D_8015BBA8;

Gfx* func_8002E830(Vec3f* object, Vec3f* eye, Vec3f* lightDir, GraphicsContext* gfxCtx, Gfx* gfx, Hilite** hilite) {
    LookAt* lookAt;
    f32 correctedEyeX;

    lookAt = Graph_Alloc(gfxCtx, sizeof(LookAt));

    correctedEyeX = (eye->x == object->x) && (eye->z == object->z) ? eye->x + 0.001f : eye->x;

    *hilite = Graph_Alloc(gfxCtx, sizeof(Hilite));

    if (HREG(80) == 6) {
        osSyncPrintf("z_actor.c 3529 eye=[%f(%f) %f %f] object=[%f %f %f] light_direction=[%f %f %f]\n", correctedEyeX,
                     eye->x, eye->y, eye->z, object->x, object->y, object->z, lightDir->x, lightDir->y, lightDir->z);
    }

    func_800ABE74(correctedEyeX, eye->y, eye->z);
    guLookAtHilite(&D_8015BBA8, lookAt, *hilite, correctedEyeX, eye->y, eye->z, object->x, object->y, object->z, 0.0f,
                   1.0f, 0.0f, lightDir->x, lightDir->y, lightDir->z, lightDir->x, lightDir->y, lightDir->z, 0x10,
                   0x10);

    gSPLookAt(gfx++, lookAt);
    gDPSetHilite1Tile(gfx++, 1, *hilite, 0x10, 0x10);

    return gfx;
}

Hilite* func_8002EABC(Vec3f* object, Vec3f* eye, Vec3f* lightDir, GraphicsContext* gfxCtx) {
    Hilite* hilite;

    OPEN_DISPS(gfxCtx);

    POLY_OPA_DISP = func_8002E830(object, eye, lightDir, gfxCtx, POLY_OPA_DISP, &hilite);

    CLOSE_DISPS(gfxCtx);

    return hilite;
}

Hilite* func_8002EB44(Vec3f* object, Vec3f* eye, Vec3f* lightDir, GraphicsContext* gfxCtx) {
    Hilite* hilite;

    OPEN_DISPS(gfxCtx);

    POLY_XLU_DISP = func_8002E830(object, eye, lightDir, gfxCtx, POLY_XLU_DISP, &hilite);

    CLOSE_DISPS(gfxCtx);

    return hilite;
}

void func_8002EBCC(Actor* actor, PlayState* play, s32 flag) {
    Hilite* hilite;
    Vec3f lightDir;
    Gfx* displayListHead;
    Gfx* displayList;

    lightDir.x = play->envCtx.dirLight1.params.dir.x;
    lightDir.y = play->envCtx.dirLight1.params.dir.y;
    lightDir.z = play->envCtx.dirLight1.params.dir.z;

    if (HREG(80) == 6) {
        osSyncPrintf("z_actor.c 3637 game_play->view.eye=[%f(%f) %f %f]\n", play->view.eye.x,
                     play->view.eye.y, play->view.eye.z);
    }

    hilite = func_8002EABC(&actor->world.pos, &play->view.eye, &lightDir, play->state.gfxCtx);

    if (flag != 0) {
        displayList = Graph_Alloc(play->state.gfxCtx, 2 * sizeof(Gfx));
        displayListHead = displayList;

        OPEN_DISPS(play->state.gfxCtx);

        gDPSetHilite1Tile(displayListHead++, 1, hilite, 0x10, 0x10);
        gSPEndDisplayList(displayListHead);
        gSPSegment(POLY_OPA_DISP++, 0x07, displayList);

        CLOSE_DISPS(play->state.gfxCtx);
    }
}

void func_8002ED80(Actor* actor, PlayState* play, s32 flag) {
    Hilite* hilite;
    Vec3f lightDir;
    Gfx* displayListHead;
    Gfx* displayList;

    lightDir.x = play->envCtx.dirLight1.params.dir.x;
    lightDir.y = play->envCtx.dirLight1.params.dir.y;
    lightDir.z = play->envCtx.dirLight1.params.dir.z;

    hilite = func_8002EB44(&actor->world.pos, &play->view.eye, &lightDir, play->state.gfxCtx);

    if (flag != 0) {
        displayList = Graph_Alloc(play->state.gfxCtx, 2 * sizeof(Gfx));
        displayListHead = displayList;

        OPEN_DISPS(play->state.gfxCtx);

        gDPSetHilite1Tile(displayListHead++, 1, hilite, 0x10, 0x10);
        gSPEndDisplayList(displayListHead);
        gSPSegment(POLY_XLU_DISP++, 0x07, displayList);

        CLOSE_DISPS(play->state.gfxCtx);
    }
}

PosRot* Actor_GetFocus(PosRot* dest, Actor* actor) {
    *dest = actor->focus;

    return dest;
}

PosRot* Actor_GetWorld(PosRot* dest, Actor* actor) {
    *dest = actor->world;

    return dest;
}

PosRot* Actor_GetWorldPosShapeRot(PosRot* arg0, Actor* actor) {
    PosRot sp1C;

    Math_Vec3f_Copy(&sp1C.pos, &actor->world.pos);
    sp1C.rot = actor->shape.rot;
    *arg0 = sp1C;

    return arg0;
}

f32 func_8002EFC0(Actor* actor, Player* player, s16 arg2) {
    s16 yawTemp = (s16)(actor->yawTowardsPlayer - 0x8000) - arg2;
    s16 yawTempAbs = ABS(yawTemp);

    if (player->focusActor != NULL) {
        if ((yawTempAbs > 0x4000) || (actor->flags & ACTOR_FLAG_LOCK_ON_DISABLED)) {
            return FLT_MAX;
        } else {
            f32 ret =
                actor->xyzDistToPlayerSq - actor->xyzDistToPlayerSq * 0.8f * ((0x4000 - yawTempAbs) * (1.0f / 0x8000));

            return ret;
        }
    }

    if (yawTempAbs > 0x2AAA) {
        return FLT_MAX;
    }

    return actor->xyzDistToPlayerSq;
}

typedef struct {
    /* 0x0 */ f32 rangeSq;
    /* 0x4 */ f32 leashScale;
} TargetRangeParams; // size = 0x8

#define TARGET_RANGE(range, leash) \
    { SQ(range), (f32)range / leash }

TargetRangeParams D_80115FF8[] = {
    TARGET_RANGE(70, 140),   TARGET_RANGE(170, 255),    TARGET_RANGE(280, 5600),      TARGET_RANGE(350, 525),
    TARGET_RANGE(700, 1050), TARGET_RANGE(1000, 1500),  TARGET_RANGE(100, 105.36842), TARGET_RANGE(140, 163.33333),
    TARGET_RANGE(240, 576),  TARGET_RANGE(280, 280000),
};

u32 func_8002F090(Actor* actor, f32 arg1) {
    return arg1 < D_80115FF8[actor->targetMode].rangeSq;
}

s32 func_8002F0C8(Actor* actor, Player* player, s32 flag) {
    if ((actor->update == NULL) || !(actor->flags & ACTOR_FLAG_ATTENTION_ENABLED)) {
        return true;
    }

    if (!flag) {
        s16 var = (s16)(actor->yawTowardsPlayer - 0x8000) - player->actor.shape.rot.y;
        s16 abs_var = ABS(var);
        f32 dist;

        if ((player->focusActor == NULL) && (abs_var > 0x2AAA)) {
            dist = FLT_MAX;
        } else {
            dist = actor->xyzDistToPlayerSq;
        }

        return !func_8002F090(actor, D_80115FF8[actor->targetMode].leashScale * dist);
    }

    return false;
}

u32 Actor_ProcessTalkRequest(Actor* actor, PlayState* play) {
    if (actor->flags & ACTOR_FLAG_TALK) {
        actor->flags &= ~ACTOR_FLAG_TALK;
        return true;
    }

    return false;
}

s32 func_8002F1C4(Actor* actor, PlayState* play, f32 arg2, f32 arg3, u32 exchangeItemId) {
    Player* player = GET_PLAYER(play);

    // This is convoluted but it seems like it must be a single if statement to match
    if ((player->actor.flags & ACTOR_FLAG_TALK) || ((exchangeItemId != EXCH_ITEM_NONE) && Player_InCsMode(play)) ||
        (!actor->isTargeted &&
         ((arg3 < fabsf(actor->yDistToPlayer)) || (player->talkActorDistance < actor->xzDistToPlayer) ||
          (arg2 < actor->xzDistToPlayer)))) {
        return false;
    }

    player->talkActor = actor;
    player->talkActorDistance = actor->xzDistToPlayer;
    player->exchangeItemId = exchangeItemId;

    return true;
}

s32 func_8002F298(Actor* actor, PlayState* play, f32 arg2, u32 exchangeItemId) {
    return func_8002F1C4(actor, play, arg2, arg2, exchangeItemId);
}

s32 func_8002F2CC(Actor* actor, PlayState* play, f32 arg2) {
    return func_8002F298(actor, play, arg2, EXCH_ITEM_NONE);
}

s32 func_8002F2F4(Actor* actor, PlayState* play) {
    f32 var1 = 50.0f + actor->colChkInfo.cylRadius;

    return func_8002F2CC(actor, play, var1);
}

u32 Actor_TextboxIsClosing(Actor* actor, PlayState* play) {
    if (Message_GetState(&play->msgCtx) == TEXT_STATE_CLOSING) {
        return true;
    } else {
        return false;
    }
}

s8 func_8002F368(PlayState* play) {
    Player* player = GET_PLAYER(play);

    return player->exchangeItemId;
}

void Actor_GetScreenPos(PlayState* play, Actor* actor, s16* x, s16* y) {
    Vec3f projectedPos;
    f32 w;

    func_8002BE04(play, &actor->focus.pos, &projectedPos, &w);
    *x = projectedPos.x * w * (SCREEN_WIDTH / 2) + (SCREEN_WIDTH / 2);
    *y = projectedPos.y * w * -(SCREEN_HEIGHT / 2) + (SCREEN_HEIGHT / 2);
}

u32 Actor_HasParent(Actor* actor, PlayState* play) {
    if (actor->parent != NULL) {
        return true;
    } else {
        return false;
    }
}

/**
 * Uses the given `GetItemEntry` to prepare the player to receive an item via the animation
 * where Link holds an item over his head. This function does not require an actor for giving
 * the player an item, instead setting the player as their own interactRangeActor and getItemDirection.
 *
 * \param play the Global Context
 * \param getItemEntry the GetItemEntry for the item you want the player to receive.
 * \return true if the player can receive an item, false if not.
 */
s32 GiveItemEntryWithoutActor(PlayState* play, GetItemEntry getItemEntry) {
    Player* player = GET_PLAYER(play);

    if (!(player->stateFlags1 &
        (PLAYER_STATE1_DEAD | PLAYER_STATE1_CHARGING_SPIN_ATTACK | PLAYER_STATE1_HANGING_OFF_LEDGE | PLAYER_STATE1_CLIMBING_LEDGE |
         PLAYER_STATE1_JUMPING | PLAYER_STATE1_FREEFALL | PLAYER_STATE1_FIRST_PERSON | PLAYER_STATE1_CLIMBING_LADDER)) && 
        Player_GetExplosiveHeld(player) < 0) {
        if (((player->heldActor != NULL) && ((getItemEntry.getItemId > GI_NONE) && (getItemEntry.getItemId < GI_MAX)) ||
             (IS_RANDO && (getItemEntry.getItemId > RG_NONE) && (getItemEntry.getItemId < RG_MAX))) ||
            (!(player->stateFlags1 & (PLAYER_STATE1_CARRYING_ACTOR | PLAYER_STATE1_IN_CUTSCENE)))) {
            if ((getItemEntry.getItemId != GI_NONE)) {
                player->getItemEntry = getItemEntry;
                player->getItemId = getItemEntry.getItemId;
                player->interactRangeActor = &player->actor;
                player->getItemDirection = player->actor.shape.rot.y;
                return true;
            }
        }
    }

    return false;
}

/**
 * Uses the given `GetItemEntry` to prepare the player to receive an item via the animation
 * where Link holds an item over his head. This uses data from the actor link is receiving
 * the item from to set the player's interactRangeActor and getItemDirection. It also checks
 * a range from which Link must be from said actor in order to receive the item.
 *
 * \param actor the actor link is receiving an item from. Will usually be a chest but can also
 * be an npc.
 * \param play the Global Context
 * \param getItemEntry the GetItemEntry for the item you want the player to receive.
 * \param xzRange the distance on the x and z axes that the player can be from the target
 * actor to receive the item.
 * \param yRange the distance on the y axis that the player can be from the target actor
 * to receive the item.
 * \return true if the player can receive an item, false if not.
 */
s32 GiveItemEntryFromActor(Actor* actor, PlayState* play, GetItemEntry getItemEntry, f32 xzRange, f32 yRange) {
    Player* player = GET_PLAYER(play);

    if (!(player->stateFlags1 &
        (PLAYER_STATE1_DEAD | PLAYER_STATE1_CHARGING_SPIN_ATTACK | PLAYER_STATE1_HANGING_OFF_LEDGE | PLAYER_STATE1_CLIMBING_LEDGE |
         PLAYER_STATE1_JUMPING | PLAYER_STATE1_FREEFALL | PLAYER_STATE1_FIRST_PERSON | PLAYER_STATE1_CLIMBING_LADDER)) && 
        Player_GetExplosiveHeld(player) < 0) {
        if ((((player->heldActor != NULL) || (actor == player->talkActor)) &&
             ((!IS_RANDO && ((getItemEntry.getItemId > GI_NONE) && (getItemEntry.getItemId < GI_MAX))) ||
              (IS_RANDO && ((getItemEntry.getItemId > RG_NONE) && (getItemEntry.getItemId < RG_MAX))))) ||
            (!(player->stateFlags1 & (PLAYER_STATE1_CARRYING_ACTOR | PLAYER_STATE1_IN_CUTSCENE)))) {
            if ((actor->xzDistToPlayer < xzRange) && (fabsf(actor->yDistToPlayer) < yRange)) {
                s16 yawDiff = actor->yawTowardsPlayer - player->actor.shape.rot.y;
                s32 absYawDiff = ABS(yawDiff);

                if ((getItemEntry.getItemId != GI_NONE) || (player->getItemDirection < absYawDiff)) {
                    iceTrapScale = 0.0f;
                    player->getItemEntry = getItemEntry;
                    player->getItemId = getItemEntry.getItemId;
                    player->interactRangeActor = actor;
                    player->getItemDirection = absYawDiff;
                    return true;
                }
            }
        }
    }

    return false;
}

/**
 * Uses the given `GetItemEntry` to prepare the player to receive an item via the animation
 * where Link holds an item over his head. This is a wrapper function around `GiveItemEntryFromActor`
 * that supplies a fixed xzRange of 50.0f and a fixed yRange of 10.0f.
 *
 * \param actor the target actor that link is receiving an item from.
 * \param play the Global Context
 * \param getItemEntry the GetItemEntry for the item you want the player to receive.
 */
s32 GiveItemEntryFromActorWithFixedRange(Actor* actor, PlayState* play, GetItemEntry getItemEntry) {
    return GiveItemEntryFromActor(actor, play, getItemEntry, 50.0f, 10.0f);
}

// If you're doing something for randomizer, you're probably looking for GiveItemEntryFromActor
s32 Actor_OfferGetItem(Actor* actor, PlayState* play, s32 getItemId, f32 xzRange, f32 yRange) {
    Player* player = GET_PLAYER(play);

    if (!(player->stateFlags1 &
        (PLAYER_STATE1_DEAD | PLAYER_STATE1_CHARGING_SPIN_ATTACK | PLAYER_STATE1_HANGING_OFF_LEDGE | PLAYER_STATE1_CLIMBING_LEDGE |
         PLAYER_STATE1_JUMPING | PLAYER_STATE1_FREEFALL | PLAYER_STATE1_FIRST_PERSON | PLAYER_STATE1_CLIMBING_LADDER)) && 
        Player_GetExplosiveHeld(player) < 0) {
        if ((((player->heldActor != NULL) || (actor == player->talkActor)) &&
            ((!IS_RANDO && ((getItemId > GI_NONE) && (getItemId < GI_MAX))) || (IS_RANDO && ((getItemId > RG_NONE) && (getItemId < RG_MAX))))) ||
            (!(player->stateFlags1 & (PLAYER_STATE1_CARRYING_ACTOR | PLAYER_STATE1_IN_CUTSCENE)))) {
            if ((actor->xzDistToPlayer < xzRange) && (fabsf(actor->yDistToPlayer) < yRange)) {
                s16 yawDiff = actor->yawTowardsPlayer - player->actor.shape.rot.y;
                s32 absYawDiff = ABS(yawDiff);

                if ((getItemId != GI_NONE) || (player->getItemDirection < absYawDiff)) {
                    player->getItemId = getItemId;
                    player->interactRangeActor = actor;
                    player->getItemDirection = absYawDiff;
                    return true;
                }
            }
        }
    }

    return false;
}

// If you're doing something for randomizer, you're probably looking for GiveItemEntryFromActorWithFixedRange
void Actor_OfferGetItemNearby(Actor* actor, PlayState* play, s32 getItemId) {
    Actor_OfferGetItem(actor, play, getItemId, 50.0f, 10.0f);
}

void Actor_OfferCarry(Actor* actor, PlayState* play) {
    Actor_OfferGetItemNearby(actor, play, GI_NONE);
}

u32 Actor_HasNoParent(Actor* actor, PlayState* play) {
    if (actor->parent == NULL) {
        return true;
    } else {
        return false;
    }
}

void func_8002F5C4(Actor* actorA, Actor* actorB, PlayState* play) {
    Actor* parent = actorA->parent;

    if (parent->id == ACTOR_PLAYER) {
        Player* player = (Player*)parent;

        player->heldActor = actorB;
        player->interactRangeActor = actorB;
    }

    parent->child = actorB;
    actorB->parent = parent;
    actorA->parent = NULL;
}

void func_8002F5F0(Actor* actor, PlayState* play) {
    Player* player = GET_PLAYER(play);

    if (actor->xyzDistToPlayerSq < player->closestSecretDistSq) {
        player->closestSecretDistSq = actor->xyzDistToPlayerSq;
    }
}

s32 Actor_IsMounted(PlayState* play, Actor* horse) {
    if (horse->child != NULL) {
        return true;
    } else {
        return false;
    }
}

u32 Actor_SetRideActor(PlayState* play, Actor* horse, s32 mountSide) {
    Player* player = GET_PLAYER(play);

    if (!(player->stateFlags1 &
        (PLAYER_STATE1_DEAD | PLAYER_STATE1_CARRYING_ACTOR | PLAYER_STATE1_CHARGING_SPIN_ATTACK | PLAYER_STATE1_HANGING_OFF_LEDGE |
         PLAYER_STATE1_CLIMBING_LEDGE | PLAYER_STATE1_JUMPING | PLAYER_STATE1_FREEFALL | PLAYER_STATE1_FIRST_PERSON | PLAYER_STATE1_CLIMBING_LADDER))) {
        player->rideActor = horse;
        player->mountSide = mountSide;
        return true;
    }

    return false;
}

s32 Actor_NotMounted(PlayState* play, Actor* horse) {
    if (horse->child == NULL) {
        return true;
    } else {
        return false;
    }
}

void func_8002F698(PlayState* play, Actor* actor, f32 arg2, s16 arg3, f32 arg4, u32 arg5, u32 arg6) {
    Player* player = GET_PLAYER(play);

    player->knockbackDamage = arg6;
    player->knockbackType = arg5;
    player->knockbackRot = arg3;
    player->knockbackSpeed = arg2;
    player->knockbackYVelocity = arg4;
}

void func_8002F6D4(PlayState* play, Actor* actor, f32 arg2, s16 arg3, f32 arg4, u32 arg5) {
    func_8002F698(play, actor, arg2, arg3, arg4, 2, arg5);
}

void func_8002F71C(PlayState* play, Actor* actor, f32 arg2, s16 arg3, f32 arg4) {
    func_8002F6D4(play, actor, arg2, arg3, arg4, 0);
}

void func_8002F758(PlayState* play, Actor* actor, f32 arg2, s16 arg3, f32 arg4, u32 arg5) {
    func_8002F698(play, actor, arg2, arg3, arg4, 1, arg5);
}

void func_8002F7A0(PlayState* play, Actor* actor, f32 arg2, s16 arg3, f32 arg4) {
    func_8002F758(play, actor, arg2, arg3, arg4, 0);
}

void Player_PlaySfx(Actor* actor, u16 sfxId) {
    if (actor->id != ACTOR_PLAYER || sfxId < NA_SE_VO_LI_SWORD_N || sfxId > NA_SE_VO_LI_ELECTRIC_SHOCK_LV_KID) {
        Audio_PlaySoundGeneral(sfxId, &actor->projectedPos, 4, &gSfxDefaultFreqAndVolScale , &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
    } else {
        freqMultiplier = CVarGetFloat(CVAR_AUDIO("LinkVoiceFreqMultiplier"), 1.0);
        if (freqMultiplier <= 0) {
            freqMultiplier = 1;
        }
        // Authentic behavior uses D_801333E0 for both freqScale and a4
        // Audio_PlaySoundGeneral(sfxId, &actor->projectedPos, 4, &D_801333E0 , &D_801333E0, &D_801333E8);
        Audio_PlaySoundGeneral(sfxId, &actor->projectedPos, 4, &freqMultiplier, &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
    }
}

void Audio_PlayActorSound2(Actor* actor, u16 sfxId) {
    Sfx_PlaySfxAtPos(&actor->projectedPos, sfxId);
}

void func_8002F850(PlayState* play, Actor* actor) {
    s32 sfxId;

    if (actor->bgCheckFlags & 0x20) {
        if (actor->yDistToWater < 20.0f) {
            sfxId = NA_SE_PL_WALK_WATER0 - SFX_FLAG;
        } else {
            sfxId = NA_SE_PL_WALK_WATER1 - SFX_FLAG;
        }
    } else {
        sfxId = SurfaceType_GetSfx(&play->colCtx, actor->floorPoly, actor->floorBgId);
    }

    Sfx_PlaySfxAtPos(&actor->projectedPos, NA_SE_EV_BOMB_BOUND);
    Sfx_PlaySfxAtPos(&actor->projectedPos, sfxId + SFX_FLAG);
}

void func_8002F8F0(Actor* actor, u16 sfxId) {
    actor->sfx = sfxId;
    actor->flags |= ACTOR_FLAG_SFX_ACTOR_POS_2;
    actor->flags &= ~(ACTOR_AUDIO_FLAG_SFX_CENTERED_1 | ACTOR_AUDIO_FLAG_SFX_CENTERED_2 | ACTOR_FLAG_SFX_TIMER);
}

void func_8002F91C(Actor* actor, u16 sfxId) {
    actor->sfx = sfxId;
    actor->flags |= ACTOR_AUDIO_FLAG_SFX_CENTERED_1;
    actor->flags &= ~(ACTOR_FLAG_SFX_ACTOR_POS_2 | ACTOR_AUDIO_FLAG_SFX_CENTERED_2 | ACTOR_FLAG_SFX_TIMER);
}

void func_8002F948(Actor* actor, u16 sfxId) {
    actor->sfx = sfxId;
    actor->flags |= ACTOR_AUDIO_FLAG_SFX_CENTERED_2;
    actor->flags &= ~(ACTOR_FLAG_SFX_ACTOR_POS_2 | ACTOR_AUDIO_FLAG_SFX_CENTERED_1 | ACTOR_FLAG_SFX_TIMER);
}

void func_8002F974(Actor* actor, u16 sfxId) {
    actor->flags &= ~(ACTOR_FLAG_SFX_ACTOR_POS_2 | ACTOR_AUDIO_FLAG_SFX_CENTERED_1 | ACTOR_AUDIO_FLAG_SFX_CENTERED_2 | ACTOR_FLAG_SFX_TIMER);
    actor->sfx = sfxId;
}

void func_8002F994(Actor* actor, s32 arg1) {
    actor->flags |= ACTOR_FLAG_SFX_TIMER;
    actor->flags &= ~(ACTOR_FLAG_SFX_ACTOR_POS_2 | ACTOR_AUDIO_FLAG_SFX_CENTERED_1 | ACTOR_AUDIO_FLAG_SFX_CENTERED_2);
    if (arg1 < 40) {
        actor->sfx = NA_SE_PL_WALK_DIRT - SFX_FLAG;
    } else if (arg1 < 100) {
        actor->sfx = NA_SE_PL_WALK_CONCRETE - SFX_FLAG;
    } else {
        actor->sfx = NA_SE_PL_WALK_SAND - SFX_FLAG;
    }
}

// Tests if something hit Jabu Jabu surface, displaying hit splash and playing sfx if true
s32 func_8002F9EC(PlayState* play, Actor* actor, CollisionPoly* poly, s32 bgId, Vec3f* pos) {
    if (func_80041D4C(&play->colCtx, poly, bgId) == 8) {
        play->roomCtx.unk_74[0] = 1;
        CollisionCheck_BlueBlood(play, NULL, pos);
        Audio_PlayActorSound2(actor, NA_SE_IT_WALL_HIT_BUYO);
        return true;
    }

    return false;
}

// Local data used for Farore's Wind light (stored in BSS, possibly a struct?)
LightInfo D_8015BC00;
LightNode* D_8015BC10;
s32 D_8015BC14;
f32 D_8015BC18;

void func_8002FA60(PlayState* play) {
    Vec3f lightPos;

    if (gSaveContext.fw.set) {
        gSaveContext.respawn[RESPAWN_MODE_TOP].data = 0x28;
        gSaveContext.respawn[RESPAWN_MODE_TOP].pos.x = gSaveContext.fw.pos.x;
        gSaveContext.respawn[RESPAWN_MODE_TOP].pos.y = gSaveContext.fw.pos.y;
        gSaveContext.respawn[RESPAWN_MODE_TOP].pos.z = gSaveContext.fw.pos.z;
        gSaveContext.respawn[RESPAWN_MODE_TOP].yaw = gSaveContext.fw.yaw;
        gSaveContext.respawn[RESPAWN_MODE_TOP].playerParams = gSaveContext.fw.playerParams;
        gSaveContext.respawn[RESPAWN_MODE_TOP].entranceIndex = gSaveContext.fw.entranceIndex;
        gSaveContext.respawn[RESPAWN_MODE_TOP].roomIndex = gSaveContext.fw.roomIndex;
        gSaveContext.respawn[RESPAWN_MODE_TOP].tempSwchFlags = gSaveContext.fw.tempSwchFlags;
        gSaveContext.respawn[RESPAWN_MODE_TOP].tempCollectFlags = gSaveContext.fw.tempCollectFlags;
    } else {
        gSaveContext.respawn[RESPAWN_MODE_TOP].data = 0;
        gSaveContext.respawn[RESPAWN_MODE_TOP].pos.x = 0.0f;
        gSaveContext.respawn[RESPAWN_MODE_TOP].pos.y = 0.0f;
        gSaveContext.respawn[RESPAWN_MODE_TOP].pos.z = 0.0f;
    }

    lightPos.x = gSaveContext.respawn[RESPAWN_MODE_TOP].pos.x;
    lightPos.y = gSaveContext.respawn[RESPAWN_MODE_TOP].pos.y + 80.0f;
    lightPos.z = gSaveContext.respawn[RESPAWN_MODE_TOP].pos.z;

    Lights_PointNoGlowSetInfo(&D_8015BC00, lightPos.x, lightPos.y, lightPos.z, 0xFF, 0xFF, 0xFF, -1);

    D_8015BC10 = LightContext_InsertLight(play, &play->lightCtx, &D_8015BC00);
    D_8015BC14 = 0;
    D_8015BC18 = 0.0f;
}

void Actor_DrawFaroresWindPointer(PlayState* play) {
    s32 lightRadius = -1;
    s32 params;

    OPEN_DISPS(play->state.gfxCtx);

    params = gSaveContext.respawn[RESPAWN_MODE_TOP].data;

    if (params) {
        f32 yOffset = LINK_IS_ADULT ? 80.0f : 60.0f;
        f32 ratio = 1.0f;
        s32 alpha = 255;
        s32 temp = params - 40;

        if (temp < 0) {
            gSaveContext.respawn[RESPAWN_MODE_TOP].data = ++params;
            ratio = ABS(params) * 0.025f;
            D_8015BC14 = 60;
            D_8015BC18 = 1.0f;
        } else if (D_8015BC14) {
            D_8015BC14-= CVarGetInteger(CVAR_ENHANCEMENT("FastFarores"), 0) ? 5 : 1;
        } else if (D_8015BC18 > 0.0f) {
            static Vec3f effectVel = { 0.0f, -0.05f, 0.0f };
            static Vec3f effectAccel = { 0.0f, -0.025f, 0.0f };
            Color_RGBA8 effectPrimCol = { 255, 255, 255, 0 };
            Color_RGBA8 effectEnvCol = { 100, 200, 0, 0 };
            if (CVarGetInteger(CVAR_COSMETIC("Magic.FaroresSecondary.Changed"), 0)) {
                effectEnvCol = CVarGetColor(CVAR_COSMETIC("Magic.FaroresSecondary.Value"), effectEnvCol);
            }
            if (CVarGetInteger(CVAR_COSMETIC("Magic.FaroresPrimary.Changed"), 0)) {
                effectPrimCol = CVarGetColor(CVAR_COSMETIC("Magic.FaroresPrimary.Value"), effectPrimCol);
            }
            Vec3f* curPos = &gSaveContext.respawn[RESPAWN_MODE_TOP].pos;
            Vec3f* nextPos = &gSaveContext.respawn[RESPAWN_MODE_DOWN].pos;
            f32 prevNum = D_8015BC18;
            Vec3f dist;
            f32 diff = Math_Vec3f_DistXYZAndStoreDiff(nextPos, curPos, &dist);
            Vec3f effectPos;
            f32 factor;
            f32 length;
            f32 dx;
            f32 speed;

            if (diff < 20.0f) {
                D_8015BC18 = 0.0f;
                Math_Vec3f_Copy(curPos, nextPos);
            } else {
                length = diff * (1.0f / D_8015BC18);
                speed = 20.0f / length;
                speed = CLAMP_MIN(speed, 0.05f);
                Math_StepToF(&D_8015BC18, 0.0f, speed);
                factor = (diff * (D_8015BC18 / prevNum)) / diff;
                curPos->x = nextPos->x + (dist.x * factor);
                curPos->y = nextPos->y + (dist.y * factor);
                curPos->z = nextPos->z + (dist.z * factor);
                length *= 0.5f;
                dx = diff - length;
                yOffset += sqrtf(SQ(length) - SQ(dx)) * 0.2f;
                osSyncPrintf("-------- DISPLAY Y=%f\n", yOffset);
            }

            effectPos.x = curPos->x + Rand_CenteredFloat(6.0f);
            effectPos.y = curPos->y + 80.0f + (6.0f * Rand_ZeroOne());
            effectPos.z = curPos->z + Rand_CenteredFloat(6.0f);

            EffectSsKiraKira_SpawnDispersed(play, &effectPos, &effectVel, &effectAccel, &effectPrimCol,
                                            &effectEnvCol, 1000, 16);

            if (D_8015BC18 == 0.0f) {
                gSaveContext.respawn[RESPAWN_MODE_TOP] = gSaveContext.respawn[RESPAWN_MODE_DOWN];
                gSaveContext.respawn[RESPAWN_MODE_TOP].playerParams = 0x06FF;
                gSaveContext.respawn[RESPAWN_MODE_TOP].data = 40;
            }

            gSaveContext.respawn[RESPAWN_MODE_TOP].pos = *curPos;
        } else if (temp > 0) {
            Vec3f* curPos = &gSaveContext.respawn[RESPAWN_MODE_TOP].pos;
            f32 nextRatio = 1.0f - temp * 0.1f;
            f32 curRatio = 1.0f - (f32)(temp - 1) * 0.1f;
            Vec3f eye;
            Vec3f dist;
            f32 diff;

            if (nextRatio > 0.0f) {
                eye.x = play->view.eye.x;
                eye.y = play->view.eye.y - yOffset;
                eye.z = play->view.eye.z;
                diff = Math_Vec3f_DistXYZAndStoreDiff(&eye, curPos, &dist);
                diff = (diff * (nextRatio / curRatio)) / diff;
                curPos->x = eye.x + (dist.x * diff);
                curPos->y = eye.y + (dist.y * diff);
                curPos->z = eye.z + (dist.z * diff);
                gSaveContext.respawn[RESPAWN_MODE_TOP].pos = *curPos;
            }

            alpha = 255 - (temp * 30);

            if (alpha < 0) {
                gSaveContext.fw.set = 0;
                gSaveContext.respawn[RESPAWN_MODE_TOP].data = 0;
                alpha = 0;
            } else {
                gSaveContext.respawn[RESPAWN_MODE_TOP].data = ++params;
            }

            ratio = 1.0f + ((f32)temp * 0.2); // required to match
        }

        lightRadius = 500.0f * ratio;

        if ((play->csCtx.state == CS_STATE_IDLE) &&
            (((void)0, gSaveContext.respawn[RESPAWN_MODE_TOP].entranceIndex) ==
             ((void)0, gSaveContext.entranceIndex)) &&
            (((void)0, gSaveContext.respawn[RESPAWN_MODE_TOP].roomIndex) == play->roomCtx.curRoom.num)) {
            f32 scale = 0.025f * ratio;

            POLY_XLU_DISP = Gfx_SetupDL(POLY_XLU_DISP, 0x19);

            Matrix_Translate(((void)0, gSaveContext.respawn[RESPAWN_MODE_TOP].pos.x),
                             ((void)0, gSaveContext.respawn[RESPAWN_MODE_TOP].pos.y) + yOffset,
                             ((void)0, gSaveContext.respawn[RESPAWN_MODE_TOP].pos.z), MTXMODE_NEW);
            Matrix_Scale(scale, scale, scale, MTXMODE_APPLY);
            Matrix_Mult(&play->billboardMtxF, MTXMODE_APPLY);
            Matrix_Push();

            gDPPipeSync(POLY_XLU_DISP++);
            Color_RGB8 Spell_env = { 100, 200, 0 };
            Color_RGB8 Spell_col = { 255, 255, 200 };
            if (CVarGetInteger(CVAR_COSMETIC("Magic.FaroresSecondary.Changed"), 0)) {
                Spell_env = CVarGetColor24(CVAR_COSMETIC("Magic.FaroresSecondary.Value"), Spell_env);
            }
            if (CVarGetInteger(CVAR_COSMETIC("Magic.FaroresPrimary.Changed"), 0)) {
                Spell_col = CVarGetColor24(CVAR_COSMETIC("Magic.FaroresPrimary.Value"), Spell_col);
            }
            gDPSetPrimColor(POLY_XLU_DISP++, 128, 128, Spell_col.r, Spell_col.g, Spell_col.b, alpha);
            gDPSetEnvColor(POLY_XLU_DISP++, Spell_env.r, Spell_env.g, Spell_env.b, 255);

            Matrix_RotateZ(((play->gameplayFrames * 1500) & 0xFFFF) * M_PI / 32768.0f, MTXMODE_APPLY);
            gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(play->state.gfxCtx),
                      G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH);
            gSPDisplayList(POLY_XLU_DISP++, gEffFlash1DL);

            Matrix_Pop();
            Matrix_RotateZ(~((play->gameplayFrames * 1200) & 0xFFFF) * M_PI / 32768.0f, MTXMODE_APPLY);

            gSPMatrix(POLY_XLU_DISP++, MATRIX_NEWMTX(play->state.gfxCtx),
                      G_MTX_MODELVIEW | G_MTX_LOAD | G_MTX_NOPUSH);
            gSPDisplayList(POLY_XLU_DISP++, gEffFlash1DL);
        }

        Lights_PointNoGlowSetInfo(&D_8015BC00, ((void)0, gSaveContext.respawn[RESPAWN_MODE_TOP].pos.x),
                                  ((void)0, gSaveContext.respawn[RESPAWN_MODE_TOP].pos.y) + yOffset,
                                  ((void)0, gSaveContext.respawn[RESPAWN_MODE_TOP].pos.z), 255, 255, 255, lightRadius);

    }
    CLOSE_DISPS(play->state.gfxCtx);
}

void func_80030488(PlayState* play) {
    LightContext_RemoveLight(play, &play->lightCtx, D_8015BC10);
}

void Actor_DisableLens(PlayState* play) {
    if (play->actorCtx.lensActive) {
        play->actorCtx.lensActive = false;
        Magic_Reset(play);
    }
}

// Actor_InitContext
void func_800304DC(PlayState* play, ActorContext* actorCtx, ActorEntry* actorEntry) {
    SavedSceneFlags* savedSceneFlags;
    s32 i;

    savedSceneFlags = &gSaveContext.sceneFlags[play->sceneNum];

    memset(actorCtx, 0, sizeof(*actorCtx));

    Matrix_MtxFCopy(&play->billboardMtxF, &gMtxFClear);
    Matrix_MtxFCopy(&play->viewProjectionMtxF, &gMtxFClear);

    actorCtx->flags.chest = savedSceneFlags->chest;
    actorCtx->flags.swch = savedSceneFlags->swch;
    actorCtx->flags.clear = savedSceneFlags->clear;
    actorCtx->flags.collect = savedSceneFlags->collect;

    func_8002CDE4(play, &actorCtx->titleCtx);

    actorCtx->absoluteSpace = NULL;

    Actor_SpawnEntry(actorCtx, actorEntry, play);
    func_8002C0C0(&actorCtx->targetCtx, actorCtx->actorLists[ACTORCAT_PLAYER].head, play);
    func_8002FA60(play);
}

u32 D_80116068[ACTORCAT_MAX] = {
    PLAYER_STATE1_TALKING | PLAYER_STATE1_DEAD | PLAYER_STATE1_IN_ITEM_CS,
    PLAYER_STATE1_TALKING | PLAYER_STATE1_DEAD | PLAYER_STATE1_IN_ITEM_CS,
    0,
    PLAYER_STATE1_TALKING | PLAYER_STATE1_GETTING_ITEM | PLAYER_STATE1_DEAD | PLAYER_STATE1_IN_ITEM_CS,
    PLAYER_STATE1_DEAD,
    PLAYER_STATE1_TALKING | PLAYER_STATE1_DEAD | PLAYER_STATE1_IN_ITEM_CS | PLAYER_STATE1_IN_CUTSCENE,
    PLAYER_STATE1_DEAD | PLAYER_STATE1_IN_ITEM_CS,
    0,
    PLAYER_STATE1_TALKING | PLAYER_STATE1_DEAD | PLAYER_STATE1_IN_ITEM_CS | PLAYER_STATE1_IN_CUTSCENE,
    PLAYER_STATE1_TALKING | PLAYER_STATE1_GETTING_ITEM | PLAYER_STATE1_DEAD | PLAYER_STATE1_IN_ITEM_CS,
    0,
    PLAYER_STATE1_TALKING | PLAYER_STATE1_DEAD | PLAYER_STATE1_IN_ITEM_CS,
};

void Actor_UpdateAll(PlayState* play, ActorContext* actorCtx) {
    Actor* refActor;
    Actor* actor;
    Player* player;
    u32* sp80;
    u32 unkFlag;
    u32 unkCondition;
    Actor* sp74;
    ActorEntry* actorEntry;
    s32 i;

    player = GET_PLAYER(play);

    sp74 = NULL;
    unkFlag = 0;

    if (play->numSetupActors != 0) {
        actorEntry = &play->setupActorList[0];
        for (i = 0; i < play->numSetupActors; i++) {
            Actor_SpawnEntry(&play->actorCtx, actorEntry++, play);
        }
        play->numSetupActors = 0;
        GameInteractor_ExecuteOnSceneSpawnActors();
    }

    if (actorCtx->unk_02 != 0) {
        actorCtx->unk_02--;
    }

    if (KREG(0) == -100) {
        refActor = &GET_PLAYER(play)->actor;
        KREG(0) = 0;
        Actor_Spawn(&play->actorCtx, play, ACTOR_EN_CLEAR_TAG, refActor->world.pos.x,
                    refActor->world.pos.y + 100.0f, refActor->world.pos.z, 0, 0, 0, 1, true);
    }

    sp80 = &D_80116068[0];

    if (player->stateFlags2 & PLAYER_STATE2_OCARINA_PLAYING) {
        unkFlag = ACTOR_FLAG_UPDATE_DURING_OCARINA;
    }

    if ((player->stateFlags1 & PLAYER_STATE1_TALKING) && ((player->actor.textId & 0xFF00) != 0x600)) {
        sp74 = player->talkActor;
    }

    for (i = 0; i < ARRAY_COUNT(actorCtx->actorLists); i++, sp80++) {
        unkCondition = (*sp80 & player->stateFlags1);

        actor = actorCtx->actorLists[i].head;
        while (actor != NULL) {
            if (actor->world.pos.y < -25000.0f) {
                actor->world.pos.y = -25000.0f;
            }

            actor->sfx = 0;

            if (actor->init != NULL) {
                if (Object_IsLoaded(&play->objectCtx, actor->objBankIndex))
                {
                    Actor_SetObjectDependency(play, actor);
                    actor->init(actor, play);
                    actor->init = NULL;

                    GameInteractor_ExecuteOnActorInit(actor);

                    // For enemy health bar we need to know the max health during init
                    if (actor->category == ACTORCAT_ENEMY) {
                        actor->maximumHealth = actor->colChkInfo.health;
                    }
                }
                actor = actor->next;
            } else if (!Object_IsLoaded(&play->objectCtx, actor->objBankIndex)) {
                Actor_Kill(actor);
                actor = actor->next;
            } else if ((unkFlag && !(actor->flags & unkFlag)) ||
                       (!unkFlag && unkCondition && (sp74 != actor) && (actor != player->naviActor) &&
                        (actor != player->heldActor) && (&player->actor != actor->parent))) {
                CollisionCheck_ResetDamage(&actor->colChkInfo);
                actor = actor->next;
            } else if (actor->update == NULL) {
                if (!actor->isDrawn) {
                    actor = Actor_Delete(&play->actorCtx, actor, play);
                } else {
                    Actor_Destroy(actor, play);
                    actor = actor->next;
                }
            } else {
                Math_Vec3f_Copy(&actor->prevPos, &actor->world.pos);
                actor->xzDistToPlayer = Actor_WorldDistXZToActor(actor, &player->actor);
                actor->yDistToPlayer = Actor_HeightDiff(actor, &player->actor);
                actor->xyzDistToPlayerSq = SQ(actor->xzDistToPlayer) + SQ(actor->yDistToPlayer);

                actor->yawTowardsPlayer = Actor_WorldYawTowardActor(actor, &player->actor);
                actor->flags &= ~ACTOR_FLAG_SFX_FOR_PLAYER_BODY_HIT;

                if ((DECR(actor->freezeTimer) == 0) && (actor->flags & (ACTOR_FLAG_UPDATE_CULLING_DISABLED | ACTOR_FLAG_INSIDE_CULLING_VOLUME))) {
                    if (actor == player->focusActor) {
                        actor->isTargeted = true;
                    } else {
                        actor->isTargeted = false;
                    }

                    if ((actor->targetPriority != 0) && (player->focusActor == NULL)) {
                        actor->targetPriority = 0;
                    }

                    Actor_SetObjectDependency(play, actor);
                    if (actor->colorFilterTimer != 0) {
                        actor->colorFilterTimer--;
                    }
                    actor->update(actor, play);
                    GameInteractor_ExecuteOnActorUpdate(actor);
                    func_8003F8EC(play, &play->colCtx.dyna, actor);
                }

                CollisionCheck_ResetDamage(&actor->colChkInfo);

                actor = actor->next;
            }
        }

        if (i == ACTORCAT_BG) {
            DynaPoly_Setup(play, &play->colCtx.dyna);
        }
    }

    actor = player->focusActor;

    if ((actor != NULL) && (actor->update == NULL)) {
        actor = NULL;
        Player_ReleaseLockOn(player);
    }

    if ((actor == NULL) || (player->zTargetActiveTimer < 5)) {
        actor = NULL;
        if (actorCtx->targetCtx.unk_4B != 0) {
            actorCtx->targetCtx.unk_4B = 0;
            Sfx_PlaySfxCentered(NA_SE_SY_LOCK_OFF);
        }
    }

    func_8002C7BC(&actorCtx->targetCtx, player, actor, play);
    TitleCard_Update(play, &actorCtx->titleCtx);
    DynaPoly_UpdateBgActorTransforms(play, &play->colCtx.dyna);
}

void Actor_FaultPrint(Actor* actor, char* command) {
    char* name;

    if (actor == NULL) {
        FaultDrawer_SetCursor(48, 24);
        FaultDrawer_Printf("ACTOR NAME is NULL");
        return;
    }

    name = ActorDB_Retrieve(actor->id)->name;

    osSyncPrintf("アクターの名前(%08x:%s)\n", actor, name); // "Actor name (%08x:%s)"

    if (command != NULL) {
        osSyncPrintf("コメント:%s\n", command); // "Command:%s"
    }

    FaultDrawer_SetCursor(48, 24);
    FaultDrawer_Printf("ACTOR NAME %08x:%s", actor, name);
}

void Actor_Draw(PlayState* play, Actor* actor) {
    FaultClient faultClient;
    Lights* lights;

    Fault_AddClient(&faultClient, Actor_FaultPrint, actor, "Actor_draw");

    FrameInterpolation_RecordOpenChild(actor, 0);
    OPEN_DISPS(play->state.gfxCtx);

    lights = LightContext_NewLights(&play->lightCtx, play->state.gfxCtx);

    Lights_BindAll(lights, play->lightCtx.listHead, (actor->flags & ACTOR_FLAG_IGNORE_POINTLIGHTS) ? NULL : &actor->world.pos);
    Lights_Draw(lights, play->state.gfxCtx);

    FrameInterpolation_RecordActorPosRotMatrix();
    if (actor->flags & ACTOR_FLAG_IGNORE_QUAKE) {
        Matrix_SetTranslateRotateYXZ(
            actor->world.pos.x + play->mainCamera.skyboxOffset.x,
            actor->world.pos.y + (f32)((actor->shape.yOffset * actor->scale.y) + play->mainCamera.skyboxOffset.y),
            actor->world.pos.z + play->mainCamera.skyboxOffset.z, &actor->shape.rot);
    } else {
        Matrix_SetTranslateRotateYXZ(actor->world.pos.x, actor->world.pos.y + (actor->shape.yOffset * actor->scale.y),
                                     actor->world.pos.z, &actor->shape.rot);
    }

    Matrix_Scale(actor->scale.x, actor->scale.y, actor->scale.z, MTXMODE_APPLY);
    Actor_SetObjectDependency(play, actor);

    gSPSegment(POLY_OPA_DISP++, 0x06, play->objectCtx.status[actor->objBankIndex].segment);
    gSPSegment(POLY_XLU_DISP++, 0x06, play->objectCtx.status[actor->objBankIndex].segment);

    if (actor->colorFilterTimer != 0) {
        Color_RGBA8 color = { 0, 0, 0, 255 };

        if (actor->colorFilterParams & 0x8000) {
            color.r = color.g = color.b = ((actor->colorFilterParams & 0x1F00) >> 5) | 7;
        } else if (actor->colorFilterParams & 0x4000) {
            color.r = ((actor->colorFilterParams & 0x1F00) >> 5) | 7;
        } else {
            color.b = ((actor->colorFilterParams & 0x1F00) >> 5) | 7;
        }

        if (actor->colorFilterParams & 0x2000) {
            func_80026860(play, &color, actor->colorFilterTimer, actor->colorFilterParams & 0xFF);
        } else {
            func_80026400(play, &color, actor->colorFilterTimer, actor->colorFilterParams & 0xFF);
        }
    }

    actor->draw(actor, play);

    if (actor->colorFilterTimer != 0) {
        if (actor->colorFilterParams & 0x2000) {
            func_80026A6C(play);
        } else {
            func_80026608(play);
        }
    }

    if (actor->shape.shadowDraw != NULL) {
        actor->shape.shadowDraw(actor, lights, play);
    }

    CLOSE_DISPS(play->state.gfxCtx);
    FrameInterpolation_RecordCloseChild();

    Fault_RemoveClient(&faultClient);
}

void func_80030ED8(Actor* actor) {
    if (actor->flags & ACTOR_FLAG_SFX_ACTOR_POS_2) {
        Audio_PlaySoundGeneral(actor->sfx, &actor->projectedPos, 4, &gSfxDefaultFreqAndVolScale, &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
    } else if (actor->flags & ACTOR_AUDIO_FLAG_SFX_CENTERED_1) {
        Sfx_PlaySfxCentered(actor->sfx);
    } else if (actor->flags & ACTOR_AUDIO_FLAG_SFX_CENTERED_2) {
        Sfx_PlaySfxCentered2(actor->sfx);
    } else if (actor->flags & ACTOR_FLAG_SFX_TIMER) {
        func_800F4C58(&gSfxDefaultPos, NA_SE_SY_TIMER - SFX_FLAG, (s8)(actor->sfx - 1));
    } else {
        Sfx_PlaySfxAtPos(&actor->projectedPos, actor->sfx);
    }
}

#define LENS_MASK_WIDTH 64
#define LENS_MASK_HEIGHT 64
// 26 and 6 are for padding between the mask texture and the screen borders
#define LENS_MASK_OFFSET_S ((SCREEN_WIDTH / 2 - LENS_MASK_WIDTH) - 26)
#define LENS_MASK_OFFSET_T ((SCREEN_HEIGHT / 2 - LENS_MASK_HEIGHT) - 6)

void Actor_DrawLensOverlay(GraphicsContext* gfxCtx) {
    OPEN_DISPS(gfxCtx);

    gDPLoadTextureBlock(POLY_XLU_DISP++, gLensOfTruthMaskTex, G_IM_FMT_I, G_IM_SIZ_8b, LENS_MASK_WIDTH,
                        LENS_MASK_HEIGHT, 0, G_TX_MIRROR | G_TX_CLAMP, G_TX_MIRROR | G_TX_CLAMP, 6, 6, G_TX_NOLOD,
                        G_TX_NOLOD);

    s32 x = OTRGetRectDimensionFromLeftEdge(0) << 2;
    s32 w = OTRGetRectDimensionFromRightEdge(SCREEN_WIDTH) << 2;

    gDPSetTileSize(POLY_XLU_DISP++, G_TX_RENDERTILE, (SCREEN_WIDTH / 2 - LENS_MASK_WIDTH) << 2,
                   (SCREEN_HEIGHT / 2 - LENS_MASK_HEIGHT) << 2, (SCREEN_WIDTH / 2 + LENS_MASK_WIDTH - 1) << 2,
                   (SCREEN_HEIGHT / 2 + LENS_MASK_HEIGHT - 1) << 2);
    gSPWideTextureRectangle(POLY_XLU_DISP++, x, 0, x + abs(x), SCREEN_HEIGHT << 2, G_TX_RENDERTILE, 0, 0, 0, 0);
    gSPWideTextureRectangle(POLY_XLU_DISP++, 0, 0, w, SCREEN_HEIGHT << 2, G_TX_RENDERTILE,
                        LENS_MASK_OFFSET_S << 5, LENS_MASK_OFFSET_T << 5,
                        (1 << 10) * (SCREEN_WIDTH - 2 * LENS_MASK_OFFSET_S) / SCREEN_WIDTH,
                            (1 << 10) * (SCREEN_HEIGHT - 2 * LENS_MASK_OFFSET_T) / SCREEN_HEIGHT);
    gDPPipeSync(POLY_XLU_DISP++);
    CLOSE_DISPS(gfxCtx);
}

void Actor_DrawLensActors(PlayState* play, s32 numInvisibleActors, Actor** invisibleActors) {
    Actor** invisibleActor;
    GraphicsContext* gfxCtx;
    s32 i;

    gfxCtx = play->state.gfxCtx;

    OPEN_DISPS(gfxCtx);

    gDPNoOpString(POLY_OPA_DISP++, "魔法のメガネ START", 0); // "Magic lens START"

    gDPPipeSync(POLY_XLU_DISP++);

    if (play->roomCtx.curRoom.lensMode == LENS_MODE_HIDE_ACTORS) {
        // Update both the color frame buffer and the z-buffer
        gDPSetOtherMode(POLY_XLU_DISP++,
                        G_AD_DISABLE | G_CD_MAGICSQ | G_CK_NONE | G_TC_FILT | G_TF_BILERP | G_TT_NONE | G_TL_TILE |
                            G_TD_CLAMP | G_TP_NONE | G_CYC_1CYCLE | G_PM_NPRIMITIVE,
                        G_AC_THRESHOLD | G_ZS_PRIM | Z_UPD | G_RM_CLD_SURF | G_RM_CLD_SURF2);

        gDPSetCombineMode(POLY_XLU_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM);
        gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 255, 0, 0, 255);

        // the z-buffer will later only allow drawing inside the lens circle
    } else {
        // Update the z-buffer but not the color frame buffer
        gDPSetOtherMode(POLY_XLU_DISP++,
                        G_AD_DISABLE | G_CD_MAGICSQ | G_CK_NONE | G_TC_FILT | G_TF_BILERP | G_TT_NONE | G_TL_TILE |
                            G_TD_CLAMP | G_TP_NONE | G_CYC_1CYCLE | G_PM_NPRIMITIVE,
                        G_AC_THRESHOLD | G_ZS_PRIM | Z_UPD | IM_RD | CVG_DST_SAVE | ZMODE_OPA | FORCE_BL |
                            GBL_c1(G_BL_CLR_BL, G_BL_0, G_BL_CLR_MEM, G_BL_1MA) |
                            GBL_c2(G_BL_CLR_BL, G_BL_0, G_BL_CLR_MEM, G_BL_1MA));

        // inverts the mask image, which initially is 0 inner and 74 outer,
        // by setting the combiner to draw 74 - image instead of the image
        gDPSetCombineLERP(POLY_XLU_DISP++, PRIMITIVE, TEXEL0, PRIM_LOD_FRAC, 0, PRIMITIVE, TEXEL0, PRIM_LOD_FRAC, 0,
                          PRIMITIVE, TEXEL0, PRIM_LOD_FRAC, 0, PRIMITIVE, TEXEL0, PRIM_LOD_FRAC, 0);
        gDPSetPrimColor(POLY_XLU_DISP++, 0, 0xFF, 74, 74, 74, 74);

        // the z-buffer will later only allow drawing outside the lens circle
    }

    // Together with the depth source set above, this sets the depth to the closest.
    // For a pixel with such a depth value, the z-buffer will reject drawing to that pixel.
    gDPSetPrimDepth(POLY_XLU_DISP++, 0, 0);

    // The z-buffer will be updated where the mask is not fully transparent.
    Actor_DrawLensOverlay(gfxCtx);

    // "Magic lens invisible Actor display START"
    gDPNoOpString(POLY_OPA_DISP++, "魔法のメガネ 見えないＡcｔｏｒ表示 START", numInvisibleActors);

    invisibleActor = &invisibleActors[0];
    for (i = 0; i < numInvisibleActors; i++) {
        // "Magic lens invisible Actor display"
        gDPNoOpString(POLY_OPA_DISP++, "魔法のメガネ 見えないＡcｔｏｒ表示", i);
        Actor_Draw(play, *(invisibleActor++));
    }

    // "Magic lens invisible Actor display END"
    gDPNoOpString(POLY_OPA_DISP++, "魔法のメガネ 見えないＡcｔｏｒ表示 END", numInvisibleActors);

    if (play->roomCtx.curRoom.lensMode != LENS_MODE_HIDE_ACTORS) {
        // Draw the lens overlay to the color frame buffer

        gDPNoOpString(POLY_OPA_DISP++, "青い眼鏡(外側)", 0); // "Blue spectacles (exterior)"

        gDPPipeSync(POLY_XLU_DISP++);

        gDPSetOtherMode(POLY_XLU_DISP++,
                        G_AD_DISABLE | G_CD_MAGICSQ | G_CK_NONE | G_TC_FILT | G_TF_BILERP | G_TT_NONE | G_TL_TILE |
                            G_TD_CLAMP | G_TP_NONE | G_CYC_1CYCLE | G_PM_NPRIMITIVE,
                        G_AC_THRESHOLD | G_ZS_PRIM | G_RM_CLD_SURF | G_RM_CLD_SURF2);
        gDPSetCombineMode(POLY_XLU_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM);
        gDPSetPrimColor(POLY_XLU_DISP++, 0, 0, 255, 0, 0, 255);

        Actor_DrawLensOverlay(gfxCtx);

        gDPNoOpString(POLY_OPA_DISP++, "青い眼鏡(外側)", 1); // "Blue spectacles (exterior)"
    }

    gDPNoOpString(POLY_OPA_DISP++, "魔法のメガネ END", 0); // "Magic lens END"

    CLOSE_DISPS(gfxCtx);
}

s32 func_800314B0(PlayState* play, Actor* actor) {
    return func_800314D4(play, actor, &actor->projectedPos, actor->projectedW);
}

s32 func_800314D4(PlayState* play, Actor* actor, Vec3f* arg2, f32 arg3) {
    f32 var;

    if ((arg2->z > -actor->uncullZoneScale) && (arg2->z < (actor->uncullZoneForward + actor->uncullZoneScale))) {
        var = (arg3 < 1.0f) ? 1.0f : 1.0f / arg3;

        if ((((fabsf(arg2->x) - actor->uncullZoneScale) * var) < 1.0f) &&
            (((arg2->y + actor->uncullZoneDownward) * var) > -1.0f) &&
            (((arg2->y - actor->uncullZoneScale) * var) < 1.0f)) {
            return true;
        }
    }

    return false;
}

// #region SOH [Enhancements] Allows us to increase the draw and update distance independently,
// mostly a modified version of the function above and additional tweaks for some specfic actors
s32 Ship_CalcShouldDrawAndUpdate(PlayState* play, Actor* actor, Vec3f* projectedPos, f32 projectedW, bool* shouldDraw,
                                 bool* shouldUpdate) {
    f32 clampedProjectedW;

    // Check if the actor passes its original/vanilla culling requirements
    if (func_800314D4(play, actor, projectedPos, projectedW)) {
        *shouldUpdate = true;
        *shouldDraw = true;
        return true;
    }

    // Skip cutscne actors that depend on culling to hide from camera pans
    if (actor->id == ACTOR_EN_VIEWER) {
        return false;
    }

    s32 multiplier = CVarGetInteger(CVAR_ENHANCEMENT("DisableDrawDistance"), 1);
    multiplier = MAX(multiplier, 1);

    // Some actors have a really short forward value, so we need to add to it before the multiplier to increase the
    // final strength of the forward culling
    f32 adder = (actor->uncullZoneForward < 500) ? 1000.0f : 0.0f;

    if ((projectedPos->z > -actor->uncullZoneScale) &&
        (projectedPos->z < (((actor->uncullZoneForward + adder) * multiplier) + actor->uncullZoneScale))) {
        clampedProjectedW = (projectedW < 1.0f) ? 1.0f : 1.0f / projectedW;

        f32 ratioAdjusted = 1.0f;

        if (CVarGetInteger(CVAR_ENHANCEMENT("WidescreenActorCulling"), 0)) {
            f32 originalAspectRatio = 4.0f / 3.0f;
            f32 currentAspectRatio = OTRGetAspectRatio();
            ratioAdjusted = MAX(currentAspectRatio / originalAspectRatio, 1.0f);
        }

        if ((((fabsf(projectedPos->x) - actor->uncullZoneScale) * (clampedProjectedW / ratioAdjusted)) < 1.0f) &&
            (((projectedPos->y + actor->uncullZoneDownward) * clampedProjectedW) > -1.0f) &&
            (((projectedPos->y - actor->uncullZoneScale) * clampedProjectedW) < 1.0f)) {

            if (CVarGetInteger(CVAR_ENHANCEMENT("ExtendedCullingExcludeGlitchActors"), 0)) {
                // These actors are safe to draw without impacting glitches
                if ((actor->id == ACTOR_OBJ_BOMBIWA || actor->id == ACTOR_OBJ_HAMISHI ||
                     actor->id == ACTOR_EN_ISHI) || // Boulders (hookshot through collision)
                    actor->id == ACTOR_EN_GS ||     // Gossip stones (text delay)
                    actor->id == ACTOR_EN_GE1 ||    // White gerudos (gate clip/archery room transition)
                    actor->id == ACTOR_EN_KZ ||     // King Zora (unfreeze glitch)
                    actor->id == ACTOR_EN_DU ||     // Darunia (Fire temple BK skip)
                    actor->id == ACTOR_DOOR_WARP1   // Blue warps (wrong warps)
                ) {
                    *shouldDraw = true;
                    return true;
                }

                // Skip these actors entirely as their draw funcs impacts glitches
                if ((actor->id == ACTOR_EN_SW &&
                     (((actor->params & 0xE000) >> 0xD) == 1 ||
                      ((actor->params & 0xE000) >> 0xD) == 2)) // Gold Skulltulas (hitbox at 0,0)
                ) {
                    return false;
                }
            }

            *shouldDraw = true;
            *shouldUpdate = true;
            return true;
        }
    }

    return false;
}
// #endregion

void func_800315AC(PlayState* play, ActorContext* actorCtx) {
    s32 invisibleActorCounter;
    Actor* invisibleActors[INVISIBLE_ACTOR_MAX];
    ActorListEntry* actorListEntry;
    Actor* actor;
    s32 i;

    invisibleActorCounter = 0;

    OPEN_DISPS(play->state.gfxCtx);

    actorListEntry = &actorCtx->actorLists[0];

    for (i = 0; i < ARRAY_COUNT(actorCtx->actorLists); i++, actorListEntry++) {
        actor = actorListEntry->head;

        while (actor != NULL) {
            char* actorName = ActorDB_Retrieve(actor->id)->name;

            gDPNoOpString(POLY_OPA_DISP++, actorName, i);
            gDPNoOpString(POLY_XLU_DISP++, actorName, i);

            HREG(66) = i;

            if ((HREG(64) != 1) || ((HREG(65) != -1) && (HREG(65) != HREG(66))) || (HREG(68) == 0)) {
                SkinMatrix_Vec3fMtxFMultXYZW(&play->viewProjectionMtxF, &actor->world.pos, &actor->projectedPos,
                                             &actor->projectedW);
            }

            if ((HREG(64) != 1) || ((HREG(65) != -1) && (HREG(65) != HREG(66))) || (HREG(69) == 0)) {
                if (actor->sfx != 0) {
                    func_80030ED8(actor);
                }
            }

            // #region SOH [Enhancement] Extended culling updates
            bool shipShouldDraw = false;
            bool shipShouldUpdate = false;
            if ((HREG(64) != 1) || ((HREG(65) != -1) && (HREG(65) != HREG(66))) || (HREG(70) == 0)) {
                if (CVarGetInteger(CVAR_ENHANCEMENT("DisableDrawDistance"), 1) > 1 ||
                    CVarGetInteger(CVAR_ENHANCEMENT("WidescreenActorCulling"), 0)) {
                    Ship_CalcShouldDrawAndUpdate(play, actor, &actor->projectedPos, actor->projectedW, &shipShouldDraw,
                                                 &shipShouldUpdate);

                    if (shipShouldUpdate) {
                        actor->flags |= ACTOR_FLAG_INSIDE_CULLING_VOLUME;
                    } else {
                        actor->flags &= ~ACTOR_FLAG_INSIDE_CULLING_VOLUME;
                    }
                } else {
                    if (func_800314B0(play, actor)) {
                        actor->flags |= ACTOR_FLAG_INSIDE_CULLING_VOLUME;
                    } else {
                        actor->flags &= ~ACTOR_FLAG_INSIDE_CULLING_VOLUME;
                    }
                }
            }

            actor->isDrawn = false;

            if ((HREG(64) != 1) || ((HREG(65) != -1) && (HREG(65) != HREG(66))) || (HREG(71) == 0)) {
                if ((actor->init == NULL) && (actor->draw != NULL) &&
                    ((actor->flags & (ACTOR_FLAG_DRAW_CULLING_DISABLED | ACTOR_FLAG_INSIDE_CULLING_VOLUME)) || shipShouldDraw)) {
                    // #endregion
                    if ((actor->flags & ACTOR_FLAG_REACT_TO_LENS) &&
                        ((play->roomCtx.curRoom.lensMode == LENS_MODE_HIDE_ACTORS) ||
                         play->actorCtx.lensActive || (actor->room != play->roomCtx.curRoom.num))) {
                        assert(invisibleActorCounter < INVISIBLE_ACTOR_MAX);
                        invisibleActors[invisibleActorCounter] = actor;
                        invisibleActorCounter++;
                    } else {
                        if ((HREG(64) != 1) || ((HREG(65) != -1) && (HREG(65) != HREG(66))) || (HREG(72) == 0)) {
                            Actor_Draw(play, actor);
                            actor->isDrawn = true;
                        }
                    }
                }
            }

            actor = actor->next;
        }
    }

    if ((HREG(64) != 1) || (HREG(73) != 0)) {
        Effect_DrawAll(play->state.gfxCtx);
    }

    if ((HREG(64) != 1) || (HREG(74) != 0)) {
        EffectSs_DrawAll(play);
    }

    if ((HREG(64) != 1) || (HREG(72) != 0)) {
        if (play->actorCtx.lensActive) {
            Actor_DrawLensActors(play, invisibleActorCounter, invisibleActors);
            if ((play->csCtx.state != CS_STATE_IDLE) || Player_InCsMode(play)) {
                Actor_DisableLens(play);
            }
        }
    }

    Actor_DrawFaroresWindPointer(play);

    if (IREG(32) == 0) {
        Lights_DrawGlow(play);
    }

    if ((HREG(64) != 1) || (HREG(75) != 0)) {
        TitleCard_Draw(play, &actorCtx->titleCtx);
    }

    if ((HREG(64) != 1) || (HREG(76) != 0)) {
        CollisionCheck_DrawCollision(play, &play->colChkCtx);
    }

    CLOSE_DISPS(play->state.gfxCtx);
}

void func_80031A28(PlayState* play, ActorContext* actorCtx) {
    Actor* actor;
    s32 i;

    for (i = 0; i < ARRAY_COUNT(actorCtx->actorLists); i++) {
        actor = actorCtx->actorLists[i].head;
        while (actor != NULL) {
            if (!Object_IsLoaded(&play->objectCtx, actor->objBankIndex)) {
                Actor_Kill(actor);
            }
            actor = actor->next;
        }
    }
}

u8 sEnemyActorCategories[] = { ACTORCAT_ENEMY, ACTORCAT_BOSS };

void Actor_FreezeAllEnemies(PlayState* play, ActorContext* actorCtx, s32 duration) {
    Actor* actor;
    s32 i;

    for (i = 0; i < ARRAY_COUNT(sEnemyActorCategories); i++) {
        actor = actorCtx->actorLists[sEnemyActorCategories[i]].head;
        while (actor != NULL) {
            actor->freezeTimer = duration;
            actor = actor->next;
        }
    }
}

void func_80031B14(PlayState* play, ActorContext* actorCtx) {
    Actor* actor;
    s32 i;

    for (i = 0; i < ARRAY_COUNT(actorCtx->actorLists); i++) {
        actor = actorCtx->actorLists[i].head;
        while (actor != NULL) {
            if ((actor->room >= 0) && (actor->room != play->roomCtx.curRoom.num) &&
                (actor->room != play->roomCtx.prevRoom.num)) {
                if (!actor->isDrawn) {
                    actor = Actor_Delete(actorCtx, actor, play);
                } else {
                    Actor_Kill(actor);
                    Actor_Destroy(actor, play);
                    actor = actor->next;
                }
            } else {
                actor = actor->next;
            }
        }
    }

    CollisionCheck_ClearContext(play, &play->colChkCtx);
    actorCtx->flags.tempClear = 0;
    actorCtx->flags.tempSwch &= 0xFFFFFF;
    play->msgCtx.unk_E3F4 = 0;
}

// Actor_CleanupContext
void func_80031C3C(ActorContext* actorCtx, PlayState* play) {
    Actor* actor;
    s32 i;

    for (i = 0; i < ARRAY_COUNT(actorCtx->actorLists); i++) {
        actor = actorCtx->actorLists[i].head;
        while (actor != NULL) {
            Actor_Delete(actorCtx, actor, play);
            actor = actorCtx->actorLists[i].head;
        }
    }

    if (HREG(20) != 0) {
        osSyncPrintf("絶対魔法領域解放\n"); // "Absolute magic field deallocation"
    }

    if (actorCtx->absoluteSpace != NULL) {
        ZELDA_ARENA_FREE_DEBUG(actorCtx->absoluteSpace);
        actorCtx->absoluteSpace = NULL;
    }

    Play_SaveSceneFlags(play);
    func_80030488(play);
}

/**
 * Adds a given actor instance at the front of the actor list of the specified category.
 * Also sets the actor instance as being of that category.
 */
void Actor_AddToCategory(ActorContext* actorCtx, Actor* actorToAdd, u8 actorCategory) {
    Actor* prevHead;

    actorToAdd->category = actorCategory;

    actorCtx->total++;
    actorCtx->actorLists[actorCategory].length++;
    prevHead = actorCtx->actorLists[actorCategory].head;

    if (prevHead != NULL) {
        prevHead->prev = actorToAdd;
    }

    actorCtx->actorLists[actorCategory].head = actorToAdd;
    actorToAdd->next = prevHead;
}

/**
 * Removes a given actor instance from its actor list.
 * Also sets the temp clear flag of the current room if the actor removed was the last enemy loaded.
 */
Actor* Actor_RemoveFromCategory(PlayState* play, ActorContext* actorCtx, Actor* actorToRemove) {
    Actor* newHead;

    actorCtx->total--;
    actorCtx->actorLists[actorToRemove->category].length--;

    if (actorToRemove->prev != NULL) {
        actorToRemove->prev->next = actorToRemove->next;
    } else {
        actorCtx->actorLists[actorToRemove->category].head = actorToRemove->next;
    }

    newHead = actorToRemove->next;

    if (newHead != NULL) {
        newHead->prev = actorToRemove->prev;
    }

    actorToRemove->next = NULL;
    actorToRemove->prev = NULL;

    if ((actorToRemove->room == play->roomCtx.curRoom.num) && (actorToRemove->category == ACTORCAT_ENEMY) &&
        (actorCtx->actorLists[ACTORCAT_ENEMY].length == 0)) {
        Flags_SetTempClear(play, play->roomCtx.curRoom.num);
    }

    return newHead;
}

void Actor_FreeOverlay(ActorDBEntry* dbEntry) {
    osSyncPrintf(VT_FGCOL(CYAN));

    if (dbEntry->numLoaded == 0) {

        if (dbEntry->reset != NULL) {
            dbEntry->reset();
        }

        if (HREG(20) != 0) {
            osSyncPrintf("アクタークライアントが０になりました\n"); // "Actor client is now 0"
        }
    } else if (HREG(20) != 0) {
        // "%d of actor client remains"
        osSyncPrintf("アクタークライアントはあと %d 残っています\n", dbEntry->numLoaded);
    }

    osSyncPrintf(VT_RST);
}

// SoH: Flag to check if actors are being spawned from the actor entry list
//      This flag is checked against to allow actors which dont have an objectBankIndex in the objectCtx slot/status array to spawn
//      An example of what this fixes, is that it allows hookshot to be used as child
int gMapLoading = 0;

Actor* Actor_Spawn(ActorContext* actorCtx, PlayState* play, s16 actorId, f32 posX, f32 posY, f32 posZ,
                   s16 rotX, s16 rotY, s16 rotZ, s16 params, s16 canRandomize) {

    uint8_t tryRandomizeEnemy = CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) && gSaveContext.fileNum >= 0 && gSaveContext.fileNum <= 2 && canRandomize;

    if (tryRandomizeEnemy) {
        if (!GetRandomizedEnemy(play, &actorId, &posX, &posY, &posZ, &rotX, &rotY, &rotZ, &params)) {
            return NULL;
        }
    }

    Actor* actor;
    s32 objBankIndex;
    u32 temp;

    ActorDBEntry* dbEntry = ActorDB_Retrieve(actorId);

    assert(dbEntry->valid);

    if (HREG(20) != 0) {
        // "Actor class addition [%d:%s]"
        osSyncPrintf("アクタークラス追加 [%d:%s]\n", actorId, dbEntry->name);
    }

    if (actorCtx->total > ACTOR_NUMBER_MAX) {
        // "Ａｃｔｏｒ set number exceeded"
        osSyncPrintf(VT_COL(YELLOW, BLACK) "Ａｃｔｏｒセット数オーバー\n" VT_RST);
        return NULL;
    }

    objBankIndex = Object_GetIndex(&gPlayState->objectCtx, dbEntry->objectId);

    if (objBankIndex < 0 && (!gMapLoading || CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0))) {
        objBankIndex = 0;
    }

    if ((objBankIndex < 0) ||
        ((dbEntry->category == ACTORCAT_ENEMY) && Flags_GetClear(play, play->roomCtx.curRoom.num))) {
        // "No data bank!! <data bank＝%d> (profilep->bank=%d)"
        osSyncPrintf(VT_COL(RED, WHITE) "データバンク無し！！<データバンク＝%d>(profilep->bank=%d)\n" VT_RST,
                     objBankIndex, dbEntry->objectId);
        Actor_FreeOverlay(dbEntry);
        return NULL;
    }

    actor = ZELDA_ARENA_MALLOC_DEBUG(dbEntry->instanceSize);

    if (actor == NULL) {
        // "Actor class cannot be reserved! %s <size＝%d bytes>"
        osSyncPrintf(VT_COL(RED, WHITE) "Ａｃｔｏｒクラス確保できません！ %s <サイズ＝%dバイト>\n", VT_RST,
                     dbEntry->name, dbEntry->instanceSize);
        Actor_FreeOverlay(dbEntry);
        return NULL;
    }

    assert(dbEntry->numLoaded < 255);

    dbEntry->numLoaded++;

    if (HREG(20) != 0) {
        // "Actor client No. %d"
        osSyncPrintf("アクタークライアントは %d 個目です\n", dbEntry->numLoaded);
    }

    memset((u8*)actor, 0, dbEntry->instanceSize);
    actor->id = dbEntry->id;
    actor->flags = dbEntry->flags;

    if (dbEntry->id == ACTOR_EN_PART) {
        actor->objBankIndex = rotZ;
        rotZ = 0;
    } else {
        actor->objBankIndex = objBankIndex;
    }

    actor->init = dbEntry->init;
    actor->destroy = dbEntry->destroy;
    actor->update = dbEntry->update;
    actor->draw = dbEntry->draw;
    actor->room = play->roomCtx.curRoom.num;
    actor->home.pos.x = posX;
    actor->home.pos.y = posY;
    actor->home.pos.z = posZ;
    actor->home.rot.x = rotX;
    actor->home.rot.y = rotY;
    actor->home.rot.z = rotZ;
    actor->params = params;

    Actor_AddToCategory(actorCtx, actor, dbEntry->category);

    temp = gSegments[6];
    Actor_Init(actor, play);
    gSegments[6] = temp;

    return actor;
}

Actor* Actor_SpawnAsChild(ActorContext* actorCtx, Actor* parent, PlayState* play, s16 actorId, f32 posX,
                          f32 posY, f32 posZ, s16 rotX, s16 rotY, s16 rotZ, s16 params) {
    Actor* spawnedActor = Actor_Spawn(actorCtx, play, actorId, posX, posY, posZ, rotX, rotY, rotZ, params, true);

    if (spawnedActor == NULL) {
        return NULL;
    }

    // The following enemies break when the parent actor isn't the same as what would happen in authentic gameplay.
    // As such, don't assign a parent to them at all when spawned with Enemy Randomizer.
    // Gohma (z_boss_goma.c), the Stalchildren spawner (z_en_encount1.c) and the falling platform spawning Stalfos in
    // Forest Temple (z_bg_mori_bigst.c) that normally rely on this behaviour are changed when
    // Enemy Rando is on so they still work properly even without assigning a parent.
    if (CVarGetInteger(CVAR_ENHANCEMENT("RandomizedEnemies"), 0) && (spawnedActor->id == ACTOR_EN_FLOORMAS || spawnedActor->id == ACTOR_EN_PEEHAT)) {
        return spawnedActor;
    }

    parent->child = spawnedActor;
    spawnedActor->parent = parent;

    if (spawnedActor->room >= 0) {
        spawnedActor->room = parent->room;
    }

    return spawnedActor;
}

void Actor_SpawnTransitionActors(PlayState* play, ActorContext* actorCtx) {
    TransitionActorEntry* transitionActor;
    u8 numActors;
    s32 i;

    transitionActor = play->transiActorCtx.list;
    numActors = play->transiActorCtx.numActors;

    for (i = 0; i < numActors; i++) {
        if (transitionActor->id >= 0) {
            if (((transitionActor->sides[0].room >= 0) &&
                 ((transitionActor->sides[0].room == play->roomCtx.curRoom.num) ||
                  (transitionActor->sides[0].room == play->roomCtx.prevRoom.num))) ||
                ((transitionActor->sides[1].room >= 0) &&
                 ((transitionActor->sides[1].room == play->roomCtx.curRoom.num) ||
                  (transitionActor->sides[1].room == play->roomCtx.prevRoom.num)))) {
                Actor_Spawn(actorCtx, play, (s16)(transitionActor->id & 0x1FFF), transitionActor->pos.x,
                            transitionActor->pos.y, transitionActor->pos.z, 0, transitionActor->rotY, 0,
                            (i << 0xA) + transitionActor->params, true);

                transitionActor->id = -transitionActor->id;
                numActors = play->transiActorCtx.numActors;
            }
        }
        transitionActor++;
    }
}

Actor* Actor_SpawnEntry(ActorContext* actorCtx, ActorEntry* actorEntry, PlayState* play) {
    gMapLoading = 1;
    Actor* ret = Actor_Spawn(actorCtx, play, actorEntry->id, actorEntry->pos.x, actorEntry->pos.y, actorEntry->pos.z,
                             actorEntry->rot.x, actorEntry->rot.y, actorEntry->rot.z, actorEntry->params, true);
    gMapLoading = 0;

    return ret;
}

Actor* Actor_Delete(ActorContext* actorCtx, Actor* actor, PlayState* play) {
    Player* player;
    Actor* newHead;
    ActorDBEntry* dbEntry;

    player = GET_PLAYER(play);

    dbEntry = ActorDB_Retrieve(actor->id);

    if (HREG(20) != 0) {
        osSyncPrintf("アクタークラス削除 [%s]\n", dbEntry->name); // "Actor class deleted [%s]"
    }

    if ((player != NULL) && (actor == player->focusActor)) {
        Player_ReleaseLockOn(player);
        Camera_ChangeMode(Play_GetCamera(play, Play_GetActiveCamId(play)), 0);
    }

    if (actor == actorCtx->targetCtx.arrowPointedActor) {
        actorCtx->targetCtx.arrowPointedActor = NULL;
    }

    if (actor == actorCtx->targetCtx.unk_8C) {
        actorCtx->targetCtx.unk_8C = NULL;
    }

    if (actor == actorCtx->targetCtx.bgmEnemy) {
        actorCtx->targetCtx.bgmEnemy = NULL;
    }

    Audio_StopSfxByPos(&actor->projectedPos);
    Actor_Destroy(actor, play);

    newHead = Actor_RemoveFromCategory(play, actorCtx, actor);

    ZELDA_ARENA_FREE_DEBUG(actor);

    dbEntry->numLoaded--;
    Actor_FreeOverlay(dbEntry);

    return newHead;
}

s32 func_80032880(PlayState* play, Actor* actor) {
    s16 sp1E;
    s16 sp1C;

    Actor_GetScreenPos(play, actor, &sp1E, &sp1C);

    return (sp1E > -20) && (sp1E < 340) && (sp1C > -160) && (sp1C < 400);
}

Actor* D_8015BBE8;
Actor* D_8015BBEC;
f32 D_8015BBF0;
f32 sbgmEnemyDistSq;
s32 D_8015BBF8;
s16 D_8015BBFC;

void func_800328D4(PlayState* play, ActorContext* actorCtx, Player* player, u32 actorCategory) {
    f32 var;
    Actor* actor;
    Actor* sp84;
    CollisionPoly* sp80;
    s32 sp7C;
    Vec3f sp70;

    actor = actorCtx->actorLists[actorCategory].head;
    sp84 = player->focusActor;

    while (actor != NULL) {
        if ((actor->update != NULL) && ((Player*)actor != player) && CHECK_FLAG_ALL(actor->flags, ACTOR_FLAG_ATTENTION_ENABLED)) {

            // This block below is for determining the closest actor to player in determining the volume
            // used while playing enemy bgm music
            if ((actorCategory == ACTORCAT_ENEMY) && CHECK_FLAG_ALL(actor->flags, ACTOR_FLAG_ATTENTION_ENABLED | ACTOR_FLAG_HOSTILE) &&
                (actor->xyzDistToPlayerSq < SQ(500.0f)) && (actor->xyzDistToPlayerSq < sbgmEnemyDistSq)) {
                actorCtx->targetCtx.bgmEnemy = actor;
                sbgmEnemyDistSq = actor->xyzDistToPlayerSq;
            }

            if (actor != sp84) {
                var = func_8002EFC0(actor, player, D_8015BBFC);
                if ((var < D_8015BBF0) && func_8002F090(actor, var) && func_80032880(play, actor) &&
                    (!BgCheck_CameraLineTest1(&play->colCtx, &player->actor.focus.pos, &actor->focus.pos, &sp70,
                                              &sp80, 1, 1, 1, 1, &sp7C) ||
                     SurfaceType_IsIgnoredByProjectiles(&play->colCtx, sp80, sp7C))) {
                    if (actor->targetPriority != 0) {
                        if (actor->targetPriority < D_8015BBF8) {
                            D_8015BBEC = actor;
                            D_8015BBF8 = actor->targetPriority;
                        }
                    } else {
                        D_8015BBE8 = actor;
                        D_8015BBF0 = var;
                    }
                }
            }
        }

        actor = actor->next;
    }
}

u8 D_801160A0[] = {
    ACTORCAT_BOSS,  ACTORCAT_ENEMY,  ACTORCAT_BG,   ACTORCAT_EXPLOSIVE, ACTORCAT_NPC,  ACTORCAT_ITEMACTION,
    ACTORCAT_CHEST, ACTORCAT_SWITCH, ACTORCAT_PROP, ACTORCAT_MISC,      ACTORCAT_DOOR, ACTORCAT_SWITCH,
};

Actor* func_80032AF0(PlayState* play, ActorContext* actorCtx, Actor** actorPtr, Player* player) {
    s32 i;
    u8* entry;

    D_8015BBE8 = D_8015BBEC = NULL;
    D_8015BBF0 = sbgmEnemyDistSq = FLT_MAX;
    D_8015BBF8 = 0x7FFFFFFF;

    if (!Player_InCsMode(play)) {
        entry = &D_801160A0[0];

        actorCtx->targetCtx.bgmEnemy = NULL;
        D_8015BBFC = player->actor.shape.rot.y;

        for (i = 0; i < 3; i++) {
            func_800328D4(play, actorCtx, player, *entry);
            entry++;
        }

        if (D_8015BBE8 == NULL) {
            for (; i < ARRAY_COUNT(D_801160A0); i++) {
                func_800328D4(play, actorCtx, player, *entry);
                entry++;
            }
        }
    }

    if (D_8015BBE8 == 0) {
        *actorPtr = D_8015BBEC;
    } else {
        *actorPtr = D_8015BBE8;
    }

    return *actorPtr;
}

/**
 * Finds the first actor instance of a specified ID and category if there is one.
 */
Actor* Actor_Find(ActorContext* actorCtx, s32 actorId, s32 actorCategory) {
    Actor* actor = actorCtx->actorLists[actorCategory].head;

    while (actor != NULL) {
        if (actorId == actor->id) {
            return actor;
        }
        actor = actor->next;
    }

    return NULL;
}

/**
 * Play the death sound effect and flash the screen white for 4 frames.
 * While the screen flashes, the game freezes.
 */
void Enemy_StartFinishingBlow(PlayState* play, Actor* actor) {
    play->actorCtx.freezeFlashTimer = 5;
    SoundSource_PlaySfxAtFixedWorldPos(play, &actor->world.pos, 20, NA_SE_EN_LAST_DAMAGE);
}

s16 func_80032CB4(s16* arg0, s16 arg1, s16 arg2, s16 arg3) {
    if (DECR(arg0[1]) == 0) {
        arg0[1] = Rand_S16Offset(arg1, arg2);
    }

    if ((arg0[1] - arg3) > 0) {
        arg0[0] = 0;
    } else if (((arg0[1] - arg3) > -2) || (arg0[1] < 2)) {
        arg0[0] = 1;
    } else {
        arg0[0] = 2;
    }

    return arg0[0];
}

s16 func_80032D60(s16* arg0, s16 arg1, s16 arg2, s16 arg3) {
    if (DECR(arg0[1]) == 0) {
        arg0[1] = Rand_S16Offset(arg1, arg2);
        arg0[0]++;

        if ((arg0[0] % 3) == 0) {
            arg0[0] = (s32)(Rand_ZeroOne() * arg3) * 3;
        }
    }

    return arg0[0];
}

void BodyBreak_Alloc(BodyBreak* bodyBreak, s32 count, PlayState* play) {
    u32 matricesSize;
    u32 dListsSize;
    u32 objectIdsSize;

    matricesSize = (count + 1) * sizeof(*bodyBreak->matrices);
    bodyBreak->matrices = ZELDA_ARENA_MALLOC_DEBUG(matricesSize);

    if (bodyBreak->matrices != NULL) {
        dListsSize = (count + 1) * sizeof(*bodyBreak->dLists);
        bodyBreak->dLists = ZELDA_ARENA_MALLOC_DEBUG(dListsSize);

        if (bodyBreak->dLists != NULL) {
            objectIdsSize = (count + 1) * sizeof(*bodyBreak->objectIds);
            bodyBreak->objectIds = ZELDA_ARENA_MALLOC_DEBUG(objectIdsSize);

            if (bodyBreak->objectIds != NULL) {
                memset((u8*)bodyBreak->matrices,0, matricesSize);
                memset((u8*)bodyBreak->dLists, 0, dListsSize);
                memset((u8*)bodyBreak->objectIds, 0, objectIdsSize);
                bodyBreak->val = 1;
                return;
            }
        }
    }

    if (bodyBreak->matrices != NULL) {
        ZELDA_ARENA_FREE_DEBUG(bodyBreak->matrices);
    }

    if (bodyBreak->dLists != NULL) {
        ZELDA_ARENA_FREE_DEBUG(bodyBreak->dLists);
    }

    if (bodyBreak->objectIds != NULL) {
        ZELDA_ARENA_FREE_DEBUG(bodyBreak->objectIds);
    }
}

void BodyBreak_SetInfo(BodyBreak* bodyBreak, s32 limbIndex, s32 minLimbIndex, s32 maxLimbIndex, u32 count, Gfx** dList,
                       s16 objectId) {
    PlayState* play = Effect_GetPlayState();

    if ((play->actorCtx.freezeFlashTimer == 0) && (bodyBreak->val > 0)) {
        if ((limbIndex >= minLimbIndex) && (limbIndex <= maxLimbIndex) && (*dList != NULL)) {
            bodyBreak->dLists[bodyBreak->val] = *dList;
            Matrix_Get(&bodyBreak->matrices[bodyBreak->val]);
            bodyBreak->objectIds[bodyBreak->val] = objectId;
            bodyBreak->val++;
        }

        if (limbIndex != bodyBreak->prevLimbIndex) {
            bodyBreak->count++;
        }

        if ((u32)bodyBreak->count >= count) {
            bodyBreak->count = bodyBreak->val - 1;
            bodyBreak->val = BODYBREAK_STATUS_READY;
        }
    }

    bodyBreak->prevLimbIndex = limbIndex;
}

s32 BodyBreak_SpawnParts(Actor* actor, BodyBreak* bodyBreak, PlayState* play, s16 type) {
    EnPart* spawnedEnPart;
    MtxF* mtx;
    s16 objBankIndex;

    if (bodyBreak->val != BODYBREAK_STATUS_READY) {
        return false;
    }

    while (bodyBreak->count > 0) {
        Matrix_Put(&bodyBreak->matrices[bodyBreak->count]);
        Matrix_Scale(1.0f / actor->scale.x, 1.0f / actor->scale.y, 1.0f / actor->scale.z, MTXMODE_APPLY);
        Matrix_Get(&bodyBreak->matrices[bodyBreak->count]);

        if (1) {
            if (bodyBreak->objectIds[bodyBreak->count] >= 0) {
                objBankIndex = bodyBreak->objectIds[bodyBreak->count];
            } else {
                objBankIndex = actor->objBankIndex;
            }
        }

        mtx = &bodyBreak->matrices[bodyBreak->count];

        spawnedEnPart = (EnPart*)Actor_SpawnAsChild(&play->actorCtx, actor, play, ACTOR_EN_PART, mtx->xw,
                                                    mtx->yw, mtx->zw, 0, 0, objBankIndex, type);

        if (spawnedEnPart != NULL) {
            Matrix_MtxFToYXZRotS(&bodyBreak->matrices[bodyBreak->count], &spawnedEnPart->actor.shape.rot, 0);
            spawnedEnPart->displayList = bodyBreak->dLists[bodyBreak->count];
            spawnedEnPart->actor.scale = actor->scale;
        }

        bodyBreak->count--;
    }

    bodyBreak->val = BODYBREAK_STATUS_FINISHED;

    ZELDA_ARENA_FREE_DEBUG(bodyBreak->matrices);
    ZELDA_ARENA_FREE_DEBUG(bodyBreak->dLists);
    ZELDA_ARENA_FREE_DEBUG(bodyBreak->objectIds);

    return true;
}

void Actor_SpawnFloorDustRing(PlayState* play, Actor* actor, Vec3f* posXZ, f32 radius, s32 amountMinusOne,
                              f32 randAccelWeight, s16 scale, s16 scaleStep, u8 useLighting) {
    Vec3f pos;
    Vec3f velocity = { 0.0f, 0.0f, 0.0f };
    Vec3f accel = { 0.0f, 0.3f, 0.0f };
    f32 angle;
    s32 i;

    angle = (Rand_ZeroOne() - 0.5f) * (2.0f * 3.14f);
    pos.y = actor->floorHeight;
    accel.y += (Rand_ZeroOne() - 0.5f) * 0.2f;

    for (i = amountMinusOne; i >= 0; i--) {
        pos.x = Math_SinF(angle) * radius + posXZ->x;
        pos.z = Math_CosF(angle) * radius + posXZ->z;
        accel.x = (Rand_ZeroOne() - 0.5f) * randAccelWeight;
        accel.z = (Rand_ZeroOne() - 0.5f) * randAccelWeight;

        if (scale == 0) {
            func_8002857C(play, &pos, &velocity, &accel);
        } else {
            if (useLighting) {
                func_800286CC(play, &pos, &velocity, &accel, scale, scaleStep);
            } else {
                func_8002865C(play, &pos, &velocity, &accel, scale, scaleStep);
            }
        }

        angle += (2.0f * 3.14f) / (amountMinusOne + 1.0f);
    }
}

void func_80033480(PlayState* play, Vec3f* posBase, f32 randRangeDiameter, s32 amountMinusOne, s16 scaleBase,
                   s16 scaleStep, u8 arg6) {
    Vec3f pos;
    Vec3f velocity = { 0.0f, 0.0f, 0.0f };
    Vec3f accel = { 0.0f, 0.3f, 0.0f };
    s16 scale;
    u32 var2;
    s32 i;

    for (i = amountMinusOne; i >= 0; i--) {
        pos.x = posBase->x + ((Rand_ZeroOne() - 0.5f) * randRangeDiameter);
        pos.y = posBase->y + ((Rand_ZeroOne() - 0.5f) * randRangeDiameter);
        pos.z = posBase->z + ((Rand_ZeroOne() - 0.5f) * randRangeDiameter);

        scale = (s16)((Rand_ZeroOne() * scaleBase) * 0.2f) + scaleBase;
        var2 = arg6;

        if (var2 != 0) {
            func_800286CC(play, &pos, &velocity, &accel, scale, scaleStep);
        } else {
            func_8002865C(play, &pos, &velocity, &accel, scale, scaleStep);
        }
    }
}

Actor* Actor_GetCollidedExplosive(PlayState* play, Collider* collider) {
    if ((collider->acFlags & AC_HIT) && (collider->ac->category == ACTORCAT_EXPLOSIVE)) {
        collider->acFlags &= ~AC_HIT;
        return collider->ac;
    }

    return NULL;
}

Actor* func_80033684(PlayState* play, Actor* explosiveActor) {
    Actor* actor = play->actorCtx.actorLists[ACTORCAT_EXPLOSIVE].head;

    while (actor != NULL) {
        if ((actor == explosiveActor) || (actor->params != 1)) {
            actor = actor->next;
        } else {
            if (Actor_WorldDistXYZToActor(explosiveActor, actor) <= (actor->shape.rot.z * 10) + 80.0f) {
                return actor;
            } else {
                actor = actor->next;
            }
        }
    }

    return NULL;
}

/**
 * Dynamically changes the category of a given actor instance.
 * This is done by moving it to the corresponding category list and setting its category variable accordingly.
 */
void Actor_ChangeCategory(PlayState* play, ActorContext* actorCtx, Actor* actor, u8 actorCategory) {
    Actor_RemoveFromCategory(play, actorCtx, actor);
    Actor_AddToCategory(actorCtx, actor, actorCategory);
}

/**
 * Checks if a hookshot or arrow actor is going to collide with the cylinder denoted by the
 * actor's `cylRadius` and `cylHeight`.
 * The check is only peformed if the projectile actor is within the provided sphere radius.
 *
 * Returns the actor if there will be collision, NULL otherwise.
 */
Actor* Actor_GetProjectileActor(PlayState* play, Actor* refActor, f32 radius) {
    Actor* actor;
    Vec3f spA8;
    f32 deltaX;
    f32 deltaY;
    f32 deltaZ;
    Vec3f sp90;
    Vec3f sp84;

    actor = play->actorCtx.actorLists[ACTORCAT_ITEMACTION].head;
    while (actor != NULL) {
        if (((actor->id != ACTOR_ARMS_HOOK) && (actor->id != ACTOR_EN_ARROW)) || (actor == refActor)) {
            actor = actor->next;
        } else {
            //! @bug The projectile actor gets unsafely casted to a hookshot to check its timer, even though
            //  it can also be an arrow.
            //  Luckily, the field at the same offset in the arrow actor is the x component of a vector
            //  which will rarely ever be 0. So it's very unlikely for this bug to cause an issue.
            //
            //  SoH [Port] We're making a change here, it doesn't technically fix the bug but makes it behave
            //  more like hardware. Because of pointer size differences in SoH this was accessing a different
            //  place in memory and causing issues with Dark link behavior, and probably other places too
            if ((Math_Vec3f_DistXYZ(&refActor->world.pos, &actor->world.pos) > radius) ||
                (actor->id == ACTOR_ARMS_HOOK && ((ArmsHook*)actor)->timer == 0) ||
                (actor->id == ACTOR_EN_ARROW && ((EnArrow*)actor)->unk_210.x == 0)
            ) {
                actor = actor->next;
            } else {
                deltaX = Math_SinS(actor->world.rot.y) * (actor->speedXZ * 10.0f);
                deltaY = actor->velocity.y + (actor->gravity * 10.0f);
                deltaZ = Math_CosS(actor->world.rot.y) * (actor->speedXZ * 10.0f);

                spA8.x = actor->world.pos.x + deltaX;
                spA8.y = actor->world.pos.y + deltaY;
                spA8.z = actor->world.pos.z + deltaZ;

                if (CollisionCheck_CylSideVsLineSeg(refActor->colChkInfo.cylRadius, refActor->colChkInfo.cylHeight,
                                                    0.0f, &refActor->world.pos, &actor->world.pos, &spA8, &sp90,
                                                    &sp84)) {
                    return actor;
                } else {
                    actor = actor->next;
                }
            }
        }
    }

    return NULL;
}

/**
 * Sets the actor's text id with a dynamic prefix based on the current scene.
 */
void Actor_SetTextWithPrefix(PlayState* play, Actor* actor, s16 baseTextId) {
    s16 prefix;

    switch (play->sceneNum) {
        case SCENE_DEKU_TREE:
        case SCENE_DEKU_TREE_BOSS:
        case SCENE_FOREST_TEMPLE_BOSS:
        case SCENE_KNOW_IT_ALL_BROS_HOUSE:
        case SCENE_TWINS_HOUSE:
        case SCENE_MIDOS_HOUSE:
        case SCENE_SARIAS_HOUSE:
        case SCENE_KOKIRI_SHOP:
        case SCENE_LINKS_HOUSE:
        case SCENE_KOKIRI_FOREST:
        case SCENE_SACRED_FOREST_MEADOW:
        case SCENE_LOST_WOODS:
        case 112:
            prefix = 0x1000;
            break;
        case SCENE_STABLE:
        case SCENE_HYRULE_FIELD:
        case SCENE_LON_LON_RANCH:
            prefix = 0x2000;
            break;
        case SCENE_FIRE_TEMPLE:
        case SCENE_DODONGOS_CAVERN_BOSS:
        case SCENE_FIRE_TEMPLE_BOSS:
        case SCENE_DEATH_MOUNTAIN_TRAIL:
        case SCENE_DEATH_MOUNTAIN_CRATER:
        case SCENE_GORON_CITY:
            prefix = 0x3000;
            break;
        case SCENE_JABU_JABU:
        case SCENE_JABU_JABU_BOSS:
        case SCENE_ZORAS_RIVER:
        case SCENE_ZORAS_DOMAIN:
        case SCENE_ZORAS_FOUNTAIN:
            prefix = 0x4000;
            break;
        case SCENE_SHADOW_TEMPLE:
        case SCENE_SHADOW_TEMPLE_BOSS:
        case SCENE_KAKARIKO_CENTER_GUEST_HOUSE:
        case SCENE_BACK_ALLEY_HOUSE:
        case SCENE_DOG_LADY_HOUSE:
        case SCENE_GRAVEKEEPERS_HUT:
        case SCENE_REDEAD_GRAVE:
        case SCENE_WINDMILL_AND_DAMPES_GRAVE:
        case SCENE_KAKARIKO_VILLAGE:
        case SCENE_GRAVEYARD:
            prefix = 0x5000;
            break;
        case SCENE_SPIRIT_TEMPLE:
        case SCENE_SPIRIT_TEMPLE_BOSS:
        case SCENE_IMPAS_HOUSE:
        case SCENE_CARPENTERS_TENT:
        case SCENE_LAKE_HYLIA:
        case SCENE_GERUDO_VALLEY:
        case SCENE_DESERT_COLOSSUS:
            prefix = 0x6000;
            break;
        case SCENE_MARKET_ENTRANCE_DAY:
        case SCENE_BACK_ALLEY_DAY:
        case SCENE_BACK_ALLEY_NIGHT:
        case SCENE_MARKET_DAY:
        case SCENE_MARKET_NIGHT:
        case SCENE_MARKET_RUINS:
        case SCENE_HYRULE_CASTLE:
            prefix = 0x7000;
            break;
        default:
            prefix = 0x0000;
            break;
    }

    actor->textId = prefix | baseTextId;
}

/**
 * Checks if a given actor will be standing on the ground after being translated
 * by the provided distance and angle.
 *
 * Returns true if the actor will be standing on ground.
 */
s16 Actor_TestFloorInDirection(Actor* actor, PlayState* play, f32 distance, s16 angle) {
    s16 ret;
    s16 prevBgCheckFlags;
    f32 dx;
    f32 dz;
    Vec3f prevActorPos;

    Math_Vec3f_Copy(&prevActorPos, &actor->world.pos);
    prevBgCheckFlags = actor->bgCheckFlags;

    dx = Math_SinS(angle) * distance;
    dz = Math_CosS(angle) * distance;
    actor->world.pos.x += dx;
    actor->world.pos.z += dz;

    Actor_UpdateBgCheckInfo(play, actor, 0.0f, 0.0f, 0.0f, 4);

    Math_Vec3f_Copy(&actor->world.pos, &prevActorPos);

    ret = actor->bgCheckFlags & 1;
    actor->bgCheckFlags = prevBgCheckFlags;

    return ret;
}

/**
 * Returns true if the player is targeting the provided actor
 */
s32 Actor_IsTargeted(PlayState* play, Actor* actor) {
    Player* player = GET_PLAYER(play);

    if ((player->stateFlags1 & PLAYER_STATE1_HOSTILE_LOCK_ON) && actor->isTargeted) {
        return true;
    } else {
        return false;
    }
}

/**
 * Returns true if the player is targeting an actor other than the provided actor
 */
s32 Actor_OtherIsTargeted(PlayState* play, Actor* actor) {
    Player* player = GET_PLAYER(play);

    if ((player->stateFlags1 & PLAYER_STATE1_HOSTILE_LOCK_ON) && !actor->isTargeted) {
        return true;
    } else {
        return false;
    }
}

f32 func_80033AEC(Vec3f* arg0, Vec3f* arg1, f32 arg2, f32 arg3, f32 arg4, f32 arg5) {
    f32 ret = 0.0f;

    if (arg4 <= Math_Vec3f_DistXYZ(arg0, arg1)) {
        ret = Math_SmoothStepToF(&arg1->x, arg0->x, arg2, arg3, 0.0f);
        ret += Math_SmoothStepToF(&arg1->y, arg0->y, arg2, arg3, 0.0f);
        ret += Math_SmoothStepToF(&arg1->z, arg0->z, arg2, arg3, 0.0f);
    } else if (arg5 < Math_Vec3f_DistXYZ(arg0, arg1)) {
        ret = Math_SmoothStepToF(&arg1->x, arg0->x, arg2, arg3, 0.0f);
        ret += Math_SmoothStepToF(&arg1->y, arg0->y, arg2, arg3, 0.0f);
        ret += Math_SmoothStepToF(&arg1->z, arg0->z, arg2, arg3, 0.0f);
    }

    return ret;
}

void func_80033C30(Vec3f* arg0, Vec3f* arg1, u8 alpha, PlayState* play) {
    MtxF sp60;
    f32 var;
    Vec3f sp50;
    CollisionPoly* sp4C;

    OPEN_DISPS(play->state.gfxCtx);

    POLY_OPA_DISP = Gfx_SetupDL(POLY_OPA_DISP, 0x2C);

    gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 0, 0, 0, alpha);

    sp50.x = arg0->x;
    sp50.y = arg0->y + 1.0f;
    sp50.z = arg0->z;

    var = BgCheck_EntityRaycastFloor2(play, &play->colCtx, &sp4C, &sp50);

    if (sp4C != NULL) {
        func_80038A28(sp4C, arg0->x, var, arg0->z, &sp60);
        Matrix_Put(&sp60);
    } else {
        Matrix_Translate(arg0->x, arg0->y, arg0->z, MTXMODE_NEW);
    }

    Matrix_Scale(arg1->x, 1.0f, arg1->z, MTXMODE_APPLY);

    gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx),
              G_MTX_MODELVIEW | G_MTX_LOAD);
    gSPDisplayList(POLY_OPA_DISP++, gCircleShadowDL);

    CLOSE_DISPS(play->state.gfxCtx);
}

void func_80033DB8(PlayState* play, s16 arg1, s16 arg2) {
    s16 var = Quake_Add(&play->mainCamera, 3);

    Quake_SetSpeed(var, 20000);
    Quake_SetQuakeValues(var, arg1, 0, 0, 0);
    Quake_SetCountdown(var, arg2);
}

void func_80033E1C(PlayState* play, s16 arg1, s16 arg2, s16 arg3) {
    s16 var = Quake_Add(&play->mainCamera, 3);

    Quake_SetSpeed(var, arg3);
    Quake_SetQuakeValues(var, arg1, 0, 0, 0);
    Quake_SetCountdown(var, arg2);
}

void func_80033E88(Actor* actor, PlayState* play, s16 arg2, s16 arg3) {
    if (arg2 >= 5) {
        func_800AA000(actor->xyzDistToPlayerSq, 0xFF, 0x14, 0x96);
    } else {
        func_800AA000(actor->xyzDistToPlayerSq, 0xB4, 0x14, 0x64);
    }

    func_80033DB8(play, arg2, arg3);
}

f32 Rand_ZeroFloat(f32 f) {
    return Rand_ZeroOne() * f;
}

f32 Rand_CenteredFloat(f32 f) {
    return (Rand_ZeroOne() - 0.5f) * f;
}

typedef struct {
    /* 0x00 */ f32 chainAngle;
    /* 0x04 */ f32 chainLength;
    /* 0x08 */ f32 yShift;
    /* 0x0C */ f32 chainsScale;
    /* 0x10 */ f32 chainsRotZInit;
    /* 0x14 */ Gfx* chainDL;
    /* 0x18 */ Gfx* lockDL;
} DoorLockInfo; // size = 0x1C

static DoorLockInfo sDoorLocksInfo[] = {
    /* DOORLOCK_NORMAL */ { 0.54f, 6000.0f, 5000.0f, 1.0f, 0.0f, gDoorChainDL, gDoorLockDL },
    /* DOORLOCK_BOSS */ { 0.644f, 12000.0f, 8000.0f, 1.0f, 0.0f, gBossDoorChainDL, gBossDoorLockDL },
    /* DOORLOCK_NORMAL_SPIRIT */ { 0.64000005f, 8500.0f, 8000.0f, 1.75f, 0.1f, gDoorChainDL, gDoorLockDL },
};

/**
 * Draws chains and lock of a locked door, of the specified `type` (see `DoorLockType`).
 * `frame` can be 0 to 10, where 0 is "open" and 10 is "closed", the chains slide accordingly.
 */
void Actor_DrawDoorLock(PlayState* play, s32 frame, s32 type) {
    DoorLockInfo* entry;
    s32 i;
    MtxF baseMtxF;
    f32 chainRotZ;
    f32 chainsTranslateX;
    f32 chainsTranslateY;
    f32 rotZStep;

    entry = &sDoorLocksInfo[type];
    chainRotZ = entry->chainsRotZInit;

    OPEN_DISPS(play->state.gfxCtx);

    Matrix_Translate(0.0f, entry->yShift, 500.0f, MTXMODE_APPLY);
    Matrix_Get(&baseMtxF);

    chainsTranslateX = sinf(entry->chainAngle - chainRotZ) * -(10 - frame) * 0.1f * entry->chainLength;
    chainsTranslateY = cosf(entry->chainAngle - chainRotZ) * (10 - frame) * 0.1f * entry->chainLength;

    for (i = 0; i < 4; i++) {
        FrameInterpolation_RecordOpenChild(entry, i);
        Matrix_Put(&baseMtxF);
        Matrix_RotateZ(chainRotZ, MTXMODE_APPLY);
        Matrix_Translate(chainsTranslateX, chainsTranslateY, 0.0f, MTXMODE_APPLY);

        if (entry->chainsScale != 1.0f) {
            Matrix_Scale(entry->chainsScale, entry->chainsScale, entry->chainsScale, MTXMODE_APPLY);
        }

        gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx),
                  G_MTX_MODELVIEW | G_MTX_LOAD);
        gSPDisplayList(POLY_OPA_DISP++, entry->chainDL);

        if (i % 2) {
            rotZStep = 2.0f * entry->chainAngle;
        } else {
            rotZStep = M_PI - (2.0f * entry->chainAngle);
        }

        chainRotZ += rotZStep;
        FrameInterpolation_RecordCloseChild();
    }

    Matrix_Put(&baseMtxF);
    Matrix_Scale(frame * 0.1f, frame * 0.1f, frame * 0.1f, MTXMODE_APPLY);

    gSPMatrix(POLY_OPA_DISP++, MATRIX_NEWMTX(play->state.gfxCtx),
              G_MTX_MODELVIEW | G_MTX_LOAD);
    gSPDisplayList(POLY_OPA_DISP++, entry->lockDL);

    CLOSE_DISPS(play->state.gfxCtx);
}

void func_8003424C(PlayState* play, Vec3f* arg1) {
    CollisionCheck_SpawnShieldParticlesMetal(play, arg1);
}

void Actor_SetColorFilter(Actor* actor, s16 colorFlag, s16 colorIntensityMax, s16 xluFlag, s16 duration) {
    if ((colorFlag == 0x8000) && !(colorIntensityMax & 0x8000)) {
        Audio_PlayActorSound2(actor, NA_SE_EN_LIGHT_ARROW_HIT);
    }

    actor->colorFilterParams = colorFlag | xluFlag | ((colorIntensityMax & 0xF8) << 5) | duration;
    actor->colorFilterTimer = duration;
}

Hilite* func_800342EC(Vec3f* object, PlayState* play) {
    Vec3f lightDir;

    lightDir.x = play->envCtx.dirLight1.params.dir.x;
    lightDir.y = play->envCtx.dirLight1.params.dir.y;
    lightDir.z = play->envCtx.dirLight1.params.dir.z;

    return func_8002EABC(object, &play->view.eye, &lightDir, play->state.gfxCtx);
}

Hilite* func_8003435C(Vec3f* object, PlayState* play) {
    Vec3f lightDir;

    lightDir.x = play->envCtx.dirLight1.params.dir.x;
    lightDir.y = play->envCtx.dirLight1.params.dir.y;
    lightDir.z = play->envCtx.dirLight1.params.dir.z;

    return func_8002EB44(object, &play->view.eye, &lightDir, play->state.gfxCtx);
}

/**
 * Updates NPC talking state. Checks for a talk request and updates
 * the talkState parameter when a dialog is ongoing. Otherwise checks if
 * the actor is onscreen, advertises the interaction in a range and sets
 * the current text id if necessary.
 *
 * The talk state values are defined in the NpcTalkState enum.
 *
 * @see NpcTalkState
 *
 * @param[in,out] talkState Talk state
 * @param interactRange The interact (talking) range for the actor
 * @param getTextId Callback for getting the next text id
 * @param updateTalkState Callback for getting the next talkState value
 * @return True if a new dialog was started (player talked to the actor). False otherwise.
 */
s32 Npc_UpdateTalking(PlayState* play, Actor* actor, s16* talkState, f32 interactRange, NpcGetTextIdFunc getTextId,
                      NpcUpdateTalkStateFunc updateTalkState) {
    s16 x;
    s16 y;

    if (Actor_ProcessTalkRequest(actor, play)) {
        *talkState = NPC_TALK_STATE_TALKING;
        return true;
    }

    if (*talkState != NPC_TALK_STATE_IDLE) {
        *talkState = updateTalkState(play, actor);
        return false;
    }

    Actor_GetScreenPos(play, actor, &x, &y);

    if ((x < 0) || (x > SCREEN_WIDTH) || (y < 0) || (y > SCREEN_HEIGHT)) {
        // Actor is offscreen
        return false;
    }

    if (!func_8002F2CC(actor, play, interactRange)) {
        return false;
    }

    actor->textId = getTextId(play, actor);

    return false;
}

typedef struct {
    /* 0x00 */ s16 maxHeadYaw;
    /* 0x02 */ s16 minHeadPitch;
    /* 0x04 */ s16 maxHeadPitch;
    /* 0x06 */ s16 maxTorsoYaw;
    /* 0x08 */ s16 minTorsoPitch;
    /* 0x0A */ s16 maxTorsoPitch;
    /* 0x0C */ u8 rotateYaw;
} NpcTrackingRotLimits; // size = 0x10

typedef struct {
    /* 0x00 */ NpcTrackingRotLimits rotLimits;
    // Fields specific to NPC_TRACKING_PLAYER_AUTO_TURN mode
    /* 0x10 */ f32 autoTurnDistanceRange;   // Max distance to player to enable tracking and auto-turn
    /* 0x14 */ s16 maxYawForPlayerTracking; // Player is tracked if within this yaw
} NpcTrackingParams;                        // size = 0x18

/**
 * Npc tracking angle limit presets to use with Npc_TrackPoint.
 *
 * @see Npc_TrackPoint
 */
static NpcTrackingParams sNpcTrackingPresets[] = {
    { { 0x2AA8, -0x0E38, 0x18E2, 0x1554, 0x0000, 0x0000, true }, 170.0f, 0x3FFC },
    { { 0x2AA8, -0x1554, 0x1554, 0x1554, -0x071C, 0x0E38, true }, 170.0f, 0x3FFC },
    { { 0x31C4, -0x1C70, 0x0E38, 0x0E38, -0x0E38, 0x071C, true }, 170.0f, 0x3FFC },
    { { 0x1554, -0x0E38, 0x0000, 0x071C, -0x071C, 0x0000, true }, 170.0f, 0x3FFC },
    { { 0x2AA8, -0x071C, 0x071C, 0x0E38, -0x2AA8, 0x2AA8, true }, 170.0f, 0x3FFC },
    { { 0x0000, -0x1C70, 0x2AA8, 0x3FFC, -0x0E38, 0x0E38, true }, 170.0f, 0x3FFC },
    { { 0x2AA8, -0x0E38, 0x0E38, 0x0E38, 0x0000, 0x0000, true }, 0.0f, 0x0000 },
    { { 0x2AA8, -0x0E38, 0x0000, 0x0E38, 0x0000, 0x1C70, true }, 0.0f, 0x0000 },
    { { 0x2AA8, -0x0E38, -0x0E38, 0x0000, 0x0000, 0x0000, true }, 0.0f, 0x0000 },
    { { 0x071C, -0x0E38, 0x0E38, 0x1C70, 0x0000, 0x0000, true }, 0.0f, 0x0000 },
    { { 0x0E38, -0x0E38, 0x0000, 0x1C70, 0x0000, 0x0E38, true }, 0.0f, 0x0000 },
    { { 0x2AA8, -0x1C70, 0x1C70, 0x0E38, -0x0E38, 0x0E38, true }, 0.0f, 0x0000 },
    { { 0x18E2, -0x0E38, 0x0E38, 0x0E38, 0x0000, 0x0000, true }, 0.0f, 0x0000 },
};

/**
 * Smoothly turns the actor's whole body and updates torso and head rotations in
 * NpcInteractInfo so that the actor tracks the point specified in NpcInteractInfo.trackPos.
 * Rotations are limited to specified angles.
 *
 * Head and torso rotation angles are determined by calculating the pitch and yaw
 * from the actor position to the given target position.
 *
 * The y position of the actor is offset by NpcInteractInfo.yOffset
 * before calculating the angles. It can be used to configure the height difference
 * between the actor and the target.
 *
 * @param maxHeadYaw maximum head yaw difference from neutral position
 * @param maxHeadPitch maximum head pitch angle
 * @param minHeadPitch minimum head pitch angle
 * @param maxTorsoYaw maximum torso yaw difference from neutral position
 * @param maxTorsoPitch maximum torso pitch angle
 * @param minTorsoPitch minimum torso pitch angle
 * @param rotateYaw if true, the actor's yaw (shape.rot.y) is updated to turn the actor's whole body
 */
void Npc_TrackPointWithLimits(Actor* actor, NpcInteractInfo* interactInfo, s16 maxHeadYaw, s16 maxHeadPitch,
                              s16 minHeadPitch, s16 maxTorsoYaw, s16 maxTorsoPitch, s16 minTorsoPitch, u8 rotateYaw) {
    s16 pitchTowardsTarget;
    s16 yawTowardsTarget;
    s16 torsoPitch;
    s16 bodyYawDiff;
    s16 temp;
    Vec3f offsetActorPos;

    offsetActorPos.x = actor->world.pos.x;
    offsetActorPos.y = actor->world.pos.y + interactInfo->yOffset;
    offsetActorPos.z = actor->world.pos.z;

    pitchTowardsTarget = Math_Vec3f_Pitch(&offsetActorPos, &interactInfo->trackPos);
    yawTowardsTarget = Math_Vec3f_Yaw(&offsetActorPos, &interactInfo->trackPos);
    bodyYawDiff = Math_Vec3f_Yaw(&actor->world.pos, &interactInfo->trackPos) - actor->shape.rot.y;

    temp = CLAMP(bodyYawDiff, -maxHeadYaw, maxHeadYaw);
    Math_SmoothStepToS(&interactInfo->headRot.y, temp, 6, 2000, 1);

    temp = (ABS(bodyYawDiff) >= 0x8000) ? 0 : ABS(bodyYawDiff);
    interactInfo->headRot.y = CLAMP(interactInfo->headRot.y, -temp, temp);

    bodyYawDiff -= interactInfo->headRot.y;

    temp = CLAMP(bodyYawDiff, -maxTorsoYaw, maxTorsoYaw);
    Math_SmoothStepToS(&interactInfo->torsoRot.y, temp, 6, 2000, 1);

    temp = (ABS(bodyYawDiff) >= 0x8000) ? 0 : ABS(bodyYawDiff);
    interactInfo->torsoRot.y = CLAMP(interactInfo->torsoRot.y, -temp, temp);

    if (rotateYaw) {
        Math_SmoothStepToS(&actor->shape.rot.y, yawTowardsTarget, 6, 2000, 1);
    }

    temp = CLAMP(pitchTowardsTarget, minHeadPitch, (s16)(u16)maxHeadPitch);
    Math_SmoothStepToS(&interactInfo->headRot.x, temp, 6, 2000, 1);

    torsoPitch = pitchTowardsTarget - interactInfo->headRot.x;

    temp = CLAMP(torsoPitch, minTorsoPitch, maxTorsoPitch);
    Math_SmoothStepToS(&interactInfo->torsoRot.x, temp, 6, 2000, 1);
}

s16 Npc_GetTrackingPresetMaxPlayerYaw(s16 presetIndex) {
    return sNpcTrackingPresets[presetIndex].maxYawForPlayerTracking;
}

/**
 * Handles NPC tracking modes and auto-turning towards the player when
 * NPC_TRACKING_PLAYER_AUTO_TURN tracking mode is used.
 *
 * Returns a tracking mode that will determine which actor limbs
 * will be rotated towards the target.
 *
 * When the player is behind the actor (i.e. not in the yaw range in front of the actor
 * defined by maxYawForPlayerTracking), the actor will start an auto-turn sequence:
 *   - look forward for 30-60 frames
 *   - turn head to look at the player for 10-20 frames
 *   - look forward for 30-60 frames
 *   - turn the entire body to face the player
 *
 * @param distanceRange Max distance to player that tracking and auto-turning will be active for
 * @param maxYawForPlayerTracking Maximum angle for tracking the player.
 * @param trackingMode The tracking mode selected by the actor. If this is not
 *        NPC_TRACKING_PLAYER_AUTO_TURN this function does nothing
 *
 * @return The tracking mode (NpcTrackingMode) to use for the current frame.
 */
s16 Npc_UpdateAutoTurn(Actor* actor, NpcInteractInfo* interactInfo, f32 distanceRange, s16 maxYawForPlayerTracking,
                       s16 trackingMode) {
    s32 pad;
    s16 yaw;
    s16 yawDiff;

    if (trackingMode != NPC_TRACKING_PLAYER_AUTO_TURN) {
        return trackingMode;
    }

    if (interactInfo->talkState != NPC_TALK_STATE_IDLE) {
        // When talking, always fully turn to face the player
        return NPC_TRACKING_FULL_BODY;
    }

    if (distanceRange < Math_Vec3f_DistXYZ(&actor->world.pos, &interactInfo->trackPos)) {
        // Player is too far away, do not track
        interactInfo->autoTurnTimer = 0;
        interactInfo->autoTurnState = 0;
        return NPC_TRACKING_NONE;
    }

    yaw = Math_Vec3f_Yaw(&actor->world.pos, &interactInfo->trackPos);
    yawDiff = ABS((s16)((f32)yaw - actor->shape.rot.y));
    if (maxYawForPlayerTracking >= yawDiff) {
        // Player is in front of the actor, track with the head and the torso
        interactInfo->autoTurnTimer = 0;
        interactInfo->autoTurnState = 0;
        return NPC_TRACKING_HEAD_AND_TORSO;
    }

    // Player is behind the actor, run the auto-turn sequence.
    if (DECR(interactInfo->autoTurnTimer) != 0) {
        // While the timer is still running, return the previous tracking mode
        return interactInfo->trackingMode;
    }

    switch (interactInfo->autoTurnState) {
        case 0:
        case 2:
            // Just stand still, not tracking the player
            interactInfo->autoTurnTimer = Rand_S16Offset(30, 30);
            interactInfo->autoTurnState++;
            return NPC_TRACKING_NONE;
        case 1:
            // Glance at the player by only turning the head
            interactInfo->autoTurnTimer = Rand_S16Offset(10, 10);
            interactInfo->autoTurnState++;
            return NPC_TRACKING_HEAD;
    }

    // Auto-turn sequence complete, turn towards the player
    return NPC_TRACKING_FULL_BODY;
}

/**
 * Rotates the actor's whole body, torso and head tracking the point specified in NpcInteractInfo.trackPos.
 * Uses angle limits from a preset selected from from sNpcTrackingPresets.
 *
 * The trackingMode parameter controls whether the head and torso are turned towards the target.
 * If not, they are smoothly turned towards zero. Setting the parameter to NPC_TRACKING_FULL_BODY
 * causes the actor's whole body to be rotated to face the target.
 *
 * If NPC_TRACKING_PLAYER_AUTO_TURN is used, the actor will track the player with its head and torso as long
 * as the player is in front of the actor (within a yaw angle specified in the option preset).
 * If the player is outside of this angle, the actor will turn to face the player after a while.
 *
 * @see Npc_UpdateAutoTurn
 * @see sNpcTrackingPresets
 * @see NpcTrackingMode
 *
 * @param presetIndex The index to a preset in sNpcTrackingPresets
 * @param trackingMode A value from NpcTrackingMode enum
 */
void Npc_TrackPoint(Actor* actor, NpcInteractInfo* interactInfo, s16 presetIndex, s16 trackingMode) {
    NpcTrackingRotLimits rotLimits;

    interactInfo->trackingMode =
        Npc_UpdateAutoTurn(actor, interactInfo, sNpcTrackingPresets[presetIndex].autoTurnDistanceRange,
                           sNpcTrackingPresets[presetIndex].maxYawForPlayerTracking, trackingMode);

    rotLimits = sNpcTrackingPresets[presetIndex].rotLimits;

    switch (interactInfo->trackingMode) {
        case NPC_TRACKING_NONE:
            rotLimits.maxHeadYaw = 0;
            rotLimits.maxHeadPitch = 0;
            rotLimits.minHeadPitch = 0;
        case NPC_TRACKING_HEAD:
            rotLimits.maxTorsoYaw = 0;
            rotLimits.maxTorsoPitch = 0;
            rotLimits.minTorsoPitch = 0;
        case NPC_TRACKING_HEAD_AND_TORSO:
            rotLimits.rotateYaw = false;
    }

    Npc_TrackPointWithLimits(actor, interactInfo, rotLimits.maxHeadYaw, rotLimits.maxHeadPitch, rotLimits.minHeadPitch,
                             rotLimits.maxTorsoYaw, rotLimits.maxTorsoPitch, rotLimits.minTorsoPitch,
                             rotLimits.rotateYaw);
}

Gfx* func_80034B28(GraphicsContext* gfxCtx) {
    Gfx* displayList;

    displayList = Graph_Alloc(gfxCtx, sizeof(Gfx));
    gSPEndDisplayList(displayList);

    return displayList;
}

Gfx* func_80034B54(GraphicsContext* gfxCtx) {
    Gfx* displayListHead;
    Gfx* displayList;

    displayList = displayListHead = Graph_Alloc(gfxCtx, 2 * sizeof(Gfx));

    gDPSetRenderMode(displayListHead++, G_RM_FOG_SHADE_A,
                     AA_EN | Z_CMP | Z_UPD | IM_RD | CLR_ON_CVG | CVG_DST_WRAP | ZMODE_XLU | FORCE_BL |
                         GBL_c2(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA));

    gSPEndDisplayList(displayListHead++);

    return displayList;
}

void func_80034BA0(PlayState* play, SkelAnime* skelAnime, OverrideLimbDraw overrideLimbDraw,
                   PostLimbDraw postLimbDraw, Actor* actor, s16 alpha) {
    OPEN_DISPS(play->state.gfxCtx);

    Gfx_SetupDL_25Opa(play->state.gfxCtx);

    gDPPipeSync(POLY_OPA_DISP++);
    gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 0, alpha);
    gDPPipeSync(POLY_OPA_DISP++);
    gSPSegment(POLY_OPA_DISP++, 0x0C, func_80034B28(play->state.gfxCtx));

    POLY_OPA_DISP = SkelAnime_DrawFlex(play, skelAnime->skeleton, skelAnime->jointTable, skelAnime->dListCount,
                                       overrideLimbDraw, postLimbDraw, actor, POLY_OPA_DISP);

    CLOSE_DISPS(play->state.gfxCtx);
}

void func_80034CC4(PlayState* play, SkelAnime* skelAnime, OverrideLimbDraw overrideLimbDraw,
                   PostLimbDraw postLimbDraw, Actor* actor, s16 alpha) {
    OPEN_DISPS(play->state.gfxCtx);

    Gfx_SetupDL_25Xlu(play->state.gfxCtx);

    gDPPipeSync(POLY_XLU_DISP++);
    gDPSetEnvColor(POLY_XLU_DISP++, 0, 0, 0, alpha);
    gSPSegment(POLY_XLU_DISP++, 0x0C, func_80034B54(play->state.gfxCtx));

    POLY_XLU_DISP = SkelAnime_DrawFlex(play, skelAnime->skeleton, skelAnime->jointTable, skelAnime->dListCount,
                                       overrideLimbDraw, postLimbDraw, actor, POLY_XLU_DISP);

    CLOSE_DISPS(play->state.gfxCtx);
}

s16 Actor_UpdateAlphaByDistance(Actor* actor, PlayState* play, s16 alpha, f32 radius) {
    Player* player = GET_PLAYER(play);
    f32 distance;

    if ((play->csCtx.state != CS_STATE_IDLE) || (gDbgCamEnabled)) {
        distance = Math_Vec3f_DistXYZ(&actor->world.pos, &play->view.eye) * 0.25f;
    } else {
        distance = Math_Vec3f_DistXYZ(&actor->world.pos, &player->actor.world.pos);
    }

    if (radius < distance) {
        actor->flags &= ~ACTOR_FLAG_ATTENTION_ENABLED;
        Math_SmoothStepToS(&alpha, 0, 6, 0x14, 1);
    } else {
        actor->flags |= ACTOR_FLAG_ATTENTION_ENABLED;
        Math_SmoothStepToS(&alpha, 0xFF, 6, 0x14, 1);
    }

    return alpha;
}

void Animation_ChangeByInfo(SkelAnime* skelAnime, AnimationInfo* animationInfo, s32 index) {
    f32 frameCount;

    animationInfo += index;

    if (animationInfo->frameCount > 0.0f) {
        frameCount = animationInfo->frameCount;
    } else {
        frameCount = Animation_GetLastFrame(animationInfo->animation);
    }

    Animation_Change(skelAnime, animationInfo->animation, animationInfo->playSpeed, animationInfo->startFrame,
                     frameCount, animationInfo->mode, animationInfo->morphFrames);
}

void func_80034F54(PlayState* play, s16* arg1, s16* arg2, s32 arg3) {
    u32 frames = play->gameplayFrames;
    s32 i;

    for (i = 0; i < arg3; i++) {
        arg1[i] = (0x814 + 50 * i) * frames;
        arg2[i] = (0x940 + 50 * i) * frames;
    }
}

void Actor_Noop(Actor* actor, PlayState* play) {
}

s32 func_80035124(Actor* actor, PlayState* play) {
    s32 ret = 0;

    switch (actor->params) {
        case 0:
            if (Actor_HasParent(actor, play)) {
                actor->params = 1;
            } else if (!(actor->bgCheckFlags & 1)) {
                Actor_MoveXZGravity(actor);
                Math_SmoothStepToF(&actor->speedXZ, 0.0f, 1.0f, 0.1f, 0.0f);
            } else if ((actor->bgCheckFlags & 2) && (actor->velocity.y < -4.0f)) {
                ret = 1;
            } else {
                actor->shape.rot.x = actor->shape.rot.z = 0;
                Actor_OfferCarry(actor, play);
            }
            break;
        case 1:
            if (Actor_HasNoParent(actor, play)) {
                actor->params = 0;
            }
            break;
    }

    Actor_UpdateBgCheckInfo(play, actor, actor->colChkInfo.cylHeight, actor->colChkInfo.cylRadius,
                            actor->colChkInfo.cylRadius, 0x1D);

    return ret;
}

u8 func_800353E8(PlayState* play) {
    Player* player = GET_PLAYER(play);

    return player->unk_845;
}

/**
 * Finds the first actor instance of a specified ID and category within a given range from
 * an actor if there is one. If the ID provided is -1, this will look for any actor of the
 * specified category rather than a specific ID.
 */
Actor* Actor_FindNearby(PlayState* play, Actor* refActor, s16 actorId, u8 actorCategory, f32 range) {
    Actor* actor = play->actorCtx.actorLists[actorCategory].head;

    while (actor != NULL) {
        if (actor == refActor || ((actorId != -1) && (actorId != actor->id))) {
            actor = actor->next;
        } else {
            if (Actor_WorldDistXYZToActor(refActor, actor) <= range) {
                return actor;
            } else {
                actor = actor->next;
            }
        }
    }

    return NULL;
}

s32 func_800354B4(PlayState* play, Actor* actor, f32 range, s16 arg3, s16 arg4, s16 arg5) {
    Player* player = GET_PLAYER(play);
    s16 var1;
    s16 var2;

    var1 = (s16)(actor->yawTowardsPlayer + 0x8000) - player->actor.shape.rot.y;
    var2 = actor->yawTowardsPlayer - arg5;

    if ((actor->xzDistToPlayer <= range) && (player->meleeWeaponState != 0) && (arg4 >= ABS(var1)) && (arg3 >= ABS(var2))) {
        return true;
    } else {
        return false;
    }
}

void func_8003555C(PlayState* play, Vec3f* pos, Vec3f* velocity, Vec3f* accel) {
    Color_RGBA8 color1;
    Color_RGBA8 color2;

    color1.r = 200;
    color1.g = 160;
    color1.b = 120;

    color2.r = 130;
    color2.g = 90;
    color2.b = 50;

    //! @bug color1 and color2 alpha components not set before being passed on
    EffectSsKiraKira_SpawnSmall(play, pos, velocity, accel, &color1, &color2);
}

Vec3f D_80116268 = { 0.0f, -1.5f, 0.0f };
Vec3f D_80116274 = { 0.0f, -0.2f, 0.0f };

Gfx D_80116280[] = {
    gsDPSetRenderMode(G_RM_FOG_SHADE_A, AA_EN | Z_CMP | Z_UPD | IM_RD | CLR_ON_CVG | CVG_DST_WRAP | ZMODE_XLU |
                                            FORCE_BL | GBL_c2(G_BL_CLR_IN, G_BL_A_IN, G_BL_CLR_MEM, G_BL_1MA)),
    gsDPSetAlphaCompare(G_AC_THRESHOLD),
    gsSPEndDisplayList(),
};

void func_800355B8(PlayState* play, Vec3f* pos) {
    func_8003555C(play, pos, &D_80116268, &D_80116274);
}

u8 func_800355E4(PlayState* play, Collider* collider) {
    Player* player = GET_PLAYER(play);

    if ((collider->acFlags & AC_TYPE_PLAYER) && (player->meleeWeaponState != 0) && (player->meleeWeaponAnimation == 0x16)) {
        return true;
    } else {
        return false;
    }
}

u8 Actor_ApplyDamage(Actor* actor) {
    if (actor->colChkInfo.damage >= actor->colChkInfo.health) {
        actor->colChkInfo.health = 0;
    } else {
        actor->colChkInfo.health -= actor->colChkInfo.damage;
    }

    return actor->colChkInfo.health;
}

void Actor_SetDropFlag(Actor* actor, ColliderInfo* colInfo, s32 freezeFlag) {
    if (colInfo->acHitInfo == NULL) {
        actor->dropFlag = 0x00;
    } else if (freezeFlag && (colInfo->acHitInfo->toucher.dmgFlags & 0x10060000)) {
        actor->freezeTimer = colInfo->acHitInfo->toucher.damage;
        actor->dropFlag = 0x00;
    } else if (colInfo->acHitInfo->toucher.dmgFlags & 0x0800) {
        actor->dropFlag = 0x01;
    } else if (colInfo->acHitInfo->toucher.dmgFlags & 0x1000) {
        actor->dropFlag = 0x02;
    } else if (colInfo->acHitInfo->toucher.dmgFlags & 0x4000) {
        actor->dropFlag = 0x04;
    } else if (colInfo->acHitInfo->toucher.dmgFlags & 0x8000) {
        actor->dropFlag = 0x08;
    } else if (colInfo->acHitInfo->toucher.dmgFlags & 0x10000) {
        actor->dropFlag = 0x10;
    } else if (colInfo->acHitInfo->toucher.dmgFlags & 0x2000) {
        actor->dropFlag = 0x20;
    } else if (colInfo->acHitInfo->toucher.dmgFlags & 0x80000) {
        if (freezeFlag) {
            actor->freezeTimer = colInfo->acHitInfo->toucher.damage;
        }
        actor->dropFlag = 0x40;
    } else {
        actor->dropFlag = 0x00;
    }
}

void Actor_SetDropFlagJntSph(Actor* actor, ColliderJntSph* jntSph, s32 freezeFlag) {
    ColliderInfo* curColInfo;
    s32 flag;
    s32 i;

    actor->dropFlag = 0x00;

    for (i = jntSph->count - 1; i >= 0; i--) {
        curColInfo = &jntSph->elements[i].info;
        if (curColInfo->acHitInfo == NULL) {
            flag = 0x00;
        } else if (freezeFlag && (curColInfo->acHitInfo->toucher.dmgFlags & 0x10060000)) {
            actor->freezeTimer = curColInfo->acHitInfo->toucher.damage;
            flag = 0x00;
        } else if (curColInfo->acHitInfo->toucher.dmgFlags & 0x0800) {
            flag = 0x01;
        } else if (curColInfo->acHitInfo->toucher.dmgFlags & 0x1000) {
            flag = 0x02;
        } else if (curColInfo->acHitInfo->toucher.dmgFlags & 0x4000) {
            flag = 0x04;
        } else if (curColInfo->acHitInfo->toucher.dmgFlags & 0x8000) {
            flag = 0x08;
        } else if (curColInfo->acHitInfo->toucher.dmgFlags & 0x10000) {
            flag = 0x10;
        } else if (curColInfo->acHitInfo->toucher.dmgFlags & 0x2000) {
            flag = 0x20;
        } else if (curColInfo->acHitInfo->toucher.dmgFlags & 0x80000) {
            if (freezeFlag) {
                actor->freezeTimer = curColInfo->acHitInfo->toucher.damage;
            }
            flag = 0x40;
        } else {
            flag = 0x00;
        }
        actor->dropFlag |= flag;
    }
}

void func_80035844(Vec3f* arg0, Vec3f* arg1, Vec3s* arg2, s32 arg3) {
    f32 dx = arg1->x - arg0->x;
    f32 dz = arg1->z - arg0->z;
    f32 dy = arg3 ? (arg1->y - arg0->y) : (arg0->y - arg1->y);

    arg2->y = Math_Atan2S(dz, dx);
    arg2->x = Math_Atan2S(sqrtf(SQ(dx) + SQ(dz)), dy);
}

/**
 * Spawns En_Part (Dissipating Flames) actor as a child of the given actor.
 */
Actor* func_800358DC(Actor* actor, Vec3f* spawnPos, Vec3s* spawnRot, f32* arg3, s32 timer, s16* unused,
                     PlayState* play, s16 params, s32 arg8) {
    EnPart* spawnedEnPart;

    spawnedEnPart =
        (EnPart*)Actor_SpawnAsChild(&play->actorCtx, actor, play, ACTOR_EN_PART, spawnPos->x, spawnPos->y,
                                                spawnPos->z, spawnRot->x, spawnRot->y, actor->objBankIndex, params);
    if (spawnedEnPart != NULL) {
        spawnedEnPart->actor.scale = actor->scale;
        spawnedEnPart->actor.speedXZ = arg3[0];
        spawnedEnPart->displayList = arg8;
        spawnedEnPart->action = 2;
        spawnedEnPart->timer = timer;
        spawnedEnPart->rotZ = arg3[1];
        spawnedEnPart->rotZSpeed = arg3[2];
        return &spawnedEnPart->actor;
    }

    return NULL;
}

void func_800359B8(Actor* actor, s16 arg1, Vec3s* arg2) {
    f32 sp44;
    f32 sp40;
    f32 sp3C;
    f32 sp38;
    f32 sp34;
    f32 sp30;
    f32 sp2C;
    f32 sp28;
    f32 sp24;
    CollisionPoly* floorPoly;
    s32 pad;

    if (actor->floorPoly != NULL) {
        floorPoly = actor->floorPoly;
        sp44 = COLPOLY_GET_NORMAL(floorPoly->normal.x);
        sp40 = COLPOLY_GET_NORMAL(floorPoly->normal.y);
        sp3C = COLPOLY_GET_NORMAL(floorPoly->normal.z);

        sp38 = Math_SinS(arg1);
        sp34 = Math_CosS(arg1);
        sp28 = (-(sp44 * sp38) - (sp3C * sp34));
        arg2->x = -(s16)(Math_FAtan2F(sp28 * sp40, 1.0f) * (32768 / M_PI));

        sp2C = Math_SinS(arg1 - 16375);
        sp30 = Math_CosS(arg1 - 16375);
        sp24 = (-(sp44 * sp2C) - (sp3C * sp30));
        arg2->z = -(s16)(Math_FAtan2F(sp24 * sp40, 1.0f) * (32768 / M_PI));
    }
}

void func_80035B18(PlayState* play, Actor* actor, u16 textId) {
    Message_ContinueTextbox(play, textId);
    actor->textId = textId;
}

/**
 * Tests if "eventChkInf" flag is set.
 */
s32 Flags_GetEventChkInf(s32 flag) {
    return gSaveContext.eventChkInf[flag >> 4] & (1 << (flag & 0xF));
}

/**
 * Sets "eventChkInf" flag.
 */
void Flags_SetEventChkInf(s32 flag) {
    u8 previouslyOff = !Flags_GetEventChkInf(flag);
    gSaveContext.eventChkInf[flag >> 4] |= (1 << (flag & 0xF));
    if (previouslyOff) {
        LUSLOG_INFO("EventChkInf Flag Set - %#x", flag);
        GameInteractor_ExecuteOnFlagSet(FLAG_EVENT_CHECK_INF, flag);
    }
}

/**
 * Unsets "eventChkInf" flag.
 */
void Flags_UnsetEventChkInf(s32 flag) {
    u8 previouslyOn = Flags_GetEventChkInf(flag);
    gSaveContext.eventChkInf[flag >> 4] &= ~(1 << (flag & 0xF));
    if (previouslyOn) {
        LUSLOG_INFO("EventChkInf Flag Unset - %#x", flag);
        GameInteractor_ExecuteOnFlagUnset(FLAG_EVENT_CHECK_INF, flag);
    }
}

/**
 * Tests if "itemGetInf" flag is set.
 */
s32 Flags_GetItemGetInf(s32 flag) {
    return gSaveContext.itemGetInf[flag >> 4] & (1 << (flag & 0xF));
}

/**
 * Sets "itemGetInf" flag.
 */
void Flags_SetItemGetInf(s32 flag) {
    u8 previouslyOff = !Flags_GetItemGetInf(flag);
    gSaveContext.itemGetInf[flag >> 4] |= (1 << (flag & 0xF));
    if (previouslyOff) {
        LUSLOG_INFO("ItemGetInf Flag Set - %#x", flag);
        GameInteractor_ExecuteOnFlagSet(FLAG_ITEM_GET_INF, flag);
    }
}

/**
 * Unsets "itemGetInf" flag.
 */
void Flags_UnsetItemGetInf(s32 flag) {
    u8 previouslyOn = Flags_GetItemGetInf(flag);
    gSaveContext.itemGetInf[flag >> 4] &= ~(1 << (flag & 0xF));
    if (previouslyOn) {
        LUSLOG_INFO("ItemGetInf Flag Unset - %#x", flag);
        GameInteractor_ExecuteOnFlagUnset(FLAG_ITEM_GET_INF, flag);
    }
}

/**
 * Tests if "infTable" flag is set.
 */
s32 Flags_GetInfTable(s32 flag) {
    return gSaveContext.infTable[flag >> 4] & (1 << (flag & 0xF));
}

/**
 * Sets "infTable" flag.
 */
void Flags_SetInfTable(s32 flag) {
    u8 previouslyOff = !Flags_GetInfTable(flag);
    gSaveContext.infTable[flag >> 4] |= (1 << (flag & 0xF));
    if (previouslyOff) {
        LUSLOG_INFO("InfTable Flag Set - %#x", flag);
        GameInteractor_ExecuteOnFlagSet(FLAG_INF_TABLE, flag);
    }
}

/**
 * Unsets "infTable" flag.
 */
void Flags_UnsetInfTable(s32 flag) {
    u8 previouslyOn = Flags_GetInfTable(flag);
    gSaveContext.infTable[flag >> 4] &= ~(1 << (flag & 0xF));
    if (previouslyOn) {
        LUSLOG_INFO("InfTable Flag Unset - %#x", flag);
        GameInteractor_ExecuteOnFlagUnset(FLAG_INF_TABLE, flag);
    }
}

/**
 * Tests if "eventInf" flag is set.
 */
s32 Flags_GetEventInf(s32 flag) {
    return gSaveContext.eventInf[flag >> 4] & (1 << (flag & 0xF));
}

/**
 * Sets "eventInf" flag.
 */
void Flags_SetEventInf(s32 flag) {
    u8 previouslyOff = !Flags_GetEventInf(flag);
    gSaveContext.eventInf[flag >> 4] |= (1 << (flag & 0xF));
    if (previouslyOff) {
        LUSLOG_INFO("EventInf Flag Set - %#x", flag);
        GameInteractor_ExecuteOnFlagSet(FLAG_EVENT_INF, flag);
    }
}

/**
 * Unsets "eventInf" flag.
 */
void Flags_UnsetEventInf(s32 flag) {
    u8 previouslyOn = Flags_GetEventInf(flag);
    gSaveContext.eventInf[flag >> 4] &= ~(1 << (flag & 0xF));
    if (previouslyOn) {
        LUSLOG_INFO("EventInf Flag Unset - %#x", flag);
        GameInteractor_ExecuteOnFlagUnset(FLAG_EVENT_INF, flag);
    }
}

/**
 * Tests if "randomizerInf" flag is set.
 */
s32 Flags_GetRandomizerInf(RandomizerInf flag) {
    // Randomizer flags are currently accessible from any quest (boss rush as an example)
    /*
    if (!IS_RANDO) {
        LUSLOG_ERROR("Tried to get randomizerInf flag \"%d\" outside of rando", flag);
        assert(false);
        return 0;
    }
    */

    return gSaveContext.ship.randomizerInf[flag >> 4] & (1 << (flag & 0xF));
}

/**
 * Sets "randomizerInf" flag.
 */
void Flags_SetRandomizerInf(RandomizerInf flag) {
    // Randomizer flags are currently accessible from any quest (boss rush as an example)
    /*
    if (!IS_RANDO) {
        LUSLOG_ERROR("Tried to set randomizerInf flag \"%d\" outside of rando", flag);
        assert(false);
        return;
    }
    */

    s32 previouslyOff = !Flags_GetRandomizerInf(flag);
    gSaveContext.ship.randomizerInf[flag >> 4] |= (1 << (flag & 0xF));
    if (previouslyOff) {
        LUSLOG_INFO("RandomizerInf Flag Set - %#x", flag);
        GameInteractor_ExecuteOnFlagSet(FLAG_RANDOMIZER_INF, flag);
    }
}

/**
 * Unsets "randomizerInf" flag.
 */
void Flags_UnsetRandomizerInf(RandomizerInf flag) {
    // Randomizer flags are currently accessible from any quest (boss rush as an example)
    /*
    if (!IS_RANDO) {
        LUSLOG_ERROR("Tried to unset randomizerInf flag \"%d\" outside of rando", flag);
        assert(false);
        return;
    }
    */

    s32 previouslyOn = Flags_GetRandomizerInf(flag);
    gSaveContext.ship.randomizerInf[flag >> 4] &= ~(1 << (flag & 0xF));
    if (previouslyOn) {
        LUSLOG_INFO("RandomizerInf Flag Unset - %#x", flag);
        GameInteractor_ExecuteOnFlagUnset(FLAG_RANDOMIZER_INF, flag);
    }
}

u32 func_80035BFC(PlayState* play, s16 arg1) {
    u16 retTextId = 0;

    switch (arg1) {
        case 0:
            if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP)) {
                if (Flags_GetInfTable(INFTABLE_05)) {
                    retTextId = 0x1048;
                } else {
                    retTextId = 0x1047;
                }
            } else {
                if (Flags_GetEventChkInf(EVENTCHKINF_FIRST_SPOKE_TO_MIDO)) {
                    if (Flags_GetInfTable(INFTABLE_03)) {
                        retTextId = 0x1032;
                    } else {
                        retTextId = 0x1031;
                    }
                } else {
                    if (Flags_GetInfTable(INFTABLE_GREETED_BY_SARIA)) {
                        if (Flags_GetInfTable(INFTABLE_01)) {
                            retTextId = 0x1003;
                        } else {
                            retTextId = 0x1002;
                        }
                    } else {
                        retTextId = 0x1001;
                    }
                }
            }
            break;
        case 1:
            if (!LINK_IS_ADULT) {
                if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP)) {
                    if (Flags_GetInfTable(INFTABLE_10)) {
                        retTextId = 0x1046;
                    } else {
                        retTextId = 0x1045;
                    }
                } else {
                    if (Flags_GetEventChkInf(EVENTCHKINF_COMPLAINED_ABOUT_MIDO)) {
                        if (Flags_GetInfTable(INFTABLE_0E)) {
                            retTextId = 0x1034;
                        } else {
                            retTextId = 0x1033;
                        }
                    } else {
                        if (Flags_GetInfTable(INFTABLE_0C)) {
                            retTextId = 0x1030;
                        } else {
                            retTextId = 0x102F;
                        }
                    }
                }
            } else {
                if (Flags_GetEventChkInf(EVENTCHKINF_5C)) {
                    if (Flags_GetInfTable(INFTABLE_19)) {
                        retTextId = 0x1071;
                    } else {
                        retTextId = 0x1070;
                    }
                } else {
                    if (Flags_GetEventChkInf(EVENTCHKINF_0B)) {
                        if (Flags_GetInfTable(INFTABLE_17)) {
                            retTextId = 0x1068;
                        } else {
                            retTextId = 0x1067;
                        }
                    } else {
                        if (Flags_GetInfTable(INFTABLE_15)) {
                            retTextId = 0x1061;
                        } else {
                            retTextId = 0x1060;
                        }
                    }
                }
            }
            break;
        case 2:
            if (!LINK_IS_ADULT) {
                if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP)) {
                    retTextId = 0x1042;
                } else {
                    retTextId = 0x1004;
                }
            } else {
                if (Flags_GetEventChkInf(EVENTCHKINF_5C)) {
                    retTextId = 0x1072;
                } else if (Flags_GetInfTable(INFTABLE_41)) {
                    retTextId = 0x1055;
                } else {
                    retTextId = 0x1056;
                }
            }
            break;
        case 3:
            if (!LINK_IS_ADULT) {
                if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP)) {
                    retTextId = 0x1043;
                } else {
                    if (Flags_GetInfTable(INFTABLE_1E)) {
                        retTextId = 0x1006;
                    } else {
                        retTextId = 0x1005;
                    }
                }
            } else {
                if (Flags_GetEventChkInf(EVENTCHKINF_5C)) {
                    retTextId = 0x1073;
                } else {
                    retTextId = 0x105A;
                }
            }
            break;
        case 4:
            if (!LINK_IS_ADULT) {
                if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP)) {
                    retTextId = 0x1042;
                } else {
                    retTextId = 0x1007;
                }
            } else {
                if (Flags_GetEventChkInf(EVENTCHKINF_5C)) {
                    retTextId = 0x1072;
                } else if (Flags_GetInfTable(INFTABLE_47)) {
                    retTextId = 0x105E;
                } else {
                    retTextId = 0x105D;
                }
            }
            break;
        case 5:
            if (!LINK_IS_ADULT) {
                if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP)) {
                    retTextId = 0x1044;
                } else if (Flags_GetInfTable(INFTABLE_22)) {
                    retTextId = 0x1009;
                } else {
                    retTextId = 0x1008;
                }
            } else {
                if (Flags_GetEventChkInf(EVENTCHKINF_5C)) {
                    retTextId = 0x1075;
                } else {
                    retTextId = 0x105B;
                }
            }
            break;
        case 6:
            if (!LINK_IS_ADULT) {
                if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP)) {
                    retTextId = 0x1042;
                } else if (Flags_GetInfTable(INFTABLE_24)) {
                    retTextId = 0x100B;
                } else {
                    retTextId = 0x100A;
                }
            } else {
                if (Flags_GetEventChkInf(EVENTCHKINF_5C)) {
                    retTextId = 0x1056;
                } else {
                    retTextId = 0x105F;
                }
            }
            break;
        case 7:
            if (!LINK_IS_ADULT) {
                if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP)) {
                    retTextId = 0x1043;
                } else if (Flags_GetInfTable(INFTABLE_26)) {
                    retTextId = 0x100D;
                } else {
                    retTextId = 0x100C;
                }
            } else {
                if (Flags_GetEventChkInf(EVENTCHKINF_5C)) {
                    retTextId = 0x1057;
                } else {
                    retTextId = 0x1057;
                }
            }
            break;
        case 8:
            if (!LINK_IS_ADULT) {
                if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP)) {
                    retTextId = 0x1043;
                } else if (Flags_GetInfTable(INFTABLE_28)) {
                    retTextId = 0x1019;
                } else {
                    retTextId = 0x100E;
                }
            } else {
                if (Flags_GetEventChkInf(EVENTCHKINF_5C)) {
                    retTextId = 0x1077;
                } else if (Flags_GetInfTable(INFTABLE_51)) {
                    retTextId = 0x1058;
                } else {
                    retTextId = 0x1059;
                }
            }
            break;
        case 9:
            if (!LINK_IS_ADULT) {
                if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP)) {
                    retTextId = 0x1049;
                } else {
                    retTextId = 0x1035;
                }
            } else {
                if (Flags_GetEventChkInf(EVENTCHKINF_5C)) {
                    retTextId = 0x1079;
                } else {
                    retTextId = 0x104E;
                }
            }
            break;
        case 10:
            if (!LINK_IS_ADULT) {
                if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP)) {
                    retTextId = 0x104A;
                } else {
                    retTextId = 0x1038;
                }
            } else {
                if (Flags_GetEventChkInf(EVENTCHKINF_5C)) {
                    retTextId = 0x1079;
                } else if (Flags_GetInfTable(INFTABLE_59)) {
                    retTextId = 0x1050;
                } else {
                    retTextId = 0x104F;
                }
            }
            break;
        case 11:
            if (!LINK_IS_ADULT) {
                if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP)) {
                    retTextId = 0x104B;
                } else {
                    retTextId = 0x103C;
                }
            } else {
                if (Flags_GetEventChkInf(EVENTCHKINF_5C)) {
                    retTextId = 0x107B;
                } else {
                    retTextId = 0x1051;
                }
            }
            break;
        case 12:
            if (!LINK_IS_ADULT) {
                if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP)) {
                    retTextId = 0x104C;
                } else {
                    retTextId = 0x103D;
                }
            } else {
                if (Flags_GetEventChkInf(EVENTCHKINF_5C)) {
                    retTextId = 0x107C;
                } else {
                    retTextId = 0x1052;
                }
            }
            break;
        case 13:
            if (!LINK_IS_ADULT) {
                if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP)) {
                    retTextId = 0x104D;
                } else {
                    retTextId = 0x103E;
                }
            } else {
                if (Flags_GetEventChkInf(EVENTCHKINF_5C)) {
                    retTextId = 0x106E;
                } else if (Flags_GetInfTable(INFTABLE_61)) {
                    retTextId = 0x1053;
                } else {
                    retTextId = 0x1054;
                }
            }
            break;
        case 15:
            if (Flags_GetEventChkInf(EVENTCHKINF_5C)) {
                retTextId = 0x1078;
            } else if (Flags_GetInfTable(INFTABLE_66)) {
                retTextId = 0x1066;
            } else {
                retTextId = 0x1062;
            }
            break;
        case 16:
            if (play->sceneNum == SCENE_HYRULE_CASTLE) {
                retTextId = 0x7002;
            } else if (Flags_GetInfTable(INFTABLE_6A)) {
                retTextId = 0x7004;
            } else if ((gSaveContext.dayTime >= 0x4000) && (gSaveContext.dayTime < 0xC556)) {
                retTextId = 0x7002;
            } else {
                retTextId = 0x7003;
            }
            break;
        case 17:
            if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP)) {
                if (Flags_GetInfTable(INFTABLE_6C)) {
                    retTextId = 0x7008;
                } else {
                    retTextId = 0x7007;
                }
            } else {
                retTextId = 0;
            }
            break;
        case 19:
            retTextId = 0x702D;
            break;
        case 18:
            if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP)) {
                retTextId = 0x7006;
            } else {
                if (Flags_GetEventChkInf(EVENTCHKINF_OBTAINED_POCKET_EGG)) {
                    if (Flags_GetInfTable(INFTABLE_71)) {
                        retTextId = 0x7072;
                    } else {
                        retTextId = 0x7071;
                    }
                } else {
                    retTextId = 0x7029;
                }
            }
            break;
        case 20:
        case 21:
            if (Flags_GetEventChkInf(EVENTCHKINF_42)) {
                retTextId = 0x2012;
            } else if (Flags_GetEventChkInf(EVENTCHKINF_41)) {
                if (Flags_GetInfTable(INFTABLE_SHOWED_ZELDAS_LETTER_TO_GATE_GUARD)) {
                    retTextId = 0x2011;
                } else {
                    retTextId = 0x2010;
                }
            } else if (Flags_GetEventChkInf(EVENTCHKINF_OBTAINED_ZELDAS_LETTER)) {
                retTextId = 0x200F;
            } else {
                retTextId = 0x200E;
            }
            break;
        case 24:
            if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP)) {
                retTextId = 0x7044;
            } else {
                retTextId = 0x7015;
            }
            break;
        case 25:
            if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP)) {
                retTextId = 0x7045;
            } else {
                Flags_GetInfTable(INFTABLE_C2);
                retTextId = 0x7016;
            }
            break;
        case 26:
            if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP)) {
                retTextId = 0x7046;
            } else {
                Flags_GetInfTable(INFTABLE_C2);
                retTextId = 0x7018;
            }
            break;
        case 27:
            if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP)) {
                retTextId = 0x7047;
            } else if (Flags_GetEventChkInf(EVENTCHKINF_TALON_RETURNED_FROM_CASTLE)) {
                retTextId = 0x701A;
            } else if (Flags_GetEventChkInf(EVENTCHKINF_SPOKE_TO_INGO_AT_RANCH_BEFORE_TALON_RETURNS)) {
                if (Flags_GetInfTable(INFTABLE_C6)) {
                    retTextId = 0x701C;
                } else {
                    retTextId = 0x701B;
                }
            } else {
                retTextId = 0x701A;
            }
            break;
        case 28:
            if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP)) {
                retTextId = 0x7048;
            } else {
                Flags_GetInfTable(INFTABLE_CA);
                retTextId = 0x701D;
            }
            break;
        case 29:
            if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP)) {
                retTextId = 0x7049;
            } else {
                Flags_GetInfTable(INFTABLE_CC);
                retTextId = 0x701F;
            }
            break;
        case 30:
            if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP)) {
                retTextId = 0x704A;
            } else {
                Flags_GetInfTable(INFTABLE_CE);
                retTextId = 0x7021;
            }
            break;
        case 31:
            if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP)) {
                retTextId = 0x704B;
            } else {
                Flags_GetInfTable(INFTABLE_D0);
                retTextId = 0x7023;
            }
            break;
        case 32:
            if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP)) {
                retTextId = 0x704C;
            } else {
                Flags_GetInfTable(INFTABLE_D2);
                retTextId = 0x7025;
            }
            break;
        case 33:
            if (Flags_GetEventChkInf(EVENTCHKINF_USED_DEKU_TREE_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP) && Flags_GetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP)) {
                retTextId = 0x704D;
            } else {
                Flags_GetInfTable(INFTABLE_D4);
                retTextId = 0x7027;
            }
            break;
        case 34:
            Flags_GetInfTable(INFTABLE_D6);
            retTextId = 0x403C;
            break;
        case 35:
            if (Flags_GetInfTable(INFTABLE_D8)) {
                retTextId = 0x5029;
            } else {
                retTextId = 0x5028;
            }
            break;
        case 37:
            retTextId = 0x5002;
            break;
        case 38:
            if (!LINK_IS_ADULT) {
                if (Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP)) {
                    retTextId = 0x3027;
                } else if (Flags_GetEventChkInf(EVENTCHKINF_BOMBED_DODONGOS_CAVERN_ENTRANCE)) {
                    retTextId = 0x3021;
                } else if (Flags_GetInfTable(INFTABLE_E0)) {
                    retTextId = 0x302A;
                } else {
                    retTextId = 0x3008;
                }
            } else {
                if (Flags_GetEventChkInf(EVENTCHKINF_20)) {
                    retTextId = 0x4043;
                } else {
                    retTextId = 0x302A;
                }
            }
            break;
        case 39:
            if (!LINK_IS_ADULT) {
                if (Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP)) {
                    retTextId = 0x3027;
                } else if (Flags_GetEventChkInf(EVENTCHKINF_BOMBED_DODONGOS_CAVERN_ENTRANCE)) {
                    retTextId = 0x3026;
                } else {
                    retTextId = 0x3009;
                }
            } else {
                if (Flags_GetEventChkInf(EVENTCHKINF_2A)) {
                    retTextId = 0x4043;
                } else {
                    retTextId = 0x302A;
                }
            }
            break;
        case 40:
            if (!LINK_IS_ADULT) {
                if (Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP)) {
                    retTextId = 0x3027;
                } else if (Flags_GetEventChkInf(EVENTCHKINF_BOMBED_DODONGOS_CAVERN_ENTRANCE)) {
                    retTextId = 0x3026;
                } else if (Flags_GetInfTable(INFTABLE_EB)) {
                    retTextId = 0x302B;
                } else {
                    retTextId = 0x300A;
                }
            } else {
                if (Flags_GetEventChkInf(EVENTCHKINF_2B)) {
                    retTextId = 0x4043;
                } else {
                    retTextId = 0x302A;
                }
            }
            break;
        case 41:
            if (!LINK_IS_ADULT) {
                if (Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP)) {
                    retTextId = 0x3027;
                } else if (Flags_GetInfTable(INFTABLE_F0)) {
                    retTextId = 0x3015;
                } else {
                    retTextId = 0x3014;
                }
            } else {
                if (Flags_GetEventChkInf(EVENTCHKINF_2C)) {
                    retTextId = 0x4043;
                } else {
                    retTextId = 0x302A;
                }
            }
            break;
        case 42:
            if (!LINK_IS_ADULT) {
                if (Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP)) {
                    retTextId = 0x3027;
                } else if (Flags_GetInfTable(INFTABLE_F4)) {
                    retTextId = 0x3017;
                } else {
                    retTextId = 0x3016;
                }
            } else {
                if (Flags_GetEventChkInf(EVENTCHKINF_2C)) {
                    retTextId = 0x4043;
                } else {
                    retTextId = 0x302A;
                }
            }
            break;
        case 43:
            if (!LINK_IS_ADULT) {
                if (Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP)) {
                    retTextId = 0x3027;
                } else if (Flags_GetInfTable(INFTABLE_F8)) {
                    retTextId = 0x3019;
                } else {
                    retTextId = 0x3018;
                }
            } else {
                if (Flags_GetEventChkInf(EVENTCHKINF_2D)) {
                    retTextId = 0x4043;
                } else {
                    retTextId = 0x302A;
                }
            }
            break;
        case 48:
            if (Flags_GetEventChkInf(EVENTCHKINF_USED_DODONGOS_CAVERN_BLUE_WARP)) {
                retTextId = 0x3029;
            } else if (Flags_GetEventChkInf(EVENTCHKINF_20) && Flags_GetEventChkInf(EVENTCHKINF_21)) {
                retTextId = 0x301B;
            } else {
                retTextId = 0x301A;
            }
            break;
        case 49:
            if (Flags_GetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP)) {
                retTextId = 0x402D;
            } else if (Flags_GetEventChkInf(EVENTCHKINF_SPOKE_TO_A_ZORA)) {
                retTextId = 0x4007;
            } else {
                retTextId = 0x4006;
            }
            break;
        case 50:
            if (Flags_GetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP)) {
                retTextId = 0x402E;
            } else if (Flags_GetEventChkInf(EVENTCHKINF_SPOKE_TO_A_ZORA)) {
                if (Flags_GetInfTable(INFTABLE_124)) {
                    retTextId = 0x4009;
                } else {
                    retTextId = 0x4008;
                }
            } else {
                retTextId = 0x4006;
            }
            break;
        case 51:
            if (Flags_GetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP)) {
                retTextId = 0x402D;
            } else if (Flags_GetEventChkInf(EVENTCHKINF_OBTAINED_RUTOS_LETTER)) {
                if (Flags_GetInfTable(INFTABLE_12A)) {
                    retTextId = 0x400B;
                } else {
                    retTextId = 0x402F;
                }
            } else if (Flags_GetEventChkInf(EVENTCHKINF_SPOKE_TO_A_ZORA)) {
                retTextId = 0x400A;
            } else {
                retTextId = 0x4006;
            }
            break;
        case 52:
            if (Flags_GetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP)) {
                retTextId = 0x402E;
            } else if (Flags_GetEventChkInf(EVENTCHKINF_SPOKE_TO_A_ZORA)) {
                retTextId = 0x400C;
            } else {
                retTextId = 0x4006;
            }
            break;
        case 53:
            if (Flags_GetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP)) {
                retTextId = 0x402D;
            } else if (Flags_GetEventChkInf(EVENTCHKINF_KING_ZORA_MOVED)) {
                retTextId = 0x4010;
            } else if (Flags_GetEventChkInf(EVENTCHKINF_SPOKE_TO_A_ZORA)) {
                retTextId = 0x400F;
            } else {
                retTextId = 0x4006;
            }
            break;
        case 54:
            if (Flags_GetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP)) {
                retTextId = 0x402E;
            } else if (Flags_GetEventChkInf(EVENTCHKINF_SPOKE_TO_A_ZORA)) {
                retTextId = 0x4011;
            } else {
                retTextId = 0x4006;
            }
            break;
        case 55:
            if (!LINK_IS_ADULT) {
                if (Flags_GetEventChkInf(EVENTCHKINF_USED_JABU_JABUS_BELLY_BLUE_WARP)) {
                    retTextId = 0x402B;
                } else if (Flags_GetEventChkInf(EVENTCHKINF_OBTAINED_RUTOS_LETTER)) {
                    if (Flags_GetInfTable(INFTABLE_138)) {
                        retTextId = 0x401C;
                    } else {
                        retTextId = 0x401B;
                    }
                } else {
                    retTextId = 0x401A;
                }
            } else {
                retTextId = 0;
            }
            break;
        case 58:
            retTextId = 0x500F;
            break;
        case 59:
            retTextId = 0x5010;
            break;
        case 60:
            retTextId = 0x5012;
            break;
        case 61:
            if (Flags_GetInfTable(INFTABLE_166)) {
                retTextId = 0x5001;
            } else {
                retTextId = 0x5000;
            }
            break;
        case 62:
            retTextId = 0x5012;
            break;
        case 63:
            if (Flags_GetInfTable(INFTABLE_16A)) {
                retTextId = 0x5001;
            } else {
                retTextId = 0x5000;
            }
            break;
        case 71:
            if (Flags_GetEventChkInf(EVENTCHKINF_INVITED_TO_SING_WITH_CHILD_MALON)) {
                retTextId = 0x2049;
            } else if (Flags_GetEventChkInf(EVENTCHKINF_SPOKE_TO_CHILD_MALON_AT_RANCH)) {
                retTextId = 0x2048;
            } else if (Flags_GetEventChkInf(EVENTCHKINF_TALON_RETURNED_FROM_CASTLE)) {
                retTextId = 0x2047;
            } else if (Flags_GetEventChkInf(EVENTCHKINF_OBTAINED_POCKET_EGG) && !Flags_GetEventChkInf(EVENTCHKINF_TALON_RETURNED_FROM_CASTLE)) {
                retTextId = 0x2044;
            } else if (Flags_GetEventChkInf(EVENTCHKINF_SPOKE_TO_CHILD_MALON_AT_CASTLE_OR_MARKET)) {
                if (Flags_GetEventChkInf(EVENTCHKINF_SPOKE_TO_INGO_AT_RANCH_BEFORE_TALON_RETURNS)) {
                    retTextId = 0x2043;
                } else {
                    retTextId = 0x2042;
                }
            } else {
                retTextId = 0x2041;
            }
            break;
        case 72:
            if (!LINK_IS_ADULT) {
                if (Flags_GetEventChkInf(EVENTCHKINF_TALON_RETURNED_FROM_CASTLE)) {
                    retTextId = 0x2040;
                } else if (Flags_GetInfTable(INFTABLE_94)) {
                    retTextId = 0x2040;
                } else {
                    retTextId = 0x203F;
                }
            } else {
                if (!Flags_GetEventChkInf(EVENTCHKINF_EPONA_OBTAINED)) {
                    if (!IS_DAY) {
                        retTextId = 0x204E;
                    } else if (Flags_GetInfTable(INFTABLE_SPOKE_TO_INGO_ONCE_AS_ADULT)) {
                        retTextId = 0x2031;
                    } else {
                        retTextId = 0x2030;
                    }
                } else {
                    retTextId = 0;
                }
            }
            break;
    }

    if (retTextId == 0) {
        retTextId = 1;
    }

    return retTextId;
}

void func_80036E50(u16 textId, s16 arg1) {
    switch (arg1) {
        case 0:
            switch (textId) {
                case 0x1001:
                    Flags_SetInfTable(INFTABLE_GREETED_BY_SARIA);
                    return;
                case 0x1002:
                    Flags_SetInfTable(INFTABLE_01);
                    return;
                case 0x1031:
                    Flags_SetEventChkInf(EVENTCHKINF_COMPLAINED_ABOUT_MIDO);
                    Flags_SetInfTable(INFTABLE_03);
                    return;
                case 0x1047:
                    Flags_SetInfTable(INFTABLE_05);
                    return;
            }
            return;
        case 1:
            switch (textId) {
                case 0x102F:
                    Flags_SetEventChkInf(EVENTCHKINF_FIRST_SPOKE_TO_MIDO);
                    Flags_SetInfTable(INFTABLE_0C);
                    return;
                case 0x1033:
                    Audio_PlaySoundGeneral(NA_SE_SY_CORRECT_CHIME, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, &gSfxDefaultFreqAndVolScale,
                                           &gSfxDefaultReverb);
                    Flags_SetEventChkInf(EVENTCHKINF_SHOWED_MIDO_SWORD_SHIELD);
                    Flags_SetInfTable(INFTABLE_0E);
                    return;
                case 0x1045:
                    Flags_SetInfTable(INFTABLE_10);
                    return;
                case 0x1060:
                    Flags_SetInfTable(INFTABLE_15);
                    return;
                case 0x1067:
                    Flags_SetEventChkInf(EVENTCHKINF_PLAYED_SARIAS_SONG_FOR_MIDO_AS_ADULT);
                    Flags_SetInfTable(INFTABLE_17);
                    return;
                case 0x1070:
                    Flags_SetInfTable(INFTABLE_19);
                    return;
            }
            return;
        case 2:
            if (textId == 0x1056) {
                Flags_SetInfTable(INFTABLE_41);
            }
            return;
        case 3:
            if (textId == 0x1005) {
                Flags_SetInfTable(INFTABLE_1E);
            }
            return;
        case 4:
            if (textId == 0x105D) {
                Flags_SetInfTable(INFTABLE_47);
            }
            return;
        case 5:
            if (textId == 0x1008) {
                Flags_SetInfTable(INFTABLE_22);
            }
            return;
        case 6:
            if (textId == 0x100A) {
                Flags_SetInfTable(INFTABLE_24);
            }
            return;
        case 7:
            if (textId == 0x100C) {
                Flags_SetInfTable(INFTABLE_26);
            }
            return;
        case 8:
            if (textId == 0x100E) {
                Flags_SetInfTable(INFTABLE_28);
            }
            if (textId == 0x1059) {
                Flags_SetInfTable(INFTABLE_51);
            }
            return;
        case 10:
            if (textId == 0x104F) {
                Flags_SetInfTable(INFTABLE_59);
            }
            return;
        case 13:
            if (textId == 0x1054) {
                Flags_SetInfTable(INFTABLE_61);
            }
            return;
        case 15:
            if (textId == 0x1062) {
                Flags_SetInfTable(INFTABLE_66);
            }
            return;
        case 16:
            if (textId == 0x7002) {
                Flags_SetInfTable(INFTABLE_6A);
            }
            if (textId == 0x7003) {
                Flags_SetInfTable(INFTABLE_6A);
            }
            return;
        case 17:
            if (textId == 0x7007) {
                Flags_SetInfTable(INFTABLE_6C);
            }
            return;
        case 18:
            if (textId == 0x7071) {
                Flags_SetInfTable(INFTABLE_71);
            }
            return;
        case 20:
        case 21:
            if (textId == 0x2010) {
                Flags_SetInfTable(INFTABLE_SHOWED_ZELDAS_LETTER_TO_GATE_GUARD);
            }
            return;
        case 25:
            if (textId == 0x7016) {
                Flags_SetInfTable(INFTABLE_C2);
            }
            return;
        case 26:
            if (textId == 0x7018) {
                Flags_SetInfTable(INFTABLE_C4);
            }
            return;
        case 28:
            if (textId == 0x701D) {
                Flags_SetInfTable(INFTABLE_CA);
            }
            return;
        case 29:
            if (textId == 0x701F) {
                Flags_SetInfTable(INFTABLE_CC);
            }
            return;
        case 30:
            if (textId == 0x7021) {
                Flags_SetInfTable(INFTABLE_CE);
            }
            return;
        case 31:
            if (textId == 0x7023) {
                Flags_SetInfTable(INFTABLE_D0);
            }
            return;
        case 32:
            if (textId == 0x7025) {
                Flags_SetInfTable(INFTABLE_D2);
            }
            return;
        case 33:
            if (textId == 0x7027) {
                Flags_SetInfTable(INFTABLE_D4);
            }
            return;
        case 34:
            if (textId == 0x403C) {
                Flags_SetInfTable(INFTABLE_D6);
            }
            return;
        case 35:
            if (textId == 0x5028) {
                Flags_SetInfTable(INFTABLE_D8);
            }
            return;
        case 38:
            if (textId == 0x3008) {
                Flags_SetInfTable(INFTABLE_E0);
            }
            return;
        case 40:
            if (textId == 0x300B) {
                Flags_SetInfTable(INFTABLE_EB);
            }
            return;
        case 41:
            if (textId == 0x3014) {
                Flags_SetInfTable(INFTABLE_F0);
            }
            return;
        case 42:
            if (textId == 0x3016) {
                Flags_SetInfTable(INFTABLE_F4);
            }
            return;
        case 43:
            if (textId == 0x3018) {
                Flags_SetEventChkInf(EVENTCHKINF_20);
                Flags_SetInfTable(INFTABLE_F8);
            }
            return;
        case 48:
            if (textId == 0x3020) {
                Flags_SetEventChkInf(EVENTCHKINF_22);
                Flags_SetInfTable(INFTABLE_113);
            }
            return;
        case 49:
        case 52:
        case 53:
        case 54:
            if (textId == 0x4006) {
                Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_A_ZORA);
            }
            return;
        case 50:
            if (textId == 0x4006) {
                Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_A_ZORA);
            }
            if (textId == 0x4008) {
                Flags_SetInfTable(INFTABLE_124);
            }
            return;
        case 51:
            if (textId == 0x4006) {
                Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_A_ZORA);
            }
            if (textId == 0x400A) {
                Flags_SetEventChkInf(EVENTCHKINF_32);
            }
            if (textId == 0x402F) {
                Flags_SetInfTable(INFTABLE_12A);
            }
            return;
        case 55:
            if (textId == 0x401B) {
                Flags_SetEventChkInf(EVENTCHKINF_KING_ZORA_MOVED);
                Flags_SetInfTable(INFTABLE_138);
            }
            return;
        case 61:
            if (textId == 0x5000) {
                Flags_SetInfTable(INFTABLE_166);
            }
            return;
        case 63:
            if (textId == 0x5013) {
                Flags_SetInfTable(INFTABLE_16A);
            }
            return;
        case 71:
            if (textId == 0x2041) {
                Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_CHILD_MALON_AT_CASTLE_OR_MARKET);
            }
            if (textId == 0x2044) {
                Flags_SetEventChkInf(EVENTCHKINF_OBTAINED_POCKET_EGG);
            }
            if (textId == 0x2047) {
                Flags_SetEventChkInf(EVENTCHKINF_SPOKE_TO_CHILD_MALON_AT_RANCH);
            }
            if (textId == 0x2048) {
                Flags_SetEventChkInf(EVENTCHKINF_INVITED_TO_SING_WITH_CHILD_MALON);
            }
            return;
        case 72:
            return;
    }
}

s32 func_800374E0(PlayState* play, Actor* actor, u16 textId) {
    MessageContext* msgCtx = &play->msgCtx;
    s32 ret = 1;

    switch (textId) {
        case 0x1035:
            if (msgCtx->choiceIndex == 0) {
                if (Flags_GetInfTable(INFTABLE_2A)) {
                    func_80035B18(play, actor, 0x1036);
                } else {
                    func_80035B18(play, actor, 0x1041);
                }
            }
            if (msgCtx->choiceIndex == 1) {
                if (Flags_GetInfTable(INFTABLE_2B)) {
                    func_80035B18(play, actor, 0x1037);
                } else {
                    func_80035B18(play, actor, 0x1041);
                }
            }
            ret = 0;
            break;
        case 0x1038:
            if (msgCtx->choiceIndex == 0) {
                if (Flags_GetInfTable(INFTABLE_2E)) {
                    func_80035B18(play, actor, 0x1039);
                } else {
                    func_80035B18(play, actor, 0x1041);
                }
            }
            if (msgCtx->choiceIndex == 1) {
                if (Flags_GetInfTable(INFTABLE_2F)) {
                    func_80035B18(play, actor, 0x103A);
                } else {
                    func_80035B18(play, actor, 0x1041);
                }
            }
            if (msgCtx->choiceIndex == 2) {
                if (Flags_GetInfTable(INFTABLE_30)) {
                    func_80035B18(play, actor, 0x103B);
                } else {
                    func_80035B18(play, actor, 0x1041);
                }
            }
            ret = 0;
            break;
        case 0x103E:
            if (msgCtx->choiceIndex == 0) {
                func_80035B18(play, actor, 0x103F);
            }
            if (msgCtx->choiceIndex == 1) {
                func_80035B18(play, actor, 0x1040);
            }
            ret = 0;
            break;
        case 0x1041:
            if (msgCtx->choiceTextId == 0x1035) {
                if (msgCtx->choiceIndex == 0) {
                    func_80035B18(play, actor, 0x1036);
                    Flags_SetInfTable(INFTABLE_2A);
                }
                if (msgCtx->choiceIndex == 1) {
                    func_80035B18(play, actor, 0x1037);
                    Flags_SetInfTable(INFTABLE_2B);
                }
            }
            if (msgCtx->choiceTextId == 0x1038) {
                if (msgCtx->choiceIndex == 0) {
                    func_80035B18(play, actor, 0x1039);
                    Flags_SetInfTable(INFTABLE_2E);
                }
                if (msgCtx->choiceIndex == 1) {
                    func_80035B18(play, actor, 0x103A);
                    Flags_SetInfTable(INFTABLE_2F);
                }
                if (msgCtx->choiceIndex == 2) {
                    func_80035B18(play, actor, 0x103B);
                    Flags_SetInfTable(INFTABLE_30);
                }
            }
            ret = 0;
            break;
        case 0x1062:
            if (msgCtx->choiceIndex == 0) {
                func_80035B18(play, actor, 0x1063);
            }
            if (msgCtx->choiceIndex == 1) {
                func_80035B18(play, actor, 0x1064);
            }
            ret = 0;
            break;
        case 0x2030:
        case 0x2031:
            if (msgCtx->choiceIndex == 0) {
                if (gSaveContext.rupees >= 10) {
                    func_80035B18(play, actor, 0x2034);
                    Rupees_ChangeBy(-10);
                } else {
                    func_80035B18(play, actor, 0x2032);
                }
            }
            if (msgCtx->choiceIndex == 1) {
                func_80035B18(play, actor, 0x2032);
            }
            Flags_SetInfTable(INFTABLE_SPOKE_TO_INGO_ONCE_AS_ADULT);
            ret = 0;
            break;
        case 0x2036:
        case 0x2037:
            if (msgCtx->choiceIndex == 0) {
                func_80035B18(play, actor, 0x201F);
            }
            if (msgCtx->choiceIndex == 1) {
                func_80035B18(play, actor, 0x205A);
            }
            ret = 0;
            break;
        case 0x2038:
            if (msgCtx->choiceIndex == 0) {
                break;
            }
            if (msgCtx->choiceIndex == 1) {
                func_80035B18(play, actor, 0x205A);
            }
            ret = 0;
            break;
        case 0x2034:
            if (msgCtx->choiceIndex != 0) {
                break;
            }
            func_80035B18(play, actor, 0x2035);
            ret = 0;
            break;
        case 0x2043:
            if (Flags_GetEventChkInf(EVENTCHKINF_OBTAINED_POCKET_EGG)) {
                break;
            }
            func_80035B18(play, actor, 0x2044);
            ret = 0;
            break;
        case 0x205A:
            break;
        case 0x300A:
            if (msgCtx->choiceIndex == 0) {
                if (Flags_GetEventChkInf(EVENTCHKINF_22)) {
                    func_80035B18(play, actor, 0x300B);
                } else {
                    func_80035B18(play, actor, 0x300C);
                }
            }
            if (msgCtx->choiceIndex == 1) {
                func_80035B18(play, actor, 0x300D);
            }
            ret = 0;
            break;
        case 0x301B:
            if (msgCtx->choiceIndex == 0) {
                func_80035B18(play, actor, 0x301D);
            }
            if (msgCtx->choiceIndex == 1) {
                if (Flags_GetInfTable(INFTABLE_113)) {
                    func_80035B18(play, actor, 0x301F);
                } else {
                    func_80035B18(play, actor, 0x301E);
                }
            }
            ret = 0;
            break;
        case 0x301E:
            func_80035B18(play, actor, 0x3020);
            ret = 0;
            break;
        case 0x400C:
            if (msgCtx->choiceIndex == 0) {
                func_80035B18(play, actor, 0x400D);
            }
            if (msgCtx->choiceIndex == 1) {
                func_80035B18(play, actor, 0x400E);
            }
            ret = 0;
            break;
        case 0x7007:
            func_80035B18(play, actor, 0x703E);
            ret = 0;
            break;
        case 0x703E:
            func_80035B18(play, actor, 0x703F);
            ret = 0;
            break;
        case 0x703F:
            func_80035B18(play, actor, 0x7042);
            ret = 0;
            break;
    }

    return ret;
}

u16 func_80037C30(PlayState* play, s16 arg1) {
    return func_80035BFC(play, arg1);
}

s32 func_80037C5C(PlayState* play, s16 arg1, u16 textId) {
    func_80036E50(textId, arg1);
    return false;
}

s32 func_80037C94(PlayState* play, Actor* actor, s32 arg2) {
    return func_800374E0(play, actor, actor->textId);
}

s32 func_80037CB8(PlayState* play, Actor* actor, s16 arg2) {
    MessageContext* msgCtx = &play->msgCtx;
    s32 ret = false;

    switch (Message_GetState(msgCtx)) {
        case TEXT_STATE_CLOSING:
            func_80037C5C(play, arg2, actor->textId);
            ret = true;
            break;
        case TEXT_STATE_CHOICE:
        case TEXT_STATE_EVENT:
            if (Message_ShouldAdvance(play) && func_80037C94(play, actor, arg2)) {
                Audio_PlaySoundGeneral(NA_SE_SY_CANCEL, &gSfxDefaultPos, 4, &gSfxDefaultFreqAndVolScale, &gSfxDefaultFreqAndVolScale, &gSfxDefaultReverb);
                msgCtx->msgMode = MSGMODE_TEXT_CLOSING;
                ret = true;
            }
            break;
    }

    return ret;
}

s32 func_80037D98(PlayState* play, Actor* actor, s16 arg2, s32* arg3) {
    s16 var;
    s16 sp2C;
    s16 sp2A;
    s16 abs_var;

    if (Actor_ProcessTalkRequest(actor, play)) {
        *arg3 = 1;
        return true;
    }

    if (*arg3 == 1) {
        if (func_80037CB8(play, actor, arg2)) {
            *arg3 = 0;
        }
        return false;
    }

    Actor_GetScreenPos(play, actor, &sp2C, &sp2A);

    if ((sp2C < 0) || (sp2C > SCREEN_WIDTH) || (sp2A < 0) || (sp2A > SCREEN_HEIGHT)) {
        return false;
    }

    var = actor->yawTowardsPlayer - actor->shape.rot.y;
    abs_var = ABS(var);

    if (abs_var >= 0x4300) {
        return false;
    }

    if ((actor->xyzDistToPlayerSq > SQ(160.0f)) && !actor->isTargeted) {
        return false;
    }

    if (actor->xyzDistToPlayerSq <= SQ(80.0f)) {
        if (func_8002F2CC(actor, play, 80.0f)) {
            actor->textId = func_80037C30(play, arg2);
        }
    } else {
        if (func_8002F2F4(actor, play)) {
            actor->textId = func_80037C30(play, arg2);
        }
    }

    return false;
}

s32 func_80037F30(Vec3s* arg0, Vec3s* arg1) {
    Math_SmoothStepToS(&arg0->y, 0, 6, 6200, 100);
    Math_SmoothStepToS(&arg0->x, 0, 6, 6200, 100);
    Math_SmoothStepToS(&arg1->y, 0, 6, 6200, 100);
    Math_SmoothStepToS(&arg1->x, 0, 6, 6200, 100);
    return true;
}

s32 func_80037FC8(Actor* actor, Vec3f* arg1, Vec3s* arg2, Vec3s* arg3) {
    s16 sp36;
    s16 sp34;
    s16 var;

    sp36 = Math_Vec3f_Pitch(&actor->focus.pos, arg1);
    sp34 = Math_Vec3f_Yaw(&actor->focus.pos, arg1) - actor->world.rot.y;

    Math_SmoothStepToS(&arg2->x, sp36, 6, 2000, 1);
    arg2->x = (arg2->x < -6000) ? -6000 : ((arg2->x > 6000) ? 6000 : arg2->x);

    var = Math_SmoothStepToS(&arg2->y, sp34, 6, 2000, 1);
    arg2->y = (arg2->y < -8000) ? -8000 : ((arg2->y > 8000) ? 8000 : arg2->y);

    if (var && (ABS(arg2->y) < 8000)) {
        return false;
    }

    Math_SmoothStepToS(&arg3->y, sp34 - arg2->y, 4, 2000, 1);
    arg3->y = (arg3->y < -12000) ? -12000 : ((arg3->y > 12000) ? 12000 : arg3->y);

    return true;
}

s32 func_80038154(PlayState* play, Actor* actor, Vec3s* arg2, Vec3s* arg3, f32 arg4) {
    Player* player = GET_PLAYER(play);
    s32 pad;
    Vec3f sp2C;
    s16 var;
    s16 abs_var;

    actor->focus.pos = actor->world.pos;
    actor->focus.pos.y += arg4;

    if (!(((play->csCtx.state != CS_STATE_IDLE) || (gDbgCamEnabled)) && (gSaveContext.entranceIndex == ENTR_KOKIRI_FOREST_0))) {
        var = actor->yawTowardsPlayer - actor->shape.rot.y;
        abs_var = ABS(var);
        if (abs_var >= 0x4300) {
            func_80037F30(arg2, arg3);
            return false;
        }
    }

    if (((play->csCtx.state != CS_STATE_IDLE) || (gDbgCamEnabled)) && (gSaveContext.entranceIndex == ENTR_KOKIRI_FOREST_0)) {
        sp2C = play->view.eye;
    } else {
        sp2C = player->actor.focus.pos;
    }

    func_80037FC8(actor, &sp2C, arg2, arg3);

    return true;
}

s32 func_80038290(PlayState* play, Actor* actor, Vec3s* arg2, Vec3s* arg3, Vec3f arg4) {
    Player* player = GET_PLAYER(play);
    s32 pad;
    Vec3f sp24;
    s16 var;
    s16 abs_var;

    actor->focus.pos = arg4;

    if (!(((play->csCtx.state != CS_STATE_IDLE) || (gDbgCamEnabled)) && (gSaveContext.entranceIndex == ENTR_KOKIRI_FOREST_0))) {
        var = actor->yawTowardsPlayer - actor->shape.rot.y;
        abs_var = ABS(var);
        if (abs_var >= 0x4300) {
            func_80037F30(arg2, arg3);
            return false;
        }
    }

    if (((play->csCtx.state != CS_STATE_IDLE) || (gDbgCamEnabled)) && (gSaveContext.entranceIndex == ENTR_KOKIRI_FOREST_0)) {
        sp24 = play->view.eye;
    } else {
        sp24 = player->actor.focus.pos;
    }

    func_80037FC8(actor, &sp24, arg2, arg3);

    return true;
}
