/**
 * @file game.c
 * @ingroup Tasks
 *
 * @brief Game task
 */

#include "global.h"
#include "area.h"
#include "asm.h"
#include "common.h"
#include "droptables.h"
#include "enemy.h"
#include "entity.h"
#include "fileselect.h"
#include "flags.h"
#include "functions.h"
#include "game.h"
#include "item.h"
#include "itemMetaData.h"
#include "main.h"
#include "menu.h"
#include "message.h"
#include "object.h"
#include "player.h"
#include "room.h"
#include "save.h"
#include "screen.h"
#include "sound.h"
#include "subtask.h"
#include "itemMetaData.h"
#include "player.h"

// Game task

typedef void(GameState)(void);
typedef void(GameMainState)(void);

static GameState GameTask_Transition;
static GameState GameTask_Init;
static GameState GameTask_Exit;
static GameState GameTask_Main;

static GameMainState GameMain_InitRoom;
static GameMainState GameMain_ChangeRoom;
static GameMainState GameMain_Update;
static GameMainState GameMain_ChangeArea;
GameMainState GameMain_MinishPortal;
static GameMainState GameMain_BarrelUpdate;
/*static GameMainState 00000000;*/
GameMainState GameMain_Subtask;

// Cutscene subtask

typedef void(AuxCutsceneState)(void);
typedef void(CutsceneMainState)(void);

static AuxCutsceneState AuxCutscene_Init;
static AuxCutsceneState AuxCutscene_Main;
static AuxCutsceneState AuxCutscene_Exit;

CutsceneMainState CutsceneMain_Init;
CutsceneMainState CutsceneMain_Update;
CutsceneMainState CutsceneMain_Exit;

// Game Over task

typedef void(GameOverState)(void);

static GameOverState GameOver_Init;
static GameOverState GameOver_FadeIn;
static GameOverState GameOver_TextMove;
static GameOverState GameOver_Update;
static GameOverState GameOver_Exit;

extern u8 gUpdateVisibleTiles;

extern u16 gUnk_020178E0[];
extern u8 gUnk_02024090[];

extern void** gAreaTilesets[];
extern void** gAreaRoomMaps[];
extern void* gAreaMetatiles[];
extern void* gUnk_080B755C[];
extern void** gExitLists[];
extern void** gAreaTable[];

extern void CreateDialogBox(u32, u32);
extern void FinalizeSave(void);
extern void ClearArmosData(void);
extern void ClearRoomMemory(void);
extern void ClearMenuSavestate(void);
extern void ResetUI(void);
extern void sub_0806FD8C(void);
extern void sub_080300C4(void);
extern u32 sub_0805BC04(void);
extern void DeleteSleepingEntities(void);
extern u32 UpdateLightLevel(void);
extern void sub_080185F8(void);
extern void UpdateDoorTransition(void);
extern bool32 IsEnterPortal(void);
extern void UpdateCarriedObject(void);
extern void DrawUI(void);
extern u32 CheckPlayerInactive(void);
extern void CollisionMain(void);
extern void sub_0805BB74(u32);
extern void CreateZeldaFollower(void);
extern void LoadRoomGfx(void);
extern void RecycleEntities(void);
extern void sub_0804AF90(void);
extern void CallRoomProp6(void);
extern void UpdateScroll(void);
extern void UpdateBgAnim(void);
extern void CleanUpGFXSlots(void);
extern void sub_080ADE24(void);
extern void InitUI(u32);
extern void sub_0801AE44(u32);
extern void GenerateAreaHint(void);
extern void ForceSetPlayerState(u32);
extern void UpdateRoomTracker(void);
extern void InitScriptData(void);
extern void sub_08054524(void);
extern void sub_080186D4(void);
extern void sub_0806F364(void);
extern void sub_08052FF4(u32 area, u32 room);
extern void CloneMapData(void);
extern void sub_0807C740(void);
extern void SetBGDefaults(void);
extern void LoadItemGfx(void);

static void LoadRoomBgm(void);
static void sub_08052010(void);
static void ResetTmpFlags(void);
static void InitializeEntities(void);
static void CheckAreaDiscovery(void);
static void CreateManagerF(void);
static void UpdateWindcrests(void);
static void UpdateFakeScroll(void);
static void UpdatePlayerMapCoords(void);
static void sub_08052C3C(void);
static void sub_0805340C(void);
static void sub_08051D98(void);
static void sub_08051DCC(void);
static bool32 CheckGameOver(void);
static bool32 CheckRoomExit(void);
static void UpdatePlayerRoomStatus(void);
static void sub_0805329C(void);
static void InitializePlayer(void);
/* static */ void sub_08051F9C(u32 a1, u32 a2, u32 a3, u32 a4);
static void DrawGameOverText(void);
static u32 StairsAreValid(void);
static void ClearFlagArray(const u16*);
static void DummyHandler(u32* a1);
static void sub_08053434(u32* a1);
static void sub_080534E4(u32* a1);
static void InitAllRoomResInfo(void);
static void InitRoomResInfo(RoomResInfo* info, RoomHeader* hdr, u32 area, u32 room);
static void sub_080532E4(void);
static void sub_08053460(void);

typedef struct {
    u8 _0;
    u8 _1;
    u8 _2;
    u8 _3;
    u8 _4;
    u16 _6;
} struct_08127F94;
extern struct_08127F94 gUnk_08127F94[];

typedef struct {
    u16* dest;
    void* gfx_dest;
    void* buffer_loc;
    u32 _c;
    u16 gfx_src;
    u8 width;
    u8 right_align : 1;
    u8 sm_border : 1;
    u8 unused : 1;
    u8 draw_border : 1;
    u8 border_type : 4;
    u8 fill_type;
    u8 _15;
    u8 _16;
    u8 stylized;
} Font;

typedef struct {
    u8 dest_off[8];
    u8 _8;
    u8 right_align;
    u16 _a;
} PopupOption;

typedef struct {
    u8 area;
    u8 room;
    u8 _2;
    u8 _3;
    u16 x;
    u16 y;
} CutsceneData;
static const CutsceneData sCutsceneData[];

extern u8 gUnk_0200AF13;
extern u8 gUnk_0200AF14;
extern u8 gUnk_080FE1C6[];
extern void (*const gUnk_080FE2AC[])(void);

/** @see Item */
void ForceEquipItem(u32, u32);
extern void SetInventoryValue(u32, u32);

extern u8 gUnk_080FE1DD[];

extern void (*const gUnk_080FE2A0[])(void);

extern void (*const gUnk_080FCBB4[])(void);

extern const EntityData gUnk_080FCB94[];

void sub_08052004(void);

extern void (*const gUnk_080FCCFC[])(void);

extern void (*const gUnk_080FCD38[])(void);

extern const EntityData gUnk_080FCC54[];

void sub_08051FF0(void);

extern void (*const gUnk_080FCD40[])(void);

extern const EntityData gUnk_080FCBC4[];
void sub_08051F78(void);

extern const EntityData gUnk_080FCC14[];

extern void DeleteAllEntities(void);

extern void (*const gUnk_080FCDD4[])(void);

extern const EntityData gUnk_080FCD84[];

extern void (*const gUnk_080FCEB0[])(void);

extern const EntityData gUnk_080FCDE0[];

extern void (*const gUnk_080FCEEC[])(void);

extern const EntityData gUnk_080FCEBC[];

extern void (*const gUnk_080FCEF8[])(void);

extern void (*const gUnk_080FCFA4[])(void);

extern void* GetRoomProperty(u32, u32, u32);

extern void (*const gUnk_080FD108[])(void);

extern const EntityData* gUnk_080FCFB8[];

extern void (*const gUnk_080FD138[])(void);

extern const u16 gUnk_080FD964[];

void GameTask(void) {
    static GameState* const sStates[] = {
        GameTask_Transition,
        GameTask_Init,
        GameTask_Main,
        GameTask_Exit,
    };

    gRoomTransition.frameCount++;
    sStates[gMain.state]();
#ifdef DEMO_USA
    if (gSave.demo_timer != 0) {
        if (--gSave.demo_timer == 0) {
            SetFade(7, 2);
            gMain.state = GAMETASK_EXIT;
        }
    }
#endif
}

static void GameTask_Transition(void) {
    // wait for file select to fade out
    if (gFadeControl.active)
        return;

    DispReset(1);
    InitSoundPlayingInfo();
    zMallocInit();
    ResetUI();
    ClearMenuSavestate();
    MemClear(&gRoomTransition, sizeof(gRoomTransition));
    ClearRoomMemory();
    ClearArmosData();

    FinalizeSave();
    // spawn in with saved status
    MemCopy(&gSave.saved_status, &gRoomTransition.player_status, sizeof(gRoomTransition.player_status));
    gRoomTransition.type = TRANSITION_FADE_BLACK_SLOW;
    ResetTmpFlags();

    gMain.state = GAMETASK_INIT;
    gMain.substate = GAMEMAIN_INITROOM;
}

static void GameTask_Init(void) {
    DispReset(1);
    gFadeControl.mask = 0xffffffff;
    MemClear(&gOAMControls, 0xB74);
    MemClear(&gUI, sizeof(gUI));
    EraseAllEntities();
    SetBGDefaults();
    ClearTilemaps();
    ResetPalettes();
    ResetPaletteTable(1);
    sub_0806FD8C();
    gRoomControls.area = gRoomTransition.player_status.area_next;
    gRoomControls.room = gRoomTransition.player_status.room_next;
    LoadGfxGroups();
    gGFXSlots.unk0 = 1;
    gMain.state = GAMETASK_MAIN;
}

static void GameTask_Main(void) {
    static GameMainState* const sStates[] = {
        GameMain_InitRoom,
        GameMain_ChangeRoom,
        GameMain_Update,
        GameMain_ChangeArea,
        GameMain_MinishPortal,
        GameMain_BarrelUpdate,
        0,
        GameMain_Subtask,
    };
    sStates[gMain.substate]();
}

static void GameMain_InitRoom(void) {
    SetInitializationPriority();
    gScreen.lcd.displayControl = DISPCNT_BG0_ON | DISPCNT_BG1_ON | DISPCNT_BG2_ON | DISPCNT_OBJ_ON | DISPCNT_OBJ_1D_MAP;
    gMain.substate = GAMEMAIN_CHANGEROOM;
    gRoomTransition.transitioningOut = 0;
    gRoomTransition.field_0x4[0] = 0;
    gRoomTransition.field_0x4[1] = 0;
    MessageInitialize();
    InitRoom();
    InitUI(0);
    InitializeEntities();
#ifndef EU
    sub_0801855C();
#endif
}

