#include "global.h"
#include "overworld.h"
#include "battle_setup.h"
#include "berry.h"
#include "cable_club.h"
#include "clock.h"
#include "constants/map_types.h"
#include "constants/maps.h"
#include "constants/songs.h"
#include "constants/species.h"
#include "contest_util.h"
#include "event_data.h"
#include "event_object_movement.h"
#include "field_camera.h"
#include "field_control_avatar.h"
#include "field_effect.h"
#include "field_fadetransition.h"
#include "field_message_box.h"
#include "field_player_avatar.h"
#include "field_screen_effect.h"
#include "field_special_scene.h"
#include "field_specials.h"
#include "field_tasks.h"
#include "field_weather.h"
#include "fieldmap.h"
#include "fldeff_flash.h"
#include "heal_location.h"
#include "link.h"
#include "load_save.h"
#include "m4a.h"
#include "main.h"
#include "map_name_popup.h"
#include "menu.h"
#include "metatile_behavior.h"
#include "new_game.h"
#include "palette.h"
#include "play_time.h"
#include "random.h"
#include "roamer.h"
#include "rotating_gate.h"
#include "safari_zone.h"
#include "scanline_effect.h"
#include "script.h"
#include "secret_base.h"
#include "sound.h"
#include "start_menu.h"
#include "task.h"
#include "tileset_anim.h"
#include "time_events.h"
#include "tv.h"
#include "wild_encounter.h"

#ifdef SAPPHIRE
#define LEGENDARY_MUSIC MUS_WEATHER_KYOGRE  // Heavy Rain
#else
#define LEGENDARY_MUSIC MUS_WEATHER_GROUDON // Drought
#endif

extern u8 gUnknown_020297ED;
extern u16 gTotalCameraPixelOffsetY;
extern u16 gTotalCameraPixelOffsetX;

extern u8 EventScript_WhiteOut[];
extern u8 EventScript_ResetMrBriney[];
extern u8 SingleBattleColosseum_EventScript_1A436F[];
extern u8 SingleBattleColosseum_EventScript_1A4379[];
extern u8 DoubleBattleColosseum_EventScript_1A4383[];
extern u8 DoubleBattleColosseum_EventScript_1A439E[];
extern u8 DoubleBattleColosseum_EventScript_1A43B9[];
extern u8 DoubleBattleColosseum_EventScript_1A43D4[];
extern u8 TradeCenter_EventScript_1A43F0[];
extern u8 TradeCenter_EventScript_1A43FA[];
extern u8 RecordCorner_EventScript_1A4418[];
extern u8 RecordCorner_EventScript_1A442D[];
extern u8 RecordCorner_EventScript_1A4442[];
extern u8 RecordCorner_EventScript_1A4457[];
extern u8 TradeRoom_ReadTrainerCard1[];
extern u8 TradeRoom_ReadTrainerCard2[];
extern u8 TradeRoom_TooBusyToNotice[];
extern u8 TradeRoom_PromptToCancelLink[];
extern u8 TradeRoom_TerminateLink[];
extern u8 gUnknown_081A4508[];

extern struct MapLayout * const gMapLayouts[];
extern struct MapHeader * const * const gMapGroups[];
extern s32 gMaxFlashLevel;

u16 word_3004858;
void (*gFieldCallback)(void);
u8 gUnknown_03004860;
u8 gFieldLinkPlayerCount;

EWRAM_DATA struct WarpData gLastUsedWarp = {0};
EWRAM_DATA struct WarpData gWarpDestination = {0};
EWRAM_DATA struct WarpData gFixedDiveWarp = {0};
EWRAM_DATA struct WarpData gFixedHoleWarp = {0};
EWRAM_DATA struct InitialPlayerAvatarState gInitialPlayerAvatarState = {0};
EWRAM_DATA static u16 sAmbientCrySpecies = 0;
EWRAM_DATA static bool8 sIsAmbientCryWaterMon = FALSE;
EWRAM_DATA struct LinkPlayerObjectEvent gLinkPlayerObjectEvents[4] = {0};

static u8 gUnknown_03000580[4];
static u16 (*gUnknown_03000584)(u32);
static u8 gUnknown_03000588;

static void CB2_Overworld(void);
static u8 GetAdjustedInitialTransitionFlags(struct InitialPlayerAvatarState*, u16, u8);
static u8 GetAdjustedInitialDirection(struct InitialPlayerAvatarState*, u8, u16, u8);
static bool32 sub_805483C(u8*);
static void c2_80567AC(void);
static void Overworld_ResetStateAfterWhiteOut(void);
static void InitOverworldGraphicsRegisters(void);
static void ChooseAmbientCrySpecies(void);
static void SetFieldVBlankCallback(void);
static void VBlankCB_Field(void);
static void SpawnLinkPlayerObjectEvent(u8, s16, s16, u8);
static void InitLinkPlayerObjectEventPos(struct ObjectEvent *, s16, s16);
static u8 GetLinkPlayerIdAt(s16, s16);
static u8 npc_something3(u8, u8);
static u8 LinkPlayerDetectCollision(u8, u8, s16, s16);
static void CreateLinkPlayerSprite(u8);
static void SpriteCB_LinkPlayer(struct Sprite *);
static u16 sub_8055758(const u8 *);
static void sub_8055808(const u8 *);
static void sub_8055840(const u8 *);

static const struct WarpData sDummyWarpData =
{
    .mapGroup = -1,
    .mapNum = -1,
    .warpId = -1,
    .x = -1,
    .y = -1,
};

static const u8 sUnusedData[] =
{
    0xB0, 0x04, 0x00, 0x00,
    0x10, 0x0E, 0x00, 0x00,
    0xB0, 0x04, 0x00, 0x00,
    0x60, 0x09, 0x00, 0x00,
    0x32, 0x00, 0x00, 0x00,
    0x50, 0x00, 0x00, 0x00,
    0xD4, 0xFF, 0xFF, 0xFF,
    0x2C, 0x00, 0x00, 0x00,
};

const struct UCoords32 gDirectionToVectors[] =
{
    { 0,  0}, // DIR_NONE
    { 0,  1}, // DIR_SOUTH
    { 0, -1}, // DIR_NORTH
    {-1,  0}, // DIR_WEST
    { 1,  0}, // DIR_EAST
    {-1,  1}, // DIR_SOUTHWEST
    { 1,  1}, // DIR_SOUTHEAST
    {-1, -1}, // DIR_NORTHWEST
    { 1, -1}, // DIR_NORTHEAST
};

static const struct ScanlineEffectParams sFlashEffectParams =
{
    (void *)REG_ADDR_WIN0H,
    ((DMA_ENABLE | DMA_START_HBLANK | DMA_REPEAT | DMA_DEST_RELOAD) << 16) | 1,
    1,
    0,
};

static u8 sub_8055C68(struct LinkPlayerObjectEvent *, struct ObjectEvent *, u8);
static u8 sub_8055C88(struct LinkPlayerObjectEvent *, struct ObjectEvent *, u8);
static u8 sub_8055C8C(struct LinkPlayerObjectEvent *, struct ObjectEvent *, u8);

static u8 (*const gUnknown_082166A0[])(struct LinkPlayerObjectEvent *, struct ObjectEvent *, u8) =
{
    sub_8055C68,
    sub_8055C88,
    sub_8055C8C,
};

static u8 sub_8055CAC(struct LinkPlayerObjectEvent *, struct ObjectEvent *, u8);
static u8 sub_8055CB0(struct LinkPlayerObjectEvent *, struct ObjectEvent *, u8);
static u8 sub_8055D18(struct LinkPlayerObjectEvent *, struct ObjectEvent *, u8);

static u8 (*const gUnknown_082166AC[])(struct LinkPlayerObjectEvent *, struct ObjectEvent *, u8) =
{
    sub_8055CAC,
    sub_8055CB0,
    sub_8055CB0,
    sub_8055CB0,
    sub_8055CB0,
    sub_8055CAC,
    sub_8055CAC,
    sub_8055D18,
    sub_8055D18,
    sub_8055D18,
    sub_8055D18,
};

static void sub_8055D30(struct LinkPlayerObjectEvent *, struct ObjectEvent *);
static void sub_8055D38(struct LinkPlayerObjectEvent *, struct ObjectEvent *);

static void (*const gUnknown_082166D8[])(struct LinkPlayerObjectEvent *, struct ObjectEvent *) =
{
    sub_8055D30,
    sub_8055D38,
};


static void DoWhiteOut(void)
{
    ScriptContext2_RunNewScript(EventScript_WhiteOut);
    gSaveBlock1.money /= 2;
    ScrSpecial_HealPlayerParty();
    Overworld_ResetStateAfterWhiteOut();
    Overworld_SetWarpDestToLastHealLoc();
    WarpIntoMap();
}

void Overworld_ResetStateAfterFly(void)
{
    ResetInitialPlayerAvatarState();
    FlagClear(FLAG_SYS_CYCLING_ROAD);
    FlagClear(FLAG_SYS_CRUISE_MODE);
    FlagClear(FLAG_SYS_SAFARI_MODE);
    FlagClear(FLAG_SYS_USE_STRENGTH);
    FlagClear(FLAG_SYS_USE_FLASH);
}

void Overworld_ResetStateAfterTeleport(void)
{
    ResetInitialPlayerAvatarState();
    FlagClear(FLAG_SYS_CYCLING_ROAD);
    FlagClear(FLAG_SYS_CRUISE_MODE);
    FlagClear(FLAG_SYS_SAFARI_MODE);
    FlagClear(FLAG_SYS_USE_STRENGTH);
    FlagClear(FLAG_SYS_USE_FLASH);
    ScriptContext2_RunNewScript(EventScript_ResetMrBriney);
}

void Overworld_ResetStateAfterDigEscRope(void)
{
    ResetInitialPlayerAvatarState();
    FlagClear(FLAG_SYS_CYCLING_ROAD);
    FlagClear(FLAG_SYS_CRUISE_MODE);
    FlagClear(FLAG_SYS_SAFARI_MODE);
    FlagClear(FLAG_SYS_USE_STRENGTH);
    FlagClear(FLAG_SYS_USE_FLASH);
}

static void Overworld_ResetStateAfterWhiteOut(void)
{
    ResetInitialPlayerAvatarState();
    FlagClear(FLAG_SYS_CYCLING_ROAD);
    FlagClear(FLAG_SYS_CRUISE_MODE);
    FlagClear(FLAG_SYS_SAFARI_MODE);
    FlagClear(FLAG_SYS_USE_STRENGTH);
    FlagClear(FLAG_SYS_USE_FLASH);
}

static void sub_805308C(void)
{
    FlagClear(FLAG_SYS_SAFARI_MODE);
    ChooseAmbientCrySpecies();
    ResetCyclingRoadChallengeData();
    UpdateLocationHistoryForRoamer();
    RoamerMoveToOtherLocationSet();
}

void ResetGameStats(void)
{
    s32 i;

    for (i = 0; i < NUM_GAME_STATS; i++)
        gSaveBlock1.gameStats[i] = 0;
}

