#include "global.h"
#define IN_POKEMON_C
#include "constants/abilities.h"
#include "constants/items.h"
#include "constants/moves.h"
#include "constants/sinnoh_dex.h"
#include "constants/trainer_classes.h"

#include "MI_memory.h"
#include "filesystem.h"
#include "heap.h"
#include "itemtool.h"
#include "math_util.h"
#include "move_data.h"
#include "msgdata.h"
#include "party.h"
#include "player_data.h"
#include "pokemon.h"
#include "proto.h"
#include "seal.h"
#include "sound_chatot.h"
#include "string_util.h"
#include "unk_020051F4.h"

u32 GetMonDataInternal(struct Pokemon *pokemon, int attr, void *ptr);
u32 GetBoxMonDataInternal(struct BoxPokemon *pokemon, int attr, void *ptr);
void SetMonDataInternal(struct Pokemon *pokemon, int attr, void *ptr);
void SetBoxMonDataInternal(struct BoxPokemon *pokemon, int attr, void *ptr);
void AddMonDataInternal(struct Pokemon *pokemon, int attr, int amount);
void AddBoxMonData(struct BoxPokemon *pokemon, int attr, int amount);
u32 CalcBoxMonExpToNextLevel(struct BoxPokemon *boxmon);
u16 ModifyStatByNature(u8 nature, u16 statval, u8 statno);
u8 GetGenderBySpeciesAndPersonality_PreloadedPersonal(struct BaseStats *personal, u16 species, u32 pid);
u8 BoxMonIsShiny(struct BoxPokemon *boxmon);
u8 CalcShininessByOtIdAndPersonality(u32 otid, u32 pid);
void InitBoxMonMoveset(struct BoxPokemon *boxmon);
u32 sub_020696A8(struct BoxPokemon *boxmon, u16 move);
void sub_02069718(struct BoxPokemon *boxmon, u16 move);
void BoxMonSetMoveInSlot(struct BoxPokemon *boxmon, u16 move, u8 slot);
void sub_020698E8(struct BoxPokemon *boxmon, int slot1, int slot2);
s8 BoxMonGetFlavorPreference(struct BoxPokemon *boxmon, int flavor);
s8 GetFlavorPreferenceFromPID(u32 personality, int flavor);
u8 Party_MaskMonsWithPokerus(struct Party *party_p, u8 mask);
BOOL BoxMon_HasPokerus(struct BoxPokemon *boxmon);
BOOL BoxMon_IsImmuneToPokerus(struct BoxPokemon *boxmon);
void BoxMon_UpdateArceusForm(struct BoxPokemon *boxmon);
void LoadLevelUpLearnset_HandleAlternateForm(int species, int form, u16 *levelUpLearnset);
void sub_0206A054(struct BoxPokemon *boxmon, PlayerProfile *a1, u32 pokeball, u32 a3, u32 encounterType, HeapID heapId);
BOOL MonHasMove(struct Pokemon *pokemon, u16 move);
BOOL sub_0206A144(struct BoxPokemon *boxmon, u32 a1);
BOOL sub_0206A16C(u16 species, int form, u32 a2);
void sub_0206A1CC(struct BoxPokemon *boxmon);
u32 MaskOfFlagNo(int flagno);
void LoadMonPersonal(int species, struct BaseStats *personal);
void LoadMonEvolutionTable(u16 species, struct Evolution *dest);

int ResolveMonForm(int species, int form);
void MonEncryptSegment(u16 *datap, u32 size, u32 key);
void MonDecryptSegment(u16 *datap, u32 size, u32 key);
u16 MonEncryptionLCRNG(u32 *seed);
u16 CalcMonChecksum(u16 *datap, u32 size);
PokemonDataBlock *GetSubstruct(struct BoxPokemon *boxmon, u32 personality, u8 which_struct);
void LoadMonBaseStats_HandleAlternateForm(int species, int form, struct BaseStats *baseStats);
u8 GetBoxMonUnownLetter(struct BoxPokemon *boxmon);

#define ENCRY_ARGS_PTY(mon)    (u16 *)&(mon)->party, sizeof((mon)->party), (mon)->box.pid
#define ENCRY_ARGS_BOX(boxmon) (u16 *)&(boxmon)->substructs, sizeof((boxmon)->substructs), (boxmon)->checksum
#define ENCRYPT_PTY(mon)       MonEncryptSegment(ENCRY_ARGS_PTY(mon))
#define ENCRYPT_BOX(boxmon)    MonEncryptSegment(ENCRY_ARGS_BOX(boxmon))
#define DECRYPT_PTY(mon)       MonDecryptSegment(ENCRY_ARGS_PTY(mon))
#define DECRYPT_BOX(boxmon)    MonDecryptSegment(ENCRY_ARGS_BOX(boxmon))
#define CHECKSUM(boxmon)       CalcMonChecksum((u16 *)(boxmon)->substructs, sizeof((boxmon)->substructs))
#define SHINY_CHECK(otid, pid) ((                                                                                                                      \
                                    ((((otid) & 0xFFFF0000u) >> 16u)) ^ (((otid) & 0xFFFFu)) ^ ((((pid) & 0xFFFF0000u) >> 16u)) ^ (((pid) & 0xFFFFu))) \
    < 8u)
#define CALC_UNOWN_LETTER(pid) ((u32)((((pid) & 0x3000000) >> 18) | (((pid) & 0x30000) >> 12) | (((pid) & 0x300) >> 6) | (((pid) & 0x3) >> 0)) % 28u)

const u16 sItemOdds[2][2] = {
    { 45, 95 },
    { 20, 80 },
};

const s8 sFriendshipModTable[][3] = {
    { 5,  3,  2   },
    { 5,  3,  2   },
    { 1,  1,  0   },
    { 3,  2,  1   },
    { 1,  1,  0   },
    { 1,  1,  1   },
    { -1, -1, -1  },
    { -5, -5, -10 },
    { -5, -5, -10 },
    { 3,  2,  1   },
};

const u16 sLegendaryMonsList[] = {
    SPECIES_MEWTWO,
    SPECIES_MEW,
    SPECIES_HO_OH,
    SPECIES_LUGIA,
    SPECIES_CELEBI,
    SPECIES_KYOGRE,
    SPECIES_GROUDON,
    SPECIES_RAYQUAZA,
    SPECIES_JIRACHI,
    SPECIES_DEOXYS,
    SPECIES_DIALGA,
    SPECIES_PALKIA,
    SPECIES_GIRATINA,
    SPECIES_PHIONE,
    SPECIES_MANAPHY,
    SPECIES_DARKRAI,
    SPECIES_SHAYMIN,
    SPECIES_ARCEUS,
};

const s8 sFlavorPreferencesByNature[][5] = {
    // Spicy, Dry, Sweet, Bitter, Sour
    { 0,  0,  0,  0,  0  }, // NATURE_HARDY
    { 1,  0,  0,  0,  -1 }, // NATURE_LONELY
    { 1,  0,  -1, 0,  0  }, // NATURE_BRAVE
    { 1,  -1, 0,  0,  0  }, // NATURE_ADAMANT
    { 1,  0,  0,  -1, 0  }, // NATURE_NAUGHTY
    { -1, 0,  0,  0,  1  }, // NATURE_BOLD
    { 0,  0,  0,  0,  0  }, // NATURE_DOCILE
    { 0,  0,  -1, 0,  1  }, // NATURE_RELAXED
    { 0,  -1, 0,  0,  1  }, // NATURE_IMPISH
    { 0,  0,  0,  -1, 1  }, // NATURE_LAX
    { -1, 0,  1,  0,  0  }, // NATURE_TIMID
    { 0,  0,  1,  0,  -1 }, // NATURE_HASTY
    { 0,  0,  0,  0,  0  }, // NATURE_SERIOUS
    { 0,  -1, 1,  0,  0  }, // NATURE_JOLLY
    { 0,  0,  1,  -1, 0  }, // NATURE_NAIVE
    { -1, 1,  0,  0,  0  }, // NATURE_MODEST
    { 0,  1,  0,  0,  -1 }, // NATURE_MILD
    { 0,  1,  -1, 0,  0  }, // NATURE_QUIET
    { 0,  0,  0,  0,  0  }, // NATURE_BASHFUL
    { 0,  1,  0,  -1, 0  }, // NATURE_RASH
    { -1, 0,  0,  1,  0  }, // NATURE_CALM
    { 0,  0,  0,  1,  -1 }, // NATURE_GENTLE
    { 0,  0,  -1, 1,  0  }, // NATURE_SASSY
    { 0,  -1, 0,  1,  0  }, // NATURE_CAREFUL
    { 0,  0,  0,  0,  0  }, // NATURE_QUIRKY
};

const s8 sNatureStatMods[][5] = {
    // Atk, Def, Speed, SpAtk, SpDef
    { 0,  0,  0,  0,  0  }, // NATURE_HARDY
    { 1,  -1, 0,  0,  0  }, // NATURE_LONELY
    { 1,  0,  -1, 0,  0  }, // NATURE_BRAVE
    { 1,  0,  0,  -1, 0  }, // NATURE_ADAMANT
    { 1,  0,  0,  0,  -1 }, // NATURE_NAUGHTY
    { -1, 1,  0,  0,  0  }, // NATURE_BOLD
    { 0,  0,  0,  0,  0  }, // NATURE_DOCILE
    { 0,  1,  -1, 0,  0  }, // NATURE_RELAXED
    { 0,  1,  0,  -1, 0  }, // NATURE_IMPISH
    { 0,  1,  0,  0,  -1 }, // NATURE_LAX
    { -1, 0,  1,  0,  0  }, // NATURE_TIMID
    { 0,  -1, 1,  0,  0  }, // NATURE_HASTY
    { 0,  0,  0,  0,  0  }, // NATURE_SERIOUS
    { 0,  0,  1,  -1, 0  }, // NATURE_JOLLY
    { 0,  0,  1,  0,  -1 }, // NATURE_NAIVE
    { -1, 0,  0,  1,  0  }, // NATURE_MODEST
    { 0,  -1, 0,  1,  0  }, // NATURE_MILD
    { 0,  0,  -1, 1,  0  }, // NATURE_QUIET
    { 0,  0,  0,  0,  0  }, // NATURE_BASHFUL
    { 0,  0,  0,  1,  -1 }, // NATURE_RASH
    { -1, 0,  0,  0,  1  }, // NATURE_CALM
    { 0,  -1, 0,  0,  1  }, // NATURE_GENTLE
    { 0,  0,  -1, 0,  1  }, // NATURE_SASSY
    { 0,  0,  0,  -1, 1  }, // NATURE_CAREFUL
    { 0,  0,  0,  0,  0  }, // NATURE_QUIRKY
};

void ZeroMonData(struct Pokemon *pokemon) {
    MI_CpuClearFast(pokemon, sizeof(struct Pokemon));
    ENCRYPT_BOX(&pokemon->box);
    ENCRYPT_PTY(pokemon);
}

void ZeroBoxMonData(struct BoxPokemon *boxmon) {
    MI_CpuClearFast(boxmon, sizeof(struct BoxPokemon));
    ENCRYPT_BOX(boxmon);
}

u32 SizeOfStructPokemon(void) {
    return sizeof(struct Pokemon);
}

struct Pokemon *AllocMonZeroed(HeapID heapId) {
    struct Pokemon *pokemon = (struct Pokemon *)AllocFromHeap(heapId, sizeof(struct Pokemon));
    ZeroMonData(pokemon);
    return pokemon;
}

BOOL AcquireMonLock(struct Pokemon *mon) {
    BOOL ret = FALSE;

    if (!mon->box.party_lock) {
        ret = TRUE;
        GF_ASSERT(!mon->box.box_lock);
        mon->box.party_lock = TRUE;
        mon->box.box_lock = TRUE;
        DECRYPT_PTY(mon);
        DECRYPT_BOX(&mon->box);
    }
    return ret;
}

BOOL ReleaseMonLock(struct Pokemon *mon, BOOL decrypt_result) {
    BOOL ret = FALSE;
    if (mon->box.party_lock == TRUE && decrypt_result == TRUE) {
        ret = TRUE;
        mon->box.party_lock = FALSE;
        mon->box.box_lock = FALSE;
        ENCRYPT_PTY(mon);
        mon->box.checksum = CHECKSUM(&mon->box);
        ENCRYPT_BOX(&mon->box);
    }
    return ret;
}

BOOL AcquireBoxMonLock(struct BoxPokemon *mon) {
    BOOL ret = FALSE;

    if (!mon->box_lock) {
        ret = TRUE;
        mon->box_lock = TRUE;
        DECRYPT_BOX(mon);
    }
    return ret;
}

BOOL ReleaseBoxMonLock(struct BoxPokemon *mon, BOOL decrypt_result) {
    BOOL ret = FALSE;
    if (mon->box_lock == TRUE && decrypt_result == TRUE) {
        ret = TRUE;
        mon->box_lock = FALSE;
        mon->checksum = CHECKSUM(mon);
        ENCRYPT_BOX(mon);
    }
    return ret;
}

void CreateMon(struct Pokemon *pokemon, int species, int level, int fixedIV, int hasFixedPersonality, int fixedPersonality, int otIdType, int fixedOtId) {
    struct Mail *mail;
    u32 capsule;
    u8 seal_coords[0x18];
    ZeroMonData(pokemon);
    CreateBoxMon(&pokemon->box, species, level, fixedIV, hasFixedPersonality, fixedPersonality, otIdType, fixedOtId);
    // Not your average encryption call
    MonEncryptSegment((u16 *)&pokemon->party, sizeof(pokemon->party), 0);
    ENCRYPT_PTY(pokemon);
    SetMonData(pokemon, MON_DATA_LEVEL, &level);
    mail = Mail_New(HEAP_ID_DEFAULT);
    SetMonData(pokemon, MON_DATA_MAIL_STRUCT, mail);
    FreeToHeap(mail);
    capsule = 0;
    SetMonData(pokemon, MON_DATA_CAPSULE, &capsule);
    MI_CpuClearFast(seal_coords, sizeof(seal_coords));
    SetMonData(pokemon, MON_DATA_SEAL_COORDS, seal_coords);
    CalcMonLevelAndStats(pokemon);
}

void CreateBoxMon(struct BoxPokemon *boxPokemon, int species, int level, int fixedIV, int hasFixedPersonality, int fixedPersonality, int otIdType, int fixedOtId) {
    BOOL decry;
    u32 exp;
    u32 iv;
    ZeroBoxMonData(boxPokemon);
    decry = AcquireBoxMonLock(boxPokemon);
    if (hasFixedPersonality == 0) {
        fixedPersonality = (LCRandom() | (LCRandom() << 16));
    }
    SetBoxMonData(boxPokemon, MON_DATA_PERSONALITY, &fixedPersonality);
    if (otIdType == 2) {
        do {
            fixedOtId = (LCRandom() | (LCRandom() << 16));
        } while (SHINY_CHECK(fixedOtId, fixedPersonality));
    } else if (otIdType != 1) {
        fixedOtId = 0;
    }
    SetBoxMonData(boxPokemon, MON_DATA_OTID, &fixedOtId);
    SetBoxMonData(boxPokemon, MON_DATA_GAME_LANGUAGE, (void *)&gGameLanguage);
    SetBoxMonData(boxPokemon, MON_DATA_SPECIES, &species);
    SetBoxMonData(boxPokemon, MON_DATA_SPECIES_NAME, NULL);
    exp = GetMonExpBySpeciesAndLevel(species, level);
    SetBoxMonData(boxPokemon, MON_DATA_EXPERIENCE, &exp);
    exp = (u32)GetMonBaseStat(species, BASE_FRIENDSHIP);
    SetBoxMonData(boxPokemon, MON_DATA_FRIENDSHIP, &exp);
    SetBoxMonData(boxPokemon, MON_DATA_MET_LEVEL, &level);
    SetBoxMonData(boxPokemon, MON_DATA_GAME_VERSION, (void *)&gGameVersion);
    exp = ITEM_POKE_BALL;
    SetBoxMonData(boxPokemon, MON_DATA_POKEBALL, &exp);
    if (fixedIV < 0x20) {
        SetBoxMonData(boxPokemon, MON_DATA_HP_IV, &fixedIV);
        SetBoxMonData(boxPokemon, MON_DATA_ATK_IV, &fixedIV);
        SetBoxMonData(boxPokemon, MON_DATA_DEF_IV, &fixedIV);
        SetBoxMonData(boxPokemon, MON_DATA_SPEED_IV, &fixedIV);
        SetBoxMonData(boxPokemon, MON_DATA_SPATK_IV, &fixedIV);
        SetBoxMonData(boxPokemon, MON_DATA_SPDEF_IV, &fixedIV);
    } else {
        exp = LCRandom();
        iv = exp & 0x1F;
        SetBoxMonData(boxPokemon, MON_DATA_HP_IV, &iv);
        iv = (exp & 0x3E0) >> 5;
        SetBoxMonData(boxPokemon, MON_DATA_ATK_IV, &iv);
        iv = (exp & 0x7C00) >> 10;
        SetBoxMonData(boxPokemon, MON_DATA_DEF_IV, &iv);
        exp = LCRandom();
        iv = exp & 0x1F;
        SetBoxMonData(boxPokemon, MON_DATA_SPEED_IV, &iv);
        iv = (exp & 0x3E0) >> 5;
        SetBoxMonData(boxPokemon, MON_DATA_SPATK_IV, &iv);
        iv = (exp & 0x7C00) >> 10;
        SetBoxMonData(boxPokemon, MON_DATA_SPDEF_IV, &iv);
    }
    exp = (u32)GetMonBaseStat(species, BASE_ABILITY_1);
    iv = (u32)GetMonBaseStat(species, BASE_ABILITY_2);
    if (iv != 0) {
        if (fixedPersonality & 1) {
            SetBoxMonData(boxPokemon, MON_DATA_ABILITY, &iv);
        } else {
            SetBoxMonData(boxPokemon, MON_DATA_ABILITY, &exp);
        }
    } else {
        SetBoxMonData(boxPokemon, MON_DATA_ABILITY, &exp);
    }
    exp = GetBoxMonGender(boxPokemon);
    SetBoxMonData(boxPokemon, MON_DATA_GENDER, &exp);
    InitBoxMonMoveset(boxPokemon);
    ReleaseBoxMonLock(boxPokemon, decry);
}

void CreateMonWithNature(struct Pokemon *pokemon, u16 species, u8 level, u8 fixedIv, u8 nature) {
    u32 personality;
    do {
        personality = (u32)(LCRandom() | (LCRandom() << 16));
    } while (nature != GetNatureFromPersonality(personality));
    CreateMon(pokemon, (int)species, (int)level, (int)fixedIv, 1, (int)personality, (int)0, (int)0);
}