static void GameMain_ChangeRoom(void) {
    UpdateEntities();
    if (!UpdateLightLevel())
        UpdateScroll();
    UpdateBgAnim();
    UpdateScrollVram();
    DrawUI();
    UpdateManagers();
    FlushSprites();
    DrawOAMCmd();
    UpdateCarriedObject();
    DrawEntities();
    CopyOAM();

    if (gFadeControl.active || gRoomControls.reload_flags != 0)
        return;

    UpdateFakeScroll();
    if (gArea.bgm != gArea.queued_bgm) {
        gArea.bgm = gArea.queued_bgm;
        SoundReq(gArea.queued_bgm | SONG_PLAY_VOL_RESET);
    }

    DeleteSleepingEntities();

    if (sub_0805BC04())
        return;

    UpdatePlayerMapCoords();
    ResetSystemPriority();
    UpdateWindcrests();
    sub_080300C4();
    gMain.substate = GAMEMAIN_UPDATE;
    SetPlayerControl(0);
    gUnk_02034490.unk0 = 0;
#if defined(USA) || defined(DEMO_USA)
    if (gArea.inventoryGfxIdx != 0xff) {
        sub_0801855C();
    }
    CreateManagerF();
    CheckAreaDiscovery();
#elif defined(EU)
    CheckAreaDiscovery();
    sub_0801855C();
#elif defined(JP)
    CheckAreaDiscovery();
    if (gArea.inventoryGfxIdx != 0xff) {
        sub_0801855C();
    }
#elif defined(DEMO_JP)
    if (gRoomTransition.field_0x2c[5])
        CheckAreaDiscovery();
    if (gArea.inventoryGfxIdx != 0xff) {
        sub_0801855C();
    }
    CreateManagerF();
#endif
    if (!gRoomVars.field_0x0) {
        RequestPriorityDuration(NULL, 1);
    }
}

static void GameMain_Update(void) {
    if (CheckPlayerInactive() || IsEnterPortal()) {
        return;
    }
    sub_0805340C();

    // leave early if player is now entering a portal
    if (gMain.substate != GAMEMAIN_UPDATE) {
        return;
    }

    if ((gMessage.doTextBox & 0x7f) || gPriorityHandler.priority_timer != 0)
        sub_08078B48();

    FlushSprites();
    UpdateEntities();
    UpdateDoorTransition();
    CollisionMain();
    UpdateScroll();
    UpdateBgAnim();
    UpdateScrollVram();
    sub_08052C3C();
    DrawUI();
    UpdateManagers();
    DrawOAMCmd();
    UpdateCarriedObject();
    DrawEntities();
    CheckRoomExit();
    UpdatePlayerMapCoords();
    CheckGameOver();
    sub_080185F8();
    CopyOAM();
    switch (gRoomControls.reload_flags) {
        case RELOAD_ALL:
            gPlayerState.queued_action = PLAYER_ROOMTRANSITION;
            gMain.substate = GAMEMAIN_CHANGEROOM;
            SetRoomReloadPriority();
            sub_08051D98();
            break;
        case RELOAD_ENTITIES:
            gPlayerState.queued_action = PLAYER_ROOMTRANSITION;
            gMain.substate = GAMEMAIN_CHANGEROOM;
            SetRoomReloadPriority();
            sub_08051DCC();
            break;
    }
}

static void GameMain_BarrelUpdate(void) {
    if (CheckPlayerInactive())
        return;

    UpdateEntities();
    CollisionMain();
    DrawUI();
    UpdateManagers();
    FlushSprites();
    DrawOAMCmd();
    UpdateCarriedObject();
    DrawEntities();
    CheckRoomExit();
    CheckGameOver();
    CopyOAM();
    if (!gFadeControl.active)
        ResetSystemPriority();
}

static void GameMain_ChangeArea(void) {
    FlushSprites();
    DrawOAMCmd();
    DrawEntities();
    gMain.pad = 1;
    CopyOAM();
    if (!gFadeControl.active) {
        DispReset(1);
        gMain.state = GAMETASK_INIT;
        gMain.substate = GAMEMAIN_INITROOM;
        gRoomTransition.transitioningOut = 1;
    }
}

static void GameTask_Exit(void) {
#ifdef DEMO_USA
    if (!gFadeControl.active)
        DoSoftReset();
#else
    SetFade(7, 8);
    SetTask(TASK_GAMEOVER);
#endif
}

static void InitializeEntities(void) {
    sub_08052EA0();
    sub_0804AF90();
    CallRoomProp6();
    InitializePlayer();
    gUnk_03004030.unk_00 = NULL;
    sub_0807C740();
    gUpdateVisibleTiles = 1;
    LoadRoomBgm();
    SetColor(0, 0);
    LoadRoom();
    CreateZeldaFollower();
    CallRoomProp5And7();
    sub_0805329C();
    UpdateScrollVram();
    sub_0805BB74(0xffffffff);
    UpdatePlayerRoomStatus();
}

static void sub_08051D98(void) {
    sub_08052EA0();
    gRoomVars.field_0x0 = 1;

    // remove old entities, unless persistent
    RecycleEntities();

    sub_0804AF90();
    CallRoomProp6();
    LoadRoomGfx();
    LoadRoomBgm();
    LoadRoom();
    CallRoomProp5And7();
    SetPlayerControl(1);
}

static void sub_08051DCC(void) {
    gRoomControls.area = gRoomTransition.player_status.area_next;
    gRoomControls.room = gRoomTransition.player_status.room_next;
    RoomExitCallback();
    gRoomTransition.type = TRANSITION_3;
    InitRoom();
    sub_08052EA0();
    RecycleEntities();
    sub_0804AF90();
    CallRoomProp6();
    LoadRoomBgm();
}

static void UpdateWindcrests(void) {
    if (AreaIsOverworld()) {
        struct_08127F94* i;
        u32 hi_x, hi_y;
        s32 x, y;

        x = gPlayerEntity.x.HALF.HI;
        if (x < 0)
            x += 0xf;
        hi_x = x >> 4;

        y = gPlayerEntity.y.HALF.HI;
        if (y < 0)
            y += 0xf;
        hi_y = y >> 4;

        for (i = gUnk_08127F94; i->_0 != 0xFF; i++) {
            if (i->_0 <= hi_x && i->_2 >= hi_x && i->_1 <= hi_y && i->_3 >= hi_y) {
                gSave.windcrests |= 1 << i->_4;
                break;
            }
        }
    }
}

void Subtask_AuxCutscene(void) {
    static AuxCutsceneState* const sStates[] = {
        AuxCutscene_Init,
        AuxCutscene_Main,
        AuxCutscene_Exit,
    };

    sStates[gMenu.menuType]();
}

static void AuxCutscene_Init(void) {
    const CutsceneData* p = &sCutsceneData[gUI.field_0x3];
    gRoomControls.area = p->area;
    gRoomControls.room = p->room;
    LoadGfxGroups();
    gArea.localFlagOffset = GetFlagBankOffset(gRoomControls.area);
    SetCurrentRoomPropertyList(p->area, p->room);
    LoadAuxiliaryRoom(p->area, p->room);
    gRoomControls.scroll_x = gRoomControls.origin_x + p->x;
    gRoomControls.scroll_y = gRoomControls.origin_y + p->y;
    gMenu.field_0x0 = p->_2;
    gMenu.field_0x3 = p->_3 & 0xf;
    gMenu.field_0xc = (void*)p;
    gMenu.menuType++;
    gMenu.overlayType = 0;
    gMenu.transitionTimer = 300;
    AuxCutscene_Main(); // init
}

static void AuxCutscene_Main(void) {
    static CutsceneMainState* const sStates[] = {
        CutsceneMain_Init,
        CutsceneMain_Update,
        CutsceneMain_Exit,
    };

    sStates[gMenu.field_0x0]();
    FlushSprites();
    UpdateEntities();
    DrawEntities();
    CopyOAM();
    UpdateScroll();
    UpdateBgAnim();
    UpdateManagers();
    UpdateScrollVram();
}

static const CutsceneData sCutsceneData[] = {
    { AREA_MINISH_WOODS, 0, 0, 0, 336, 528 },
    { AREA_HYRULE_FIELD, 1, 1, 0, 472, 312 },
    { AREA_HYRULE_CASTLE, 2, 1, 1, 16, 16 },
    { AREA_SANCTUARY, 2, 1, 2, 0, 0 },
    { AREA_EZLO_CUTSCENE, 0, 1, 3, 0, 0 },
    { AREA_HOUSE_INTERIORS_2, 16, 1, 4, 0, 0 },
    { AREA_DARK_HYRULE_CASTLE_OUTSIDE, 0, 1, 5, 0, 40 },
    { AREA_FORTRESS_OF_WINDS, 28, 2, 0, 0, 0 },
    { AREA_FORTRESS_OF_WINDS, 29, 2, 1, 0, 0 },
    { AREA_DARK_HYRULE_CASTLE, 14, 2, 2, 16, 16 },
};

static void AuxCutscene_Exit(void) {
    u32 flag = sCutsceneData[gUI.field_0x3]._3;
    if (flag & 0xF0) {
        MenuFadeIn(2, flag >> 4);
    } else {
        gUI.nextToLoad = 3;
        SetFadeInverted(0x10);
        MessageInitialize();
    }
}

void sub_08051F78(void) {
    u32 idx = gUI.field_0x3;
    const CutsceneData* p = &sCutsceneData[idx];
    sub_08051F9C(p->area, p->room, p->x, p->y);
}

void sub_08051F9C(u32 a1, u32 a2, u32 a3, u32 a4) {
    u32 tmp = gScreen.lcd.displayControl & (DISPCNT_WIN0_ON | DISPCNT_WIN1_ON);
    sub_08052FF4(a1, a2);
    gRoomControls.scroll_x = gRoomControls.origin_x + a3;
    gRoomControls.scroll_y = gRoomControls.origin_y + a4;
    sub_0807C740();
    gUpdateVisibleTiles = 1;
    gUsedPalettes = 0;
    gScreen.lcd.displayControl |= tmp;
}

void sub_08051FF0(void) {
    sub_0804B0B0(gMenu.field_0xc[0], gMenu.field_0xc[1]);
}

void sub_08052004(void) {
    gMenu.menuType = 2;
}

static void sub_08052010(void) {
    InitSoundPlayingInfo();
    MessageInitialize();
    DispReset(1);
    MemClear(gBG1Buffer, 0x800);
    MemClear(gBG2Buffer, 0x800);
    sub_080A4D34();
    LoadPaletteGroup(0xA);
    SetColor(0, 0);
    LoadGfxGroup(4);
    MemClear((void*)0x06000000, 0x20);
    MemClear(&gMenu, 0x30);
    gScreen.lcd.displayControl |= DISPCNT_OBJ_ON;
    gScreen.bg1.control = BGCNT_PRIORITY(1) | BGCNT_SCREENBASE(28) | BGCNT_CHARBASE(0);
    gScreen.bg2.control = BGCNT_PRIORITY(1) | BGCNT_SCREENBASE(29) | BGCNT_CHARBASE(1);
    gScreen.bg1.updated = 1;
    gScreen.bg2.updated = 1;
}

