/**
 * @file gameUtils.c
 *
 * @brief Game Utils
 */
#include "backgroundAnimations.h"
#include "enemy.h"
#include "entity.h"
#include "fade.h"
#include "fileselect.h"
#include "functions.h"
#include "game.h"
#include "item.h"
#include "itemMetaData.h"
#include "main.h"
#include "menu.h"
#include "npc.h"
#include "object.h"
#include "save.h"
#include "screen.h"
#include "script.h"
#include "sound.h"
#include "ui.h"

u32 StairsAreValid(void);
void ClearFlagArray(const u16*);
void DummyHandler(u32* a1);
void sub_08053434(u32* a1);
void sub_080534E4(u32* a1);
void InitAllRoomResInfo(void);
void InitRoomResInfo(RoomResInfo* info, RoomHeader* hdr, u32 area, u32 room);
void sub_080532E4(void);
void ResetTimerFlags(void);

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

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

extern u8 gUnk_0200AF14;

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,
        .charColor = 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;
    ShowTextBox(opt->_a, &font);
    gScreen.bg1.updated = fakematch;
}

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_ROOM_EXIT,
        [PL_SPAWN_SLEEPING] = PLAYER_SLEEP,
        [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(&gActiveItems, sizeof(gActiveItems));
    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].size) {
            newRupeeCount = gWalletSizes[s->walletType].size;
        }
    }
    s->rupees = newRupeeCount;
}

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

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

bool32 HasDungeonMap(void) {
    u32 tmp;

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

bool32 HasDungeonCompass(void) {
    u32 tmp;

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

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

bool32 HasDungeonSmallKey(void) {
    u32 tmp;

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

extern u8 gPaletteBufferBackup[];
void RestoreGameTask(u32 a1) {
    LoadGfxGroups();
#ifndef EU
    CleanUpGFXSlots();
#endif
    sub_080ADE24();
    InitUI(TRUE);
    sub_0801AE44(a1);
    MemCopy(gPaletteBufferBackup, gPaletteBuffer, 1024);
    gUsedPalettes = 0xffffffff;
}

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);
}

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

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

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

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(FADE_IN_OUT | FADE_INSTANT | FADE_MOSAIC, 8);
                break;
            case TRANSITION_CUT_FAST:
                SetFade(FADE_IN_OUT | FADE_INSTANT | FADE_MOSAIC, 3);
                break;
            case TRANSITION_FADE_WHITE_SLOW:
                SetFade(FADE_IN_OUT | FADE_BLACK_WHITE | FADE_INSTANT, 4);
                break;
            case TRANSITION_FADE_BLACK_SLOW:
                SetFade(FADE_IN_OUT | FADE_INSTANT, 4);
                break;
            case TRANSITION_FADE_BLACK:
                SetFade(FADE_IN_OUT | FADE_INSTANT, 16);
                break;
            case TRANSITION_FADE_BLACK_FAST:
                SetFade(FADE_IN_OUT | FADE_INSTANT, 256);
                break;
            case TRANSITION_7:
            case TRANSITION_FADE_WHITE_FAST:
                SetFade(FADE_IN_OUT | FADE_BLACK_WHITE | FADE_INSTANT, 256);
                break;
            default:
                SetFade(FADE_IN_OUT | FADE_BLACK_WHITE | FADE_INSTANT, 16);
                break;
        }
        RoomExitCallback();
        gMain.substate = GAMEMAIN_CHANGEAREA;
        *(&gMain.pauseInterval + 1) = 1;
        return TRUE;
    }
    return FALSE;
}

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;
}

void InitRoomTransition(void) {
    switch (gRoomTransition.type) {
        case TRANSITION_CUT:
            SetFade(FADE_INSTANT | FADE_MOSAIC, 8);
            break;
        case TRANSITION_CUT_FAST:
            SetFade(FADE_INSTANT | FADE_MOSAIC, 3);
            break;
        case TRANSITION_FADE_WHITE_SLOW:
            SetFade(FADE_BLACK_WHITE | FADE_INSTANT, 4);
            break;
        case TRANSITION_3:
            break;
        case TRANSITION_FADE_BLACK_FAST:
            SetFade(FADE_IN_OUT | FADE_INSTANT, 256);
            break;
        case TRANSITION_7:
            SetFade(FADE_IN_OUT | FADE_BLACK_WHITE | FADE_INSTANT, 256);
            break;
        case TRANSITION_FADE_BLACK:
            SetFade(FADE_INSTANT, 16);
            break;
        case TRANSITION_FADE_WHITE_FAST:
            SetFade(FADE_BLACK_WHITE | FADE_INSTANT, 8);
            break;
        default:
            SetFadeInverted(16);
            break;
    }
}

bool32 CanDispEzloMessage(void) {
    s32 tmp = PL_STATE_WALK;

    if (!(gInput.heldKeys & SELECT_BUTTON) || gPlayerState.controlMode != CONTROL_ENABLED ||
        gPauseMenuOptions.disabled || gUnk_0200AF00.unk_1)
        return FALSE;

    if ((gPlayerState.flags & (PL_NO_CAP | PL_CAPTURED | PL_DISABLE_ITEMS)) || (gPlayerState.framestate_last > tmp) ||
        gPlayerState.item || gPlayerEntity.field_0x7a.HWORD)
        return FALSE;

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

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

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)
void CreateMiscManager(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 = MISC_MANAGER;
    e->type = 15;
    AppendEntityToList(e, 0);
}
#endif

void DecreasePortalTimer(void) {
    if (gArea.portal_mode == 0)
        gArea.portal_timer = 0;
    if (gArea.portal_timer) {
        gArea.portal_timer--;
        gArea.portal_mode = 0;
    }
}

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.flag_bank = a_hdr->flag_bank;
    gArea.portal_timer = 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;
    }
}

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();
}

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 = (void*)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();
}

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.
 */
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();
    InitializeCamera();
}

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 = (void*)gUnk_080B755C[area];
}

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

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;
}

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;
}

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 = ENTER_ROOM_TEXTBOX_MANAGER;
            AppendEntityToList(e, 8);
            if (!gRoomVars.field_0x0 && !ReadBit(gSave.areaVisitFlags, gArea.locationIndex)) {
                e->type2 = 1;
                SetPlayerControl(3);
                SetInitializationPriority();
            }
        }
    }
    WriteBit(gSave.areaVisitFlags, gArea.locationIndex);
}

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);
}

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:
                ResetTimerFlags();
                break;
        }
    }
}

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);
}

extern u16 gUnk_020178E0[];
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;
}

void UpdateTimerCallbacks(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);
    }
}

void DummyHandler(u32* a1) {
}

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

void ResetTimerFlags(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 StartDarkNutTimer(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);
    }
}

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

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

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,
    };

    ResetTimerFlags();
    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);
}

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

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