#include "common.h"
#include "ld_addrs.h"
#include "entity.h"
#include "model.h"
#include "sprite/player.h"

#ifdef SHIFT
extern Addr WorldEntityHeapBottom;
extern Addr WorldEntityHeapBase;
#define WORLD_ENTITY_HEAP_BOTTOM (s32) WorldEntityHeapBottom
#define WORLD_ENTITY_HEAP_BASE (s32) WorldEntityHeapBase
#define entity_jan_iwa_VRAM (void*) entity_jan_iwa_VRAM
#define entity_sbk_omo_VRAM (void*) entity_sbk_omo_VRAM
#define entity_default_VRAM (void*) entity_default_VRAM
#else
#define WORLD_ENTITY_HEAP_BOTTOM 0x80250000
#define WORLD_ENTITY_HEAP_BASE 0x80267FF0
#define entity_jan_iwa_VRAM (void*) 0x802BAE00
#define entity_sbk_omo_VRAM (void*) 0x802BAE00
#define entity_default_VRAM (void*) 0x802BAE00
#endif

#if VERSION_JP // TODO remove once segments are split
extern Addr entity_default_ROM_END;
extern Addr entity_default_ROM_START;
extern Addr entity_jan_iwa_ROM_END;
extern Addr entity_jan_iwa_ROM_START;
extern Addr entity_sbk_omo_ROM_END;
extern Addr entity_sbk_omo_ROM_START;
#endif

s32 D_8014AFB0 = 255;

s32 CreateEntityVarArgBuffer[4];
HiddenPanelsData gCurrentHiddenPanels;
s32 gEntityHideMode;

s32 D_801512BC;
s32 D_80151304;
s32 D_80151344;
s32 entity_numEntities;
s32 gEntityHeapBase;
s32 gLastCreatedEntityIndex;

s32 gEntityHeapBottom;
s32 entity_numShadows;
s32 isAreaSpecificEntityDataLoaded;
s32 entity_updateCounter;

BSS EntityList gWorldEntityList;
BSS EntityList gBattleEntityList;
BSS EntityList* gCurrentEntityListPtr;
BSS s32 D_80151474;
BSS ShadowList gWorldShadowList;
BSS ShadowList gBattleShadowList;
BSS ShadowList* gCurrentShadowListPtr;

BSS s32 wEntityDataLoadedSize;
BSS s32 bEntityDataLoadedSize;
BSS s32 D_80151664;

BSS EntityBlueprint* wEntityBlueprint[MAX_ENTITIES + 2];
BSS EntityBlueprint* bEntityBlueprint[4];
BSS s32 D_801516F8;
BSS s32 D_801516FC;

extern Addr BattleEntityHeapBottom; // todo ???

void update_shadows(void);
s32 step_entity_commandlist(Entity* entity);
void entity_swizzle_anim_pointers(EntityBlueprint* entityData, void* baseAnim, void* baseGfx);
void render_shadows(void);
void update_entity_transform_matrix(Entity* entity);
void update_shadow_transform_matrix(Shadow* shadow);
void update_entity_inverse_rotation_matrix(Entity* entity);
void delete_entity(s32 entityIndex);
void delete_entity_and_unload_data(s32 entityIndex);
void _delete_shadow(s32 shadowIndex);
void reload_world_entity_data(void);
s32 entity_get_collision_flags(Entity* entity);
void entity_free_static_data(EntityBlueprint* data);
s32 create_entity_shadow(Entity* entity, f32 x, f32 y, f32 z);
void update_entity_shadow_position(Entity* entity);

void update_entities(void) {
    s32 i;

    D_801512BC = 0;
    entity_numEntities = 0;
    entity_updateCounter++;

    for (i = 0; i < MAX_ENTITIES; i++) {
        Entity* entity = get_entity_by_index(i);

        if (entity != NULL) {
            entity_numEntities++;

            if (!(entity->flags & ENTITY_FLAG_SKIP_UPDATE)) {
                if (entity->flags & ENTITY_FLAG_BOUND_SCRIPT_DIRTY) {
                    entity->flags &= ~ENTITY_FLAG_BOUND_SCRIPT_DIRTY;
                    if (!(entity->flags & ENTITY_FLAG_8000)) {
                        entity->flags |= ENTITY_FLAG_2000000;
                    }
                    entity->boundScript = start_script(entity->boundScriptBytecode, EVT_PRIORITY_A, EVT_FLAG_RUN_IMMEDIATELY);
                }

                if (entity->flags & ENTITY_FLAG_2000000) {
                    if (does_script_exist(entity->boundScript->id)) {
                        if (entity->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
                            update_model_animator(entity->virtualModelIndex);
                        } else {
                            exec_entity_model_commandlist(entity->virtualModelIndex);
                        }

                        if (entity->flags & ENTITY_FLAG_ALWAYS_FACE_CAMERA) {
                            entity->rot.y = -gCameras[gCurrentCameraID].curYaw;
                        }

                        if (!(entity->flags & ENTITY_FLAG_SKIP_UPDATE_TRANSFORM_MATRIX)) {
                            update_entity_transform_matrix(entity);
                        }
                        continue;
                    } else {
                        entity->flags &= ~ENTITY_FLAG_2000000;
                    }
                }

                if (entity->collisionTimer == 0) {
                    entity->collisionFlags = entity_get_collision_flags(entity);

                    if (entity->collisionFlags) {
                        EntityCallback handleCollision = entity->blueprint->fpHandleCollision;

                        if (handleCollision != NULL && handleCollision(entity) != 0) {
                            entity->collisionTimer = 10;
                            entity->flags |= ENTITY_FLAG_DETECTED_COLLISION;
                        }
                    }
                } else {
                    entity->collisionTimer--;
                    if (entity->flags & ENTITY_FLAG_CONTINUOUS_COLLISION) {
                        if (entity->collisionTimer == 0) {
                            entity->flags &= ~(ENTITY_FLAG_DISABLE_COLLISION | ENTITY_FLAG_CONTINUOUS_COLLISION);
                        } else {
                            entity->flags |= ENTITY_FLAG_DISABLE_COLLISION;
                        }
                    } else if (entity->collisionTimer == 0) {
                        entity->flags &= ~ENTITY_FLAG_DETECTED_COLLISION;
                        entity->flags &= ~ENTITY_FLAG_PARTNER_COLLISION;
                        entity->collisionFlags = 0;
                    }
                }

                if (entity->flags & ENTITY_FLAG_ALWAYS_FACE_CAMERA) {
                    entity->rot.y = -gCameras[gCurrentCameraID].curYaw;
                }

                if (gGameStatusPtr->debugScripts == DEBUG_SCRIPTS_NONE) {
                    if (entity->updateScriptCallback != NULL) {
                        entity->updateScriptCallback(entity);
                    }

                    if (entity->scriptReadPos != NULL) {
                        if (entity->scriptDelay != 0) {
                            entity->scriptDelay--;
                            if (entity->scriptDelay == 0) {
                                while (step_entity_commandlist(entity));
                            }
                        }
                    }
                }

                if (!(entity->flags & ENTITY_FLAG_SKIP_UPDATE_TRANSFORM_MATRIX)) {
                    update_entity_transform_matrix(entity);
                }

                if (!(entity->flags & ENTITY_FLAG_DISABLE_COLLISION)) {
                    update_entity_inverse_rotation_matrix(entity);
                }

                if (entity->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
                    update_model_animator(entity->virtualModelIndex);
                } else {
                    exec_entity_model_commandlist(entity->virtualModelIndex);
                }

                if (entity->shadowIndex >= 0) {
                    update_entity_shadow_position(entity);
                }

                if (entity->flags & ENTITY_FLAG_PENDING_INSTANCE_DELETE) {
                    delete_entity(entity->listIndex);
                }

                if (entity->flags & ENTITY_FLAG_PENDING_FULL_DELETE) {
                    delete_entity_and_unload_data(entity->listIndex);
                }
            }
        }
    }

    update_shadows();
    gCurrentHiddenPanels.tryFlipTrigger = FALSE;
}

void update_shadows(void) {
    s32 i;

    entity_numShadows = 0;

    for (i = 0; i < MAX_SHADOWS; i++) {
        Shadow* shadow = get_shadow_by_index(i);

        if (shadow != NULL) {
            entity_numShadows++;

            if (!(shadow->flags & ENTITY_FLAG_SKIP_UPDATE)) {
                if (shadow->flags & ENTITY_FLAG_ALWAYS_FACE_CAMERA) {
                    shadow->rot.y = -gCameras[gCurrentCameraID].curYaw;
                }

                update_shadow_transform_matrix(shadow);

                if (shadow->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
                    update_model_animator(shadow->entityModelID);
                } else {
                    exec_entity_model_commandlist(shadow->entityModelID);
                }

                if (shadow->flags & ENTITY_FLAG_PENDING_INSTANCE_DELETE) {
                    _delete_shadow(shadow->listIndex);
                }
            }
        }
    }
}