void GameOverTask(void) {
    static GameOverState* const sStates[] = {
        GameOver_Init, GameOver_FadeIn, GameOver_TextMove, GameOver_Update, GameOver_Exit,
    };

    sStates[gMain.state]();
    if (gMain.state != GAMETASK_TRANSITION) {
        FlushSprites();
        DrawGameOverText();
        CopyOAM();
    }
}

static void switch_state(u32 idx) {
    gMain.state = idx;
    SetMenuType(0);
}

static void GameOver_Init(void) {
    if (gFadeControl.active)
        return;
    sub_08052010();
    gGenericMenu.unk10.a[0] = 80;
    gMenu.transitionTimer = 60;
    gSave.stats.health = 24;
    gMain.field_0x5 = 60;
    SoundReq(BGM_GAMEOVER);
    SetFadeInverted(4);
    gFadeControl.mask = 0xFFFF0001;
    switch_state(GAMETASK_INIT);
}

static void GameOver_FadeIn(void) {
    if (gFadeControl.active)
        return;

    if (gMain.field_0x5 == 0) {
        if (gGenericMenu.unk10.a[0] >= 38) {
            gGenericMenu.unk10.a[0]--;
        } else {
            gMenu.transitionTimer--;
            if (gMenu.transitionTimer == 0) {
                switch_state(GAMETASK_MAIN);
#if defined(DEMO_USA) || defined(DEMO_JP)
                SoundReq(SONG_VOL_FADE_OUT);
                SetFade(7, 4);
#else
                SetPopupState(0, 0);
                gScreen.lcd.displayControl |= DISPCNT_BG1_ON | DISPCNT_BG2_ON;
                gFadeControl.mask = 0x0000ffff;
                SetFade(4, 16);
#endif
            }
        }
    } else {
        gMain.field_0x5--;
    }
}

static void GameOver_TextMove(void) {
#if defined(DEMO_USA) || defined(DEMO_JP)
    if (gFadeControl.active == 0) {
        DoSoftReset();
    }
}
#else
    s32 temp3;
    u32 temp2;

    if (gFadeControl.active)
        return;

    switch (gMenu.menuType) {
        case 0:
            gMenu.transitionTimer = 30;
            gMenu.field_0x3 = 0;
            SetMenuType(1);
            SetPopupState(0, 0);
            gFadeControl.mask = 0xffffffff;
            return;
        case 1:
            if (gMenu.transitionTimer == 0) {
                u32 temp = gMenu.field_0x3;
                switch (gInput.newKeys) {
                    case DPAD_UP:
                        temp = 0;
                        break;
                    case DPAD_DOWN:
                        temp = 1;
                        break;
                    case A_BUTTON:
                        if (gMenu.field_0x3 != 0) {
                            temp2 = 4;
                        } else {
                            CreateDialogBox(8, 0);
                            temp2 = 2;
                        }
                        gMenu.transitionTimer = 60;
                        SetMenuType(temp2);
                        SoundReq(SFX_TEXTBOX_SELECT);
                        break;
                }
                if (gMenu.field_0x3 != temp) {
                    gMenu.field_0x3 = temp;
                    SetPopupState(0, temp);
                    SoundReq(SFX_TEXTBOX_CHOICE);
                }
                return;
            }
            gMenu.transitionTimer--;
            return;
        case 2:
            temp3 = HandleSave(0);
            gMenu.field_0x0 = temp3;
            switch (temp3) {
                case 1:
                    SetMenuType(4);
                    break;
                case -1:
                    gMenu.transitionTimer = 60;
                    CreateDialogBox(9, 0);
                    SetMenuType(3);
                    break;
            }
            return;
        case 3:
            if (gMenu.transitionTimer != 0) {
                gMenu.transitionTimer--;
            } else if (gInput.newKeys & (A_BUTTON | B_BUTTON | START_BUTTON)) {
                SetMenuType(0);
            }
            return;
        case 4:
        default:
            gScreen.lcd.displayControl &= ~DISPCNT_BG1_ON;
            sub_08050384();
            switch_state(GAMETASK_EXIT);
            return;
    }
}
#endif

static void GameOver_Update(void) {
    switch (gMenu.menuType) {
        case 0x0:
            gMenu.transitionTimer = 0x1e;
            gMenu.field_0x3 = 0;
            SetMenuType(1);
            SetPopupState(1, 0);
            return;
        case 0x1:
            gScreen.lcd.displayControl |= DISPCNT_BG1_ON;
            if (gMenu.transitionTimer != 0) {
                gMenu.transitionTimer--;
                return;
            } else {
                u32 temp = gMenu.field_0x3;
                switch (gInput.newKeys) {
                    case DPAD_UP:
                        temp = 0;
                        break;
                    case DPAD_DOWN:
                        temp = 1;
                        break;
                    case A_BUTTON:
                        SetMenuType(2);
                        SoundReq(SFX_TEXTBOX_SELECT);
                        if (temp == 0) {
                            SetFade(5, 8);
                        } else {
                            SetFade(7, 8);
                        }
                        break;
                }
                if (gMenu.field_0x3 != temp) {
                    gMenu.field_0x3 = temp;
                    SetPopupState(1, temp);
                    SoundReq(SFX_TEXTBOX_CHOICE);
                }
            }
            return;
        case 0x2:
        default:
            if (gFadeControl.active == 0) {
                if (gMenu.field_0x3 == 0) {
                    SetTask(TASK_GAME);
                } else {
                    DoSoftReset();
                }
            }
            return;
    }
}

// Unused, since task is set above
// if we got here, it would be a softlock
static void GameOver_Exit(void) {
}

static void DrawGameOverText(void) {
    static const u8 sOffsets[] = {
        48, 68, 88, 108, 137, 156, 174, 192,
    };

    u32 i;

    gOamCmd._4 = 0;
    gOamCmd._6 = 0;
    gOamCmd._8 = 0x8600;
    gOamCmd.y = gGenericMenu.unk10.a[0];
    for (i = 0; i < 8; ++i) {
        gOamCmd.x = sOffsets[i];
#ifdef EU
        DrawDirect(0x1fc, i);
#else
        DrawDirect(0x1fd, i);
#endif
    }
}

void SetPopupState(u32 type, u32 choice_idx) {
    static const Font sDefaultFont = {
        .dest = gBG1Buffer,
        .gfx_dest = (u16*)0x06006000,
        .buffer_loc = gTextGfxBuffer,
        ._c = 0,
        .gfx_src = 0xD300,
        .width = 0xE0,
        .right_align = 0,
        .sm_border = 0,
        .unused = 0,
        .draw_border = 0,
        .border_type = 0,
        .fill_type = 6,
        ._15 = 4,
        ._16 = 1,
        .stylized = 0,
    };
    static const PopupOption sPopupOptions[] = {
        { { 11, 11, 11, 10, 11, 10, 10, 0 }, 8, 0, 16 },
        { { 10, 11, 11, 9, 11, 9, 9, 0 }, 8, 0, 17 },
        { { 15, 15, 15, 15, 15, 15, 15, 0 }, 5, 1, 13 },
    };

    Font font;
    const PopupOption* opt;
    u32 fakematch;

    MemClear(gBG1Buffer, sizeof gBG1Buffer);
    gUnk_020227E8[0]._0.WORD = 0xf;
    gUnk_020227E8[1]._0.WORD = 0xf;
    gUnk_020227E8[2]._0.WORD = 0xf;
    gUnk_020227E8[3]._0.WORD = 0xf;
    *(&gUnk_020227E8[choice_idx]._0.BYTES.byte0 + 1) = fakematch = 1;

    MemCopy(&sDefaultFont, &font, sizeof font);
    opt = &sPopupOptions[type];
    font.dest = sDefaultFont.dest + (opt->dest_off[gSaveHeader->language] + opt->_8 * 32);
    font.right_align = opt->right_align;
    sub_0805F46C(opt->_a, &font);
    gScreen.bg1.updated = fakematch;
}

static void InitializePlayer(void) {
    static const u8 sPlayerSpawnStates[] = {
        [PL_SPAWN_DEFAULT] = PLAYER_INIT,
        [PL_SPAWN_MINISH] = PLAYER_MINISH,
        [PL_SPAWN_DROP] = PLAYER_INIT,
        [PL_SPAWN_WALKING] = PLAYER_ROOMTRANSITION,
        [PL_SPAWN_STEP_IN] = PLAYER_080720DC,
        [PL_SPAWN_SPECIAL] = PLAYER_08074C44,
        [PL_SPAWN_DROP_MINISH] = PLAYER_MINISH,
        [PL_SPAWN_STAIRS_ASCEND] = PLAYER_USEENTRANCE,
        [PL_SPAWN_STAIRS_DESCEND] = PLAYER_USEENTRANCE,
        [PL_SPAWN_9] = PLAYER_MINISH,
        [PL_SPAWN_PARACHUTE_FORWARD] = PLAYER_WARP,
        [PL_SPAWN_PARACHUTE_UP] = PLAYER_WARP,
        [PL_SPAWN_FAST_TRAVEL] = PLAYER_WARP,
    };

    Entity* pl;

    sub_080784C8();
    MemClear(&gUnk_03000B80, sizeof gUnk_03000B80);
    MemClear(&gPlayerState, sizeof gPlayerState);
    MemFill32(0xffffffff, &gPlayerState.path_memory, sizeof gPlayerState.path_memory);
    MemClear(&gPlayerEntity, sizeof gPlayerEntity);

    pl = &gPlayerEntity;

    gRoomControls.camera_target = pl;
    gPlayerState.queued_action = sPlayerSpawnStates[gRoomTransition.player_status.spawn_type];
    if (!CheckGlobalFlag(EZERO_1ST)) {
        gPlayerState.flags |= PL_NO_CAP;
    }
    switch (gRoomTransition.player_status.spawn_type) {
        case PL_SPAWN_DROP:
        case PL_SPAWN_DROP_MINISH:
            pl->z.HALF.HI = -0xc0;
            break;
        case PL_SPAWN_STEP_IN:
            gPlayerState.field_0x38 = 16;
            pl->direction = Direction8FromAnimationState(gRoomTransition.player_status.start_anim);
        case PL_SPAWN_WALKING:
            pl->speed = 224;
            break;
        case PL_SPAWN_STAIRS_ASCEND:
        case PL_SPAWN_STAIRS_DESCEND:
            gPlayerState.field_0x38 = 1;
            gPlayerState.field_0x39 = gRoomTransition.player_status.spawn_type;
            break;
        case PL_SPAWN_PARACHUTE_FORWARD:
            gPlayerState.field_0x38 = 1;
            break;
        case PL_SPAWN_PARACHUTE_UP:
            gPlayerState.field_0x38 = 3;
            break;
        case PL_SPAWN_FAST_TRAVEL:
            gPlayerState.field_0x38 = 4;
    }

    pl->kind = PLAYER;
    pl->flags |= ENT_COLLIDE | ENT_PERSIST;
    pl->spritePriority.b0 = 4;
    pl->health = gSave.stats.health;
    pl->x.HALF.HI = gRoomTransition.player_status.start_pos_x;
    pl->y.HALF.HI = gRoomTransition.player_status.start_pos_y;
    pl->animationState = gRoomTransition.player_status.start_anim;
    pl->collisionLayer = gRoomTransition.player_status.layer;
    UpdateSpriteForCollisionLayer(pl);
    AppendEntityToList(pl, 1);
    RegisterPlayerHitbox();
}