// FIXME: stack storage of pokemon, fixedIv swapped
void CreateMonWithGenderNatureLetter(struct Pokemon *pokemon, u16 species, u8 level, u8 fixedIv, u8 gender, u8 nature, u8 letter) {
    u32 pid = 0;
    u16 test = 0;
    if (letter != 0 && letter < 29) {
        do {
            pid = (u32)(LCRandom() | (LCRandom() << 16));
            test = (u16)CALC_UNOWN_LETTER(pid);
        } while (nature != GetNatureFromPersonality(pid) || gender != GetGenderBySpeciesAndPersonality(species, pid) || test != letter - 1);
    } else {
        pid = GenPersonalityByGenderAndNature(species, gender, nature);
    }
    CreateMon(pokemon, (int)species, (int)level, (int)fixedIv, 1, (int)pid, 0, 0);
}

u32 GenPersonalityByGenderAndNature(u16 species, u8 gender, u8 nature) {
    int pid = nature;
    u8 ratio = (u8)GetMonBaseStat(species, BASE_GENDER_RATIO);
    switch (ratio) {
    case MON_RATIO_MALE:
    case MON_RATIO_FEMALE:
    case MON_RATIO_UNKNOWN:
        break;
    default:
        if (gender == MON_MALE) {
            // Smallest increment that forces the low byte to exceed the
            // gender ratio, thus making the mon male
            pid = 25 * ((ratio / 25) + 1);
            pid += nature;
        }
        break;
    }
    return (u32)pid;
}

void CreateMonWithFixedIVs(struct Pokemon *pokemon, int species, int level, int ivs, int personality) {
    CreateMon(pokemon, species, level, 0, 1, personality, 0, 0);
    SetMonData(pokemon, MON_DATA_IVS_WORD, &ivs);
    CalcMonLevelAndStats(pokemon);
}

void CalcMonLevelAndStats(struct Pokemon *pokemon) {
    BOOL decry = AcquireMonLock(pokemon);
    u32 level = (u32)CalcMonLevel(pokemon);
    SetMonData(pokemon, MON_DATA_LEVEL, &level);
    CalcMonStats(pokemon);
    ReleaseMonLock(pokemon, decry);
}

void CalcMonStats(struct Pokemon *pokemon) {
    struct BaseStats *baseStats;
    int level;
    int maxHp;
    int hpIv;
    int hpEv;
    int atkIv;
    int defIv;
    int speedIv;
    int spatkIv;
    int spdefIv;
    int atkEv;
    int defEv;
    int speedEv;
    int spatkEv;
    int spdefEv;
    int form;
    int hp;
    int species;
    int newMaxHp;
    int newAtk;
    int newDef;
    int newSpeed;
    int newSpatk;
    int newSpdef;
    BOOL decry = AcquireMonLock(pokemon);
    level = (int)GetMonData(pokemon, MON_DATA_LEVEL, NULL);
    maxHp = (int)GetMonData(pokemon, MON_DATA_MAXHP, NULL);
    hp = (int)GetMonData(pokemon, MON_DATA_HP, NULL);
    hpIv = (int)GetMonData(pokemon, MON_DATA_HP_IV, NULL);
    hpEv = (int)GetMonData(pokemon, MON_DATA_HP_EV, NULL);
    atkIv = (int)GetMonData(pokemon, MON_DATA_ATK_IV, NULL);
    atkEv = (int)GetMonData(pokemon, MON_DATA_ATK_EV, NULL);
    defIv = (int)GetMonData(pokemon, MON_DATA_DEF_IV, NULL);
    defEv = (int)GetMonData(pokemon, MON_DATA_DEF_EV, NULL);
    speedIv = (int)GetMonData(pokemon, MON_DATA_SPEED_IV, NULL);
    speedEv = (int)GetMonData(pokemon, MON_DATA_SPEED_EV, NULL);
    spatkIv = (int)GetMonData(pokemon, MON_DATA_SPATK_IV, NULL);
    spatkEv = (int)GetMonData(pokemon, MON_DATA_SPATK_EV, NULL);
    spdefIv = (int)GetMonData(pokemon, MON_DATA_SPDEF_IV, NULL);
    spdefEv = (int)GetMonData(pokemon, MON_DATA_SPDEF_EV, NULL);
    form = (int)GetMonData(pokemon, MON_DATA_FORM, NULL);
    species = (int)GetMonData(pokemon, MON_DATA_SPECIES, NULL);

    baseStats = (struct BaseStats *)AllocFromHeap(HEAP_ID_DEFAULT, sizeof(struct BaseStats));
    LoadMonBaseStats_HandleAlternateForm(species, form, baseStats);

    if (species == SPECIES_SHEDINJA) {
        newMaxHp = 1;
    } else {
        newMaxHp = (baseStats->hp * 2 + hpIv + hpEv / 4) * level / 100 + level + 10;
    }
    SetMonData(pokemon, MON_DATA_MAXHP, &newMaxHp);

    newAtk = (baseStats->atk * 2 + atkIv + atkEv / 4) * level / 100 + 5;
    newAtk = ModifyStatByNature(GetMonNature(pokemon), (u16)newAtk, 1);
    SetMonData(pokemon, MON_DATA_ATK, &newAtk);

    newDef = (baseStats->def * 2 + defIv + defEv / 4) * level / 100 + 5;
    newDef = ModifyStatByNature(GetMonNature(pokemon), (u16)newDef, 2);
    SetMonData(pokemon, MON_DATA_DEF, &newDef);

    newSpeed = (baseStats->speed * 2 + speedIv + speedEv / 4) * level / 100 + 5;
    newSpeed = ModifyStatByNature(GetMonNature(pokemon), (u16)newSpeed, 3);
    SetMonData(pokemon, MON_DATA_SPEED, &newSpeed);

    newSpatk = (baseStats->spatk * 2 + spatkIv + spatkEv / 4) * level / 100 + 5;
    newSpatk = ModifyStatByNature(GetMonNature(pokemon), (u16)newSpatk, 4);
    SetMonData(pokemon, MON_DATA_SPATK, &newSpatk);

    newSpdef = (baseStats->spdef * 2 + spdefIv + spdefEv / 4) * level / 100 + 5;
    newSpdef = ModifyStatByNature(GetMonNature(pokemon), (u16)newSpdef, 5);
    SetMonData(pokemon, MON_DATA_SPDEF, &newSpdef);

    FreeToHeap(baseStats);

    if (hp != 0 || maxHp == 0) {
        if (species == SPECIES_SHEDINJA) {
            hp = 1;
        } else if (hp == 0) {
            hp = newMaxHp;
        } else {
            hp += newMaxHp - maxHp;
        }
    }
    if (hp != 0) {
        SetMonData(pokemon, MON_DATA_HP, &hp);
    }
    ReleaseMonLock(pokemon, decry);
}

u32 GetMonData(struct Pokemon *pokemon, int attr, void *dest) {
    u32 ret;
    u32 checksum;
    if (!pokemon->box.party_lock) {
        DECRYPT_PTY(pokemon);
        DECRYPT_BOX(&pokemon->box);
        checksum = CHECKSUM(&pokemon->box);
        if (checksum != pokemon->box.checksum) {
            GF_ASSERT(checksum == pokemon->box.checksum);
            pokemon->box.checksum_fail = TRUE;
        }
    }
    ret = GetMonDataInternal(pokemon, attr, dest);
    if (!pokemon->box.party_lock) {
        ENCRYPT_PTY(pokemon);
        ENCRYPT_BOX(&pokemon->box);
    }
    return ret;
}

u32 GetMonDataInternal(struct Pokemon *pokemon, int attr, void *dest) {
    switch (attr) {
    case MON_DATA_STATUS:
        return pokemon->party.status;
    case MON_DATA_LEVEL:
        return pokemon->party.level;
    case MON_DATA_CAPSULE:
        return pokemon->party.capsule;
    case MON_DATA_HP:
        return pokemon->party.hp;
    case MON_DATA_MAXHP:
        return pokemon->party.maxHp;
    case MON_DATA_ATK:
        return pokemon->party.atk;
    case MON_DATA_DEF:
        return pokemon->party.def;
    case MON_DATA_SPEED:
        return pokemon->party.speed;
    case MON_DATA_SPATK:
        return pokemon->party.spatk;
    case MON_DATA_SPDEF:
        return pokemon->party.spdef;
    case MON_DATA_MAIL_STRUCT:
        Mail_Copy(&pokemon->party.mail, dest);
        return 1;
    case MON_DATA_SEAL_COORDS:
        CapsuleArray_Copy(&pokemon->party.sealCoords, dest);
        return 1;
    default:
        return GetBoxMonDataInternal(&pokemon->box, attr, dest);
    }
}

u32 GetBoxMonData(struct BoxPokemon *boxmon, int attr, void *dest) {
    u32 ret;
    u32 checksum;
    if (!boxmon->box_lock) {
        DECRYPT_BOX(boxmon);
        checksum = CHECKSUM(boxmon);
        if (checksum != boxmon->checksum) {
            GF_ASSERT(checksum == boxmon->checksum);
            boxmon->checksum_fail = TRUE;
        }
    }
    ret = GetBoxMonDataInternal(boxmon, attr, dest);
    if (!boxmon->box_lock) {
        ENCRYPT_BOX(boxmon);
    }
    return ret;
}

u32 GetBoxMonDataInternal(struct BoxPokemon *boxmon, int attr, void *dest) {
    u32 ret = 0;
    PokemonDataBlockA *blockA = &GetSubstruct(boxmon, boxmon->pid, 0)->blockA;
    PokemonDataBlockB *blockB = &GetSubstruct(boxmon, boxmon->pid, 1)->blockB;
    PokemonDataBlockC *blockC = &GetSubstruct(boxmon, boxmon->pid, 2)->blockC;
    PokemonDataBlockD *blockD = &GetSubstruct(boxmon, boxmon->pid, 3)->blockD;

    switch (attr) {
    default:
        ret = 0;
        break;
    case MON_DATA_PERSONALITY:
        ret = boxmon->pid;
        break;
    case MON_DATA_PARTY_LOCK:
        ret = boxmon->party_lock;
        break;
    case MON_DATA_BOX_LOCK:
        ret = boxmon->box_lock;
        break;
    case MON_DATA_CHECKSUM_FAILED:
        ret = boxmon->checksum_fail;
        break;
    case MON_DATA_CHECKSUM:
        ret = boxmon->checksum;
        break;
    case MON_DATA_SPECIES_EXISTS:
        if (blockA->species != SPECIES_NONE) {
            ret = TRUE;
        } else {
            ret = FALSE;
        }
        break;
    case MON_DATA_SANITY_IS_EGG:
        ret = boxmon->checksum_fail;
        if (!ret) {
            ret = blockB->isEgg;
        }
        break;
    case MON_DATA_SPECIES2:
        ret = blockA->species;
        if (ret != SPECIES_NONE && (blockB->isEgg || boxmon->checksum_fail)) {
            ret = SPECIES_EGG;
        }
        break;
    case MON_DATA_LEVEL:
        ret = (u32)CalcLevelBySpeciesAndExp(blockA->species, blockA->exp);
        break;
    case MON_DATA_SPECIES:
        if (boxmon->checksum_fail) {
            ret = SPECIES_EGG;
        } else {
            ret = blockA->species;
        }
        break;
    case MON_DATA_HELD_ITEM:
        ret = blockA->heldItem;
        break;
    case MON_DATA_OTID:
        ret = blockA->otID;
        break;
    case MON_DATA_EXPERIENCE:
        ret = blockA->exp;
        break;
    case MON_DATA_FRIENDSHIP:
        ret = blockA->friendship;
        break;
    case MON_DATA_ABILITY:
        ret = blockA->ability;
        break;
    case MON_DATA_MARKINGS:
        ret = blockA->markings;
        break;
    case MON_DATA_GAME_LANGUAGE:
        ret = blockA->originLanguage;
        break;
    case MON_DATA_HP_EV:
        ret = blockA->hpEV;
        break;
    case MON_DATA_ATK_EV:
        ret = blockA->atkEV;
        break;
    case MON_DATA_DEF_EV:
        ret = blockA->defEV;
        break;
    case MON_DATA_SPEED_EV:
        ret = blockA->spdEV;
        break;
    case MON_DATA_SPATK_EV:
        ret = blockA->spatkEV;
        break;
    case MON_DATA_SPDEF_EV:
        ret = blockA->spdefEV;
        break;
    case MON_DATA_COOL:
        ret = blockA->coolStat;
        break;
    case MON_DATA_BEAUTY:
        ret = blockA->beautyStat;
        break;
    case MON_DATA_CUTE:
        ret = blockA->cuteStat;
        break;
    case MON_DATA_SMART:
        ret = blockA->smartStat;
        break;
    case MON_DATA_TOUGH:
        ret = blockA->toughStat;
        break;
    case MON_DATA_SHEEN:
        ret = blockA->sheen;
        break;
    case MON_DATA_SINNOH_CHAMP_RIBBON:
    case MON_DATA_ABILITY_RIBBON:
    case MON_DATA_GREAT_ABILITY_RIBBON:
    case MON_DATA_DOUBLE_ABILITY_RIBBON:
    case MON_DATA_MULTI_ABILITY_RIBBON:
    case MON_DATA_PAIR_ABILITY_RIBBON:
    case MON_DATA_WORLD_ABILITY_RIBBON:
    case MON_DATA_ALERT_RIBBON:
    case MON_DATA_SHOCK_RIBBON:
    case MON_DATA_DOWNCAST_RIBBON:
    case MON_DATA_CARELESS_RIBBON:
    case MON_DATA_RELAX_RIBBON:
    case MON_DATA_SNOOZE_RIBBON:
    case MON_DATA_SMILE_RIBBON:
    case MON_DATA_GORGEOUS_RIBBON:
    case MON_DATA_ROYAL_RIBBON:
    case MON_DATA_GORGEOUS_ROYAL_RIBBON:
    case MON_DATA_FOOTPRINT_RIBBON:
    case MON_DATA_RECORD_RIBBON:
    case MON_DATA_HISTORY_RIBBON:
    case MON_DATA_LEGEND_RIBBON:
    case MON_DATA_RED_RIBBON:
    case MON_DATA_GREEN_RIBBON:
    case MON_DATA_BLUE_RIBBON:
    case MON_DATA_FESTIVAL_RIBBON:
    case MON_DATA_CARNIVAL_RIBBON:
    case MON_DATA_CLASSIC_RIBBON:
    case MON_DATA_PREMIER_RIBBON:
    case MON_DATA_SINNOH_RIBBON_53: {
        if (blockA->sinnohRibbons & (1ll << (attr - MON_DATA_SINNOH_CHAMP_RIBBON))) {
            ret = TRUE;
        } else {
            ret = FALSE;
        }
    } break;
    case MON_DATA_MOVE1:
    case MON_DATA_MOVE2:
    case MON_DATA_MOVE3:
    case MON_DATA_MOVE4:
        ret = blockB->moves[attr - MON_DATA_MOVE1];
        break;
    case MON_DATA_MOVE1PP:
    case MON_DATA_MOVE2PP:
    case MON_DATA_MOVE3PP:
    case MON_DATA_MOVE4PP:
        ret = blockB->movePP[attr - MON_DATA_MOVE1PP];
        break;
    case MON_DATA_MOVE1PPUP:
    case MON_DATA_MOVE2PPUP:
    case MON_DATA_MOVE3PPUP:
    case MON_DATA_MOVE4PPUP:
        ret = blockB->movePpUps[attr - MON_DATA_MOVE1PPUP];
        break;
    case MON_DATA_MOVE1MAXPP:
    case MON_DATA_MOVE2MAXPP:
    case MON_DATA_MOVE3MAXPP:
    case MON_DATA_MOVE4MAXPP:
        ret = (u32)WazaGetMaxPp(blockB->moves[attr - MON_DATA_MOVE1MAXPP], blockB->movePpUps[attr - MON_DATA_MOVE1MAXPP]);
        break;
    case MON_DATA_HP_IV:
        ret = blockB->hpIV;
        break;
    case MON_DATA_ATK_IV:
        ret = blockB->atkIV;
        break;
    case MON_DATA_DEF_IV:
        ret = blockB->defIV;
        break;
    case MON_DATA_SPEED_IV:
        ret = blockB->spdIV;
        break;
    case MON_DATA_SPATK_IV:
        ret = blockB->spatkIV;
        break;
    case MON_DATA_SPDEF_IV:
        ret = blockB->spdefIV;
        break;
    case MON_DATA_IS_EGG:
        ret = boxmon->checksum_fail;
        if (!ret) {
            ret = blockB->isEgg;
        }
        break;
    case MON_DATA_HAS_NICKNAME:
        ret = blockB->isNicknamed;
        break;
    case MON_DATA_HOENN_COOL_RIBBON:
    case MON_DATA_HOENN_COOL_RIBBON_SUPER:
    case MON_DATA_HOENN_COOL_RIBBON_HYPER:
    case MON_DATA_HOENN_COOL_RIBBON_MASTER:
    case MON_DATA_HOENN_BEAUTY_RIBBON:
    case MON_DATA_HOENN_BEAUTY_RIBBON_SUPER:
    case MON_DATA_HOENN_BEAUTY_RIBBON_HYPER:
    case MON_DATA_HOENN_BEAUTY_RIBBON_MASTER:
    case MON_DATA_HOENN_CUTE_RIBBON:
    case MON_DATA_HOENN_CUTE_RIBBON_SUPER:
    case MON_DATA_HOENN_CUTE_RIBBON_HYPER:
    case MON_DATA_HOENN_CUTE_RIBBON_MASTER:
    case MON_DATA_HOENN_SMART_RIBBON:
    case MON_DATA_HOENN_SMART_RIBBON_SUPER:
    case MON_DATA_HOENN_SMART_RIBBON_HYPER:
    case MON_DATA_HOENN_SMART_RIBBON_MASTER:
    case MON_DATA_HOENN_TOUGH_RIBBON:
    case MON_DATA_HOENN_TOUGH_RIBBON_SUPER:
    case MON_DATA_HOENN_TOUGH_RIBBON_HYPER:
    case MON_DATA_HOENN_TOUGH_RIBBON_MASTER:
    case MON_DATA_HOENN_CHAMPION_RIBBON:
    case MON_DATA_HOENN_WINNING_RIBBON:
    case MON_DATA_HOENN_VICTORY_RIBBON:
    case MON_DATA_HOENN_ARTIST_RIBBON:
    case MON_DATA_HOENN_EFFORT_RIBBON:
    case MON_DATA_HOENN_MARINE_RIBBON:
    case MON_DATA_HOENN_LAND_RIBBON:
    case MON_DATA_HOENN_SKY_RIBBON:
    case MON_DATA_HOENN_COUNTRY_RIBBON:
    case MON_DATA_HOENN_NATIONAL_RIBBON:
    case MON_DATA_HOENN_EARTH_RIBBON:
    case MON_DATA_HOENN_WORLD_RIBBON:
        if (blockB->ribbonFlags & (1ll << (attr - MON_DATA_HOENN_COOL_RIBBON))) {
            ret = TRUE;
        } else {
            ret = FALSE;
        }
        break;
    case MON_DATA_FATEFUL_ENCOUNTER:
        ret = blockB->fatefulEncounter;
        break;
    case MON_DATA_GENDER:
        ret = blockB->gender;
        break;
    case MON_DATA_FORM:
        ret = blockB->alternateForm;
        break;
    case MON_DATA_RESERVED_113:
        ret = blockB->HGSS_shinyLeaves;
        break;
    case MON_DATA_RESERVED_114:
        ret = blockB->Unused;
        break;
    case MON_DATA_NICKNAME:
        if (boxmon->checksum_fail) {
            GetSpeciesNameIntoArray(SPECIES_MANAPHY_EGG, HEAP_ID_DEFAULT, dest);
        } else {
            u16 *dest16 = (u16 *)dest;
            for (ret = 0; ret < POKEMON_NAME_LENGTH; ret++) {
                dest16[ret] = blockC->nickname[ret];
            }
            dest16[ret] = EOS;
        }
        break;
    case MON_DATA_NICKNAME_4:
        ret = blockB->isNicknamed;
        // fallthrough
    case MON_DATA_NICKNAME_3:
        if (boxmon->checksum_fail) {
            struct String *buffer = GetSpeciesName(SPECIES_MANAPHY_EGG, HEAP_ID_DEFAULT);
            StringCopy(dest, buffer);
            String_Delete(buffer);
        } else {
            CopyU16ArrayToString(dest, blockC->nickname);
        }
        break;
    case MON_DATA_UNK_120:
        ret = blockC->Unused;
        break;
    case MON_DATA_GAME_VERSION:
        ret = blockC->originGame;
        break;
    case MON_DATA_COOL_RIBBON:
    case MON_DATA_COOL_RIBBON_GREAT:
    case MON_DATA_COOL_RIBBON_ULTRA:
    case MON_DATA_COOL_RIBBON_MASTER:
    case MON_DATA_BEAUTY_RIBBON:
    case MON_DATA_BEAUTY_RIBBON_GREAT:
    case MON_DATA_BEAUTY_RIBBON_ULTRA:
    case MON_DATA_BEAUTY_RIBBON_MASTER:
    case MON_DATA_CUTE_RIBBON:
    case MON_DATA_CUTE_RIBBON_GREAT:
    case MON_DATA_CUTE_RIBBON_ULTRA:
    case MON_DATA_CUTE_RIBBON_MASTER:
    case MON_DATA_SMART_RIBBON:
    case MON_DATA_SMART_RIBBON_GREAT:
    case MON_DATA_SMART_RIBBON_ULTRA:
    case MON_DATA_SMART_RIBBON_MASTER:
    case MON_DATA_TOUGH_RIBBON:
    case MON_DATA_TOUGH_RIBBON_GREAT:
    case MON_DATA_TOUGH_RIBBON_ULTRA:
    case MON_DATA_TOUGH_RIBBON_MASTER:
    case MON_DATA_SINNOH_RIBBON_142:
        if (blockC->sinnohRibbons2 & (1ll << (attr - MON_DATA_COOL_RIBBON))) {
            ret = TRUE;
        } else {
            ret = FALSE;
        }
        break;
    case MON_DATA_OT_NAME: {
        u16 *dest16 = (u16 *)dest;
        for (ret = 0; ret < PLAYER_NAME_LENGTH; ret++) {
            dest16[ret] = blockD->otTrainerName[ret];
        }
        dest16[ret] = EOS;
    } break;
    case MON_DATA_OT_NAME_2:
        CopyU16ArrayToString(dest, blockD->otTrainerName);
        break;
    case MON_DATA_EGG_MET_YEAR:
        ret = blockD->dateEggReceived[0];
        break;
    case MON_DATA_EGG_MET_MONTH:
        ret = blockD->dateEggReceived[1];
        break;
    case MON_DATA_EGG_MET_DAY:
        ret = blockD->dateEggReceived[2];
        break;
    case MON_DATA_MET_YEAR:
        ret = blockD->dateMet[0];
        break;
    case MON_DATA_MET_MONTH:
        ret = blockD->dateMet[1];
        break;
    case MON_DATA_MET_DAY:
        ret = blockD->dateMet[2];
        break;
    case MON_DATA_EGG_MET_LOCATION:
        ret = blockD->DP_EggLocation;
        break;
    case MON_DATA_MET_LOCATION:
        ret = blockD->DP_MetLocation;
        break;
    case MON_DATA_POKERUS:
        ret = blockD->pokerus;
        break;
    case MON_DATA_POKEBALL:
        ret = blockD->pokeball;
        break;
    case MON_DATA_MET_LEVEL:
        ret = blockD->metLevel;
        break;
    case MON_DATA_MET_GENDER:
        ret = blockD->metGender;
        break;
    case MON_DATA_ENCOUNTER_TYPE:
        ret = blockD->encounterType;
        break;
    case MON_DATA_RESERVED_158:
        ret = blockD->HGSS_Pokeball;
        break;
    case MON_DATA_IVS_WORD:
        ret = (blockB->hpIV) | (blockB->atkIV << 5) | (blockB->defIV << 10) | (blockB->spdIV << 15) | (blockB->spatkIV << 20) | (blockB->spdefIV << 25);
        break;
    case MON_DATA_UNK_175:
        if ((blockA->species == SPECIES_NIDORAN_F || blockA->species == SPECIES_NIDORAN_M) && !blockB->isNicknamed) {
            ret = FALSE;
        } else {
            ret = TRUE;
        }
        break;
    case MON_DATA_TYPE_1:
    case MON_DATA_TYPE_2:
        if (blockA->species == SPECIES_ARCEUS && blockA->ability == ABILITY_MULTITYPE) {
            ret = (u32)GetArceusTypeByHeldItemEffect((u16)GetItemAttr(blockA->heldItem, 1, HEAP_ID_DEFAULT));
        } else {
            ret = (u32)GetMonBaseStat_HandleFormConversion(blockA->species, blockB->alternateForm, (enum BaseStat)(attr - MON_DATA_TYPE_1 + BASE_TYPE1));
        }
        break;
    case MON_DATA_SPECIES_NAME:
        GetSpeciesNameIntoArray(blockA->species, HEAP_ID_DEFAULT, dest);
        break;
    }
    return ret;
}