void IncrementGameStat(u8 index)
{
    if (index < NUM_GAME_STATS)
    {
        if (gSaveBlock1.gameStats[index] < 0xFFFFFF)
            gSaveBlock1.gameStats[index]++;
        else
            gSaveBlock1.gameStats[index] = 0xFFFFFF;
    }
}

u32 GetGameStat(u8 stat)
{
    if (stat >= NUM_GAME_STATS)
        return 0;

    return gSaveBlock1.gameStats[stat];
}

void SetGameStat(u8 stat, u32 value)
{
    if (stat < NUM_GAME_STATS)
        gSaveBlock1.gameStats[stat] = value;
}

void LoadObjEventTemplatesFromHeader(void)
{
    // Clear object event templates
    CpuFill32(0, gSaveBlock1.objectEventTemplates, sizeof(gSaveBlock1.objectEventTemplates));

    // Copy map header events to save block
    CpuCopy32(gMapHeader.events->objectEvents,
              gSaveBlock1.objectEventTemplates,
              gMapHeader.events->objectEventCount * sizeof(struct ObjectEventTemplate));
}

static void LoadSaveblockObjEventScripts(void)
{
    struct ObjectEventTemplate *objectEventTemplates = gSaveBlock1.objectEventTemplates;
    s32 i;

    for (i = 0; i < 64; i++)
        objectEventTemplates[i].script = gMapHeader.events->objectEvents[i].script;
}

void Overworld_SetObjEventTemplateCoords(u8 localId, s16 x, s16 y)
{
    s32 i;
    for (i = 0; i < 64; i++)
    {
        struct ObjectEventTemplate *objectEventTemplate = &gSaveBlock1.objectEventTemplates[i];
        if (objectEventTemplate->localId == localId)
        {
            objectEventTemplate->x = x;
            objectEventTemplate->y = y;
            return;
        }
    }
}

void Overworld_SetObjEventTemplateMovementType(u8 localId, u8 movementType)
{
    s32 i;

    for (i = 0; i < 64; i++)
    {
        struct ObjectEventTemplate *objectEventTemplate = &gSaveBlock1.objectEventTemplates[i];
        if (objectEventTemplate->localId == localId)
        {
            objectEventTemplate->movementType = movementType;
            return;
        }
    }
}

static void mapdata_load_assets_to_gpu_and_full_redraw(void)
{
    move_tilemap_camera_to_upper_left_corner();
    CopyMapTilesetsToVram(gMapHeader.mapLayout);
    LoadMapTilesetPalettes(gMapHeader.mapLayout);
    DrawWholeMapView();
    cur_mapheader_run_tileset_funcs_after_some_cpuset();
}

static struct MapLayout *GetMapLayout(void)
{
    u16 mapLayoutId = gSaveBlock1.mapLayoutId;
    if (mapLayoutId)
        return gMapLayouts[mapLayoutId - 1];
    return NULL;
}

static void ApplyCurrentWarp(void)
{
    gLastUsedWarp = gSaveBlock1.location;
    gSaveBlock1.location = gWarpDestination;
    gFixedDiveWarp = sDummyWarpData;
    gFixedHoleWarp = sDummyWarpData;
}

static void SetWarpData(struct WarpData *warp, s8 mapGroup, s8 mapNum, s8 warpId, s8 x, s8 y)
{
    warp->mapGroup = mapGroup;
    warp->mapNum = mapNum;
    warp->warpId = warpId;
    warp->x = x;
    warp->y = y;
}

static bool32 IsDummyWarp(struct WarpData *warp)
{
    if (warp->mapGroup != -1)
        return FALSE;
    if (warp->mapNum != -1)
        return FALSE;
    if (warp->warpId != -1)
        return FALSE;
    if (warp->x != -1)
        return FALSE;
    if (warp->y != -1)
        return FALSE;
    return TRUE;
}

struct MapHeader *const Overworld_GetMapHeaderByGroupAndId(u16 mapGroup, u16 mapNum)
{
    return gMapGroups[mapGroup][mapNum];
}

struct MapHeader *const GetDestinationWarpMapHeader(void)
{
    return Overworld_GetMapHeaderByGroupAndId(gWarpDestination.mapGroup, gWarpDestination.mapNum);
}

static void LoadCurrentMapData(void)
{
    gMapHeader = *Overworld_GetMapHeaderByGroupAndId(gSaveBlock1.location.mapGroup, gSaveBlock1.location.mapNum);
    gSaveBlock1.mapLayoutId = gMapHeader.mapLayoutId;
    gMapHeader.mapLayout = GetMapLayout();
}

static void LoadSaveblockMapHeader(void)
{
    gMapHeader = *Overworld_GetMapHeaderByGroupAndId(gSaveBlock1.location.mapGroup, gSaveBlock1.location.mapNum);
    gMapHeader.mapLayout = GetMapLayout();
}

static void SetPlayerCoordsFromWarp(void)
{
    if (gSaveBlock1.location.warpId >= 0 && gSaveBlock1.location.warpId < gMapHeader.events->warpCount)
    {
        gSaveBlock1.pos.x = gMapHeader.events->warps[gSaveBlock1.location.warpId].x;
        gSaveBlock1.pos.y = gMapHeader.events->warps[gSaveBlock1.location.warpId].y;
    }
    else if (gSaveBlock1.location.x >= 0 && gSaveBlock1.location.y >= 0)
    {
        gSaveBlock1.pos.x = gSaveBlock1.location.x;
        gSaveBlock1.pos.y = gSaveBlock1.location.y;
    }
    else
    {
        gSaveBlock1.pos.x = gMapHeader.mapLayout->width / 2;
        gSaveBlock1.pos.y = gMapHeader.mapLayout->height / 2;
    }
}

void WarpIntoMap(void)
{
    ApplyCurrentWarp();
    LoadCurrentMapData();
    SetPlayerCoordsFromWarp();
}

void Overworld_SetWarpDestination(s8 mapGroup, s8 mapNum, s8 warpId, s8 x, s8 y)
{
    SetWarpData(&gWarpDestination, mapGroup, mapNum, warpId, x, y);
}

void warp1_set_2(s8 mapGroup, s8 mapNum, s8 warpId)
{
    Overworld_SetWarpDestination(mapGroup, mapNum, warpId, -1, -1);
}

void saved_warp2_set(int unused, s8 mapGroup, s8 mapNum, s8 warpId)
{
    SetWarpData(&gSaveBlock1.dynamicWarp, mapGroup, mapNum, warpId, gSaveBlock1.pos.x, gSaveBlock1.pos.y);
}

void saved_warp2_set_2(int unused, s8 mapGroup, s8 mapNum, s8 warpId, s8 x, s8 y)
{
    SetWarpData(&gSaveBlock1.dynamicWarp, mapGroup, mapNum, warpId, x, y);
}

void copy_saved_warp2_bank_and_enter_x_to_warp1(u8 unused)
{
    gWarpDestination = gSaveBlock1.dynamicWarp;
}

void sub_8053538(u8 a1)
{
    const struct HealLocation *warp = GetHealLocation(a1);

    if (warp)
        Overworld_SetWarpDestination(warp->group, warp->map, -1, warp->x, warp->y);
}

void Overworld_SetWarpDestToLastHealLoc(void)
{
    gWarpDestination = gSaveBlock1.lastHealLocation;
}

void Overworld_SetHealLocationWarp(u8 healLocationId)
{
    const struct HealLocation *healLocation = GetHealLocation(healLocationId);

    if (healLocation != NULL)
        SetWarpData(&gSaveBlock1.lastHealLocation, healLocation->group, healLocation->map, -1, healLocation->x, healLocation->y);
}

void sub_80535C4(s16 a1, s16 a2)
{
    u8 currMapType = Overworld_GetMapTypeOfSaveblockLocation();
    u8 destMapType = GetMapTypeByGroupAndId(gWarpDestination.mapGroup, gWarpDestination.mapNum);
    if (is_map_type_1_2_3_5_or_6(currMapType) && is_map_type_1_2_3_5_or_6(destMapType) != TRUE)
        sub_805363C(gSaveBlock1.location.mapGroup, gSaveBlock1.location.mapNum, -1, a1 - 7, a2 - 6);
}

void sub_805363C(s8 mapGroup, s8 mapNum, s8 warpId, s8 x, s8 y)
{
    SetWarpData(&gSaveBlock1.warp4, mapGroup, mapNum, warpId, x, y);
}

void sub_8053678(void)
{
    gWarpDestination = gSaveBlock1.warp4;
}

void SetFixedDiveWarp(s8 mapGroup, s8 mapNum, s8 warpId, s8 x, s8 y)
{
    SetWarpData(&gFixedDiveWarp, mapGroup, mapNum, warpId, x, y);
}

static void SetFixedDiveWarpAsDestination(void)
{
    gWarpDestination = gFixedDiveWarp;
}

void SetFixedHoleWarp(s8 mapGroup, s8 mapNum, s8 warpId, s8 x, s8 y)
{
    SetWarpData(&gFixedHoleWarp, mapGroup, mapNum, warpId, x, y);
}

void SetFixedHoleWarpAsDestination(s16 x, s16 y)
{
    if (IsDummyWarp(&gFixedHoleWarp) == TRUE)
        gWarpDestination = gLastUsedWarp;
    else
        Overworld_SetWarpDestination(gFixedHoleWarp.mapGroup, gFixedHoleWarp.mapNum, -1, x, y);
}

void sub_8053778(void)
{
    gWarpDestination = gSaveBlock1.warp1;
}

void unref_sub_8053790(s8 mapGroup, s8 mapNum, s8 warpId, s8 x, s8 y)
{
    SetWarpData(&gSaveBlock1.warp1, mapGroup, mapNum, warpId, x, y);
}

void sub_80537CC(u8 a1)
{
    const struct HealLocation *warp = GetHealLocation(a1);
    if (warp)
        SetWarpData(&gSaveBlock1.warp1, warp->group, warp->map, -1, warp->x, warp->y);
}

void gpu_sync_bg_hide()
{
    gSaveBlock1.warp1 = gSaveBlock1.dynamicWarp;
}

const struct MapConnection *GetMapConnection(u8 dir)
{
    s32 i;
    s32 count = gMapHeader.connections->count;
    const struct MapConnection *connection = gMapHeader.connections->connections;

    if (connection == NULL)
        return NULL;

    for(i = 0; i < count; i++, connection++)
        if (connection->direction == dir)
            return connection;

    return NULL;
}

static bool8 SetDiveWarp(u8 direction, u16 x, u16 y)
{
    const struct MapConnection *connection = GetMapConnection(direction);

    if (connection != NULL)
    {
        Overworld_SetWarpDestination(connection->mapGroup, connection->mapNum, -1, x, y);
    }
    else
    {
        RunOnDiveWarpMapScript();
        if (IsDummyWarp(&gFixedDiveWarp))
            return FALSE;

        SetFixedDiveWarpAsDestination();
    }

    return TRUE;
}

bool8 SetDiveWarpEmerge(u16 x, u16 y)
{
    return SetDiveWarp(CONNECTION_EMERGE, x, y);
}