bool32 AreaIsOverworld(void) {
#ifdef EU
    return gArea.areaMetadata == 0x01;
#else
    return gArea.areaMetadata == 0x81;
#endif
}

bool32 CheckAreaOverworld(u32 area) {
#if EU
    return gAreaMetadata[area].flags == 0x01;
#else
    return gAreaMetadata[area].flags == 0x81;
#endif
}

#ifndef EU
bool32 AreaAllowsWarp(void) {
    return (gArea.areaMetadata >> 7) & 1;
}
#endif

bool32 AreaIsDungeon(void) {
    return (gArea.areaMetadata >> 2) & 1;
}

bool32 AreaHasEnemies(void) {
    return (gArea.areaMetadata >> 4) & 1;
}

bool32 AreaHasNoEnemies(void) {
    return (gArea.areaMetadata >> 6) & 1;
}

bool32 AreaHasMap(void) {
    return (gArea.areaMetadata >> 3) & 1;
}

s32 ModHealth(s32 delta) {
    s32 newHealth;
    Stats* stats = &gSave.stats;

    newHealth = stats->health + delta;
    if (newHealth < 0) {
        newHealth = 0;
    }
    if (stats->maxHealth < newHealth) {
        newHealth = (u32)stats->maxHealth;
    }
    stats->health = newHealth;
    gPlayerEntity.health = newHealth;
    return newHealth;
}

void ModRupees(s32 delta) {
    s32 newRupeeCount;
    Stats* s = &gSave.stats;

    newRupeeCount = s->rupees + delta;
    if (newRupeeCount < 0) {
        newRupeeCount = 0;
    } else {
        if (newRupeeCount > gWalletSizes[s->walletType * 2]) {
            newRupeeCount = gWalletSizes[s->walletType * 2];
        }
    }
    s->rupees = newRupeeCount;
}

void sub_080526F8(s32 a1) {
    if (AreaHasKeys()) {
        u8* p = &gSave.unk45C[gArea.dungeon_idx];
        if (*p + a1 < 0)
            *p = 0;
        else
            *p += a1;
    }
}

bool32 AreaHasKeys(void) {
    return (gArea.areaMetadata >> 1) & 1;
}

bool32 HasDungeonMap(void) {
    u32 tmp;

    if (AreaHasKeys())
        tmp = gSave.unk45C[gArea.dungeon_idx];
    return tmp ? 1 : 0;
}

bool32 HasDungeonCompass(void) {
    u32 tmp;

    if (AreaHasKeys())
        tmp = gSave.unk46C[gArea.dungeon_idx] & 4;
    return tmp ? 1 : 0;
}

bool32 HasDungeonBigKey(void) {
    if (!AreaHasKeys())
        return 0;
    return (gSave.unk46C[gArea.dungeon_idx] >> 1) & 1;
}

bool32 HasDungeonSmallKey(void) {
    u32 tmp;

    if (!AreaHasKeys())
        return 0;
    return gSave.unk46C[gArea.dungeon_idx] & 1;
}

void RestoreGameTask(u32 a1) {
    LoadGfxGroups();
#ifndef EU
    CleanUpGFXSlots();
#endif
    sub_080ADE24();
    InitUI(1);
    sub_0801AE44(a1);
    MemCopy(gUnk_02024090, gPaletteBuffer, 1024);
    gUsedPalettes = 0xffffffff;
}

static void LoadRoomBgm(void) {
    gArea.queued_bgm = gAreaMetadata[gRoomControls.area]._3;
    if (CheckLocalFlagByBank(FLAG_BANK_10, LV6_KANE_START)) {
        gArea.queued_bgm = BGM_FIGHT_THEME2;
    }
}

#ifndef EU
void sub_08052878(void) {
    gArea.bgm = gArea.queued_bgm;
    SoundReq(SONG_STOP_ALL);
}

static void sub_0805289C(void) {
    gArea.queued_bgm = gArea.bgm;
}
#endif

static bool32 CheckGameOver(void) {
    if (gRoomTransition.field_0x4[1]) {
        InitFade();
        gMain.state = GAMETASK_EXIT;
        gMain.substate = GAMEMAIN_INITROOM;
        SetFade(5, 8);
        SoundReq(SONG_STOP_BGM);
        return TRUE;
    }
    return FALSE;
}

void RoomExitCallback(void) {
    if (gArea.transitionManager != NULL && gArea.onExit != NULL)
        gArea.onExit(gArea.transitionManager);
}

static bool32 CheckRoomExit(void) {
    if (gRoomTransition.transitioningOut && gSave.stats.health != 0 && gPlayerState.framestate != PL_STATE_DIE) {
        if (StairsAreValid()) {
            gRoomTransition.transitioningOut = 0;
            return FALSE;
        }

        switch (gRoomTransition.type) {
            case TRANSITION_CUT:
                SetFade(13, 8);
                break;
            case TRANSITION_CUT_FAST:
                SetFade(13, 3);
                break;
            case TRANSITION_FADE_WHITE_SLOW:
                SetFade(7, 4);
                break;
            case TRANSITION_FADE_BLACK_SLOW:
                SetFade(5, 4);
                break;
            case TRANSITION_FADE_BLACK:
                SetFade(5, 16);
                break;
            case TRANSITION_FADE_BLACK_FAST:
                SetFade(5, 256);
                break;
            case TRANSITION_7:
            case TRANSITION_FADE_WHITE_FAST:
                SetFade(7, 256);
                break;
            default:
                SetFade(7, 16);
                break;
        }
        RoomExitCallback();
        gMain.substate = GAMEMAIN_CHANGEAREA;
        *(&gMain.pauseInterval + 1) = 1;
        return TRUE;
    }
    return FALSE;
}

static u32 StairsAreValid(void) {
    static const u16 sStairTypes[] = { 0x91, PL_SPAWN_STAIRS_ASCEND, 0x92, PL_SPAWN_STAIRS_DESCEND,
                                       0x93, PL_SPAWN_STAIRS_ASCEND, 0x94, PL_SPAWN_STAIRS_DESCEND,
                                       0x95, PL_SPAWN_STAIRS_ASCEND, 0x96, PL_SPAWN_STAIRS_DESCEND,
                                       0x97, PL_SPAWN_STAIRS_ASCEND, 0x98, PL_SPAWN_STAIRS_DESCEND,
                                       0 };

    const u16* i;
    u32 tgt = gRoomTransition.stairs_idx;

    for (i = sStairTypes; i[0] != 0; i += 2) {
        if (tgt == i[0]) {
            gPlayerState.queued_action = PLAYER_USEENTRANCE;
            gPlayerState.field_0x38 = 0;
            gPlayerState.field_0x39 = i[1];
            if (!gRoomTransition.player_status.spawn_type)
                gRoomTransition.player_status.spawn_type = i[1];
            return 1;
        }
    }
    return 0;
}

void InitParachuteRoom(void) {
    gRoomTransition.transitioningOut = 1;
    gRoomTransition.player_status.start_pos_x = (gPlayerEntity.x.HALF.HI - gRoomControls.origin_x) & 0x3F8;
    gRoomTransition.player_status.start_pos_y = (gPlayerEntity.y.HALF.HI - gRoomControls.origin_y) & 0x3F8;
    gRoomTransition.player_status.start_anim = 4;
    gRoomTransition.player_status.spawn_type = PL_SPAWN_PARACHUTE_FORWARD;
    gRoomTransition.player_status.area_next = gRoomControls.area;
    gRoomTransition.player_status.room_next = gRoomControls.room - 1;
}

static void InitRoomTransition(void) {
    switch (gRoomTransition.type) {
        case TRANSITION_CUT:
            SetFade(12, 8);
            break;
        case TRANSITION_CUT_FAST:
            SetFade(12, 3);
            break;
        case TRANSITION_FADE_WHITE_SLOW:
            SetFade(6, 4);
            break;
        case TRANSITION_3:
            break;
        case TRANSITION_FADE_BLACK_FAST:
            SetFade(5, 256);
            break;
        case TRANSITION_7:
            SetFade(7, 256);
            break;
        case TRANSITION_FADE_BLACK:
            SetFade(4, 16);
            break;
        case TRANSITION_FADE_WHITE_FAST:
            SetFade(6, 8);
            break;
        default:
            SetFadeInverted(16);
            break;
    }
}

bool32 CanDispEzloMessage(void) {
    s32 tmp = PL_STATE_WALK;

    if (!(gInput.heldKeys & SELECT_BUTTON) || gPlayerState.controlMode != CONTROL_ENABLED || gUnk_02034490.unk0 ||
        gUnk_0200AF00.filler0[1])
        return 0;

    if ((gPlayerState.flags & (PL_NO_CAP | 0x110)) || (gPlayerState.framestate_last > tmp) || gPlayerState.item ||
        gPlayerEntity.field_0x7a.HWORD)
        return 0;

    if ((gPlayerEntity.z.HALF.HI & 0x8000) && !gPlayerState.field_0xa)
        return 0;

    GenerateAreaHint();
    ForceSetPlayerState(PL_STATE_TALKEZLO);
    SetPlayerEventPriority();
    return 1;
}

void DisplayEzloMessage(void) {
    u32 height;
    u32 idx;
#if defined(JP) || defined(EU)
    idx = 0x10;
#else
    idx = 0x11;
#endif

    if (gRoomTransition.player_status.field_0x24[idx] == 0) {
        height = gPlayerEntity.y.HALF.HI - gRoomControls.scroll_y > 96 ? 1 : 13;
    } else {
        height = gRoomTransition.player_status.field_0x24[idx];
    }
    MessageAtHeight(gRoomTransition.hint_idx, height);
}

#if defined(USA) || defined(DEMO_USA) || defined(DEMO_JP)
static void CreateManagerF(void) {
    Entity* e = NULL;

    if (gRoomTransition.player_status.field_0x24[13])
        return;
    gRoomTransition.player_status.field_0x24[13] = 1;
#ifndef DEMO_JP
    gRoomTransition.player_status.field_0x24[10] = gArea.locationIndex;
#endif
    e = (Entity*)GetEmptyManager();
    if (e == NULL)
        return;
    e->kind = MANAGER;
    e->id = 15;
    e->type = 15;
    AppendEntityToList(e, 0);
}
#endif

static void sub_08052C3C(void) {
    if (gArea.field_0x18 == 0)
        gArea.unk1A = gArea.field_0x18;
    if (gArea.unk1A) {
        gArea.unk1A--;
        gArea.field_0x18 = 0;
    }
}

