#include "global.h"
#include "overworld.h"
#include "battle_pyramid.h"
#include "battle_setup.h"
#include "berry.h"
#include "bg.h"
#include "cable_club.h"
#include "clock.h"
#include "event_data.h"
#include "field_camera.h"
#include "field_control_avatar.h"
#include "field_effect.h"
#include "event_object_movement.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.h"
#include "gpu_regs.h"
#include "heal_location.h"
#include "link.h"
#include "link_rfu.h"
#include "load_save.h"
#include "main.h"
#include "alloc.h"
#include "m4a.h"
#include "map_name_popup.h"
#include "match_call.h"
#include "menu.h"
#include "metatile_behavior.h"
#include "mirage_tower.h"
#include "money.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 "save.h"
#include "save_location.h"
#include "script.h"
#include "script_pokemon_util_80F87D8.h"
#include "secret_base.h"
#include "sound.h"
#include "start_menu.h"
#include "task.h"
#include "tileset_anims.h"
#include "time_events.h"
#include "trainer_hill.h"
#include "trainer_pokemon_sprites.h"
#include "tv.h"
#include "scanline_effect.h"
#include "wild_encounter.h"
#include "frontier_util.h"
#include "constants/abilities.h"
#include "constants/layouts.h"
#include "constants/map_types.h"
#include "constants/maps.h"
#include "constants/region_map_sections.h"
#include "constants/songs.h"
#include "constants/species.h"
#include "constants/weather.h"

// event scripts
extern const u8 EventScript_WhiteOut[];
extern const u8 EventScript_271862[];
extern const u8 EventScript_277513[];
extern const u8 EventScript_TradeRoom_TooBusyToNotice[];
extern const u8 EventScript_TradeRoom_ReadTrainerCard1[];
extern const u8 EventScript_TradeRoom_ReadTrainerCard2[];
extern const u8 gUnknown_08277388[];
extern const u8 gUnknown_082773A3[];
extern const u8 gUnknown_082773BE[];
extern const u8 gUnknown_082773D9[];
extern const u8 gUnknown_0827741D[];
extern const u8 gUnknown_08277432[];
extern const u8 gUnknown_08277447[];
extern const u8 gUnknown_0827745C[];
extern const u8 gUnknown_08277374[];
extern const u8 gUnknown_0827737E[];
extern const u8 gUnknown_082773FF[];
extern const u8 gUnknown_082773F5[];
extern const u8 gUnknown_082774EF[];
extern const u8 gUnknown_08277509[];

extern const struct MapLayout *const gMapLayouts[];
extern const struct MapHeader *const *const gMapGroups[];
extern const int gMaxFlashLevel;
extern const u16 gUnknown_82EC7C4[];

static void Overworld_ResetStateAfterWhiteOut(void);
static void c2_80567AC(void);
static void CB2_LoadMap2(void);
static void VBlankCB_Field(void);
static void SpriteCB_LinkPlayer(struct Sprite *sprite);
static void ChooseAmbientCrySpecies(void);
static void do_load_map_stuff_loop(u8 *state);
static bool32 map_loading_iteration_3(u8 *state);
static bool32 sub_8086638(u8 *state);
static bool32 load_map_stuff(u8 *state, u32);
static bool32 map_loading_iteration_2_link(u8 *state);
static void mli4_mapscripts_and_other(void);
static void InitOverworldGraphicsRegisters(void);
static u8 sub_8087858(u8);
static u16 sub_80871C0(u32 a1);
static void sub_80867C8(void);
static void sub_80867D8(void);
static void sub_8086AE4(void);
static void sub_80869DC(void);
static void sub_8086B14(void);
static void sub_8086AAC(void);
static void sub_8086988(bool32 arg0);
static void sub_8086A80(void);
static void sub_8086A68(void);
static void sub_8086860(void);
static void sub_8086AC8(void);
static void sub_8086B9C(void);
static void sub_8086C40(void);
static void sub_8086C90(void);
static void sub_8086FA0(u16);
static void sub_8086F38(u16*, s32);
static u8 npc_something3(u8 a1, u8 a2);
static u8 LinkPlayerDetectCollision(u8 selfEventObjId, u8 a2, s16 x, s16 y);
static void CreateLinkPlayerSprite(u8 linkPlayerId, u8 gameVersion);
static void sub_8087878(u8 linkPlayerId, u16 *x, u16 *y);
static u8 sub_80878A0(u8 linkPlayerId);
static u8 sub_80878C0(u8 linkPlayerId);
static s32 sub_80878E4(u8 linkPlayerId);
static u8 GetLinkPlayerIdAt(s16 x, s16 y);
static void sub_808796C(u8 linkPlayerId, u8 a2);
static void ZeroEventObject(struct EventObject *eventObj);
static void SpawnLinkPlayerEventObject(u8 linkPlayerId, s16 x, s16 y, u8 a4);
static void InitLinkPlayerEventObjectPos(struct EventObject *eventObj, s16 x, s16 y);
static void sub_80877DC(u8 linkPlayerId, u8 a2);
static void sub_808780C(u8 linkPlayerId);
static u8 sub_8087858(u8 linkPlayerId);
static void sub_8087584(void);
static u32 sub_8087690(void);
static void ZeroLinkPlayerEventObject(struct LinkPlayerEventObject *linkPlayerEventObj);
static const u8 *sub_80873B4(struct UnkStruct_8054FF8 *a1);
static u16 sub_8087480(const u8 *script);
static void sub_8087510(void);
static void sub_808751C(void);
static void sub_8087530(const u8 *script);
static void sub_808754C(void);
static void sub_8087568(const u8 *script);
static void sub_80872D8(s32 linkPlayerId, s32 a2, struct UnkStruct_8054FF8 *a3);
static bool32 sub_8087340(struct UnkStruct_8054FF8 *a1);
static bool32 sub_8087358(struct UnkStruct_8054FF8 *a1);
static u8 *sub_8087370(struct UnkStruct_8054FF8 *a1);
static bool32 sub_8087388(struct UnkStruct_8054FF8 *a1);
static const u8 *sub_80873B4(struct UnkStruct_8054FF8 *a1);
static u16 sub_808711C(u32);
static u16 sub_8087140(u32);
static void sub_808709C(u16 *a1);
static u16 sub_80870B0(u32 a1);
static u16 sub_80870F8(u32 a1);
static u16 sub_8087068(u16 a1);
static void c1_link_related(void);
static void c1_link_related_func_set(u16 (*func)(u32));
static void SetFieldVBlankCallback(void);
static void FieldClearVBlankHBlankCallbacks(void);
static void sub_8085810(void);
static u8 GetAdjustedInitialTransitionFlags(struct InitialPlayerAvatarState *playerStruct, u16 a2, u8 a3);
static u8 GetAdjustedInitialDirection(struct InitialPlayerAvatarState *playerStruct, u8 a2, u16 a3, u8 a4);
static u16 GetCenterScreenMetatileBehavior(void);

// IWRAM bss vars
IWRAM_DATA static void *sUnknown_03000E0C;
IWRAM_DATA static u8 sUnknown_03000E10[4];
IWRAM_DATA static u16 (*sUnknown_03000E14)(u32);
IWRAM_DATA static u8 sUnknown_03000E18;
IWRAM_DATA static u8 sUnknown_03000E19;
IWRAM_DATA static u32 sUnusedVar;

// IWRAM common
u16 *gBGTilemapBuffers1;
u16 *gBGTilemapBuffers2;
u16 *gBGTilemapBuffers3;
u16 gUnknown_03005DA8;
void (*gFieldCallback)(void);
bool8 (*gFieldCallback2)(void);
u8 gUnknown_03005DB4;
u8 gFieldLinkPlayerCount;

// EWRAM vars
EWRAM_DATA static u8 sUnknown_020322D8 = 0;
EWRAM_DATA struct WarpData gLastUsedWarp = {0};
EWRAM_DATA static struct WarpData sWarpDestination = {0};  // new warp position
EWRAM_DATA static struct WarpData gFixedDiveWarp = {0};
EWRAM_DATA static struct WarpData gFixedHoleWarp = {0};
EWRAM_DATA static u16 sLastMapSectionId = 0;
EWRAM_DATA static struct InitialPlayerAvatarState gInitialPlayerAvatarState = {0};
EWRAM_DATA static u16 sAmbientCrySpecies = 0;
EWRAM_DATA static bool8 sIsAmbientCryWaterMon = FALSE;
EWRAM_DATA struct LinkPlayerEventObject gLinkPlayerEventObjects[4] = {0};

// const rom data
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[] =
{
    [DIR_NONE] =
    {
        .x =  0,
        .y =  0,
    },
    [DIR_SOUTH] =
    {
        .x =  0,
        .y =  1,
    },
    [DIR_NORTH] =
    {
        .x =  0,
        .y = -1,
    },
    [DIR_WEST] =
    {
        .x = -1,
        .y =  0,
    },
    [DIR_EAST] =
    {
        .x =  1,
        .y =  0,
    },
    [DIR_SOUTHWEST] =
    {
        .x = -1,
        .y =  1,
    },
    [DIR_SOUTHEAST] =
    {
        .x =  1,
        .y =  1,
    },
    [DIR_NORTHWEST] =
    {
        .x = -1,
        .y = -1,
    },
    [DIR_NORTHEAST] =
    {
        .x =  1,
        .y = -1,
    },
};

