Use assertf to detect errors

This commit is contained in:
Martin Griffin 2025-11-10 14:11:37 +00:00
parent cc8c8bd668
commit 35255475cb
21 changed files with 361 additions and 413 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 275 B

After

Width:  |  Height:  |  Size: 282 B

View File

@ -233,8 +233,24 @@ static inline bool8 IsPartnerTrainerId(u16 trainerId)
static inline u16 SanitizeTrainerId(u16 trainerId)
{
if (trainerId >= TRAINERS_COUNT && !IsPartnerTrainerId(trainerId))
switch (trainerId)
{
case TRAINER_RECORD_MIXING_FRIEND:
case TRAINER_RECORD_MIXING_APPRENTICE:
case TRAINER_EREADER:
case TRAINER_FRONTIER_BRAIN:
case TRAINER_PLAYER:
case TRAINER_SECRET_BASE:
case TRAINER_LINK_OPPONENT:
case TRAINER_UNION_ROOM:
return TRAINER_NONE;
}
assertf(trainerId < TRAINERS_COUNT || IsPartnerTrainerId(trainerId), "invalid trainer: %d", trainerId)
{
return TRAINER_NONE;
}
return trainerId;
}

View File

@ -138,7 +138,10 @@ struct MoveInfo
union {
struct {
u16 stringId;
u16 status;
union {
u16 status;
u16 weather;
};
} twoTurnAttack;
struct {
u16 species;
@ -175,10 +178,12 @@ extern const struct BattleMoveEffect gBattleMoveEffects[];
static inline u32 SanitizeMoveId(u32 moveId)
{
if (moveId >= MOVES_COUNT_ALL)
assertf(moveId < MOVES_COUNT_ALL, "invalid move: %d", moveId)
{
return MOVE_NONE;
else
return moveId;
}
return moveId;
}
static inline const u8 *GetMoveName(u32 moveId)
@ -231,12 +236,16 @@ static inline u32 GetMovePP(u32 moveId)
static inline u32 GetMoveZEffect(u32 moveId)
{
return gMovesInfo[SanitizeMoveId(moveId)].zMove.effect;
moveId = SanitizeMoveId(moveId);
assertf(GetMoveCategory(moveId) == DAMAGE_CATEGORY_STATUS, "not a status move: %S", gMovesInfo[moveId].name);
return gMovesInfo[moveId].zMove.effect;
}
static inline u32 GetMoveZPowerOverride(u32 moveId)
{
return gMovesInfo[SanitizeMoveId(moveId)].zMove.powerOverride;
moveId = SanitizeMoveId(moveId);
assertf(GetMoveCategory(moveId) != DAMAGE_CATEGORY_STATUS, "not a damaging move: %S", gMovesInfo[moveId].name);
return gMovesInfo[moveId].zMove.powerOverride;
}
static inline s32 GetMovePriority(u32 moveId)
@ -496,17 +505,26 @@ static inline bool32 IsValidApprenticeMove(u32 moveId)
static inline u32 GetMoveTwoTurnAttackStringId(u32 moveId)
{
return gMovesInfo[SanitizeMoveId(moveId)].argument.twoTurnAttack.stringId;
moveId = SanitizeMoveId(moveId);
enum BattleMoveEffects effect = gMovesInfo[moveId].effect;
assertf(gBattleMoveEffects[effect].twoTurnEffect, "not a two-turn move: %S", gMovesInfo[moveId].name);
return gMovesInfo[moveId].argument.twoTurnAttack.stringId;
}
static inline u32 GetMoveTwoTurnAttackStatus(u32 moveId)
{
return gMovesInfo[SanitizeMoveId(moveId)].argument.twoTurnAttack.status;
moveId = SanitizeMoveId(moveId);
enum BattleMoveEffects effect = gMovesInfo[moveId].effect;
assertf(effect == EFFECT_SEMI_INVULNERABLE || effect == EFFECT_SKY_DROP, "not a two-turn move with status: %S", gMovesInfo[moveId].name);
return gMovesInfo[moveId].argument.twoTurnAttack.status;
}
static inline u32 GetMoveTwoTurnAttackWeather(u32 moveId)
{
return gMovesInfo[SanitizeMoveId(moveId)].argument.twoTurnAttack.status;
moveId = SanitizeMoveId(moveId);
enum BattleMoveEffects effect = gMovesInfo[moveId].effect;
assertf(effect == EFFECT_TWO_TURNS_ATTACK || effect == EFFECT_SOLAR_BEAM, "not a two-turn move with weather: %S", gMovesInfo[moveId].name);
return gMovesInfo[moveId].argument.twoTurnAttack.weather;
}
static inline u32 GetMoveSpeciesPowerOverride_Species(u32 moveId)
@ -526,42 +544,60 @@ static inline u32 GetMoveSpeciesPowerOverride_NumOfHits(u32 moveId)
static inline enum ProtectMethod GetMoveProtectMethod(u32 moveId)
{
return gMovesInfo[SanitizeMoveId(moveId)].argument.protectMethod;
}
static inline u32 GetMoveTerrainFlag(u32 moveId)
{
return gMovesInfo[SanitizeMoveId(moveId)].argument.moveProperty;
moveId = SanitizeMoveId(moveId);
enum BattleMoveEffects effect = gMovesInfo[moveId].effect;
assertf(effect == EFFECT_PROTECT || effect == EFFECT_ENDURE, "not a protect move: %S", gMovesInfo[moveId].name);
return gMovesInfo[moveId].argument.protectMethod;
}
static inline u32 GetMoveEffectArg_Status(u32 moveId)
{
return gMovesInfo[SanitizeMoveId(moveId)].argument.status;
// Forward-declared here because 'include/battle_util.h' includes
// this file.
extern bool32 MoveHasAdditionalEffect(u32 move, u32 moveEffect);
moveId = SanitizeMoveId(moveId);
enum BattleMoveEffects effect = gMovesInfo[moveId].effect;
assertf(effect == EFFECT_FOCUS_ENERGY || effect == EFFECT_DOUBLE_POWER_ON_ARG_STATUS || MoveHasAdditionalEffect(moveId, MOVE_EFFECT_REMOVE_STATUS), "not a move with status: %S", gMovesInfo[moveId].name);
return gMovesInfo[moveId].argument.status;
}
static inline u32 GetMoveEffectArg_MoveProperty(u32 moveId)
{
moveId = SanitizeMoveId(moveId);
enum BattleMoveEffects effect = gMovesInfo[moveId].effect;
assertf(effect == EFFECT_FIRST_TURN_ONLY || effect == EFFECT_HEAL_PULSE, "not a move with moveProperty: %S", gMovesInfo[moveId].name);
return gMovesInfo[SanitizeMoveId(moveId)].argument.moveProperty;
}
static inline u32 GetMoveEffectArg_HoldEffect(u32 moveId)
{
return gMovesInfo[SanitizeMoveId(moveId)].argument.holdEffect;
moveId = SanitizeMoveId(moveId);
enum BattleMoveEffects effect = gMovesInfo[moveId].effect;
assertf(effect == EFFECT_CHANGE_TYPE_ON_ITEM, "not a move with a hold effect: %S", gMovesInfo[moveId].name);
return gMovesInfo[moveId].argument.holdEffect;
}
static inline u32 GetMoveArgType(u32 moveId)
{
return gMovesInfo[SanitizeMoveId(moveId)].argument.type;
moveId = SanitizeMoveId(moveId);
enum BattleMoveEffects effect = gMovesInfo[moveId].effect;
assertf(effect == EFFECT_SOAK || effect == EFFECT_TWO_TYPED_MOVE || effect == EFFECT_THIRD_TYPE || effect == EFFECT_SUPER_EFFECTIVE_ON_ARG || effect == EFFECT_FAIL_IF_NOT_ARG_TYPE, "not a move with a type: %S", gMovesInfo[moveId].name);
return gMovesInfo[moveId].argument.type;
}
static inline u32 GetMoveFixedHPDamage(u32 moveId)
{
return gMovesInfo[SanitizeMoveId(moveId)].argument.fixedDamage;
moveId = SanitizeMoveId(moveId);
assertf(gMovesInfo[moveId].effect == EFFECT_FIXED_HP_DAMAGE, "not a fixed-damage move: %S", gMovesInfo[moveId].name);
return gMovesInfo[moveId].argument.fixedDamage;
}
static inline u32 GetMoveAbsorbPercentage(u32 moveId)
{
moveId = SanitizeMoveId(moveId);
enum BattleMoveEffects effect = gMovesInfo[moveId].effect;
assertf(effect == EFFECT_ABSORB || effect == EFFECT_DREAM_EATER, "not an absorbing move: %S", gMovesInfo[moveId].name);
if (gMovesInfo[moveId].argument.absorbPercentage == 0)
return 50;
return gMovesInfo[moveId].argument.absorbPercentage;
@ -569,13 +605,15 @@ static inline u32 GetMoveAbsorbPercentage(u32 moveId)
static inline u32 GetMoveRecoil(u32 moveId)
{
return gMovesInfo[SanitizeMoveId(moveId)].argument.recoilPercentage;
moveId = SanitizeMoveId(moveId);
assertf(gMovesInfo[moveId].effect == EFFECT_RECOIL, "not a recoil move: %S", gMovesInfo[moveId].name);
return gMovesInfo[moveId].argument.recoilPercentage;
}
static inline u32 GetMoveNonVolatileStatus(u32 move)
{
move = SanitizeMoveId(move);
switch(GetMoveEffect(move))
switch (GetMoveEffect(move))
{
case EFFECT_NON_VOLATILE_STATUS:
case EFFECT_YAWN:
@ -588,12 +626,16 @@ static inline u32 GetMoveNonVolatileStatus(u32 move)
static inline u32 GetMoveDamagePercentage(u32 move)
{
return gMovesInfo[SanitizeMoveId(move)].argument.damagePercentage;
move = SanitizeMoveId(move);
assertf(gMovesInfo[move].effect == EFFECT_FIXED_PERCENT_DAMAGE, "not a percentage-damage move: %S", gMovesInfo[move].name);
return gMovesInfo[move].argument.damagePercentage;
}
static inline u32 GetMoveOverwriteAbility(u32 move)
{
return gMovesInfo[SanitizeMoveId(move)].argument.overwriteAbility;
move = SanitizeMoveId(move);
assertf(gMovesInfo[move].effect == EFFECT_OVERWRITE_ABILITY, "not a move that overwrites abilities: %S", gMovesInfo[move].name);
return gMovesInfo[move].argument.overwriteAbility;
}
static inline const struct AdditionalEffect *GetMoveAdditionalEffectById(u32 moveId, u32 effect)
@ -624,9 +666,8 @@ static inline u32 GetMoveContestComboMoves(u32 moveId, u32 comboMove)
static inline const u8 *GetMoveAnimationScript(u32 moveId)
{
moveId = SanitizeMoveId(moveId);
if (gMovesInfo[moveId].battleAnimScript == NULL)
assertf(gMovesInfo[moveId].battleAnimScript, "No animation for %S", gMovesInfo[moveId].name)
{
DebugPrintfLevel(MGBA_LOG_WARN, "No animation for moveId=%u", moveId);
return gMovesInfo[MOVE_NONE].battleAnimScript;
}
return gMovesInfo[moveId].battleAnimScript;
@ -635,9 +676,8 @@ static inline const u8 *GetMoveAnimationScript(u32 moveId)
static inline const u8 *GetMoveBattleScript(u32 moveId)
{
moveId = SanitizeMoveId(moveId);
if (gBattleMoveEffects[GetMoveEffect(moveId)].battleScript == NULL)
assertf(gBattleMoveEffects[GetMoveEffect(moveId)].battleScript, "No battle script for %S", gMovesInfo[moveId].name)
{
DebugPrintfLevel(MGBA_LOG_WARN, "No effect for moveId=%u", moveId);
return gBattleMoveEffects[EFFECT_PLACEHOLDER].battleScript;
}
return gBattleMoveEffects[GetMoveEffect(moveId)].battleScript;

View File

@ -25,7 +25,6 @@ void TestRunner_Battle_CheckSwitch(u32 battlerId, u32 partyIndex);
void TestRunner_Battle_CheckAiMoveScores(u32 battlerId);
void TestRunner_Battle_AISetScore(const char *file, u32 line, u32 battlerId, u32 moveIndex, s32 score);
void TestRunner_Battle_AIAdjustScore(const char *file, u32 line, u32 battlerId, u32 moveIndex, s32 score);
void TestRunner_Battle_InvalidNoHPMon(u32 battlerId, u32 partyIndex);
void TestRunner_CheckMemory(void);
void TestRunner_Battle_CheckBattleRecordActionType(u32 battlerId, u32 recordIndex, u32 actionType);
@ -48,7 +47,6 @@ u32 TestRunner_Battle_GetForcedEnvironment(void);
#define TestRunner_Battle_CheckAiMoveScores(...) (void)0
#define TestRunner_Battle_AISetScore(...) (void)0
#define TestRunner_Battle_AIAdjustScore(...) (void)0
#define TestRunner_Battle_InvalidNoHPMon(...) (void)0
#define TestRunner_Battle_CheckBattleRecordActionType(...) (void)0

View File

@ -880,9 +880,7 @@ static void Cmd_end(void)
if (!continuousAnim) // May have been used for debug?
{
// Debugging - ensure no hanging mon bg tasks
if (FuncIsActiveTask(Task_UpdateMonBg))
DebugPrintf("Move %d animation still has Task_UpdateMonBg active at the end!", gAnimMoveIndex);
assertf(!FuncIsActiveTask(Task_UpdateMonBg), "move %d still has Task_UpdateMonBg active at the end", gAnimMoveIndex);
m4aMPlayVolumeControl(&gMPlayInfo_BGM, TRACKS_ALL, 256);
if (!IsContest())

View File

@ -1961,8 +1961,7 @@ u8 CreateNPCTrainerPartyFromTrainer(struct Pokemon *party, const struct Trainer
if (speciesInfo->abilities[abilityNum] == partyData[monIndex].ability)
break;
}
if (abilityNum >= maxAbilityNum)
abilityNum = 0;
assertf(abilityNum < maxAbilityNum, "illegal ability %S for %S", gAbilitiesInfo[partyData[monIndex].ability], speciesInfo->speciesName);
}
else if (B_TRAINER_MON_RANDOM_ABILITY)
{

View File

@ -956,22 +956,11 @@ static const struct PickupItem sPickupTable[] =
static void ValidateSavedBattlerCounts(void)
{
if (gBattleStruct->savedAttackerCount > 0)
{
if (TESTING)
{
Test_ExitWithResult(TEST_RESULT_ERROR, 0, "savedAttackerCount is greater than 0! More calls to SaveBattlerAttacker than RestoreBattlerAttacker!", __FILE__, __LINE__);
}
else
DebugPrintfLevel(MGBA_LOG_WARN, "savedAttackerCount is greater than 0! More calls to SaveBattlerAttacker than RestoreBattlerAttacker!");
}
if (gBattleStruct->savedTargetCount > 0)
{
if (TESTING)
Test_ExitWithResult(TEST_RESULT_ERROR, 0, "savedTargetCount is greater than 0! More calls to SaveBattlerTarget than RestoreBattlerTarget!", __FILE__, __LINE__);
else
DebugPrintfLevel(MGBA_LOG_WARN, "savedTargetCount is greater than 0! More calls to SaveBattlerTarget than RestoreBattlerTarget!");
}
// More calls to SaveBattlerAttacker than RestoreBattlerAttacker.
assertf(gBattleStruct->savedAttackerCount == 0, "savedAttackerCount is greater than 0");
// More calls to SaveBattlerTarget than RestoreBattlerTarget.
assertf(gBattleStruct->savedTargetCount == 0, "savedTargetCount is greater than 0");
}
static bool32 NoTargetPresent(u8 battler, u32 move)
@ -1145,6 +1134,8 @@ static inline bool32 IsBattlerUsingBeakBlast(u32 battler)
static void Cmd_attackcanceler(void)
{
CMD_ARGS();
assertf(gBattlerAttacker < gBattlersCount, "invalid gBattlerAttacker: %d", gBattlerAttacker);
assertf(gBattlerTarget < gBattlersCount, "invalid gBattlerTarget: %d", gBattlerTarget);
if (gBattleStruct->battlerState[gBattlerAttacker].usedEjectItem)
{
@ -5862,6 +5853,8 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect)
static void Cmd_moveend(void)
{
CMD_ARGS(u8 endMode, u8 endState);
assertf(gBattlerAttacker < gBattlersCount, "invalid gBattlerAttacker: %d", gBattlerAttacker);
assertf(gBattlerTarget < gBattlersCount, "invalid gBattlerTarget: %d", gBattlerTarget);
s32 i;
bool32 effect = FALSE;
@ -6878,6 +6871,8 @@ static void Cmd_moveend(void)
gBattleScripting.moveendState++;
break;
case MOVEEND_CLEAR_BITS: // Clear/Set bits for things like using a move for all targets and all hits.
assertf(gBattlerAttacker < gBattlersCount, "invalid gBattlerAttacker: %d", gBattlerAttacker);
assertf(gBattlerTarget < gBattlersCount, "invalid gBattlerTarget: %d", gBattlerTarget);
if (gSpecialStatuses[gBattlerAttacker].instructedChosenTarget)
gBattleStruct->moveTarget[gBattlerAttacker] = gSpecialStatuses[gBattlerAttacker].instructedChosenTarget & 0x3;
if (gSpecialStatuses[gBattlerAttacker].dancerOriginalTarget)
@ -7049,6 +7044,36 @@ static void Cmd_returnatktoball(void)
gBattlescriptCurrInstr = cmd->nextInstr;
}
static bool32 IsValidSwitchIn(enum BattleSide side, u32 index)
{
if (index >= PARTY_SIZE)
return FALSE;
struct Pokemon *party = GetSideParty(side);
if (!IsValidForBattle(&party[index]))
return FALSE;
for (u32 i = 0; i < gBattlersCount; i++)
{
if (GetBattlerSide(i) == side && gBattlerPartyIndexes[i] == index && IsBattlerAlive(i))
return FALSE;
}
return TRUE;
}
static u32 GetArbitraryValidSwitchIn(enum BattleSide side)
{
for (u32 i = 0; i < PARTY_SIZE; i++)
{
if (IsValidSwitchIn(side, i))
return i;
}
assertf(FALSE, "no valid switch ins for side: %d", side);
return 0;
}
static void Cmd_getswitchedmondata(void)
{
CMD_ARGS(u8 battler);
@ -7057,10 +7082,11 @@ static void Cmd_getswitchedmondata(void)
if (gBattleControllerExecFlags)
return;
if (TESTING
&& gBattlerPartyIndexes[battler] == gBattleStruct->monToSwitchIntoId[battler]
&& IsBattlerAlive(battler))
Test_ExitWithResult(TEST_RESULT_ERROR, 0, ":L:%s:%d: battler is trying to switch to themself", __FILE__, __LINE__);
enum BattleSide side = GetBattlerSide(battler);
assertf(IsValidSwitchIn(side, gBattleStruct->monToSwitchIntoId[battler]))
{
gBattleStruct->monToSwitchIntoId[battler] = GetArbitraryValidSwitchIn(side);
}
gBattlerPartyIndexes[battler] = gBattleStruct->monToSwitchIntoId[battler];
@ -7088,33 +7114,13 @@ static void Cmd_switchindataupdate(void)
for (i = 0; i < sizeof(struct BattlePokemon); i++)
monData[i] = gBattleResources->bufferB[battler][4 + i];
// Edge case: the sent out pokemon has 0 HP. This should never happen.
if (!IsBattlerAlive(battler))
enum BattleSide side = GetBattlerSide(battler);
assertf(IsBattlerAlive(battler))
{
// If it's a test, mark it as invalid.
if (gTestRunnerEnabled)
{
TestRunner_Battle_InvalidNoHPMon(battler, gBattlerPartyIndexes[battler]);
}
// Handle in-game scenario.
else
{
struct Pokemon *party = GetBattlerParty(battler);
// Find the first possible replacement for the not valid pokemon.
for (i = 0; i < PARTY_SIZE; i++)
{
if (IsValidForBattle(&party[i]))
break;
}
// There is valid replacement.
if (i != PARTY_SIZE)
{
gBattlerPartyIndexes[battler] = gBattleStruct->monToSwitchIntoId[battler] = i;
BtlController_EmitGetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_ALL_BATTLE, 1u << gBattlerPartyIndexes[battler]);
MarkBattlerForControllerExec(battler);
return;
}
}
gBattlerPartyIndexes[battler] = gBattleStruct->monToSwitchIntoId[battler] = GetArbitraryValidSwitchIn(side);
BtlController_EmitGetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_ALL_BATTLE, 1u << gBattlerPartyIndexes[battler]);
MarkBattlerForControllerExec(battler);
return;
}
gBattleMons[battler].types[0] = GetSpeciesType(gBattleMons[battler].species, 0);
@ -9243,12 +9249,14 @@ static void Cmd_unused_0x78(void)
static void TryResetProtectUseCounter(u32 battler)
{
u32 lastMove = gLastResultingMoves[battler];
enum BattleMoveEffects lastEffect = GetMoveEffect(lastMove);
if (lastMove == MOVE_UNAVAILABLE)
{
gBattleMons[battler].volatiles.protectUses = 0;
return;
}
else if (!gBattleMoveEffects[lastEffect].usesProtectCounter)
enum BattleMoveEffects lastEffect = GetMoveEffect(lastMove);
if (!gBattleMoveEffects[lastEffect].usesProtectCounter)
{
if (GetConfig(CONFIG_ALLY_SWITCH_FAIL_CHANCE) < GEN_9 || lastEffect != EFFECT_ALLY_SWITCH)
gBattleMons[battler].volatiles.protectUses = 0;
@ -13962,18 +13970,22 @@ static void Cmd_callnative(void)
void SaveBattlerTarget(u32 battler)
{
if (gBattleStruct->savedTargetCount < NELEMS(gBattleStruct->savedBattlerTarget))
gBattleStruct->savedBattlerTarget[gBattleStruct->savedTargetCount++] = battler;
else
DebugPrintfLevel(MGBA_LOG_WARN, "Attempting to exceed savedBattlerTarget array size!");
assertf(gBattleStruct->savedTargetCount < ARRAY_COUNT(gBattleStruct->savedBattlerTarget), "Too many savedBattlerTargets")
{
return;
}
gBattleStruct->savedBattlerTarget[gBattleStruct->savedTargetCount++] = battler;
}
void SaveBattlerAttacker(u32 battler)
{
if (gBattleStruct->savedAttackerCount < NELEMS(gBattleStruct->savedBattlerAttacker))
gBattleStruct->savedBattlerAttacker[gBattleStruct->savedAttackerCount++] = battler;
else
DebugPrintfLevel(MGBA_LOG_WARN, "Attempting to exceed savedBattlerAttacker array size!");
assertf(gBattleStruct->savedAttackerCount < ARRAY_COUNT(gBattleStruct->savedBattlerAttacker), "Too many savedBattlerAttackers")
{
return;
}
gBattleStruct->savedBattlerAttacker[gBattleStruct->savedAttackerCount++] = battler;
}
void BS_SaveTarget(void)
@ -13986,19 +13998,15 @@ void BS_SaveTarget(void)
void BS_RestoreTarget(void)
{
NATIVE_ARGS();
if (gBattleStruct->savedTargetCount > 0)
assertf(gBattleStruct->savedTargetCount > 0, "No savedBattlerTargets")
{
gBattleStruct->savedTargetCount--;
gBattlerTarget = gBattleStruct->savedBattlerTarget[gBattleStruct->savedTargetCount];
}
else
{
// #if TESTING
// Test_ExitWithResult(TEST_RESULT_ERROR, "BS_RestoreTarget attempting to restore an empty target!");
// #else
DebugPrintfLevel(MGBA_LOG_WARN, "BS_RestoreTarget attempting to restore an empty target!");
// #endif
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
gBattleStruct->savedTargetCount--;
gBattlerTarget = gBattleStruct->savedBattlerTarget[gBattleStruct->savedTargetCount];
gBattlescriptCurrInstr = cmd->nextInstr;
}
@ -14012,19 +14020,14 @@ void BS_SaveAttacker(void)
void BS_RestoreAttacker(void)
{
NATIVE_ARGS();
if (gBattleStruct->savedAttackerCount > 0)
assertf(gBattleStruct->savedAttackerCount > 0, "No savedBattlerAttackers")
{
gBattleStruct->savedAttackerCount--;
gBattlerAttacker = gBattleStruct->savedBattlerAttacker[gBattleStruct->savedAttackerCount];
}
else
{
// #if TESTING
// Test_ExitWithResult(TEST_RESULT_ERROR, "BS_RestoreAttacker attempting to restore an empty attacker!");
// #else
DebugPrintfLevel(MGBA_LOG_WARN, "BS_RestoreAttacker attempting to restore an empty attacker!");
// #endif
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
gBattleStruct->savedAttackerCount--;
gBattlerAttacker = gBattleStruct->savedBattlerAttacker[gBattleStruct->savedAttackerCount];
gBattlescriptCurrInstr = cmd->nextInstr;
}

View File

@ -282,6 +282,9 @@ bool32 EndOrContinueWeather(void)
static u32 CalcBeatUpPower(void)
{
u32 species = gBattleStruct->beatUpSpecies[gBattleStruct->beatUpSlot++];
// FIXME: Why call CalcBeatUpPower when 'beatUpSlot' is OOB?
if (species == 0xFFFF)
return 0;
return (GetSpeciesBaseAttack(species) / 10) + 5;
}
@ -10517,7 +10520,7 @@ bool32 MoveHasAdditionalEffectSelf(u32 move, u32 moveEffect)
bool32 IsMoveEffectRemoveSpeciesType(u32 move, u32 moveEffect, u32 argument)
{
return (GetMoveArgType(move) == argument) && MoveHasAdditionalEffectSelf(move, moveEffect);
return (MoveHasAdditionalEffectSelf(move, moveEffect) && GetMoveArgType(move) == argument);
}
bool32 MoveHasChargeTurnAdditionalEffect(u32 move)

View File

@ -5327,15 +5327,21 @@ static void Task_WaitForSliderHeartAnim(u8 taskId)
static u16 SanitizeMove(u16 move)
{
if (move >= MOVES_COUNT)
move = MOVE_POUND;
assertf(move < MOVES_COUNT, "invalid move: %d", move)
{
return MOVE_POUND;
}
return move;
}
static u16 SanitizeSpecies(u16 species)
{
if (species >= NUM_SPECIES)
species = SPECIES_NONE;
assertf(species < NUM_SPECIES, "invalid species: %d", species)
{
return SPECIES_NONE;
}
return species;
}

View File

@ -2118,7 +2118,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] =
.category = DAMAGE_CATEGORY_SPECIAL,
.sleepTalkBanned = TRUE,
.instructBanned = TRUE,
.argument.twoTurnAttack = { .stringId = STRINGID_PKMNTOOKSUNLIGHT, .status = B_WEATHER_SUN },
.argument.twoTurnAttack = { .stringId = STRINGID_PKMNTOOKSUNLIGHT, .weather = B_WEATHER_SUN },
.contestEffect = C_UPDATED_MOVE_EFFECTS >= GEN_6 ? CONTEST_EFFECT_AFFECTED_BY_PREV_APPEAL : CONTEST_EFFECT_HIGHLY_APPEALING,
.contestCategory = CONTEST_CATEGORY_COOL,
.contestComboStarterId = 0,
@ -3757,6 +3757,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] =
.target = MOVE_TARGET_SELECTED,
.priority = 0,
.category = DAMAGE_CATEGORY_SPECIAL,
.argument = { .absorbPercentage = 50 },
.healingMove = B_HEAL_BLOCKING >= GEN_6,
.contestEffect = CONTEST_EFFECT_STARTLE_PREV_MONS,
.contestCategory = CONTEST_CATEGORY_SMART,
@ -5517,6 +5518,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] =
.priority = 2,
#endif
.category = DAMAGE_CATEGORY_STATUS,
.argument = { .protectMethod = PROTECT_NONE },
.zMove = { .effect = Z_EFFECT_RESET_STATS },
.ignoresProtect = TRUE,
.mirrorMoveBanned = TRUE,
@ -16277,7 +16279,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] =
.slicingMove = TRUE,
.sleepTalkBanned = TRUE,
.instructBanned = TRUE,
.argument.twoTurnAttack = { .stringId = STRINGID_PKMNTOOKSUNLIGHT, .status = B_WEATHER_SUN },
.argument.twoTurnAttack = { .stringId = STRINGID_PKMNTOOKSUNLIGHT, .weather = B_WEATHER_SUN },
.contestEffect = CONTEST_EFFECT_HIGHLY_APPEALING,
.contestCategory = CONTEST_CATEGORY_TOUGH,
.contestComboStarterId = 0,
@ -21026,7 +21028,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] =
.target = MOVE_TARGET_SELECTED,
.priority = 0,
.category = DAMAGE_CATEGORY_SPECIAL,
.argument.twoTurnAttack = { .stringId = STRINGID_ELECTROSHOTCHARGING, .status = B_WEATHER_RAIN },
.argument.twoTurnAttack = { .stringId = STRINGID_ELECTROSHOTCHARGING, .weather = B_WEATHER_RAIN },
.additionalEffects = ADDITIONAL_EFFECTS({
.moveEffect = MOVE_EFFECT_SP_ATK_PLUS_1,
.self = TRUE,

View File

@ -1,193 +1,9 @@
#include "decompress_error_handler.h"
#include "global.h"
#include "data.h"
#include "menu.h"
#include "menu_helpers.h"
#include "malloc.h"
#include "palette.h"
#include "graphics.h"
#include "gpu_regs.h"
#include "bg.h"
#include "main.h"
#include "text_window.h"
#include "string_util.h"
#include "constants/rgb.h"
static EWRAM_DATA u32 sErrorAddress;
static EWRAM_DATA enum CompressionError sCompressionError;
static const struct BgTemplate sBgTemplates[3] =
{
{
.bg = 0,
.charBaseIndex = 2,
.mapBaseIndex = 31,
.screenSize = 0,
.paletteMode = 0,
.priority = 0,
.baseTile = 0,
},
{
.bg = 2,
.charBaseIndex = 0,
.mapBaseIndex = 14,
.screenSize = 0,
.paletteMode = 0,
.priority = 2,
.baseTile = 0,
},
{
.bg = 3,
.charBaseIndex = 0,
.mapBaseIndex = 15,
.screenSize = 0,
.paletteMode = 0,
.priority = 3,
.baseTile = 0,
},
};
static void DecompressErrorScreenTextPrint(u32 window, const u8 *text, u8 x, u8 y)
{
u8 color[3];
color[0] = TEXT_COLOR_TRANSPARENT;
color[1] = TEXT_DYNAMIC_COLOR_6;
color[2] = TEXT_COLOR_LIGHT_GRAY;
AddTextPrinterParameterized4(window, FONT_NORMAL, x * 8, y * 8 + 1, 0, 0, color, 0, text);
}
static void GetHexStringFromU32(u8 *str, u32 value)
{
str[0] = CHAR_0;
str[1] = CHAR_x;
str[10] = EOS;
for (u32 i = 0; i < 8; i++)
{
u8 currChar = 0;
switch ((value >> (4*i)) & 0xF)
{
case 0:
currChar = CHAR_0;
break;
case 1:
currChar = CHAR_1;
break;
case 2:
currChar = CHAR_2;
break;
case 3:
currChar = CHAR_3;
break;
case 4:
currChar = CHAR_4;
break;
case 5:
currChar = CHAR_5;
break;
case 6:
currChar = CHAR_6;
break;
case 7:
currChar = CHAR_7;
break;
case 8:
currChar = CHAR_8;
break;
case 9:
currChar = CHAR_9;
break;
case 10:
currChar = CHAR_A;
break;
case 11:
currChar = CHAR_B;
break;
case 12:
currChar = CHAR_C;
break;
case 13:
currChar = CHAR_D;
break;
case 14:
currChar = CHAR_E;
break;
case 15:
currChar = CHAR_F;
break;
}
u32 pos = 9 - i;
str[pos] = currChar;
}
}
static const struct WindowTemplate sTextWin =
{
.bg = 0,
.tilemapLeft = 3,
.tilemapTop = 2,
.width = 24,
.height = 16,
.paletteNum = 15,
.baseBlock = 1,
};
void DecompressionError_CB2(void)
{
if (sErrorAddress == 0)
return;
ResetVramOamAndBgCntRegs();
ResetAllBgsCoordinates();
FreeAllWindowBuffers();
SetGpuReg(REG_OFFSET_DISPCNT, 0);
SetGpuReg(REG_OFFSET_BLDCNT, 0);
SetGpuReg(REG_OFFSET_BG0CNT, 0);
SetGpuReg(REG_OFFSET_BG0HOFS, 0);
SetGpuReg(REG_OFFSET_BG0VOFS, 0);
DmaFill16(3, 0, VRAM, VRAM_SIZE);
DmaFill32(3, 0, OAM, OAM_SIZE);
DmaFill16(3, 0, PLTT, PLTT_SIZE);
ResetBgsAndClearDma3BusyFlags(0);
InitBgsFromTemplates(0, sBgTemplates, ARRAY_COUNT(sBgTemplates));
LoadBgTiles(0, gTextWindowFrame1_Gfx, 0x120, 0x214);
DeactivateAllTextPrinters();
ResetTasks();
ResetPaletteFade();
LoadPalette(gTextWindowFrame1_Pal, 0xE0, 0x20);
LoadPalette(gStandardMenuPalette, 0xF0, 0x20);
u32 window = AddWindow(&sTextWin);
DrawStdFrameWithCustomTileAndPalette(window, TRUE, 0x214, 0xE);
static const u8 romCheckFailMessage[] =_(
"{COLOR RED}ERROR! {COLOR DARK_GRAY}Decompression Failed!\n"
"\n"
"Address:\n"
"Error:\n");
DecompressErrorScreenTextPrint(window, romCheckFailMessage, 1, 0);
u8 addressStr[11];
u8 errorStr[11];
GetHexStringFromU32(addressStr, sErrorAddress);
GetHexStringFromU32(errorStr, sCompressionError);
DecompressErrorScreenTextPrint(window, addressStr, 7, 4);
DecompressErrorScreenTextPrint(window, errorStr, 7, 6);
TransferPlttBuffer();
*(u16*)PLTT = RGB(17, 18, 31);
ShowBg(0);
sErrorAddress = 0;
// This loop is apparently needed to prevent the game from doing
// stupid stuff with data it couldn't decompress
while(TRUE)
sCompressionError++;
}
#include "decompress_error_handler.h"
void DecompressionError(const u32 *src, enum CompressionError error)
{
sErrorAddress = (u32)src;
sCompressionError = error;
SetMainCallback2(DecompressionError_CB2);
DecompressionError_CB2();
assertf(0, "Decompression failed.\nAddress: 0x%p\nError: 0x%x\n", src, error);
}
void DoDecompressionError(void)

View File

@ -2314,12 +2314,13 @@ static void DexNavGuiInit(MainCallback callback)
void Task_OpenDexNavFromStartMenu(u8 taskId)
{
if (DEXNAV_ENABLED == FALSE)
{ // must have it enabled to enter
DebugPrintfLevel(MGBA_LOG_ERROR, "DexNav was opened when DEXNAV_ENABLED config was disabled! Check include/config/dexnav.h");
assertf(DEXNAV_ENABLED, "DexNav was opened when DEXNAV_ENABLED config was disabled! Check include/config/dexnav.h")
{
DestroyTask(taskId);
return;
}
else if (!gPaletteFade.active)
if (!gPaletteFade.active)
{
CleanupOverworldWindowsAndTilemaps();
DexNavGuiInit(CB2_ReturnToFieldWithOpenMenu);

View File

@ -640,7 +640,7 @@ static void SetPositionFromConnection(const struct MapConnection *connection, in
gSaveBlock1Ptr->pos.y = mapHeader->mapLayout->height;
break;
default:
DebugPrintfLevel(MGBA_LOG_WARN, "SetPositionFromConnection was passed an invalid direction (%d)!", direction);
assertf(0, "invalid direction: %d", direction);
break;
}
}
@ -664,23 +664,21 @@ bool8 CameraMove(int x, int y)
old_x = gSaveBlock1Ptr->pos.x;
old_y = gSaveBlock1Ptr->pos.y;
connection = GetIncomingConnection(direction, gSaveBlock1Ptr->pos.x, gSaveBlock1Ptr->pos.y);
if (connection)
assertf(connection)
{
SetPositionFromConnection(connection, direction, x, y);
LoadMapFromCameraTransition(connection->mapGroup, connection->mapNum);
gCamera.active = TRUE;
gCamera.x = old_x - gSaveBlock1Ptr->pos.x;
gCamera.y = old_y - gSaveBlock1Ptr->pos.y;
gSaveBlock1Ptr->pos.x += x;
gSaveBlock1Ptr->pos.y += y;
MoveMapViewToBackup(direction);
}
else
{
DebugPrintfLevel(MGBA_LOG_WARN, "GetIncomingConnection returned an invalid connection inside CameraMove!");
return gCamera.active;
}
SetPositionFromConnection(connection, direction, x, y);
LoadMapFromCameraTransition(connection->mapGroup, connection->mapNum);
gCamera.active = TRUE;
gCamera.x = old_x - gSaveBlock1Ptr->pos.x;
gCamera.y = old_y - gSaveBlock1Ptr->pos.y;
gSaveBlock1Ptr->pos.x += x;
gSaveBlock1Ptr->pos.y += y;
MoveMapViewToBackup(direction);
}
return gCamera.active;
}

View File

@ -792,10 +792,12 @@ bool32 RemovePyramidBagItem(u16 itemId, u16 count)
static u16 SanitizeItemId(u16 itemId)
{
if (itemId >= ITEMS_COUNT)
assertf(itemId < ITEMS_COUNT, "invalid item: %d", itemId)
{
return ITEM_NONE;
else
return itemId;
}
return itemId;
}
const u8 *GetItemName(u16 itemId)

View File

@ -81,9 +81,9 @@ void *AllocInternal(void *heapStart, u32 size, const char *location)
}
}
#if TESTING
if (pos->next == head)
{
#if TESTING
const struct MemBlock *head = HeapHead();
const struct MemBlock *block = head;
do
@ -99,13 +99,10 @@ void *AllocInternal(void *heapStart, u32 size, const char *location)
block = block->next;
}
while (block != head);
Test_ExitWithResult(TEST_RESULT_ERROR, SourceLine(0), ":L%s:%d, %s: OOM allocating %d bytes", gTestRunnerState.test->filename, SourceLine(0), location, size);
}
#endif
if (location)
{
DebugPrintfLevel(MGBA_LOG_ERROR, "%s: out of memory trying to allocate %d bytes", location, size);
}
AGB_ASSERT(FALSE);
assertf(pos->next != head, "%s: out of memory trying to allocate %d bytes", location, size)
{
return NULL;
}

View File

@ -534,37 +534,33 @@ void LoadSaveblockObjEventScripts(void)
savObjTemplates[i].script = mapHeaderObjTemplates[i].script;
}
static struct ObjectEventTemplate *GetObjectEventTemplate(u8 localId)
{
for (u32 i = 0; i < OBJECT_EVENT_TEMPLATES_COUNT; i++)
{
if (gSaveBlock1Ptr->objectEventTemplates[i].localId == localId)
return &gSaveBlock1Ptr->objectEventTemplates[i];
}
assertf(0, "no object event template for localId %d", localId);
return NULL;
}
void SetObjEventTemplateCoords(u8 localId, s16 x, s16 y)
{
s32 i;
struct ObjectEventTemplate *savObjTemplates = gSaveBlock1Ptr->objectEventTemplates;
for (i = 0; i < OBJECT_EVENT_TEMPLATES_COUNT; i++)
struct ObjectEventTemplate *objectEventTemplate = GetObjectEventTemplate(localId);
if (objectEventTemplate)
{
struct ObjectEventTemplate *objectEventTemplate = &savObjTemplates[i];
if (objectEventTemplate->localId == localId)
{
objectEventTemplate->x = x;
objectEventTemplate->y = y;
return;
}
objectEventTemplate->x = x;
objectEventTemplate->y = y;
}
}
void SetObjEventTemplateMovementType(u8 localId, u8 movementType)
{
s32 i;
struct ObjectEventTemplate *savObjTemplates = gSaveBlock1Ptr->objectEventTemplates;
for (i = 0; i < OBJECT_EVENT_TEMPLATES_COUNT; i++)
{
struct ObjectEventTemplate *objectEventTemplate = &savObjTemplates[i];
if (objectEventTemplate->localId == localId)
{
objectEventTemplate->movementType = movementType;
return;
}
}
struct ObjectEventTemplate *objectEventTemplate = GetObjectEventTemplate(localId);
if (objectEventTemplate)
objectEventTemplate->movementType = movementType;
}
static void InitMapView(void)

View File

@ -1227,31 +1227,66 @@ void CreateMonWithNature(struct Pokemon *mon, u16 species, u8 level, u8 fixedIV,
CreateMon(mon, species, level, fixedIV, TRUE, personality, OT_ID_PLAYER_ID, 0);
}
static bool32 GenderRatioCanBe(u32 genderRatio, u32 gender)
{
switch (gender)
{
case MON_MALE:
return genderRatio != MON_FEMALE && genderRatio != MON_GENDERLESS;
case MON_FEMALE:
return genderRatio != MON_MALE && genderRatio != MON_GENDERLESS;
case MON_GENDERLESS:
return genderRatio == MON_GENDERLESS;
default:
assertf(FALSE, "unknown gender: %d", gender);
return FALSE;
}
}
void CreateMonWithGenderNatureLetter(struct Pokemon *mon, u16 species, u8 level, u8 fixedIV, u8 gender, u8 nature, u8 unownLetter)
{
u32 personality;
u32 genderRatio = gSpeciesInfo[species].genderRatio;
if ((u8)(unownLetter - 1) < NUM_UNOWN_FORMS)
{
u16 actualLetter;
do
while (TRUE)
{
personality = Random32();
actualLetter = GET_UNOWN_LETTER(personality);
assertf(GenderRatioCanBe(genderRatio, gender), "genderRatio %d can't be gender %d", genderRatio, gender)
{
break;
}
if (nature == GetNatureFromPersonality(personality)
&& gender == GetGenderFromSpeciesAndPersonality(species, personality)
&& actualLetter == unownLetter - 1)
{
break;
}
}
while (nature != GetNatureFromPersonality(personality)
|| gender != GetGenderFromSpeciesAndPersonality(species, personality)
|| actualLetter != unownLetter - 1);
}
else
{
do
while (TRUE)
{
personality = Random32();
assertf(GenderRatioCanBe(genderRatio, gender), "genderRatio %d can't be gender %d", genderRatio, gender)
{
break;
}
if (nature == GetNatureFromPersonality(personality)
&& gender == GetGenderFromSpeciesAndPersonality(species, personality))
{
break;
}
}
while (nature != GetNatureFromPersonality(personality)
|| gender != GetGenderFromSpeciesAndPersonality(species, personality));
}
CreateMon(mon, species, level, fixedIV, TRUE, personality, OT_ID_PLAYER_ID, 0);
@ -1262,13 +1297,22 @@ void CreateMaleMon(struct Pokemon *mon, u16 species, u8 level)
{
u32 personality;
u32 otId;
u32 genderRatio = gSpeciesInfo[species].genderRatio;
do
while (TRUE)
{
otId = Random32();
personality = Random32();
assertf(GenderRatioCanBe(genderRatio, MON_MALE), "genderRatio %d can't be MON_MALE", genderRatio)
{
break;
}
if (GetGenderFromSpeciesAndPersonality(species, personality) == MON_MALE)
break;
}
while (GetGenderFromSpeciesAndPersonality(species, personality) != MON_MALE);
CreateMon(mon, species, level, USE_RANDOM_IVS, TRUE, personality, OT_ID_PRESET, otId);
}
@ -7188,10 +7232,12 @@ bool32 TryFormChange(u32 monId, enum BattleSide side, enum FormChanges method)
u16 SanitizeSpeciesId(u16 species)
{
if (species > NUM_SPECIES || !IsSpeciesEnabled(species))
assertf(species <= NUM_SPECIES && (species == SPECIES_NONE || IsSpeciesEnabled(species)), "invalid species: %d", species)
{
return SPECIES_NONE;
else
return species;
}
return species;
}
bool32 IsSpeciesEnabled(u16 species)

View File

@ -173,14 +173,17 @@ const void *RandomElementArray(enum RandomTag tag, const void *array, size_t siz
u32 RandomUniformDefault(enum RandomTag tag, u32 lo, u32 hi)
{
assertf(lo <= hi);
return lo + (((hi - lo + 1) * Random()) >> 16);
}
u32 RandomUniformExceptDefault(enum RandomTag tag, u32 lo, u32 hi, bool32 (*reject)(u32))
{
assertf(lo <= hi);
LOOP_RANDOM_START;
while (TRUE)
{
// TODO: assertf to abort after too many iterations.
u32 n = lo + (((hi - lo + 1) * LOOP_RANDOM) >> 16);
if (!reject(n))
return n;
@ -190,6 +193,7 @@ u32 RandomUniformExceptDefault(enum RandomTag tag, u32 lo, u32 hi, bool32 (*reje
u32 RandomWeightedArrayDefault(enum RandomTag tag, u32 sum, u32 n, const u8 *weights)
{
assertf(n > 0);
s32 i, targetSum;
targetSum = (sum * Random()) >> 16;
for (i = 0; i < n - 1; i++)
@ -203,6 +207,7 @@ u32 RandomWeightedArrayDefault(enum RandomTag tag, u32 sum, u32 n, const u8 *wei
const void *RandomElementArrayDefault(enum RandomTag tag, const void *array, size_t size, size_t count)
{
assertf(count > 0);
return (const u8 *)array + size * RandomUniformDefault(tag, 0, count - 1);
}

View File

@ -162,7 +162,11 @@ void ScriptJump(struct ScriptContext *ctx, const u8 *ptr)
void ScriptCall(struct ScriptContext *ctx, const u8 *ptr)
{
ScriptPush(ctx, ctx->scriptPtr);
bool32 failed = ScriptPush(ctx, ctx->scriptPtr);
assertf(!failed, "could not push %p", ptr)
{
return;
}
ctx->scriptPtr = ptr;
}

View File

@ -243,9 +243,20 @@ void CanHyperTrain(struct ScriptContext *ctx)
Script_RequestEffects(SCREFF_V1);
if (stat < NUM_STATS
&& partyIndex < PARTY_SIZE
&& !GetMonData(&gPlayerParty[partyIndex], MON_DATA_HYPER_TRAINED_HP + stat)
assertf(stat < NUM_STATS, "invalid stat: %d", stat)
{
gSpecialVar_Result = FALSE;
return;
}
CalculatePlayerPartyCount();
assertf(partyIndex < gPlayerPartyCount, "invalid party index: %d", partyIndex)
{
gSpecialVar_Result = FALSE;
return;
}
if (!GetMonData(&gPlayerParty[partyIndex], MON_DATA_HYPER_TRAINED_HP + stat)
&& GetMonData(&gPlayerParty[partyIndex], MON_DATA_HP_IV + stat) < MAX_PER_STAT_IVS)
{
gSpecialVar_Result = TRUE;
@ -263,12 +274,20 @@ void HyperTrain(struct ScriptContext *ctx)
Script_RequestEffects(SCREFF_V1 | SCREFF_SAVE);
if (stat < NUM_STATS && partyIndex < PARTY_SIZE)
assertf(stat < NUM_STATS, "invalid stat: %d", stat)
{
bool32 data = TRUE;
SetMonData(&gPlayerParty[partyIndex], MON_DATA_HYPER_TRAINED_HP + stat, &data);
CalculateMonStats(&gPlayerParty[partyIndex]);
return;
}
CalculatePlayerPartyCount();
assertf(partyIndex < gPlayerPartyCount, "invalid party index: %d", partyIndex)
{
return;
}
bool32 data = TRUE;
SetMonData(&gPlayerParty[partyIndex], MON_DATA_HYPER_TRAINED_HP + stat, &data);
CalculateMonStats(&gPlayerParty[partyIndex]);
}
void HasGigantamaxFactor(struct ScriptContext *ctx)
@ -338,7 +357,6 @@ static u32 ScriptGiveMonParameterized(u8 side, u8 slot, u16 species, u8 level, u
int sentToPc;
struct Pokemon mon;
u32 i;
u8 genderRatio = gSpeciesInfo[species].genderRatio;
u16 targetSpecies;
bool32 isShiny;
@ -353,9 +371,9 @@ static u32 ScriptGiveMonParameterized(u8 side, u8 slot, u16 species, u8 level, u
}
// 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))
// TODO: Use another value for "any gender" so that we can report an
// error if genderless.
if (gender != MON_GENDERLESS)
CreateMonWithGenderNatureLetter(&mon, species, level, 32, gender, nature, 0);
else
CreateMonWithNature(&mon, species, level, 32, nature);
@ -434,36 +452,33 @@ static u32 ScriptGiveMonParameterized(u8 side, u8 slot, u16 species, u8 level, u
SetMonData(&mon, MON_DATA_OT_NAME, gSaveBlock2Ptr->playerName);
SetMonData(&mon, MON_DATA_OT_GENDER, &gSaveBlock2Ptr->playerGender);
if (slot < PARTY_SIZE)
if (side == B_SIDE_PLAYER)
{
if (side == 0)
if (slot < PARTY_SIZE)
{
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;
}
}
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)
@ -472,6 +487,15 @@ static u32 ScriptGiveMonParameterized(u8 side, u8 slot, u16 species, u8 level, u
GetSetPokedexFlag(nationalDexNum, FLAG_SET_CAUGHT);
}
}
else
{
assertf(slot < PARTY_SIZE, "invalid slot: %d", slot)
{
return MON_CANT_GIVE;
}
CopyMon(&gEnemyParty[slot], &mon, sizeof(struct Pokemon));
sentToPc = MON_GIVEN_TO_PARTY;
}
return sentToPc;
}

View File

@ -1113,12 +1113,6 @@ void TestRunner_Battle_CheckSwitch(u32 battlerId, u32 partyIndex)
DATA.trial.aiActionsPlayed[battlerId]++;
}
void TestRunner_Battle_InvalidNoHPMon(u32 battlerId, u32 partyIndex)
{
Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":L%s: INVALID: %s trying to send out a mon(id: %d) with 0 HP.",
gTestRunnerState.test->filename, BattlerIdentifier(battlerId), gBattlerPartyIndexes[battlerId]);
}
static bool32 CheckComparision(s32 val1, s32 val2, u32 cmp)
{
switch (cmp)