void set_entity_commandlist(Entity* entity, s32* entityScript) {
    entity->scriptReadPos = entityScript;
    entity->scriptDelay = 1;
    entity->savedReadPos[0] = entity->scriptReadPos;
}

s32 step_entity_commandlist(Entity* entity) {
    s32* args = entity->scriptReadPos;
    s32 ret;
    s32 labelId;
    void (*tempfunc)(Entity*);

    switch (*args++) {
        case ENTITY_SCRIPT_OP_End:
            entity->scriptDelay = -1;
            entity->updateScriptCallback = NULL;
            entity->scriptReadPos = NULL;
            ret = FALSE;
            break;
        case ENTITY_SCRIPT_OP_Jump:
            entity->scriptReadPos = (s32*)*args;
            entity->scriptDelay = 1;
            entity->savedReadPos[0] = entity->scriptReadPos;
            ret = TRUE;
            break;
        case ENTITY_SCRIPT_OP_Call:
            tempfunc = (void (*)(Entity*))(*args++);
            entity->scriptReadPos = args;
            (tempfunc)(entity);
            ret = TRUE;
            break;
        case ENTITY_SCRIPT_OP_SetCallback:
            entity->scriptDelay = *args++;
            entity->updateScriptCallback = (s32 (*)(Entity*)) *args++;
            entity->scriptReadPos = args++;
            ret = FALSE;
            break;
        case ENTITY_SCRIPT_OP_Goto:
            entity->scriptReadPos = entity->savedReadPos[*args];
            ret = TRUE;
            break;
        case ENTITY_SCRIPT_OP_Label:
            labelId = *args++;
            entity->savedReadPos[labelId] = args;
            entity->scriptReadPos = args;
            ret = TRUE;
            break;
        case ENTITY_SCRIPT_OP_RestartBoundScript:
            if (entity->boundScriptBytecode != NULL) {
                entity->flags |= ENTITY_FLAG_BOUND_SCRIPT_DIRTY;
            }
            entity->scriptReadPos = args++;
            ret = TRUE;
            break;
        case ENTITY_SCRIPT_OP_SetFlags:
            entity->flags |= *args++;
            entity->scriptReadPos = args++;
            ret = TRUE;
            break;
        case ENTITY_SCRIPT_OP_ClearFlags:
            entity->flags &= ~*args++;
            entity->scriptReadPos = args++;
            ret = TRUE;
            break;
        case ENTITY_SCRIPT_OP_PlaySound:
            sfx_play_sound(*args++);
            entity->scriptReadPos = args++;
            ret = TRUE;
            break;
        default:
            args++;
            entity->scriptReadPos = args++;
            ret = TRUE;
            break;
    }
    return ret;
}

void exec_entity_commandlist(Entity* entity) {
    while (step_entity_commandlist(entity));
}

void func_8010FD98(void* arg0, s32 alpha) {
    if (alpha >= 255) {
        gDPSetRenderMode(gMainGfxPos++, G_RM_AA_ZB_OPA_SURF, G_RM_AA_ZB_OPA_SURF2);
        gDPSetCombineMode(gMainGfxPos++, G_CC_MODULATEIA, G_CC_MODULATEIA);
    } else {
        gDPSetCombineMode(gMainGfxPos++, PM_CC_01, PM_CC_02);
        gDPSetPrimColor(gMainGfxPos++, 0, 0, 0, 0, 0, alpha);
    }
}

void func_8010FE44(void* arg0) {
    func_8010FD98(arg0, D_8014AFB0);
}

void entity_model_set_shadow_color(void* data) {
    s32 alpha = (s32)data;

    gDPSetCombineMode(gMainGfxPos++, PM_CC1_SHADOW, PM_CC2_SHADOW);
    gDPSetPrimColor(gMainGfxPos++, 0, 0, 0, 0, 0, alpha);
}

void render_entities(void) {
    s32 i;

    for (i = 0; i < MAX_ENTITIES; i++) {
        Entity* entity = get_entity_by_index(i);

        if (entity != NULL) {
            if (gGameStatusPtr->context == CONTEXT_WORLD) {
                if (gEntityHideMode != ENTITY_HIDE_MODE_0 &&
                    !(entity->flags & ENTITY_FLAG_IGNORE_DISTANCE_CULLING) &&
                    dist2D(gPlayerStatusPtr->pos.x,
                           gPlayerStatusPtr->pos.z,
                           entity->pos.x,
                           entity->pos.z) > 200.0f
                ) {
                    continue;
                }

                if (gEntityHideMode == ENTITY_HIDE_MODE_1) {
                    if (!(entity->flags & ENTITY_FLAG_DRAW_IF_CLOSE_HIDE_MODE1)) {
                        continue;
                    }
                } else if (gEntityHideMode == ENTITY_HIDE_MODE_2) {
                    if (!(entity->flags & ENTITY_FLAG_DRAW_IF_CLOSE_HIDE_MODE2)) {
                        continue;
                    }
                }
            }

            if (!(entity->flags & ENTITY_FLAG_HIDDEN)) {
                if (entity->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
                    if (D_8014AFB0 == 255) {
                        if (entity->renderSetupFunc != NULL) {
                            set_animator_render_callback(
                                entity->virtualModelIndex,
                                (void*)(u32) entity->listIndex,
                                (void (*)(void*)) entity->renderSetupFunc
                            );
                        }
                    } else {
                        set_animator_render_callback(
                            entity->virtualModelIndex,
                            (void*)(u32) entity->listIndex,
                            func_8010FE44
                        );
                    }

                    if (entity->gfxBaseAddr == NULL) {
                        render_animated_model(entity->virtualModelIndex, &entity->transformMatrix);
                    } else {
                        render_animated_model_with_vertices(entity->virtualModelIndex,
                                      &entity->transformMatrix,
                                      entity->vertexSegment,
                                      entity->gfxBaseAddr);
                    }
                } else {
                    if (D_8014AFB0 == 255) {
                        if (entity->renderSetupFunc != NULL) {
                            bind_entity_model_setupGfx(
                                entity->virtualModelIndex,
                                (void*)(u32) entity->listIndex,
                                (void (*)(void*)) entity->renderSetupFunc
                            );
                        } else {
                            get_entity_model(entity->virtualModelIndex)->fpSetupGfxCallback = NULL;
                        }
                    } else {
                        bind_entity_model_setupGfx(entity->virtualModelIndex, (void*)(u32)entity->listIndex, func_8010FE44);
                    }

                    if (entity->gfxBaseAddr == NULL) {
                        draw_entity_model_A(entity->virtualModelIndex, &entity->transformMatrix);
                    } else {
                        draw_entity_model_B(entity->virtualModelIndex,
                                               &entity->transformMatrix,
                                               entity->vertexSegment,
                                               entity->gfxBaseAddr);
                    }
                }
            }
        }
    }

    render_shadows();
}

void render_shadows(void) {
    s32 i;

    for (i = 0; i < MAX_SHADOWS; i++) {
        Shadow* shadow = get_shadow_by_index(i);

        if (shadow != NULL) {
            if (shadow->flags & ENTITY_FLAG_HIDDEN) {
                if (shadow->flags & ENTITY_FLAG_FADING_AWAY) {
                    shadow->alpha -= 20;
                    if (shadow->alpha <= 20) {
                        shadow->flags |= ENTITY_FLAG_PENDING_INSTANCE_DELETE;
                    }
                }
            } else if (shadow->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
                if (shadow->vertexArray == NULL) {
                    render_animated_model(shadow->entityModelID, &shadow->transformMatrix);
                } else {
                    render_animated_model_with_vertices(shadow->entityModelID,
                                  &shadow->transformMatrix,
                                  shadow->vertexSegment,
                                  shadow->vertexArray);
                }
            } else {
                if (shadow->flags & ENTITY_FLAG_FADING_AWAY) {
                    shadow->alpha -= 20;
                    if (shadow->alpha <= 20) {
                        shadow->flags |=  ENTITY_FLAG_PENDING_INSTANCE_DELETE;
                    }
                }

                bind_entity_model_setupGfx(shadow->entityModelID, (void*)(u32)shadow->alpha, entity_model_set_shadow_color);

                if (shadow->vertexArray == NULL) {
                    draw_entity_model_A(shadow->entityModelID, &shadow->transformMatrix);
                } else {
                    draw_entity_model_B(shadow->entityModelID,
                                           &shadow->transformMatrix,
                                           shadow->vertexSegment,
                                           shadow->vertexArray);
                }
            }
        }
    }
}