bool8 SetDiveWarpDive(u16 x, u16 y)
{
    return SetDiveWarp(CONNECTION_DIVE, x, y);
}

void LoadMapFromCameraTransition(u8 mapGroup, u8 mapNum)
{
    s32 paletteIndex;

    Overworld_SetWarpDestination(mapGroup, mapNum, -1, -1, -1);
    sub_8053F0C();
    ApplyCurrentWarp();
    LoadCurrentMapData();
    LoadObjEventTemplatesFromHeader();
    ClearTempFieldEventData();
    ResetCyclingRoadChallengeData();
    RestartWildEncounterImmunitySteps();
    TryUpdateRandomTrainerRematches(mapGroup, mapNum);
    DoTimeBasedEvents();
    SetSav1WeatherFromCurrMapHeader();
    ChooseAmbientCrySpecies();
    SetDefaultFlashLevel();
    Overworld_ClearSavedMusic();
    RunOnTransitionMapScript();
    InitMap();
    CopySecondaryTilesetToVram(gMapHeader.mapLayout);
    LoadSecondaryTilesetPalette(gMapHeader.mapLayout);

    for (paletteIndex = 6; paletteIndex < 12; paletteIndex++)
        ApplyWeatherGammaShiftToPal(paletteIndex);

    sub_8072ED0();
    UpdateLocationHistoryForRoamer();
    RoamerMove();
    DoCurrentWeather();
    ResetFieldTasksArgs();
    RunOnResumeMapScript();
    ShowMapNamePopup();
}

void sub_8053994(u32 a1)
{
    bool8 v2;
    bool8 v3;

    LoadCurrentMapData();
    LoadObjEventTemplatesFromHeader();
    v2 = is_map_type_1_2_3_5_or_6(gMapHeader.mapType);
    v3 = Overworld_MapTypeIsIndoors(gMapHeader.mapType);
    ClearTempFieldEventData();
    ResetCyclingRoadChallengeData();
    RestartWildEncounterImmunitySteps();
    TryUpdateRandomTrainerRematches(gSaveBlock1.location.mapGroup, gSaveBlock1.location.mapNum);
    if (a1 != 1)
        DoTimeBasedEvents();
    SetSav1WeatherFromCurrMapHeader();
    ChooseAmbientCrySpecies();
    if (v2)
        FlagClear(FLAG_SYS_USE_FLASH);
    SetDefaultFlashLevel();
    Overworld_ClearSavedMusic();
    RunOnTransitionMapScript();
    UpdateLocationHistoryForRoamer();
    RoamerMoveToOtherLocationSet();
    InitMap();
    if (a1 != 1 && v3)
    {
        UpdateTVScreensOnMap(gBackupMapLayout.width, gBackupMapLayout.height);
        InitSecretBaseAppearance(TRUE);
    }
}

void ResetInitialPlayerAvatarState(void)
{
    gInitialPlayerAvatarState.direction = DIR_SOUTH;
    gInitialPlayerAvatarState.transitionFlags = PLAYER_AVATAR_FLAG_ON_FOOT;
}

void StoreInitialPlayerAvatarState(void)
{
    gInitialPlayerAvatarState.direction = GetPlayerFacingDirection();

    if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_MACH_BIKE))
        gInitialPlayerAvatarState.transitionFlags = PLAYER_AVATAR_FLAG_MACH_BIKE;
    else if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_ACRO_BIKE))
        gInitialPlayerAvatarState.transitionFlags = PLAYER_AVATAR_FLAG_ACRO_BIKE;
    else if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_SURFING))
        gInitialPlayerAvatarState.transitionFlags = PLAYER_AVATAR_FLAG_SURFING;
    else if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_UNDERWATER))
        gInitialPlayerAvatarState.transitionFlags = PLAYER_AVATAR_FLAG_UNDERWATER;
    else
        gInitialPlayerAvatarState.transitionFlags = PLAYER_AVATAR_FLAG_ON_FOOT;
}

static struct InitialPlayerAvatarState *GetInitialPlayerAvatarState(void)
{
    struct InitialPlayerAvatarState playerStruct;
    u8 mapType = Overworld_GetMapTypeOfSaveblockLocation();
    u16 metatileBehavior = GetCenterScreenMetatileBehavior();
    u8 transitionFlags = GetAdjustedInitialTransitionFlags(&gInitialPlayerAvatarState, metatileBehavior, mapType);
    playerStruct.transitionFlags = transitionFlags;
    playerStruct.direction = GetAdjustedInitialDirection(&gInitialPlayerAvatarState, transitionFlags, metatileBehavior, mapType);
    gInitialPlayerAvatarState = playerStruct;
    return &gInitialPlayerAvatarState;
}

static u8 GetAdjustedInitialTransitionFlags(struct InitialPlayerAvatarState *playerStruct, u16 metatileBehavior, u8 mapType)
{
    if (mapType != MAP_TYPE_INDOOR && FlagGet(FLAG_SYS_CRUISE_MODE))
        return PLAYER_AVATAR_FLAG_ON_FOOT;
    if (mapType == MAP_TYPE_UNDERWATER)
        return PLAYER_AVATAR_FLAG_UNDERWATER;
    if (MetatileBehavior_IsSurfableWaterOrUnderwater(metatileBehavior) == TRUE)
        return PLAYER_AVATAR_FLAG_SURFING;
    if (Overworld_IsBikingAllowed() != TRUE)
        return PLAYER_AVATAR_FLAG_ON_FOOT;
    if (playerStruct->transitionFlags == PLAYER_AVATAR_FLAG_MACH_BIKE)
        return PLAYER_AVATAR_FLAG_MACH_BIKE;
    if (playerStruct->transitionFlags != PLAYER_AVATAR_FLAG_ACRO_BIKE)
        return PLAYER_AVATAR_FLAG_ON_FOOT;
    return PLAYER_AVATAR_FLAG_ACRO_BIKE;
}

static u8 GetAdjustedInitialDirection(struct InitialPlayerAvatarState *playerStruct, u8 transitionFlags, u16 metatileBehavior, u8 mapType)
{
    if (FlagGet(FLAG_SYS_CRUISE_MODE) && mapType == MAP_TYPE_6)
        return DIR_EAST;
    if (MetatileBehavior_IsDeepSouthWarp(metatileBehavior) == TRUE)
        return DIR_NORTH;
    if (MetatileBehavior_IsNonAnimDoor(metatileBehavior) == TRUE || MetatileBehavior_IsDoor(metatileBehavior) == TRUE)
        return DIR_SOUTH;
    if (MetatileBehavior_IsSouthArrowWarp(metatileBehavior) == TRUE)
        return DIR_NORTH;
    if (MetatileBehavior_IsNorthArrowWarp(metatileBehavior) == TRUE)
        return DIR_SOUTH;
    if (MetatileBehavior_IsWestArrowWarp(metatileBehavior) == TRUE)
        return DIR_EAST;
    if (MetatileBehavior_IsEastArrowWarp(metatileBehavior) == TRUE)
        return DIR_WEST;
    if ((playerStruct->transitionFlags == PLAYER_AVATAR_FLAG_UNDERWATER && transitionFlags == PLAYER_AVATAR_FLAG_SURFING)
     || (playerStruct->transitionFlags == PLAYER_AVATAR_FLAG_SURFING && transitionFlags == PLAYER_AVATAR_FLAG_UNDERWATER))
        return playerStruct->direction;
    if (MetatileBehavior_IsLadder(metatileBehavior) == TRUE)
        return playerStruct->direction;
    return DIR_SOUTH;
}

u16 GetCenterScreenMetatileBehavior(void)
{
    return MapGridGetMetatileBehaviorAt(gSaveBlock1.pos.x + 7, gSaveBlock1.pos.y + 7);
}

bool32 Overworld_IsBikingAllowed(void)
{
    // is player in cycling road entrance?
    if (gSaveBlock1.location.mapGroup == MAP_GROUP(ROUTE110_SEASIDE_CYCLING_ROAD_SOUTH_ENTRANCE)
     && (gSaveBlock1.location.mapNum == MAP_NUM(ROUTE110_SEASIDE_CYCLING_ROAD_SOUTH_ENTRANCE)
      || gSaveBlock1.location.mapNum == MAP_NUM(ROUTE110_SEASIDE_CYCLING_ROAD_NORTH_ENTRANCE)))
        return TRUE;

    // is player indoor, in a secret base, or underwater?
    if (gMapHeader.mapType == MAP_TYPE_INDOOR)
        return FALSE;
    if (gMapHeader.mapType == MAP_TYPE_SECRET_BASE)
        return FALSE;
    if (gMapHeader.mapType == MAP_TYPE_UNDERWATER)
        return FALSE;

    // Thou shalt not bike on the sacred resting grounds of Kyogre/Groudon.
    if (gSaveBlock1.location.mapGroup == MAP_GROUP(SEAFLOOR_CAVERN_ROOM9)
     && gSaveBlock1.location.mapNum == MAP_NUM(SEAFLOOR_CAVERN_ROOM9))
        return FALSE;
    if (gSaveBlock1.location.mapGroup == MAP_GROUP(CAVE_OF_ORIGIN_B4F)
     && gSaveBlock1.location.mapNum == MAP_NUM(CAVE_OF_ORIGIN_B4F))
        return FALSE;

    return TRUE;
}

void SetDefaultFlashLevel(void)
{
    if (!gMapHeader.cave)
        gSaveBlock1.flashLevel = 0;
    else if (FlagGet(FLAG_SYS_USE_FLASH))
        gSaveBlock1.flashLevel = 1;
    else
        gSaveBlock1.flashLevel = gMaxFlashLevel;
}

void Overworld_SetFlashLevel(s32 flashLevel)
{
    if (flashLevel < 0 || flashLevel > gMaxFlashLevel)
        flashLevel = 0;
    gSaveBlock1.flashLevel = flashLevel;
}

u8 Overworld_GetFlashLevel(void)
{
    return gSaveBlock1.flashLevel;
}

void sub_8053D14(u16 mapLayoutId)
{
    gSaveBlock1.mapLayoutId = mapLayoutId;
    gMapHeader.mapLayout = GetMapLayout();
}

static bool16 ShouldLegendaryMusicPlayAtLocation(struct WarpData *warp)
{
    if (!FlagGet(FLAG_SYS_WEATHER_CTRL))
        return FALSE;
    if (warp->mapGroup == 0)
    {
        switch (warp->mapNum)
        {
        case MAP_NUM(LILYCOVE_CITY):
        case MAP_NUM(MOSSDEEP_CITY):
        case MAP_NUM(SOOTOPOLIS_CITY):
        case MAP_NUM(EVER_GRANDE_CITY):
            return TRUE;
        case MAP_NUM(ROUTE124):
        case MAP_NUM(ROUTE125):
        case MAP_NUM(ROUTE126):
        case MAP_NUM(ROUTE127):
        case MAP_NUM(ROUTE128):
            return TRUE;
        }
    }
    return FALSE;
}