void SetMonData(struct Pokemon *pokemon, int attr, void *value) {
    u16 checksum;
    if (!pokemon->box.party_lock) {
        DECRYPT_PTY(pokemon);
        DECRYPT_BOX(&pokemon->box);
        checksum = CHECKSUM(&pokemon->box);
        if (checksum != pokemon->box.checksum) {
            GF_ASSERT(checksum == pokemon->box.checksum);
            pokemon->box.checksum_fail = TRUE;
            ENCRYPT_BOX(&pokemon->box);
            return;
        }
    }
    SetMonDataInternal(pokemon, attr, value);
    if (!pokemon->box.party_lock) {
        ENCRYPT_PTY(pokemon);
        pokemon->box.checksum = CHECKSUM(&pokemon->box);
        ENCRYPT_BOX(&pokemon->box);
    }
}

void SetMonDataInternal(struct Pokemon *pokemon, int attr, void *value) {
#define VALUE(type) (*(const type *)value)
    switch (attr) {
    case MON_DATA_STATUS:
        pokemon->party.status = VALUE(u32);
        break;
    case MON_DATA_LEVEL:
        pokemon->party.level = VALUE(u8);
        break;
    case MON_DATA_CAPSULE:
        pokemon->party.capsule = VALUE(u8);
        break;
    case MON_DATA_HP:
        pokemon->party.hp = VALUE(u16);
        break;
    case MON_DATA_MAXHP:
        pokemon->party.maxHp = VALUE(u16);
        break;
    case MON_DATA_ATK:
        pokemon->party.atk = VALUE(u16);
        break;
    case MON_DATA_DEF:
        pokemon->party.def = VALUE(u16);
        break;
    case MON_DATA_SPEED:
        pokemon->party.speed = VALUE(u16);
        break;
    case MON_DATA_SPATK:
        pokemon->party.spatk = VALUE(u16);
        break;
    case MON_DATA_SPDEF:
        pokemon->party.spdef = VALUE(u16);
        break;
    case MON_DATA_MAIL_STRUCT:
        Mail_Copy((const struct Mail *)value, &pokemon->party.mail);
        break;
    case MON_DATA_SEAL_COORDS:
        CapsuleArray_Copy((CapsuleArray *)value, &pokemon->party.sealCoords);
        break;
    default:
        SetBoxMonDataInternal(&pokemon->box, attr, value);
        break;
    }
#undef VALUE
}

void SetBoxMonData(struct BoxPokemon *boxmon, int attr, void *value) {
    u16 checksum;
    if (!boxmon->box_lock) {
        DECRYPT_BOX(boxmon);
        checksum = CHECKSUM(boxmon);
        if (checksum != boxmon->checksum) {
            GF_ASSERT(checksum == boxmon->checksum);
            boxmon->checksum_fail = TRUE;
            ENCRYPT_BOX(boxmon);
            return;
        }
    }
    SetBoxMonDataInternal(boxmon, attr, value);
    if (!boxmon->box_lock) {
        boxmon->checksum = CHECKSUM(boxmon);
        ENCRYPT_BOX(boxmon);
    }
}

void SetBoxMonDataInternal(struct BoxPokemon *boxmon, int attr, void *value) {
#define VALUE(type) (*(const type *)value)
    u8 flag;
    u64 mask;
    u32 i;
    u16 namebuf[POKEMON_NAME_LENGTH + 1];
    u16 namebuf2[POKEMON_NAME_LENGTH + 1];
    u16 namebuf3[POKEMON_NAME_LENGTH + 1];
    struct String *speciesName;

    PokemonDataBlockA *blockA = &GetSubstruct(boxmon, boxmon->pid, 0)->blockA;
    PokemonDataBlockB *blockB = &GetSubstruct(boxmon, boxmon->pid, 1)->blockB;
    PokemonDataBlockC *blockC = &GetSubstruct(boxmon, boxmon->pid, 2)->blockC;
    PokemonDataBlockD *blockD = &GetSubstruct(boxmon, boxmon->pid, 3)->blockD;

    switch (attr) {
    case MON_DATA_PERSONALITY:
        boxmon->pid = VALUE(u32);
        break;
    case MON_DATA_PARTY_LOCK:
        GF_ASSERT(0);
        boxmon->party_lock = VALUE(u8);
        break;
    case MON_DATA_BOX_LOCK:
        GF_ASSERT(0);
        boxmon->box_lock = VALUE(u8);
        break;
    case MON_DATA_CHECKSUM_FAILED:
        boxmon->checksum_fail = VALUE(u8);
        break;
    case MON_DATA_CHECKSUM:
        boxmon->checksum = VALUE(u16);
        break;
    case MON_DATA_SPECIES:
        blockA->species = VALUE(u16);
        break;
    case MON_DATA_HELD_ITEM:
        blockA->heldItem = VALUE(u16);
        break;
    case MON_DATA_OTID:
        blockA->otID = VALUE(u32);
        break;
    case MON_DATA_EXPERIENCE:
        blockA->exp = VALUE(u32);
        break;
    case MON_DATA_FRIENDSHIP:
        blockA->friendship = VALUE(u8);
        break;
    case MON_DATA_ABILITY:
        blockA->ability = VALUE(u8);
        break;
    case MON_DATA_MARKINGS:
        blockA->markings = VALUE(u8);
        break;
    case MON_DATA_GAME_LANGUAGE:
        blockA->originLanguage = VALUE(u8);
        break;
    case MON_DATA_HP_EV:
        blockA->hpEV = VALUE(u8);
        break;
    case MON_DATA_ATK_EV:
        blockA->atkEV = VALUE(u8);
        break;
    case MON_DATA_DEF_EV:
        blockA->defEV = VALUE(u8);
        break;
    case MON_DATA_SPEED_EV:
        blockA->spdEV = VALUE(u8);
        break;
    case MON_DATA_SPATK_EV:
        blockA->spatkEV = VALUE(u8);
        break;
    case MON_DATA_SPDEF_EV:
        blockA->spdefEV = VALUE(u8);
        break;
    case MON_DATA_COOL:
        blockA->coolStat = VALUE(u8);
        break;
    case MON_DATA_BEAUTY:
        blockA->beautyStat = VALUE(u8);
        break;
    case MON_DATA_CUTE:
        blockA->cuteStat = VALUE(u8);
        break;
    case MON_DATA_SMART:
        blockA->smartStat = VALUE(u8);
        break;
    case MON_DATA_TOUGH:
        blockA->toughStat = VALUE(u8);
        break;
    case MON_DATA_SHEEN:
        blockA->sheen = VALUE(u8);
        break;
    case MON_DATA_SINNOH_CHAMP_RIBBON:
    case MON_DATA_ABILITY_RIBBON:
    case MON_DATA_GREAT_ABILITY_RIBBON:
    case MON_DATA_DOUBLE_ABILITY_RIBBON:
    case MON_DATA_MULTI_ABILITY_RIBBON:
    case MON_DATA_PAIR_ABILITY_RIBBON:
    case MON_DATA_WORLD_ABILITY_RIBBON:
    case MON_DATA_ALERT_RIBBON:
    case MON_DATA_SHOCK_RIBBON:
    case MON_DATA_DOWNCAST_RIBBON:
    case MON_DATA_CARELESS_RIBBON:
    case MON_DATA_RELAX_RIBBON:
    case MON_DATA_SNOOZE_RIBBON:
    case MON_DATA_SMILE_RIBBON:
    case MON_DATA_GORGEOUS_RIBBON:
    case MON_DATA_ROYAL_RIBBON:
    case MON_DATA_GORGEOUS_ROYAL_RIBBON:
    case MON_DATA_FOOTPRINT_RIBBON:
    case MON_DATA_RECORD_RIBBON:
    case MON_DATA_HISTORY_RIBBON:
    case MON_DATA_LEGEND_RIBBON:
    case MON_DATA_RED_RIBBON:
    case MON_DATA_GREEN_RIBBON:
    case MON_DATA_BLUE_RIBBON:
    case MON_DATA_FESTIVAL_RIBBON:
    case MON_DATA_CARNIVAL_RIBBON:
    case MON_DATA_CLASSIC_RIBBON:
    case MON_DATA_PREMIER_RIBBON:
    case MON_DATA_SINNOH_RIBBON_53:
        flag = VALUE(u8);
        mask = (u64)flag << (attr - MON_DATA_SINNOH_CHAMP_RIBBON);
        if (flag) {
            blockA->sinnohRibbons |= mask;
        } else {
            blockA->sinnohRibbons &= -1ull ^ mask;
        }
        break;
    case MON_DATA_MOVE1:
    case MON_DATA_MOVE2:
    case MON_DATA_MOVE3:
    case MON_DATA_MOVE4:
        blockB->moves[attr - MON_DATA_MOVE1] = VALUE(u16);
        break;
    case MON_DATA_MOVE1PP:
    case MON_DATA_MOVE2PP:
    case MON_DATA_MOVE3PP:
    case MON_DATA_MOVE4PP:
        blockB->movePP[attr - MON_DATA_MOVE1PP] = VALUE(u8);
        break;
    case MON_DATA_MOVE1PPUP:
    case MON_DATA_MOVE2PPUP:
    case MON_DATA_MOVE3PPUP:
    case MON_DATA_MOVE4PPUP:
        blockB->movePpUps[attr - MON_DATA_MOVE1PPUP] = VALUE(u8);
        break;
    case MON_DATA_HP_IV:
        blockB->hpIV = VALUE(u8);
        break;
    case MON_DATA_ATK_IV:
        blockB->atkIV = VALUE(u8);
        break;
    case MON_DATA_DEF_IV:
        blockB->defIV = VALUE(u8);
        break;
    case MON_DATA_SPEED_IV:
        blockB->spdIV = VALUE(u8);
        break;
    case MON_DATA_SPATK_IV:
        blockB->spatkIV = VALUE(u8);
        break;
    case MON_DATA_SPDEF_IV:
        blockB->spdefIV = VALUE(u8);
        break;
    case MON_DATA_IS_EGG:
        blockB->isEgg = VALUE(u8);
        break;
    case MON_DATA_HAS_NICKNAME:
        blockB->isNicknamed = VALUE(u8);
        break;
    case MON_DATA_HOENN_COOL_RIBBON:
    case MON_DATA_HOENN_COOL_RIBBON_SUPER:
    case MON_DATA_HOENN_COOL_RIBBON_HYPER:
    case MON_DATA_HOENN_COOL_RIBBON_MASTER:
    case MON_DATA_HOENN_BEAUTY_RIBBON:
    case MON_DATA_HOENN_BEAUTY_RIBBON_SUPER:
    case MON_DATA_HOENN_BEAUTY_RIBBON_HYPER:
    case MON_DATA_HOENN_BEAUTY_RIBBON_MASTER:
    case MON_DATA_HOENN_CUTE_RIBBON:
    case MON_DATA_HOENN_CUTE_RIBBON_SUPER:
    case MON_DATA_HOENN_CUTE_RIBBON_HYPER:
    case MON_DATA_HOENN_CUTE_RIBBON_MASTER:
    case MON_DATA_HOENN_SMART_RIBBON:
    case MON_DATA_HOENN_SMART_RIBBON_SUPER:
    case MON_DATA_HOENN_SMART_RIBBON_HYPER:
    case MON_DATA_HOENN_SMART_RIBBON_MASTER:
    case MON_DATA_HOENN_TOUGH_RIBBON:
    case MON_DATA_HOENN_TOUGH_RIBBON_SUPER:
    case MON_DATA_HOENN_TOUGH_RIBBON_HYPER:
    case MON_DATA_HOENN_TOUGH_RIBBON_MASTER:
    case MON_DATA_HOENN_CHAMPION_RIBBON:
    case MON_DATA_HOENN_WINNING_RIBBON:
    case MON_DATA_HOENN_VICTORY_RIBBON:
    case MON_DATA_HOENN_ARTIST_RIBBON:
    case MON_DATA_HOENN_EFFORT_RIBBON:
    case MON_DATA_HOENN_MARINE_RIBBON:
    case MON_DATA_HOENN_LAND_RIBBON:
    case MON_DATA_HOENN_SKY_RIBBON:
    case MON_DATA_HOENN_COUNTRY_RIBBON:
    case MON_DATA_HOENN_NATIONAL_RIBBON:
    case MON_DATA_HOENN_EARTH_RIBBON:
    case MON_DATA_HOENN_WORLD_RIBBON:
        flag = VALUE(u8);
        mask = (u64)flag << (attr - MON_DATA_HOENN_COOL_RIBBON);
        if (flag) {
            blockB->ribbonFlags |= mask;
        } else {
            blockB->ribbonFlags &= -1ull ^ mask;
        }
        break;
    case MON_DATA_FATEFUL_ENCOUNTER:
        blockB->fatefulEncounter = VALUE(u8);
        break;
    case MON_DATA_GENDER:
        blockB->gender = VALUE(u8);
        break;
    case MON_DATA_FORM:
        blockB->alternateForm = VALUE(u8);
        break;
    case MON_DATA_RESERVED_113:
        blockB->HGSS_shinyLeaves = VALUE(u8);
        break;
    case MON_DATA_RESERVED_114:
        blockB->Unused = VALUE(u16);
        break;
    case MON_DATA_NICKNAME_2:
        GetSpeciesNameIntoArray(blockA->species, HEAP_ID_DEFAULT, namebuf);
        blockB->isNicknamed = StringNotEqual(namebuf, value);
        // fallthrough
    case MON_DATA_NICKNAME:
        for (i = 0; i < POKEMON_NAME_LENGTH + 1; i++) {
            blockC->nickname[i] = VALUE(u16);
            value = (void *const)((char *)value + 2);
        }
        break;
    case MON_DATA_NICKNAME_4:
        GetSpeciesNameIntoArray(blockA->species, HEAP_ID_DEFAULT, namebuf2);
        CopyStringToU16Array(value, namebuf3, POKEMON_NAME_LENGTH + 1);
        blockB->isNicknamed = StringNotEqual(namebuf2, namebuf3);
        // fallthrough
    case MON_DATA_NICKNAME_3:
        CopyStringToU16Array(value, blockC->nickname, POKEMON_NAME_LENGTH + 1);
        break;
    case MON_DATA_UNK_120:
        blockC->Unused = VALUE(u8);
        break;
    case MON_DATA_GAME_VERSION:
        blockC->originGame = VALUE(u8);
        break;
    case MON_DATA_COOL_RIBBON:
    case MON_DATA_COOL_RIBBON_GREAT:
    case MON_DATA_COOL_RIBBON_ULTRA:
    case MON_DATA_COOL_RIBBON_MASTER:
    case MON_DATA_BEAUTY_RIBBON:
    case MON_DATA_BEAUTY_RIBBON_GREAT:
    case MON_DATA_BEAUTY_RIBBON_ULTRA:
    case MON_DATA_BEAUTY_RIBBON_MASTER:
    case MON_DATA_CUTE_RIBBON:
    case MON_DATA_CUTE_RIBBON_GREAT:
    case MON_DATA_CUTE_RIBBON_ULTRA:
    case MON_DATA_CUTE_RIBBON_MASTER:
    case MON_DATA_SMART_RIBBON:
    case MON_DATA_SMART_RIBBON_GREAT:
    case MON_DATA_SMART_RIBBON_ULTRA:
    case MON_DATA_SMART_RIBBON_MASTER:
    case MON_DATA_TOUGH_RIBBON:
    case MON_DATA_TOUGH_RIBBON_GREAT:
    case MON_DATA_TOUGH_RIBBON_ULTRA:
    case MON_DATA_TOUGH_RIBBON_MASTER:
    case MON_DATA_SINNOH_RIBBON_142:
        flag = VALUE(u8);
        mask = (u64)flag << (attr - MON_DATA_COOL_RIBBON);
        if (flag) {
            blockC->sinnohRibbons2 |= mask;
        } else {
            blockC->sinnohRibbons2 &= -1ull ^ mask;
        }
        break;
    case MON_DATA_OT_NAME:
        for (i = 0; i < PLAYER_NAME_LENGTH + 1; i++) {
            blockD->otTrainerName[i] = VALUE(u16);
            value = (void *)((char *)value + 2);
        }
        break;
    case MON_DATA_OT_NAME_2:
        CopyStringToU16Array(value, blockD->otTrainerName, PLAYER_NAME_LENGTH + 1);
        break;
    case MON_DATA_EGG_MET_YEAR:
        blockD->dateEggReceived[0] = VALUE(u8);
        break;
    case MON_DATA_EGG_MET_MONTH:
        blockD->dateEggReceived[1] = VALUE(u8);
        break;
    case MON_DATA_EGG_MET_DAY:
        blockD->dateEggReceived[2] = VALUE(u8);
        break;
    case MON_DATA_MET_YEAR:
        blockD->dateMet[0] = VALUE(u8);
        break;
    case MON_DATA_MET_MONTH:
        blockD->dateMet[1] = VALUE(u8);
        break;
    case MON_DATA_MET_DAY:
        blockD->dateMet[2] = VALUE(u8);
        break;
    case MON_DATA_EGG_MET_LOCATION:
        blockD->DP_EggLocation = VALUE(u16);
        break;
    case MON_DATA_MET_LOCATION:
        blockD->DP_MetLocation = VALUE(u16);
        break;
    case MON_DATA_POKERUS:
        blockD->pokerus = VALUE(u8);
        break;
    case MON_DATA_POKEBALL:
        blockD->pokeball = VALUE(u8);
        break;
    case MON_DATA_MET_LEVEL:
        blockD->metLevel = VALUE(u8);
        break;
    case MON_DATA_MET_GENDER:
        blockD->metGender = VALUE(u8);
        break;
    case MON_DATA_ENCOUNTER_TYPE:
        blockD->encounterType = VALUE(u8);
        break;
    case MON_DATA_RESERVED_158:
        blockD->HGSS_Pokeball = VALUE(u16);
        break;
    case MON_DATA_IVS_WORD:
        blockB->hpIV = (VALUE(u32) >> 0) & 0x1F;
        blockB->atkIV = (VALUE(u32) >> 5) & 0x1F;
        blockB->defIV = (VALUE(u32) >> 10) & 0x1F;
        blockB->spdIV = (VALUE(u32) >> 15) & 0x1F;
        blockB->spatkIV = (VALUE(u32) >> 20) & 0x1F;
        blockB->spdefIV = (VALUE(u32) >> 25) & 0x1F;
        break;
    case MON_DATA_SPECIES_NAME:
        speciesName = GetSpeciesName(blockA->species, HEAP_ID_DEFAULT);
        CopyStringToU16Array(speciesName, blockC->nickname, POKEMON_NAME_LENGTH + 1);
        String_Delete(speciesName);
        break;
    }
#undef VALUE
}