void update_entity_transform_matrix(Entity* entity) {
    Matrix4f sp18;
    Matrix4f sp58;
    Matrix4f sp98;
    Matrix4f spD8;
    Matrix4f sp118;
    Matrix4f sp158;
    Matrix4f sp198;

    if (entity->updateMatrixOverride != NULL) {
        entity->updateMatrixOverride(entity);
        return;
    }

    guTranslateF(sp58, entity->pos.x, entity->pos.y, entity->pos.z);
    guRotateF(spD8, entity->rot.x, 1.0f, 0.0f, 0.0f);
    guRotateF(sp118, entity->rot.y, 0.0f, 1.0f, 0.0f);
    guRotateF(sp158, entity->rot.z, 0.0f, 0.0f, 1.0f);
    guMtxCatF(sp158, spD8, sp18);
    guMtxCatF(sp18, sp118, sp98);
    guScaleF(sp198, entity->scale.x, entity->scale.y, entity->scale.z);
    guMtxCatF(sp198, sp98, sp18);
    guMtxCatF(sp18, sp58, sp98);
    guMtxF2L(sp98, &entity->transformMatrix);
}

void update_shadow_transform_matrix(Shadow* shadow) {
    Matrix4f sp18;
    Matrix4f sp58;
    Matrix4f sp98;
    Matrix4f spD8;
    Matrix4f sp118;
    Matrix4f sp158;
    Matrix4f sp198;

    guTranslateF(sp58, shadow->pos.x, shadow->pos.y, shadow->pos.z);
    guRotateF(sp118, shadow->rot.x, 1.0f, 0.0f, 0.0f);
    guRotateF(spD8, shadow->rot.y, 0.0f, 1.0f, 0.0f);
    guRotateF(sp158, shadow->rot.z, 0.0f, 0.0f, 1.0f);
    guMtxCatF(sp158, sp118, sp98);
    guMtxCatF(spD8, sp98, sp98);
    guScaleF(sp198, shadow->scale.x, shadow->scale.y, shadow->scale.z);
    guMtxCatF(sp198, sp98, sp18);
    guMtxCatF(sp18, sp58, sp98);
    guMtxF2L(sp98, &shadow->transformMatrix);
}

void update_entity_inverse_rotation_matrix(Entity* entity) {
    Matrix4f sp18;
    Matrix4f sp58;

    guRotateF(sp18, -entity->rot.y, 0.0f, 1.0f, 0.0f);
    guRotateF(sp58, -entity->rot.z, 0.0f, 0.0f, 1.0f);
    guMtxCatF(sp18, sp58, sp18);
    guRotateF(sp58, -entity->rot.x, 1.0f, 0.0f, 0.0f);
    guMtxCatF(sp18, sp58, entity->inverseTransformMatrix);

    entity->effectiveSize = sqrtf(((SQ(entity->aabb.x) + SQ(entity->aabb.z)) * 0.25f) + SQ(entity->aabb.y));
}

Entity* get_entity_by_index(s32 index) {
    return (*gCurrentEntityListPtr)[index & 0xFFF];
}

Shadow* get_shadow_by_index(s32 index) {
    return (*gCurrentShadowListPtr)[index & 0xFFF];
}

EntityList* get_entity_list(void) {
    EntityList* ret;

    if (gGameStatusPtr->context == CONTEXT_WORLD) {
        ret = &gWorldEntityList;
    } else {
        ret = &gBattleEntityList;
    }
    return ret;
}

ShadowList* get_shadow_list(void) {
    ShadowList* ret;

    if (gGameStatusPtr->context == CONTEXT_WORLD) {
        ret = &gWorldShadowList;
    } else {
        ret = &gBattleShadowList;
    }
    return ret;
}

s32 entity_start_script(Entity* entity) {
    if (entity->boundScriptBytecode != NULL) {
        entity->flags |= ENTITY_FLAG_BOUND_SCRIPT_DIRTY;
        return 1;
    }
    return 0;
}

u32 get_entity_type(s32 index) {
    Entity* entity = get_entity_by_index(index);

    if (entity == NULL) {
        return -1;
    } else {
        return entity->blueprint->entityType;
    }
}

void delete_entity(s32 entityIndex) {
    Entity* entity = get_entity_by_index(entityIndex);

    if (entity->dataBuf.any != NULL) {
        heap_free(entity->dataBuf.any);
    }

    if (!(entity->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL)) {
        free_entity_model_by_index(entity->virtualModelIndex);
    } else {
        delete_model_animator(get_animator_by_index(entity->virtualModelIndex));
    }

    if (entity->shadowIndex >= 0) {
        Shadow* shadow = get_shadow_by_index(entity->shadowIndex);

        shadow->flags |= ENTITY_FLAG_FADING_AWAY;
    }

    heap_free((*gCurrentEntityListPtr)[entityIndex]);
    (*gCurrentEntityListPtr)[entityIndex] = NULL;
}

void delete_entity_and_unload_data(s32 entityIndex) {
    Entity* entity = get_entity_by_index(entityIndex);

    if (entity->dataBuf.any != NULL) {
        heap_free(entity->dataBuf.any);
    }

    if (!(entity->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL)) {
        free_entity_model_by_index(entity->virtualModelIndex);
    } else {
        delete_model_animator(get_animator_by_index(entity->virtualModelIndex));
    }

    entity_free_static_data(entity->blueprint);

    if (entity->shadowIndex >= 0) {
        Shadow* shadow = get_shadow_by_index(entity->shadowIndex);

        shadow->flags |= ENTITY_FLAG_FADING_AWAY;
    }

    heap_free((*gCurrentEntityListPtr)[entityIndex]);
    (*gCurrentEntityListPtr)[entityIndex] = NULL;
}

void _delete_shadow(s32 shadowIndex) {
    Shadow* shadow = get_shadow_by_index(shadowIndex);

    free_entity_model_by_index(shadow->entityModelID);
    heap_free((*gCurrentShadowListPtr)[shadowIndex]);
    (*gCurrentShadowListPtr)[shadowIndex] = NULL;
}

s32 entity_get_collision_flags(Entity* entity) {
    u32 listIndex = entity->listIndex;
    s32 entityFlags = 0;
    u32 flag;

    if (entity->flags & ENTITY_FLAG_PARTNER_COLLISION) {
        entityFlags = ENTITY_COLLISION_PARTNER;
        entity->flags &= ~ENTITY_FLAG_PARTNER_COLLISION;
    }

    flag = gCollisionStatus.curFloor;
    if (flag != -1 && (flag & COLLISION_WITH_ENTITY_BIT) && listIndex == (u8)flag) {
        entityFlags |= ENTITY_COLLISION_PLAYER_TOUCH_FLOOR;
    }

    flag = gCollisionStatus.lastTouchedFloor;
    if (flag != -1 && (flag & COLLISION_WITH_ENTITY_BIT) && listIndex == (u8)flag) {
        entityFlags |= ENTITY_COLLISION_PLAYER_LAST_FLOOR;
    }

    flag = gCollisionStatus.curCeiling;
    if (flag != -1 && (flag & COLLISION_WITH_ENTITY_BIT) && listIndex == (u8)flag) {
        entityFlags |= ENTITY_COLLISION_PLAYER_TOUCH_CEILING;
    }

    flag = gCollisionStatus.pushingAgainstWall;
    if (flag != -1 && (flag & COLLISION_WITH_ENTITY_BIT) && listIndex == (u8)flag) {
        entityFlags |= ENTITY_COLLISION_PLAYER_PUSHING_AGAINST;
    }

    flag = gCollisionStatus.lastWallHammered;
    if (flag != -1 && (flag & COLLISION_WITH_ENTITY_BIT) && listIndex == (u8)flag) {
        entityFlags |= ENTITY_COLLISION_PLAYER_HAMMER;
    }

    flag = gCollisionStatus.curWall;
    if (flag != -1 && (flag & COLLISION_WITH_ENTITY_BIT) && listIndex == (u8)flag && gPlayerStatusPtr->pressedButtons & BUTTON_A) {
        entityFlags |= ENTITY_COLLISION_PLAYER_TOUCH_WALL;
    }

    return entityFlags;
}