static bool16 IsInfiltratedWeatherInstitute(struct WarpData *warp)
{
    if (VarGet(VAR_WEATHER_INSTITUTE_STATE))
        return FALSE;
    if (warp->mapGroup != MAP_GROUP(ROUTE119_WEATHER_INSTITUTE_1F))
        return FALSE;
    if (warp->mapNum == MAP_NUM(ROUTE119_WEATHER_INSTITUTE_1F)
     || warp->mapNum == MAP_NUM(ROUTE119_WEATHER_INSTITUTE_2F))
        return TRUE;
    return FALSE;
}

static u16 GetLocationMusic(struct WarpData *warp)
{
    if (ShouldLegendaryMusicPlayAtLocation(warp) == TRUE)
        return LEGENDARY_MUSIC;
    else if (IsInfiltratedWeatherInstitute(warp) == TRUE)
        return MUS_MT_CHIMNEY;
    else
        return Overworld_GetMapHeaderByGroupAndId(warp->mapGroup, warp->mapNum)->music;
}

u16 GetCurrLocationDefaultMusic(void)
{
    u16 music;

    // Play the desert music only when the sandstorm is active on Route 111.
    if (gSaveBlock1.location.mapGroup == MAP_GROUP(ROUTE111)
     && gSaveBlock1.location.mapNum == MAP_NUM(ROUTE111)
     && GetSav1Weather() == 8)
        return MUS_ROUTE111;

    music = GetLocationMusic(&gSaveBlock1.location);
    if (music != 0x7FFF)
    {
        return music;
    }
    else
    {
        if (gSaveBlock1.pos.x < 24)
            return MUS_ROUTE110;
        else
            return MUS_ROUTE119;
    }
}

u16 GetWarpDestinationMusic(void)
{
    u16 music = GetLocationMusic(&gWarpDestination);
    if (music != 0x7FFF)
    {
        return music;
    }
    else
    {
        if (gSaveBlock1.location.mapGroup == MAP_GROUP(MAUVILLE_CITY)
         && gSaveBlock1.location.mapNum == MAP_NUM(MAUVILLE_CITY))
            return MUS_ROUTE110;
        else
            return MUS_ROUTE119;
    }
}

void Overworld_ResetMapMusic(void)
{
    ResetMapMusic();
}

void Overworld_PlaySpecialMapMusic(void)
{
    u16 music = GetCurrLocationDefaultMusic();

    if (music != LEGENDARY_MUSIC)
    {
        if (gSaveBlock1.savedMusic)
            music = gSaveBlock1.savedMusic;
        else if (Overworld_GetMapTypeOfSaveblockLocation() == MAP_TYPE_UNDERWATER)
            music = MUS_UNDERWATER;
        else if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_SURFING))
            music = MUS_SURF;
    }

    if (music != GetCurrentMapMusic())
        PlayNewMapMusic(music);
}

void Overworld_SetSavedMusic(u16 songNum)
{
    gSaveBlock1.savedMusic = songNum;
}

void Overworld_ClearSavedMusic(void)
{
    gSaveBlock1.savedMusic = 0;
}

void sub_8053F0C(void)
{
    if (FlagGet(FLAG_DONT_TRANSITION_MUSIC) != TRUE)
    {
        u16 newMusic = GetWarpDestinationMusic();
        u16 currentMusic = GetCurrentMapMusic();
        if (newMusic != LEGENDARY_MUSIC)
        {
            if (currentMusic == MUS_UNDERWATER || currentMusic == MUS_SURF)
                return;
            if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_SURFING))
                newMusic = MUS_SURF;
        }
        if (newMusic != currentMusic)
        {
            if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_MACH_BIKE | PLAYER_AVATAR_FLAG_ACRO_BIKE))
                FadeOutAndFadeInNewMapMusic(newMusic, 4, 4);
            else
                FadeOutAndPlayNewMapMusic(newMusic, 8);
        }
    }
}

void Overworld_ChangeMusicToDefault(void)
{
    u16 currentMusic = GetCurrentMapMusic();
    if (currentMusic != GetCurrLocationDefaultMusic())
        FadeOutAndPlayNewMapMusic(GetCurrLocationDefaultMusic(), 8);
}

void Overworld_ChangeMusicTo(u16 newMusic)
{
    u16 currentMusic = GetCurrentMapMusic();
    if (currentMusic != newMusic && currentMusic != LEGENDARY_MUSIC)
        FadeOutAndPlayNewMapMusic(newMusic, 8);
}

u8 GetMapMusicFadeoutSpeed(void)
{
    const struct MapHeader *mapHeader = GetDestinationWarpMapHeader();
    if (Overworld_MapTypeIsIndoors(mapHeader->mapType) == TRUE)
        return 2;
    else
        return 4;
}

void TryFadeOutOldMapMusic(void)
{
    u16 music = GetWarpDestinationMusic();
    if (FlagGet(FLAG_DONT_TRANSITION_MUSIC) != TRUE && music != GetCurrentMapMusic())
    {
        u8 speed = GetMapMusicFadeoutSpeed();
        FadeOutMapMusic(speed);
    }
}

bool8 BGMusicStopped(void)
{
    return IsNotWaitingForBGMStop();
}

void Overworld_FadeOutMapMusic(void)
{
    FadeOutMapMusic(4);
}

static void PlayAmbientCry(void)
{
    s16 x, y;
    s8 pan;
    s8 volume;

    PlayerGetDestCoords(&x, &y);
    if (sIsAmbientCryWaterMon == TRUE
     && !MetatileBehavior_IsSurfableWaterOrUnderwater(MapGridGetMetatileBehaviorAt(x, y)))
        return;
    pan = (Random() % 88) + 212;
    volume = (Random() % 30) + 50;
    PlayCry2(sAmbientCrySpecies, pan, volume, 1);
}

void UpdateAmbientCry(s16 *state, u16 *delayCounter)
{
    switch (*state)
    {
    case 0:
        if (sAmbientCrySpecies == SPECIES_NONE)
            *state = 4;
        else
            *state = 1;
        break;
    case 1:
        *delayCounter = (Random() % 2400) + 1200;
        *state = 3;
        break;
    case 2:
        *delayCounter = (Random() % 1200) + 1200;
        *state = 3;
        break;
    case 3:
        (*delayCounter)--;
        if (*delayCounter == 0)
        {
            PlayAmbientCry();
            *state = 2;
        }
        break;
    case 4:
        break;
    }
}

static void ChooseAmbientCrySpecies(void)
{
    if ((gSaveBlock1.location.mapGroup == MAP_GROUP(ROUTE130)
     && gSaveBlock1.location.mapNum == MAP_NUM(ROUTE130))
     && !IsMirageIslandPresent())
    {
        // Only play water pokemon cries on this route
        // when Mirage Island is not present
        sIsAmbientCryWaterMon = TRUE;
        sAmbientCrySpecies = GetLocalWaterMon();
    }
    else
    {
        sAmbientCrySpecies = GetLocalWildMon(&sIsAmbientCryWaterMon);
    }
}

u8 GetMapTypeByGroupAndId(s8 mapGroup, s8 mapNum)
{
    return Overworld_GetMapHeaderByGroupAndId(mapGroup, mapNum)->mapType;
}

u8 GetMapTypeByWarpData(struct WarpData *warp)
{
    return GetMapTypeByGroupAndId(warp->mapGroup, warp->mapNum);
}

u8 Overworld_GetMapTypeOfSaveblockLocation(void)
{
    return GetMapTypeByWarpData(&gSaveBlock1.location);
}

u8 GetLastUsedWarpMapType(void)
{
    return GetMapTypeByWarpData(&gLastUsedWarp);
}

bool8 is_map_type_1_2_3_5_or_6(u8 mapType)
{
    if (mapType == MAP_TYPE_ROUTE
     || mapType == MAP_TYPE_TOWN
     || mapType == MAP_TYPE_UNDERWATER
     || mapType == MAP_TYPE_CITY
     || mapType == MAP_TYPE_6)
        return TRUE;
    else
        return FALSE;
}

bool8 Overworld_MapTypeAllowsTeleportAndFly(u8 mapType)
{
    if (mapType == MAP_TYPE_ROUTE
     || mapType == MAP_TYPE_TOWN
     || mapType == MAP_TYPE_6
     || mapType == MAP_TYPE_CITY)
        return TRUE;
    else
        return FALSE;
}

bool8 Overworld_MapTypeIsIndoors(u8 mapType)
{
    if (mapType == MAP_TYPE_INDOOR
     || mapType == MAP_TYPE_SECRET_BASE)
        return TRUE;
    else
        return FALSE;
}

u8 unref_sub_8054260(void)
{
    return Overworld_GetMapHeaderByGroupAndId(gSaveBlock1.dynamicWarp.mapGroup, gSaveBlock1.dynamicWarp.mapNum)->regionMapSectionId;
}

u8 sav1_map_get_name(void)
{
    return Overworld_GetMapHeaderByGroupAndId(gSaveBlock1.location.mapGroup, gSaveBlock1.location.mapNum)->regionMapSectionId;
}

u8 sav1_map_get_battletype(void)
{
    return Overworld_GetMapHeaderByGroupAndId(gSaveBlock1.location.mapGroup, gSaveBlock1.location.mapNum)->battleType;
}

#if DEBUG

void debug_sub_8076B68(void);

void debug_sub_80589D8(void);

void debug_sub_8058A50(void);

void CB2_InitTestMenu(void)
{
    m4aSoundVSyncOff();
    SetVBlankCallback(NULL);
    DmaFill32(3, 0, (void *) VRAM, VRAM_SIZE);
    DmaFill32(3, 0, (void *) PLTT, PLTT_SIZE);
    ResetPaletteFade();
    ResetSpriteData();
    ResetTasks();
    ScanlineEffect_Stop();
    Text_LoadWindowTemplate(&gMenuTextWindowTemplate);
    InitMenuWindow(&gMenuTextWindowTemplate);
    debug_sub_8076B68();
    BeginNormalPaletteFade(0xFFFFFFFF, 0, 16, 0, RGB(0, 0, 0));
    REG_IE |= 1;
    REG_DISPCNT = DISPCNT_OBJ_ON | DISPCNT_BG0_ON | DISPCNT_OBJ_1D_MAP;
    m4aSoundVSyncOn();
    SetVBlankCallback(debug_sub_8058A50);
    m4aSongNumStart(0x19D);
    SetMainCallback2(debug_sub_80589D8);
}

void debug_sub_80589D8(void)
{
    if (UpdatePaletteFade())
        return;

    RunTasks();
    AnimateSprites();
    BuildOamBuffer();
}

void debug_sub_80589F4(void)
{
    if (UpdatePaletteFade())
        return;

    SetVBlankCallback(NULL);

    DmaFill32(3, 0, (void *) VRAM, VRAM_SIZE);
    DmaFill32(3, 0, (void *) PLTT, PLTT_SIZE);

    SetMainCallback2(gMain.savedCallback);
}

void debug_sub_8058A50(void)
{
    ProcessSpriteCopyRequests();
    LoadOam();
    TransferPlttBuffer();
}

#endif

void ResetSafariZoneFlag_(void)
{
    ResetSafariZoneFlag();
}

bool32 is_c1_link_related_active(void)
{
    if (gMain.callback1 == sub_8055354)
        return TRUE;
    else
        return FALSE;
}