static void UpdatePlayerMapCoords(void) {
    if (!AreaHasNoEnemies()) {
        if (AreaIsOverworld()) {
            gRoomTransition.player_status.overworld_map_x = gPlayerEntity.x.HALF_U.HI;
            gRoomTransition.player_status.overworld_map_y = gPlayerEntity.y.HALF_U.HI;
        } else if (AreaIsDungeon()) {
            gRoomTransition.player_status.dungeon_map_x = gPlayerEntity.x.HALF.HI;
            gRoomTransition.player_status.dungeon_map_y = gPlayerEntity.y.HALF.HI;
        }
    }
}

void SetWorldMapPos(u32 area, u32 room, u32 x, u32 y) {
    RoomHeader* hdr = gAreaRoomHeaders[area] + room;
    gRoomTransition.player_status.overworld_map_x = hdr->map_x + x;
    gRoomTransition.player_status.overworld_map_y = hdr->map_y + y;
}

void SetDungeonMapPos(u32 area, u32 room, u32 x, u32 y) {
    RoomHeader* hdr = gAreaRoomHeaders[area] + room;
    gRoomTransition.player_status.dungeon_map_x = hdr->map_x + x;
    gRoomTransition.player_status.dungeon_map_y = hdr->map_y + y;
}

void InitRoom(void) {
    AreaHeader* a_hdr = NULL;

    MemClear(&gArea, sizeof gArea);
    a_hdr = &gAreaMetadata[gRoomControls.area];
    gArea.areaMetadata = a_hdr->flags;
    gArea.locationIndex = a_hdr->location;
    gArea.dungeon_idx = a_hdr->location - 23;
    gArea.localFlagOffset = gLocalFlagBanks[a_hdr->flag_bank];
    gArea.filler[0] = a_hdr->flag_bank;
    gArea.unk1A = 180;
    gArea.lightLevel = 256;
    InitRoomTransition();
    InitAllRoomResInfo();
}

u32 GetFlagBankOffset(u32 idx) {
    AreaHeader* a_hdr = &gAreaMetadata[idx];
    return gLocalFlagBanks[a_hdr->flag_bank];
}

void RegisterTransitionManager(void* mgr, void (*onEnter)(), void (*onExit)()) {
    if (gMain.substate != GAMEMAIN_SUBTASK) {
        gArea.transitionManager = mgr;
        gArea.onEnter = onEnter;
        gArea.onExit = onExit;
    }
}

static void InitAllRoomResInfo(void) {
    RoomHeader* r_hdr = gAreaRoomHeaders[gRoomControls.area];
    RoomResInfo* info = gArea.roomResInfos;
    u32 i;
    for (i = 0; i < MAX_ROOMS && *(u16*)r_hdr != 0xFFFF; i++, r_hdr++) {
        if (r_hdr->tileset_id != 0xFFFF)
            InitRoomResInfo(info, r_hdr, gRoomControls.area, i);
        info++;
    }
    gArea.pCurrentRoomInfo = GetCurrentRoomInfo();
}

static void InitRoomResInfo(RoomResInfo* info, RoomHeader* r_hdr, u32 area, u32 room) {
    info->map_x = r_hdr->map_x;
    info->map_y = r_hdr->map_y;
    info->pixel_width = r_hdr->pixel_width;
    info->pixel_height = r_hdr->pixel_height;
    info->tileset = *(gAreaTilesets[area] + r_hdr->tileset_id);
    info->map = *(gAreaRoomMaps[area] + room);
    info->metatiles = gAreaMetatiles[area];
    info->bg_anim = gUnk_080B755C[area];
    info->exits = *(gExitLists[area] + room);
    if (gAreaTable[area] != NULL) {
        info->properties = *(gAreaTable[area] + room);
    }
}

RoomResInfo* GetCurrentRoomInfo(void) {
    return &gArea.roomResInfos[gRoomControls.room];
}

void sub_08052EA0(void) {
    MemClear(&gRoomVars, sizeof gRoomVars);
    gRoomVars.unk_10[0] = -1;
    gRoomVars.unk_10[1] = gRoomVars.unk_10[0];
    gRoomVars.unk_10[2] = gRoomVars.unk_10[0];
    gRoomVars.unk_10[3] = gRoomVars.unk_10[0];
    gRoomVars.lightLevel = 256;
    gArea.locationIndex = gAreaMetadata[gRoomControls.area].location;
    UpdateRoomTracker();
    InitScriptData();
    sub_08054524();
    sub_080186D4();
    sub_0806F364();
    UpdateGlobalProgress();
}

static u32 sub_08052EF4(s32 idx) {
    AreaHeader* a_hdr = NULL;
    u32 i = idx < 0 ? gRoomControls.area : idx;
    a_hdr = &gAreaMetadata[i];
    return gLocalFlagBanks[a_hdr->flag_bank];
}

/**
 * @brief If enabled, this type of transition does not change the room
 * and keeps all entities.
 */
static void UpdateFakeScroll(void) {
    u32 x, y;
    LinkedList* ll;
    Entity* e;

    if (gArea.unk_0c_0 == 0 || !gRoomVars.field_0x0)
        return;

    y = 0;
    x = 0;

    // WTF?
    switch (gRoomControls.scroll_direction) {
        case 0:
            y = gArea.pCurrentRoomInfo->pixel_height;
        case 1:
            y = gArea.pCurrentRoomInfo->pixel_height;
        case 2:
            y = gArea.pCurrentRoomInfo->pixel_height;
        case 3:
            x = gArea.pCurrentRoomInfo->pixel_width;
    }

    gArea.pCurrentRoomInfo->map_x += x;
    gArea.pCurrentRoomInfo->map_y += y;
    gRoomControls.origin_x += x;
    gRoomControls.origin_y += y;
    gRoomControls.scroll_x += x;
    gRoomControls.scroll_y += y;

    ll = gEntityLists;
    do {
        for (e = ll->first; e != (Entity*)ll; e = e->next) {
            if (e->kind != MANAGER) {
                e->x.HALF.HI += x;
                e->y.HALF.HI += y;
            }
        }
    } while (++ll < gEntityLists + 9);
}

void LoadAuxiliaryRoom(u32 area, u32 room) {
    sub_08052FF4(area, room);
    gRoomControls.camera_target = NULL;
    CloneMapData();
    sub_0807C740();
}

void sub_08052FF4(u32 area, u32 room) {
    RoomHeader* r_hdr = NULL;

    ClearTilemaps();
    SetBGDefaults();
    gRoomControls.area = area;
    gRoomControls.room = room;
    gScreen.lcd.displayControl = 0x1740;
    MemClear(&gArea.currentRoomInfo, sizeof gArea.currentRoomInfo);
    gArea.pCurrentRoomInfo = &gArea.currentRoomInfo;
    r_hdr = gAreaRoomHeaders[area] + room;
    gArea.currentRoomInfo.map_x = r_hdr->map_x;
    gArea.currentRoomInfo.map_y = r_hdr->map_y;
    gArea.currentRoomInfo.pixel_width = r_hdr->pixel_width;
    gArea.currentRoomInfo.pixel_height = r_hdr->pixel_height;
    gArea.currentRoomInfo.tileset = *(gAreaTilesets[area] + r_hdr->tileset_id);
    gArea.currentRoomInfo.map = *(gAreaRoomMaps[area] + room);
    gArea.currentRoomInfo.metatiles = gAreaMetatiles[area];
    gArea.currentRoomInfo.bg_anim = gUnk_080B755C[area];
}

void ChangeLightLevel(s32 lightLevel) {
    lightLevel += gRoomVars.lightLevel;
    if (lightLevel < 0)
        lightLevel = 0;
    if (lightLevel > 256)
        lightLevel = 256;
    gRoomVars.lightLevel = lightLevel;
}

static void sub_080530B0(void) {
    static const u16 sMinecartData[] = { 0x189, 0x0, 0x102, 0x4, 0x1af, 0x0, 0x204, 0x0,
                                         0x1cf, 0x0, 0x10,  0x4, 0x0,   0x0, 0x0,   0x0 };

    MemCopy(sMinecartData, gRoomTransition.minecart_data, sizeof sMinecartData);
}

void UpdateGlobalProgress(void) {
    u8 pcnt = 1;
    if (CheckLocalFlagByBank(FLAG_BANK_3, SEIIKI_STAINED_GLASS)) {
        pcnt = 9;
    } else if (CheckGlobalFlag(LV5_CLEAR)) {
        pcnt = 8;
    } else if (CheckLocalFlagByBank(FLAG_BANK_3, OUBO_KAKERA)) {
        pcnt = 7;
    } else if (CheckGlobalFlag(LV4_CLEAR)) {
        pcnt = 6;
    } else if (CheckGlobalFlag(LV3_CLEAR)) {
        pcnt = 5;
    } else if (CheckLocalFlagByBank(FLAG_BANK_1, SOUGEN_08_TORITSUKI)) {
        pcnt = 4;
    } else if (CheckGlobalFlag(LV1_CLEAR)) {
        pcnt = 2;
    }
    gSave.global_progress = pcnt;
}

static u32 sub_08053144(void) {
    u32 ret;

    if (CheckGlobalFlag(ENDING))
        return 0;
    ret = 0;
    if (gArea.locationIndex != 0)
        ret = !!(gRoomTransition.player_status.field_0x24[10] ^ gArea.locationIndex);
    return ret;
}

static void CheckAreaDiscovery(void) {
    if (!sub_08053144())
        return;

    gRoomTransition.player_status.field_0x24[10] = gArea.locationIndex;

    if (!CheckGlobalFlag(TABIDACHI))
        return;

    if (!CheckGlobalFlag(ENDING)) {
        Entity* e = (Entity*)GetEmptyManager();
        if (e != NULL) {
            e->kind = MANAGER;
            e->id = 0x39;
            AppendEntityToList(e, 8);
            if (!gRoomVars.field_0x0 && !ReadBit(gSave.areaVisitFlags, gArea.locationIndex)) {
                e->type2 = 1;
                SetPlayerControl(3);
                SetInitializationPriority();
            }
        }
    }
    WriteBit(gSave.areaVisitFlags, gArea.locationIndex);
}

static void UpdatePlayerRoomStatus(void) {
    gPlayerState.startPosX = gPlayerEntity.x.HALF.HI;
    gPlayerState.startPosY = gPlayerEntity.y.HALF.HI;
    if (sub_08053144()) {
        MemCopy(&gRoomTransition.player_status, &gSave.saved_status, sizeof gRoomTransition.player_status);
        if (AreaIsDungeon()) {
            gRoomTransition.player_status.dungeon_area = gRoomControls.area;
            gRoomTransition.player_status.dungeon_room = gRoomControls.room;
            gRoomTransition.player_status.dungeon_x = gPlayerEntity.x.HALF.HI;
            gRoomTransition.player_status.dungeon_y = gPlayerEntity.y.HALF.HI;
        }
    }
}