static const struct BgTemplate sOverworldBgTemplates[] =
{
    {
        .bg = 0,
        .charBaseIndex = 2,
        .mapBaseIndex = 31,
        .screenSize = 0,
        .paletteMode = 0,
        .priority = 0,
        .baseTile = 0
    },
    {
        .bg = 1,
        .charBaseIndex = 0,
        .mapBaseIndex = 29,
        .screenSize = 0,
        .paletteMode = 0,
        .priority = 1,
        .baseTile = 0
    },
    {
        .bg = 2,
        .charBaseIndex = 0,
        .mapBaseIndex = 28,
        .screenSize = 0,
        .paletteMode = 0,
        .priority = 2,
        .baseTile = 0
    },
    {
        .bg = 3,
        .charBaseIndex = 0,
        .mapBaseIndex = 30,
        .screenSize = 0,
        .paletteMode = 0,
        .priority = 3,
        .baseTile = 0
    }
};

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_80879D8(struct LinkPlayerEventObject *, struct EventObject *, u8);
static u8 sub_80879F8(struct LinkPlayerEventObject *, struct EventObject *, u8);
static u8 sub_80879FC(struct LinkPlayerEventObject *, struct EventObject *, u8);

static u8 (*const gUnknown_08339DC8[])(struct LinkPlayerEventObject *, struct EventObject *, u8) =
{
    sub_80879D8,
    sub_80879F8,
    sub_80879FC,
};

static u8 sub_8087A1C(struct LinkPlayerEventObject *, struct EventObject *, u8);
static u8 sub_8087A20(struct LinkPlayerEventObject *, struct EventObject *, u8);
static u8 sub_8087A88(struct LinkPlayerEventObject *, struct EventObject *, u8);

static u8 (*const gUnknown_08339DD4[])(struct LinkPlayerEventObject *, struct EventObject *, u8) =
{
    sub_8087A1C,
    sub_8087A20,
    sub_8087A20,
    sub_8087A20,
    sub_8087A20,
    sub_8087A1C,
    sub_8087A1C,
    sub_8087A88,
    sub_8087A88,
    sub_8087A88,
    sub_8087A88,
};

static void sub_8087AA0(struct LinkPlayerEventObject *, struct EventObject *);
static void sub_8087AA8(struct LinkPlayerEventObject *, struct EventObject *);

static void (*const gUnknown_08339E00[])(struct LinkPlayerEventObject *, struct EventObject *) =
{
    sub_8087AA0,
    sub_8087AA8,
};

// code
void DoWhiteOut(void)
{
    ScriptContext2_RunNewScript(EventScript_WhiteOut);
    SetMoney(&gSaveBlock1Ptr->money, GetMoney(&gSaveBlock1Ptr->money) / 2);
    HealPlayerParty();
    Overworld_ResetStateAfterWhiteOut();
    SetWarpDestinationToLastHealLocation();
    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_271862);
}

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);
    if (VarGet(VAR_0x4039) == 1)
    {
        VarSet(VAR_0x4039, 0);
        VarSet(VAR_0x4037, 0);
    }
}

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

void ResetGameStats(void)
{
    s32 i;

    for (i = 0; i < NUM_GAME_STATS; i++)
        SetGameStat(i, 0);
}

void IncrementGameStat(u8 index)
{
    if (index < NUM_USED_GAME_STATS)
    {
        u32 statVal = GetGameStat(index);
        if (statVal < 0xFFFFFF)
            statVal++;
        else
            statVal = 0xFFFFFF;

        SetGameStat(index, statVal);
    }
}

u32 GetGameStat(u8 index)
{
    if (index >= NUM_USED_GAME_STATS)
        return 0;

    return gSaveBlock1Ptr->gameStats[index] ^ gSaveBlock2Ptr->encryptionKey;
}

void SetGameStat(u8 index, u32 value)
{
    if (index < NUM_USED_GAME_STATS)
        gSaveBlock1Ptr->gameStats[index] = value ^ gSaveBlock2Ptr->encryptionKey;
}

void ApplyNewEncryptionKeyToGameStats(u32 newKey)
{
    u8 i;

    for (i = 0; i < NUM_GAME_STATS; i++)
        ApplyNewEncryptionKeyToWord(&gSaveBlock1Ptr->gameStats[i], newKey);
}

void LoadEventObjTemplatesFromHeader(void)
{
    // Clear map object templates
    CpuFill32(0, gSaveBlock1Ptr->eventObjectTemplates, sizeof(gSaveBlock1Ptr->eventObjectTemplates));

    // Copy map header events to save block
    CpuCopy32(gMapHeader.events->eventObjects,
              gSaveBlock1Ptr->eventObjectTemplates,
              gMapHeader.events->eventObjectCount * sizeof(struct EventObjectTemplate));
}

void LoadSaveblockEventObjScripts(void)
{
    struct EventObjectTemplate *mapHeaderObjTemplates = gMapHeader.events->eventObjects;
    struct EventObjectTemplate *savObjTemplates = gSaveBlock1Ptr->eventObjectTemplates;
    s32 i;

    for (i = 0; i < EVENT_OBJECT_TEMPLATES_COUNT; i++)
        savObjTemplates[i].script = mapHeaderObjTemplates[i].script;
}

void Overworld_SetEventObjTemplateCoords(u8 localId, s16 x, s16 y)
{
    s32 i;
    struct EventObjectTemplate *savObjTemplates = gSaveBlock1Ptr->eventObjectTemplates;

    for (i = 0; i < EVENT_OBJECT_TEMPLATES_COUNT; i++)
    {
        struct EventObjectTemplate *eventObjectTemplate = &savObjTemplates[i];
        if (eventObjectTemplate->localId == localId)
        {
            eventObjectTemplate->x = x;
            eventObjectTemplate->y = y;
            return;
        }
    }
}

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

    struct EventObjectTemplate *savObjTemplates = gSaveBlock1Ptr->eventObjectTemplates;
    for (i = 0; i < EVENT_OBJECT_TEMPLATES_COUNT; i++)
    {
        struct EventObjectTemplate *eventObjectTemplate = &savObjTemplates[i];
        if (eventObjectTemplate->localId == localId)
        {
            eventObjectTemplate->movementType = movementType;
            return;
        }
    }
}

static void mapdata_load_assets_to_gpu_and_full_redraw(void)
{
    move_tilemap_camera_to_upper_left_corner();
    copy_map_tileset1_tileset2_to_vram(gMapHeader.mapLayout);
    apply_map_tileset1_tileset2_palette(gMapHeader.mapLayout);
    DrawWholeMapView();
    cur_mapheader_run_tileset_funcs_after_some_cpuset();
}

const struct MapLayout *GetMapLayout(void)
{
    u16 mapLayoutId = gSaveBlock1Ptr->mapLayoutId;
    if (mapLayoutId)
        return gMapLayouts[mapLayoutId - 1];
    return NULL;
}

void ApplyCurrentWarp(void)
{
    gLastUsedWarp = gSaveBlock1Ptr->location;
    gSaveBlock1Ptr->location = sWarpDestination;
    gFixedDiveWarp = sDummyWarpData;
    gFixedHoleWarp = sDummyWarpData;
}

static void ClearDiveAndHoleWarps(void)
{
    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;
    else if (warp->mapNum != -1)
        return FALSE;
    else if (warp->warpId != -1)
        return FALSE;
    else if (warp->x != -1)
        return FALSE;
    else if (warp->y != -1)
        return FALSE;
    else
        return TRUE;
}

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

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

static void LoadCurrentMapData(void)
{
    sLastMapSectionId = gMapHeader.regionMapSectionId;
    gMapHeader = *Overworld_GetMapHeaderByGroupAndId(gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum);
    gSaveBlock1Ptr->mapLayoutId = gMapHeader.mapLayoutId;
    gMapHeader.mapLayout = GetMapLayout();
}

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

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

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

void SetWarpDestination(s8 mapGroup, s8 mapNum, s8 warpId, s8 x, s8 y)
{
    SetWarpData(&sWarpDestination, mapGroup, mapNum, warpId, x, y);
}

void SetWarpDestinationToMapWarp(s8 mapGroup, s8 mapNum, s8 warpId)
{
    SetWarpDestination(mapGroup, mapNum, warpId, -1, -1);
}

void SetDynamicWarp(s32 unused, s8 mapGroup, s8 mapNum, s8 warpId)
{
    SetWarpData(&gSaveBlock1Ptr->dynamicWarp, mapGroup, mapNum, warpId, gSaveBlock1Ptr->pos.x, gSaveBlock1Ptr->pos.y);
}

void SetDynamicWarpWithCoords(s32 unused, s8 mapGroup, s8 mapNum, s8 warpId, s8 x, s8 y)
{
    SetWarpData(&gSaveBlock1Ptr->dynamicWarp, mapGroup, mapNum, warpId, x, y);
}

void SetWarpDestinationToDynamicWarp(u8 unusedWarpId)
{
    sWarpDestination = gSaveBlock1Ptr->dynamicWarp;
}

void SetWarpDestinationToHealLocation(u8 healLocationId)
{
    const struct HealLocation *warp = GetHealLocation(healLocationId);
    if (warp)
        SetWarpDestination(warp->group, warp->map, -1, warp->x, warp->y);
}

void SetWarpDestinationToLastHealLocation(void)
{
    sWarpDestination = gSaveBlock1Ptr->lastHealLocation;
}

void SetLastHealLocationWarp(u8 healLocationId)
{
    const struct HealLocation *healLocation = GetHealLocation(healLocationId);
    if (healLocation)
        SetWarpData(&gSaveBlock1Ptr->lastHealLocation, healLocation->group, healLocation->map, -1, healLocation->x, healLocation->y);
}