static void DoCB1_Overworld(u16 newKeys, u16 heldKeys)
{
    struct FieldInput fieldInput;

    sub_8059204();
    ClearPlayerFieldInput(&fieldInput);
    GetPlayerFieldInput(&fieldInput, newKeys, heldKeys);
    if (!ScriptContext2_IsEnabled())
    {
        if (ProcessPlayerFieldInput(&fieldInput) == 1)
        {
            ScriptContext2_Enable();
            HideMapNamePopup();
        }
        else
        {
            player_step(fieldInput.dpadDirection, newKeys, heldKeys);
        }
    }
}

static void CB1_Overworld(void)
{
    if (gMain.callback2 == CB2_Overworld)
        DoCB1_Overworld(gMain.newKeys, gMain.heldKeys);
}

static void OverworldBasic(void)
{
    ScriptContext2_RunScript();
    RunTasks();
    AnimateSprites();
    CameraUpdate();
    UpdateCameraPanning();
    BuildOamBuffer();
    UpdatePaletteFade();
    sub_8072EDC();
}

// This CB2 is used when starting
void CB2_OverworldBasic(void)
{
    OverworldBasic();
}

static void CB2_Overworld(void)
{
    int fading = (gPaletteFade.active != 0);
    if (fading)
        SetVBlankCallback(NULL);
    OverworldBasic();
    if (fading)
        SetFieldVBlankCallback();
}

void SetMainCallback1(MainCallback cb)
{
    gMain.callback1 = cb;
}

void sub_80543DC(u16 (*a1)(u32))
{
    gUnknown_03000584 = a1;
}

static void RunFieldCallback(void)
{
    if (gFieldCallback != NULL)
        gFieldCallback();
    else
        mapldr_default();
    gFieldCallback = NULL;
}

void CB2_NewGame(void)
{
    FieldClearVBlankHBlankCallbacks();
    StopMapMusic();
    ResetSafariZoneFlag_();
    NewGameInitData();
    ResetInitialPlayerAvatarState();
    PlayTimeCounter_Start();
    ScriptContext1_Init();
    ScriptContext2_Disable();
    gFieldCallback = ExecuteTruckSequence;
    do_load_map_stuff_loop(&gMain.state);
    SetFieldVBlankCallback();
    SetMainCallback1(CB1_Overworld);
    SetMainCallback2(CB2_Overworld);
}

#if DEBUG

void debug_sub_8058C00(void)
{
    FieldClearVBlankHBlankCallbacks();
    StopMapMusic();
    ResetSafariZoneFlag_();
    ResetInitialPlayerAvatarState();
    PlayTimeCounter_Start();
    ScriptContext1_Init();
    ScriptContext2_Disable();

    if (gMain.heldKeys & R_BUTTON)
        gFieldCallback = ExecuteTruckSequence;
    else
        gFieldCallback = sub_8080B60;

    do_load_map_stuff_loop(&gMain.state);
    SetFieldVBlankCallback();
    SetMainCallback1(CB1_Overworld);
    SetMainCallback2(CB2_Overworld);
}

#endif

void CB2_WhiteOut(void)
{
    u8 val;
    gMain.state++;
    if (gMain.state >= 120)
    {
        FieldClearVBlankHBlankCallbacks();
        StopMapMusic();
        ResetSafariZoneFlag_();
        DoWhiteOut();
        ResetInitialPlayerAvatarState();
        ScriptContext1_Init();
        ScriptContext2_Disable();
        gFieldCallback = sub_8080B60;
        val = 0;
        do_load_map_stuff_loop(&val);
        SetFieldVBlankCallback();
        SetMainCallback1(CB1_Overworld);
        SetMainCallback2(CB2_Overworld);
    }
}

void CB2_LoadMap(void)
{
    FieldClearVBlankHBlankCallbacks();
    ScriptContext1_Init();
    ScriptContext2_Disable();
    SetMainCallback1(NULL);
    SetMainCallback2(sub_810CC80);
    gMain.savedCallback = CB2_LoadMap2;
}

void CB2_LoadMap2(void)
{
    do_load_map_stuff_loop(&gMain.state);
    SetFieldVBlankCallback();
    SetMainCallback1(CB1_Overworld);
    SetMainCallback2(CB2_Overworld);
}

void sub_8054534(void)
{
    if (!gMain.state)
    {
        FieldClearVBlankHBlankCallbacks();
        ScriptContext1_Init();
        ScriptContext2_Disable();
        SetMainCallback1(NULL);
    }
    if (sub_805493C(&gMain.state, 1))
    {
        SetFieldVBlankCallback();
        SetMainCallback1(CB1_Overworld);
        SetMainCallback2(CB2_Overworld);
    }
}

void sub_8054588(void)
{
    FieldClearVBlankHBlankCallbacks();
    gFieldCallback = sub_8080AC4;
    SetMainCallback2(c2_80567AC);
}

static void c2_80567AC(void)
{
    if (sub_805483C(&gMain.state))
    {
        SetFieldVBlankCallback();
        SetMainCallback1(sub_8055354);
        sub_80543DC(sub_8055390);
        SetMainCallback2(CB2_Overworld);
    }
}

void CB2_ReturnToField(void)
{
    if (is_c1_link_related_active() == TRUE)
    {
        SetMainCallback2(CB2_ReturnToFieldLink);
    }
    else
    {
        FieldClearVBlankHBlankCallbacks();
        SetMainCallback2(CB2_ReturnToFieldLocal);
    }
}

void CB2_ReturnToFieldLocal(void)
{
    if (sub_8054A4C(&gMain.state))
    {
        SetFieldVBlankCallback();
        SetMainCallback2(CB2_Overworld);
    }
}

void CB2_ReturnToFieldLink(void)
{
    if (!sub_8055870() && sub_8054A9C(&gMain.state))
        SetMainCallback2(CB2_Overworld);
}

void sub_805465C(void)
{
    FieldClearVBlankHBlankCallbacks();
    StopMapMusic();
    sub_8054F70();
    SetMainCallback1(sub_8055354);
    sub_80543DC(sub_8055390);
    gFieldCallback = sub_8080A3C;
    ScriptContext1_Init();
    ScriptContext2_Disable();
    CB2_ReturnToField();
}

void c2_exit_to_overworld_1_sub_8080DEC(void)
{
    FieldClearVBlankHBlankCallbacks();
    gFieldCallback = sub_8080DEC;
    CB2_ReturnToField();
}

void sub_80546B8(void)
{
    FieldClearVBlankHBlankCallbacks();
    gFieldCallback = sub_80809B0;
    CB2_ReturnToField();
}

void CB2_ReturnToFieldContinueScriptPlayMapMusic(void)
{
    FieldClearVBlankHBlankCallbacks();
    gFieldCallback = sub_8080990;
    CB2_ReturnToField();
}

void sub_80546F0(void)
{
    FieldClearVBlankHBlankCallbacks();
    gFieldCallback = sub_8080B60;
    CB2_ReturnToField();
}

void sub_805470C(void)
{
    if (gMapHeader.flags == 1 && sub_80BBB24() == 1)
        ShowMapNamePopup();
    sub_8080B60();
}

void CB2_ContinueSavedGame(void)
{
    FieldClearVBlankHBlankCallbacks();
    StopMapMusic();
#if DEBUG
    if (gMain.heldKeys & R_BUTTON)
        gUnknown_020297ED = TRUE;
#endif
    ResetSafariZoneFlag_();
    LoadSaveblockMapHeader();
    LoadSaveblockObjEventScripts();
    UnfreezeObjectEvents();
    DoTimeBasedEvents();
    sub_805308C();
    InitMapFromSavedGame();
    PlayTimeCounter_Start();
    ScriptContext1_Init();
    ScriptContext2_Disable();
    if (GetSecretBase2Field_9() == 1)
    {
        ClearSecretBase2Field_9();
        sub_8053778();
        WarpIntoMap();
        SetMainCallback2(CB2_LoadMap);
    }
    else
    {
        gFieldCallback = sub_805470C;
        SetMainCallback1(CB1_Overworld);
        CB2_ReturnToField();
    }
}

void FieldClearVBlankHBlankCallbacks(void)
{
    u16 savedIme = REG_IME;
    REG_IME = 0;
    REG_IE &= ~INTR_FLAG_HBLANK;
    REG_IE |= INTR_FLAG_VBLANK;
    REG_IME = savedIme;
    SetVBlankCallback(NULL);
    SetHBlankCallback(NULL);
}

static void SetFieldVBlankCallback(void)
{
    SetVBlankCallback(VBlankCB_Field);
}

static void VBlankCB_Field(void)
{
    LoadOam();
    ProcessSpriteCopyRequests();
    ScanlineEffect_InitHBlankDmaTransfer();
    sub_8057A58();
    TransferPlttBuffer();
    sub_8072E74();
}

static void InitCurrentFlashLevelScanlineEffect(void)
{
    u8 flashLevel = Overworld_GetFlashLevel();
    if (flashLevel)
    {
        WriteFlashScanlineEffectBuffer(flashLevel);
        ScanlineEffect_SetParams(sFlashEffectParams);
    }
}

static bool32 sub_805483C(u8 *state)
{
    switch (*state)
    {
    case 0:
        FieldClearVBlankHBlankCallbacks();
        ScriptContext1_Init();
        ScriptContext2_Disable();
        sub_8054F70();
        sub_8054BA8();
        (*state)++;
        break;
    case 1:
        sub_8053994(1);
        (*state)++;
        break;
    case 2:
        sub_8054D4C(1);
        (*state)++;
        break;
    case 3:
        sub_8054E98();
        sub_8054D90();
        sub_8054EC8();
        sub_8054E60();
        (*state)++;
        break;
    case 4:
        InitCurrentFlashLevelScanlineEffect();
        InitOverworldGraphicsRegisters();
        Text_LoadWindowTemplate(&gWindowTemplate_81E6C3C);
        InitMenuWindow(&gMenuTextWindowTemplate);
        (*state)++;
        break;
    case 5:
        move_tilemap_camera_to_upper_left_corner();
        (*state)++;
        break;
    case 6:
        CopyPrimaryTilesetToVram(gMapHeader.mapLayout);
        (*state)++;
        break;
    case 7:
        CopySecondaryTilesetToVram(gMapHeader.mapLayout);
        (*state)++;
        break;
    case 8:
        LoadMapTilesetPalettes(gMapHeader.mapLayout);
        (*state)++;
        break;
    case 9:
        DrawWholeMapView();
        (*state)++;
        break;
    case 10:
        cur_mapheader_run_tileset_funcs_after_some_cpuset();
        (*state)++;
        break;
    case 12:
        RunFieldCallback();
        (*state)++;
        break;
    case 11:
        (*state)++;
        break;
    case 13:
        return 1;
    }
    return 0;
}