void sub_08053250(void) {
    gRoomTransition.player_status.spawn_type = PL_SPAWN_DEFAULT;
    gRoomTransition.player_status.start_pos_x = gPlayerEntity.x.HALF.HI - gRoomControls.origin_x;
    gRoomTransition.player_status.start_pos_y = gPlayerEntity.y.HALF.HI - gRoomControls.origin_y;
    gRoomTransition.player_status.start_anim = gPlayerEntity.animationState;
    gRoomTransition.player_status.layer = gPlayerEntity.collisionLayer;
    gRoomTransition.player_status.area_next = gRoomControls.area;
    gRoomTransition.player_status.room_next = gRoomControls.room;
    MemCopy(&gRoomTransition.player_status, &gSave.saved_status, sizeof gRoomTransition.player_status);
}

static void sub_0805329C(void) {
    if (sub_08053144()) {
        switch (gRoomControls.area) {
            case AREA_DEEPWOOD_SHRINE:
                gSave.unk7 = 0;
                break;
            case AREA_CAVE_OF_FLAMES:
                sub_080530B0();
                break;
            case AREA_OUTER_FORTRESS_OF_WINDS:
                sub_080532E4();
                break;
            default:
                sub_08053460();
                break;
        }
    }
}

static void sub_080532E4(void) {
    s32 x, y;

    RoomHeader* r_hdr = gAreaRoomHeaders[AREA_FORTRESS_OF_WINDS] + 33;

    gRoomTransition.player_status.dungeon_area = AREA_FORTRESS_OF_WINDS;
    gRoomTransition.player_status.dungeon_room = 33;

    gRoomTransition.player_status.dungeon_x = r_hdr->map_x + r_hdr->pixel_width / 2;
    gRoomTransition.player_status.dungeon_map_x = gRoomTransition.player_status.dungeon_x;
    gRoomTransition.player_status.dungeon_y = r_hdr->map_y + r_hdr->pixel_height + 0xa0;
    gRoomTransition.player_status.dungeon_map_y = gRoomTransition.player_status.dungeon_y;
}

void LoadGfxGroups(void) {
    MemClear(&gBG0Buffer, sizeof gBG0Buffer);
    MemClear(&gBG1Buffer, sizeof gBG1Buffer);
    MemClear(&gBG2Buffer, sizeof gBG2Buffer);
    MemClear(&gBG3Buffer, sizeof gBG3Buffer);
    LoadGfxGroup(16);
    LoadGfxGroup(23);
    if (gRoomControls.area == AREA_CASTOR_WILDS)
        LoadGfxGroup(26);
    LoadItemGfx();
    LoadPaletteGroup(11);
    LoadPaletteGroup(12);
    SetColor(0, 0);
}

void LoadItemGfx(void) {
    LoadGfxGroup(GetInventoryValue(ITEM_REMOTE_BOMBS) ? 24 : 25);
    if (GetInventoryValue(ITEM_LIGHT_ARROW))
        LoadGfxGroup(29);
    LoadGfxGroup(GetInventoryValue(ITEM_MAGIC_BOOMERANG) ? 28 : 27);
}

void sub_080533CC(void) {
    u16* p1 = gUnk_020178E0;
    u16* p2 = gUnk_020178E0 - 0x100;
    *p2++ = *p1++;
    *p2++ = *p1++;
    *p2++ = *p1++;
    *p2++ = *p1++;
    *p2++ = *p1++;
    gUsedPalettes |= 8;
}

static void sub_0805340C(void) {
    static void (*const sHandlers[])(u32*) = {
        sub_08053434, DummyHandler, sub_080534E4, DummyHandler, DummyHandler, DummyHandler, DummyHandler, DummyHandler,
    };

    u32* p;
    u32 i;

    p = gSave.timers;
    for (i = 0; i < 8; i++, p++) {
        (sHandlers[i])(p);
    }
}

static void DummyHandler(u32* a1) {
}

static void sub_08053434(u32* a1) {
    if (gArea.locationIndex == 29 && *a1) {
        if (!--*a1) {
            sub_08053460();
            MenuFadeIn(5, 6);
        }
    }
}

static void sub_08053460(void) {
    static const u16 sClearFlags[] = { FLAG_BANK_10, LV6_GUFUU1_GISHIKI, FLAG_BANK_10, LV6_GUFUU1_DEMO,
                                       FLAG_BANK_10, LV6_ZELDA_DISCURSE, FLAG_BANK_10, LV6_00_ESCAPE,
                                       FLAG_BANK_10, LV6_GUFUU2_DEAD,    FLAG_BANK_G,  ENDING,
                                       FLAG_BANK_10, LV6_KANE_START,     FLAG_BANK_10, LV6_KANE_1ST,
                                       FLAG_BANK_10, LV6_KANE_2ND,       FLAG_BANK_10, LV6_SOTO_ENDING,
                                       0xFFFF };

    gSave.timers[0] = 0;
    if (CheckLocalFlagByBank(FLAG_BANK_10, LV6_ZELDA_DISCURSE))
        ClearGlobalFlag(ZELDA_CHASE);
    ClearFlagArray(sClearFlags);
}

void sub_08053494(void) {
    gSave.timers[0] = 10800;
}

void sub_080534AC(void) {
    if (CheckLocalFlagByBank(FLAG_BANK_10, LV6_KANE_START)) {
        ClearLocalFlagByBank(FLAG_BANK_10, LV6_KANE_START);
        gSave.timers[0] = 0;
        SoundReq(SONG_STOP_BGM);
    }
}

static void sub_080534E4(u32* a1) {
    if (gRoomControls.area != AREA_VEIL_FALLS_TOP) {
        if (*a1)
            --*a1;
    }
}

void InitBiggoronTimer(void) {
    gSave.timers[2] = 36000;
}

static void ResetTmpFlags(void) {
    static const u16 sClearFlags[] = { FLAG_BANK_2, BILL00_SHICHOU_00, FLAG_BANK_2, BILL0A_YADO_TAKARA_00,
                                       FLAG_BANK_2, BILL0C_SCHOOLR_00, FLAG_BANK_1, MACHI00_00,
                                       FLAG_BANK_1, MACHI00_02,        FLAG_BANK_2, MHOUSE06_00,
                                       FLAG_BANK_2, MHOUSE14_00,       FLAG_BANK_2, MHOUSE2_00_02,
                                       FLAG_BANK_2, MHOUSE2_01_T0,     FLAG_BANK_2, MHOUSE2_02_KAME,
                                       FLAG_BANK_2, SHOP00_ITEM_01,    FLAG_BANK_2, SHOP01_CAFE_01,
                                       0xFFFF };

    sub_08053460();
    ClearFlagArray(sClearFlags);

    if (!CheckGlobalFlag(WATERBEAN_PUT))
        ClearGlobalFlag(WATERBEAN_OUT);
    if (!GetInventoryValue(ITEM_EARTH_ELEMENT))
        ClearGlobalFlag(LV1_CLEAR);
    if (!GetInventoryValue(ITEM_FIRE_ELEMENT))
        ClearGlobalFlag(LV2_CLEAR);
    if (!GetInventoryValue(ITEM_WATER_ELEMENT))
        ClearGlobalFlag(LV4_CLEAR);
}

static void ClearFlagArray(const u16* p) {
    const u16* i;

    for (i = p; i[0] != 0xFFFF; i += 2)
        ClearLocalFlagByBank(i[0], i[1]);
}

void CutsceneMain_Init(void) {
    gUnk_080FCBB4[gMenu.overlayType]();
}

void sub_080535AC(void) {
    gMenu.overlayType = 1;
    gMenu.transitionTimer = 0x78;
    gUI.field_0x6 = 1;
    gUpdateVisibleTiles = 1;
    gScreen.lcd.displayControl &= 0xfeff;
    LoadRoomEntityList((EntityData*)gUnk_080FCB94);
    SetFade(6, 8);
}

void sub_080535F4(void) {
    if (gFadeControl.active == 0) {
        ResetSystemPriority();
        gMenu.overlayType = 2;
    }
}

void nullsub_481(void) {
}

void sub_08053618(void) {
    gMenu.transitionTimer--;
    if (gMenu.transitionTimer == 0) {
        sub_08052004();
    }
}

void sub_08053634(void) {
    gUI.nextToLoad = 3;
    MessageInitialize();
}

void sub_08053648(void) {
    Entity* obj = CreateObject(SMOKE, 0, 0);
    if (obj != NULL) {
        obj->x.HALF.HI = gRoomControls.origin_x + 0x2d0;
        obj->y.HALF.HI = gRoomControls.origin_y + 0x148;
    }
}

void sub_0805367C(void) {
    gMenu.overlayType++;
}

void sub_0805368C(void) {
    Entity* entity = FindEntityByID(OBJECT, HOUSE_DOOR_INT, 6);
    if (entity != NULL) {
        DeleteEntity(entity);
        SoundReq(SFX_F0);
    }
}

void sub_080536A8(void) {
    sub_080A71C4(5, 5, 4, 0x10);
}

void sub_080536B8(void) {
    sub_080A71C4(5, 3, 4, 4);
    SetFade(5, 0x100);
}

void sub_080536D4(void) {
    gUnk_080FCCFC[gMenu.overlayType]();
}

void sub_080536F0(void) {
    gUnk_080FCD38[gMenu.overlayType]();
}

void sub_0805370C(void) {
    gMenu.overlayType++;
    gUpdateVisibleTiles = 1;
    sub_08051FF0();
    LoadRoomEntityList((EntityData*)gUnk_080FCC54);
    SetFade(4, 0x10);
}

void sub_0805373C(void) {
    gUnk_080FCD40[gMenu.overlayType]();
}

ASM_FUNC("asm/non_matching/game/sub_08053758.inc", void sub_08053758())

ASM_FUNC("asm/non_matching/game/sub_08053800.inc", void sub_08053800())

ASM_FUNC("asm/non_matching/game/sub_08053894.inc", void sub_08053894())

ASM_FUNC("asm/non_matching/game/sub_08053904.inc", void sub_08053904())

void sub_08053974(void) {
    if (gFadeControl.active == 0) {
        InitFade();
        DispReset(1);
        SetBGDefaults();
        sub_08051F78();
        LoadRoomEntityList((EntityData*)&gUnk_080FCBC4);
        SetFade(5, 0x100);
        gMenu.overlayType++;
    }
}

void nullsub_482(void) {
}

void sub_080539BC(void) {
    SetBGDefaults();
    DeleteAllEntities();
    sub_08051F9C(0x22, 0x11, 0, 0);
    sub_0804B0B0(0x22, 0x11);
    LoadRoomEntityList((EntityData*)&gUnk_080FCC14);
    gMenu.overlayType++;
}

void sub_080539F4(void) {
    if (gFadeControl.active == 0) {
        DispReset(1);
        gMenu.overlayType++;
        gMenu.transitionTimer = 0x3c;
    }
}