void AddMonData(struct Pokemon *pokemon, int attr, int value) {
    u16 checksum;
    if (!pokemon->box.party_lock) {
        DECRYPT_PTY(pokemon);
        DECRYPT_BOX(&pokemon->box);
        checksum = CHECKSUM(&pokemon->box);
        if (checksum != pokemon->box.checksum) {
            GF_ASSERT(checksum == pokemon->box.checksum);
            ENCRYPT_BOX(&pokemon->box);
            return;
        }
    }
    AddMonDataInternal(pokemon, attr, value);
    if (!pokemon->box.party_lock) {
        ENCRYPT_PTY(pokemon);
        pokemon->box.checksum = CHECKSUM(&pokemon->box);
        ENCRYPT_BOX(&pokemon->box);
    }
}

void AddMonDataInternal(struct Pokemon *pokemon, int attr, int value) {
    s32 maxHp;
    switch (attr) {
    case MON_DATA_HP:
        maxHp = pokemon->party.maxHp;
        if ((s32)(pokemon->party.hp + value) > maxHp) {
            pokemon->party.hp = (u16)maxHp;
        } else {
            pokemon->party.hp += value;
        }
        break;
    case MON_DATA_STATUS:
    case MON_DATA_LEVEL:
    case MON_DATA_CAPSULE:
    case MON_DATA_MAXHP:
    case MON_DATA_ATK:
    case MON_DATA_DEF:
    case MON_DATA_SPEED:
    case MON_DATA_SPATK:
    case MON_DATA_SPDEF:
    case MON_DATA_MAIL_STRUCT:
        // case MON_DATA_SEAL_COORDS:
        GF_ASSERT(0);
        break;
    default:
        AddBoxMonData(&pokemon->box, attr, value);
        break;
    }
}

void AddBoxMonData(struct BoxPokemon *boxmon, int attr, int value) {
    PokemonDataBlockA *blockA = &GetSubstruct(boxmon, boxmon->pid, 0)->blockA;
    PokemonDataBlockB *blockB = &GetSubstruct(boxmon, boxmon->pid, 1)->blockB;
    PokemonDataBlockC *blockC = &GetSubstruct(boxmon, boxmon->pid, 2)->blockC;
    PokemonDataBlockD *blockD = &GetSubstruct(boxmon, boxmon->pid, 3)->blockD;

    switch (attr) {
    case MON_DATA_EXPERIENCE:
        if (blockA->exp + value > GetMonExpBySpeciesAndLevel(blockA->species, 100)) {
            blockA->exp = GetMonExpBySpeciesAndLevel(blockA->species, 100);
        } else {
            blockA->exp += value;
        }
        break;
    case MON_DATA_FRIENDSHIP:
        if (blockA->friendship + value > 255) {
            blockA->friendship = 255;
        } else {
            blockA->friendship += value;
        }
        break;
    case MON_DATA_HP_EV:
        blockA->hpEV += value;
        break;
    case MON_DATA_ATK_EV:
        blockA->atkEV += value;
        break;
    case MON_DATA_DEF_EV:
        blockA->defEV += value;
        break;
    case MON_DATA_SPEED_EV:
        blockA->spdEV += value;
        break;
    case MON_DATA_SPATK_EV:
        blockA->spatkEV += value;
        break;
    case MON_DATA_SPDEF_EV:
        blockA->spdefEV += value;
        break;
    case MON_DATA_COOL:
        if (blockA->coolStat + value > 255) {
            blockA->coolStat = 255;
        } else {
            blockA->coolStat += value;
        }
        break;
    case MON_DATA_BEAUTY:
        if (blockA->beautyStat + value > 255) {
            blockA->beautyStat = 255;
        } else {
            blockA->beautyStat += value;
        }
        break;
    case MON_DATA_CUTE:
        if (blockA->cuteStat + value > 255) {
            blockA->cuteStat = 255;
        } else {
            blockA->cuteStat += value;
        }
        break;
    case MON_DATA_SMART:
        if (blockA->smartStat + value > 255) {
            blockA->smartStat = 255;
        } else {
            blockA->smartStat += value;
        }
        break;
    case MON_DATA_TOUGH:
        if (blockA->toughStat + value > 255) {
            blockA->toughStat = 255;
        } else {
            blockA->toughStat += value;
        }
        break;
    case MON_DATA_SHEEN:
        if (blockA->sheen + value > 255) {
            blockA->sheen = 255;
        } else {
            blockA->sheen += value;
        }
        break;
    case MON_DATA_MOVE1PP:
    case MON_DATA_MOVE2PP:
    case MON_DATA_MOVE3PP:
    case MON_DATA_MOVE4PP:
        if (blockB->movePP[attr - MON_DATA_MOVE1PP] + value > WazaGetMaxPp(blockB->moves[attr - MON_DATA_MOVE1PP], blockB->movePpUps[attr - MON_DATA_MOVE1PP])) {
            blockB->movePP[attr - MON_DATA_MOVE1PP] = (u8)WazaGetMaxPp(blockB->moves[attr - MON_DATA_MOVE1PP], blockB->movePpUps[attr - MON_DATA_MOVE1PP]);
        } else {
            blockB->movePP[attr - MON_DATA_MOVE1PP] += value;
        }
        break;
    case MON_DATA_MOVE1PPUP:
    case MON_DATA_MOVE2PPUP:
    case MON_DATA_MOVE3PPUP:
    case MON_DATA_MOVE4PPUP:
        if (blockB->movePpUps[attr - MON_DATA_MOVE1PPUP] + value > 3) {
            blockB->movePpUps[attr - MON_DATA_MOVE1PPUP] = 3;
        } else {
            blockB->movePpUps[attr - MON_DATA_MOVE1PPUP] += value;
        }
        break;
    case MON_DATA_MOVE1MAXPP:
    case MON_DATA_MOVE2MAXPP:
    case MON_DATA_MOVE3MAXPP:
    case MON_DATA_MOVE4MAXPP:
        break;
    case MON_DATA_HP_IV:
        if (blockB->hpIV + value > 31) {
            blockB->hpIV = 31;
        } else {
            blockB->hpIV += value;
        }
        break;
    case MON_DATA_ATK_IV:
        if (blockB->atkIV + value > 31) {
            blockB->atkIV = 31;
        } else {
            blockB->atkIV += value;
        }
        break;
    case MON_DATA_DEF_IV:
        if (blockB->defIV + value > 31) {
            blockB->defIV = 31;
        } else {
            blockB->defIV += value;
        }
        break;
    case MON_DATA_SPEED_IV:
        if (blockB->spdIV + value > 31) {
            blockB->spdIV = 31;
        } else {
            blockB->spdIV += value;
        }
        break;
    case MON_DATA_SPATK_IV:
        if (blockB->spatkIV + value > 31) {
            blockB->spatkIV = 31;
        } else {
            blockB->spatkIV += value;
        }
        break;
    case MON_DATA_SPDEF_IV:
        if (blockB->spdefIV + value > 31) {
            blockB->spdefIV = 31;
        } else {
            blockB->spdefIV += value;
        }
        break;
    case MON_DATA_PERSONALITY:
    case MON_DATA_PARTY_LOCK:
    case MON_DATA_BOX_LOCK:
    case MON_DATA_CHECKSUM_FAILED:
    case MON_DATA_CHECKSUM:
    case MON_DATA_SPECIES:
    case MON_DATA_HELD_ITEM:
    case MON_DATA_OTID:
    case MON_DATA_ABILITY:
    case MON_DATA_MARKINGS:
    case MON_DATA_GAME_LANGUAGE:
    case MON_DATA_SINNOH_CHAMP_RIBBON:
    case MON_DATA_ABILITY_RIBBON:
    case MON_DATA_GREAT_ABILITY_RIBBON:
    case MON_DATA_DOUBLE_ABILITY_RIBBON:
    case MON_DATA_MULTI_ABILITY_RIBBON:
    case MON_DATA_PAIR_ABILITY_RIBBON:
    case MON_DATA_WORLD_ABILITY_RIBBON:
    case MON_DATA_ALERT_RIBBON:
    case MON_DATA_SHOCK_RIBBON:
    case MON_DATA_DOWNCAST_RIBBON:
    case MON_DATA_CARELESS_RIBBON:
    case MON_DATA_RELAX_RIBBON:
    case MON_DATA_SNOOZE_RIBBON:
    case MON_DATA_SMILE_RIBBON:
    case MON_DATA_GORGEOUS_RIBBON:
    case MON_DATA_ROYAL_RIBBON:
    case MON_DATA_GORGEOUS_ROYAL_RIBBON:
    case MON_DATA_FOOTPRINT_RIBBON:
    case MON_DATA_RECORD_RIBBON:
    case MON_DATA_HISTORY_RIBBON:
    case MON_DATA_LEGEND_RIBBON:
    case MON_DATA_RED_RIBBON:
    case MON_DATA_GREEN_RIBBON:
    case MON_DATA_BLUE_RIBBON:
    case MON_DATA_FESTIVAL_RIBBON:
    case MON_DATA_CARNIVAL_RIBBON:
    case MON_DATA_CLASSIC_RIBBON:
    case MON_DATA_PREMIER_RIBBON:
    case MON_DATA_SINNOH_RIBBON_53:
    case MON_DATA_MOVE1:
    case MON_DATA_MOVE2:
    case MON_DATA_MOVE3:
    case MON_DATA_MOVE4:
    case MON_DATA_IS_EGG:
    case MON_DATA_HAS_NICKNAME:
    case MON_DATA_HOENN_COOL_RIBBON:
    case MON_DATA_HOENN_COOL_RIBBON_SUPER:
    case MON_DATA_HOENN_COOL_RIBBON_HYPER:
    case MON_DATA_HOENN_COOL_RIBBON_MASTER:
    case MON_DATA_HOENN_BEAUTY_RIBBON:
    case MON_DATA_HOENN_BEAUTY_RIBBON_SUPER:
    case MON_DATA_HOENN_BEAUTY_RIBBON_HYPER:
    case MON_DATA_HOENN_BEAUTY_RIBBON_MASTER:
    case MON_DATA_HOENN_CUTE_RIBBON:
    case MON_DATA_HOENN_CUTE_RIBBON_SUPER:
    case MON_DATA_HOENN_CUTE_RIBBON_HYPER:
    case MON_DATA_HOENN_CUTE_RIBBON_MASTER:
    case MON_DATA_HOENN_SMART_RIBBON:
    case MON_DATA_HOENN_SMART_RIBBON_SUPER:
    case MON_DATA_HOENN_SMART_RIBBON_HYPER:
    case MON_DATA_HOENN_SMART_RIBBON_MASTER:
    case MON_DATA_HOENN_TOUGH_RIBBON:
    case MON_DATA_HOENN_TOUGH_RIBBON_SUPER:
    case MON_DATA_HOENN_TOUGH_RIBBON_HYPER:
    case MON_DATA_HOENN_TOUGH_RIBBON_MASTER:
    case MON_DATA_HOENN_CHAMPION_RIBBON:
    case MON_DATA_HOENN_WINNING_RIBBON:
    case MON_DATA_HOENN_VICTORY_RIBBON:
    case MON_DATA_HOENN_ARTIST_RIBBON:
    case MON_DATA_HOENN_EFFORT_RIBBON:
    case MON_DATA_HOENN_MARINE_RIBBON:
    case MON_DATA_HOENN_LAND_RIBBON:
    case MON_DATA_HOENN_SKY_RIBBON:
    case MON_DATA_HOENN_COUNTRY_RIBBON:
    case MON_DATA_HOENN_NATIONAL_RIBBON:
    case MON_DATA_HOENN_EARTH_RIBBON:
    case MON_DATA_HOENN_WORLD_RIBBON:
    case MON_DATA_FATEFUL_ENCOUNTER:
    case MON_DATA_GENDER:
    case MON_DATA_FORM:
    case MON_DATA_RESERVED_113:
    case MON_DATA_RESERVED_114:
    case MON_DATA_UNUSED_115:
    case MON_DATA_NICKNAME:
    case MON_DATA_NICKNAME_2:
    case MON_DATA_NICKNAME_3:
    case MON_DATA_NICKNAME_4:
    case MON_DATA_UNK_120:
    case MON_DATA_GAME_VERSION:
    case MON_DATA_COOL_RIBBON:
    case MON_DATA_COOL_RIBBON_GREAT:
    case MON_DATA_COOL_RIBBON_ULTRA:
    case MON_DATA_COOL_RIBBON_MASTER:
    case MON_DATA_BEAUTY_RIBBON:
    case MON_DATA_BEAUTY_RIBBON_GREAT:
    case MON_DATA_BEAUTY_RIBBON_ULTRA:
    case MON_DATA_BEAUTY_RIBBON_MASTER:
    case MON_DATA_CUTE_RIBBON:
    case MON_DATA_CUTE_RIBBON_GREAT:
    case MON_DATA_CUTE_RIBBON_ULTRA:
    case MON_DATA_CUTE_RIBBON_MASTER:
    case MON_DATA_SMART_RIBBON:
    case MON_DATA_SMART_RIBBON_GREAT:
    case MON_DATA_SMART_RIBBON_ULTRA:
    case MON_DATA_SMART_RIBBON_MASTER:
    case MON_DATA_TOUGH_RIBBON:
    case MON_DATA_TOUGH_RIBBON_GREAT:
    case MON_DATA_TOUGH_RIBBON_ULTRA:
    case MON_DATA_TOUGH_RIBBON_MASTER:
    case MON_DATA_SINNOH_RIBBON_142:
    case MON_DATA_OT_NAME:
    case MON_DATA_OT_NAME_2:
    case MON_DATA_EGG_MET_YEAR:
    case MON_DATA_EGG_MET_MONTH:
    case MON_DATA_EGG_MET_DAY:
    case MON_DATA_MET_YEAR:
    case MON_DATA_MET_MONTH:
    case MON_DATA_MET_DAY:
    case MON_DATA_EGG_MET_LOCATION:
    case MON_DATA_MET_LOCATION:
    case MON_DATA_POKERUS:
    case MON_DATA_POKEBALL:
    case MON_DATA_MET_LEVEL:
    case MON_DATA_MET_GENDER:
    case MON_DATA_ENCOUNTER_TYPE:
    case MON_DATA_RESERVED_158:
    case MON_DATA_STATUS:
    case MON_DATA_LEVEL:
    case MON_DATA_CAPSULE:
    case MON_DATA_HP:
    case MON_DATA_MAXHP:
    case MON_DATA_ATK:
    case MON_DATA_DEF:
    case MON_DATA_SPEED:
    case MON_DATA_SPATK:
    case MON_DATA_SPDEF:
    case MON_DATA_MAIL_STRUCT:
    case MON_DATA_SEAL_COORDS:
    case MON_DATA_SPECIES_EXISTS:
    case MON_DATA_SANITY_IS_EGG:
    case MON_DATA_SPECIES2:
    case MON_DATA_IVS_WORD:
    case MON_DATA_UNK_175:
    case MON_DATA_TYPE_1:
    case MON_DATA_TYPE_2:
    case MON_DATA_SPECIES_NAME:
    default:
        GF_ASSERT(0);
    }
}