bool32 sub_805493C(u8 *state, u32 a2)
{
    switch (*state)
    {
    case 0:
        FieldClearVBlankHBlankCallbacks();
        sub_8053994(a2);
        (*state)++;
        break;
    case 1:
        sub_8054BA8();
        (*state)++;
        break;
    case 2:
        sub_8054D4C(a2);
        (*state)++;
        break;
    case 3:
        mli4_mapscripts_and_other();
        sub_8054E34();
        (*state)++;
        break;
    case 4:
        InitCurrentFlashLevelScanlineEffect();
        InitOverworldGraphicsRegisters();
        Text_LoadWindowTemplate(&gWindowTemplate_81E6C3C);
        InitMenuWindow(&gMenuTextWindowTemplate);
        (*state)++;
        break;
    case 5:
        move_tilemap_camera_to_upper_left_corner();
        (*state)++;
        break;
    case 6:
        CopyPrimaryTilesetToVram(gMapHeader.mapLayout);
        (*state)++;
        break;
    case 7:
        CopySecondaryTilesetToVram(gMapHeader.mapLayout);
        (*state)++;
        break;
    case 8:
        LoadMapTilesetPalettes(gMapHeader.mapLayout);
        (*state)++;
        break;
    case 9:
        DrawWholeMapView();
        (*state)++;
        break;
    case 10:
        cur_mapheader_run_tileset_funcs_after_some_cpuset();
        (*state)++;
        break;
    case 11:
        if (gMapHeader.flags == 1 && sub_80BBB24() == 1)
            ShowMapNamePopup();
        (*state)++;
        break;
    case 12:
        RunFieldCallback();
        (*state)++;
        break;
    case 13:
        return 1;
    }
    return 0;
}

bool32 sub_8054A4C(u8 *state)
{
    switch (*state)
    {
    case 0:
        sub_8054BA8();
        sub_8054D4C(0);
        sub_8054E20();
        sub_8054E34();
        (*state)++;
        break;
    case 1:
        sub_8054C2C();
        (*state)++;
        break;
    case 2:
        RunFieldCallback();
        (*state)++;
        break;
    case 3:
        return 1;
    }
    return 0;
}

bool32 sub_8054A9C(u8 *state)
{
    switch (*state)
    {
    case 0:
        FieldClearVBlankHBlankCallbacks();
        sub_8054BA8();
        (*state)++;
        break;
    case 1:
        sub_8054D4C(1);
        (*state)++;
        break;
    case 2:
        sub_8054F48();
        sub_8054E20();
        sub_8054E7C();
        (*state)++;
        break;
    case 3:
        InitCurrentFlashLevelScanlineEffect();
        InitOverworldGraphicsRegisters();
        Text_LoadWindowTemplate(&gWindowTemplate_81E6C3C);
        InitMenuWindow(&gMenuTextWindowTemplate);
        (*state)++;
        break;
    case 4:
        move_tilemap_camera_to_upper_left_corner();
        (*state)++;
        break;
    case 5:
        CopyPrimaryTilesetToVram(gMapHeader.mapLayout);
        (*state)++;
        break;
    case 6:
        CopySecondaryTilesetToVram(gMapHeader.mapLayout);
        (*state)++;
        break;
    case 7:
        LoadMapTilesetPalettes(gMapHeader.mapLayout);
        (*state)++;
        break;
    case 8:
        DrawWholeMapView();
        (*state)++;
        break;
    case 9:
        cur_mapheader_run_tileset_funcs_after_some_cpuset();
        (*state)++;
        break;
    case 12:
        RunFieldCallback();
        (*state)++;
        break;
    case 10:
    case 11:
        (*state)++;
        break;
    case 13:
        SetFieldVBlankCallback();
        (*state)++;
        return 1;
    }
    return 0;
}

void do_load_map_stuff_loop(u8 *a1)
{
    while (!sub_805493C(a1, 0))
        ;
}

void sub_8054BA8(void)
{
    REG_DISPCNT = 0;

    ScanlineEffect_Stop();

    DmaClear16(3, PLTT + 2, PLTT_SIZE - 2);
    DmaFill16Large(3, 0, (void *)(VRAM + 0x0), 0x18000, 0x1000);
    ResetOamRange(0, 128);
    LoadOam();
}

void sub_8054C2C(void)
{
    InitCurrentFlashLevelScanlineEffect();
    InitOverworldGraphicsRegisters();
    Text_LoadWindowTemplate(&gWindowTemplate_81E6C3C);
    InitMenuWindow(&gMenuTextWindowTemplate);
    mapdata_load_assets_to_gpu_and_full_redraw();
}

static void InitOverworldGraphicsRegisters(void)
{
    REG_MOSAIC = 0;
    REG_WININ = 7967;
    REG_WINOUT = 257;
    REG_WIN0H = 255;
    REG_WIN0V = 255;
    REG_WIN1H = -1;
    REG_WIN1V = -1;
    REG_BLDCNT = gUnknown_081E29E0[1] | gUnknown_081E29E0[2] | gUnknown_081E29E0[3] | 0x1040;
    REG_BLDALPHA = 1805;
    *gBGHOffsetRegs[0] = 0;
    *gBGVOffsetRegs[0] = 0;
    *gBGControlRegs[0] = 0;
    *gBGHOffsetRegs[1] = 0;
    *gBGVOffsetRegs[1] = 0;
    *gBGControlRegs[1] = 7489;
    *gBGHOffsetRegs[2] = 0;
    *gBGVOffsetRegs[2] = 0;
    *gBGControlRegs[2] = 7234;
    *gBGHOffsetRegs[3] = 0;
    *gBGVOffsetRegs[3] = 0;
    *gBGControlRegs[3] = 7747;
    REG_DISPCNT = gUnknown_081E29D8[1] | 0x7060 | gUnknown_081E29D8[2] | gUnknown_081E29D8[0] | gUnknown_081E29D8[3];
}

void sub_8054D4C(u32 a1)
{
    ResetTasks();
    ResetSpriteData();
    ResetPaletteFade();
    ScanlineEffect_Clear();
    ResetCameraUpdateInfo();
    InstallCameraPanAheadCallback();
    InitObjectEventPalettes(0);
    FieldEffectActiveListClear();
    InitFieldMessageBox();
    StartWeather();
    sub_8080750();
    if (!a1)
        SetUpFieldTasks();
    RunOnResumeMapScript();
}

void sub_8054D90(void)
{
    gTotalCameraPixelOffsetX = 0;
    gTotalCameraPixelOffsetY = 0;
    ResetObjectEvents();
    TrySpawnObjectEvents(0, 0);
    TryRunOnWarpIntoMapScript();
}

void mli4_mapscripts_and_other(void)
{
    s16 x, y;
    struct InitialPlayerAvatarState *initialPlayerAvatarState;
    gTotalCameraPixelOffsetX = 0;
    gTotalCameraPixelOffsetY = 0;
    ResetObjectEvents();
    GetCameraFocusCoords(&x, &y);
    initialPlayerAvatarState = GetInitialPlayerAvatarState();
    InitPlayerAvatar(x, y, initialPlayerAvatarState->direction, gSaveBlock2.playerGender);
    SetPlayerAvatarTransitionFlags(initialPlayerAvatarState->transitionFlags);
    ResetInitialPlayerAvatarState();
    TrySpawnObjectEvents(0, 0);
    ResetBerryTreeSparkleFlags();
    TryRunOnWarpIntoMapScript();
}

void sub_8054E20(void)
{
    sub_805B710(0, 0);
    RotatingGate_InitPuzzleAndGraphics();
}

void sub_8054E34(void)
{
    gObjectEvents[gPlayerAvatar.objectEventId].trackedByCamera = 1;
    InitCameraUpdateCallback(gPlayerAvatar.spriteId);
}

void sub_8054E60(void)
{
    InitCameraUpdateCallback(sub_8055AE8(gUnknown_03004860));
}

void sub_8054E7C(void)
{
    InitCameraUpdateCallback(sub_8055AE8(gUnknown_03004860));
}

void sub_8054E98(void)
{
    u16 x, y;
    GetCameraFocusCoords(&x, &y);
    SetCameraFocusCoords(x + gUnknown_03004860, y);
}

void sub_8054EC8(void)
{
    u16 i;
    u16 x, y;

    GetCameraFocusCoords(&x, &y);
    x -= gUnknown_03004860;

    for (i = 0; i < gFieldLinkPlayerCount; i++)
    {
        SpawnLinkPlayerObjectEvent(i, i + x, y, gLinkPlayers[i].gender);
        CreateLinkPlayerSprite(i);
    }

    sub_8055340(word_3002910);
}

void sub_8054F48(void)
{
    u16 i;
    for (i = 0; i < gFieldLinkPlayerCount; i++)
        CreateLinkPlayerSprite(i);
}

void sub_8054F70(void)
{
    int i;
    for (i = 0; i < 4; i++)
        gUnknown_03000580[i] = 0x80;
}

bool32 sub_8054F88(u16 a1)
{
    int i;
    int count = gFieldLinkPlayerCount;

    for (i = 0; i < count; i++)
        if (gUnknown_03000580[i] != a1)
            return FALSE;
    return TRUE;
}

bool32 sub_8054FC0(u16 a1)
{
    int i;
    int count = gFieldLinkPlayerCount;

    for (i = 0; i < count; i++)
        if (gUnknown_03000580[i] == a1)
            return TRUE;
    return FALSE;
}

void sub_8054FF8(u32 a1, u16 a2, struct UnkStruct_8054FF8 *a3, u16 *a4)
{
    const u8 *script;

    if (gUnknown_03000580[a1] == 0x80)
    {
        script = sub_8055648(a3);
        if (script)
        {
            *a4 = sub_8055758(script);
            gUnknown_03000580[a1] = 0x81;
            if (a3->b)
            {
                sub_80543DC(sub_80553E4);
                sub_8055808(script);
            }
            return;
        }
        if (sub_8054FC0(0x83) == 1)
        {
            gUnknown_03000580[a1] = 0x81;
            if (a3->b)
            {
                sub_80543DC(sub_80553E4);
                sub_805585C();
            }
            return;
        }
        switch (a2)
        {
        case 24:
            if (sub_8055630(a3))
            {
                gUnknown_03000580[a1] = 0x81;
                if (a3->b)
                {
                    sub_80543DC(sub_80553E4);
                    sub_80557F4();
                }
            }
            break;
        case 18:
            if (sub_8055660(a3) == 1)
            {
                gUnknown_03000580[a1] = 0x81;
                if (a3->b)
                {
                    sub_80543DC(sub_80553E4);
                    sub_8055824();
                }
            }
            break;
        case 25:
            script = sub_805568C(a3);
            if (script)
            {
                gUnknown_03000580[a1] = 0x81;
                if (a3->b)
                {
                    sub_80543DC(sub_80553E4);
                    sub_8055840(script);
                }
            }
            break;
        case 27:
            if (sub_8055618(a3))
            {
                gUnknown_03000580[a1] = 0x81;
                if (a3->b)
                {
                    sub_80543DC(sub_8055408);
                    sub_80557E8();
                }
            }
            break;
        case 28:
            if (sub_8055618(a3))
            {
                gUnknown_03000580[a1] = 0x81;
                if (a3->b)
                {
                    sub_80543DC(sub_8055438);
                    sub_80557E8();
                }
            }
            break;
        }
    }

    switch (a2)
    {
    case 23:
        gUnknown_03000580[a1] = 0x83;
        break;
    case 22:
        gUnknown_03000580[a1] = 0x82;
        break;
    case 26:
        gUnknown_03000580[a1] = 0x80;
        if (a3->b)
            sub_80543DC(sub_8055390);
        break;
    case 29:
        if (gUnknown_03000580[a1] == 0x82)
            gUnknown_03000580[a1] = 0x81;
        break;
    }
}