void sub_08053A1C(void) {
    gMenu.transitionTimer--;
    if (gMenu.transitionTimer == 0) {
        gMenu.overlayType++;
        gMenu.transitionTimer = 8;
        MessageFromTarget(0xf07);
        gMessage.textWindowPosX = 1;
        gMessage.textWindowPosY = 8;
        SetFade(4, 8);
    }
}

void sub_08053A5C(void) {
    if (((gMessage.doTextBox & 0x7f) == 0) && --gMenu.transitionTimer == 0) {
        gMenu.overlayType++;
        SetFade(5, 8);
    }
}

void sub_08053A90(void) {
    if (gFadeControl.active == 0) {
        gUI.nextToLoad = 3;
        SetBGDefaults();
    }
}

void sub_08053AB0(void) {
    gUnk_080FCDD4[gMenu.overlayType]();
}

void sub_08053ACC(void) {
    gMenu.overlayType++;
    gUpdateVisibleTiles = 1;
    sub_08051FF0();
    LoadRoomEntityList((EntityData*)&gUnk_080FCD84);
    SetFade(5, 0x100);
}

void sub_08053B00(void) {
    gMenu.overlayType++;
}

void sub_08053B10(void) {
    if (CheckRoomFlag(1)) {
        gMenu.menuType++;
        DispReset(1);
        SetFade(4, 0x100);
    }
}

void sub_08053B3C(void) {
    sub_080A71C4(5, 4, 5, 0x100);
    SetFade(5, 0x100);
}

void sub_08053B58(void) {
    gUnk_080FCEB0[gMenu.overlayType]();
}

void sub_08053B74(void) {
    gMenu.overlayType++;
    gUpdateVisibleTiles = 1;
    LoadRoomEntityList((EntityData*)&gUnk_080FCDE0);
    ResetSystemPriority();
    ResetEntityPriority();
    SetFade(5, 0x100);
}

void sub_08053BAC(void) {
    gMenu.overlayType++;
}

void sub_08053BBC(void) {
    if (CheckRoomFlag(0)) {
        gMenu.menuType++;
        DispReset(1);
        SetFade(4, 0x100);
    }
}

void sub_08053BE8(void) {
    sub_080A71C4(5, 2, 5, 0x100);
    SetFade(5, 0x100);
}

void sub_08053C04(void) {
    gUnk_080FCEEC[gMenu.overlayType]();
}

void sub_08053C20(void) {
    gMenu.overlayType = 1;
    gMenu.transitionTimer = 0x78;
    gUpdateVisibleTiles = 1;
    sub_08051FF0();
    sub_0805B4D0(4);
    LoadRoomEntityList((EntityData*)&gUnk_080FCEBC);
    SetFade(4, 0x10);
    SoundReq(BGM_FIGHT_THEME2);
}

void nullsub_483(void) {
}

void sub_08053C60(void) {
    SetFade(5, 2);
    SoundReq(SFX_SUMMON);
    SoundReq(SONG_STOP_BGM);
    SetTask(3);
}

void sub_08053C84(void) {
    gMenu.overlayType = 2;
}

void CutsceneMain_Exit(void) {
    gUnk_080FCEF8[gMenu.field_0x3]();
}

void sub_08053CAC(void) {
    gUnk_080FCFA4[gMenu.overlayType]();
}

ASM_FUNC("asm/non_matching/game/sub_08053CC8.inc", void sub_08053CC8())

void sub_08053D34(void) {
    if (gMenu.field_0xa != 0) {
        gMenu.field_0xa = 0;
        if (CheckLocalFlagByBank(0x680, 0x3d)) {
            SetTileType(0x74, 0xc4, 1);
        }
        if (CheckLocalFlagByBank(0x680, 0x3e)) {
            SetTileType(0x74, 0xcc, 1);
        }
    }
    if (gFadeControl.active == 0) {
        gMenu.transitionTimer = 0x78;
        gMenu.overlayType++;
    }
}

void sub_08053D90(void) {
    gMenu.transitionTimer--;
    if (gMenu.transitionTimer == 0) {
        gMenu.overlayType++;
        SetFadeInverted(0x10);
    }
}

void sub_08053DB4(void) {
    if (gFadeControl.active == 0) {
        u8* ptr = gMenu.field_0xc;
        sub_08052FF4(gMenu.field_0xc[4], gMenu.field_0xc[5]);
        sub_0807C740();
        gUpdateVisibleTiles = 1;
        gRoomControls.scroll_x = (s8)ptr[6] + gRoomControls.scroll_x;
        gRoomControls.scroll_y = (s8)ptr[7] + gRoomControls.scroll_y;
        LoadRoomEntityList(*(EntityData**)ptr);
        LoadRoomEntityList((EntityData*)GetRoomProperty(ptr[4], ptr[5], 1));
        LoadRoomEntityList((EntityData*)GetRoomProperty(ptr[4], ptr[5], 2));
        gMenu.transitionTimer = 0x78;
        gMenu.overlayType++;
        SetFadeInverted(0x10);
    }
}

void sub_08053E30(void) {
    if ((gFadeControl.active == 0) && --gMenu.transitionTimer == 0) {
        gMenu.menuType++;
    }
}

void sub_08053E58(void) {
    gUnk_080FD108[gMenu.overlayType]();
}

void sub_08053E74(void) {
    const EntityData** ptr = gUnk_080FCFB8;
    gMenu.field_0xc = (u8*)ptr;
    LoadRoomEntityList((EntityData*)ptr[0]);
    gMenu.transitionTimer = 0x78;
    gMenu.field_0xa = 0x3c;
    gMenu.overlayType++;
    gScreen.lcd.displayControl &= 0xfeff;
    gUpdateVisibleTiles = 1;
    SetMinPriority(1);
    SetFade(4, 8);
}

void sub_08053EC4(void) {
    EntityData** data;
    if ((gFadeControl.active == 0) && (--gMenu.field_0xa == 0)) {
        data = (EntityData**)(gMenu.field_0xc + 4);
        gMenu.field_0xc += 0xc;
        LoadRoomEntityList(*data);
        gMenu.overlayType++;
    }
}

void sub_08053EFC(void) {
    if (--gMenu.transitionTimer == 0) {
        gMenu.overlayType++;
        SetFadeInverted(8);
    }
}

void sub_08053F20(void) {
    u8* ptr;
    if (gFadeControl.active == 0) {
        DeleteAllEntities();
        ptr = gMenu.field_0xc;
        sub_08052FF4(gMenu.field_0xc[8], gMenu.field_0xc[9]);
        sub_0807C740();
        gUpdateVisibleTiles = 1;
        gRoomControls.scroll_x = (s8)ptr[10] + gRoomControls.scroll_x;
        gRoomControls.scroll_y = (s8)ptr[0xb] + gRoomControls.scroll_y;
        LoadRoomEntityList(*(EntityData**)ptr);
        gMenu.transitionTimer = 0x78;
        gMenu.field_0xa = 0x3c;
        gMenu.overlayType++;
        SetFadeInverted(8);
    }
}

void sub_08053F88(void) {
    if ((gFadeControl.active == 0) && --gMenu.transitionTimer == 0) {
        gMenu.menuType++;
        ResetEntityPriority();
    }
}

void CutsceneMain_Update(void) {
    gUnk_080FD138[gMenu.field_0x3]();
}

u32 GetSaleItemConfirmMessageID(u32 item) {
    const u16* ptr = &gUnk_080FD964[item * 4];
    return ptr[2];
}

s32 GetItemPrice(u32 item) {
    const u16* ptr = &gUnk_080FD964[item * 4];
    return ptr[0];
}

ASM_FUNC("asm/non_matching/game/GiveItem.inc", u32 GiveItem(u32 a, u32 b))

// TODO Adapt ItemMetaData struct
void sub_08054288(s32 param_1) {
    const ItemMetaData* ptr1 = gItemMetaData;
    u16* ptr = (u16*)&((ptr1)[param_1]);
    MessageFromTarget(ptr[3]);
}

u32 sub_0805429C(s32 param_1) {
    const ItemMetaData* ptr1 = gItemMetaData;
    u16* ptr = (u16*)&((ptr1)[param_1]);
    return ptr[2];
}

u32 sub_080542AC(u32 param_1) {
    const ItemMetaData* ptr1 = gItemMetaData;
    u8* ptr = (u8*)&((ptr1)[param_1]);
    return ptr[3] & 1;
}

void ModShells(s32 shells) {
    if (shells == 0) {
        shells = 1;
    }
    shells += gSave.stats.shells;
    if (shells < 0) {
        shells = 0;
    } else if (999 < shells) {
        shells = 999;
    }
    gSave.stats.shells = shells;
}

void ModBombs(s32 bombs) {
    s32 bombCount = bombs;
    s32 bagSize;
    bombCount += gSave.stats.bombCount;
    bagSize = gBombBagSizes[gSave.stats.bombBagType];
    if (bombCount < 0) {
        bombCount = 0;
    } else if (bagSize < bombCount) {
        bombCount = bagSize;
    }
    gSave.stats.bombCount = bombCount;
}

void ModArrows(s32 arrows) {
    s32 quiverSize;
    s32 arrowCount = arrows;
    arrowCount += gSave.stats.arrowCount;
    quiverSize = gQuiverSizes[gSave.stats.quiverType];
    if (arrowCount < 0) {
        arrowCount = 0;
    } else if (quiverSize < arrowCount) {
        arrowCount = quiverSize;
    }
    gSave.stats.arrowCount = arrowCount;
}

/**
 * @brief Returns the slot the item is equipped in.
 *
 * 0: A
 * 1: B
 * 2: Not equipped
 */
u32 IsItemEquipped(u32 itemID) {
    u32 itemSlot;

    if (itemID == gSave.stats.itemButtons[SLOT_A])
        itemSlot = 0;
    else if (itemID == gSave.stats.itemButtons[SLOT_B])
        itemSlot = 1;
    else
        itemSlot = 2;
    return itemSlot;
}

void PutItemOnSlot(u32 itemID) {
    u32 itemSlot;
    u32 itemID2 = itemID;
    if (itemID2 < 0x47) {
        SetInventoryValue(0, 1);
    }
    if (itemID2 - 1 < 0x1f) {
        itemSlot = 2;
        if (gSave.stats.itemButtons[SLOT_A] == 0) {
            itemSlot = 0;
        } else if (gSave.stats.itemButtons[SLOT_B] == 0) {
            itemSlot = 1;
        }
        if (itemSlot == 2) {
            u32 temp = gItemMetaData[itemID2].menuSlot;
            if (temp == gItemMetaData[gSave.stats.itemButtons[SLOT_A]].menuSlot) {
                itemSlot = 0;
            } else {
                if (temp == gItemMetaData[gSave.stats.itemButtons[SLOT_B]].menuSlot) {
                    itemSlot = 1;
                }
            }
            if (itemSlot == 2) {
                return;
            }
        }
        ForceEquipItem(itemID2, itemSlot);
    }
}