void UpdateEscapeWarp(s16 x, s16 y)
{
    u8 currMapType = GetCurrentMapType();
    u8 destMapType = GetMapTypeByGroupAndId(sWarpDestination.mapGroup, sWarpDestination.mapNum);
    if (is_map_type_1_2_3_5_or_6(currMapType) && is_map_type_1_2_3_5_or_6(destMapType) != TRUE)
        SetEscapeWarp(gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum, -1, x - 7, y - 6);
}

void SetEscapeWarp(s8 mapGroup, s8 mapNum, s8 warpId, s8 x, s8 y)
{
    SetWarpData(&gSaveBlock1Ptr->escapeWarp, mapGroup, mapNum, warpId, x, y);
}

void SetWarpDestinationToEscapeWarp(void)
{
    sWarpDestination = gSaveBlock1Ptr->escapeWarp;
}

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

static void SetWarpDestinationToDiveWarp(void)
{
    sWarpDestination = gFixedDiveWarp;
}

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

void SetWarpDestinationToFixedHoleWarp(s16 x, s16 y)
{
    if (IsDummyWarp(&gFixedHoleWarp) == TRUE)
        sWarpDestination = gLastUsedWarp;
    else
        SetWarpDestination(gFixedHoleWarp.mapGroup, gFixedHoleWarp.mapNum, -1, x, y);
}

static void SetWarpDestinationToContinueGameWarp(void)
{
    sWarpDestination = gSaveBlock1Ptr->continueGameWarp;
}

void SetContinueGameWarp(s8 mapGroup, s8 mapNum, s8 warpId, s8 x, s8 y)
{
    SetWarpData(&gSaveBlock1Ptr->continueGameWarp, mapGroup, mapNum, warpId, x, y);
}

void SetContinueGameWarpToHealLocation(u8 healLocationId)
{
    const struct HealLocation *warp = GetHealLocation(healLocationId);
    if (warp)
        SetWarpData(&gSaveBlock1Ptr->continueGameWarp, warp->group, warp->map, -1, warp->x, warp->y);
}

void SetContinueGameWarpToDynamicWarp(int unused)
{
    gSaveBlock1Ptr->continueGameWarp = gSaveBlock1Ptr->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 dir, u16 x, u16 y)
{
    const struct MapConnection *connection = GetMapConnection(dir);

    if (connection != NULL)
    {
        SetWarpDestination(connection->mapGroup, connection->mapNum, -1, x, y);
    }
    else
    {
        mapheader_run_script_with_tag_x6();
        if (IsDummyWarp(&gFixedDiveWarp))
            return FALSE;
        SetWarpDestinationToDiveWarp();
    }
    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 mliX_load_map(u8 mapGroup, u8 mapNum)
{
    s32 paletteIndex;

    SetWarpDestination(mapGroup, mapNum, -1, -1, -1);
    if (gMapHeader.regionMapSectionId != 0x3A)
        sub_8085810();

    ApplyCurrentWarp();
    LoadCurrentMapData();
    LoadEventObjTemplatesFromHeader();
    TrySetMapSaveWarpStatus();
    ClearTempFieldEventData();
    ResetCyclingRoadChallengeData();
    RestartWildEncounterImmunitySteps();
    TryUpdateRandomTrainerRematches(mapGroup, mapNum);
    DoTimeBasedEvents();
    SetSav1WeatherFromCurrMapHeader();
    ChooseAmbientCrySpecies();
    SetDefaultFlashLevel();
    Overworld_ClearSavedMusic();
    mapheader_run_script_with_tag_x3();
    InitMap();
    copy_map_tileset2_to_vram_2(gMapHeader.mapLayout);
    apply_map_tileset2_palette(gMapHeader.mapLayout);

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

    sub_80A0A2C();
    UpdateLocationHistoryForRoamer();
    RoamerMove();
    DoCurrentWeather();
    ResetFieldTasksArgs();
    mapheader_run_script_with_tag_x5();

    if (gMapHeader.regionMapSectionId != MAPSEC_BATTLE_FRONTIER || gMapHeader.regionMapSectionId != sLastMapSectionId)
        ShowMapNamePopup();
}

static void mli0_load_map(u32 a1)
{
    bool8 v2;
    bool8 indoors;

    LoadCurrentMapData();
    if (!(sUnknown_020322D8 & 1))
    {
        if (gMapHeader.mapLayoutId == LAYOUT_BATTLE_FRONTIER_BATTLE_PYRAMID_EMPTY_SQUARE)
            LoadBattlePyramidEventObjectTemplates();
        else if (InTrainerHill())
            sub_81D5DF8();
        else
            LoadEventObjTemplatesFromHeader();
    }

    v2 = is_map_type_1_2_3_5_or_6(gMapHeader.mapType);
    indoors = Overworld_MapTypeIsIndoors(gMapHeader.mapType);

    sub_80EB218();
    TrySetMapSaveWarpStatus();
    ClearTempFieldEventData();
    ResetCyclingRoadChallengeData();
    RestartWildEncounterImmunitySteps();
    TryUpdateRandomTrainerRematches(gSaveBlock1Ptr->location.mapGroup, gSaveBlock1Ptr->location.mapNum);
    if (a1 != 1)
        DoTimeBasedEvents();
    SetSav1WeatherFromCurrMapHeader();
    ChooseAmbientCrySpecies();
    if (v2)
        FlagClear(FLAG_SYS_USE_FLASH);
    SetDefaultFlashLevel();
    Overworld_ClearSavedMusic();
    mapheader_run_script_with_tag_x3();
    UpdateLocationHistoryForRoamer();
    RoamerMoveToOtherLocationSet();
    if (gMapHeader.mapLayoutId == LAYOUT_BATTLE_FRONTIER_BATTLE_PYRAMID_EMPTY_SQUARE)
        InitBattlePyramidMap(FALSE);
    else if (InTrainerHill())
        InitTrainerHillMap();
    else
        InitMap();

    if (a1 != 1 && indoors)
    {
        UpdateTVScreensOnMap(gBackupMapLayout.width, gBackupMapLayout.height);
        sub_80E9238(1);
    }
}

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 = GetCurrentMapType();
    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;
    else if (mapType == MAP_TYPE_UNDERWATER)
        return PLAYER_AVATAR_FLAG_UNDERWATER;
    else if (MetatileBehavior_IsSurfableWaterOrUnderwater(metatileBehavior) == TRUE)
        return PLAYER_AVATAR_FLAG_SURFING;
    else if (Overworld_IsBikingAllowed() != TRUE)
        return PLAYER_AVATAR_FLAG_ON_FOOT;
    else if (playerStruct->transitionFlags == PLAYER_AVATAR_FLAG_MACH_BIKE)
        return PLAYER_AVATAR_FLAG_MACH_BIKE;
    else if (playerStruct->transitionFlags != PLAYER_AVATAR_FLAG_ACRO_BIKE)
        return PLAYER_AVATAR_FLAG_ON_FOOT;
    else
        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;
    else if (MetatileBehavior_IsDeepSouthWarp(metatileBehavior) == TRUE)
        return DIR_NORTH;
    else if (MetatileBehavior_IsNonAnimDoor(metatileBehavior) == TRUE || MetatileBehavior_IsDoor(metatileBehavior) == TRUE)
        return DIR_SOUTH;
    else if (MetatileBehavior_IsSouthArrowWarp(metatileBehavior) == TRUE)
        return DIR_NORTH;
    else if (MetatileBehavior_IsNorthArrowWarp(metatileBehavior) == TRUE)
        return DIR_SOUTH;
    else if (MetatileBehavior_IsWestArrowWarp(metatileBehavior) == TRUE)
        return DIR_EAST;
    else if (MetatileBehavior_IsEastArrowWarp(metatileBehavior) == TRUE)
        return DIR_WEST;
    else 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;
    else if (MetatileBehavior_IsLadder(metatileBehavior) == TRUE)
        return playerStruct->direction;
    else
        return DIR_SOUTH;
}

static u16 GetCenterScreenMetatileBehavior(void)
{
    return MapGridGetMetatileBehaviorAt(gSaveBlock1Ptr->pos.x + 7, gSaveBlock1Ptr->pos.y + 7);
}

bool32 Overworld_IsBikingAllowed(void)
{
    if (!(gMapHeader.flags & 1))
        return FALSE;
    else
        return TRUE;
}

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

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

u8 Overworld_GetFlashLevel(void)
{
    return gSaveBlock1Ptr->flashLevel;
}

void SetCurrentMapLayout(u16 mapLayoutId)
{
    gSaveBlock1Ptr->mapLayoutId = mapLayoutId;
    gMapHeader.mapLayout = GetMapLayout();
}

void sub_8085540(u8 var)
{
    sUnknown_020322D8 = var;
}

u8 sub_808554C(void)
{
    return sUnknown_020322D8;
}

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):
        case MAP_NUM(ROUTE124):
        case MAP_NUM(ROUTE125):
        case MAP_NUM(ROUTE126):
        case MAP_NUM(ROUTE127):
        case MAP_NUM(ROUTE128):
            return TRUE;
        default:
            if (VarGet(VAR_0x405E) < 4)
                return FALSE;
            switch (warp->mapNum)
            {
            case MAP_NUM(ROUTE129):
            case MAP_NUM(ROUTE130):
            case MAP_NUM(ROUTE131):
                return TRUE;
            }
        }
    }
    return FALSE;
}

static bool16 NoMusicInSotopolisWithLegendaries(struct WarpData *warp)
{
    if (VarGet(VAR_0x40CA) != 1)
        return FALSE;
    else if (warp->mapGroup != MAP_GROUP(SOOTOPOLIS_CITY))
        return FALSE;
    else if (warp->mapNum == MAP_NUM(SOOTOPOLIS_CITY))
        return TRUE;
    else
        return FALSE;
}

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