struct BaseStats *AllocAndLoadMonPersonal(int species, HeapID heapId) {
    struct BaseStats *baseStats = (struct BaseStats *)AllocFromHeap(heapId, sizeof(struct BaseStats));
    LoadMonPersonal(species, baseStats);
    return baseStats;
}

int GetPersonalAttr(struct BaseStats *baseStats, enum BaseStat attr) {
    int ret;
    GF_ASSERT(baseStats != NULL);
    switch (attr) {
    case BASE_HP:
        ret = baseStats->hp;
        break;
    case BASE_ATK:
        ret = baseStats->atk;
        break;
    case BASE_DEF:
        ret = baseStats->def;
        break;
    case BASE_SPEED:
        ret = baseStats->speed;
        break;
    case BASE_SPATK:
        ret = baseStats->spatk;
        break;
    case BASE_SPDEF:
        ret = baseStats->spdef;
        break;
    case BASE_TYPE1:
        ret = baseStats->types[0];
        break;
    case BASE_TYPE2:
        ret = baseStats->types[1];
        break;
    case BASE_CATCH_RATE:
        ret = baseStats->catchRate;
        break;
    case BASE_EXP_YIELD:
        ret = baseStats->expYield;
        break;
    case BASE_HP_YIELD:
        ret = baseStats->hp_yield;
        break;
    case BASE_ATK_YIELD:
        ret = baseStats->atk_yield;
        break;
    case BASE_DEF_YIELD:
        ret = baseStats->def_yield;
        break;
    case BASE_SPEED_YIELD:
        ret = baseStats->speed_yield;
        break;
    case BASE_SPATK_YIELD:
        ret = baseStats->spatk_yield;
        break;
    case BASE_SPDEF_YIELD:
        ret = baseStats->spdef_yield;
        break;
    case BASE_ITEM_1:
        ret = baseStats->item1;
        break;
    case BASE_ITEM_2:
        ret = baseStats->item2;
        break;
    case BASE_GENDER_RATIO:
        ret = baseStats->genderRatio;
        break;
    case BASE_EGG_CYCLES:
        ret = baseStats->eggCycles;
        break;
    case BASE_FRIENDSHIP:
        ret = baseStats->friendship;
        break;
    case BASE_GROWTH_RATE:
        ret = baseStats->growthRate;
        break;
    case BASE_EGG_GROUP_1:
        ret = baseStats->eggGroups[0];
        break;
    case GASE_EGG_GROUP_2:
        ret = baseStats->eggGroups[1];
        break;
    case BASE_ABILITY_1:
        ret = baseStats->abilities[0];
        break;
    case BASE_ABILITY_2:
        ret = baseStats->abilities[1];
        break;
    case BASE_GREAT_MARSH_RATE:
        ret = baseStats->greatMarshRate;
        break;
    case BASE_COLOR:
        ret = baseStats->color;
        break;
    case BASE_FLIP:
        ret = baseStats->flip;
        break;
    case BASE_TMHM_1:
        ret = (int)baseStats->unk1C;
        break;
    case BASE_TMHM_2:
        ret = (int)baseStats->unk20;
        break;
    case BASE_TMHM_3:
        ret = (int)baseStats->unk24;
        break;
    case BASE_TMHM_4:
        ret = (int)baseStats->unk28;
        break;
    }
    return ret;
}

void FreeMonPersonal(struct BaseStats *personal) {
    GF_ASSERT(personal != NULL);
    FreeToHeap(personal);
}

int GetMonBaseStat_HandleFormConversion(int species, int form, enum BaseStat attr) {
    int ret;
    struct BaseStats *personal = AllocAndLoadMonPersonal(ResolveMonForm(species, form), HEAP_ID_DEFAULT);
    ret = GetPersonalAttr(personal, attr);
    FreeMonPersonal(personal);
    return ret;
}

int GetMonBaseStat(int species, enum BaseStat attr) {
    int ret;
    struct BaseStats *personal = AllocAndLoadMonPersonal(species, HEAP_ID_DEFAULT);
    ret = GetPersonalAttr(personal, attr);
    FreeMonPersonal(personal);
    return ret;
}

u8 GetPercentProgressTowardsNextLevel(struct Pokemon *pokemon) {
    BOOL decry = AcquireMonLock(pokemon);
    u16 species = (u16)GetMonData(pokemon, MON_DATA_SPECIES, NULL);
    u8 level = (u8)GetMonData(pokemon, MON_DATA_LEVEL, NULL);
    u32 lo = GetMonExpBySpeciesAndLevel(species, level);
    u32 hi = GetMonExpBySpeciesAndLevel(species, level + 1);
    u32 cur = GetMonData(pokemon, MON_DATA_EXPERIENCE, NULL);
    ReleaseMonLock(pokemon, decry);
    return (u8)(100 * (cur - lo) / (hi - lo));
}

u32 CalcMonExpToNextLevel(struct Pokemon *pokemon) {
    return CalcBoxMonExpToNextLevel(&pokemon->box);
}

u32 CalcBoxMonExpToNextLevel(struct BoxPokemon *boxmon) {
    u16 species = (u16)GetBoxMonData(boxmon, MON_DATA_SPECIES, NULL);
    u16 level = (u16)(CalcBoxMonLevel(boxmon) + 1);
    u32 cur = GetBoxMonData(boxmon, MON_DATA_EXPERIENCE, NULL);
    u32 hi = GetMonExpBySpeciesAndLevel(species, level);
    return hi - cur;
}

u32 GetMonBaseExperienceAtCurrentLevel(struct Pokemon *pokemon) {
    int species = (int)GetMonData(pokemon, MON_DATA_SPECIES, NULL);
    int level = (int)GetMonData(pokemon, MON_DATA_LEVEL, NULL);
    return GetMonExpBySpeciesAndLevel(species, level);
}

u32 GetMonExpBySpeciesAndLevel(int species, int level) {
    return GetExpByGrowthRateAndLevel(GetMonBaseStat(species, BASE_GROWTH_RATE), level);
}

void LoadGrowthTable(int growthRate, u32 *dest) {
    GF_ASSERT(growthRate < 8);
    ReadWholeNarcMemberByIdPair(dest, NARC_POKETOOL_PERSONAL_GROWTBL, growthRate);
}

u32 GetExpByGrowthRateAndLevel(int growthRate, int level) {
    u32 *table;
    u32 ret;
    GF_ASSERT(growthRate < 8);
    GF_ASSERT(level <= 101);
    table = (u32 *)AllocFromHeap(HEAP_ID_DEFAULT, 101 * sizeof(u32));
    LoadGrowthTable(growthRate, table);
    ret = table[level];
    FreeToHeap(table);
    return ret;
}

int CalcMonLevel(struct Pokemon *pokemon) {
    return CalcBoxMonLevel(&pokemon->box);
}

int CalcBoxMonLevel(struct BoxPokemon *boxmon) {
    BOOL decry = AcquireBoxMonLock(boxmon);
    int species = (int)GetBoxMonData(boxmon, MON_DATA_SPECIES, NULL);
    int exp = (int)GetBoxMonData(boxmon, MON_DATA_EXPERIENCE, NULL);
    ReleaseBoxMonLock(boxmon, decry);
    return CalcLevelBySpeciesAndExp((u16)species, (u32)exp);
}

int CalcLevelBySpeciesAndExp(u16 species, u32 exp) {
    int level;
    struct BaseStats *personal = AllocAndLoadMonPersonal(species, HEAP_ID_DEFAULT);
    level = CalcLevelBySpeciesAndExp_PreloadedPersonal(personal, species, exp);
    FreeMonPersonal(personal);
    return level;
}

int CalcLevelBySpeciesAndExp_PreloadedPersonal(struct BaseStats *personal, u16 species, u32 exp) {
#pragma unused(species)
    static u32 table[101];
    int i;
    LoadGrowthTable(GetPersonalAttr(personal, BASE_GROWTH_RATE), table);
    for (i = 1; i < 101; i++) {
        if (table[i] > exp) {
            break;
        }
    }
    return i - 1;
}

u8 GetMonNature(struct Pokemon *pokemon) {
    return GetBoxMonNature(&pokemon->box);
}

u8 GetBoxMonNature(struct BoxPokemon *boxmon) {
    BOOL decry = AcquireBoxMonLock(boxmon);
    u32 personality = GetBoxMonData(boxmon, MON_DATA_PERSONALITY, NULL);
    ReleaseBoxMonLock(boxmon, decry);
    return GetNatureFromPersonality(personality);
}

u8 GetNatureFromPersonality(u32 pid) {
    return (u8)(pid % 25);
}

u16 ModifyStatByNature(u8 nature, u16 n, u8 statIndex) {
    u16 retVal;

    // Dont modify HP, Accuracy, or Evasion by nature
    if (statIndex < 1 || statIndex > 5) {
        return n;
    }

    switch (sNatureStatMods[nature][statIndex - 1]) {
    case 1:
        retVal = (u16)((u16)(n * 110) / 100); // NOTE: will overflow for n > 595 because the intermediate value is cast to u16 before the division. Fix by removing (u16) cast
        break;
    case -1:
        retVal = (u16)((u16)(n * 90) / 100); // NOTE: will overflow for n > 728, see above
        break;
    default:
        retVal = n;
        break;
    }
    return retVal;
}

void MonApplyFriendshipMod(struct Pokemon *pokemon, u32 kind, u32 location) {
    u16 species;
    u8 effect;
    u8 r4;
    s16 friendship;
    s8 mod;

    if (kind == 5 && (LCRandom() & 1)) {
        return;
    }

    species = (u16)GetMonData(pokemon, MON_DATA_SPECIES2, NULL);
    if (species == SPECIES_NONE || species == SPECIES_EGG) {
        return;
    }

    effect = (u8)GetItemAttr((u16)GetMonData(pokemon, MON_DATA_HELD_ITEM, NULL), 1, HEAP_ID_DEFAULT);
    r4 = 0;
    friendship = (s16)GetMonData(pokemon, MON_DATA_FRIENDSHIP, NULL);
    if (friendship >= 100) {
        r4++;
    }
    if (friendship >= 200) {
        r4++;
    }
    mod = sFriendshipModTable[kind][r4];
    if (mod > 0 && GetMonData(pokemon, MON_DATA_POKEBALL, NULL) == ITEM_LUXURY_BALL) {
        mod++;
    }
    if (mod > 0 && GetMonData(pokemon, MON_DATA_EGG_MET_LOCATION, NULL) == location) {
        mod++;
    }
    if (mod > 0 && effect == 52) { // Soothe Bell effect?
        mod = (s8)(mod * 150 / 100);
    }
    friendship += mod;
    if (friendship < 0) {
        friendship = 0;
    }
    if (friendship > 255) {
        friendship = 255;
    }
    SetMonData(pokemon, MON_DATA_FRIENDSHIP, &friendship);
}

u8 GetMonGender(struct Pokemon *pokemon) {
    return GetBoxMonGender(&pokemon->box);
}

u8 GetBoxMonGender(struct BoxPokemon *boxmon) {
    BOOL decry = AcquireBoxMonLock(boxmon);
    u16 species = (u16)GetBoxMonData(boxmon, MON_DATA_SPECIES, NULL);
    u32 personality = GetBoxMonData(boxmon, MON_DATA_PERSONALITY, NULL);
    ReleaseBoxMonLock(boxmon, decry);
    return GetGenderBySpeciesAndPersonality(species, personality);
}

u8 GetGenderBySpeciesAndPersonality(u16 species, u32 pid) {
    struct BaseStats *personal = AllocAndLoadMonPersonal(species, HEAP_ID_DEFAULT);
    u8 gender = GetGenderBySpeciesAndPersonality_PreloadedPersonal(personal, species, pid);
    FreeMonPersonal(personal);
    return gender;
}

u8 GetGenderBySpeciesAndPersonality_PreloadedPersonal(struct BaseStats *personal, u16 species, u32 pid) {
#pragma unused(species)
    u8 ratio = (u8)GetPersonalAttr(personal, BASE_GENDER_RATIO);
    switch (ratio) {
    case MON_RATIO_MALE:
        return MON_MALE;
    case MON_RATIO_FEMALE:
        return MON_FEMALE;
    case MON_RATIO_UNKNOWN:
        return MON_GENDERLESS;
    default:
        if (ratio > (u8)pid) {
            return MON_FEMALE;
        } else {
            return MON_MALE;
        }
    }
}

u8 MonIsShiny(struct Pokemon *pokemon) {
    return BoxMonIsShiny(&pokemon->box);
}

u8 BoxMonIsShiny(struct BoxPokemon *boxmon) {
    u32 otid = GetBoxMonData(boxmon, MON_DATA_OTID, NULL);
    u32 pid = GetBoxMonData(boxmon, MON_DATA_PERSONALITY, NULL);
    return CalcShininessByOtIdAndPersonality(otid, pid);
}

u8 CalcShininessByOtIdAndPersonality(u32 otid, u32 pid) {
    return SHINY_CHECK(otid, pid);
}

u32 GenerateShinyPersonality(u32 otid) {
    int r4;
    u16 r6;
    u16 r5;
    otid = (u32)((((otid & 0xFFFF0000) >> 16) ^ (otid & 0xFFFF)) >> 3u);
    r6 = (u16)(LCRandom() & 7);
    r5 = (u16)(LCRandom() & 7);
    for (r4 = 0; r4 < 13; r4++) {
        if (MaskOfFlagNo(r4) & otid) {
            if (LCRandom() & 1) {
                r6 |= MaskOfFlagNo(r4 + 3);
            } else {
                r5 |= MaskOfFlagNo(r4 + 3);
            }
        } else if (LCRandom() & 1) {
            r6 |= MaskOfFlagNo(r4 + 3);
            r5 |= MaskOfFlagNo(r4 + 3);
        }
    }
    return (u32)((r5 << 16) | r6);
}

void sub_02068B68(struct SomeDrawPokemonStruct *spC, struct Pokemon *pokemon, u8 sp10) {
    sub_02068B70(spC, &pokemon->box, sp10);
}

void sub_02068B70(struct SomeDrawPokemonStruct *spC, struct BoxPokemon *boxmon, u8 sp10) {
    BOOL decry = AcquireBoxMonLock(boxmon);
    u16 species = (u16)GetBoxMonData(boxmon, MON_DATA_SPECIES2, NULL);
    u8 gender = GetBoxMonGender(boxmon);
    u8 shiny = BoxMonIsShiny(boxmon);
    u32 personality = GetBoxMonData(boxmon, MON_DATA_PERSONALITY, NULL);
    u8 form;
    if (species == SPECIES_EGG) {
        if (GetBoxMonData(boxmon, MON_DATA_SPECIES, NULL) == SPECIES_MANAPHY) {
            form = 1;
        } else {
            form = 0;
        }
    } else {
        form = (u8)GetBoxMonData(boxmon, MON_DATA_FORM, NULL);
    }
    sub_02068C00(spC, species, gender, sp10, shiny, form, personality);
    ReleaseBoxMonLock(boxmon, decry);
}

