sovereignx/src/script_pokemon_util.c
Eduardo Quezada cc00446eb8 Merge branch '_RHH/master' into _RHH/upcoming
# Conflicts:
#	src/data/graphics/pokemon.h
#	src/data/pokemon/species_info/gen_9_families.h
2024-07-26 09:42:52 -04:00

563 lines
19 KiB
C

#include "global.h"
#include "battle.h"
#include "battle_gfx_sfx_util.h"
#include "berry.h"
#include "data.h"
#include "daycare.h"
#include "decompress.h"
#include "event_data.h"
#include "international_string_util.h"
#include "item.h"
#include "link.h"
#include "link_rfu.h"
#include "main.h"
#include "menu.h"
#include "overworld.h"
#include "palette.h"
#include "party_menu.h"
#include "pokedex.h"
#include "pokemon.h"
#include "pokemon_storage_system.h"
#include "random.h"
#include "script.h"
#include "sprite.h"
#include "string_util.h"
#include "tv.h"
#include "wild_encounter.h"
#include "constants/abilities.h"
#include "constants/items.h"
#include "constants/battle_frontier.h"
static void CB2_ReturnFromChooseHalfParty(void);
static void CB2_ReturnFromChooseBattleFrontierParty(void);
static void HealPlayerBoxes(void);
void HealPlayerParty(void)
{
u32 i;
for (i = 0; i < gPlayerPartyCount; i++)
HealPokemon(&gPlayerParty[i]);
if (OW_PC_HEAL >= GEN_8)
HealPlayerBoxes();
// Recharge Tera Orb, if possible.
if (B_FLAG_TERA_ORB_CHARGED != 0 && CheckBagHasItem(ITEM_TERA_ORB, 1))
FlagSet(B_FLAG_TERA_ORB_CHARGED);
}
static void HealPlayerBoxes(void)
{
int boxId, boxPosition;
struct BoxPokemon *boxMon;
for (boxId = 0; boxId < TOTAL_BOXES_COUNT; boxId++)
{
for (boxPosition = 0; boxPosition < IN_BOX_COUNT; boxPosition++)
{
boxMon = &gPokemonStoragePtr->boxes[boxId][boxPosition];
if (GetBoxMonData(boxMon, MON_DATA_SANITY_HAS_SPECIES))
HealBoxPokemon(boxMon);
}
}
}
u8 ScriptGiveEgg(u16 species)
{
struct Pokemon mon;
u8 isEgg;
CreateEgg(&mon, species, TRUE);
isEgg = TRUE;
SetMonData(&mon, MON_DATA_IS_EGG, &isEgg);
return GiveMonToPlayer(&mon);
}
void HasEnoughMonsForDoubleBattle(void)
{
switch (GetMonsStateToDoubles())
{
case PLAYER_HAS_TWO_USABLE_MONS:
gSpecialVar_Result = PLAYER_HAS_TWO_USABLE_MONS;
break;
case PLAYER_HAS_ONE_MON:
gSpecialVar_Result = PLAYER_HAS_ONE_MON;
break;
case PLAYER_HAS_ONE_USABLE_MON:
gSpecialVar_Result = PLAYER_HAS_ONE_USABLE_MON;
break;
}
}
static bool8 CheckPartyMonHasHeldItem(u16 item)
{
int i;
for(i = 0; i < PARTY_SIZE; i++)
{
u16 species = GetMonData(&gPlayerParty[i], MON_DATA_SPECIES_OR_EGG);
if (species != SPECIES_NONE && species != SPECIES_EGG && GetMonData(&gPlayerParty[i], MON_DATA_HELD_ITEM) == item)
return TRUE;
}
return FALSE;
}
bool8 DoesPartyHaveEnigmaBerry(void)
{
bool8 hasItem = CheckPartyMonHasHeldItem(ITEM_ENIGMA_BERRY_E_READER);
if (hasItem == TRUE)
GetBerryNameByBerryType(ItemIdToBerryType(ITEM_ENIGMA_BERRY_E_READER), gStringVar1);
return hasItem;
}
void CreateScriptedWildMon(u16 species, u8 level, u16 item)
{
u8 heldItem[2];
ZeroEnemyPartyMons();
if (OW_SYNCHRONIZE_NATURE > GEN_3)
CreateMonWithNature(&gEnemyParty[0], species, level, USE_RANDOM_IVS, PickWildMonNature());
else
CreateMon(&gEnemyParty[0], species, level, USE_RANDOM_IVS, 0, 0, OT_ID_PLAYER_ID, 0);
if (item)
{
heldItem[0] = item;
heldItem[1] = item >> 8;
SetMonData(&gEnemyParty[0], MON_DATA_HELD_ITEM, heldItem);
}
}
void CreateScriptedDoubleWildMon(u16 species1, u8 level1, u16 item1, u16 species2, u8 level2, u16 item2)
{
u8 heldItem1[2];
u8 heldItem2[2];
ZeroEnemyPartyMons();
if (OW_SYNCHRONIZE_NATURE > GEN_3)
CreateMonWithNature(&gEnemyParty[0], species1, level1, 32, PickWildMonNature());
else
CreateMon(&gEnemyParty[0], species1, level1, 32, 0, 0, OT_ID_PLAYER_ID, 0);
if (item1)
{
heldItem1[0] = item1;
heldItem1[1] = item1 >> 8;
SetMonData(&gEnemyParty[0], MON_DATA_HELD_ITEM, heldItem1);
}
if (OW_SYNCHRONIZE_NATURE > GEN_3)
CreateMonWithNature(&gEnemyParty[1], species2, level2, 32, PickWildMonNature());
else
CreateMon(&gEnemyParty[1], species2, level2, 32, 0, 0, OT_ID_PLAYER_ID, 0);
if (item2)
{
heldItem2[0] = item2;
heldItem2[1] = item2 >> 8;
SetMonData(&gEnemyParty[1], MON_DATA_HELD_ITEM, heldItem2);
}
}
void ScriptSetMonMoveSlot(u8 monIndex, u16 move, u8 slot)
{
// Allows monIndex to go out of bounds of gPlayerParty. Doesn't occur in vanilla
#ifdef BUGFIX
if (monIndex >= PARTY_SIZE)
#else
if (monIndex > PARTY_SIZE)
#endif
monIndex = gPlayerPartyCount - 1;
SetMonMoveSlot(&gPlayerParty[monIndex], move, slot);
}
// Note: When control returns to the event script, gSpecialVar_Result will be
// TRUE if the party selection was successful.
void ChooseHalfPartyForBattle(void)
{
gMain.savedCallback = CB2_ReturnFromChooseHalfParty;
VarSet(VAR_FRONTIER_FACILITY, FACILITY_MULTI_OR_EREADER);
InitChooseHalfPartyForBattle(0);
}
static void CB2_ReturnFromChooseHalfParty(void)
{
switch (gSelectedOrderFromParty[0])
{
case 0:
gSpecialVar_Result = FALSE;
break;
default:
gSpecialVar_Result = TRUE;
break;
}
SetMainCallback2(CB2_ReturnToFieldContinueScriptPlayMapMusic);
}
void ChoosePartyForBattleFrontier(void)
{
gMain.savedCallback = CB2_ReturnFromChooseBattleFrontierParty;
InitChooseHalfPartyForBattle(gSpecialVar_0x8004 + 1);
}
static void CB2_ReturnFromChooseBattleFrontierParty(void)
{
switch (gSelectedOrderFromParty[0])
{
case 0:
gSpecialVar_Result = FALSE;
break;
default:
gSpecialVar_Result = TRUE;
break;
}
SetMainCallback2(CB2_ReturnToFieldContinueScriptPlayMapMusic);
}
void ReducePlayerPartyToSelectedMons(void)
{
struct Pokemon party[MAX_FRONTIER_PARTY_SIZE];
int i;
CpuFill32(0, party, sizeof party);
// copy the selected Pokémon according to the order.
for (i = 0; i < MAX_FRONTIER_PARTY_SIZE; i++)
if (gSelectedOrderFromParty[i]) // as long as the order keeps going (did the player select 1 mon? 2? 3?), do not stop
party[i] = gPlayerParty[gSelectedOrderFromParty[i] - 1]; // index is 0 based, not literal
CpuFill32(0, gPlayerParty, sizeof gPlayerParty);
// overwrite the first 4 with the order copied to.
for (i = 0; i < MAX_FRONTIER_PARTY_SIZE; i++)
gPlayerParty[i] = party[i];
CalculatePlayerPartyCount();
}
void CanHyperTrain(struct ScriptContext *ctx)
{
u32 stat = ScriptReadByte(ctx);
u32 partyIndex = VarGet(ScriptReadHalfword(ctx));
if (stat < NUM_STATS
&& partyIndex < PARTY_SIZE
&& !GetMonData(&gPlayerParty[partyIndex], MON_DATA_HYPER_TRAINED_HP + stat)
&& GetMonData(&gPlayerParty[partyIndex], MON_DATA_HP_IV + stat) < MAX_PER_STAT_IVS)
{
gSpecialVar_Result = TRUE;
}
else
{
gSpecialVar_Result = FALSE;
}
}
void HyperTrain(struct ScriptContext *ctx)
{
u32 stat = ScriptReadByte(ctx);
u32 partyIndex = VarGet(ScriptReadHalfword(ctx));
if (stat < NUM_STATS && partyIndex < PARTY_SIZE)
{
bool32 data = TRUE;
SetMonData(&gPlayerParty[partyIndex], MON_DATA_HYPER_TRAINED_HP + stat, &data);
CalculateMonStats(&gPlayerParty[partyIndex]);
}
}
void HasGigantamaxFactor(struct ScriptContext *ctx)
{
u32 partyIndex = VarGet(ScriptReadHalfword(ctx));
if (partyIndex < PARTY_SIZE)
gSpecialVar_Result = GetMonData(&gPlayerParty[partyIndex], MON_DATA_GIGANTAMAX_FACTOR);
else
gSpecialVar_Result = FALSE;
}
void ToggleGigantamaxFactor(struct ScriptContext *ctx)
{
u32 partyIndex = VarGet(ScriptReadHalfword(ctx));
gSpecialVar_Result = FALSE;
if (partyIndex < PARTY_SIZE)
{
bool32 gigantamaxFactor;
if (gSpeciesInfo[SanitizeSpeciesId(GetMonData(&gPlayerParty[partyIndex], MON_DATA_SPECIES))].isMythical)
return;
gigantamaxFactor = GetMonData(&gPlayerParty[partyIndex], MON_DATA_GIGANTAMAX_FACTOR);
gigantamaxFactor = !gigantamaxFactor;
SetMonData(&gPlayerParty[partyIndex], MON_DATA_GIGANTAMAX_FACTOR, &gigantamaxFactor);
gSpecialVar_Result = TRUE;
}
}
void CheckTeraType(struct ScriptContext *ctx)
{
u32 partyIndex = VarGet(ScriptReadHalfword(ctx));
gSpecialVar_Result = TYPE_NONE;
if (partyIndex < PARTY_SIZE)
gSpecialVar_Result = GetMonData(&gPlayerParty[partyIndex], MON_DATA_TERA_TYPE);
}
void SetTeraType(struct ScriptContext *ctx)
{
u32 type = ScriptReadByte(ctx);
u32 partyIndex = VarGet(ScriptReadHalfword(ctx));
if (type < NUMBER_OF_MON_TYPES && partyIndex < PARTY_SIZE)
SetMonData(&gPlayerParty[partyIndex], MON_DATA_TERA_TYPE, &type);
}
/* Creates a Pokemon via script
* if side/slot are assigned, it will create the mon at the assigned party location
* if slot == PARTY_SIZE, it will give the mon to first available party or storage slot
*/
static u32 ScriptGiveMonParameterized(u8 side, u8 slot, u16 species, u8 level, u16 item, u8 ball, u8 nature, u8 abilityNum, u8 gender, u8 *evs, u8 *ivs, u16 *moves, bool8 isShiny, bool8 ggMaxFactor, u8 teraType)
{
u16 nationalDexNum;
int sentToPc;
struct Pokemon mon;
u32 i;
u8 genderRatio = gSpeciesInfo[species].genderRatio;
u16 targetSpecies;
// check whether to use a specific nature or a random one
if (nature >= NUM_NATURES)
{
if (OW_SYNCHRONIZE_NATURE >= GEN_6
&& (gSpeciesInfo[species].eggGroups[0] == EGG_GROUP_NO_EGGS_DISCOVERED || OW_SYNCHRONIZE_NATURE == GEN_7))
nature = PickWildMonNature();
else
nature = Random() % NUM_NATURES;
}
// create a Pokémon with basic data
if ((gender == MON_MALE && genderRatio != MON_FEMALE && genderRatio != MON_GENDERLESS)
|| (gender == MON_FEMALE && genderRatio != MON_MALE && genderRatio != MON_GENDERLESS)
|| (gender == MON_GENDERLESS && genderRatio == MON_GENDERLESS))
CreateMonWithGenderNatureLetter(&mon, species, level, 32, gender, nature, 0);
else
CreateMonWithNature(&mon, species, level, 32, nature);
// shininess
if (P_FLAG_FORCE_SHINY != 0 && FlagGet(P_FLAG_FORCE_SHINY))
isShiny = TRUE;
else if (P_FLAG_FORCE_NO_SHINY != 0 && FlagGet(P_FLAG_FORCE_NO_SHINY))
isShiny = FALSE;
SetMonData(&mon, MON_DATA_IS_SHINY, &isShiny);
// gigantamax factor
SetMonData(&mon, MON_DATA_GIGANTAMAX_FACTOR, &ggMaxFactor);
// tera type
if (teraType >= NUMBER_OF_MON_TYPES)
teraType = TYPE_NONE;
SetMonData(&mon, MON_DATA_TERA_TYPE, &teraType);
// EV and IV
for (i = 0; i < NUM_STATS; i++)
{
// EV
if (evs[i] <= MAX_PER_STAT_EVS)
SetMonData(&mon, MON_DATA_HP_EV + i, &evs[i]);
// IV
if (ivs[i] <= MAX_PER_STAT_IVS)
SetMonData(&mon, MON_DATA_HP_IV + i, &ivs[i]);
}
CalculateMonStats(&mon);
// moves
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (moves[0] == MOVE_NONE)
break;
if (moves[i] >= MOVES_COUNT)
continue;
SetMonMoveSlot(&mon, moves[i], i);
}
// ability
if (abilityNum == NUM_ABILITY_PERSONALITY)
{
abilityNum = GetMonData(&mon, MON_DATA_PERSONALITY) & 1;
}
else if (abilityNum > NUM_NORMAL_ABILITY_SLOTS || GetAbilityBySpecies(species, abilityNum) == ABILITY_NONE)
{
do {
abilityNum = Random() % NUM_ABILITY_SLOTS; // includes hidden abilities
} while (GetAbilityBySpecies(species, abilityNum) == ABILITY_NONE);
}
SetMonData(&mon, MON_DATA_ABILITY_NUM, &abilityNum);
// ball
if (ball > LAST_BALL)
ball = ITEM_POKE_BALL;
SetMonData(&mon, MON_DATA_POKEBALL, &ball);
// held item
SetMonData(&mon, MON_DATA_HELD_ITEM, &item);
// In case a mon with a form changing item is given. Eg: SPECIES_ARCEUS_NORMAL with ITEM_SPLASH_PLATE will transform into SPECIES_ARCEUS_WATER upon gifted.
targetSpecies = GetFormChangeTargetSpecies(&mon, FORM_CHANGE_ITEM_HOLD, 0);
if (targetSpecies != SPECIES_NONE)
SetMonData(&mon, MON_DATA_SPECIES, &targetSpecies);
// assign OT name and gender
SetMonData(&mon, MON_DATA_OT_NAME, gSaveBlock2Ptr->playerName);
SetMonData(&mon, MON_DATA_OT_GENDER, &gSaveBlock2Ptr->playerGender);
if (slot < PARTY_SIZE)
{
if (side == 0)
CopyMon(&gPlayerParty[slot], &mon, sizeof(struct Pokemon));
else
CopyMon(&gEnemyParty[slot], &mon, sizeof(struct Pokemon));
sentToPc = MON_GIVEN_TO_PARTY;
}
else
{
// find empty party slot to decide whether the Pokémon goes to the Player's party or the storage system.
for (i = 0; i < PARTY_SIZE; i++)
{
if (GetMonData(&gPlayerParty[i], MON_DATA_SPECIES, NULL) == SPECIES_NONE)
break;
}
if (i >= PARTY_SIZE)
{
sentToPc = CopyMonToPC(&mon);
}
else
{
sentToPc = MON_GIVEN_TO_PARTY;
CopyMon(&gPlayerParty[i], &mon, sizeof(mon));
gPlayerPartyCount = i + 1;
}
}
if (side == 0)
{
// set pokédex flags
nationalDexNum = SpeciesToNationalPokedexNum(species);
if (sentToPc != MON_CANT_GIVE)
{
GetSetPokedexFlag(nationalDexNum, FLAG_SET_SEEN);
GetSetPokedexFlag(nationalDexNum, FLAG_SET_CAUGHT);
}
}
return sentToPc;
}
u32 ScriptGiveMon(u16 species, u8 level, u16 item)
{
u8 evs[NUM_STATS] = {0, 0, 0, 0, 0, 0};
u8 ivs[NUM_STATS] = {MAX_PER_STAT_IVS + 1, MAX_PER_STAT_IVS + 1, MAX_PER_STAT_IVS + 1, // We pass "MAX_PER_STAT_IVS + 1" here to ensure that
MAX_PER_STAT_IVS + 1, MAX_PER_STAT_IVS + 1, MAX_PER_STAT_IVS + 1}; // ScriptGiveMonParameterized won't touch the stats' IV.
u16 moves[MAX_MON_MOVES] = {MOVE_NONE, MOVE_NONE, MOVE_NONE, MOVE_NONE};
return ScriptGiveMonParameterized(0, PARTY_SIZE, species, level, item, ITEM_POKE_BALL, NUM_NATURES, NUM_ABILITY_PERSONALITY, MON_GENDERLESS, evs, ivs, moves, FALSE, FALSE, NUMBER_OF_MON_TYPES);
}
#define PARSE_FLAG(n, default_) (flags & (1 << (n))) ? VarGet(ScriptReadHalfword(ctx)) : (default_)
/* Give or create a mon to either player or opponent
*/
void ScrCmd_createmon(struct ScriptContext *ctx)
{
u8 side = ScriptReadByte(ctx);
u8 slot = ScriptReadByte(ctx);
u16 species = VarGet(ScriptReadHalfword(ctx));
u8 level = VarGet(ScriptReadHalfword(ctx));
u32 flags = ScriptReadWord(ctx);
u16 item = PARSE_FLAG(0, ITEM_NONE);
u8 ball = PARSE_FLAG(1, ITEM_POKE_BALL);
u8 nature = PARSE_FLAG(2, NUM_NATURES);
u8 abilityNum = PARSE_FLAG(3, NUM_ABILITY_PERSONALITY);
u8 gender = PARSE_FLAG(4, MON_GENDERLESS); // TODO: Find a better way to assign a random gender.
u8 hpEv = PARSE_FLAG(5, 0);
u8 atkEv = PARSE_FLAG(6, 0);
u8 defEv = PARSE_FLAG(7, 0);
u8 speedEv = PARSE_FLAG(8, 0);
u8 spAtkEv = PARSE_FLAG(9, 0);
u8 spDefEv = PARSE_FLAG(10, 0);
u8 hpIv = PARSE_FLAG(11, Random() % (MAX_PER_STAT_IVS + 1));
u8 atkIv = PARSE_FLAG(12, Random() % (MAX_PER_STAT_IVS + 1));
u8 defIv = PARSE_FLAG(13, Random() % (MAX_PER_STAT_IVS + 1));
u8 speedIv = PARSE_FLAG(14, Random() % (MAX_PER_STAT_IVS + 1));
u8 spAtkIv = PARSE_FLAG(15, Random() % (MAX_PER_STAT_IVS + 1));
u8 spDefIv = PARSE_FLAG(16, Random() % (MAX_PER_STAT_IVS + 1));
u16 move1 = PARSE_FLAG(17, MOVE_NONE);
u16 move2 = PARSE_FLAG(18, MOVE_NONE);
u16 move3 = PARSE_FLAG(19, MOVE_NONE);
u16 move4 = PARSE_FLAG(20, MOVE_NONE);
bool8 isShiny = PARSE_FLAG(21, FALSE);
bool8 ggMaxFactor = PARSE_FLAG(22, FALSE);
u8 teraType = PARSE_FLAG(23, NUMBER_OF_MON_TYPES);
u8 evs[NUM_STATS] = {hpEv, atkEv, defEv, speedEv, spAtkEv, spDefEv};
u8 ivs[NUM_STATS] = {hpIv, atkIv, defIv, speedIv, spAtkIv, spDefIv};
u16 moves[MAX_MON_MOVES] = {move1, move2, move3, move4};
gSpecialVar_Result = ScriptGiveMonParameterized(side, slot, species, level, item, ball, nature, abilityNum, gender, evs, ivs, moves, isShiny, ggMaxFactor, teraType);
}
#undef PARSE_FLAG
void Script_GetChosenMonOffensiveEVs(void)
{
ConvertIntToDecimalStringN(gStringVar1, GetMonData(&gPlayerParty[gSpecialVar_0x8004], MON_DATA_ATK_EV), STR_CONV_MODE_LEFT_ALIGN, 3);
ConvertIntToDecimalStringN(gStringVar2, GetMonData(&gPlayerParty[gSpecialVar_0x8004], MON_DATA_SPATK_EV), STR_CONV_MODE_LEFT_ALIGN, 3);
ConvertIntToDecimalStringN(gStringVar3, GetMonData(&gPlayerParty[gSpecialVar_0x8004], MON_DATA_SPEED_EV), STR_CONV_MODE_LEFT_ALIGN, 3);
}
void Script_GetChosenMonDefensiveEVs(void)
{
ConvertIntToDecimalStringN(gStringVar1, GetMonData(&gPlayerParty[gSpecialVar_0x8004], MON_DATA_HP_EV), STR_CONV_MODE_LEFT_ALIGN, 3);
ConvertIntToDecimalStringN(gStringVar2, GetMonData(&gPlayerParty[gSpecialVar_0x8004], MON_DATA_DEF_EV), STR_CONV_MODE_LEFT_ALIGN, 3);
ConvertIntToDecimalStringN(gStringVar3, GetMonData(&gPlayerParty[gSpecialVar_0x8004], MON_DATA_SPDEF_EV), STR_CONV_MODE_LEFT_ALIGN, 3);
}
void Script_GetChosenMonOffensiveIVs(void)
{
ConvertIntToDecimalStringN(gStringVar1, GetMonData(&gPlayerParty[gSpecialVar_0x8004], MON_DATA_ATK_IV), STR_CONV_MODE_LEFT_ALIGN, 3);
ConvertIntToDecimalStringN(gStringVar2, GetMonData(&gPlayerParty[gSpecialVar_0x8004], MON_DATA_SPATK_IV), STR_CONV_MODE_LEFT_ALIGN, 3);
ConvertIntToDecimalStringN(gStringVar3, GetMonData(&gPlayerParty[gSpecialVar_0x8004], MON_DATA_SPEED_IV), STR_CONV_MODE_LEFT_ALIGN, 3);
}
void Script_GetChosenMonDefensiveIVs(void)
{
ConvertIntToDecimalStringN(gStringVar1, GetMonData(&gPlayerParty[gSpecialVar_0x8004], MON_DATA_HP_IV), STR_CONV_MODE_LEFT_ALIGN, 3);
ConvertIntToDecimalStringN(gStringVar2, GetMonData(&gPlayerParty[gSpecialVar_0x8004], MON_DATA_DEF_IV), STR_CONV_MODE_LEFT_ALIGN, 3);
ConvertIntToDecimalStringN(gStringVar3, GetMonData(&gPlayerParty[gSpecialVar_0x8004], MON_DATA_SPDEF_IV), STR_CONV_MODE_LEFT_ALIGN, 3);
}
void Script_SetStatus1(struct ScriptContext *ctx)
{
u32 status1 = VarGet(ScriptReadHalfword(ctx));
u32 slot = VarGet(ScriptReadHalfword(ctx));
if (slot >= PARTY_SIZE)
{
u16 species;
for (slot = 0; slot < PARTY_SIZE; slot++)
{
species = GetMonData(&gPlayerParty[slot], MON_DATA_SPECIES);
if (species != SPECIES_NONE
&& species != SPECIES_EGG
&& GetMonData(&gPlayerParty[slot], MON_DATA_HP) != 0)
SetMonData(&gPlayerParty[slot], MON_DATA_STATUS, &status1);
}
}
else
{
SetMonData(&gPlayerParty[slot], MON_DATA_STATUS, &status1);
}
}