static bool16 IsInflitratedSpaceCenter(struct WarpData *warp)
{
    if (VarGet(VAR_0x405D) == 0)
        return FALSE;
    else if (VarGet(VAR_0x405D) > 2)
        return FALSE;
    else if (warp->mapGroup != MAP_GROUP(MOSSDEEP_CITY_SPACE_CENTER_1F))
        return FALSE;
    else if (warp->mapNum == MAP_NUM(MOSSDEEP_CITY_SPACE_CENTER_1F)
     || warp->mapNum == MAP_NUM(MOSSDEEP_CITY_SPACE_CENTER_2F))
        return TRUE;
    return FALSE;
}

u16 GetLocationMusic(struct WarpData *warp)
{
    if (NoMusicInSotopolisWithLegendaries(warp) == TRUE)
        return 0xFFFF;
    else if (ShouldLegendaryMusicPlayAtLocation(warp) == TRUE)
        return MUS_OOAME;
    else if (IsInflitratedSpaceCenter(warp) == TRUE)
        return MUS_MGM0;
    else if (IsInfiltratedWeatherInstitute(warp) == TRUE)
        return MUS_TOZAN;
    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 (gSaveBlock1Ptr->location.mapGroup == MAP_GROUP(ROUTE111)
     && gSaveBlock1Ptr->location.mapNum == MAP_NUM(ROUTE111)
     && GetSav1Weather() == WEATHER_SANDSTORM)
        return MUS_ASHROAD;

    music = GetLocationMusic(&gSaveBlock1Ptr->location);
    if (music != MUS_ROUTE_118)
    {
        return music;
    }
    else
    {
        if (gSaveBlock1Ptr->pos.x < 24)
            return MUS_DOORO_X1;
        else
            return MUS_GRANROAD;
    }
}

u16 GetWarpDestinationMusic(void)
{
    u16 music = GetLocationMusic(&sWarpDestination);
    if (music != MUS_ROUTE_118)
    {
        return music;
    }
    else
    {
        if (gSaveBlock1Ptr->location.mapGroup == MAP_GROUP(MAUVILLE_CITY)
         && gSaveBlock1Ptr->location.mapNum == MAP_NUM(MAUVILLE_CITY))
            return MUS_DOORO_X1;
        else
            return MUS_GRANROAD;
    }
}

void Overworld_ResetMapMusic(void)
{
    ResetMapMusic();
}

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

    if (music != MUS_OOAME && music != 0xFFFF)
    {
        if (gSaveBlock1Ptr->savedMusic)
            music = gSaveBlock1Ptr->savedMusic;
        else if (GetCurrentMapType() == MAP_TYPE_UNDERWATER)
            music = MUS_DEEPDEEP;
        else if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_SURFING))
            music = MUS_NAMINORI;
    }

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

void Overworld_SetSavedMusic(u16 songNum)
{
    gSaveBlock1Ptr->savedMusic = songNum;
}

void Overworld_ClearSavedMusic(void)
{
    gSaveBlock1Ptr->savedMusic = 0;
}

static void sub_8085810(void)
{
    if (FlagGet(FLAG_SPECIAL_FLAG_0x4001) != TRUE)
    {
        u16 newMusic = GetWarpDestinationMusic();
        u16 currentMusic = GetCurrentMapMusic();
        if (newMusic != MUS_OOAME && newMusic != 0xFFFF)
        {
            if (currentMusic == MUS_DEEPDEEP || currentMusic == MUS_NAMINORI)
                return;
            if (TestPlayerAvatarFlags(PLAYER_AVATAR_FLAG_SURFING))
                newMusic = MUS_NAMINORI;
        }
        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 != MUS_OOAME)
        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 currentMusic = GetCurrentMapMusic();
    u16 warpMusic = GetWarpDestinationMusic();
    if (FlagGet(FLAG_SPECIAL_FLAG_0x4001) != TRUE && warpMusic != GetCurrentMapMusic())
    {
        if (currentMusic == MUS_NAMINORI
            && VarGet(VAR_0x40CA) == 2
            && gSaveBlock1Ptr->location.mapGroup == MAP_GROUP(SOOTOPOLIS_CITY)
            && gSaveBlock1Ptr->location.mapNum == MAP_NUM(SOOTOPOLIS_CITY)
            && sWarpDestination.mapGroup == MAP_GROUP(SOOTOPOLIS_CITY)
            && sWarpDestination.mapNum == MAP_NUM(SOOTOPOLIS_CITY)
            && sWarpDestination.x == 29
            && sWarpDestination.y == 53)
            return;
        FadeOutMapMusic(GetMapMusicFadeoutSpeed());
    }
}

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)
{
    u8 i, monsCount, divBy;

    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:
        divBy = 1;
        monsCount = CalculatePlayerPartyCount();
        for (i = 0; i < monsCount; i++)
        {
            if (!GetMonData(&gPlayerParty[i], MON_DATA_SANITY_IS_EGG)
                && GetMonAbility(&gPlayerParty[0]) == ABILITY_SWARM)
            {
                divBy = 2;
                break;
            }
        }
        *delayCounter = ((Random() % 1200) + 1200) / divBy;
        *state = 3;
        break;
    case 3:
        (*delayCounter)--;
        if (*delayCounter == 0)
        {
            PlayAmbientCry();
            *state = 2;
        }
        break;
    case 4:
        break;
    }
}