void sub_8055218(u16 *a1, int a2)
{
    struct UnkStruct_8054FF8 st;
    int i;
    for (i = 0; i < 4; i++)
    {
        u16 v5 = a1[i];
        u16 v8 = 0;
        sub_80555B0(i, a2, &st);
        sub_8054FF8(i, v5, &st, &v8);
        if (gUnknown_03000580[i] == 0x80)
            v8 = sub_805530C(v5);
        sub_8055BFC(i, v8);
    }
}

void sub_8055280(u16 a1)
{
    if (a1 >= 17 && a1 < 30)
        word_3004858 = a1;
    else
        word_3004858 = 17;
}

u16 sub_80552B0(u32 a1)
{
    if (gMain.heldKeys & DPAD_UP)
    {
        return 19;
    }
    else if (gMain.heldKeys & DPAD_DOWN)
    {
        return 18;
    }
    else if (gMain.heldKeys & DPAD_LEFT)
    {
        return 20;
    }
    else if (gMain.heldKeys & DPAD_RIGHT)
    {
        return 21;
    }
    else if (gMain.newKeys & START_BUTTON)
    {
        return 24;
    }
    else if (gMain.newKeys & A_BUTTON)
    {
        return 25;
    }
    else
    {
        return 17;
    }
}

u16 sub_805530C(u16 a1)
{
    switch (a1)
    {
    case 21:
        return 4;
    case 20:
        return 3;
    case 19:
        return 1;
    case 18:
        return 2;
    default:
        return 0;
    }
}

void sub_8055340(u16 *a1)
{
    int i;
    for (i = 0; i < 4; i++)
        a1[i] = 17;
}

void sub_8055354(void)
{
    u8 val = gUnknown_03004860;
    sub_8055218(word_3002910, val);
    sub_8055280(gUnknown_03000584(val));
    sub_8055340(word_3002910);
}

u16 sub_8055390(u32 a1)
{
    if (ScriptContext2_IsEnabled() == 1)
        return 17;
    if (gLink.recvQueue.count > 4)
        return 27;
    if (gLink.sendQueue.count <= 4)
        return sub_80552B0(a1);
    return 28;
}

u16 sub_80553E0(u32 a1)
{
    return 17;
}

u16 sub_80553E4(u32 a1)
{
    u16 retVal;
    if (ScriptContext2_IsEnabled() == 1)
    {
        retVal = 17;
    }
    else
    {
        retVal = 26;
        sub_80543DC(sub_80553E0);
    }
    return retVal;
}

u16 sub_8055408(u32 a1)
{
    u16 retVal;
    if (gLink.recvQueue.count > 2)
    {
        retVal = 17;
    }
    else
    {
        retVal = 26;
        ScriptContext2_Disable();
        sub_80543DC(sub_80553E0);
    }
    return retVal;
}

u16 sub_8055438(u32 a1)
{
    u16 retVal;
    if (gLink.sendQueue.count > 2)
    {
        retVal = 17;
    }
    else
    {
        retVal = 26;
        ScriptContext2_Disable();
        sub_80543DC(sub_80553E0);
    }
    return retVal;
}

u16 sub_8055468(u32 a1)
{
    return 17;
}

u16 sub_805546C(u32 linkPlayerId)
{
    if (gUnknown_03000580[linkPlayerId] == 0x82 && (gMain.newKeys & B_BUTTON))
    {
        sub_80543DC(sub_8055468);
        return 29;
    }
    else
    {
        return 17;
    }
}

u16 sub_80554A4(u32 a1)
{
    sub_80543DC(sub_805546C);
    return 22;
}

u16 sub_80554B8(u32 a1)
{
    return 17;
}

u16 sub_80554BC(u32 a1)
{
    if (sub_8054F88(0x83) == TRUE)
    {
        ScriptContext1_SetupScript(gUnknown_081A4508);
        sub_80543DC(sub_80554B8);
    }
    return 17;
}

u16 sub_80554E4(u32 a1)
{
    sub_80543DC(sub_80554BC);
    return 23;
}

s32 sub_80554F8(void)
{
    if (sub_8054FC0(0x83) == TRUE)
        return 2;
    if (gUnknown_03000584 == sub_805546C && gUnknown_03000580[gUnknown_03004860] != 0x82)
        return 0;
    if (gUnknown_03000584 == sub_8055468 && gUnknown_03000580[gUnknown_03004860] == 0x81)
        return 2;
    return sub_8054F88(0x82);
}

bool32 unref_sub_8055568(void)
{
    return sub_8054FC0(0x83);
}

u16 sub_8055574(void)
{
    sub_80543DC(sub_80554A4);
    return 0;
}

u16 sub_8055588(void)
{
    sub_80543DC(sub_80553E4);
    return 0;
}

u16 sub_805559C(void)
{
    sub_80543DC(sub_80554E4);
    return 0;
}

void sub_80555B0(int linkPlayerId, int a2, struct UnkStruct_8054FF8 *a3)
{
    s16 x, y;

    a3->a = linkPlayerId;
    a3->b = (linkPlayerId == a2) ? 1 : 0;
    a3->c = gLinkPlayerObjectEvents[linkPlayerId].mode;
    a3->d = sub_8055B30(linkPlayerId);
    sub_8055B08(linkPlayerId, &x, &y);
    a3->sub.x = x;
    a3->sub.y = y;
    a3->sub.height = sub_8055B50(linkPlayerId);
    a3->field_C = MapGridGetMetatileBehaviorAt(x, y);
}

bool32 sub_8055618(struct UnkStruct_8054FF8 *a1)
{
    u8 v1 = a1->c;
    if (v1 == 2 || v1 == 0)
        return TRUE;
    else
        return FALSE;
}

bool32 sub_8055630(struct UnkStruct_8054FF8 *a1)
{
    u8 v1 = a1->c;
    if (v1 == 2 || v1 == 0)
        return TRUE;
    else
        return FALSE;
}

const u8 *sub_8055648(struct UnkStruct_8054FF8 *a1)
{
    if (a1->c != 2)
        return 0;
    return GetCoordEventScriptAtMapPosition(&a1->sub);
}

bool32 sub_8055660(struct UnkStruct_8054FF8 *a1)
{
    if (a1->c != 2 && a1->c != 0)
        return FALSE;
    if (!MetatileBehavior_IsSouthArrowWarp(a1->field_C))
        return FALSE;
    if (a1->d != 1)
        return FALSE;
    return TRUE;
}

const u8 *sub_805568C(struct UnkStruct_8054FF8 *a1)
{
    struct MapPosition unkStruct;
    u8 linkPlayerId;

    if (a1->c && a1->c != 2)
        return 0;

    unkStruct = a1->sub;
    unkStruct.x += gDirectionToVectors[a1->d].x;
    unkStruct.y += gDirectionToVectors[a1->d].y;
    unkStruct.height = 0;
    linkPlayerId = GetLinkPlayerIdAt(unkStruct.x, unkStruct.y);

    if (linkPlayerId != 4)
    {
        if (!a1->b)
            return TradeRoom_TooBusyToNotice;
        if (gUnknown_03000580[linkPlayerId] != 0x80)
            return TradeRoom_TooBusyToNotice;
        if (!sub_8083BF4(linkPlayerId))
            return TradeRoom_ReadTrainerCard1;
        else
            return TradeRoom_ReadTrainerCard2;
    }

    return GetInteractedLinkPlayerScript(&unkStruct, a1->field_C, a1->d);
}

static u16 sub_8055758(const u8 *script)
{
    if (script == DoubleBattleColosseum_EventScript_1A4383)
        return 10;
    if (script == DoubleBattleColosseum_EventScript_1A439E)
        return 9;
    if (script == DoubleBattleColosseum_EventScript_1A43B9)
        return 10;
    if (script == DoubleBattleColosseum_EventScript_1A43D4)
        return 9;
    if (script == RecordCorner_EventScript_1A4418)
        return 10;
    if (script == RecordCorner_EventScript_1A442D)
        return 9;
    if (script == RecordCorner_EventScript_1A4442)
        return 10;
    if (script == RecordCorner_EventScript_1A4457)
        return 9;
    if (script == SingleBattleColosseum_EventScript_1A436F)
        return 10;
    if (script == SingleBattleColosseum_EventScript_1A4379)
        return 9;
    if (script == TradeCenter_EventScript_1A43F0)
        return 10;
    if (script == TradeCenter_EventScript_1A43FA)
        return 9;
    return 0;
}

void sub_80557E8(void)
{
    ScriptContext2_Enable();
}

void sub_80557F4(void)
{
    PlaySE(SE_WIN_OPEN);
    sub_8071310();
    ScriptContext2_Enable();
}

static void sub_8055808(const u8 *script)
{
    PlaySE(SE_SELECT);
    ScriptContext1_SetupScript(script);
    ScriptContext2_Enable();
}

void sub_8055824(void)
{
    PlaySE(SE_WIN_OPEN);
    ScriptContext1_SetupScript(TradeRoom_PromptToCancelLink);
    ScriptContext2_Enable();
}

static void sub_8055840(const u8 *script)
{
    PlaySE(SE_SELECT);
    ScriptContext1_SetupScript(script);
    ScriptContext2_Enable();
}

void sub_805585C(void)
{
    ScriptContext1_SetupScript(TradeRoom_TerminateLink);
    ScriptContext2_Enable();
}

bool32 sub_8055870(void)
{
    if (!is_c1_link_related_active())
        return 0;
    if (gLink.recvQueue.count >= 3)
        gUnknown_03000588 = 1;
    else
        gUnknown_03000588 = 0;
    return gUnknown_03000588;
}

bool32 sub_80558AC(void)
{
    u8 temp;

    if (is_c1_link_related_active() != TRUE)
        return FALSE;

    if (sub_8007B24() != TRUE)
        return FALSE;

    if (gUnknown_03000584 == sub_8055408)
        return TRUE;

    if (gUnknown_03000584 != sub_80553E4)
        return FALSE;

    temp = gUnknown_03000588;
    gUnknown_03000588 = 0;

    if (temp == TRUE)
        return TRUE;

    if (gPaletteFade.active && gPaletteFade.softwareFadeFinishing)
        return TRUE;

    return FALSE;
}

bool32 sub_8055910(void)
{
    if (is_c1_link_related_active() != TRUE)
        return FALSE;

    if (sub_8007B24() != TRUE)
        return FALSE;

    if (gUnknown_03000584 == sub_8055438)
        return TRUE;

    return FALSE;
}

bool32 sub_8055940(void)
{
    if (!sub_8007B24())
        return FALSE;
    return TRUE;
}

