diff --git a/graphics/crash_screen/font.png b/graphics/crash_screen/font.png index d00d29a619..60db49dd9c 100644 Binary files a/graphics/crash_screen/font.png and b/graphics/crash_screen/font.png differ diff --git a/include/data.h b/include/data.h index d24482d508..7637cb46b0 100644 --- a/include/data.h +++ b/include/data.h @@ -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; } diff --git a/include/move.h b/include/move.h index 7acbad16bd..475cc03c9a 100644 --- a/include/move.h +++ b/include/move.h @@ -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; diff --git a/include/test_runner.h b/include/test_runner.h index b1d90889fb..a351dffb5d 100644 --- a/include/test_runner.h +++ b/include/test_runner.h @@ -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 diff --git a/src/battle_anim.c b/src/battle_anim.c index 1347390283..43489ce06c 100644 --- a/src/battle_anim.c +++ b/src/battle_anim.c @@ -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()) diff --git a/src/battle_main.c b/src/battle_main.c index 2460633b0f..7d06d9bf6b 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -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) { diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 3e1847d1ec..770042b597 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -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; } diff --git a/src/battle_util.c b/src/battle_util.c index bedcab2878..6d20430f6a 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -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) diff --git a/src/contest.c b/src/contest.c index 2ab5308f5c..d2aefa5dd4 100644 --- a/src/contest.c +++ b/src/contest.c @@ -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; } diff --git a/src/data/moves_info.h b/src/data/moves_info.h index c936244c31..c589008730 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -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, diff --git a/src/decompress_error_handler.c b/src/decompress_error_handler.c index ff332618ff..ed3616239c 100644 --- a/src/decompress_error_handler.c +++ b/src/decompress_error_handler.c @@ -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) diff --git a/src/dexnav.c b/src/dexnav.c index 432d8debce..88c340a2ca 100644 --- a/src/dexnav.c +++ b/src/dexnav.c @@ -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); diff --git a/src/fieldmap.c b/src/fieldmap.c index 5e8a261cab..151acaebe2 100644 --- a/src/fieldmap.c +++ b/src/fieldmap.c @@ -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; } diff --git a/src/item.c b/src/item.c index c3e4f23a83..9f103b4a35 100644 --- a/src/item.c +++ b/src/item.c @@ -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) diff --git a/src/malloc.c b/src/malloc.c index f4f079f30a..f31c4e024e 100644 --- a/src/malloc.c +++ b/src/malloc.c @@ -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; } diff --git a/src/overworld.c b/src/overworld.c index 277b2d84a1..3fd3236cb5 100644 --- a/src/overworld.c +++ b/src/overworld.c @@ -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) diff --git a/src/pokemon.c b/src/pokemon.c index 2631741438..e35116e34c 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -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) diff --git a/src/random.c b/src/random.c index aabc24316b..e19f6f6fbf 100644 --- a/src/random.c +++ b/src/random.c @@ -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); } diff --git a/src/script.c b/src/script.c index 3a0c7c7afd..fbbd9063b1 100644 --- a/src/script.c +++ b/src/script.c @@ -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; } diff --git a/src/script_pokemon_util.c b/src/script_pokemon_util.c index b50d443d7b..e5c54b0ae2 100644 --- a/src/script_pokemon_util.c +++ b/src/script_pokemon_util.c @@ -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; } diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index be4f3eccbf..8c5b1c63f7 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -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)