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) 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; return TRAINER_NONE;
}
assertf(trainerId < TRAINERS_COUNT || IsPartnerTrainerId(trainerId), "invalid trainer: %d", trainerId)
{
return TRAINER_NONE;
}
return trainerId; return trainerId;
} }

View File

@ -138,7 +138,10 @@ struct MoveInfo
union { union {
struct { struct {
u16 stringId; u16 stringId;
u16 status; union {
u16 status;
u16 weather;
};
} twoTurnAttack; } twoTurnAttack;
struct { struct {
u16 species; u16 species;
@ -175,10 +178,12 @@ extern const struct BattleMoveEffect gBattleMoveEffects[];
static inline u32 SanitizeMoveId(u32 moveId) static inline u32 SanitizeMoveId(u32 moveId)
{ {
if (moveId >= MOVES_COUNT_ALL) assertf(moveId < MOVES_COUNT_ALL, "invalid move: %d", moveId)
{
return MOVE_NONE; return MOVE_NONE;
else }
return moveId;
return moveId;
} }
static inline const u8 *GetMoveName(u32 moveId) static inline const u8 *GetMoveName(u32 moveId)
@ -231,12 +236,16 @@ static inline u32 GetMovePP(u32 moveId)
static inline u32 GetMoveZEffect(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) 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) static inline s32 GetMovePriority(u32 moveId)
@ -496,17 +505,26 @@ static inline bool32 IsValidApprenticeMove(u32 moveId)
static inline u32 GetMoveTwoTurnAttackStringId(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) 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) 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) 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) static inline enum ProtectMethod GetMoveProtectMethod(u32 moveId)
{ {
return gMovesInfo[SanitizeMoveId(moveId)].argument.protectMethod; moveId = SanitizeMoveId(moveId);
} enum BattleMoveEffects effect = gMovesInfo[moveId].effect;
assertf(effect == EFFECT_PROTECT || effect == EFFECT_ENDURE, "not a protect move: %S", gMovesInfo[moveId].name);
static inline u32 GetMoveTerrainFlag(u32 moveId) return gMovesInfo[moveId].argument.protectMethod;
{
return gMovesInfo[SanitizeMoveId(moveId)].argument.moveProperty;
} }
static inline u32 GetMoveEffectArg_Status(u32 moveId) 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) 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; return gMovesInfo[SanitizeMoveId(moveId)].argument.moveProperty;
} }
static inline u32 GetMoveEffectArg_HoldEffect(u32 moveId) 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) 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) 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) static inline u32 GetMoveAbsorbPercentage(u32 moveId)
{ {
moveId = SanitizeMoveId(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) if (gMovesInfo[moveId].argument.absorbPercentage == 0)
return 50; return 50;
return gMovesInfo[moveId].argument.absorbPercentage; return gMovesInfo[moveId].argument.absorbPercentage;
@ -569,13 +605,15 @@ static inline u32 GetMoveAbsorbPercentage(u32 moveId)
static inline u32 GetMoveRecoil(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) static inline u32 GetMoveNonVolatileStatus(u32 move)
{ {
move = SanitizeMoveId(move); move = SanitizeMoveId(move);
switch(GetMoveEffect(move)) switch (GetMoveEffect(move))
{ {
case EFFECT_NON_VOLATILE_STATUS: case EFFECT_NON_VOLATILE_STATUS:
case EFFECT_YAWN: case EFFECT_YAWN:
@ -588,12 +626,16 @@ static inline u32 GetMoveNonVolatileStatus(u32 move)
static inline u32 GetMoveDamagePercentage(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) 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) 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) static inline const u8 *GetMoveAnimationScript(u32 moveId)
{ {
moveId = SanitizeMoveId(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[MOVE_NONE].battleAnimScript;
} }
return gMovesInfo[moveId].battleAnimScript; return gMovesInfo[moveId].battleAnimScript;
@ -635,9 +676,8 @@ static inline const u8 *GetMoveAnimationScript(u32 moveId)
static inline const u8 *GetMoveBattleScript(u32 moveId) static inline const u8 *GetMoveBattleScript(u32 moveId)
{ {
moveId = SanitizeMoveId(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[EFFECT_PLACEHOLDER].battleScript;
} }
return gBattleMoveEffects[GetMoveEffect(moveId)].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_CheckAiMoveScores(u32 battlerId);
void TestRunner_Battle_AISetScore(const char *file, u32 line, u32 battlerId, u32 moveIndex, s32 score); 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_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_CheckMemory(void);
void TestRunner_Battle_CheckBattleRecordActionType(u32 battlerId, u32 recordIndex, u32 actionType); 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_CheckAiMoveScores(...) (void)0
#define TestRunner_Battle_AISetScore(...) (void)0 #define TestRunner_Battle_AISetScore(...) (void)0
#define TestRunner_Battle_AIAdjustScore(...) (void)0 #define TestRunner_Battle_AIAdjustScore(...) (void)0
#define TestRunner_Battle_InvalidNoHPMon(...) (void)0
#define TestRunner_Battle_CheckBattleRecordActionType(...) (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? if (!continuousAnim) // May have been used for debug?
{ {
// Debugging - ensure no hanging mon bg tasks assertf(!FuncIsActiveTask(Task_UpdateMonBg), "move %d still has Task_UpdateMonBg active at the end", gAnimMoveIndex);
if (FuncIsActiveTask(Task_UpdateMonBg))
DebugPrintf("Move %d animation still has Task_UpdateMonBg active at the end!", gAnimMoveIndex);
m4aMPlayVolumeControl(&gMPlayInfo_BGM, TRACKS_ALL, 256); m4aMPlayVolumeControl(&gMPlayInfo_BGM, TRACKS_ALL, 256);
if (!IsContest()) if (!IsContest())

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,193 +1,9 @@
#include "decompress_error_handler.h"
#include "global.h" #include "global.h"
#include "data.h" #include "decompress_error_handler.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++;
}
void DecompressionError(const u32 *src, enum CompressionError error) void DecompressionError(const u32 *src, enum CompressionError error)
{ {
sErrorAddress = (u32)src; assertf(0, "Decompression failed.\nAddress: 0x%p\nError: 0x%x\n", src, error);
sCompressionError = error;
SetMainCallback2(DecompressionError_CB2);
DecompressionError_CB2();
} }
void DoDecompressionError(void) void DoDecompressionError(void)

View File

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

View File

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

View File

@ -792,10 +792,12 @@ bool32 RemovePyramidBagItem(u16 itemId, u16 count)
static u16 SanitizeItemId(u16 itemId) static u16 SanitizeItemId(u16 itemId)
{ {
if (itemId >= ITEMS_COUNT) assertf(itemId < ITEMS_COUNT, "invalid item: %d", itemId)
{
return ITEM_NONE; return ITEM_NONE;
else }
return itemId;
return itemId;
} }
const u8 *GetItemName(u16 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 (pos->next == head)
{ {
#if TESTING
const struct MemBlock *head = HeapHead(); const struct MemBlock *head = HeapHead();
const struct MemBlock *block = head; const struct MemBlock *block = head;
do do
@ -99,13 +99,10 @@ void *AllocInternal(void *heapStart, u32 size, const char *location)
block = block->next; block = block->next;
} }
while (block != head); 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 #endif
if (location) assertf(pos->next != head, "%s: out of memory trying to allocate %d bytes", location, size)
{ {
DebugPrintfLevel(MGBA_LOG_ERROR, "%s: out of memory trying to allocate %d bytes", location, size);
}
AGB_ASSERT(FALSE);
return NULL; return NULL;
} }

View File

@ -534,37 +534,33 @@ void LoadSaveblockObjEventScripts(void)
savObjTemplates[i].script = mapHeaderObjTemplates[i].script; 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) void SetObjEventTemplateCoords(u8 localId, s16 x, s16 y)
{ {
s32 i; struct ObjectEventTemplate *objectEventTemplate = GetObjectEventTemplate(localId);
struct ObjectEventTemplate *savObjTemplates = gSaveBlock1Ptr->objectEventTemplates; if (objectEventTemplate)
for (i = 0; i < OBJECT_EVENT_TEMPLATES_COUNT; i++)
{ {
struct ObjectEventTemplate *objectEventTemplate = &savObjTemplates[i]; objectEventTemplate->x = x;
if (objectEventTemplate->localId == localId) objectEventTemplate->y = y;
{
objectEventTemplate->x = x;
objectEventTemplate->y = y;
return;
}
} }
} }
void SetObjEventTemplateMovementType(u8 localId, u8 movementType) void SetObjEventTemplateMovementType(u8 localId, u8 movementType)
{ {
s32 i; struct ObjectEventTemplate *objectEventTemplate = GetObjectEventTemplate(localId);
if (objectEventTemplate)
struct ObjectEventTemplate *savObjTemplates = gSaveBlock1Ptr->objectEventTemplates; objectEventTemplate->movementType = movementType;
for (i = 0; i < OBJECT_EVENT_TEMPLATES_COUNT; i++)
{
struct ObjectEventTemplate *objectEventTemplate = &savObjTemplates[i];
if (objectEventTemplate->localId == localId)
{
objectEventTemplate->movementType = movementType;
return;
}
}
} }
static void InitMapView(void) 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); 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) void CreateMonWithGenderNatureLetter(struct Pokemon *mon, u16 species, u8 level, u8 fixedIV, u8 gender, u8 nature, u8 unownLetter)
{ {
u32 personality; u32 personality;
u32 genderRatio = gSpeciesInfo[species].genderRatio;
if ((u8)(unownLetter - 1) < NUM_UNOWN_FORMS) if ((u8)(unownLetter - 1) < NUM_UNOWN_FORMS)
{ {
u16 actualLetter; u16 actualLetter;
do while (TRUE)
{ {
personality = Random32(); personality = Random32();
actualLetter = GET_UNOWN_LETTER(personality); 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 else
{ {
do while (TRUE)
{ {
personality = Random32(); 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); 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 personality;
u32 otId; u32 otId;
u32 genderRatio = gSpeciesInfo[species].genderRatio;
do while (TRUE)
{ {
otId = Random32(); otId = Random32();
personality = 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); 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) 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; return SPECIES_NONE;
else }
return species;
return species;
} }
bool32 IsSpeciesEnabled(u16 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) u32 RandomUniformDefault(enum RandomTag tag, u32 lo, u32 hi)
{ {
assertf(lo <= hi);
return lo + (((hi - lo + 1) * Random()) >> 16); return lo + (((hi - lo + 1) * Random()) >> 16);
} }
u32 RandomUniformExceptDefault(enum RandomTag tag, u32 lo, u32 hi, bool32 (*reject)(u32)) u32 RandomUniformExceptDefault(enum RandomTag tag, u32 lo, u32 hi, bool32 (*reject)(u32))
{ {
assertf(lo <= hi);
LOOP_RANDOM_START; LOOP_RANDOM_START;
while (TRUE) while (TRUE)
{ {
// TODO: assertf to abort after too many iterations.
u32 n = lo + (((hi - lo + 1) * LOOP_RANDOM) >> 16); u32 n = lo + (((hi - lo + 1) * LOOP_RANDOM) >> 16);
if (!reject(n)) if (!reject(n))
return 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) u32 RandomWeightedArrayDefault(enum RandomTag tag, u32 sum, u32 n, const u8 *weights)
{ {
assertf(n > 0);
s32 i, targetSum; s32 i, targetSum;
targetSum = (sum * Random()) >> 16; targetSum = (sum * Random()) >> 16;
for (i = 0; i < n - 1; i++) 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) 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); 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) 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; ctx->scriptPtr = ptr;
} }

View File

@ -243,9 +243,20 @@ void CanHyperTrain(struct ScriptContext *ctx)
Script_RequestEffects(SCREFF_V1); Script_RequestEffects(SCREFF_V1);
if (stat < NUM_STATS assertf(stat < NUM_STATS, "invalid stat: %d", stat)
&& partyIndex < PARTY_SIZE {
&& !GetMonData(&gPlayerParty[partyIndex], MON_DATA_HYPER_TRAINED_HP + 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) && GetMonData(&gPlayerParty[partyIndex], MON_DATA_HP_IV + stat) < MAX_PER_STAT_IVS)
{ {
gSpecialVar_Result = TRUE; gSpecialVar_Result = TRUE;
@ -263,12 +274,20 @@ void HyperTrain(struct ScriptContext *ctx)
Script_RequestEffects(SCREFF_V1 | SCREFF_SAVE); Script_RequestEffects(SCREFF_V1 | SCREFF_SAVE);
if (stat < NUM_STATS && partyIndex < PARTY_SIZE) assertf(stat < NUM_STATS, "invalid stat: %d", stat)
{ {
bool32 data = TRUE; return;
SetMonData(&gPlayerParty[partyIndex], MON_DATA_HYPER_TRAINED_HP + stat, &data);
CalculateMonStats(&gPlayerParty[partyIndex]);
} }
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) void HasGigantamaxFactor(struct ScriptContext *ctx)
@ -338,7 +357,6 @@ static u32 ScriptGiveMonParameterized(u8 side, u8 slot, u16 species, u8 level, u
int sentToPc; int sentToPc;
struct Pokemon mon; struct Pokemon mon;
u32 i; u32 i;
u8 genderRatio = gSpeciesInfo[species].genderRatio;
u16 targetSpecies; u16 targetSpecies;
bool32 isShiny; 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 // create a Pokémon with basic data
if ((gender == MON_MALE && genderRatio != MON_FEMALE && genderRatio != MON_GENDERLESS) // TODO: Use another value for "any gender" so that we can report an
|| (gender == MON_FEMALE && genderRatio != MON_MALE && genderRatio != MON_GENDERLESS) // error if genderless.
|| (gender == MON_GENDERLESS && genderRatio == MON_GENDERLESS)) if (gender != MON_GENDERLESS)
CreateMonWithGenderNatureLetter(&mon, species, level, 32, gender, nature, 0); CreateMonWithGenderNatureLetter(&mon, species, level, 32, gender, nature, 0);
else else
CreateMonWithNature(&mon, species, level, 32, nature); 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_NAME, gSaveBlock2Ptr->playerName);
SetMonData(&mon, MON_DATA_OT_GENDER, &gSaveBlock2Ptr->playerGender); 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)); 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; 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 // set pokédex flags
nationalDexNum = SpeciesToNationalPokedexNum(species); nationalDexNum = SpeciesToNationalPokedexNum(species);
if (sentToPc != MON_CANT_GIVE) 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); 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; return sentToPc;
} }

View File

@ -1113,12 +1113,6 @@ void TestRunner_Battle_CheckSwitch(u32 battlerId, u32 partyIndex)
DATA.trial.aiActionsPlayed[battlerId]++; 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) static bool32 CheckComparision(s32 val1, s32 val2, u32 cmp)
{ {
switch (cmp) switch (cmp)