void sub_02068C00(struct SomeDrawPokemonStruct *spC, int species, u8 gender, u8 sp10, u8 shiny, u8 form, u32 personality) {
    spC->unk6 = 0;
    spC->unk8 = 0;
    spC->unkC = 0;
    switch (species) {
    case SPECIES_BURMY:
        if (form > 2) {
            form = 0;
        }
        spC->unk0 = NARC_POKETOOL_POKEGRA_OTHERPOKE;
        spC->unk2 = (u16)(sp10 / 2 + 0x48 + form * 2);
        spC->unk4 = (u16)(shiny + 0x92 + form * 2);
        break;
    case SPECIES_WORMADAM:
        if (form > 2) {
            form = 0;
        }
        spC->unk0 = NARC_POKETOOL_POKEGRA_OTHERPOKE;
        spC->unk2 = (u16)(sp10 / 2 + 0x4E + form * 2);
        spC->unk4 = (u16)(shiny + 0x98 + form * 2);
        break;
    case SPECIES_SHELLOS:
        if (form > 1) {
            form = 0;
        }
        spC->unk0 = NARC_POKETOOL_POKEGRA_OTHERPOKE;
        spC->unk2 = (u16)(sp10 + 0x54 + form);
        spC->unk4 = (u16)(shiny + 0x9E + form * 2);
        break;
    case SPECIES_GASTRODON:
        if (form > 1) {
            form = 0;
        }
        spC->unk0 = NARC_POKETOOL_POKEGRA_OTHERPOKE;
        spC->unk2 = (u16)(sp10 + 0x58 + form);
        spC->unk4 = (u16)(shiny + 0xA2 + form * 2);
        break;
    case SPECIES_CHERRIM:
        if (form > 1) {
            form = 0;
        }
        spC->unk0 = NARC_POKETOOL_POKEGRA_OTHERPOKE;
        spC->unk2 = (u16)(sp10 + 0x5C + form);
        spC->unk4 = (u16)(shiny * 2 + 0xA6 + form);
        break;
    case SPECIES_ARCEUS:
        if (form > 17) {
            form = 0;
        }
        spC->unk0 = NARC_POKETOOL_POKEGRA_OTHERPOKE;
        spC->unk2 = (u16)(sp10 / 2 + 0x60 + form * 2);
        spC->unk4 = (u16)(shiny + 0xAA + form * 2);
        break;
    case SPECIES_CASTFORM:
        if (form > 3) {
            form = 0;
        }
        spC->unk0 = NARC_POKETOOL_POKEGRA_OTHERPOKE;
        spC->unk2 = (u16)(sp10 * 2 + 0x40 + form);
        spC->unk4 = (u16)(shiny * 4 + 0x8A + form);
        break;
    case SPECIES_DEOXYS:
        if (form > 3) {
            form = 0;
        }
        spC->unk0 = NARC_POKETOOL_POKEGRA_OTHERPOKE;
        spC->unk2 = (u16)(sp10 / 2 + form * 2);
        spC->unk4 = (u16)(shiny + 0x86);
        break;
    case SPECIES_UNOWN:
        if (form >= 28) {
            form = 0;
        }
        spC->unk0 = NARC_POKETOOL_POKEGRA_OTHERPOKE;
        spC->unk2 = (u16)(sp10 / 2 + 0x8 + form * 2);
        spC->unk4 = (u16)(shiny + 0x88);
        break;
    case SPECIES_EGG:
        if (form > 1) {
            form = 0;
        }
        spC->unk0 = NARC_POKETOOL_POKEGRA_OTHERPOKE;
        spC->unk2 = (u16)(0x84 + form);
        spC->unk4 = (u16)(0xCE + form);
        break;
    case SPECIES_MANAPHY_EGG:
        spC->unk0 = NARC_POKETOOL_POKEGRA_OTHERPOKE;
        spC->unk2 = 0x84;
        spC->unk4 = 0xCE;
        break;
    default:
        spC->unk0 = 0x4;
        spC->unk2 = (u16)(species * 6 + sp10 + (gender == MON_FEMALE ? 0 : 1));
        spC->unk4 = (u16)(shiny + (species * 6 + 4));
        if (species == SPECIES_SPINDA && sp10 == 2) {
            spC->unk6 = SPECIES_SPINDA;
            spC->unk8 = 0;
            spC->unkC = personality;
        }
        break;
    }
}

u8 sub_02068E14(struct Pokemon *pokemon, u32 a1) {
    return sub_02068E1C(&pokemon->box, a1);
}

u8 sub_02068E1C(struct BoxPokemon *boxmon, u32 a1) {
    u16 species = (u16)GetBoxMonData(boxmon, MON_DATA_SPECIES2, NULL);
    u8 gender = GetBoxMonGender(boxmon);
    u32 pid = GetBoxMonData(boxmon, MON_DATA_PERSONALITY, NULL);
    u8 form;
    if (species == SPECIES_EGG) {
        if (GetBoxMonData(boxmon, MON_DATA_SPECIES, NULL) == SPECIES_MANAPHY) {
            form = 1;
        } else {
            form = 0;
        }
    } else {
        form = (u8)GetBoxMonData(boxmon, MON_DATA_FORM, NULL);
    }
    return sub_02068E88(species, gender, a1, form, pid);
}

u8 sub_02068E88(int species, u8 gender, u32 a1, u8 form, u32 pid) {
#pragma unused(pid)
    u8 ret;
    s32 fileId;
    enum NarcId narc;
    switch (species) {
    case SPECIES_BURMY:
        if (form > 2) {
            form = 0;
        }
        narc = NARC_POKETOOL_POKEGRA_HEIGHT_O;
        fileId = (s32)(a1 / 2 + 0x48 + form * 2);
        break;
    case SPECIES_WORMADAM:
        if (form > 2) {
            form = 0;
        }
        narc = NARC_POKETOOL_POKEGRA_HEIGHT_O;
        fileId = (s32)(a1 / 2 + 0x4E + form * 2);
        break;
    case SPECIES_SHELLOS:
        if (form > 1) {
            form = 0;
        }
        narc = NARC_POKETOOL_POKEGRA_HEIGHT_O;
        fileId = (s32)(a1 + 0x54 + form);
        break;
    case SPECIES_GASTRODON:
        if (form > 1) {
            form = 0;
        }
        narc = NARC_POKETOOL_POKEGRA_HEIGHT_O;
        fileId = (s32)(a1 + 0x58 + form);
        break;
    case SPECIES_CHERRIM:
        if (form > 1) {
            form = 0;
        }
        narc = NARC_POKETOOL_POKEGRA_HEIGHT_O;
        fileId = (s32)(a1 + 0x5C + form);
        break;
    case SPECIES_ARCEUS:
        if (form > 17) {
            form = 0;
        }
        narc = NARC_POKETOOL_POKEGRA_HEIGHT_O;
        fileId = (s32)(a1 / 2 + 0x60 + 2 * form);
        break;
    case SPECIES_CASTFORM:
        if (form > 3) {
            form = 0;
        }
        narc = NARC_POKETOOL_POKEGRA_HEIGHT_O;
        fileId = (s32)(a1 * 2 + 0x40 + form);
        break;
    case SPECIES_DEOXYS:
        if (form > 3) {
            form = 0;
        }
        narc = NARC_POKETOOL_POKEGRA_HEIGHT_O;
        fileId = (s32)(a1 / 2 + form * 2);
        break;
    case SPECIES_UNOWN:
        if (form >= 28) {
            form = 0;
        }
        narc = NARC_POKETOOL_POKEGRA_HEIGHT_O;
        fileId = (s32)(a1 / 2 + 0x8 + form * 2);
        break;
    case SPECIES_EGG:
        if (form > 1) {
            form = 0;
        }
        narc = NARC_POKETOOL_POKEGRA_HEIGHT_O;
        fileId = (s32)(0x84 + form);
        break;
    case SPECIES_MANAPHY_EGG:
        narc = NARC_POKETOOL_POKEGRA_HEIGHT_O;
        fileId = 0x84;
        break;
    default:
        narc = NARC_POKETOOL_POKEGRA_HEIGHT;
        fileId = (s32)(4 * species + a1 + (gender != MON_FEMALE ? 1 : 0));
        break;
    }
    ReadWholeNarcMemberByIdPair(&ret, narc, fileId);
    return ret;
}

void sub_02068FE0(struct SomeDrawPokemonStruct *a0, u16 a1, int a2) {
    if (a2 == 2) {
        a0->unk0 = NARC_POKETOOL_TRGRA_TRFGRA;
    } else {
        a0->unk0 = NARC_POKETOOL_TRGRA_TRBGRA;
        a1 = (u16)sub_0206AA30(a1);
    }
    a0->unk2 = (u16)(a1 * 2);
    a0->unk4 = (u16)(a1 * 2 + 1);
    a0->unk6 = 0;
    a0->unk8 = 0;
    a0->unkC = 0;
}

void sub_02069010(void *dest, int a1) {
    struct Pokeanm buffer;
    ReadFromNarcMemberByIdPair(&buffer, NARC_POKETOOL_POKEANM_POKEANM, 0, (u32)(28 * a1), 28);
    MI_CpuCopy8(buffer.unk8, dest, 20);
}

void sub_02069038(u32 a0, u32 a1, u32 a2, s32 a3, u32 a4, u32 a5, u32 a6) {
    struct UnkStruct_02069038 sp4;
    ReadFromNarcMemberByIdPair(&sp4.anim, NARC_POKETOOL_POKEANM_POKEANM, 0, (u32)(a2 * 28), 28);
    if (a3 == 2) {
        sp4.unk0 = sp4.anim.unk0[0].unk0;
        sp4.unk2 = sp4.anim.unk0[0].unk1;
        sp4.unk4 = (u8)a5;
    } else {
        a3 = sub_02014C3C((u8)a4);
        GF_ASSERT(a3 < 3);
        sp4.unk0 = sp4.anim.unk0[a3 + 1].unk0;
        sp4.unk2 = sp4.anim.unk0[a3 + 1].unk1;
        sp4.unk4 = (u8)a5;
    }
    sub_02014C54((int)a0, (int)a1, &sp4, (u8)a6);
}

void sub_020690AC(struct SomeDrawPokemonStruct *a0, u32 a1) {
    a0->unk0 = NARC_POKETOOL_TRGRA_TRFGRA;
    a0->unk2 = (u16)(a1 * 2);
    a0->unk4 = (u16)(a1 * 2 + 1);
    a0->unk6 = 0;
    a0->unk8 = 0;
    a0->unkC = 0;
}

u32 sub_020690C4(void) {
    return sizeof(struct Pokemon);
}

u32 sub_020690C8(void) {
    return sizeof(struct BoxPokemon);
}

u8 GetMonUnownLetter(struct Pokemon *pokemon) // not just used for unown
{
    return GetBoxMonUnownLetter(&pokemon->box);
}

u8 GetBoxMonUnownLetter(struct BoxPokemon *boxmon) {
    return (u8)GetBoxMonData(boxmon, MON_DATA_FORM, NULL);
}

struct BoxPokemon *sub_020690E4(struct Pokemon *pokemon) {
    return &pokemon->box;
}

BOOL sub_020690E8(struct Pokemon *pokemon) {
    u16 species = (u16)GetMonData(pokemon, MON_DATA_SPECIES, NULL);
    u8 level = (u8)(GetMonData(pokemon, MON_DATA_LEVEL, NULL) + 1);
    u32 exp = GetMonData(pokemon, MON_DATA_EXPERIENCE, NULL);
    u32 growthrate = (u32)GetMonBaseStat(species, BASE_GROWTH_RATE);
    u32 maxexp = GetExpByGrowthRateAndLevel((int)growthrate, 100);
    if (exp > maxexp) {
        exp = maxexp;
        SetMonData(pokemon, MON_DATA_EXPERIENCE, &exp);
    }
    if (level > 100) {
        return FALSE;
    }
    if (exp >= GetExpByGrowthRateAndLevel((int)growthrate, level)) {
        SetMonData(pokemon, MON_DATA_LEVEL, &level);
        return TRUE;
    }
    return FALSE;
}

u16 GetMonEvolution(struct Party *party, struct Pokemon *pokemon, u32 context, u32 usedItem, u32 *method_ret) {
    u16 target = SPECIES_NONE;
    u32 sp40;
    u16 species;
    u16 heldItem;
    u32 personality;
    int i;
    u8 beauty;
    u8 level;
    u16 friendship;
    u16 pid_hi;
    struct Evolution *evoTable;
    u8 r1;

    species = (u16)GetMonData(pokemon, MON_DATA_SPECIES, NULL);
    heldItem = (u16)GetMonData(pokemon, MON_DATA_HELD_ITEM, NULL);
    personality = GetMonData(pokemon, MON_DATA_PERSONALITY, NULL);
    beauty = (u8)GetMonData(pokemon, MON_DATA_BEAUTY, NULL);
    pid_hi = (u16)((personality & 0xFFFF0000) >> 16);
    r1 = (u8)GetItemAttr(heldItem, 1, HEAP_ID_DEFAULT);
    if (species != SPECIES_KADABRA && r1 == HOLD_EFFECT_NO_EVOLVE && context != 3) {
        return SPECIES_NONE;
    }
    if (method_ret == NULL) {
        method_ret = &sp40;
    }
    evoTable = AllocFromHeap(HEAP_ID_DEFAULT, 7 * sizeof(struct Evolution));
    LoadMonEvolutionTable(species, evoTable);
    switch (context) {
    case 0:
        level = (u8)GetMonData(pokemon, MON_DATA_LEVEL, NULL);
        friendship = (u16)GetMonData(pokemon, MON_DATA_FRIENDSHIP, NULL);
        for (i = 0; i < 7; i++) {
            switch (evoTable[i].method) {
            case EVO_NONE:
                break;
            case EVO_FRIENDSHIP:
                if (friendship >= 220) {
                    target = evoTable[i].target;
                    *method_ret = EVO_FRIENDSHIP;
                }
                break;
            case EVO_FRIENDSHIP_DAY:
                if (IsNighttime() == 0 && friendship >= 220) {
                    target = evoTable[i].target;
                    *method_ret = EVO_FRIENDSHIP_DAY;
                }
                break;
            case EVO_FRIENDSHIP_NIGHT:
                if (IsNighttime() == 1 && friendship >= 220) {
                    target = evoTable[i].target;
                    *method_ret = EVO_FRIENDSHIP_NIGHT;
                }
                break;
            case EVO_LEVEL:
                if (evoTable[i].param <= level) {
                    target = evoTable[i].target;
                    *method_ret = EVO_LEVEL;
                }
                break;
            case EVO_TRADE:
                break;
            case EVO_TRADE_ITEM:
                break;
            case EVO_STONE:
                break;
            case EVO_LEVEL_ATK_GT_DEF:
                if (evoTable[i].param <= level && GetMonData(pokemon, MON_DATA_ATK, NULL) > GetMonData(pokemon, MON_DATA_DEF, NULL)) {
                    target = evoTable[i].target;
                    *method_ret = EVO_LEVEL_ATK_GT_DEF;
                }
                break;
            case EVO_LEVEL_ATK_EQ_DEF:
                if (evoTable[i].param <= level && GetMonData(pokemon, MON_DATA_ATK, NULL) == GetMonData(pokemon, MON_DATA_DEF, NULL)) {
                    target = evoTable[i].target;
                    *method_ret = EVO_LEVEL_ATK_EQ_DEF;
                }
                break;
            case EVO_LEVEL_ATK_LT_DEF:
                if (evoTable[i].param <= level && GetMonData(pokemon, MON_DATA_ATK, NULL) < GetMonData(pokemon, MON_DATA_DEF, NULL)) {
                    target = evoTable[i].target;
                    *method_ret = EVO_LEVEL_ATK_LT_DEF;
                }
                break;
            case EVO_LEVEL_PID_LO:
                if (evoTable[i].param <= level && pid_hi % 10 < 5) {
                    target = evoTable[i].target;
                    *method_ret = EVO_LEVEL_PID_LO;
                }
                break;
            case EVO_LEVEL_PID_HI:
                if (evoTable[i].param <= level && pid_hi % 10 >= 5) {
                    target = evoTable[i].target;
                    *method_ret = EVO_LEVEL_PID_HI;
                }
                break;
            case EVO_LEVEL_NINJASK:
                if (evoTable[i].param <= level) {
                    target = evoTable[i].target;
                    *method_ret = EVO_LEVEL_NINJASK;
                }
                break;
            case EVO_LEVEL_SHEDINJA:
                *method_ret = EVO_LEVEL_SHEDINJA;
                break;
            case EVO_BEAUTY:
                if (evoTable[i].param <= beauty) {
                    target = evoTable[i].target;
                    *method_ret = EVO_BEAUTY;
                }
                break;
            case EVO_STONE_MALE:
                break;
            case EVO_STONE_FEMALE:
                break;
            case EVO_ITEM_DAY:
                if (IsNighttime() == 0 && evoTable[i].param == heldItem) {
                    target = evoTable[i].target;
                    *method_ret = EVO_ITEM_DAY;
                }
                break;
            case EVO_ITEM_NIGHT:
                if (IsNighttime() == 1 && evoTable[i].param == heldItem) {
                    target = evoTable[i].target;
                    *method_ret = EVO_ITEM_NIGHT;
                }
                break;
            case EVO_HAS_MOVE:
                if (MonHasMove(pokemon, evoTable[i].param) == TRUE) {
                    target = evoTable[i].target;
                    *method_ret = EVO_HAS_MOVE;
                }
                break;
            case EVO_OTHER_PARTY_MON:
                if (party != NULL && Party_HasMon(party, evoTable[i].param) == 1) {
                    target = evoTable[i].target;
                    *method_ret = EVO_OTHER_PARTY_MON;
                }
                break;
            case EVO_LEVEL_MALE:
                if (GetMonData(pokemon, MON_DATA_GENDER, NULL) == MON_MALE && evoTable[i].param <= level) {
                    target = evoTable[i].target;
                    *method_ret = EVO_LEVEL_MALE;
                }
                break;
            case EVO_LEVEL_FEMALE:
                if (GetMonData(pokemon, MON_DATA_GENDER, NULL) == MON_FEMALE && evoTable[i].param <= level) {
                    target = evoTable[i].target;
                    *method_ret = EVO_LEVEL_FEMALE;
                }
                break;
            case EVO_CORONET:
                if (usedItem == evoTable[i].method) {
                    target = evoTable[i].target;
                    *method_ret = EVO_CORONET;
                }
                break;
            case EVO_ETERNA:
                if (usedItem == evoTable[i].method) {
                    target = evoTable[i].target;
                    *method_ret = EVO_ETERNA;
                }
                break;
            case EVO_ROUTE217:
                if (usedItem == evoTable[i].method) {
                    target = evoTable[i].target;
                    *method_ret = EVO_ROUTE217;
                }
                break;
            }
            if (target != SPECIES_NONE) {
                break;
            }
        }
        break;
    case 1:
        for (i = 0; i < 7; i++) {
            switch (evoTable[i].method) {
            case EVO_TRADE:
                target = evoTable[i].target;
                *method_ret = EVO_TRADE;
                break;
            case EVO_TRADE_ITEM:
                if (heldItem == evoTable[i].param) {
                    target = evoTable[i].target;
                    *method_ret = EVO_TRADE_ITEM;
                }
                break;
            }
            if (target != SPECIES_NONE) {
                break;
            }
        }
        break;
    case 2:
    case 3:
        for (i = 0; i < 7; i++) {
            if (evoTable[i].method == EVO_STONE && usedItem == evoTable[i].param) {
                target = evoTable[i].target;
                *method_ret = 0;
                break;
            }
            if (evoTable[i].method == EVO_STONE_MALE && GetMonData(pokemon, MON_DATA_GENDER, NULL) == MON_MALE && usedItem == evoTable[i].param) {
                target = evoTable[i].target;
                *method_ret = 0;
                break;
            }
            if (evoTable[i].method == EVO_STONE_FEMALE && GetMonData(pokemon, MON_DATA_GENDER, NULL) == MON_FEMALE && usedItem == evoTable[i].param) {
                target = evoTable[i].target;
                *method_ret = 0;
                break;
            }
        }
        break;
    }
    FreeToHeap(evoTable);
    return target;
}