s32 entity_try_partner_interaction_trigger(s32 entityIdx) {
    s32 interacted = FALSE;
    u32 entityType = get_entity_type(entityIdx);
    s32 partnerID = get_current_partner_id();
    Entity* entity;

    switch (partnerID) {
        case PARTNER_BOMBETTE:
            switch (entityType) {
                default:
                    return FALSE;
                case ENTITY_TYPE_BLUE_SWITCH:
                case ENTITY_TYPE_RED_SWITCH:
                case ENTITY_TYPE_MULTI_TRIGGER_BLOCK:
                case ENTITY_TYPE_BRICK_BLOCK:
                case ENTITY_TYPE_MULTI_COIN_BRICK:
                case ENTITY_TYPE_YELLOW_BLOCK:
                case ENTITY_TYPE_SINGLE_TRIGGER_BLOCK:
                case ENTITY_TYPE_HIDDEN_YELLOW_BLOCK:
                case ENTITY_TYPE_HIDDEN_RED_BLOCK:
                case ENTITY_TYPE_RED_BLOCK:
                case ENTITY_TYPE_HAMMER1_BLOCK:
                case ENTITY_TYPE_HAMMER1_BLOCK_TINY:
                case ENTITY_TYPE_SUPER_BLOCK:
                case ENTITY_TYPE_BOMBABLE_ROCK:
                    entity = get_entity_by_index(entityIdx);
                    entity->flags |= ENTITY_FLAG_PARTNER_COLLISION;
                    interacted = TRUE;
            }
            break;
        case PARTNER_KOOPER:
             switch (entityType) {
                default:
                    return FALSE;
                case ENTITY_TYPE_BLUE_SWITCH:
                case ENTITY_TYPE_RED_SWITCH:
                case ENTITY_TYPE_MULTI_TRIGGER_BLOCK:
                case ENTITY_TYPE_BRICK_BLOCK:
                case ENTITY_TYPE_MULTI_COIN_BRICK:
                case ENTITY_TYPE_YELLOW_BLOCK:
                case ENTITY_TYPE_SINGLE_TRIGGER_BLOCK:
                case ENTITY_TYPE_HIDDEN_YELLOW_BLOCK:
                case ENTITY_TYPE_HIDDEN_RED_BLOCK:
                case ENTITY_TYPE_RED_BLOCK:
                case ENTITY_TYPE_HEALING_BLOCK:
                case ENTITY_TYPE_1C:
                case ENTITY_TYPE_SAVE_POINT:
                case ENTITY_TYPE_SUPER_BLOCK:
                    entity = get_entity_by_index(entityIdx);
                    entity->flags |= ENTITY_FLAG_PARTNER_COLLISION;
                    interacted = TRUE;
            }
            break;
    }
    return interacted;
}

s32 test_player_entity_aabb(Entity* entity) {
    f32 yTemp = entity->pos.y - (gPlayerStatus.pos.y + gPlayerStatus.colliderHeight);
    f32 xCollRadius;
    f32 zCollRadius;
    f32 xDist;
    f32 zDist;

    if (yTemp > 0.0f || gPlayerStatus.colliderHeight + entity->aabb.y < fabsf(yTemp)) {
        return 0;
    }

    xCollRadius = (gPlayerStatus.colliderDiameter + entity->aabb.x) * 0.5;
    xDist = fabsf(gPlayerStatus.pos.x - entity->pos.x);
    zCollRadius = ((gPlayerStatus.colliderDiameter + entity->aabb.z) * 0.5);
    zDist = fabsf(gPlayerStatus.pos.z - entity->pos.z);

    if (xCollRadius < xDist || zCollRadius < zDist) {
        return 0;
    }

    return 1;
}

s32 is_player_action_state(s8 actionState) {
    return actionState == gPlayerStatus.actionState;
}

void entity_set_render_script(Entity* entity, EntityModelScript* cmdList) {
    if (!(entity->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL)) {
        set_entity_model_render_command_list(entity->virtualModelIndex, cmdList);
    }
}

void entity_reset_collision(Entity* entity) {
    entity->collisionTimer = 0;
    entity->flags &= ~ENTITY_FLAG_DETECTED_COLLISION;
}

void load_area_specific_entity_data(void) {
    if (!isAreaSpecificEntityDataLoaded) {
        if (gGameStatusPtr->areaID == AREA_JAN || gGameStatusPtr->areaID == AREA_IWA) {
            DMA_COPY_SEGMENT(entity_jan_iwa);
        } else if (gGameStatusPtr->areaID == AREA_SBK || gGameStatusPtr->areaID == AREA_OMO) {
            DMA_COPY_SEGMENT(entity_sbk_omo);
        } else {
            DMA_COPY_SEGMENT(entity_default);
        }

        isAreaSpecificEntityDataLoaded = TRUE;
    }
}

void clear_entity_data(b32 arg0) {
    s32 i;

    D_801516FC = 1;
    entity_numEntities = 0;
    entity_numShadows = 0;
    entity_updateCounter = 0;
    D_80151304 = 0;

    if (gGameStatusPtr->context == CONTEXT_WORLD) {
        gEntityHideMode = ENTITY_HIDE_MODE_0;
    }

    isAreaSpecificEntityDataLoaded = FALSE;
    gCurrentHiddenPanels.panelsCount = 0;
    gCurrentHiddenPanels.activateISpy = FALSE;
    if (!arg0) {
        D_80151344 = 0;
    }
    D_8014AFB0 = 255;

    if (gGameStatusPtr->context == CONTEXT_WORLD) {
        wEntityDataLoadedSize = 0;
        for (i = 0; i < MAX_ENTITIES; i++) {
            wEntityBlueprint[i] = NULL;
        }
    } else {
        bEntityDataLoadedSize = 0;
        for (i = 0; i < ARRAY_COUNT(bEntityBlueprint); i++) {
            bEntityBlueprint[i] = NULL;
        }
    }

    if (gGameStatusPtr->context == CONTEXT_WORLD) {
        gEntityHeapBottom = WORLD_ENTITY_HEAP_BOTTOM;
        gEntityHeapBase = WORLD_ENTITY_HEAP_BASE;
    } else {
        gEntityHeapBottom = (s32) BattleEntityHeapBottom;
        gEntityHeapBase = gEntityHeapBottom + 0x3000;
    }

    gCurrentEntityListPtr = get_entity_list();
    gCurrentShadowListPtr = get_shadow_list();

    for (i = 0; i < MAX_ENTITIES; i++) {
        (*gCurrentEntityListPtr)[i] = NULL;
    }

    for (i = 0; i < MAX_SHADOWS; i++) {
        (*gCurrentShadowListPtr)[i] = NULL;
    }
}

void init_entity_data(void) {
    if (gGameStatusPtr->context == CONTEXT_WORLD) {
        gEntityHeapBottom = WORLD_ENTITY_HEAP_BOTTOM;
        gEntityHeapBase = WORLD_ENTITY_HEAP_BASE;
        reload_world_entity_data();
    } else {
        s32 i;

        for (i = 0; i < ARRAY_COUNT(bEntityBlueprint); i++) {
            bEntityBlueprint[i] = 0;
        }
        gEntityHeapBottom = (s32) BattleEntityHeapBottom;
        gEntityHeapBase = gEntityHeapBottom + 0x3000;
    }
    gCurrentEntityListPtr = get_entity_list();
    gCurrentShadowListPtr = get_shadow_list();
    entity_numEntities = 0;
    entity_numShadows = 0;
}