void ClearLinkPlayerObjectEvent(struct LinkPlayerObjectEvent *linkPlayerObjEvent)
{
    memset(linkPlayerObjEvent, 0, sizeof(struct LinkPlayerObjectEvent));
}

void ClearLinkPlayerObjectEvents(void)
{
    memset(gLinkPlayerObjectEvents, 0, sizeof(gLinkPlayerObjectEvents));
}

static void ClearObjectEvent(struct ObjectEvent *objEvent)
{
    memset(objEvent, 0, sizeof(struct ObjectEvent));
}

static void SpawnLinkPlayerObjectEvent(u8 linkPlayerId, s16 x, s16 y, u8 a4)
{
    u8 objEventId = GetFirstInactiveObjectEventId();
    struct LinkPlayerObjectEvent *linkPlayerObjEvent = &gLinkPlayerObjectEvents[linkPlayerId];
    struct ObjectEvent *objEvent = &gObjectEvents[objEventId];

    ClearLinkPlayerObjectEvent(linkPlayerObjEvent);
    ClearObjectEvent(objEvent);

    linkPlayerObjEvent->active = 1;
    linkPlayerObjEvent->linkPlayerId = linkPlayerId;
    linkPlayerObjEvent->objEventId = objEventId;
    linkPlayerObjEvent->mode = 0;

    objEvent->active = 1;
    objEvent->singleMovementActive = a4;
    objEvent->range.as_byte = 2;
    objEvent->spriteId = 64;

    InitLinkPlayerObjectEventPos(objEvent, x, y);
}

static void InitLinkPlayerObjectEventPos(struct ObjectEvent *objEvent, s16 x, s16 y)
{
    objEvent->currentCoords.x = x;
    objEvent->currentCoords.y = y;
    objEvent->previousCoords.x = x;
    objEvent->previousCoords.y = y;
    sub_80603CC(x, y, &objEvent->initialCoords.x, &objEvent->initialCoords.y);
    objEvent->initialCoords.x += 8;
    ObjectEventUpdateZCoord(objEvent);
}

void unref_sub_8055A6C(u8 linkPlayerId, u8 a2)
{
    if (gLinkPlayerObjectEvents[linkPlayerId].active)
    {
        u8 objEventId = gLinkPlayerObjectEvents[linkPlayerId].objEventId;
        struct ObjectEvent *objEvent = &gObjectEvents[objEventId];
        objEvent->range.as_byte = a2;
    }
}

void unref_sub_8055A9C(u8 linkPlayerId)
{
    struct LinkPlayerObjectEvent *linkPlayerObjEvent = &gLinkPlayerObjectEvents[linkPlayerId];
    u8 objEventId = linkPlayerObjEvent->objEventId;
    struct ObjectEvent *objEvent = &gObjectEvents[objEventId];
    if (objEvent->spriteId != 64 )
        DestroySprite(&gSprites[objEvent->spriteId]);
    linkPlayerObjEvent->active = 0;
    objEvent->active = 0;
}

u8 sub_8055AE8(u8 linkPlayerId)
{
    u8 objEventId = gLinkPlayerObjectEvents[linkPlayerId].objEventId;
    struct ObjectEvent *objEvent = &gObjectEvents[objEventId];
    return objEvent->spriteId;
}

void sub_8055B08(u8 linkPlayerId, u16 *x, u16 *y)
{
    u8 objEventId = gLinkPlayerObjectEvents[linkPlayerId].objEventId;
    struct ObjectEvent *objEvent = &gObjectEvents[objEventId];
    *x = objEvent->currentCoords.x;
    *y = objEvent->currentCoords.y;
}

u8 sub_8055B30(u8 linkPlayerId)
{
    u8 objEventId = gLinkPlayerObjectEvents[linkPlayerId].objEventId;
    struct ObjectEvent *objEvent = &gObjectEvents[objEventId];
    return objEvent->range.as_byte;
}

u8 sub_8055B50(u8 linkPlayerId)
{
    u8 objEventId = gLinkPlayerObjectEvents[linkPlayerId].objEventId;
    struct ObjectEvent *objEvent = &gObjectEvents[objEventId];
    return objEvent->currentElevation;
}

s32 unref_sub_8055B74(u8 linkPlayerId)
{
    u8 objEventId = gLinkPlayerObjectEvents[linkPlayerId].objEventId;
    struct ObjectEvent *objEvent = &gObjectEvents[objEventId];
    return 16 - (s8)objEvent->directionSequenceIndex;
}

static u8 GetLinkPlayerIdAt(s16 x, s16 y)
{
    u8 i;
    for (i = 0; i < 4; i++)
    {
        if (gLinkPlayerObjectEvents[i].active
         && (gLinkPlayerObjectEvents[i].mode == 0 || gLinkPlayerObjectEvents[i].mode == 2))
        {
            struct ObjectEvent *objEvent = &gObjectEvents[gLinkPlayerObjectEvents[i].objEventId];
            if (objEvent->currentCoords.x == x && objEvent->currentCoords.y == y)
                return i;
        }
    }
    return 4;
}

void sub_8055BFC(u8 linkPlayerId, u8 a2)
{
    struct LinkPlayerObjectEvent *linkPlayerObjEvent = &gLinkPlayerObjectEvents[linkPlayerId];
    u8 objEventId = linkPlayerObjEvent->objEventId;
    struct ObjectEvent *objEvent = &gObjectEvents[objEventId];

    if (linkPlayerObjEvent->active)
    {
        if (a2 > 10)
            objEvent->triggerGroundEffectsOnMove = 1;
        else
            gUnknown_082166D8[gUnknown_082166A0[linkPlayerObjEvent->mode](linkPlayerObjEvent, objEvent, a2)](linkPlayerObjEvent, objEvent);
    }
}

static u8 sub_8055C68(struct LinkPlayerObjectEvent *linkPlayerObjEvent, struct ObjectEvent *objEvent, u8 a3)
{
    return gUnknown_082166AC[a3](linkPlayerObjEvent, objEvent, a3);
}

static u8 sub_8055C88(struct LinkPlayerObjectEvent *linkPlayerObjEvent, struct ObjectEvent *objEvent, u8 a3)
{
    return 1;
}

static u8 sub_8055C8C(struct LinkPlayerObjectEvent *linkPlayerObjEvent, struct ObjectEvent *objEvent, u8 a3)
{
    return gUnknown_082166AC[a3](linkPlayerObjEvent, objEvent, a3);
}

static u8 sub_8055CAC(struct LinkPlayerObjectEvent *linkPlayerObjEvent, struct ObjectEvent *objEvent, u8 a3)
{
    return 0;
}

static u8 sub_8055CB0(struct LinkPlayerObjectEvent *linkPlayerObjEvent, struct ObjectEvent *objEvent, u8 a3)
{
    s16 x, y;

    objEvent->range.as_byte = npc_something3(a3, objEvent->range.as_byte);
    ObjectEventMoveDestCoords(objEvent, objEvent->range.as_byte, &x, &y);

    if (LinkPlayerDetectCollision(linkPlayerObjEvent->objEventId, objEvent->range.as_byte, x, y))
    {
        return 0;
    }
    else
    {
        objEvent->directionSequenceIndex = 16;
        ShiftObjectEventCoords(objEvent, x, y);
        ObjectEventUpdateZCoord(objEvent);
        return 1;
    }
}

static u8 sub_8055D18(struct LinkPlayerObjectEvent *linkPlayerObjEvent, struct ObjectEvent *objEvent, u8 a3)
{
    objEvent->range.as_byte = npc_something3(a3, objEvent->range.as_byte);
    return 0;
}

static void sub_8055D30(struct LinkPlayerObjectEvent *linkPlayerObjEvent, struct ObjectEvent *objEvent)
{
    linkPlayerObjEvent->mode = 0;
}

static void sub_8055D38(struct LinkPlayerObjectEvent *linkPlayerObjEvent, struct ObjectEvent *objEvent)
{
    objEvent->directionSequenceIndex--;
    linkPlayerObjEvent->mode = 1;
    MoveCoords(objEvent->range.as_byte, &objEvent->initialCoords.x, &objEvent->initialCoords.y);
    if (!objEvent->directionSequenceIndex)
    {
        ShiftStillObjectEventCoords(objEvent);
        linkPlayerObjEvent->mode = 2;
    }
}

static u8 npc_something3(u8 a1, u8 a2)
{
    switch (a1 - 1)
    {
    case 0:
    case 6:
        return 2;
    case 1:
    case 7:
        return 1;
    case 2:
    case 8:
        return 3;
    case 3:
    case 9:
        return 4;
    }
    return a2;
}

static u8 LinkPlayerDetectCollision(u8 selfObjEventId, u8 a2, s16 x, s16 y)
{
    u8 i;
    for (i = 0; i < 16; i++)
    {
        if (i != selfObjEventId)
        {
            if ((gObjectEvents[i].currentCoords.x == x && gObjectEvents[i].currentCoords.y == y)
             || (gObjectEvents[i].previousCoords.x == x && gObjectEvents[i].previousCoords.y == y))
            {
                return 1;
            }
        }
    }
    return MapGridGetCollisionAt(x, y);
}

static void CreateLinkPlayerSprite(u8 linkPlayerId)
{
    struct LinkPlayerObjectEvent *linkPlayerObjEvent = &gLinkPlayerObjectEvents[linkPlayerId];
    u8 objEventId = linkPlayerObjEvent->objEventId;
    struct ObjectEvent *objEvent = &gObjectEvents[objEventId];
    struct Sprite *sprite;

    if (linkPlayerObjEvent->active)
    {
        u8 val = GetRivalAvatarGraphicsIdByStateIdAndGender(PLAYER_AVATAR_STATE_NORMAL, objEvent->singleMovementActive);
        objEvent->spriteId = AddPseudoObjectEvent(val, SpriteCB_LinkPlayer, 0, 0, 0);
        sprite = &gSprites[objEvent->spriteId];
        sprite->coordOffsetEnabled = TRUE;
        sprite->data[0] = linkPlayerId;
        objEvent->triggerGroundEffectsOnMove = 0;
    }
}

static void SpriteCB_LinkPlayer(struct Sprite *sprite)
{
    struct LinkPlayerObjectEvent *linkPlayerObjEvent = &gLinkPlayerObjectEvents[sprite->data[0]];
    struct ObjectEvent *objEvent = &gObjectEvents[linkPlayerObjEvent->objEventId];
    sprite->x = objEvent->initialCoords.x;
    sprite->y = objEvent->initialCoords.y;
    SetObjectSubpriorityByZCoord(objEvent->previousElevation, sprite, 1);
    sprite->oam.priority = ZCoordToPriority(objEvent->previousElevation);
    if (!linkPlayerObjEvent->mode)
        StartSpriteAnim(sprite, GetFaceDirectionAnimNum(objEvent->range.as_byte));
    else
        StartSpriteAnimIfDifferent(sprite, GetMoveDirectionAnimNum(objEvent->range.as_byte));
    UpdateObjectEventSpriteVisibility(sprite, 0);
    if (objEvent->triggerGroundEffectsOnMove)
    {
        sprite->invisible = ((sprite->data[7] & 4) >> 2);
        sprite->data[7]++;
    }
}