void ForceEquipItem(u32 itemID, u32 itemSlot) {
    u32 otherItem;
    u32 otherItemIndex;
    u32 replacedItem;

    if ((itemID - 1 < 0x1f) && (itemSlot < 2)) {
        otherItemIndex = itemSlot == 0;
        replacedItem = gSave.stats.itemButtons[itemSlot];
        otherItem = gSave.stats.itemButtons[otherItemIndex];
        if (gItemMetaData[otherItem].menuSlot == gItemMetaData[itemID].menuSlot) {
            otherItem = replacedItem;
        }
        gSave.stats.itemButtons[itemSlot] = itemID;
        gSave.stats.itemButtons[otherItemIndex] = otherItem;
        gUnk_0200AF00.filler0[0x13] = 0x7f;
        gUnk_0200AF00.filler0[0x14] = 0x7f;
    }
}

u32 SetBottleContents(u32 itemID, u32 bottleIndex) {
    if (bottleIndex > 3) {
        bottleIndex = 0;
        if (gSave.stats.bottles[0] != 0x20) {
            do {
                bottleIndex++;
                if (bottleIndex > 3) {
                    return bottleIndex;
                }
            } while (gSave.stats.bottles[bottleIndex] != 0x20);
        }
        if (bottleIndex > 3) {
            return bottleIndex;
        }
    }
    gSave.stats.bottles[bottleIndex] = itemID;
    return bottleIndex;
}

bool32 ItemIsSword(u32 item) {
    switch (item) {
        case ITEM_SMITH_SWORD:
        case ITEM_GREEN_SWORD:
        case ITEM_RED_SWORD:
        case ITEM_BLUE_SWORD:
        case ITEM_FOURSWORD:
            return TRUE;
        default:
            return FALSE;
    }
}

bool32 ItemIsShield(u32 id) {
    switch (id) {
        case 13:
        case 14:
            return 1;
        default:
            return 0;
    }
}

bool32 ItemIsBottle(u32 id) {
    switch (id) {
        case 28:
        case 29:
        case 30:
        case 31:
            return 1;
        default:
            return 0;
    }
}

u32 GetBottleContaining(u32 id) {
    if (id == gSave.stats.bottles[0]) {
        return 1;
    } else if (id == gSave.stats.bottles[1]) {
        return 2;
    } else if (id == gSave.stats.bottles[2]) {
        return 3;
    } else if (id == gSave.stats.bottles[3]) {
        return 4;
    } else {
        return 0;
    }
}

void sub_08054524(void) {
    u32 bVar1;

    bVar1 = gArea.locationIndex;
    if (gArea.locationIndex == 0) {
        bVar1 = gRoomTransition.player_status.field_0x24[0xa];
    }
    if (bVar1 > 0x16) {
        bVar1 = 0;
    }

    bVar1 = gUnk_080FE1C6[bVar1];
    MemCopy(&gAreaDroptables[bVar1], &gRoomVars.currentAreaDroptable, 0x20);
}

void sub_08054564(void) {
    gRoomVars.field_0x2 = 1;
}

void sub_08054570(void) {
    gRoomVars.field_0x2 = 0;
}

extern void sub_08000F14(s16*, const s16*, const s16*, const s16*);
extern u32 sub_08000F2C(s16*, const s16*, const s16*, const s16*);
u32 CreateItemDrop(Entity* arg0, u32 itemID, u32 itemParameter);
u32 CreateRandomItemDrop(Entity* arg0, u32 arg1) {
    extern const u8 gUnk_080FE1B4[] /* = {
         ITEM_NONE,         ITEM_RUPEE1,  ITEM_RUPEE5, ITEM_RUPEE20,        ITEM_HEART,         ITEM_FAIRY,
         ITEM_BOMBS5,       ITEM_ARROWS5, ITEM_SHELLS, ITEM_KINSTONE_GREEN, ITEM_KINSTONE_BLUE, ITEM_KINSTONE_RED,
         ITEM_ENEMY_BEETLE, ITEM_NONE,    ITEM_NONE,   ITEM_NONE,           ITEM_NONE,          ITEM_NONE,
     }*/;

    int r0, r1, rand, summOdds, item;
    u32 r3;
    const Droptable *ptr2, *ptr3, *ptr4;
    Droptable droptable;
    r3 = arg1;
    if (gRoomVars.field_0x2 != 1) {
        ptr2 = &gDroptableModifiers[DROPTABLE_NONE];
        ptr4 = NULL;
        switch (r3) {
            case 1 ... 12:
                ptr4 = &gEnemyDroptables[r3];
                break;
#ifndef EU
            case 24:
            case 25:
                r0 = gRoomVars.unk2;
                ptr4 = &gUnk_0800191C[0];
                if (r0) {
                    ptr4++;
                }
                break;
#endif
            case 16 ... 23:
#ifdef EU
            case 24:
#endif
                ptr2 = &gObjectDroptables[r3 - 16];
            case 15:
                ptr4 = &gRoomVars.currentAreaDroptable;
                break;
            case 0:
            default:
                break;
        }
        if (ptr4 != 0) {
            if ((r1 = gSave.stats.picolyteType) == 0) {
                // nop
                ptr3 = &gDroptableModifiers[DROPTABLE_NONE];
            } else {
#ifdef EU
                ptr3 = &gEnemyDroptables[r1 + 9];
#else
                ptr3 = &gEnemyDroptables[r1 + 6];
#endif
            }
            // vector addition, s0 = ptr4 + ptr2 + ptr3
            sub_08000F14(droptable.a, ptr4->a, ptr2->a, ptr3->a);
            if (gSave.stats.health <= 8) {
                droptable.s.hearts += 5;
            }
            if (gSave.stats.bombCount == 0) {
                droptable.s.bombs += 3;
            }
            if (gSave.stats.arrowCount == 0) {
                droptable.s.arrows += 3;
            }
            if (gSave.stats.rupees <= 10) {
                droptable.s.rupee5 += 1;
            }
            ptr2 = &gDroptableModifiers[DROPTABLE_NONE];
            r0 = gSave.stats.hasAllFigurines;
            ptr3 = &gDroptableModifiers[DROPTABLE_NONE];
            // don't drop shells anymore
            if (r0 != 0) {
                ptr2 = &gDroptableModifiers[DROPTABLE_NO_SHELLS];
            }
            // don't drop kinstones anymore
            if (gSave.didAllFusions != 0) {
                ptr3 = &gDroptableModifiers[DROPTABLE_NO_KINSTONES];
            }
            // vector addition, s0 = s0 + ptr2 + ptr3
            // resulting values are clamped to be >= 0
            // returns sum over s0
            summOdds = sub_08000F2C(droptable.a, droptable.a, ptr2->a, ptr3->a);
            rand = Random();
            item = (rand >> 0x18);
            item &= 0xF;
            rand = rand % summOdds;
            {
                u32 r3;
                for (r3 = 0, r1 = 0; r3 < 0x10; r3++, item = (item + 1) & 0xF) {
                    if ((r1 += droptable.a[item]) > rand) {
                        break;
                    }
                }
            }
            r1 = gUnk_080FE1B4[item];
            if (r1 != ITEM_NONE) {
                return CreateItemDrop(arg0, r1, 0);
            }
        }
    }
    return ITEM_NONE;
}

u32 CreateItemDrop(Entity* arg0, u32 itemID, u32 itemParameter) {
    u32 adjustedParam = itemParameter;
    Entity* itemEntity;

    switch (itemID) {
        case ITEM_ENEMY_BEETLE:
            if (!GetInventoryValue(ITEM_SMITH_SWORD)) {
                return ITEM_NONE;
            }
            break;
        case ITEM_BOMBS5:
            if (!GetInventoryValue(ITEM_BOMBBAG)) {
                return ITEM_NONE;
            }
            break;
        case ITEM_ARROWS5:
            if (!GetInventoryValue(ITEM_BOW)) {
                return ITEM_NONE;
            }
            break;
        case ITEM_SHELLS: {
            if (!GetInventoryValue(ITEM_EARTH_ELEMENT)) {
                return ITEM_NONE;
            }
            if (itemParameter == 0) {
                adjustedParam = 1;
            }
            break;
        }
        case ITEM_KINSTONE:
        case ITEM_KINSTONE_GREEN ... ITEM_KINSTONE_RED: {
            u32 rand;

            if (GetInventoryValue(ITEM_KINSTONE_BAG) == 0) {
                return ITEM_NONE;
            }
            if (3 < gRoomVars.filler1[0]) {
                return ITEM_NONE;
            }

            if (itemID != ITEM_KINSTONE) {
                adjustedParam = itemID - ITEM_KINSTONE_GREEN;
                rand = (Random() & 0x3f);
                adjustedParam = gUnk_080FE1DD[(rand + adjustedParam * 0x40)];
                if (adjustedParam == 0) {
                    itemID = ITEM_NONE;
                } else {
                    itemID = ITEM_KINSTONE;
                }
            }
            break;
        }
    }
    if (itemID != ITEM_NONE) {
        if (itemID != ITEM_ENEMY_BEETLE) {
            itemEntity = CreateObject(GROUND_ITEM, itemID, adjustedParam);
            if (itemEntity != NULL) {
                if (arg0 == &gPlayerEntity) {
                    itemEntity->actionDelay = 1;
                } else {
                    itemEntity->actionDelay = 0;
                }
                if (arg0->kind == OBJECT) {
                    if (arg0->id == 99) {
                        arg0->child = itemEntity;
                    } else if (arg0->id == 0x1e) {
                        itemEntity->direction = arg0->animationState << 3 | 0x80;
                        itemEntity->speed = 0xc0;
                        itemEntity->zVelocity = Q_16_16(1.5);
                    }
                }
                CopyPosition(arg0, itemEntity);
            }
        } else {
            itemEntity = CreateEnemy(BEETLE, 0);
            if (itemEntity != NULL) {
                itemEntity->x.HALF.HI = arg0->x.HALF.HI;
                itemEntity->y.HALF.HI = arg0->y.HALF.HI;
                itemEntity->collisionLayer = arg0->collisionLayer;
                UpdateSpriteForCollisionLayer(itemEntity);
            }
        }
    }
    return itemID;
}

void Subtask_WorldEvent(void) {
#if !(defined(DEMO_USA) || defined(DEMO_JP))
    gUnk_080FE2A0[gMenu.menuType]();
#endif
}

ASM_FUNC("asm/non_matching/game/sub_0805488C.inc", void sub_0805488C())

void sub_080548E8(void) {
    gUnk_080FE2AC[gMenu.field_0x0]();
    FlushSprites();
    UpdateEntities();
    DrawEntities();
    CopyOAM();
    UpdateScroll();
    UpdateBgAnim();
    UpdateScrollVram();
}

ASM_FUNC("asm/non_matching/game/sub_08054920.inc", void sub_08054920())

void sub_08054968(void) {
    gMenu.menuType = 2;
}

ASM_FUNC("asm/non_matching/game/sub_08054974.inc", void sub_08054974())

ASM_FUNC("asm/non_matching/game/sub_08054A14.inc", void sub_08054A14())