void reload_world_entity_data(void) {
    s32 i;
    s32 totalSize = 0;
    s32 temp1;
    s32 dataLength;
    void* gfxData;
    void* animData;

    for (i = 0; i < MAX_ENTITIES; i++) {
        EntityBlueprint* bp = wEntityBlueprint[i];
        if (bp == NULL) {
            break;
        }

        if (!(bp->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL)) {
            void* gfxData;

            dataLength = ((bp->dma.end - bp->dma.start) >> 2);
            gfxData = (void*)(gEntityHeapBase - totalSize * 4 - dataLength * 4);
            totalSize += dma_copy(bp->dma.start, bp->dma.end, gfxData) >> 2;
        } else {
            DmaEntry* dmaList = bp->dmaList;

            if (bp->entityType == ENTITY_TYPE_RESET_MUNCHLESIA) {
                gfxData = (void*)gEntityHeapBottom;
                temp1 = dma_copy(dmaList[0].start, dmaList[0].end, gfxData) >> 2;
                dma_copy(dmaList[1].start, dmaList[1].end, (void*)(gEntityHeapBottom + temp1 * 4)) >> 2;
                animData = (void*)(gEntityHeapBottom + temp1 * 4);
                entity_swizzle_anim_pointers(bp, animData, gfxData);
            } else {
                s32 temp5;
                s32 q;

                dataLength = ((dmaList[0].end - dmaList[0].start) >> 2);
                q = gEntityHeapBase - totalSize * 4;
                gfxData = (void*)(q - dataLength * 4);
                totalSize += dma_copy(dmaList[0].start, dmaList[0].end, gfxData) >> 2;

                dataLength = ((dmaList[1].end - dmaList[1].start) >> 2);
                q = gEntityHeapBase - totalSize * 4;
                animData = (void*)(q - dataLength * 4);
                totalSize += dma_copy(dmaList[1].start, dmaList[1].end, animData) >> 2;

                entity_swizzle_anim_pointers(bp, animData, gfxData);
            }
        }
    }
}

void entity_swizzle_anim_pointers(EntityBlueprint* entityData, void* baseAnim, void* baseGfx) {
    StaticAnimatorNode* node;
    s32* ptr = (s32*)((s32)baseAnim + (s32)entityData->modelAnimationNodes);

    while (TRUE) {
        if (*ptr == -1) {
            *ptr = 0;
            return;
        }
        node = (StaticAnimatorNode*)((s32)baseAnim + ((*ptr) & 0xFFFF));
        *ptr++ = (s32)node;

        if ((s32)node->displayList != -1) {
            node->displayList = (Gfx*)((s32)baseGfx + ((s32)(node->displayList) & 0xFFFF));
        } else {
            node->displayList = NULL;
        }

        if ((s32)node->sibling != -1) {
            node->sibling = (StaticAnimatorNode*)((s32)baseAnim + ((s32)(node->sibling) & 0xFFFF));
        } else {
            node->sibling = NULL;
        }

        if ((s32)node->child != -1) {
            node->child = (StaticAnimatorNode*)((s32)baseAnim + ((s32)(node->child) & 0xFFFF));
        } else {
            node->child = NULL;
        }

        if ((s32)node->vtxList != -1) {
            node->vtxList = (Vtx*)((s32)baseGfx + ((s32)(node->vtxList) & 0xFFFFF));
        } else {
            node->vtxList = NULL;
        }
    }
}

s32 is_entity_data_loaded(Entity* entity, EntityBlueprint* blueprint, s32* loadedStart, s32* loadedEnd) {
    EntityBlueprint** blueprints;
    s32 i;
    s32 ret;
    s32 size;
    DmaEntry* entDmaList;

    *loadedStart = 0;
    *loadedEnd = 0;
    ret = FALSE;

    if (gGameStatusPtr->context == CONTEXT_WORLD) {
        blueprints = wEntityBlueprint;
    } else {
        blueprints = bEntityBlueprint;
    }

    for (i = 0; i < MAX_ENTITIES; i++, blueprints++) {
        EntityBlueprint* bp = *blueprints;
        if (bp == NULL) {
            blueprints[0] = blueprint;
            blueprints[1] = NULL;
            ret = TRUE;
            if (blueprint->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
                s32 size;
                entDmaList = blueprint->dmaList;
                size = (entDmaList[0].end - entDmaList[0].start) >> 2;
                *loadedEnd = *loadedStart + size;
            }
            break;
        } else {
            DmaEntry* bpDmaList = bp->dmaList;
            do {} while (0); // TODO find better match
            entDmaList = blueprint->dmaList;
            if (bpDmaList == entDmaList) {
                if (blueprint->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
                    s32 size = (bpDmaList[0].end - bpDmaList[0].start) >> 2;
                    *loadedEnd = *loadedStart + size;
                }
                break;
            } else if (bp == blueprint) {
                if (bp->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
                    s32 size = (entDmaList[0].end - entDmaList[0].start) >> 2;
                    *loadedEnd = *loadedStart + size;
                }
                break;
            } else {
                if (bp->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
                    s32 size = (bpDmaList[0].end - bpDmaList[0].start) >> 2;
                    *loadedEnd = *loadedStart = *loadedStart + size;
                    size = (bpDmaList[1].end - bpDmaList[1].start) >> 2;
                    *loadedStart = *loadedStart + size;
                } else {
                    *loadedStart += (bp->dma.end - bp->dma.start) >> 2;
                }
            }
        }
    }

    return ret;
}

void load_simple_entity_data(Entity* entity, EntityBlueprint* bp, s32 listIndex) {
    s32 loadedStart;
    s32 loadedEnd;
    s32 entitySize;
    u32 temp;
    s32 totalSize;

    entity->vertexSegment = 0xA;
    if (gGameStatusPtr->context == CONTEXT_WORLD) {
        totalSize = wEntityDataLoadedSize;
    } else {
        totalSize = bEntityDataLoadedSize;
    }

    if (is_entity_data_loaded(entity, bp, &loadedStart, &loadedEnd)) {
        if (totalSize + ((bp->dma.end - bp->dma.start) >> 2) > 0x5FFCU) {
            get_entity_type(entity->listIndex);
            get_entity_type(entity->listIndex);
            PANIC();
        }
        entitySize = (bp->dma.end - bp->dma.start) >> 2;
        entity->gfxBaseAddr = (void*)(gEntityHeapBase - totalSize * 4 - entitySize * 4);
        totalSize += dma_copy(bp->dma.start, bp->dma.end, entity->gfxBaseAddr) >> 2;
        get_entity_type(entity->listIndex);
    } else {
        entitySize = (bp->dma.end - bp->dma.start) >> 2;
        entity->gfxBaseAddr = (void*)(gEntityHeapBase - loadedStart * 4 - entitySize * 4);
        get_entity_type(entity->listIndex);
    }

    if (gGameStatusPtr->context == CONTEXT_WORLD) {
        wEntityDataLoadedSize = totalSize;
    } else {
        bEntityDataLoadedSize = totalSize;
    }
}