u16 ReadFromPersonalPmsNarc(u16 species) {
    u16 ret = SPECIES_NONE;
    GF_ASSERT(species < SPECIES_EGG);
    {
        FSFile file;
        FS_InitFile(&file);
        FS_OpenFile(&file, "poketool/personal/pms.narc");
        FS_SeekFile(&file, (s32)(species * sizeof(u16)), FS_SEEK_SET);
        FS_ReadFile(&file, &ret, sizeof(u16));
        FS_CloseFile(&file);
    }
    return ret;
}

u16 GetEggSpecies(u16 species) {
    switch (species) {
    case SPECIES_SUDOWOODO:
    case SPECIES_MARILL:
    case SPECIES_MR_MIME:
    case SPECIES_CHANSEY:
    case SPECIES_SNORLAX:
    case SPECIES_MANTINE:
    case SPECIES_WOBBUFFET:
    case SPECIES_ROSELIA:
    case SPECIES_CHIMECHO:
        return species;
    default:
        return ReadFromPersonalPmsNarc(species);
    }
}

void InitBoxMonMoveset(struct BoxPokemon *boxmon) {
    BOOL decry;
    u16 *levelUpLearnset;
    int i;
    u16 species;
    u32 form;
    u8 level;
    u16 move;
    levelUpLearnset = AllocFromHeap(HEAP_ID_DEFAULT, MAX_LEARNED_MOVES * sizeof(u16));
    decry = AcquireBoxMonLock(boxmon);
    species = (u16)GetBoxMonData(boxmon, MON_DATA_SPECIES, NULL);
    form = GetBoxMonData(boxmon, MON_DATA_FORM, NULL);
    level = (u8)CalcBoxMonLevel(boxmon);
    LoadLevelUpLearnset_HandleAlternateForm(species, (int)form, levelUpLearnset);
    for (i = 0; levelUpLearnset[i] != LEVEL_UP_LEARNSET_END; i++) {
        if ((levelUpLearnset[i] & LEVEL_UP_LEARNSET_LEVEL_MASK) > (level << LEVEL_UP_LEARNSET_LEVEL_SHIFT)) {
            break;
        }
        move = LEVEL_UP_LEARNSET_MOVE(levelUpLearnset[i]);
        if (sub_020696A8(boxmon, move) == 0xFFFF) {
            sub_02069718(boxmon, move);
        }
    }
    FreeToHeap(levelUpLearnset);
    ReleaseBoxMonLock(boxmon, decry);
}

u32 sub_02069698(struct Pokemon *pokemon, u16 move) {
    return sub_020696A8(sub_020690E4(pokemon), move);
}

u32 sub_020696A8(struct BoxPokemon *boxmon, u16 move) {
    u32 ret = 0xFFFF;
    int i;
    BOOL decry = AcquireBoxMonLock(boxmon);
    u16 cur_move;
    for (i = 0; i < 4; i++) {
        cur_move = (u16)GetBoxMonData(boxmon, MON_DATA_MOVE1 + i, NULL);
        if (cur_move == MOVE_NONE) {
            BoxMonSetMoveInSlot(boxmon, move, (u8)i);
            ret = move;
            break;
        }
        if (cur_move == move) {
            ret = 0xFFFE;
            break;
        }
    }
    ReleaseBoxMonLock(boxmon, decry);
    return ret;
}

void sub_02069708(struct Pokemon *pokemon, u16 move) {
    sub_02069718(sub_020690E4(pokemon), move);
}

void sub_02069718(struct BoxPokemon *boxmon, u16 move) {
    BOOL decry = AcquireBoxMonLock(boxmon);
    int i;
    u16 moves[4];
    u8 pp[4];
    u8 ppUp[4];

    for (i = 0; i < 3; i++) {
        moves[i] = (u16)GetBoxMonData(boxmon, MON_DATA_MOVE1 + i + 1, NULL);
        pp[i] = (u8)GetBoxMonData(boxmon, MON_DATA_MOVE1PP + i + 1, NULL);
        ppUp[i] = (u8)GetBoxMonData(boxmon, MON_DATA_MOVE1PPUP + i + 1, NULL);
    }

    moves[3] = move;
    pp[3] = (u8)GetWazaAttr(move, MOVEATTR_PP);
    ppUp[3] = 0;

    for (i = 0; i < 4; i++) {
        SetBoxMonData(boxmon, MON_DATA_MOVE1 + i, &moves[i]);
        SetBoxMonData(boxmon, MON_DATA_MOVE1PP + i, &pp[i]);
        SetBoxMonData(boxmon, MON_DATA_MOVE1PPUP + i, &ppUp[i]);
    }

    ReleaseBoxMonLock(boxmon, decry);
}

void MonSetMoveInSlot(struct Pokemon *pokemon, u16 move, u8 slot) {
    BoxMonSetMoveInSlot(&pokemon->box, move, slot);
}

void BoxMonSetMoveInSlot(struct BoxPokemon *boxmon, u16 move, u8 slot) {
    u8 ppUp;
    u8 pp;

    SetBoxMonData(boxmon, MON_DATA_MOVE1 + slot, &move);
    ppUp = (u8)GetBoxMonData(boxmon, MON_DATA_MOVE1PPUP + slot, NULL);
    pp = (u8)WazaGetMaxPp(move, ppUp);
    SetBoxMonData(boxmon, MON_DATA_MOVE1PP + slot, &pp);
}

u32 sub_02069818(struct Pokemon *pokemon, u32 *r5, u16 *sp0) {
    u32 ret = 0;
    u16 *levelUpLearnset = AllocFromHeap(HEAP_ID_DEFAULT, MAX_LEARNED_MOVES * sizeof(u16));
    u16 species = (u16)GetMonData(pokemon, MON_DATA_SPECIES, NULL);
    u32 form = GetMonData(pokemon, MON_DATA_FORM, NULL);
    u8 level = (u8)GetMonData(pokemon, MON_DATA_LEVEL, NULL);
    LoadLevelUpLearnset_HandleAlternateForm(species, (int)form, levelUpLearnset);

    if (levelUpLearnset[*r5] == 0xFFFF) {
        FreeToHeap(levelUpLearnset);
        return 0;
    }
    while ((levelUpLearnset[*r5] & LEVEL_UP_LEARNSET_LEVEL_MASK) != (level << LEVEL_UP_LEARNSET_LEVEL_SHIFT)) {
        (*r5)++;
        if (levelUpLearnset[*r5] == 0xFFFF) {
            FreeToHeap(levelUpLearnset);
            return 0;
        }
    }
    if ((levelUpLearnset[*r5] & LEVEL_UP_LEARNSET_LEVEL_MASK) == (level << LEVEL_UP_LEARNSET_LEVEL_SHIFT)) {
        *sp0 = LEVEL_UP_LEARNSET_MOVE(levelUpLearnset[*r5]);
        (*r5)++;
        ret = sub_02069698(pokemon, *sp0);
    }
    FreeToHeap(levelUpLearnset);
    return ret;
}

void sub_020698E0(struct Pokemon *pokemon, int slot1, int slot2) {
    sub_020698E8(&pokemon->box, slot1, slot2);
}

void sub_020698E8(struct BoxPokemon *boxmon, int slot1, int slot2) {
    u16 moves[2];
    u8 pp[2];
    u8 ppUp[2];

    moves[0] = (u16)GetBoxMonData(boxmon, MON_DATA_MOVE1 + slot1, NULL);
    pp[0] = (u8)GetBoxMonData(boxmon, MON_DATA_MOVE1PP + slot1, NULL);
    ppUp[0] = (u8)GetBoxMonData(boxmon, MON_DATA_MOVE1PPUP + slot1, NULL);
    moves[1] = (u16)GetBoxMonData(boxmon, MON_DATA_MOVE1 + slot2, NULL);
    pp[1] = (u8)GetBoxMonData(boxmon, MON_DATA_MOVE1PP + slot2, NULL);
    ppUp[1] = (u8)GetBoxMonData(boxmon, MON_DATA_MOVE1PPUP + slot2, NULL);

    SetBoxMonData(boxmon, MON_DATA_MOVE1 + slot1, &moves[1]);
    SetBoxMonData(boxmon, MON_DATA_MOVE1PP + slot1, &pp[1]);
    SetBoxMonData(boxmon, MON_DATA_MOVE1PPUP + slot1, &ppUp[1]);
    SetBoxMonData(boxmon, MON_DATA_MOVE1 + slot2, &moves[0]);
    SetBoxMonData(boxmon, MON_DATA_MOVE1PP + slot2, &pp[0]);
    SetBoxMonData(boxmon, MON_DATA_MOVE1PPUP + slot2, &ppUp[0]);
}

void sub_020699A4(struct Pokemon *pokemon, u32 slot) {
    u16 move;
    u8 pp;
    u8 ppUp;
    for (; slot < 3; slot++) {
        move = (u16)GetMonData(pokemon, (int)(MON_DATA_MOVE1 + slot + 1), NULL);
        pp = (u8)GetMonData(pokemon, (int)(MON_DATA_MOVE1PP + slot + 1), NULL);
        ppUp = (u8)GetMonData(pokemon, (int)(MON_DATA_MOVE1PPUP + slot + 1), NULL);
        SetMonData(pokemon, (int)(MON_DATA_MOVE1 + slot), &move);
        SetMonData(pokemon, (int)(MON_DATA_MOVE1PP + slot), &pp);
        SetMonData(pokemon, (int)(MON_DATA_MOVE1PPUP + slot), &ppUp);
    }
    move = MOVE_NONE;
    pp = 0;
    ppUp = 0;
    SetMonData(pokemon, MON_DATA_MOVE1 + 3, &move);
    SetMonData(pokemon, MON_DATA_MOVE1PP + 3, &pp);
    SetMonData(pokemon, MON_DATA_MOVE1PPUP + 3, &ppUp);
}

BOOL MonHasMove(struct Pokemon *pokemon, u16 move) {
    int i;
    for (i = 0; i < 4; i++) {
        if (GetMonData(pokemon, MON_DATA_MOVE1 + i, NULL) == move) {
            break;
        }
    }
    if (i != 4) {
        return TRUE;
    } else {
        return FALSE;
    }
}

void CopyBoxPokemonToPokemon(struct BoxPokemon *src, struct Pokemon *dest) {
    u32 sp0 = 0;
    CapsuleArray sp4;
    struct Mail *mail;
    dest->box = *src;
    if (dest->box.box_lock) {
        dest->box.party_lock = TRUE;
    }
    SetMonData(dest, MON_DATA_STATUS, &sp0);
    SetMonData(dest, MON_DATA_HP, &sp0);
    SetMonData(dest, MON_DATA_MAXHP, &sp0);
    mail = Mail_New(HEAP_ID_DEFAULT);
    SetMonData(dest, MON_DATA_MAIL_STRUCT, mail);
    FreeToHeap(mail);
    SetMonData(dest, MON_DATA_CAPSULE, &sp0);
    MI_CpuClearFast(&sp4, sizeof(sp4));
    SetMonData(dest, MON_DATA_SEAL_COORDS, &sp4);
    CalcMonLevelAndStats(dest);
}

u8 Party_GetMaxLevel(struct Party *party) {
    int i;
    int r7 = Party_GetCount(party);
    u8 ret = 1;
    u8 level;
    for (i = 0; i < r7; i++) {
        struct Pokemon *pokemon = Party_GetMonByIndex(party, i);
        if (GetMonData(pokemon, MON_DATA_SPECIES, NULL) != SPECIES_NONE
            && !GetMonData(pokemon, MON_DATA_IS_EGG, NULL)) {
            level = (u8)GetMonData(pokemon, MON_DATA_LEVEL, NULL);
            if (level > ret) {
                ret = level;
            }
        }
    }
    return ret;
}

u16 SpeciesToSinnohDexNo(u16 species) {
    u16 ret;
    ReadFromNarcMemberByIdPair(&ret, NARC_POKETOOL_POKEZUKAN, 0, species * sizeof(u16), sizeof(u16));
    return ret;
}

u16 SinnohDexNoToSpecies(u16 sinnoh_dex) {
    u16 ret = SPECIES_NONE;
    if (sinnoh_dex <= SINNOH_DEX_COUNT) {
        ReadFromNarcMemberByIdPair(&ret, NARC_POKETOOL_SHINZUKAN, 0, sinnoh_dex * sizeof(u16), sizeof(u16));
    }
    return ret;
}

void CopyPokemonToPokemon(struct Pokemon *src, struct Pokemon *dest) {
    *dest = *src;
}

void CopyPokemonToBoxPokemon(struct Pokemon *src, struct BoxPokemon *dest) {
    *dest = src->box;
}

void CopyBoxPokemonToBoxPokemon(struct BoxPokemon *src, struct BoxPokemon *dest) {
    *dest = *src;
}

s8 MonGetFlavorPreference(struct Pokemon *pokemon, int flavor) {
    return BoxMonGetFlavorPreference(&pokemon->box, flavor);
}

s8 BoxMonGetFlavorPreference(struct BoxPokemon *boxmon, int flavor) {
    u32 personality = GetBoxMonData(boxmon, MON_DATA_PERSONALITY, NULL);
    return GetFlavorPreferenceFromPID(personality, flavor);
}

s8 GetFlavorPreferenceFromPID(u32 personality, int flavor) {
    return sFlavorPreferencesByNature[GetNatureFromPersonality(personality)][flavor];
}

int Species_LoadLearnsetTable(u16 species, u32 form, u16 *dest) {
    int i;
    u16 *levelUpLearnset = AllocFromHeap(HEAP_ID_DEFAULT, MAX_LEARNED_MOVES * sizeof(u16));
    LoadLevelUpLearnset_HandleAlternateForm(species, (int)form, levelUpLearnset);
    for (i = 0; levelUpLearnset[i] != LEVEL_UP_LEARNSET_END; i++) {
        dest[i] = LEVEL_UP_LEARNSET_MOVE(levelUpLearnset[i]);
    }
    FreeToHeap(levelUpLearnset);
    return i;
}

void Party_GivePokerusAtRandom(struct Party *party) {
    int count = Party_GetCount(party);
    int idx;
    struct Pokemon *pokemon;
    u8 sp0;
    switch (LCRandom()) {
    case 0x4000:
    case 0x8000:
    case 0xC000:
        do {
            idx = LCRandom() % count;
            pokemon = Party_GetMonByIndex(party, idx);
        } while (GetMonData(pokemon, MON_DATA_SPECIES, NULL) == SPECIES_NONE || GetMonData(pokemon, MON_DATA_IS_EGG, NULL));
        if (!Party_MaskMonsWithPokerus(party, (u8)MaskOfFlagNo(idx))) {
            do {
                sp0 = (u8)LCRandom();
            } while (!(sp0 & 7));
            if (sp0 & 0xF0) {
                sp0 &= 7;
            }
            sp0 |= sp0 << 4;
            sp0 &= 0xF3;
            sp0++;
            SetMonData(pokemon, MON_DATA_POKERUS, &sp0);
        }
    }
}

u8 Party_MaskMonsWithPokerus(struct Party *party, u8 mask) {
    int i = 0;
    u32 flag = 1;
    u8 ret = 0;
    struct Pokemon *pokemon;
    if (mask != 0) {
        do {
            if (mask & 1) {
                pokemon = Party_GetMonByIndex(party, i);
                if (GetMonData(pokemon, MON_DATA_POKERUS, NULL)) {
                    ret |= flag;
                }
            }
            i++;
            flag <<= 1;
            mask >>= 1;
        } while (mask != 0);
    } else {
        pokemon = Party_GetMonByIndex(party, 0);
        if (GetMonData(pokemon, MON_DATA_POKERUS, NULL)) {
            ret++;
        }
    }
    return ret;
}

void Party_UpdatePokerus(struct Party *party, int r5) {
    int i;
    u8 pokerus;
    struct Pokemon *pokemon;
    int count = Party_GetCount(party);
    for (i = 0; i < count; i++) {
        pokemon = Party_GetMonByIndex(party, i);
        if (GetMonData(pokemon, MON_DATA_SPECIES, NULL) != SPECIES_NONE) {
            pokerus = (u8)GetMonData(pokemon, MON_DATA_POKERUS, NULL);
            if (pokerus & 0xF) {
                if ((pokerus & 0xF) < r5 || r5 > 4) {
                    pokerus &= 0xF0;
                } else {
                    pokerus -= r5;
                }
                if (pokerus == 0) {
                    pokerus = 0x10; // immune
                }
                SetMonData(pokemon, MON_DATA_POKERUS, &pokerus);
            }
        }
    }
}

void Party_SpreadPokerus(struct Party *party) {
    int count = Party_GetCount(party);
    int i;
    struct Pokemon *pokemon;
    u8 pokerus;
    if ((LCRandom() % 3) == 0) {
        for (i = 0; i < count; i++) {
            pokemon = Party_GetMonByIndex(party, i);
            if (GetMonData(pokemon, MON_DATA_SPECIES, NULL) != SPECIES_NONE) {
                pokerus = (u8)GetMonData(pokemon, MON_DATA_POKERUS, NULL);
                if (pokerus & 0xF) {
                    if (i != 0) {
                        pokemon = Party_GetMonByIndex(party, i - 1);
                        if (!(GetMonData(pokemon, MON_DATA_POKERUS, NULL) & 0xF0)) {
                            SetMonData(pokemon, MON_DATA_POKERUS, &pokerus);
                        }
                    }
                    if (i < count - 1) {
                        pokemon = Party_GetMonByIndex(party, i + 1);
                        if (!(GetMonData(pokemon, MON_DATA_POKERUS, NULL) & 0xF0)) {
                            SetMonData(pokemon, MON_DATA_POKERUS, &pokerus);
                            i++; // don't infect the rest of the party
                        }
                    }
                }
            }
        }
    }
}

BOOL Pokemon_HasPokerus(struct Pokemon *pokemon) {
    return BoxMon_HasPokerus(&pokemon->box);
}

BOOL BoxMon_HasPokerus(struct BoxPokemon *boxmon) {
    return !!(GetBoxMonData(boxmon, MON_DATA_POKERUS, NULL) & 0xF);
}

BOOL Pokemon_IsImmuneToPokerus(struct Pokemon *pokemon) {
    return BoxMon_IsImmuneToPokerus(&pokemon->box);
}