static void ChooseAmbientCrySpecies(void)
{
    if ((gSaveBlock1Ptr->location.mapGroup == MAP_GROUP(ROUTE130)
     && gSaveBlock1Ptr->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 GetCurrentMapType(void)
{
    return GetMapTypeByWarpData(&gSaveBlock1Ptr->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 GetSavedWarpRegionMapSectionId(void)
{
    return Overworld_GetMapHeaderByGroupAndId(gSaveBlock1Ptr->dynamicWarp.mapGroup, gSaveBlock1Ptr->dynamicWarp.mapNum)->regionMapSectionId;
}

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

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

static void InitOverworldBgs(void)
{
    InitBgsFromTemplates(0, sOverworldBgTemplates, ARRAY_COUNT(sOverworldBgTemplates));
    SetBgAttribute(1, BG_ATTR_MOSAIC, 1);
    SetBgAttribute(2, BG_ATTR_MOSAIC, 1);
    SetBgAttribute(3, BG_ATTR_MOSAIC, 1);
    gBGTilemapBuffers2 = AllocZeroed(BG_SCREEN_SIZE);
    gBGTilemapBuffers1 = AllocZeroed(BG_SCREEN_SIZE);
    gBGTilemapBuffers3 = AllocZeroed(BG_SCREEN_SIZE);
    SetBgTilemapBuffer(1, gBGTilemapBuffers2);
    SetBgTilemapBuffer(2, gBGTilemapBuffers1);
    SetBgTilemapBuffer(3, gBGTilemapBuffers3);
    InitStandardTextBoxWindows();
}

void CleanupOverworldWindowsAndTilemaps(void)
{
    ClearMirageTowerPulseBlendEffect();
    FreeAllOverworldWindowBuffers();
    if (gBGTilemapBuffers3)
        FREE_AND_SET_NULL(gBGTilemapBuffers3);
    if (gBGTilemapBuffers1)
        FREE_AND_SET_NULL(gBGTilemapBuffers1);
    if (gBGTilemapBuffers2)
        FREE_AND_SET_NULL(gBGTilemapBuffers2);
}

static void ResetSafariZoneFlag_(void)
{
    ResetSafariZoneFlag();
}

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

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

    sub_808B578();
    FieldClearPlayerInput(&inputStruct);
    FieldGetPlayerInput(&inputStruct, newKeys, heldKeys);
    if (!ScriptContext2_IsEnabled())
    {
        if (ProcessPlayerFieldInput(&inputStruct) == 1)
        {
            ScriptContext2_Enable();
            HideMapNamePopUpWindow();
        }
        else
        {
            player_step(inputStruct.dpadDirection, newKeys, heldKeys);
        }
    }
}

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

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

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

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

void sub_8085E94(void *a0)
{
    sUnknown_03000E0C = a0;
}

static bool8 map_post_load_hook_exec(void)
{
    if (gFieldCallback2 != NULL)
    {
        if (!gFieldCallback2())
        {
            return FALSE;
        }
        else
        {
            gFieldCallback2 = NULL;
            gFieldCallback = NULL;
        }
    }
    else
    {
        if (gFieldCallback != NULL)
            gFieldCallback();
        else
            mapldr_default();

        gFieldCallback = NULL;
    }

    return TRUE;
}

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

void CB2_WhiteOut(void)
{
    u8 val;

    if (++gMain.state >= 120)
    {
        FieldClearVBlankHBlankCallbacks();
        StopMapMusic();
        ResetSafariZoneFlag_();
        DoWhiteOut();
        ResetInitialPlayerAvatarState();
        ScriptContext1_Init();
        ScriptContext2_Disable();
        gFieldCallback = sub_80AF3C8;
        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(c2_change_map);
    gMain.savedCallback = CB2_LoadMap2;
}

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

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

void sub_8086074(void)
{
    FieldClearVBlankHBlankCallbacks();
    gFieldCallback = sub_80AF314;
    SetMainCallback2(c2_80567AC);
}

static void c2_80567AC(void)
{
    if (map_loading_iteration_3(&gMain.state))
    {
        SetFieldVBlankCallback();
        SetMainCallback1(c1_link_related);
        sub_8086C2C();
        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_8086638(&gMain.state))
    {
        SetFieldVBlankCallback();
        SetMainCallback2(CB2_Overworld);
    }
}

void CB2_ReturnToFieldLink(void)
{
    if (!sub_8087598() && map_loading_iteration_2_link(&gMain.state))
        SetMainCallback2(CB2_Overworld);
}

void c2_8056854(void)
{
    FieldClearVBlankHBlankCallbacks();
    StopMapMusic();
    SetMainCallback1(c1_link_related);
    sub_8086C2C();

    if (gWirelessCommType != 0)
        gFieldCallback = sub_80AF314;
    else
        gFieldCallback = sub_80AF214;

    ScriptContext1_Init();
    ScriptContext2_Disable();
    CB2_ReturnToField();
}

void CB2_ReturnToFieldWithOpenMenu(void)
{
    FieldClearVBlankHBlankCallbacks();
    gFieldCallback2 = sub_80AF6A4;
    CB2_ReturnToField();
}

void CB2_ReturnToFieldContinueScript(void)
{
    FieldClearVBlankHBlankCallbacks();
    gFieldCallback = sub_80AF188;
    CB2_ReturnToField();
}

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

void sub_80861E8(void)
{
    FieldClearVBlankHBlankCallbacks();
    gFieldCallback = sub_80AF3C8;
    CB2_ReturnToField();
}

static void sub_8086204(void)
{
    if ((gMapHeader.flags & 0xF8) == 8 && sub_80E909C() == TRUE)
        ShowMapNamePopup();
    sub_80AF3C8();
}

void CB2_ContinueSavedGame(void)
{
    u8 trainerHillMapId;

    FieldClearVBlankHBlankCallbacks();
    StopMapMusic();
    ResetSafariZoneFlag_();
    if (gSaveFileStatus == 0xFF)
        sub_81A3908();

    LoadSaveblockMapHeader();
    ClearDiveAndHoleWarps();
    trainerHillMapId = GetCurrentTrainerHillMapId();
    if (gMapHeader.mapLayoutId == LAYOUT_BATTLE_FRONTIER_BATTLE_PYRAMID_EMPTY_SQUARE)
        LoadBattlePyramidFloorEventObjectScripts();
    else if (trainerHillMapId != 0 && trainerHillMapId != 6)
        sub_81D5F48();
    else
        LoadSaveblockEventObjScripts();

    UnfreezeEventObjects();
    DoTimeBasedEvents();
    sub_8084788();
    if (gMapHeader.mapLayoutId == LAYOUT_BATTLE_FRONTIER_BATTLE_PYRAMID_EMPTY_SQUARE)
        InitBattlePyramidMap(TRUE);
    else if (trainerHillMapId != 0)
        InitTrainerHillMap();
    else
        InitMapFromSavedGame();

    PlayTimeCounter_Start();
    ScriptContext1_Init();
    ScriptContext2_Disable();
    InitMatchCallCounters();
    if (UseContinueGameWarp() == TRUE)
    {
        ClearContinueGameWarpStatus();
        SetWarpDestinationToContinueGameWarp();
        WarpIntoMap();
        sub_80EDB44();
        SetMainCallback2(CB2_LoadMap);
    }
    else
    {
        sub_80EDB44();
        gFieldCallback = sub_8086204;
        SetMainCallback1(CB1_Overworld);
        CB2_ReturnToField();
    }
}

static void FieldClearVBlankHBlankCallbacks(void)
{
    if (warp0_in_pokecenter() == TRUE)
        CloseLink();

    if (gWirelessCommType != 0)
    {
        EnableInterrupts(INTR_FLAG_VBLANK | INTR_FLAG_VCOUNT | INTR_FLAG_TIMER3 | INTR_FLAG_SERIAL);
        DisableInterrupts(INTR_FLAG_HBLANK);
    }
    else
    {
        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();
    FieldUpdateBgTilemapScroll();
    TransferPlttBuffer();
    TransferTilesetAnimsBuffer();
}

static void InitCurrentFlashLevelScanlineEffect(void)
{
    u8 flashLevel;

    if (InBattlePyramid_())
    {
        WriteBattlePyramidViewScanlineEffectBuffer();
        ScanlineEffect_SetParams(sFlashEffectParams);
    }
    else if ((flashLevel = Overworld_GetFlashLevel()))
    {
        WriteFlashScanlineEffectBuffer(flashLevel);
        ScanlineEffect_SetParams(sFlashEffectParams);
    }
}

static bool32 map_loading_iteration_3(u8 *state)
{
    switch (*state)
    {
    case 0:
        InitOverworldBgs();
        ScriptContext1_Init();
        ScriptContext2_Disable();
        sub_80867C8();
        sub_80867D8();
        (*state)++;
        break;
    case 1:
        mli0_load_map(1);
        (*state)++;
        break;
    case 2:
        sub_8086988(TRUE);
        (*state)++;
        break;
    case 3:
        sub_8086AE4();
        sub_80869DC();
        sub_8086B14();
        sub_8086AAC();
        (*state)++;
        break;
    case 4:
        InitCurrentFlashLevelScanlineEffect();
        InitOverworldGraphicsRegisters();
        sub_8197200();
        (*state)++;
        break;
    case 5:
        move_tilemap_camera_to_upper_left_corner();
        (*state)++;
        break;
    case 6:
        copy_map_tileset1_to_vram(gMapHeader.mapLayout);
        (*state)++;
        break;
    case 7:
        copy_map_tileset2_to_vram(gMapHeader.mapLayout);
        (*state)++;
        break;
    case 8:
        if (free_temp_tile_data_buffers_if_possible() != TRUE)
        {
            apply_map_tileset1_tileset2_palette(gMapHeader.mapLayout);
            (*state)++;
        }
        break;
    case 9:
        DrawWholeMapView();
        (*state)++;
        break;
    case 10:
        cur_mapheader_run_tileset_funcs_after_some_cpuset();
        (*state)++;
        break;
    case 11:
        if (gWirelessCommType != 0)
        {
            sub_800E0E8();
            CreateWirelessStatusIndicatorSprite(0, 0);
        }
        (*state)++;
        break;
    case 12:
        if (map_post_load_hook_exec())
            (*state)++;
        break;
    case 13:
        return TRUE;
    }

    return FALSE;
}

static bool32 load_map_stuff(u8 *state, u32 a2)
{
    switch (*state)
    {
    case 0:
        FieldClearVBlankHBlankCallbacks();
        mli0_load_map(a2);
        (*state)++;
        break;
    case 1:
        sub_80867C8();
        sub_80867D8();
        (*state)++;
        break;
    case 2:
        sub_8086988(a2);
        (*state)++;
        break;
    case 3:
        mli4_mapscripts_and_other();
        sub_8086A80();
        (*state)++;
        break;
    case 4:
        InitCurrentFlashLevelScanlineEffect();
        InitOverworldGraphicsRegisters();
        sub_8197200();
        (*state)++;
        break;
    case 5:
        move_tilemap_camera_to_upper_left_corner();
        (*state)++;
        break;
    case 6:
        copy_map_tileset1_to_vram(gMapHeader.mapLayout);
        (*state)++;
        break;
    case 7:
        copy_map_tileset2_to_vram(gMapHeader.mapLayout);
        (*state)++;
        break;
    case 8:
        if (free_temp_tile_data_buffers_if_possible() != TRUE)
        {
            apply_map_tileset1_tileset2_palette(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 & 0xF8) == 8 && sub_80E909C() == 1)
            ShowMapNamePopup();
        (*state)++;
        break;
    case 12:
        if (map_post_load_hook_exec())
            (*state)++;
        break;
    case 13:
        return TRUE;
    }

    return FALSE;
}

static bool32 sub_8086638(u8 *state)
{
    switch (*state)
    {
    case 0:
        sub_80867C8();
        sub_80867D8();
        sub_8086988(0);
        sub_8086A68();
        sub_8086A80();
        (*state)++;
        break;
    case 1:
        sub_8086860();
        sub_81D64C0();
        (*state)++;
        break;
    case 2:
        if (map_post_load_hook_exec())
            (*state)++;
        break;
    case 3:
        return TRUE;
    }

    return FALSE;
}

static bool32 map_loading_iteration_2_link(u8 *state)
{
    switch (*state)
    {
    case 0:
        FieldClearVBlankHBlankCallbacks();
        sub_80867C8();
        sub_80867D8();
        (*state)++;
        break;
    case 1:
        sub_8086988(1);
        (*state)++;
        break;
    case 2:
        sub_8086B9C();
        sub_8086A68();
        sub_8086AC8();
        (*state)++;
        break;
    case 3:
        InitCurrentFlashLevelScanlineEffect();
        InitOverworldGraphicsRegisters();
        sub_8197200();
        (*state)++;
        break;
    case 4:
        move_tilemap_camera_to_upper_left_corner();
        (*state)++;
        break;
    case 5:
        copy_map_tileset1_to_vram(gMapHeader.mapLayout);
        (*state)++;
        break;
    case 6:
        copy_map_tileset2_to_vram(gMapHeader.mapLayout);
        (*state)++;
        break;
    case 7:
        if (free_temp_tile_data_buffers_if_possible() != TRUE)
        {
            apply_map_tileset1_tileset2_palette(gMapHeader.mapLayout);
            (*state)++;
        }
        break;
    case 8:
        DrawWholeMapView();
        (*state)++;
        break;
    case 9:
        cur_mapheader_run_tileset_funcs_after_some_cpuset();
        (*state)++;
        break;
    case 11:
        if (gWirelessCommType != 0)
        {
            sub_800E0E8();
            CreateWirelessStatusIndicatorSprite(0, 0);
        }
        (*state)++;
        break;
    case 12:
        if (map_post_load_hook_exec())
            (*state)++;
        break;
    case 10:
        (*state)++;
        break;
    case 13:
        SetFieldVBlankCallback();
        (*state)++;
        return TRUE;
    }

    return FALSE;
}

static void do_load_map_stuff_loop(u8 *state)
{
    while (!load_map_stuff(state, 0));
}

static void sub_80867C8(void)
{
    ClearMirageTowerPulseBlend();
    MoveSaveBlocks_ResetHeap();
}

static void sub_80867D8(void)
{
    SetGpuReg(REG_OFFSET_DISPCNT, 0);
    ScanlineEffect_Stop();

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

static void sub_8086860(void)
{
    InitCurrentFlashLevelScanlineEffect();
    InitOverworldGraphicsRegisters();
    sub_8197200();
    mapdata_load_assets_to_gpu_and_full_redraw();
}

static void InitOverworldGraphicsRegisters(void)
{
    clear_scheduled_bg_copies_to_vram();
    reset_temp_tile_data_buffers();
    SetGpuReg(REG_OFFSET_MOSAIC, 0);
    SetGpuReg(REG_OFFSET_WININ, WININ_WIN0_BG_ALL | WININ_WIN0_OBJ | WININ_WIN1_BG_ALL | WININ_WIN1_OBJ);
    SetGpuReg(REG_OFFSET_WINOUT, WINOUT_WIN01_BG0 | WINOUT_WINOBJ_BG0);
    SetGpuReg(REG_OFFSET_WIN0H, 0xFF);
    SetGpuReg(REG_OFFSET_WIN0V, 0xFF);
    SetGpuReg(REG_OFFSET_WIN1H, 0xFFFF);
    SetGpuReg(REG_OFFSET_WIN1V, 0xFFFF);
    SetGpuReg(REG_OFFSET_BLDCNT, gUnknown_82EC7C4[1] | gUnknown_82EC7C4[2] | gUnknown_82EC7C4[3]
                               | BLDCNT_TGT2_OBJ | BLDCNT_EFFECT_BLEND);
    SetGpuReg(REG_OFFSET_BLDALPHA, BLDALPHA_BLEND(13, 7));
    InitOverworldBgs();
    schedule_bg_copy_tilemap_to_vram(1);
    schedule_bg_copy_tilemap_to_vram(2);
    schedule_bg_copy_tilemap_to_vram(3);
    ChangeBgX(0, 0, 0);
    ChangeBgY(0, 0, 0);
    ChangeBgX(1, 0, 0);
    ChangeBgY(1, 0, 0);
    ChangeBgX(2, 0, 0);
    ChangeBgY(2, 0, 0);
    ChangeBgX(3, 0, 0);
    ChangeBgY(3, 0, 0);
    SetGpuReg(REG_OFFSET_DISPCNT, DISPCNT_OBJ_ON | DISPCNT_WIN0_ON | DISPCNT_WIN1_ON
                                | DISPCNT_OBJ_1D_MAP | DISPCNT_HBLANK_INTERVAL);
    ShowBg(0);
    ShowBg(1);
    ShowBg(2);
    ShowBg(3);
    InitFieldMessageBox();
}

static void sub_8086988(u32 a1)
{
    ResetTasks();
    ResetSpriteData();
    ResetPaletteFade();
    ScanlineEffect_Clear();
    ResetAllPicSprites();
    ResetCameraUpdateInfo();
    InstallCameraPanAheadCallback();
    if (!a1)
        InitEventObjectPalettes(0);
    else
        InitEventObjectPalettes(1);

    FieldEffectActiveListClear();
    StartWeather();
    sub_80AEE84();
    if (!a1)
        SetUpFieldTasks();
    mapheader_run_script_with_tag_x5();
    TryStartMirageTowerPulseBlendEffect();
}

static void sub_80869DC(void)
{
    gTotalCameraPixelOffsetX = 0;
    gTotalCameraPixelOffsetY = 0;
    ResetEventObjects();
    TrySpawnEventObjects(0, 0);
    mapheader_run_first_tag4_script_list_match();
}

static void mli4_mapscripts_and_other(void)
{
    s16 x, y;
    struct InitialPlayerAvatarState *player;

    gTotalCameraPixelOffsetX = 0;
    gTotalCameraPixelOffsetY = 0;
    ResetEventObjects();
    GetCameraFocusCoords(&x, &y);
    player = GetInitialPlayerAvatarState();
    InitPlayerAvatar(x, y, player->direction, gSaveBlock2Ptr->playerGender);
    SetPlayerAvatarTransitionFlags(player->transitionFlags);
    ResetInitialPlayerAvatarState();
    TrySpawnEventObjects(0, 0);
    mapheader_run_first_tag4_script_list_match();
}

static void sub_8086A68(void)
{
    sub_808E16C(0, 0);
    RotatingGate_InitPuzzleAndGraphics();
    mapheader_run_script_with_tag_x7();
}

static void sub_8086A80(void)
{
    gEventObjects[gPlayerAvatar.eventObjectId].trackedByCamera = 1;
    InitCameraUpdateCallback(gPlayerAvatar.spriteId);
}

static void sub_8086AAC(void)
{
    InitCameraUpdateCallback(sub_8087858(gUnknown_03005DB4));
}

static void sub_8086AC8(void)
{
    InitCameraUpdateCallback(sub_8087858(gUnknown_03005DB4));
}

static void sub_8086AE4(void)
{
    u16 x, y;
    GetCameraFocusCoords(&x, &y);
    sub_8088B3C(x + gUnknown_03005DB4, y);
}

static void sub_8086B14(void)
{
    u16 i;
    u16 x, y;

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

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

    sub_8086C40();
}

static void sub_8086B9C(void)
{
    u16 i;
    for (i = 0; i < gFieldLinkPlayerCount; i++)
        CreateLinkPlayerSprite(i, gLinkPlayers[i].version);
}

static void c1_link_related(void)
{
    if (gWirelessCommType == 0 || !sub_800F0B8() || !sub_8009F3C())
    {
        u8 var = gUnknown_03005DB4;
        sub_8086F38(gLinkPartnersHeldKeys, var);
        sub_8086FA0(sUnknown_03000E14(var));
        sub_8086C40();
    }
}

void sub_8086C2C(void)
{
    sub_8086C90();
    c1_link_related_func_set(sub_80870B0);
}

static void sub_8086C40(void)
{
    sub_808709C(gLinkPartnersHeldKeys);
}

static void c1_link_related_func_set(u16 (*func)(u32))
{
    sUnknown_03000E19 = 0;
    sUnknown_03000E14 = func;
}

static void sub_8086C64(void)
{
    if (gWirelessCommType != 0 && ++sUnknown_03000E19 > 60)
        sub_8010198();
}

static void sub_8086C90(void)
{
    s32 i;
    for (i = 0; i < 4; i++)
        sUnknown_03000E10[i] = 0x80;
}

static bool32 sub_8086CA8(u16 a1)
{
    s32 i;
    s32 count = gFieldLinkPlayerCount;

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

static bool32 sub_8086CE0(u16 a1)
{
    s32 i;
    s32 count = gFieldLinkPlayerCount;

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

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

    if (sUnknown_03000E10[a1] == 0x80)
    {
        script = sub_8087370(a3);
        if (script)
        {
            *a4 = sub_8087480(script);
            sUnknown_03000E10[a1] = 0x81;
            if (a3->b)
            {
                c1_link_related_func_set(sub_80870F8);
                sub_8087530(script);
            }
            return;
        }
        if (sub_8086CE0(0x83) == 1)
        {
            sUnknown_03000E10[a1] = 0x81;
            if (a3->b)
            {
                c1_link_related_func_set(sub_80870F8);
                sub_8087584();
            }
            return;
        }
        switch (a2)
        {
        case 24:
            if (sub_8087358(a3))
            {
                sUnknown_03000E10[a1] = 0x81;
                if (a3->b)
                {
                    c1_link_related_func_set(sub_80870F8);
                    sub_808751C();
                }
            }
            break;
        case 18:
            if (sub_8087388(a3) == TRUE)
            {
                sUnknown_03000E10[a1] = 0x81;
                if (a3->b)
                {
                    c1_link_related_func_set(sub_80870F8);
                    sub_808754C();
                }
            }
            break;
        case 25:
            script = sub_80873B4(a3);
            if (script)
            {
                sUnknown_03000E10[a1] = 0x81;
                if (a3->b)
                {
                    c1_link_related_func_set(sub_80870F8);
                    sub_8087568(script);
                }
            }
            break;
        case 27:
            if (sub_8087340(a3))
            {
                sUnknown_03000E10[a1] = 0x81;
                if (a3->b)
                {
                    c1_link_related_func_set(sub_808711C);
                    sub_8087510();
                }
            }
            break;
        case 28:
            if (sub_8087340(a3))
            {
                sUnknown_03000E10[a1] = 0x81;
                if (a3->b)
                {
                    c1_link_related_func_set(sub_8087140);
                    sub_8087510();
                }
            }
            break;
        }
    }

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

static void sub_8086F38(u16 *a1, s32 a2)
{
    struct UnkStruct_8054FF8 st;
    s32 i;

    for (i = 0; i < 4; i++)
    {
        u8 v5 = a1[i];
        u16 v8 = 0;
        sub_80872D8(i, a2, &st);
        sub_8086D18(i, v5, &st, &v8);
        if (sUnknown_03000E10[i] == 0x80)
            v8 = sub_8087068(v5);
        sub_808796C(i, v8);
    }
}

static void sub_8086FA0(u16 a1)
{
    if (a1 >= 17 && a1 < 30)
        gUnknown_03005DA8 = a1;
    else
        gUnknown_03005DA8 = 17;

    if (gWirelessCommType != 0
        && sub_8087690() > 1
        && is_c1_link_related_active() == TRUE
        && sub_8009F3C() == TRUE)
    {
        switch (a1)
        {
        case 17:
        case 18:
        case 19:
        case 20:
        case 21:
        case 24:
        case 25:
            gUnknown_03005DA8 = 0;
            break;
        }
    }
}

static u16 sub_808700C(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;
}

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

static void sub_808709C(u16 *a1)
{
    s32 i;
    for (i = 0; i < 4; i++)
        a1[i] = 17;
}

static u16 sub_80870B0(u32 a1)
{
    if (ScriptContext2_IsEnabled() == 1)
        return 17;
    if (sub_800B4DC() > 4)
        return 27;
    if (sub_8087690() <= 4)
        return sub_808700C(a1);
    return 28;
}

static u16 sub_80870EC(u32 a1)
{
    sub_8086C64();
    return 17;
}

static u16 sub_80870F8(u32 a1)
{
    u16 retVal;
    if (ScriptContext2_IsEnabled() == TRUE)
    {
        retVal = 17;
    }
    else
    {
        retVal = 26;
        c1_link_related_func_set(sub_80870EC);
    }
    return retVal;
}

static u16 sub_808711C(u32 a1)
{
    u16 retVal;
    if (sub_800B4DC() > 2)
    {
        retVal = 17;
    }
    else
    {
        retVal = 26;
        ScriptContext2_Disable();
        c1_link_related_func_set(sub_80870EC);
    }
    return retVal;
}

static u16 sub_8087140(u32 a1)
{
    u16 retVal;
    if (sub_8087690() > 2)
    {
        retVal = 17;
    }
    else
    {
        retVal = 26;
        ScriptContext2_Disable();
        c1_link_related_func_set(sub_80870EC);
    }
    return retVal;
}

static u16 sub_8087164(u32 a1)
{
    sub_8086C64();
    return 17;
}

static u16 sub_8087170(u32 linkPlayerId)
{
    if (sUnknown_03000E10[linkPlayerId] == 0x82)
    {
        if (gMain.newKeys & B_BUTTON)
        {
            c1_link_related_func_set(sub_8087164);
            return 29;
        }
        else
        {
            return 17;
        }
    }
    else
    {
        sub_8086C64();
        return 17;
    }
}

static u16 sub_80871AC(u32 a1)
{
    c1_link_related_func_set(sub_8087170);
    return 22;
}

static u16 sub_80871C0(u32 a1)
{
    return 17;
}

static u16 sub_80871C4(u32 a1)
{
    if (sUnknown_03000E10[a1] != 0x83)
        sub_8086C64();
    if (sub_8086CA8(0x83) == TRUE)
    {
        ScriptContext1_SetupScript(EventScript_277513);
        c1_link_related_func_set(sub_80871C0);
    }
    return 17;
}

static u16 sub_80871FC(u32 a1)
{
    c1_link_related_func_set(sub_80871C4);
    return 23;
}

static u16 sub_8087210(u32 a1)
{
    return 17;
}

u32 sub_8087214(void)
{
    if (sub_8086CE0(0x83) == TRUE)
        return 2;
    if (sUnknown_03000E14 == sub_8087170 && sUnknown_03000E10[gUnknown_03005DB4] != 0x82)
        return 0;
    if (sUnknown_03000E14 == sub_8087164 && sUnknown_03000E10[gUnknown_03005DB4] == 0x81)
        return 2;
    if (sub_8086CA8(0x82) != 0)
        return 1;
    return 0;
}

bool32 sub_808727C(void)
{
    return sub_8086CE0(0x83);
}

u16 sub_8087288(void)
{
    c1_link_related_func_set(sub_80871AC);
    return 0;
}

u16 sub_808729C(void)
{
    c1_link_related_func_set(sub_80870F8);
    return 0;
}

u16 sub_80872B0(void)
{
    c1_link_related_func_set(sub_80871FC);
    return 0;
}

u16 sub_80872C4(void)
{
    c1_link_related_func_set(sub_8087210);
    return 0;
}

static void sub_80872D8(s32 linkPlayerId, s32 a2, struct UnkStruct_8054FF8 *a3)
{
    s16 x, y;

    a3->a = linkPlayerId;
    a3->b = (linkPlayerId == a2) ? 1 : 0;
    a3->c = gLinkPlayerEventObjects[linkPlayerId].mode;
    a3->d = sub_80878A0(linkPlayerId);
    sub_8087878(linkPlayerId, &x, &y);
    a3->sub.x = x;
    a3->sub.y = y;
    a3->sub.height = sub_80878C0(linkPlayerId);
    a3->field_C = MapGridGetMetatileBehaviorAt(x, y);
}

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

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

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

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

static const u8 *sub_80873B4(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 EventScript_TradeRoom_TooBusyToNotice;
        else if (sUnknown_03000E10[linkPlayerId] != 0x80)
            return EventScript_TradeRoom_TooBusyToNotice;
        else if (!sub_80B39D4(linkPlayerId))
            return EventScript_TradeRoom_ReadTrainerCard1;
        else
            return EventScript_TradeRoom_ReadTrainerCard2;
    }

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

static u16 sub_8087480(const u8 *script)
{
    if (script == gUnknown_08277388)
        return 10;
    else if (script == gUnknown_082773A3)
        return 9;
    else if (script == gUnknown_082773BE)
        return 10;
    else if (script == gUnknown_082773D9)
        return 9;
    else if (script == gUnknown_0827741D)
        return 10;
    else if (script == gUnknown_08277432)
        return 9;
    else if (script == gUnknown_08277447)
        return 10;
    else if (script == gUnknown_0827745C)
        return 9;
    else if (script == gUnknown_08277374)
        return 10;
    else if (script == gUnknown_0827737E)
        return 9;
    else if (script == gUnknown_082773F5)
        return 10;
    else if (script == gUnknown_082773FF)
        return 9;
    else
        return 0;
}

static void sub_8087510(void)
{
    ScriptContext2_Enable();
}

static void sub_808751C(void)
{
    PlaySE(SE_WIN_OPEN);
    ShowStartMenu();
    ScriptContext2_Enable();
}

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

static void sub_808754C(void)
{
    PlaySE(SE_WIN_OPEN);
    ScriptContext1_SetupScript(gUnknown_082774EF);
    ScriptContext2_Enable();
}

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

static void sub_8087584(void)
{
    ScriptContext1_SetupScript(gUnknown_08277509);
    ScriptContext2_Enable();
}

bool32 sub_8087598(void)
{
    if (!is_c1_link_related_active())
        return 0;
    if (sub_800B4DC() >= 3)
        sUnknown_03000E18 = 1;
    else
        sUnknown_03000E18 = 0;
    return sUnknown_03000E18;
}

bool32 sub_80875C8(void)
{
    u8 temp;

    if (sub_800B4DC() < 2)
        return FALSE;
    else if (is_c1_link_related_active() != TRUE)
        return FALSE;
    else if (sub_8009F3C() != TRUE)
        return FALSE;
    else if (sUnknown_03000E14 == sub_808711C)
        return TRUE;
    else if (sUnknown_03000E14 != sub_80870F8)
        return FALSE;

    temp = sUnknown_03000E18;
    sUnknown_03000E18 = 0;

    if (temp == TRUE)
        return TRUE;
    else if (gPaletteFade.active && gPaletteFade.softwareFadeFinishing)
        return TRUE;
    else
        return FALSE;
}

bool32 sub_8087634(void)
{
    if (sub_8087690() < 2)
        return FALSE;
    else if (is_c1_link_related_active() != TRUE)
        return FALSE;
    else if (sub_8009F3C() != TRUE)
        return FALSE;
    else if (sUnknown_03000E14 == sub_8087140)
        return TRUE;
    else
        return FALSE;
}

bool32 sub_808766C(void)
{
    if (gWirelessCommType != 0)
        return FALSE;
    else if (!sub_8009F3C())
        return FALSE;
    else
        return TRUE;
}

static u32 sub_8087690(void)
{
    if (gWirelessCommType != 0)
        return gUnknown_03005000.unk_9e8.unk_232;
    else
        return gLink.sendQueue.count;
}

static void ZeroLinkPlayerEventObject(struct LinkPlayerEventObject *linkPlayerEventObj)
{
    memset(linkPlayerEventObj, 0, sizeof(struct LinkPlayerEventObject));
}

void ClearLinkPlayerEventObjects(void)
{
    memset(gLinkPlayerEventObjects, 0, sizeof(gLinkPlayerEventObjects));
}

static void ZeroEventObject(struct EventObject *eventObj)
{
    memset(eventObj, 0, sizeof(struct EventObject));
}

static void SpawnLinkPlayerEventObject(u8 linkPlayerId, s16 x, s16 y, u8 a4)
{
    u8 eventObjId = GetFirstInactiveEventObjectId();
    struct LinkPlayerEventObject *linkPlayerEventObj = &gLinkPlayerEventObjects[linkPlayerId];
    struct EventObject *eventObj = &gEventObjects[eventObjId];

    ZeroLinkPlayerEventObject(linkPlayerEventObj);
    ZeroEventObject(eventObj);

    linkPlayerEventObj->active = 1;
    linkPlayerEventObj->linkPlayerId = linkPlayerId;
    linkPlayerEventObj->eventObjId = eventObjId;
    linkPlayerEventObj->mode = 0;

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

    InitLinkPlayerEventObjectPos(eventObj, x, y);
}

static void InitLinkPlayerEventObjectPos(struct EventObject *eventObj, s16 x, s16 y)
{
    eventObj->currentCoords.x = x;
    eventObj->currentCoords.y = y;
    eventObj->previousCoords.x = x;
    eventObj->previousCoords.y = y;
    sub_8093038(x, y, &eventObj->initialCoords.x, &eventObj->initialCoords.y);
    eventObj->initialCoords.x += 8;
    EventObjectUpdateZCoord(eventObj);
}

static void sub_80877DC(u8 linkPlayerId, u8 a2)
{
    if (gLinkPlayerEventObjects[linkPlayerId].active)
    {
        u8 eventObjId = gLinkPlayerEventObjects[linkPlayerId].eventObjId;
        struct EventObject *eventObj = &gEventObjects[eventObjId];
        eventObj->range.as_byte = a2;
    }
}

static void sub_808780C(u8 linkPlayerId)
{
    struct LinkPlayerEventObject *linkPlayerEventObj = &gLinkPlayerEventObjects[linkPlayerId];
    u8 eventObjId = linkPlayerEventObj->eventObjId;
    struct EventObject *eventObj = &gEventObjects[eventObjId];
    if (eventObj->spriteId != MAX_SPRITES)
        DestroySprite(&gSprites[eventObj->spriteId]);
    linkPlayerEventObj->active = 0;
    eventObj->active = 0;
}

static u8 sub_8087858(u8 linkPlayerId)
{
    u8 eventObjId = gLinkPlayerEventObjects[linkPlayerId].eventObjId;
    struct EventObject *eventObj = &gEventObjects[eventObjId];
    return eventObj->spriteId;
}

static void sub_8087878(u8 linkPlayerId, u16 *x, u16 *y)
{
    u8 eventObjId = gLinkPlayerEventObjects[linkPlayerId].eventObjId;
    struct EventObject *eventObj = &gEventObjects[eventObjId];
    *x = eventObj->currentCoords.x;
    *y = eventObj->currentCoords.y;
}

static u8 sub_80878A0(u8 linkPlayerId)
{
    u8 eventObjId = gLinkPlayerEventObjects[linkPlayerId].eventObjId;
    struct EventObject *eventObj = &gEventObjects[eventObjId];
    return eventObj->range.as_byte;
}

static u8 sub_80878C0(u8 linkPlayerId)
{
    u8 eventObjId = gLinkPlayerEventObjects[linkPlayerId].eventObjId;
    struct EventObject *eventObj = &gEventObjects[eventObjId];
    return eventObj->currentElevation;
}

static s32 sub_80878E4(u8 linkPlayerId)
{
    u8 eventObjId = gLinkPlayerEventObjects[linkPlayerId].eventObjId;
    struct EventObject *eventObj = &gEventObjects[eventObjId];
    return 16 - (s8)eventObj->directionSequenceIndex;
}

static u8 GetLinkPlayerIdAt(s16 x, s16 y)
{
    u8 i;
    for (i = 0; i < 4; i++)
    {
        if (gLinkPlayerEventObjects[i].active
         && (gLinkPlayerEventObjects[i].mode == 0 || gLinkPlayerEventObjects[i].mode == 2))
        {
            struct EventObject *eventObj = &gEventObjects[gLinkPlayerEventObjects[i].eventObjId];
            if (eventObj->currentCoords.x == x && eventObj->currentCoords.y == y)
                return i;
        }
    }
    return 4;
}

static void sub_808796C(u8 linkPlayerId, u8 a2)
{
    struct LinkPlayerEventObject *linkPlayerEventObj = &gLinkPlayerEventObjects[linkPlayerId];
    u8 eventObjId = linkPlayerEventObj->eventObjId;
    struct EventObject *eventObj = &gEventObjects[eventObjId];

    if (linkPlayerEventObj->active)
    {
        if (a2 > 10)
            eventObj->triggerGroundEffectsOnMove = 1;
        else
            gUnknown_08339E00[gUnknown_08339DC8[linkPlayerEventObj->mode](linkPlayerEventObj, eventObj, a2)](linkPlayerEventObj, eventObj);
    }
}

static u8 sub_80879D8(struct LinkPlayerEventObject *linkPlayerEventObj, struct EventObject *eventObj, u8 a3)
{
    return gUnknown_08339DD4[a3](linkPlayerEventObj, eventObj, a3);
}

static u8 sub_80879F8(struct LinkPlayerEventObject *linkPlayerEventObj, struct EventObject *eventObj, u8 a3)
{
    return 1;
}

static u8 sub_80879FC(struct LinkPlayerEventObject *linkPlayerEventObj, struct EventObject *eventObj, u8 a3)
{
    return gUnknown_08339DD4[a3](linkPlayerEventObj, eventObj, a3);
}

static u8 sub_8087A1C(struct LinkPlayerEventObject *linkPlayerEventObj, struct EventObject *eventObj, u8 a3)
{
    return 0;
}

static u8 sub_8087A20(struct LinkPlayerEventObject *linkPlayerEventObj, struct EventObject *eventObj, u8 a3)
{
    s16 x, y;

    eventObj->range.as_byte = npc_something3(a3, eventObj->range.as_byte);
    EventObjectMoveDestCoords(eventObj, eventObj->range.as_byte, &x, &y);

    if (LinkPlayerDetectCollision(linkPlayerEventObj->eventObjId, eventObj->range.as_byte, x, y))
    {
        return 0;
    }
    else
    {
        eventObj->directionSequenceIndex = 16;
        ShiftEventObjectCoords(eventObj, x, y);
        EventObjectUpdateZCoord(eventObj);
        return 1;
    }
}

static u8 sub_8087A88(struct LinkPlayerEventObject *linkPlayerEventObj, struct EventObject *eventObj, u8 a3)
{
    eventObj->range.as_byte = npc_something3(a3, eventObj->range.as_byte);
    return 0;
}

static void sub_8087AA0(struct LinkPlayerEventObject *linkPlayerEventObj, struct EventObject *eventObj)
{
    linkPlayerEventObj->mode = 0;
}

static void sub_8087AA8(struct LinkPlayerEventObject *linkPlayerEventObj, struct EventObject *eventObj)
{
    eventObj->directionSequenceIndex--;
    linkPlayerEventObj->mode = 1;
    MoveCoords(eventObj->range.as_byte, &eventObj->initialCoords.x, &eventObj->initialCoords.y);
    if (!eventObj->directionSequenceIndex)
    {
        ShiftStillEventObjectCoords(eventObj);
        linkPlayerEventObj->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 selfEventObjId, u8 a2, s16 x, s16 y)
{
    u8 i;
    for (i = 0; i < 16; i++)
    {
        if (i != selfEventObjId)
        {
            if ((gEventObjects[i].currentCoords.x == x && gEventObjects[i].currentCoords.y == y)
             || (gEventObjects[i].previousCoords.x == x && gEventObjects[i].previousCoords.y == y))
            {
                return 1;
            }
        }
    }
    return MapGridIsImpassableAt(x, y);
}

static void CreateLinkPlayerSprite(u8 linkPlayerId, u8 gameVersion)
{
    struct LinkPlayerEventObject *linkPlayerEventObj = &gLinkPlayerEventObjects[linkPlayerId];
    u8 eventObjId = linkPlayerEventObj->eventObjId;
    struct EventObject *eventObj = &gEventObjects[eventObjId];
    struct Sprite *sprite;

    if (linkPlayerEventObj->active)
    {
        switch (gameVersion)
        {
        case VERSION_FIRE_RED:
        case VERSION_LEAF_GREEN:
            eventObj->spriteId = AddPseudoEventObject(GetFRLGAvatarGraphicsIdByGender(eventObj->singleMovementActive), SpriteCB_LinkPlayer, 0, 0, 0);
            break;
        case VERSION_RUBY:
        case VERSION_SAPPHIRE:
            eventObj->spriteId = AddPseudoEventObject(GetRSAvatarGraphicsIdByGender(eventObj->singleMovementActive), SpriteCB_LinkPlayer, 0, 0, 0);
            break;
        case VERSION_EMERALD:
            eventObj->spriteId = AddPseudoEventObject(GetRivalAvatarGraphicsIdByStateIdAndGender(PLAYER_AVATAR_STATE_NORMAL, eventObj->singleMovementActive), SpriteCB_LinkPlayer, 0, 0, 0);
            break;
        }

        sprite = &gSprites[eventObj->spriteId];
        sprite->coordOffsetEnabled = TRUE;
        sprite->data[0] = linkPlayerId;
        eventObj->triggerGroundEffectsOnMove = 0;
    }
}

static void SpriteCB_LinkPlayer(struct Sprite *sprite)
{
    struct LinkPlayerEventObject *linkPlayerEventObj = &gLinkPlayerEventObjects[sprite->data[0]];
    struct EventObject *eventObj = &gEventObjects[linkPlayerEventObj->eventObjId];
    sprite->pos1.x = eventObj->initialCoords.x;
    sprite->pos1.y = eventObj->initialCoords.y;
    SetObjectSubpriorityByZCoord(eventObj->previousElevation, sprite, 1);
    sprite->oam.priority = ZCoordToPriority(eventObj->previousElevation);

    if (!linkPlayerEventObj->mode)
        StartSpriteAnim(sprite, GetFaceDirectionAnimNum(eventObj->range.as_byte));
    else
        StartSpriteAnimIfDifferent(sprite, GetMoveDirectionAnimNum(eventObj->range.as_byte));

    UpdateEventObjectSpriteVisibility(sprite, 0);
    if (eventObj->triggerGroundEffectsOnMove)
    {
        sprite->invisible = ((sprite->data[7] & 4) >> 2);
        sprite->data[7]++;
    }
}