void load_split_entity_data(Entity* entity, EntityBlueprint* entityData, s32 listIndex) {
    s32 swizzlePointers = FALSE;
    s32 loadedStart, loadedEnd;
    void* animBaseAddr;
    s16* animationScript;
    StaticAnimatorNode** animationNodes;
    s32 specialSize;
    s32 dma1size;
    s32 dma2size_1;
    s32 dma2size_2;
    s32 totalLoaded;

    if (entityData->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
        DmaEntry* dmaList = entityData->dmaList;
        entity->vertexSegment = 0xA;

        switch (entityData->entityType) {
            case ENTITY_TYPE_RESET_MUNCHLESIA:
            case ENTITY_TYPE_MUNCHLESIA_ENVELOP:
            case ENTITY_TYPE_MUNCHLESIA_CHEWING:
            case ENTITY_TYPE_MUNCHLESIA_RESET1:
                specialSize = 0x1000;
                break;
            case ENTITY_TYPE_MUNCHLESIA_GRAB:
            case ENTITY_TYPE_MUNCHLESIA_BEGIN_CHEW:
            case ENTITY_TYPE_MUNCHLESIA_SPIT_OUT:
            case ENTITY_TYPE_MUNCHLESIA_RESET2:
                specialSize = 0x2BC0;
                break;
            default:
                specialSize = 0;
                break;
        }

        if (specialSize != 0) {
            if (entityData->entityType == ENTITY_TYPE_RESET_MUNCHLESIA) {
                is_entity_data_loaded(entity, entityData, &loadedStart, &loadedEnd);
            }
            specialSize -= 0x1000;

            dma1size = dma_copy(dmaList[0].start, dmaList[0].end, (void*)(gEntityHeapBottom + specialSize * 4)) / 4;
            entity->gfxBaseAddr = (void*)(gEntityHeapBottom + specialSize * 4);
            dma_copy(dmaList[1].start, dmaList[1].end, (void*)(gEntityHeapBottom + specialSize * 4 + dma1size * 4));
            animBaseAddr = (void*)(gEntityHeapBottom + specialSize * 4 + dma1size * 4);
            swizzlePointers = TRUE;
        } else if (is_entity_data_loaded(entity, entityData, &loadedStart, &loadedEnd)) {
            if (gGameStatusPtr->context == CONTEXT_WORLD) {
                totalLoaded = wEntityDataLoadedSize;
            } else {
                totalLoaded = bEntityDataLoadedSize;
            }

            if ((totalLoaded + ((dmaList[0].end - dmaList[0].start) >> 2)) > 0x5FFCU) {
                get_entity_type(entity->listIndex);
                PANIC();
            }

            if ((totalLoaded + ((dmaList[1].end - dmaList[1].start) >> 2)) > 0x5FFCU) {
                get_entity_type(entity->listIndex);
                PANIC();
            }

            dma2size_1 = dma_copy(dmaList[0].start, dmaList[0].end, dmaList[0].start + ((gEntityHeapBase - totalLoaded * 4 - (s32)dmaList[0].end) >> 2) * 4) >> 2;
            entity->gfxBaseAddr = (void*)(gEntityHeapBase - totalLoaded * 4 - dma2size_1 * 4);
            totalLoaded += dma2size_1;

            dma2size_2 = dma_copy(dmaList[1].start, dmaList[1].end, dmaList[1].start + ((gEntityHeapBase - totalLoaded * 4 - (s32)dmaList[1].end) >> 2) * 4) >> 2;
            animBaseAddr = (void*)(gEntityHeapBase - totalLoaded * 4 - dma2size_2 * 4);
            totalLoaded += dma2size_2;
            get_entity_type(entity->listIndex);

            if (gGameStatusPtr->context == CONTEXT_WORLD) {
                wEntityDataLoadedSize = totalLoaded;
            } else {
                bEntityDataLoadedSize = totalLoaded;
            }
            swizzlePointers = TRUE;
        } else {
            u32 temp = (dmaList[0].end - dmaList[0].start) >> 2;
            entity->gfxBaseAddr = (void*)(gEntityHeapBase - loadedStart * 4 - temp * 4);
            temp = (dmaList[1].end - dmaList[1].start) >> 2;
            animBaseAddr = (void*)(gEntityHeapBase - loadedEnd * 4 - temp * 4);
            get_entity_type(entity->listIndex);
        }
    } else {
        entity->virtualModelIndex = create_model_animator(entityData->renderCommandList);
        load_model_animator_tree(entity->virtualModelIndex, entityData->modelAnimationNodes);
        update_model_animator(entity->virtualModelIndex);
        return;
    }
    animationScript = entityData->renderCommandList;
    animationNodes = (StaticAnimatorNode**)((s32)animBaseAddr + (s32)entityData->modelAnimationNodes);
    if (swizzlePointers) {
        entity_swizzle_anim_pointers(entityData, animBaseAddr, entity->gfxBaseAddr);
    }
    entity->virtualModelIndex = create_mesh_animator(animationScript, animBaseAddr);
    load_mesh_animator_tree(entity->virtualModelIndex, animationNodes);
    update_model_animator(entity->virtualModelIndex);
    entity->flags |= ENTITY_FLAG_HAS_ANIMATED_MODEL;
}

s32 func_80111790(EntityBlueprint* data) {
    s32 i;

    for (i = 0; i < ARRAY_COUNT(*gCurrentEntityListPtr); i++) {
        Entity* entity = (*gCurrentEntityListPtr)[i];

        if (entity != NULL && entity->blueprint->dma.start != NULL) {
            if (entity->blueprint->dma.start == entity->blueprint) {
                return TRUE;
            }
        }
    }
    return FALSE;
}

void entity_free_static_data(EntityBlueprint* data) {
    s32 freeSlot;
    s32 size;
    EntityBlueprint* bp;

    for (freeSlot = 0; freeSlot < MAX_ENTITIES; freeSlot++) {
        bp = wEntityBlueprint[freeSlot];
        if (bp == NULL) {
            break;
        }
    }

    if (freeSlot < MAX_ENTITIES) {
        bp = wEntityBlueprint[freeSlot - 1];
        if (bp == data) {
            if (bp->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL) {
                DmaEntry* dmaList = bp->dmaList;
                size = ((dmaList[0].end - dmaList[0].start) >> 2);
                size += ((dmaList[1].end - dmaList[1].start) >> 2);
                if (!func_80111790(bp)) {
                    wEntityBlueprint[freeSlot - 1] = NULL;
                    wEntityDataLoadedSize -= size;
                }
            } else {
                size = (bp->dma.end - bp->dma.start) >> 2;
                if (!func_80111790(bp)) {
                    wEntityBlueprint[freeSlot - 1] = NULL;
                    wEntityDataLoadedSize -= size;
                }
            }
        }
    }
}

s32 create_entity(EntityBlueprint* bp, ...) {
    va_list ap;
    EntityBlueprint** bpPtr;
    f32 x, y, z;
    f32 rotY;
    s32 listIndex;
    Entity* entity;
    s32* args;

    va_start(ap, bp);
    // needed to match
    bpPtr = &bp;
    *bpPtr = bp;

    load_area_specific_entity_data();

    x = va_arg(ap, s32);
    y = va_arg(ap, s32);
    z = va_arg(ap, s32);
    rotY = va_arg(ap, s32);

    args = &CreateEntityVarArgBuffer[2];

    *args-- = 0;
    *args-- = 0;
    *args = 0;

    for (listIndex = 3; listIndex > 0; listIndex--) {
        s32 arg = va_arg(ap, s32);

        if (arg == MAKE_ENTITY_END) {
            break;
        }
        *args++ = arg;
    }

    va_end(ap);

    for (listIndex = 0; listIndex < ARRAY_COUNT(*gCurrentEntityListPtr); listIndex++) {
        if ((*gCurrentEntityListPtr)[listIndex] == NULL) {
            break;
        }
    }

    if (listIndex >= MAX_ENTITIES) {
        return -1;
    }

    (*gCurrentEntityListPtr)[listIndex] = entity = heap_malloc(sizeof(*entity));
    mem_clear(entity, sizeof(*entity));
    entity->dataBuf.any = NULL;
    if (bp->typeDataSize != 0) {
        entity->dataBuf.any = heap_malloc(bp->typeDataSize);
        mem_clear(entity->dataBuf.any, bp->typeDataSize);
    }
    entity->type = bp->entityType;
    entity->listIndex = listIndex;
    entity->boundScript = NULL;
    entity->updateMatrixOverride = NULL;
    entity->blueprint = bp;
    entity->scriptReadPos = bp->updateEntityScript;
    entity->scriptDelay = entity->scriptReadPos != NULL ? 1 : 0;
    entity->savedReadPos[0] = bp->updateEntityScript;
    entity->updateScriptCallback = NULL;
    entity->flags = bp->flags | ENTITY_FLAG_CREATED;
    entity->collisionFlags = 0;
    entity->collisionTimer = 0;
    entity->renderSetupFunc = NULL;
    entity->pos.x = x;
    entity->pos.y = y;
    entity->pos.z = z;
    entity->rot.x = 0.0f;
    entity->rot.y = rotY;
    entity->rot.z = 0.0f;
    entity->scale.x = 1.0f;
    entity->scale.y = 1.0f;
    entity->scale.z = 1.0f;
    entity->aabb.x = bp->aabbSize[0];
    entity->aabb.y = bp->aabbSize[1];
    entity->aabb.z = bp->aabbSize[2];
    entity->unk_05 = 1;
    entity->unk_08 = -1;
    entity->alpha = 255;
    entity->virtualModelIndex = -1;
    entity->shadowIndex = -1;
    entity->gfxBaseAddr = NULL;

    if (!(bp->flags & ENTITY_FLAG_HAS_ANIMATED_MODEL)) {
        if (bp->dma.start != 0) {
            load_simple_entity_data(entity, bp, listIndex);
        }
        if (bp->renderCommandList != NULL) {
            entity->virtualModelIndex = load_entity_model(bp->renderCommandList);
            exec_entity_model_commandlist(entity->virtualModelIndex);
        }
    } else {
        load_split_entity_data(entity, bp, listIndex);
    }

    if (bp->entityType != ENTITY_TYPE_SHADOW && (entity->flags & (ENTITY_FLAG_FIXED_SHADOW_SIZE | ENTITY_FLAG_HAS_SHADOW))) {
        create_entity_shadow(entity, x, y, z);
    }

    switch (bp->entityType) {
        case ENTITY_TYPE_BLUE_SWITCH:
        case ENTITY_TYPE_RED_SWITCH:
        case ENTITY_TYPE_SIMPLE_SPRING:
        case ENTITY_TYPE_SCRIPT_SPRING:
        case ENTITY_TYPE_STAR_BOX_LAUNCHER:
            entity->flags |= ENTITY_FLAG_4000;
            break;
    }

    if (bp->fpInit != NULL) {
        bp->fpInit(entity);
    }

    update_entity_transform_matrix(entity);
    return entity->listIndex;
}