BOOL BoxMon_IsImmuneToPokerus(struct BoxPokemon *boxmon) {
    u8 pokerus = (u8)GetBoxMonData(boxmon, MON_DATA_POKERUS, NULL);
    if (pokerus & 0xF) {
        return FALSE;
    }
    if (pokerus & 0xF0) {
        return TRUE;
    }
    return FALSE;
}

void Pokemon_UpdateArceusForm(struct Pokemon *pokemon) {
    BoxMon_UpdateArceusForm(&pokemon->box);
}

void BoxMon_UpdateArceusForm(struct BoxPokemon *boxmon) {
    u32 species = GetBoxMonData(boxmon, MON_DATA_SPECIES, NULL);
    u32 ability = GetBoxMonData(boxmon, MON_DATA_ABILITY, NULL);
    u32 heldItem = GetBoxMonData(boxmon, MON_DATA_HELD_ITEM, NULL);
    u32 form;
    if (species == SPECIES_ARCEUS && ability == ABILITY_MULTITYPE) {
        form = GetArceusTypeByHeldItemEffect((u16)GetItemAttr((u16)heldItem, 1, HEAP_ID_DEFAULT));
        SetBoxMonData(boxmon, MON_DATA_FORM, &form);
    }
}

u32 GetArceusTypeByHeldItemEffect(u16 heldEffect) {
    switch (heldEffect) {
    case HOLD_EFFECT_ARCEUS_FIRE:
        return TYPE_FIRE;
    case HOLD_EFFECT_ARCEUS_WATER:
        return TYPE_WATER;
    case HOLD_EFFECT_ARCEUS_ELECTRIC:
        return TYPE_ELECTRIC;
    case HOLD_EFFECT_ARCEUS_GRASS:
        return TYPE_GRASS;
    case HOLD_EFFECT_ARCEUS_ICE:
        return TYPE_ICE;
    case HOLD_EFFECT_ARCEUS_FIGHT:
        return TYPE_FIGHTING;
    case HOLD_EFFECT_ARCEUS_POISON:
        return TYPE_POISON;
    case HOLD_EFFECT_ARCEUS_GROUND:
        return TYPE_GROUND;
    case HOLD_EFFECT_ARCEUS_FLYING:
        return TYPE_FLYING;
    case HOLD_EFFECT_ARCEUS_PSYCHIC:
        return TYPE_PSYCHIC;
    case HOLD_EFFECT_ARCEUS_BUG:
        return TYPE_BUG;
    case HOLD_EFFECT_ARCEUS_ROCK:
        return TYPE_ROCK;
    case HOLD_EFFECT_ARCEUS_GHOST:
        return TYPE_GHOST;
    case HOLD_EFFECT_ARCEUS_DRAGON:
        return TYPE_DRAGON;
    case HOLD_EFFECT_ARCEUS_DARK:
        return TYPE_DARK;
    case HOLD_EFFECT_ARCEUS_STEEL:
        return TYPE_STEEL;
    default:
        return TYPE_NORMAL;
    }
}

void LoadLevelUpLearnset_HandleAlternateForm(int species, int form, u16 *levelUpLearnset) {
    ReadWholeNarcMemberByIdPair(levelUpLearnset, NARC_POKETOOL_PERSONAL_WOTBL, ResolveMonForm(species, form));
}

void sub_02069FB0(struct SaveChatotSoundClip *r7, u32 r5, u16 r4, s32 r6, s32 sp18, u32 sp1C, HeapID heapId) {
    if (r4 == SPECIES_CHATOT) {
        if (!sub_02005F14((int)r5)) {
            sub_02005E80(1);
            sub_020056AC(r5, r4, r6, sp18, heapId);
        } else {
            if (sp1C) {
                sub_02005E80(1);
            }
            sub_02005E90(r7, 0, sp18, r6);
        }
    } else {
        sub_020056AC(r5, r4, r6, sp18, heapId);
    }
}

void sub_0206A014(struct Pokemon *pokemon, PlayerProfile *a1, u32 pokeball, u32 a3, u32 encounterType, HeapID heapId) {
    u32 hp;
    sub_0206A054(&pokemon->box, a1, pokeball, a3, encounterType, heapId);
    if (pokeball == ITEM_HEAL_BALL) {
        hp = GetMonData(pokemon, MON_DATA_MAXHP, NULL);
        SetMonData(pokemon, MON_DATA_HP, &hp);
        hp = 0;
        SetMonData(pokemon, MON_DATA_STATUS, &hp);
    }
}

void sub_0206A054(struct BoxPokemon *boxmon, PlayerProfile *a1, u32 pokeball, u32 a3, u32 encounterType, HeapID heapId) {
    sub_020808AC(boxmon, a1, 0, a3, heapId);
    SetBoxMonData(boxmon, MON_DATA_GAME_VERSION, (void *)&gGameVersion);
    SetBoxMonData(boxmon, MON_DATA_POKEBALL, &pokeball);
    SetBoxMonData(boxmon, MON_DATA_ENCOUNTER_TYPE, &encounterType);
}

void sub_0206A094(struct Pokemon *pokemon, u32 a1, u32 a2) {
    u32 chance;
    u16 species;
    u16 form;
    u16 item1;
    u16 item2;
    if (!(a1 & 0x81)) {
        chance = (u32)(LCRandom() % 100);
        species = (u16)GetMonData(pokemon, MON_DATA_SPECIES, 0);
        form = (u16)GetMonData(pokemon, MON_DATA_FORM, 0);
        item1 = (u16)GetMonBaseStat_HandleFormConversion(species, form, BASE_ITEM_1);
        item2 = (u16)GetMonBaseStat_HandleFormConversion(species, form, BASE_ITEM_2);
        if (item1 == item2 && item1 != ITEM_NONE) {
            SetMonData(pokemon, MON_DATA_HELD_ITEM, &item1);
        } else {
            if (chance >= sItemOdds[a2][0]) {
                if (chance < sItemOdds[a2][1]) {
                    SetMonData(pokemon, MON_DATA_HELD_ITEM, &item1);
                } else {
                    SetMonData(pokemon, MON_DATA_HELD_ITEM, &item2);
                }
            }
        }
    }
}

BOOL sub_0206A13C(struct Pokemon *pokemon, u32 a1) {
    return sub_0206A144(&pokemon->box, a1);
}

BOOL sub_0206A144(struct BoxPokemon *boxmon, u32 a1) {
    u16 species = (u16)GetBoxMonData(boxmon, MON_DATA_SPECIES2, NULL);
    int form = (int)GetBoxMonData(boxmon, MON_DATA_FORM, NULL);
    return sub_0206A16C(species, form, a1);
}

BOOL sub_0206A16C(u16 species, int form, u32 a2) {
    u32 r4;
    enum BaseStat r2;
    if (species == SPECIES_EGG) {
        return FALSE;
    }
    if (a2 < 32) {
        r4 = 1ul << a2;
        r2 = BASE_TMHM_1;
    } else if (a2 < 64) {
        r4 = 1ul << (a2 - 32);
        r2 = BASE_TMHM_2;
    } else if (a2 < 96) {
        r4 = 1ul << (a2 - 64);
        r2 = BASE_TMHM_3;
    } else {
        r4 = 1ul << (a2 - 96);
        r2 = BASE_TMHM_4;
    }
    return !!(GetMonBaseStat_HandleFormConversion(species, form, r2) & r4);
}

void sub_0206A1C4(struct Pokemon *pokemon) {
    sub_0206A1CC(&pokemon->box);
}

void sub_0206A1CC(struct BoxPokemon *boxmon) {
    BOOL decry = AcquireBoxMonLock(boxmon);
    int species = (int)GetBoxMonData(boxmon, MON_DATA_SPECIES, NULL);
    int pid = (int)GetBoxMonData(boxmon, MON_DATA_PERSONALITY, NULL);
    int ability1 = (int)GetMonBaseStat(species, BASE_ABILITY_1);
    int ability2 = (int)GetMonBaseStat(species, BASE_ABILITY_2);

    if (ability2 != ABILITY_NONE) {
        if (pid & 1) {
            SetBoxMonData(boxmon, MON_DATA_ABILITY, &ability2);
        } else {
            SetBoxMonData(boxmon, MON_DATA_ABILITY, &ability1);
        }
    } else {
        SetBoxMonData(boxmon, MON_DATA_ABILITY, &ability1);
    }
    ReleaseBoxMonLock(boxmon, decry);
}

void sub_0206A23C(struct Pokemon *r5, u32 personality) {
    PokemonDataBlockA *r4;
    PokemonDataBlockB *r6;
    PokemonDataBlockC *r7;
    PokemonDataBlockD *sp8;
    PokemonDataBlockA *spC;
    PokemonDataBlockB *sp10;
    PokemonDataBlockC *sp14;
    PokemonDataBlockD *sp18;
    struct Pokemon *sp4;

    sp4 = AllocMonZeroed(HEAP_ID_DEFAULT);
    CopyPokemonToPokemon(r5, sp4);
    r4 = &GetSubstruct(&sp4->box, r5->box.pid, 0)->blockA;
    r6 = &GetSubstruct(&sp4->box, r5->box.pid, 1)->blockB;
    r7 = &GetSubstruct(&sp4->box, r5->box.pid, 2)->blockC;
    sp8 = &GetSubstruct(&sp4->box, r5->box.pid, 3)->blockD;
    spC = &GetSubstruct(&r5->box, personality, 0)->blockA;
    sp10 = &GetSubstruct(&r5->box, personality, 1)->blockB;
    sp14 = &GetSubstruct(&r5->box, personality, 2)->blockC;
    sp18 = &GetSubstruct(&r5->box, personality, 3)->blockD;

    DECRYPT_BOX(&sp4->box);
    DECRYPT_PTY(r5);
    DECRYPT_BOX(&r5->box);
    r5->box.pid = personality;
    *spC = *r4;
    *sp10 = *r6;
    *sp14 = *r7;
    *sp18 = *sp8;
    r5->box.checksum = CHECKSUM(&r5->box);
    ENCRYPT_BOX(&r5->box);
    ENCRYPT_PTY(r5);
    FreeToHeap(sp4);
}

void LoadMonPersonal(int species, struct BaseStats *personal) {
    ReadWholeNarcMemberByIdPair(personal, NARC_POKETOOL_PERSONAL_PERSONAL, species);
}

void LoadMonBaseStats_HandleAlternateForm(int species, int form, struct BaseStats *personal) {
    ReadWholeNarcMemberByIdPair(personal, NARC_POKETOOL_PERSONAL_PERSONAL, ResolveMonForm(species, form));
}

void LoadMonEvolutionTable(u16 species, struct Evolution *evo) {
    ReadWholeNarcMemberByIdPair(evo, NARC_POKETOOL_PERSONAL_EVO, species);
}

void MonEncryptSegment(u16 *data, u32 size, u32 seed) {
    int i;
    for (i = 0; i < size / 2; i++) {
        data[i] ^= MonEncryptionLCRNG(&seed);
    }
}

void MonDecryptSegment(u16 *data, u32 size, u32 seed) {
    MonEncryptSegment(data, size, seed);
}

u16 MonEncryptionLCRNG(u32 *seed) {
    *seed = *seed * 1103515245 + 24691;
    return (u16)(*seed >> 16);
}

u16 CalcMonChecksum(u16 *data, u32 size) {
    int i;
    u16 ret = 0;
    for (i = 0; i < size / 2; i++) {
        ret += data[i];
    }
    return ret;
}

#define SUBSTRUCT_CASE(v1, v2, v3, v4)                     \
    {                                                      \
        PokemonDataBlock *substructs = boxMon->substructs; \
        switch (substructType) {                           \
        case 0:                                            \
            result = &substructs[v1];                      \
            break;                                         \
        case 1:                                            \
            result = &substructs[v2];                      \
            break;                                         \
        case 2:                                            \
            result = &substructs[v3];                      \
            break;                                         \
        case 3:                                            \
            result = &substructs[v4];                      \
            break;                                         \
        }                                                  \
                                                           \
        break;                                             \
    }

PokemonDataBlock *GetSubstruct(struct BoxPokemon *boxMon, u32 personality, u8 substructType) {
    PokemonDataBlock *result;

    switch ((personality & 0x3E000) >> 13) {
    case 0:
    case 24:
        SUBSTRUCT_CASE(0, 1, 2, 3)
    case 1:
    case 25:
        SUBSTRUCT_CASE(0, 1, 3, 2)
    case 2:
    case 26:
        SUBSTRUCT_CASE(0, 2, 1, 3)
    case 3:
    case 27:
        SUBSTRUCT_CASE(0, 3, 1, 2)
    case 4:
    case 28:
        SUBSTRUCT_CASE(0, 2, 3, 1)
    case 5:
    case 29:
        SUBSTRUCT_CASE(0, 3, 2, 1)
    case 6:
    case 30:
        SUBSTRUCT_CASE(1, 0, 2, 3)
    case 7:
    case 31:
        SUBSTRUCT_CASE(1, 0, 3, 2)
    case 8:
        SUBSTRUCT_CASE(2, 0, 1, 3)
    case 9:
        SUBSTRUCT_CASE(3, 0, 1, 2)
    case 10:
        SUBSTRUCT_CASE(2, 0, 3, 1)
    case 11:
        SUBSTRUCT_CASE(3, 0, 2, 1)
    case 12:
        SUBSTRUCT_CASE(1, 2, 0, 3)
    case 13:
        SUBSTRUCT_CASE(1, 3, 0, 2)
    case 14:
        SUBSTRUCT_CASE(2, 1, 0, 3)
    case 15:
        SUBSTRUCT_CASE(3, 1, 0, 2)
    case 16:
        SUBSTRUCT_CASE(2, 3, 0, 1)
    case 17:
        SUBSTRUCT_CASE(3, 2, 0, 1)
    case 18:
        SUBSTRUCT_CASE(1, 2, 3, 0)
    case 19:
        SUBSTRUCT_CASE(1, 3, 2, 0)
    case 20:
        SUBSTRUCT_CASE(2, 1, 3, 0)
    case 21:
        SUBSTRUCT_CASE(3, 1, 2, 0)
    case 22:
        SUBSTRUCT_CASE(2, 3, 1, 0)
    case 23:
        SUBSTRUCT_CASE(3, 2, 1, 0)
    }
    return result;
}

int ResolveMonForm(int species, int form) {
    switch (species) {
    case SPECIES_DEOXYS:
        if (form != 0 && form <= 3) {
            return SPECIES_DEOXYS_ATK + form - 1;
        }
        break;
    case SPECIES_WORMADAM:
        if (form != 0 && form <= 2) {
            return SPECIES_WORMADAM_SANDY + form - 1;
        }
        break;
    }
    return species;
}

u32 MaskOfFlagNo(int flagno) {
    // This is completely inane.
    int i;
    u32 ret = 1;
    GF_ASSERT(flagno < 32);
    for (i = 0; i < flagno; i++) {
        ret <<= 1;
    }
    return ret;
}

int LowestFlagNo(u32 mask) {
    int i;
    u32 bit = 1;
    for (i = 0; i < 32; i++) {
        if (mask & bit) {
            break;
        }
        bit <<= 1;
    }
    return i;
}

BOOL IsPokemonLegendaryOrMythical(u16 species) {
    int i;
    for (i = 0; i < NELEMS(sLegendaryMonsList); i++) {
        if (species == sLegendaryMonsList[i]) {
            return TRUE;
        }
    }
    return FALSE;
}

u16 GetLegendaryMon(u32 idx) {
    if (idx >= NELEMS(sLegendaryMonsList)) {
        idx = 0;
    }
    return sLegendaryMonsList[idx];
}

BOOL sub_0206A998(struct Pokemon *pokemon) {
    u16 species = (u16)GetMonData(pokemon, MON_DATA_SPECIES, NULL);
    return IsPokemonLegendaryOrMythical(species);
}

BOOL sub_0206A9AC(struct BoxPokemon *boxmon, PlayerProfile *sb2, HeapID heapId) {
    u32 myId = PlayerProfile_GetTrainerID(sb2);
    u32 otId = GetBoxMonData(boxmon, MON_DATA_OTID, NULL);
    u32 myGender = PlayerProfile_GetTrainerGender(sb2);
    u32 otGender = GetBoxMonData(boxmon, MON_DATA_MET_GENDER, NULL);
    struct String *r7 = PlayerProfile_GetPlayerName_NewString(sb2, heapId);
    struct String *r6 = String_New(PLAYER_NAME_LENGTH + 1, heapId);
    BOOL ret = FALSE;
    GetBoxMonData(boxmon, MON_DATA_OT_NAME_2, r6);
    if (myId == otId && myGender == otGender && String_Compare(r7, r6) == 0) {
        ret = TRUE;
    }
    String_Delete(r6);
    String_Delete(r7);
    return ret;
}

int sub_0206AA30(int x) {
    switch (x) {
    case TRAINER_CLASS_PKMN_TRAINER_BARRY:
        return TRAINER_BACKPIC_BARRY;
    case TRAINER_CLASS_PKMN_TRAINER_CHERYL:
    case TRAINER_CLASS_PKMN_TRAINER_RILEY:
    case TRAINER_CLASS_PKMN_TRAINER_MARLEY:
    case TRAINER_CLASS_PKMN_TRAINER_BUCK:
    case TRAINER_CLASS_PKMN_TRAINER_MIRA:
        return x - TRAINER_CLASS_PKMN_TRAINER_CHERYL + TRAINER_BACKPIC_CHERYL;
    default:
        if (TrainerClass_GetGenderOrTrainerCount(x) == 1) {
            return TRAINER_BACKPIC_DAWN;
        } else {
            return TRAINER_BACKPIC_LUCAS;
        }
    case TRAINER_CLASS_PKMN_TRAINER_M:
    case TRAINER_CLASS_PKMN_TRAINER_F:
        return x - TRAINER_CLASS_PKMN_TRAINER_M + TRAINER_BACKPIC_LUCAS;
    }
}

void Pokemon_RemoveCapsule(struct Pokemon *pokemon) {
    u8 sp0 = 0;
    CapsuleArray sp1;
    MI_CpuClearFast(&sp1, sizeof(sp1));
    SetMonData(pokemon, MON_DATA_CAPSULE, &sp0);
    SetMonData(pokemon, MON_DATA_SEAL_COORDS, &sp1);
}

void RestoreBoxMonPP(struct BoxPokemon *boxmon) {
    int i;
    u8 pp;
    BOOL decry = AcquireBoxMonLock(boxmon);
    for (i = 0; i < 4; i++) {
        if (GetBoxMonData(boxmon, MON_DATA_MOVE1 + i, NULL) != MOVE_NONE) {
            pp = (u8)GetBoxMonData(boxmon, MON_DATA_MOVE1MAXPP + i, NULL);
            SetBoxMonData(boxmon, MON_DATA_MOVE1PP + i, &pp);
        }
    }
    ReleaseBoxMonLock(boxmon, decry);
}