s32 create_shadow_from_data(ShadowBlueprint* bp, f32 x, f32 y, f32 z) {
    Shadow* shadow;
    s32 i;

    for (i = 0; i < ARRAY_COUNT(*gCurrentShadowListPtr); i++) {
        if ((*gCurrentShadowListPtr)[i] == NULL) {
            break;
        }
    }

    ASSERT(i < ARRAY_COUNT(*gCurrentShadowListPtr));

    shadow = heap_malloc(sizeof(*shadow));
    (*gCurrentShadowListPtr)[i] = shadow;
    mem_clear(shadow, sizeof(*shadow));
    shadow->listIndex = i;
    shadow->flags = bp->flags | ENTITY_FLAG_CREATED;
    shadow->alpha = 128;
    shadow->unk_06 = 0x80;
    shadow->pos.x = x;
    shadow->pos.y = y;
    shadow->pos.z = z;
    shadow->scale.x = 1.0f;
    shadow->scale.y = 1.0f;
    shadow->scale.z = 1.0f;

    if (bp->animModelNode != NULL) {
        shadow->flags |= ENTITY_FLAG_HAS_ANIMATED_MODEL;
        shadow->entityModelID = create_model_animator(bp->renderCommandList);
        load_model_animator_tree(shadow->entityModelID, bp->animModelNode);
    } else {
        shadow->entityModelID = load_entity_model(bp->renderCommandList);
    }

    if (bp->onCreateCallback != NULL) {
        bp->onCreateCallback(shadow);
    }
    update_shadow_transform_matrix(shadow);
    return shadow->listIndex;
}

API_CALLABLE(MakeEntity) {
    Bytecode* args = script->ptrReadPos;
    EntityBlueprint* entityData;
    s32 x, y, z;
    s32 flags;
    s32 nextArg;
    s32 entityIndex;
    s32 endOfArgs;
    s32* varArgBufPos;

    if (isInitialCall != TRUE) {
        return ApiStatus_DONE2;
    }

    entityData = (EntityBlueprint*)evt_get_variable(script, *args++);
    varArgBufPos = &CreateEntityVarArgBuffer[2];
    endOfArgs = MAKE_ENTITY_END;
    x = evt_get_variable(script, *args++);
    y = evt_get_variable(script, *args++);
    z = evt_get_variable(script, *args++);
    flags = evt_get_variable(script, *args++);

    *varArgBufPos-- = 0;
    *varArgBufPos-- = 0;
    *varArgBufPos = 0;

    do {
        nextArg = evt_get_variable(script, *args++);

        if (nextArg != endOfArgs) {
            *varArgBufPos++ = nextArg;
        }
    } while (nextArg != endOfArgs);

    entityIndex = create_entity(entityData, x, y, z, flags, CreateEntityVarArgBuffer[0], CreateEntityVarArgBuffer[1], CreateEntityVarArgBuffer[2], endOfArgs);
    gLastCreatedEntityIndex = entityIndex;
    script->varTable[0] = entityIndex;
    return ApiStatus_DONE2;
}

API_CALLABLE(SetEntityCullMode) {
    Entity* entity = get_entity_by_index(gLastCreatedEntityIndex);
    Bytecode* args = script->ptrReadPos;
    s32 mode = evt_get_variable(script, *args++);

    if (mode == 0) {
        entity->flags |= ENTITY_FLAG_DRAW_IF_CLOSE_HIDE_MODE1;
    } else if (mode == 1) {
        entity->flags |= ENTITY_FLAG_DRAW_IF_CLOSE_HIDE_MODE2;
    } else if (mode == 2) {
        entity->flags |= ENTITY_FLAG_DRAW_IF_CLOSE_HIDE_MODE2 | ENTITY_FLAG_DRAW_IF_CLOSE_HIDE_MODE1;
    } else {
        entity->flags |= ENTITY_FLAG_IGNORE_DISTANCE_CULLING | ENTITY_FLAG_DRAW_IF_CLOSE_HIDE_MODE2 |
                         ENTITY_FLAG_DRAW_IF_CLOSE_HIDE_MODE1;
    }
    return ApiStatus_DONE2;
}

API_CALLABLE(UseDynamicShadow) {
    Entity* entity = get_entity_by_index(gLastCreatedEntityIndex);
    Bytecode* args = script->ptrReadPos;

    if (evt_get_variable(script, *args++)) {
        Shadow* shadow;

        entity->flags |= ENTITY_FLAG_HAS_DYNAMIC_SHADOW;
        shadow = get_shadow_by_index(entity->shadowIndex);
        shadow->flags |= ENTITY_FLAG_SHADOW_POS_DIRTY;
    } else {
        entity->flags &= ~ENTITY_FLAG_HAS_DYNAMIC_SHADOW;
    }

    return ApiStatus_DONE2;
}

API_CALLABLE(AssignScript) {
    Bytecode* args = script->ptrReadPos;

    if (isInitialCall == TRUE) {
        EvtScript* toBind = (EvtScript*)evt_get_variable(script, *args++);

        get_entity_by_index(gLastCreatedEntityIndex)->boundScriptBytecode = toBind;
        return ApiStatus_DONE2;
    }

    return ApiStatus_DONE1;
}

API_CALLABLE(AssignSwitchFlag) {
    Bytecode* args = script->ptrReadPos;

    if (isInitialCall == TRUE) {
        s32 areaFlag = evt_get_variable(script, *args++);
        Entity* entity = get_entity_by_index(gLastCreatedEntityIndex);
        SwitchData* data = entity->dataBuf.swtch;

        data->areaFlagIndex = areaFlag;
        if (get_area_flag(areaFlag) != 0) {
            entity->flags |= ENTITY_FLAG_PENDING_INSTANCE_DELETE;
        }
        return ApiStatus_DONE2;
    }

    return ApiStatus_DONE1;
}

API_CALLABLE(AssignBlockFlag) {
    Bytecode* args = script->ptrReadPos;

    if (isInitialCall == TRUE) {
        s32 index = evt_get_variable_index(script, *args++);

        BlockData* data = get_entity_by_index(gLastCreatedEntityIndex)->dataBuf.block;
        data->gameFlagIndex = index;

        return ApiStatus_DONE2;
    }

    return ApiStatus_DONE1;
}

API_CALLABLE(AssignChestFlag) {
    Bytecode* args = script->ptrReadPos;

    if (isInitialCall == TRUE) {
        ChestData* data = get_entity_by_index(gLastCreatedEntityIndex)->dataBuf.chest;
        data->gameFlagIndex = evt_get_variable_index(script, *args);

        return ApiStatus_DONE2;
    }

    return ApiStatus_DONE1;
}

API_CALLABLE(AssignPanelFlag) {
    Bytecode* args = script->ptrReadPos;

    if (isInitialCall == TRUE) {
        HiddenPanelData* data = get_entity_by_index(gLastCreatedEntityIndex)->dataBuf.hiddenPanel;

        data->pickupVar = evt_get_variable_index(script, *args++);
        return ApiStatus_DONE2;
    }

    return ApiStatus_DONE1;
}

API_CALLABLE(AssignCrateFlag) {
    Bytecode* args = script->ptrReadPos;

    if (isInitialCall == TRUE) {
        WoodenCrateData* data = get_entity_by_index(gLastCreatedEntityIndex)->dataBuf.crate;

        data->globalFlagIndex = evt_get_variable_index(script, *args++);
        return ApiStatus_DONE2;
    }

    return ApiStatus_DONE1;
}

s32 create_entity_shadow(Entity* entity, f32 x, f32 y, f32 z) {
    u16 bpFlags = entity->blueprint->flags;
    s32 type;
    s16 shadowIndex;

    if (bpFlags & ENTITY_FLAG_FIXED_SHADOW_SIZE) {
        type = (bpFlags & ENTITY_FLAG_CIRCULAR_SHADOW) ? SHADOW_FIXED_CIRCLE : SHADOW_FIXED_SQUARE;
    } else {
        type = (bpFlags & ENTITY_FLAG_CIRCULAR_SHADOW) ? SHADOW_VARYING_CIRCLE : SHADOW_VARYING_SQUARE;
    }

    shadowIndex = create_shadow_type(type, x, y, z);
    entity->shadowIndex = shadowIndex;

    get_shadow_by_index(shadowIndex)->flags |= ENTITY_FLAG_DARK_SHADOW | ENTITY_FLAG_SHADOW_POS_DIRTY;

    return entity->shadowIndex;
}

s32 create_shadow_type(s32 type, f32 x, f32 y, f32 z) {
    s32 isFixedSize = FALSE;
    ShadowBlueprint* bp = &CircularShadowA;
    s32 shadowIndex;

    switch (type) {
        case SHADOW_FIXED_CIRCLE:
            isFixedSize = TRUE;
        case SHADOW_VARYING_CIRCLE:
            bp = &CircularShadowA;
            break;
        case SHADOW_FIXED_SQUARE:
            isFixedSize = TRUE;
        case SHADOW_VARYING_SQUARE:
            bp = &SquareShadow;
            break;
        case SHADOW_FIXED_ALT_CIRCLE:
            isFixedSize = TRUE;
        case SHADOW_VARYING_ALT_CIRCLE:
            bp = &CircularShadowB;
            break;
    }

    shadowIndex = create_shadow_from_data(bp, x, y, z);

    if (isFixedSize) {
        get_shadow_by_index(shadowIndex)->flags |= ENTITY_FLAG_FIXED_SHADOW_SIZE;
    }

    return shadowIndex;
}

void delete_shadow(s32 shadowIndex) {
    _delete_shadow(shadowIndex);
}

void update_entity_shadow_position(Entity* entity) {
    Shadow* shadow = get_shadow_by_index(entity->shadowIndex);

    if (shadow != NULL) {
        f32 rayX;
        f32 rayY;
        f32 rayZ;
        f32 hitYaw;
        f32 hitPitch;
        f32 hitLength;
        f32 origHitLength;

        if (entity->alpha < 255) {
            shadow->alpha = entity->alpha / 2;
        } else {
            u8 alphaTemp;

            if (shadow->flags & ENTITY_FLAG_DARK_SHADOW) {
                alphaTemp = 160;
            } else {
                alphaTemp = 128;
            }
            shadow->alpha = alphaTemp;
        }

        if (!(entity->flags & ENTITY_FLAG_HAS_DYNAMIC_SHADOW)) {
            if (shadow->flags & ENTITY_FLAG_SHADOW_POS_DIRTY) {
                shadow->flags &= ~ENTITY_FLAG_SHADOW_POS_DIRTY;
            } else {
                return;
            }
        }

        rayX = entity->pos.x;
        rayY = entity->pos.y;
        rayZ = entity->pos.z;

        if (!entity_raycast_down(&rayX, &rayY, &rayZ, &hitYaw, &hitPitch, &hitLength) && hitLength == 32767.0f) {
            hitLength = 0.0f;
        }

        origHitLength = hitLength;

        if (shadow->flags & ENTITY_FLAG_FIXED_SHADOW_SIZE) {
            hitLength = 212.5f;
            shadow->scale.x = entity->aabb.x / hitLength;
            shadow->scale.z = entity->aabb.z / hitLength;
        } else {
            hitLength = ((hitLength / 150.0f) + 0.95) * 250.0;
            shadow->scale.x = (entity->aabb.x / hitLength) * entity->scale.x;
            shadow->scale.z = (entity->aabb.z / hitLength) * entity->scale.z;
        }

        shadow->pos.x = entity->pos.x;
        shadow->pos.z = entity->pos.z;
        shadow->pos.y = rayY;
        entity->shadowPosY = rayY;
        shadow->rot.x = hitYaw;
        shadow->rot.z = hitPitch;
        shadow->rot.y = entity->rot.y;

        if (entity->pos.y < rayY) {
            shadow->flags |= ENTITY_FLAG_SKIP_UPDATE;
            entity->pos.y = rayY + 10.0f;
        } else {
            shadow->flags &= ~ENTITY_FLAG_SKIP_UPDATE;
        }

        shadow->flags = (shadow->flags & ~ENTITY_FLAG_HIDDEN) | ((u16)entity->flags & ENTITY_FLAG_HIDDEN);
        if (!(entity->flags & ENTITY_FLAG_400) && origHitLength == 0.0f) {
            shadow->flags |= ENTITY_FLAG_HIDDEN;
        }
    } else {
        entity->shadowPosY = 0.0f;
    }
}

s32 entity_raycast_down(f32* x, f32* y, f32* z, f32* hitYaw, f32* hitPitch, f32* hitLength) {
    f32 hitX, hitY, hitZ;
    f32 hitDepth;
    f32 hitNx, hitNy, hitNz;
    s32 entityID;
    s32 colliderID;
    s32 hitID;
    s32 ret;

    hitDepth = 32767.0f;
    *hitLength = 32767.0f;
    entityID = test_ray_entities(*x, *y, *z, 0.0f, -1.0f, 0.0f, &hitX, &hitY, &hitZ, &hitDepth, &hitNx, &hitNy, &hitNz);
    hitID = -1;
    ret = FALSE;

    if ((entityID >= 0) && ((get_entity_type(entityID) != ENTITY_TYPE_PUSH_BLOCK) || (hitNx == 0.0f && hitNz == 0.0f && hitNy == 1.0))) {
        hitID = entityID | COLLISION_WITH_ENTITY_BIT;
    }

    colliderID = test_ray_colliders(COLLIDER_FLAG_IGNORE_PLAYER, *x, *y, *z, 0.0f, -1.0f, 0.0f, &hitX, &hitY, &hitZ, &hitDepth, &hitNx,
                                    &hitNy, &hitNz);
    if (colliderID >= 0) {
        hitID = colliderID;
    }

    if (hitID >= 0) {
        *hitLength = hitDepth;
        *y = hitY;
        *hitYaw = -atan2(0.0f, 0.0f, hitNz * 100.0f, hitNy * 100.0f);
        *hitPitch = -atan2(0.0f, 0.0f, hitNx * 100.0f, hitNy * 100.0f);
        ret = TRUE;
    } else {
        *hitYaw = 0.0f;
        *hitPitch = 0.0f;
    }
    return ret;
}

void set_standard_shadow_scale(Shadow* shadow, f32 height) {
    if (gGameStatusPtr->context == CONTEXT_WORLD) {
        shadow->scale.x = 0.13 - (height / 2600.0f);
    } else {
        shadow->scale.x = 0.12 - (height / 3600.0f);
    }

    if (shadow->scale.x < 0.01) {
        shadow->scale.x = 0.01f;
    }
    shadow->scale.z = shadow->scale.x;
}

void set_npc_shadow_scale(Shadow* shadow, f32 height, f32 npcRadius) {
    if (gGameStatusPtr->context == CONTEXT_WORLD) {
        shadow->scale.x = 0.13 - (height / 2600.0f);
    } else {
        shadow->scale.x = 0.12 - (height / 3600.0f);
    }

    if (shadow->scale.x < 0.01) {
        shadow->scale.x = 0.01f;
    }

    if (npcRadius > 60.0f) {
        shadow->scale.z = shadow->scale.x * 2.0f;
    } else {
        shadow->scale.z = shadow->scale.x;
    }
}

void set_peach_shadow_scale(Shadow* shadow, f32 scale) {
    PlayerStatus* playerStatus = &gPlayerStatus;
    f32 phi_f2 = 0.12f;

    if (gGameStatusPtr->context == CONTEXT_WORLD) {
        switch (playerStatus->anim) {
            case ANIM_Peach2_Carried:
            case ANIM_Peach2_Thrown:
            case ANIM_Peach2_Land:
            case ANIM_Peach3_TiedSideways:
                shadow->scale.x = 0.26f - (scale / 2600.0f);
                if (shadow->scale.x < 0.01) {
                    shadow->scale.x = 0.01f;
                }
                shadow->scale.z = 0.13f - (scale / 2600.0f);
                if (shadow->scale.z < 0.01) {
                    shadow->scale.z = 0.01f;
                }
                return;
        }

        phi_f2 = 0.16f;
    }

    shadow->scale.x = phi_f2 - (scale / 3600.0f);
    if (shadow->scale.x < 0.01) {
        shadow->scale.x = 0.01f;
    }
    shadow->scale.z = shadow->scale.x;
}

s32 is_block_on_ground(Entity* block) {
    f32 x = block->pos.x;
    f32 y = block->pos.y;
    f32 z = block->pos.z;
    f32 hitYaw;
    f32 hitPitch;
    f32 hitLength;
    s32 ret;

    entity_raycast_down(&x, &y, &z, &hitYaw, &hitPitch, &hitLength);

    ret = hitLength;
    if (ret == 32767) {
        ret = FALSE;
    }

    return ret;
}
