sovereignx/test/test_runner_battle.c

2727 lines
94 KiB
C
Raw Normal View History

#include "global.h"
#include "battle.h"
#include "battle_ai_util.h"
#include "battle_anim.h"
#include "battle_controllers.h"
Gimmick Refactor (#4449) * consolidated gimmick checks, triggers, communication, and activation; updated test runner * fixed improper use of .usableGimmick * cleaning up battle_dynamax.c, changing function args to u32s * fixed '#ifdef TESTING' causing errors * updated z-moves to use gimmick interface, pared down redundancies; no AI/tests * added support for z-moves in tests, consolidated gimmick fields * removed ShouldUseMaxMove and .usingMaxMove * renamed TryChangeZIndicator, updated z move display * added several z-move tests and fixed various z-move interactions; fixed z-move category calc * fixed useless battler arg in GetTypeBasedZMove * added basic test check for bad Z-Move or Mega usage * reworked test runner gimmick functionality; added support for Ultra Burst + Z-Move to test Light That Burns the Sky * fixed gimmick test logic; fixed damage category override * fixed mega rayquaza test fail * consolidated gimmick indicator logic; added graphics to gGimmicksInfo * removed TeraData struct * reimplemented AI logic for Z-Moves; no changes * updated Z-Move and Ultra Burst trigger gfx * added testrunner check for multiple gimmick use * fixed duplicate z-move call in test * reorganized data/graphics/gimmicks.h * added signature Z-Move ability tests; implemented Guardian of Alola * fixed bad test update * fixed Thousand Arrows not affecting Tera Flying; clean-up * fixed -ate tests * fixed tera tests * fixed tera tests really * fixed last batch of tests * fixed -ate mega test again * code review * code review pt.2 * tweaked CanTera again * dynamax flag only required for player
2024-06-22 21:25:40 +01:00
#include "battle_gimmick.h"
#include "battle_z_move.h"
#include "characters.h"
#include "event_data.h"
#include "fieldmap.h"
#include "item_menu.h"
#include "main.h"
#include "malloc.h"
#include "random.h"
#include "test/battle.h"
#include "window.h"
#include "constants/trainers.h"
#if defined(__INTELLISENSE__)
#undef TestRunner_Battle_RecordAbilityPopUp
#undef TestRunner_Battle_RecordAnimation
#undef TestRunner_Battle_RecordHP
#undef TestRunner_Battle_RecordMessage
#undef TestRunner_Battle_RecordStatus1
#undef TestRunner_Battle_AfterLastTurn
#undef TestRunner_Battle_CheckBattleRecordActionType
#undef TestRunner_Battle_GetForcedAbility
#endif
#define INVALID(fmt, ...) Test_ExitWithResult(TEST_RESULT_INVALID, sourceLine, ":L%s:%d: " fmt, gTestRunnerState.test->filename, sourceLine, ##__VA_ARGS__)
#define INVALID_IF(c, fmt, ...) do { if (c) Test_ExitWithResult(TEST_RESULT_INVALID, sourceLine, ":L%s:%d: " fmt, gTestRunnerState.test->filename, sourceLine, ##__VA_ARGS__); } while (0)
#define ASSUMPTION_FAIL_IF(c, fmt, ...) do { if (c) Test_ExitWithResult(TEST_RESULT_ASSUMPTION_FAIL, sourceLine, ":L%s:%d: " fmt, gTestRunnerState.test->filename, sourceLine, ##__VA_ARGS__); } while (0)
Species Simplifier™ - Part 3 (#3562) * Added SpeciesInfo fields # Conflicts: # include/pokemon.h * Added missing comments * Renamed gigantamax to isGigantamax * Converted species flags to agbcc bit flags * Added padding after flags * Pikachu form config * Dex text * Reordered Paradox flag * Missing Pikachu ifdef comments * Moved P_GEN_x_POKEMON settings to the species_families file * Gen 1 Dex entries * Renamed file to species_enabled.h * Prevent Pokémon from evolving if their evolution is disabled * Prevent Pokémon from being affected by Eviolite if their evolution is disabled * Prevent Pokémon offspring if they're disabled * Corrected imprementation of evolution prevention It could potentially overwrite items by accident * Cross-evo configs * Gen 2 dex entries * Changed cry table to use assignable IDs # Conflicts: # include/pokemon.h * Form table pointers * Beginning support for moving data to gSpeciesInfo * Pokédexes reads text from gSpeciesInfo * Reading species names from gSpeciesInfo * Fixed erroneous copy-paste * Moved Bulbasaur to Raichu Family data do gSpeciesinfo * Added female pic sizes * Macros for Front, Back, Palette and Learnsets * Sandshrew to Dugtrio Families * Obtains Height/Weight from gSpeciesInfo * Read category from gSpeciesInfo * Removed unused data from pokedex_entries.h * Moved Gen 1 data to gSpeciesInfo * Moved Gen 2 data to gSpeciesInfo + fixed Ursaluna Bloodmoon not producing Teddiursa eggs * Moved Gen 3 data to gSpeciesInfo * Moved Gen 4 data to gSpeciesInfo * HGSS dex properly shows form sprites * Fixed data type for GetSpeciesHeight/Weight * Reading Pokédex Size data from gSpeciesInfo * Cleaned up mon graphic decompression function * Reading Front Pics gSpeciesInfo * Reading Back Pics gSpeciesInfo * Updated uses of GetMonSpritePalStruct to allow loading the data directly instead of reading the struct * Reading palettes from gSpeciesInfo * Reading animation delay from gSpeciesInfo * Fixed Pokémon Debug Menu not reading animation delay * Reading front anim Id from gSpeciesInfo * Reading back anim Id from gSpeciesInfo * Reading Animation Frames from gSpeciesInfo * Reading Icon Sprites from gSpeciesInfo * Reading Icon Palette IDs from gSpeciesInfo * Reading Footprints from gSpeciesInfo * Reading Mon Pic sizes from gSpeciesInfo * Separate mon pic sizes for female mon * Reading Level Up Learnsets from gSpeciesInfo * Reading Teachable Learnsets from gSpeciesInfo * Reading National Dex Number from gSpeciesInfo * Reading Enemy Mon Elevation from gSpeciesInfo * Reading Evolutions from gSpeciesInfo * Reading form table from gSpeciesInfo * Fixed button label inconsistency in pokémon debug menu * Reading form change table from gSpeciesInfo * Removed need to define mon graphics in include/graphics.h * Reordered mon graphics by family * Fixed National dex number offset * gFrontierBannedSpecies now checks IsSpeciesEnabled * Added 3 more toggles for Fusions, Primal Reversions and Ultra Burst * Gen 3 & 4 family toggles for dex entries * Fixed missing Mr. Rime data * Gen 1 dex descriptions as compound strings * Gen 2 dex descriptions as compound strings * Gen 3 dex descriptions as compound strings * Gen 4 dex descriptions as compound strings * Fixed compile in agbcc * Moved Gen 5 data to gSpeciesInfo * Using PLACEHOLDER_ANIM_SINGLE_FRAME when needed (same hash) * Renamed PLACEHOLDER_TWO_FRAME_ANIMATION to PLACEHOLDER_ANIM_TWO_FRAMES * Fixed capitalization for front pic anims * Fixed `CanEvolve` * Moved Gen 6 data to gSpeciesInfo * Gen 7 without macros * Gen 8 without macros * Gen 9 without macros * Fixed Gen 9 sound data * Finished moving Gen 7 data in macros (by Alex) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> * Ultra Necrozma is now included in the fusion forms define * Cries are now properly excluded from the rom when disabling species * Removed P_GEN_x_POKEMON from form_change_tables.h * Fixed Yanmega's cry ignoring the P_GEN_4_CROSS_EVOS setting * Added P_NEW_EVOS_IN_REGIONAL_DEX setting * Automatic ASSUMPTION_FAIL when used species are disabled * Finished moving Gen 8 data in macros (by Alex) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> * Expanded Alcremie macro * Finished moving Gen 9 data in macros (by Alex) * Fixed missing data for Komala and Marshadow * Form species id tables use family toggles + added missing Ursaluna form species id table * Fixed Genesect palettes * Fixed Flabébé family graphics * Fixed Minior Graphics * Fixed missing Calyrex data * Family toggles in mon graphcis (By Jasper/Bassoonian) Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> * Fixed missing Ogerpon data * Ordered Level Up Learnsets (missing toggles) Also added White Basculin learnset * Added more Minior synonyms * Fixed enamorus order * Reordered teachable learnsets * Removed sSpeciesXXX level up learnsets + reordered * Removed unneeded assumes * Removed unused extra animations * Family toggles in mon level up learnsets (By Jasper/Bassoonian) Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> * Fixed Phione graphics inclusion * Family toggles for Teachable Learnsets Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> * Ordered Front Pic Anims * Family toggles for front pic animations Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> * Fixed HGSS dex incorrectly showing the wrong mon when catching it * Family toggles for Egg Moves + reodered Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> * Fixed HGSS not checking for null footprints * Review changes * Fixed Female difference mon not using their macros * Fixed Alcremie Macro * Fixed compile error when disabling the Bulbasaur family * Fixed Turtwig and Chimchar family + Houndour and Mareep cries not being excluded properly * Re-added missing Egg graphical data * Fixed crash during the Birch Intro when Lotad is disabled * Renamed PALLETE to PALETTES * Removed extra spacing for YOffsets * Fixed compile when only disabling cross-gen evolutions * Added Tera Forms define (Ogerpon and Terapagos) * Fixed Necrozma macro * Removed .noFlip = FALSE * Fixed dex number in summary screen for forms when nat dex is disabled * Fixed Paldean form paths --------- Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com>
2023-12-08 14:05:10 +00:00
#define STATE gBattleTestRunnerState
#define DATA gBattleTestRunnerState->data
#if HQ_RANDOM == TRUE
#define RNG_SEED_DEFAULT {0, 0, 0, 0}
static inline bool32 RngSeedNotDefault(const rng_value_t *seed)
{
return (seed->a | seed->b | seed->c | seed->ctr) != 0;
}
#else
#define RNG_SEED_DEFAULT 0x00000000
static inline bool32 RngSeedNotDefault(const rng_value_t *seed)
{
return *seed != RNG_SEED_DEFAULT;
}
#endif
#undef Q_4_12
#define Q_4_12(n) (s32)((n) * 4096)
// Alias sBackupMapData to avoid using heap.
struct BattleTestRunnerState *const gBattleTestRunnerState = (void *)sBackupMapData;
STATIC_ASSERT(sizeof(struct BattleTestRunnerState) <= sizeof(sBackupMapData), sBackupMapDataSpace);
static void CB2_BattleTest_NextParameter(void);
static void CB2_BattleTest_NextTrial(void);
static void PushBattlerAction(u32 sourceLine, s32 battlerId, u32 actionType, u32 byte);
static void PrintAiMoveLog(u32 battlerId, u32 moveSlot, u32 moveId, s32 totalScore);
static void ClearAiLog(u32 battlerId);
static const char *BattlerIdentifier(s32 battlerId);
NAKED static void InvokeSingleTestFunctionWithStack(void *results, u32 i, struct BattlePokemon *player, struct BattlePokemon *opponent, SingleBattleTestFunction function, void *stack)
{
asm("push {r4-r6,lr}\n\
ldr r4, [sp, #16] @ function\n\
ldr r5, [sp, #20] @ stack\n\
mov r6, sp\n\
mov sp, r5\n\
push {r6}\n\
ldr r6, =SingleRestoreSP + 1\n\
mov lr, r6\n\
bx r4\n\
SingleRestoreSP:\n\
pop {r0}\n\
mov sp, r0\n\
pop {r4-r6}\n\
pop {r0}\n\
bx r0\n\
.pool");
}
NAKED static void InvokeDoubleTestFunctionWithStack(void *results, u32 i, struct BattlePokemon *playerLeft, struct BattlePokemon *opponentLeft, struct BattlePokemon *playerRight, struct BattlePokemon *opponentRight, SingleBattleTestFunction function, void *stack)
{
asm("push {r4-r7,lr}\n\
ldr r4, [sp, #28] @ function\n\
ldr r5, [sp, #32] @ stack\n\
mov r6, sp\n\
mov sp, r5\n\
push {r6}\n\
add r6, #20\n\
ldmia r6, {r6, r7} @ playerRight, opponentRight\n\
push {r6, r7}\n\
ldr r6, =DoubleRestoreSP + 1\n\
mov lr, r6\n\
bx r4\n\
DoubleRestoreSP:\n\
add sp, #8\n\
pop {r0}\n\
mov sp, r0\n\
pop {r4-r7}\n\
pop {r0}\n\
bx r0\n\
.pool");
}
// Calls test->function, but pointing its stack at DATA.stack so that
// local variables are live after the function returns (and so can be
// referenced by HP_BAR, or the next call, etc).
// NOTE: This places the stack in EWRAM which has longer waitstates than
// IWRAM so could be much slower, but a) not that much code executes,
// and b) mga-rom-test isn't meaningfully limited by the GBA frame rate.
static void InvokeTestFunction(const struct BattleTest *test)
{
STATE->parametersCount = 0;
switch (test->type)
{
case BATTLE_TEST_SINGLES:
case BATTLE_TEST_WILD:
case BATTLE_TEST_AI_SINGLES:
InvokeSingleTestFunctionWithStack(STATE->results, STATE->runParameter, &gBattleMons[B_POSITION_PLAYER_LEFT], &gBattleMons[B_POSITION_OPPONENT_LEFT], test->function.singles, &DATA.stack[BATTLE_TEST_STACK_SIZE]);
break;
case BATTLE_TEST_DOUBLES:
case BATTLE_TEST_AI_DOUBLES:
InvokeDoubleTestFunctionWithStack(STATE->results, STATE->runParameter, &gBattleMons[B_POSITION_PLAYER_LEFT], &gBattleMons[B_POSITION_OPPONENT_LEFT], &gBattleMons[B_POSITION_PLAYER_RIGHT], &gBattleMons[B_POSITION_OPPONENT_RIGHT], test->function.singles, &DATA.stack[BATTLE_TEST_STACK_SIZE]);
break;
}
}
static const struct BattleTest *GetBattleTest(void)
{
const struct BattleTest *test = gTestRunnerState.test->data;
return test;
}
static bool32 IsAITest(void)
{
switch (GetBattleTest()->type)
{
case BATTLE_TEST_AI_SINGLES:
case BATTLE_TEST_AI_DOUBLES:
return TRUE;
}
return FALSE;
}
static u32 BattleTest_EstimateCost(void *data)
{
u32 cost;
const struct BattleTest *test = data;
memset(STATE, 0, sizeof(*STATE));
STATE->runRandomly = TRUE;
InvokeTestFunction(test);
cost = 1;
if (STATE->parametersCount != 0)
cost *= STATE->parametersCount;
if (STATE->trials == 1)
cost *= 3;
else if (STATE->trials > 1)
cost *= STATE->trials;
return cost;
}
static void BattleTest_SetUp(void *data)
{
const struct BattleTest *test = data;
memset(STATE, 0, sizeof(*STATE));
InvokeTestFunction(test);
STATE->parameters = STATE->parametersCount;
2023-04-26 11:38:52 +01:00
if (STATE->parametersCount == 0 && test->resultsSize > 0)
Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":Lresults without PARAMETRIZE");
if (sizeof(*STATE) + test->resultsSize * STATE->parameters > sizeof(sBackupMapData))
Test_ExitWithResult(TEST_RESULT_ERROR, SourceLine(0), ":LOOM: STATE (%d) + STATE->results (%d) too big for sBackupMapData (%d)", sizeof(*STATE), test->resultsSize * STATE->parameters, sizeof(sBackupMapData));
STATE->results = (void *)((char *)sBackupMapData + sizeof(struct BattleTestRunnerState));
memset(STATE->results, 0, test->resultsSize * STATE->parameters);
switch (test->type)
{
case BATTLE_TEST_SINGLES:
case BATTLE_TEST_WILD:
case BATTLE_TEST_AI_SINGLES:
STATE->battlersCount = 2;
break;
case BATTLE_TEST_DOUBLES:
case BATTLE_TEST_AI_DOUBLES:
STATE->battlersCount = 4;
break;
}
}
static void PrintTestName(void)
{
if (STATE->trials && STATE->parameters)
{
if (STATE->trials == 1)
Test_MgbaPrintf(":N%s %d/%d (%d/?)", gTestRunnerState.test->name, STATE->runParameter + 1, STATE->parameters, STATE->runTrial + 1);
else
Test_MgbaPrintf(":N%s %d/%d (%d/%d)", gTestRunnerState.test->name, STATE->runParameter + 1, STATE->parameters, STATE->runTrial + 1, STATE->trials);
}
else if (STATE->trials)
{
if (STATE->trials == 1)
Test_MgbaPrintf(":N%s (%d/?)", gTestRunnerState.test->name, STATE->runTrial + 1);
else
Test_MgbaPrintf(":N%s (%d/%d)", gTestRunnerState.test->name, STATE->runTrial + 1, STATE->trials);
}
else if (STATE->parameters)
{
Test_MgbaPrintf(":N%s %d/%d", gTestRunnerState.test->name, STATE->runParameter + 1, STATE->parameters);
}
}
// This does not take into account priority, statuses, or any other
// modifiers.
static void SetImplicitSpeeds(void)
{
s32 i;
u32 speed = 12;
u32 hasSpeeds = 0;
u32 allSpeeds = ((1 << DATA.playerPartySize) - 1) | (((1 << DATA.opponentPartySize) - 1) << 6);
bool32 madeProgress;
while (hasSpeeds != allSpeeds)
{
madeProgress = FALSE;
for (i = 0; i < DATA.playerPartySize; i++)
{
if (!(hasSpeeds & (1 << i))
&& !(DATA.slowerThan[B_SIDE_PLAYER][i] & ~hasSpeeds))
{
SetMonData(&DATA.recordedBattle.playerParty[i], MON_DATA_SPEED, &speed);
speed--;
hasSpeeds |= 1 << i;
madeProgress = TRUE;
}
}
for (i = 0; i < DATA.opponentPartySize; i++)
{
if (!(hasSpeeds & ((1 << 6) << i))
&& !(DATA.slowerThan[B_SIDE_OPPONENT][i] & ~hasSpeeds))
{
SetMonData(&DATA.recordedBattle.opponentParty[i], MON_DATA_SPEED, &speed);
speed--;
hasSpeeds |= (1 << 6) << i;
madeProgress = TRUE;
}
}
if (!madeProgress)
Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":LTURNs have contradictory speeds");
}
}
static void BattleTest_Run(void *data)
{
s32 i;
u32 requiredPlayerPartySize;
u32 requiredOpponentPartySize;
const rng_value_t defaultSeed = RNG_SEED_DEFAULT;
const struct BattleTest *test = data;
memset(&DATA, 0, sizeof(DATA));
DATA.recordedBattle.rngSeed = defaultSeed;
DATA.recordedBattle.textSpeed = OPTIONS_TEXT_SPEED_FAST;
// Set battle flags and opponent ids.
switch (test->type)
{
case BATTLE_TEST_WILD:
DATA.recordedBattle.battleFlags = BATTLE_TYPE_IS_MASTER;
break;
case BATTLE_TEST_AI_SINGLES:
DATA.recordedBattle.battleFlags = BATTLE_TYPE_IS_MASTER | BATTLE_TYPE_TRAINER;
DATA.recordedBattle.opponentA = TRAINER_LEAF;
DATA.hasAI = TRUE;
break;
case BATTLE_TEST_AI_DOUBLES:
DATA.recordedBattle.battleFlags = BATTLE_TYPE_IS_MASTER | BATTLE_TYPE_TRAINER | BATTLE_TYPE_DOUBLE;
DATA.recordedBattle.opponentA = TRAINER_LEAF;
DATA.recordedBattle.opponentB = TRAINER_RED;
DATA.hasAI = TRUE;
break;
case BATTLE_TEST_SINGLES:
DATA.recordedBattle.battleFlags = BATTLE_TYPE_IS_MASTER | BATTLE_TYPE_RECORDED_IS_MASTER | BATTLE_TYPE_RECORDED_LINK | BATTLE_TYPE_TRAINER;
DATA.recordedBattle.opponentA = TRAINER_LINK_OPPONENT;
break;
case BATTLE_TEST_DOUBLES:
DATA.recordedBattle.battleFlags = BATTLE_TYPE_IS_MASTER | BATTLE_TYPE_RECORDED_IS_MASTER | BATTLE_TYPE_RECORDED_LINK | BATTLE_TYPE_TRAINER | BATTLE_TYPE_DOUBLE;
DATA.recordedBattle.opponentA = TRAINER_LINK_OPPONENT;
DATA.recordedBattle.opponentB = TRAINER_LINK_OPPONENT;
break;
}
for (i = 0; i < STATE->battlersCount; i++)
{
DATA.recordedBattle.playersName[i][0] = CHAR_1 + i;
DATA.recordedBattle.playersName[i][1] = EOS;
DATA.recordedBattle.playersLanguage[i] = GAME_LANGUAGE;
DATA.recordedBattle.playersBattlers[i] = i;
DATA.currentMonIndexes[i] = (i & BIT_FLANK) == B_FLANK_LEFT ? 0 : 1;
}
STATE->runRandomly = TRUE;
STATE->runGiven = TRUE;
STATE->runWhen = TRUE;
STATE->runScene = TRUE;
InvokeTestFunction(test);
STATE->runRandomly = FALSE;
STATE->runGiven = FALSE;
STATE->runWhen = FALSE;
STATE->runScene = FALSE;
requiredPlayerPartySize = 0;
requiredOpponentPartySize = 0;
for (i = 0; i < STATE->battlersCount; i++)
{
if ((i & BIT_SIDE) == B_SIDE_PLAYER)
requiredPlayerPartySize = DATA.currentMonIndexes[i] + 1;
else
requiredOpponentPartySize = DATA.currentMonIndexes[i] + 1;
}
if (DATA.playerPartySize < requiredPlayerPartySize)
Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":L%d PLAYER Pokemon required", requiredPlayerPartySize);
if (DATA.opponentPartySize < requiredOpponentPartySize)
Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":L%d OPPONENT Pokemon required", requiredOpponentPartySize);
for (i = 0; i < STATE->battlersCount; i++)
PushBattlerAction(0, i, RECORDED_BYTE, 0xFF);
if (DATA.hasExplicitSpeeds)
{
if (DATA.explicitSpeeds[B_SIDE_PLAYER] != (1 << DATA.playerPartySize) - 1
&& DATA.explicitSpeeds[B_SIDE_OPPONENT] != (1 << DATA.opponentPartySize) - 1)
{
Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":LSpeed required for all PLAYERs and OPPONENTs");
}
}
else
{
SetImplicitSpeeds();
}
SetVariablesForRecordedBattle(&DATA.recordedBattle);
if (STATE->trials)
gMain.savedCallback = CB2_BattleTest_NextTrial;
else if (STATE->parameters)
gMain.savedCallback = CB2_BattleTest_NextParameter;
else
gMain.savedCallback = CB2_TestRunner;
SetMainCallback2(CB2_InitBattle);
STATE->checkProgressParameter = 0;
STATE->checkProgressTrial = 0;
STATE->checkProgressTurn = 0;
PrintTestName();
}
u32 RandomUniform(enum RandomTag tag, u32 lo, u32 hi)
{
const struct BattlerTurn *turn = NULL;
if (gCurrentTurnActionNumber < gBattlersCount)
{
u32 battlerId = gBattlerByTurnOrder[gCurrentTurnActionNumber];
turn = &DATA.battleRecordTurns[gBattleResults.battleTurnCounter][battlerId];
if (turn && turn->rng.tag == tag)
return turn->rng.value;
}
if (tag == STATE->rngTag)
{
u32 n = hi - lo + 1;
if (STATE->trials == 1)
{
STATE->trials = n;
PrintTestName();
}
else if (STATE->trials != n)
{
Test_ExitWithResult(TEST_RESULT_ERROR, SourceLine(0), ":LRandomUniform called with inconsistent trials %d and %d", STATE->trials, n);
}
2023-07-19 16:44:21 +01:00
STATE->trialRatio = Q_4_12(1) / STATE->trials;
return STATE->runTrial + lo;
}
2023-07-19 16:44:21 +01:00
return hi;
}
2023-07-19 16:44:21 +01:00
u32 RandomUniformExcept(enum RandomTag tag, u32 lo, u32 hi, bool32 (*reject)(u32))
{
const struct BattlerTurn *turn = NULL;
2023-07-19 16:44:21 +01:00
u32 default_;
if (gCurrentTurnActionNumber < gBattlersCount)
{
u32 battlerId = gBattlerByTurnOrder[gCurrentTurnActionNumber];
turn = &DATA.battleRecordTurns[gBattleResults.battleTurnCounter][battlerId];
2023-07-19 16:44:21 +01:00
if (turn && turn->rng.tag == tag)
{
if (reject(turn->rng.value))
Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":LWITH_RNG specified a rejected value (%d)", turn->rng.value);
2023-07-19 16:44:21 +01:00
return turn->rng.value;
}
}
2023-07-19 16:44:21 +01:00
if (tag == STATE->rngTag)
{
2023-07-19 16:44:21 +01:00
if (STATE->trials == 1)
{
u32 n = 0, i;
for (i = lo; i <= hi; i++)
2023-07-19 16:44:21 +01:00
if (!reject(i))
n++;
STATE->trials = n;
PrintTestName();
}
STATE->trialRatio = Q_4_12(1) / STATE->trials;
2023-07-20 10:14:12 +01:00
while (reject(STATE->runTrial + lo + STATE->rngTrialOffset))
{
if (STATE->runTrial + lo + STATE->rngTrialOffset > hi)
Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":LRandomUniformExcept called with inconsistent reject");
2023-07-20 10:14:12 +01:00
STATE->rngTrialOffset++;
}
return STATE->runTrial + lo + STATE->rngTrialOffset;
}
2023-07-20 10:14:12 +01:00
default_ = hi;
while (reject(default_))
{
2023-07-20 10:14:12 +01:00
if (default_ == lo)
Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":LRandomUniformExcept rejected all values");
2023-07-20 10:14:12 +01:00
default_--;
}
return default_;
}
u32 RandomWeightedArray(enum RandomTag tag, u32 sum, u32 n, const u8 *weights)
{
const struct BattlerTurn *turn = NULL;
2023-07-20 10:14:12 +01:00
if (sum == 0)
Test_ExitWithResult(TEST_RESULT_ERROR, SourceLine(0), ":LRandomWeightedArray called with zero sum");
if (gCurrentTurnActionNumber < gBattlersCount)
{
u32 battlerId = gBattlerByTurnOrder[gCurrentTurnActionNumber];
turn = &DATA.battleRecordTurns[gBattleResults.battleTurnCounter][battlerId];
2023-07-20 10:14:12 +01:00
if (turn && turn->rng.tag == tag)
return turn->rng.value;
}
if (tag == STATE->rngTag)
{
if (STATE->trials == 1)
{
STATE->trials = n;
PrintTestName();
}
else if (STATE->trials != n)
{
Test_ExitWithResult(TEST_RESULT_ERROR, SourceLine(0), ":LRandomWeighted called with inconsistent trials %d and %d", STATE->trials, n);
}
// TODO: Detect inconsistent sum.
STATE->trialRatio = Q_4_12(weights[STATE->runTrial]) / sum;
return STATE->runTrial;
}
2023-07-20 10:14:12 +01:00
switch (tag)
{
case RNG_ACCURACY:
ASSUME(n == 2);
if (turn && turn->hit)
return turn->hit - 1;
else
return TRUE;
case RNG_CRITICAL_HIT:
ASSUME(n == 2);
if (turn && turn->criticalHit)
return turn->criticalHit - 1;
else
return weights[FALSE] > 0 ? FALSE : TRUE;
2023-07-20 10:14:12 +01:00
case RNG_SECONDARY_EFFECT:
ASSUME(n == 2);
if (turn && turn->secondaryEffect)
return turn->secondaryEffect - 1;
else
return TRUE;
default:
while (weights[n-1] == 0)
{
if (n == 1)
Test_ExitWithResult(TEST_RESULT_ERROR, SourceLine(0), ":LRandomWeightedArray called with all zero weights");
2023-07-20 10:14:12 +01:00
n--;
}
return n-1;
}
}
const void *RandomElementArray(enum RandomTag tag, const void *array, size_t size, size_t count)
{
const struct BattlerTurn *turn = NULL;
u32 index = count-1;
if (gCurrentTurnActionNumber < gBattlersCount)
{
u32 battlerId = gBattlerByTurnOrder[gCurrentTurnActionNumber];
turn = &DATA.battleRecordTurns[gBattleResults.battleTurnCounter][battlerId];
if (turn && turn->rng.tag == tag)
{
u32 element = 0;
for (index = 0; index < count; index++)
{
memcpy(&element, (const u8 *)array + size * index, size);
if (element == turn->rng.value)
return (const u8 *)array + size * index;
}
// TODO: Incorporate the line number.
Test_ExitWithResult(TEST_RESULT_ERROR, SourceLine(0), ":L%s: RandomElement illegal value requested: %d", gTestRunnerState.test->filename, turn->rng.value);
}
}
if (tag == STATE->rngTag)
{
if (STATE->trials == 1)
{
STATE->trials = count;
PrintTestName();
}
else if (STATE->trials != count)
{
Test_ExitWithResult(TEST_RESULT_ERROR, SourceLine(0), ":LRandomElement called with inconsistent trials %d and %d", STATE->trials, count);
}
STATE->trialRatio = Q_4_12(1) / count;
return (const u8 *)array + size * STATE->runTrial;
}
return (const u8 *)array + size * index;
}
static s32 TryAbilityPopUp(s32 i, s32 n, u32 battlerId, u32 ability)
{
struct QueuedAbilityEvent *event;
s32 iMax = i + n;
for (; i < iMax; i++)
{
if (DATA.queuedEvents[i].type != QUEUED_ABILITY_POPUP_EVENT)
continue;
event = &DATA.queuedEvents[i].as.ability;
if (event->battlerId == battlerId
&& (event->ability == ABILITY_NONE || event->ability == ability))
return i;
}
return -1;
}
void TestRunner_Battle_RecordAbilityPopUp(u32 battlerId, u32 ability)
{
s32 queuedEvent;
s32 match;
struct QueuedEvent *event;
if (DATA.queuedEvent == DATA.queuedEventsCount)
return;
event = &DATA.queuedEvents[DATA.queuedEvent];
switch (event->groupType)
{
case QUEUE_GROUP_NONE:
case QUEUE_GROUP_ONE_OF:
if (TryAbilityPopUp(DATA.queuedEvent, event->groupSize, battlerId, ability) != -1)
DATA.queuedEvent += event->groupSize;
break;
case QUEUE_GROUP_NONE_OF:
queuedEvent = DATA.queuedEvent;
do
{
if ((match = TryAbilityPopUp(queuedEvent, event->groupSize, battlerId, ability)) != -1)
{
const char *filename = gTestRunnerState.test->filename;
u32 line = SourceLine(DATA.queuedEvents[match].sourceLineOffset);
Test_ExitWithResult(TEST_RESULT_FAIL, line, ":L%s:%d: Matched ABILITY_POPUP", filename, line);
}
queuedEvent += event->groupSize;
if (queuedEvent == DATA.queuedEventsCount)
break;
event = &DATA.queuedEvents[queuedEvent];
if (event->groupType == QUEUE_GROUP_NONE_OF)
continue;
if (TryAbilityPopUp(queuedEvent, event->groupSize, battlerId, ability) != -1)
DATA.queuedEvent = queuedEvent + event->groupSize;
} while (FALSE);
break;
}
}
static s32 TryAnimation(s32 i, s32 n, u32 animType, u32 animId)
{
struct QueuedAnimationEvent *event;
s32 iMax = i + n;
for (; i < iMax; i++)
{
if (DATA.queuedEvents[i].type != QUEUED_ANIMATION_EVENT)
continue;
event = &DATA.queuedEvents[i].as.animation;
if (event->type == animType
&& event->id == animId
&& (event->attacker == 0xF || event->attacker == gBattleAnimAttacker)
&& (event->target == 0xF || event->target == gBattleAnimTarget))
return i;
}
return -1;
}
void TestRunner_Battle_RecordAnimation(u32 animType, u32 animId)
{
s32 queuedEvent;
s32 match;
struct QueuedEvent *event;
if (DATA.queuedEvent == DATA.queuedEventsCount)
return;
event = &DATA.queuedEvents[DATA.queuedEvent];
switch (event->groupType)
{
case QUEUE_GROUP_NONE:
case QUEUE_GROUP_ONE_OF:
if (TryAnimation(DATA.queuedEvent, event->groupSize, animType, animId) != -1)
DATA.queuedEvent += event->groupSize;
break;
case QUEUE_GROUP_NONE_OF:
queuedEvent = DATA.queuedEvent;
do
{
if ((match = TryAnimation(queuedEvent, event->groupSize, animType, animId)) != -1)
{
const char *filename = gTestRunnerState.test->filename;
u32 line = SourceLine(DATA.queuedEvents[match].sourceLineOffset);
Test_ExitWithResult(TEST_RESULT_FAIL, line, ":L%s:%d: Matched ANIMATION", filename, line);
}
queuedEvent += event->groupSize;
if (queuedEvent == DATA.queuedEventsCount)
break;
event = &DATA.queuedEvents[queuedEvent];
if (event->groupType == QUEUE_GROUP_NONE_OF)
continue;
if (TryAnimation(queuedEvent, event->groupSize, animType, animId) != -1)
DATA.queuedEvent = queuedEvent + event->groupSize;
} while (FALSE);
break;
}
}
static s32 TryHP(s32 i, s32 n, u32 battlerId, u32 oldHP, u32 newHP)
{
struct QueuedHPEvent *event;
s32 iMax = i + n;
for (; i < iMax; i++)
{
if (DATA.queuedEvents[i].type != QUEUED_HP_EVENT)
continue;
event = &DATA.queuedEvents[i].as.hp;
if (event->battlerId == battlerId)
{
if (event->address <= 0xFFFF)
{
switch (event->type)
{
case HP_EVENT_NEW_HP:
if (event->address == newHP)
return i;
break;
case HP_EVENT_DELTA_HP:
if (event->address == 0)
return i;
else if ((s16)event->address == oldHP - newHP)
return i;
break;
}
}
else
{
switch (event->type)
{
case HP_EVENT_NEW_HP:
*(u16 *)(u32)(event->address) = newHP;
break;
case HP_EVENT_DELTA_HP:
*(s16 *)(u32)(event->address) = oldHP - newHP;
break;
}
return i;
}
}
}
return -1;
}
void TestRunner_Battle_RecordHP(u32 battlerId, u32 oldHP, u32 newHP)
{
s32 queuedEvent;
s32 match;
struct QueuedEvent *event;
if (DATA.queuedEvent == DATA.queuedEventsCount)
return;
event = &DATA.queuedEvents[DATA.queuedEvent];
switch (event->groupType)
{
case QUEUE_GROUP_NONE:
case QUEUE_GROUP_ONE_OF:
if (TryHP(DATA.queuedEvent, event->groupSize, battlerId, oldHP, newHP) != -1)
DATA.queuedEvent += event->groupSize;
break;
case QUEUE_GROUP_NONE_OF:
queuedEvent = DATA.queuedEvent;
do
{
if ((match = TryHP(queuedEvent, event->groupSize, battlerId, oldHP, newHP)) != -1)
{
const char *filename = gTestRunnerState.test->filename;
u32 line = SourceLine(DATA.queuedEvents[match].sourceLineOffset);
Test_ExitWithResult(TEST_RESULT_FAIL, line, ":L%s:%d: Matched HP_BAR", filename, line);
}
queuedEvent += event->groupSize;
if (queuedEvent == DATA.queuedEventsCount)
break;
event = &DATA.queuedEvents[queuedEvent];
if (event->groupType == QUEUE_GROUP_NONE_OF)
continue;
if (TryHP(queuedEvent, event->groupSize, battlerId, oldHP, newHP) != -1)
DATA.queuedEvent = queuedEvent + event->groupSize;
} while (FALSE);
break;
}
}
static const char *const sBattleActionNames[] =
{
[B_ACTION_USE_MOVE] = "MOVE",
[B_ACTION_USE_ITEM] = "USE_ITEM",
[B_ACTION_SWITCH] = "SWITCH",
};
static u32 CountAiExpectMoves(struct ExpectedAIAction *expectedAction, u32 battlerId, bool32 printLog)
{
u32 i, countExpected = 0;
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (gBitTable[i] & expectedAction->moveSlots)
{
if (printLog)
PrintAiMoveLog(battlerId, i, gBattleMons[battlerId].moves[i], gBattleStruct->aiFinalScore[battlerId][expectedAction->target][i]);
countExpected++;
}
}
return countExpected;
}
void TestRunner_Battle_CheckChosenMove(u32 battlerId, u32 moveId, u32 target)
{
const char *filename = gTestRunnerState.test->filename;
u32 id = DATA.aiActionsPlayed[battlerId];
struct ExpectedAIAction *expectedAction = &DATA.expectedAiActions[battlerId][id];
if (!expectedAction->actionSet)
return;
if (!expectedAction->pass)
{
u32 i, expectedMoveId = 0, countExpected;
bool32 movePasses = FALSE;
if (expectedAction->type != B_ACTION_USE_MOVE)
Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Expected %s, got MOVE", filename, expectedAction->sourceLine, sBattleActionNames[expectedAction->type]);
if (expectedAction->explicitTarget && expectedAction->target != target)
Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Expected target %s, got %s", filename, expectedAction->sourceLine, BattlerIdentifier(expectedAction->target), BattlerIdentifier(target));
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (gBitTable[i] & expectedAction->moveSlots)
{
expectedMoveId = gBattleMons[battlerId].moves[i];
if (!expectedAction->notMove)
{
if (moveId == expectedMoveId)
{
movePasses = TRUE;
break;
}
}
else
{
if (moveId == expectedMoveId)
{
movePasses = FALSE;
break;
}
movePasses = TRUE;
}
}
}
countExpected = CountAiExpectMoves(expectedAction, battlerId, TRUE);
if (!expectedAction->notMove && !movePasses)
{
u32 moveSlot = GetMoveSlot(gBattleMons[battlerId].moves, moveId);
PrintAiMoveLog(battlerId, moveSlot, moveId, gBattleStruct->aiFinalScore[battlerId][expectedAction->target][moveSlot]);
if (countExpected > 1)
Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Unmatched EXPECT_MOVES %S, got %S", filename, expectedAction->sourceLine, GetMoveName(expectedMoveId), GetMoveName(moveId));
else
Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Unmatched EXPECT_MOVE %S, got %S", filename, expectedAction->sourceLine, GetMoveName(expectedMoveId), GetMoveName(moveId));
}
if (expectedAction->notMove && !movePasses)
{
if (countExpected > 1)
Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Unmatched NOT_EXPECT_MOVES %S", filename, expectedAction->sourceLine, GetMoveName(expectedMoveId));
else
Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Unmatched NOT_EXPECT_MOVE %S", filename, expectedAction->sourceLine, GetMoveName(expectedMoveId));
}
}
// Turn passed, clear logs from the turn
ClearAiLog(battlerId);
DATA.aiActionsPlayed[battlerId]++;
}
void TestRunner_Battle_CheckSwitch(u32 battlerId, u32 partyIndex)
{
const char *filename = gTestRunnerState.test->filename;
u32 id = DATA.aiActionsPlayed[battlerId];
struct ExpectedAIAction *expectedAction = &DATA.expectedAiActions[battlerId][id];
if (!expectedAction->actionSet)
return;
if (!expectedAction->pass)
{
if (expectedAction->type != B_ACTION_SWITCH)
Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Expected %s, got SWITCH/SEND_OUT", filename, expectedAction->sourceLine, sBattleActionNames[expectedAction->type]);
if (expectedAction->target != partyIndex)
Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Expected partyIndex %d, got %d", filename, expectedAction->sourceLine, expectedAction->target, partyIndex);
}
DATA.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)
{
case CMP_EQUAL:
return (val1 == val2);
case CMP_NOT_EQUAL:
return (val1 != val2);
case CMP_GREATER_THAN:
return (val1 > val2);
case CMP_LESS_THAN:
return (val1 < val2);
}
return FALSE;
}
static const char *const sCmpToStringTable[] =
{
[CMP_EQUAL] = "EQ",
[CMP_NOT_EQUAL] = "NE",
[CMP_LESS_THAN] = "LT",
[CMP_GREATER_THAN] = "GT",
};
static void CheckIfMaxScoreEqualExpectMove(u32 battlerId, s32 target, struct ExpectedAIAction *aiAction, const char *filename)
{
u32 i;
s32 *scores = gBattleStruct->aiFinalScore[battlerId][target];
s32 bestScore = 0, bestScoreId = 0;
u16 *moves = gBattleMons[battlerId].moves;
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (scores[i] > bestScore)
{
bestScore = scores[i];
bestScoreId = i;
}
}
for (i = 0; i < MAX_MON_MOVES; i++)
{
// We expect move 'i', but it has the same best score as another move that we didn't expect.
if (scores[i] == scores[bestScoreId]
&& !aiAction->notMove
&& (aiAction->moveSlots & gBitTable[i])
&& !(aiAction->moveSlots & gBitTable[bestScoreId]))
{
Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: EXPECT_MOVE %S has the same best score(%d) as not expected MOVE %S", filename,
Move data unification (#3999) * Made gBattleMoves handle the InGame name and description of battle moves No more multiple arrays in separate, individual files. Note: -Keep an eye on Task_LearnedMove. * Reintroduced move names Misc: -Fixed Trick-or-Treat and Light of Ruin's expanded names. -Introduced a new field for Z-Move names, and a constant for their name length. -Added a few TODOs to GetBattleMoveName. -Updated GetMaxMoveName and GetZMoveName. There's no reason not to let GetBattleMoveName handle everything on its own. * Updated GetBattleMoveName to handle Z-Move Names Misc: -Removed pointless TODO about MOVE_NAME_LENGTH. -The compiler doesn't allow to have a move name with a value higher than MOVE_NAME_LENGTH, therefore it's pointless to worry about it. * Fixed a couple of expanded move names * Removed zMoveName variable of struct BattleMove and extended the name variable's size * Ditched no longer used MOVE_NAME_LENGTH constant * Corrected the names of the max moves I should have done this after updating the size of the name variable of the struct BattleMove, but I didn't think about it at all until Cancer Fairy indirectly gave me the idea. * Fixed U-turn's name * Brought back MOVE_NAME_LENGTH I think it doesn't make sense to have a Z_MOVE_NAME_LENGTH because the length in question is used for all battle moves, not just the Z-Moves. * Introduced a union for Move/Z-Move names in the struct BattleMove * Fixed the union for gBattleMoves move names Also updated GetBattleMoveName to properly handle Max Move names. Also also renamed the "zMoveName" variable to "bigMoveName" which better reflects its purpose. Z-Move names weren't the only thing it covered, since it also handles Max Move names. * Removed deprecated GetZMoveName and GetMaxMoveName * Reintroduced mention to gMoveNames in sGFRomHeader * Fixed move names and ported move descriptions * Fused the struct ContestMove into the struct BattleMove * Removed no longer used Z_MOVE_NAME_LENGTH constant * Renamed the struct BattleMove's bigMoveName variable and introduced macros to prettify move names * Reintroduced the contest parameters for Pokémon moves * Renamed gBattleMoves to gMovesInfo This is consistent with gSpeciesInfo, the array that contains most of the species data. * Renamed the BattleMove struct to MovesInfo This is consistent with the struct SpeciesInfo, which contains the variables used by the gSpeciesInfo array. * Removed empty lines separating battle params from contest params in gMovesInfo * Renamed MovesInfo to MoveInfo * Added Cancer Fairy's HANDLE_EXPANDED_MOVE_NAME macro Used to handle moves with expanded names in a more comfortable manner. Also fixed Trick-or-Treat's expanded name. * Renamed GetBattleMoveName to GetMoveName * Added a comment pointing out that the shared move descriptions are shared move descriptions * Re-aligned one of the escape characters of CHECK_MOVE_FLAG * Renamed the battle_moves.h file to moves_info.h instead for consistency's sake * Applied Eduardo's adjustments * Using compound string for regular move names as well, saving 1180 bytes and making their use consistent * Move description formatting * Updated Pursuit test after merge * Renamed the BATTLE_CATEGORY constants to DAMAGE_CATEGORY --------- Co-authored-by: Nephrite <thechurchofcage@gmail.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
2024-01-29 11:51:32 +00:00
aiAction->sourceLine, GetMoveName(moves[i]), scores[i], GetMoveName(moves[bestScoreId]));
}
// We DO NOT expect move 'i', but it has the same best score as another move.
if (scores[i] == scores[bestScoreId]
&& aiAction->notMove
&& (aiAction->moveSlots & gBitTable[i])
&& !(aiAction->moveSlots & gBitTable[bestScoreId]))
{
Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: NOT_EXPECT_MOVE %S has the same best score(%d) as MOVE %S", filename,
Move data unification (#3999) * Made gBattleMoves handle the InGame name and description of battle moves No more multiple arrays in separate, individual files. Note: -Keep an eye on Task_LearnedMove. * Reintroduced move names Misc: -Fixed Trick-or-Treat and Light of Ruin's expanded names. -Introduced a new field for Z-Move names, and a constant for their name length. -Added a few TODOs to GetBattleMoveName. -Updated GetMaxMoveName and GetZMoveName. There's no reason not to let GetBattleMoveName handle everything on its own. * Updated GetBattleMoveName to handle Z-Move Names Misc: -Removed pointless TODO about MOVE_NAME_LENGTH. -The compiler doesn't allow to have a move name with a value higher than MOVE_NAME_LENGTH, therefore it's pointless to worry about it. * Fixed a couple of expanded move names * Removed zMoveName variable of struct BattleMove and extended the name variable's size * Ditched no longer used MOVE_NAME_LENGTH constant * Corrected the names of the max moves I should have done this after updating the size of the name variable of the struct BattleMove, but I didn't think about it at all until Cancer Fairy indirectly gave me the idea. * Fixed U-turn's name * Brought back MOVE_NAME_LENGTH I think it doesn't make sense to have a Z_MOVE_NAME_LENGTH because the length in question is used for all battle moves, not just the Z-Moves. * Introduced a union for Move/Z-Move names in the struct BattleMove * Fixed the union for gBattleMoves move names Also updated GetBattleMoveName to properly handle Max Move names. Also also renamed the "zMoveName" variable to "bigMoveName" which better reflects its purpose. Z-Move names weren't the only thing it covered, since it also handles Max Move names. * Removed deprecated GetZMoveName and GetMaxMoveName * Reintroduced mention to gMoveNames in sGFRomHeader * Fixed move names and ported move descriptions * Fused the struct ContestMove into the struct BattleMove * Removed no longer used Z_MOVE_NAME_LENGTH constant * Renamed the struct BattleMove's bigMoveName variable and introduced macros to prettify move names * Reintroduced the contest parameters for Pokémon moves * Renamed gBattleMoves to gMovesInfo This is consistent with gSpeciesInfo, the array that contains most of the species data. * Renamed the BattleMove struct to MovesInfo This is consistent with the struct SpeciesInfo, which contains the variables used by the gSpeciesInfo array. * Removed empty lines separating battle params from contest params in gMovesInfo * Renamed MovesInfo to MoveInfo * Added Cancer Fairy's HANDLE_EXPANDED_MOVE_NAME macro Used to handle moves with expanded names in a more comfortable manner. Also fixed Trick-or-Treat's expanded name. * Renamed GetBattleMoveName to GetMoveName * Added a comment pointing out that the shared move descriptions are shared move descriptions * Re-aligned one of the escape characters of CHECK_MOVE_FLAG * Renamed the battle_moves.h file to moves_info.h instead for consistency's sake * Applied Eduardo's adjustments * Using compound string for regular move names as well, saving 1180 bytes and making their use consistent * Move description formatting * Updated Pursuit test after merge * Renamed the BATTLE_CATEGORY constants to DAMAGE_CATEGORY --------- Co-authored-by: Nephrite <thechurchofcage@gmail.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
2024-01-29 11:51:32 +00:00
aiAction->sourceLine, GetMoveName(moves[i]), scores[i], GetMoveName(moves[bestScoreId]));
}
}
}
static void PrintAiMoveLog(u32 battlerId, u32 moveSlot, u32 moveId, s32 totalScore)
{
s32 i, scoreFromLogs = 0;
if (!DATA.logAI) return;
if (DATA.aiLogPrintedForMove[battlerId] & gBitTable[moveSlot]) return;
DATA.aiLogPrintedForMove[battlerId] |= gBitTable[moveSlot];
Test_MgbaPrintf("Score Log for move %S:\n", GetMoveName(moveId));
for (i = 0; i < MAX_AI_LOG_LINES; i++)
{
struct AILogLine *log = &DATA.aiLogLines[battlerId][moveSlot][i];
if (log->file)
{
if (log->set)
{
scoreFromLogs = log->score;
Test_MgbaPrintf("%s:%d: = %d\n", log->file, log->line, log->score);
}
else if (log->score > 0)
{
scoreFromLogs += log->score;
Test_MgbaPrintf("%s:%d: +%d\n", log->file, log->line, log->score);
}
else
{
scoreFromLogs += log->score;
Test_MgbaPrintf("%s:%d: %d\n", log->file, log->line, log->score);
}
}
else
{
break;
}
}
if (scoreFromLogs != totalScore)
{
Test_ExitWithResult(TEST_RESULT_ERROR, SourceLine(0), ":LWarning! Score from logs(%d) is different than actual score(%d). Make sure all of the score adjustments use the ADJUST_SCORE macro\n", scoreFromLogs, totalScore);
}
Test_MgbaPrintf("Total: %d\n", totalScore);
}
static void ClearAiLog(u32 battlerId)
{
u32 i, j;
for (i = 0; i < MAX_MON_MOVES; i++)
{
struct AILogLine *logs = DATA.aiLogLines[battlerId][i];
for (j = 0; j < MAX_AI_LOG_LINES; j++)
memset(&logs[j], 0, sizeof(struct AILogLine));
}
DATA.aiLogPrintedForMove[battlerId] = 0;
}
void TestRunner_Battle_CheckAiMoveScores(u32 battlerId)
{
s32 i;
struct ExpectedAIAction *aiAction;
const char *filename = gTestRunnerState.test->filename;
s32 turn = gBattleResults.battleTurnCounter;
for (i = 0; i < MAX_AI_SCORE_COMPARISION_PER_TURN; i++)
{
struct ExpectedAiScore *scoreCtx = &DATA.expectedAiScores[battlerId][turn][i];
if (scoreCtx->set)
{
u32 moveId1 = gBattleMons[battlerId].moves[scoreCtx->moveSlot1];
s32 target = scoreCtx->target;
s32 *scores = gBattleStruct->aiFinalScore[battlerId][target];
if (scoreCtx->toValue)
{
PrintAiMoveLog(battlerId, scoreCtx->moveSlot1, moveId1, scores[scoreCtx->moveSlot1]);
if (!CheckComparision(scores[scoreCtx->moveSlot1], scoreCtx->value, scoreCtx->cmp))
{
Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Unmatched SCORE_%s_VAL %S %d, got %d",
Move data unification (#3999) * Made gBattleMoves handle the InGame name and description of battle moves No more multiple arrays in separate, individual files. Note: -Keep an eye on Task_LearnedMove. * Reintroduced move names Misc: -Fixed Trick-or-Treat and Light of Ruin's expanded names. -Introduced a new field for Z-Move names, and a constant for their name length. -Added a few TODOs to GetBattleMoveName. -Updated GetMaxMoveName and GetZMoveName. There's no reason not to let GetBattleMoveName handle everything on its own. * Updated GetBattleMoveName to handle Z-Move Names Misc: -Removed pointless TODO about MOVE_NAME_LENGTH. -The compiler doesn't allow to have a move name with a value higher than MOVE_NAME_LENGTH, therefore it's pointless to worry about it. * Fixed a couple of expanded move names * Removed zMoveName variable of struct BattleMove and extended the name variable's size * Ditched no longer used MOVE_NAME_LENGTH constant * Corrected the names of the max moves I should have done this after updating the size of the name variable of the struct BattleMove, but I didn't think about it at all until Cancer Fairy indirectly gave me the idea. * Fixed U-turn's name * Brought back MOVE_NAME_LENGTH I think it doesn't make sense to have a Z_MOVE_NAME_LENGTH because the length in question is used for all battle moves, not just the Z-Moves. * Introduced a union for Move/Z-Move names in the struct BattleMove * Fixed the union for gBattleMoves move names Also updated GetBattleMoveName to properly handle Max Move names. Also also renamed the "zMoveName" variable to "bigMoveName" which better reflects its purpose. Z-Move names weren't the only thing it covered, since it also handles Max Move names. * Removed deprecated GetZMoveName and GetMaxMoveName * Reintroduced mention to gMoveNames in sGFRomHeader * Fixed move names and ported move descriptions * Fused the struct ContestMove into the struct BattleMove * Removed no longer used Z_MOVE_NAME_LENGTH constant * Renamed the struct BattleMove's bigMoveName variable and introduced macros to prettify move names * Reintroduced the contest parameters for Pokémon moves * Renamed gBattleMoves to gMovesInfo This is consistent with gSpeciesInfo, the array that contains most of the species data. * Renamed the BattleMove struct to MovesInfo This is consistent with the struct SpeciesInfo, which contains the variables used by the gSpeciesInfo array. * Removed empty lines separating battle params from contest params in gMovesInfo * Renamed MovesInfo to MoveInfo * Added Cancer Fairy's HANDLE_EXPANDED_MOVE_NAME macro Used to handle moves with expanded names in a more comfortable manner. Also fixed Trick-or-Treat's expanded name. * Renamed GetBattleMoveName to GetMoveName * Added a comment pointing out that the shared move descriptions are shared move descriptions * Re-aligned one of the escape characters of CHECK_MOVE_FLAG * Renamed the battle_moves.h file to moves_info.h instead for consistency's sake * Applied Eduardo's adjustments * Using compound string for regular move names as well, saving 1180 bytes and making their use consistent * Move description formatting * Updated Pursuit test after merge * Renamed the BATTLE_CATEGORY constants to DAMAGE_CATEGORY --------- Co-authored-by: Nephrite <thechurchofcage@gmail.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
2024-01-29 11:51:32 +00:00
filename, scoreCtx->sourceLine, sCmpToStringTable[scoreCtx->cmp], GetMoveName(moveId1), scoreCtx->value, scores[scoreCtx->moveSlot1]);
}
}
else
{
u32 moveId2 = gBattleMons[battlerId].moves[scoreCtx->moveSlot2];
PrintAiMoveLog(battlerId, scoreCtx->moveSlot1, moveId1, scores[scoreCtx->moveSlot1]);
PrintAiMoveLog(battlerId, scoreCtx->moveSlot2, moveId2, scores[scoreCtx->moveSlot2]);
if (!CheckComparision(scores[scoreCtx->moveSlot1], scores[scoreCtx->moveSlot2], scoreCtx->cmp))
{
Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Unmatched SCORE_%s, got %S: %d, %S: %d",
Move data unification (#3999) * Made gBattleMoves handle the InGame name and description of battle moves No more multiple arrays in separate, individual files. Note: -Keep an eye on Task_LearnedMove. * Reintroduced move names Misc: -Fixed Trick-or-Treat and Light of Ruin's expanded names. -Introduced a new field for Z-Move names, and a constant for their name length. -Added a few TODOs to GetBattleMoveName. -Updated GetMaxMoveName and GetZMoveName. There's no reason not to let GetBattleMoveName handle everything on its own. * Updated GetBattleMoveName to handle Z-Move Names Misc: -Removed pointless TODO about MOVE_NAME_LENGTH. -The compiler doesn't allow to have a move name with a value higher than MOVE_NAME_LENGTH, therefore it's pointless to worry about it. * Fixed a couple of expanded move names * Removed zMoveName variable of struct BattleMove and extended the name variable's size * Ditched no longer used MOVE_NAME_LENGTH constant * Corrected the names of the max moves I should have done this after updating the size of the name variable of the struct BattleMove, but I didn't think about it at all until Cancer Fairy indirectly gave me the idea. * Fixed U-turn's name * Brought back MOVE_NAME_LENGTH I think it doesn't make sense to have a Z_MOVE_NAME_LENGTH because the length in question is used for all battle moves, not just the Z-Moves. * Introduced a union for Move/Z-Move names in the struct BattleMove * Fixed the union for gBattleMoves move names Also updated GetBattleMoveName to properly handle Max Move names. Also also renamed the "zMoveName" variable to "bigMoveName" which better reflects its purpose. Z-Move names weren't the only thing it covered, since it also handles Max Move names. * Removed deprecated GetZMoveName and GetMaxMoveName * Reintroduced mention to gMoveNames in sGFRomHeader * Fixed move names and ported move descriptions * Fused the struct ContestMove into the struct BattleMove * Removed no longer used Z_MOVE_NAME_LENGTH constant * Renamed the struct BattleMove's bigMoveName variable and introduced macros to prettify move names * Reintroduced the contest parameters for Pokémon moves * Renamed gBattleMoves to gMovesInfo This is consistent with gSpeciesInfo, the array that contains most of the species data. * Renamed the BattleMove struct to MovesInfo This is consistent with the struct SpeciesInfo, which contains the variables used by the gSpeciesInfo array. * Removed empty lines separating battle params from contest params in gMovesInfo * Renamed MovesInfo to MoveInfo * Added Cancer Fairy's HANDLE_EXPANDED_MOVE_NAME macro Used to handle moves with expanded names in a more comfortable manner. Also fixed Trick-or-Treat's expanded name. * Renamed GetBattleMoveName to GetMoveName * Added a comment pointing out that the shared move descriptions are shared move descriptions * Re-aligned one of the escape characters of CHECK_MOVE_FLAG * Renamed the battle_moves.h file to moves_info.h instead for consistency's sake * Applied Eduardo's adjustments * Using compound string for regular move names as well, saving 1180 bytes and making their use consistent * Move description formatting * Updated Pursuit test after merge * Renamed the BATTLE_CATEGORY constants to DAMAGE_CATEGORY --------- Co-authored-by: Nephrite <thechurchofcage@gmail.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
2024-01-29 11:51:32 +00:00
filename, scoreCtx->sourceLine, sCmpToStringTable[scoreCtx->cmp], GetMoveName(moveId1), scores[scoreCtx->moveSlot1], GetMoveName(moveId2), scores[scoreCtx->moveSlot2]);
}
}
}
}
// We need to make sure that the expected move has the best score. We have to rule out a situation where the expected move is used, but it has the same number of points as some other moves.
aiAction = &DATA.expectedAiActions[battlerId][DATA.aiActionsPlayed[battlerId]];
if (aiAction->actionSet && !aiAction->pass)
{
s32 target = aiAction->target;
// AI's move targets self, but points for this move are distributed for all other battlers
if (aiAction->target == battlerId)
{
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
{
if (i != battlerId && IsBattlerAlive(i))
CheckIfMaxScoreEqualExpectMove(battlerId, i, aiAction, filename);
}
}
else
{
CheckIfMaxScoreEqualExpectMove(battlerId, target, aiAction, filename);
}
}
}
static s32 TryExp(s32 i, s32 n, u32 battlerId, u32 oldExp, u32 newExp)
{
struct QueuedExpEvent *event;
s32 iMax = i + n;
for (; i < iMax; i++)
{
if (DATA.queuedEvents[i].type != QUEUED_EXP_EVENT)
continue;
event = &DATA.queuedEvents[i].as.exp;
if (event->battlerId == battlerId)
{
if (event->address <= 0xFFFF)
{
switch (event->type)
{
case EXP_EVENT_NEW_EXP:
if (event->address == newExp)
return i;
break;
case EXP_EVENT_DELTA_EXP:
if (event->address == 0)
return i;
else if ((s16)event->address == oldExp - newExp)
return i;
break;
}
}
else
{
switch (event->type)
{
case EXP_EVENT_NEW_EXP:
*(u32 *)(u32)(event->address) = newExp;
break;
case EXP_EVENT_DELTA_EXP:
*(s32 *)(u32)(event->address) = oldExp - newExp;
break;
}
return i;
}
}
}
return -1;
}
void TestRunner_Battle_RecordExp(u32 battlerId, u32 oldExp, u32 newExp)
{
s32 queuedEvent;
s32 match;
struct QueuedEvent *event;
if (DATA.queuedEvent == DATA.queuedEventsCount)
return;
event = &DATA.queuedEvents[DATA.queuedEvent];
switch (event->groupType)
{
case QUEUE_GROUP_NONE:
case QUEUE_GROUP_ONE_OF:
if (TryExp(DATA.queuedEvent, event->groupSize, battlerId, oldExp, newExp) != -1)
DATA.queuedEvent += event->groupSize;
break;
case QUEUE_GROUP_NONE_OF:
queuedEvent = DATA.queuedEvent;
do
{
if ((match = TryExp(queuedEvent, event->groupSize, battlerId, oldExp, newExp)) != -1)
{
const char *filename = gTestRunnerState.test->filename;
u32 line = SourceLine(DATA.queuedEvents[match].sourceLineOffset);
Test_ExitWithResult(TEST_RESULT_FAIL, line, ":L%s:%d: Matched EXPERIENCE_BAR", filename, line);
}
queuedEvent += event->groupSize;
if (queuedEvent == DATA.queuedEventsCount)
break;
event = &DATA.queuedEvents[queuedEvent];
if (event->groupType == QUEUE_GROUP_NONE_OF)
continue;
if (TryExp(queuedEvent, event->groupSize, battlerId, oldExp, newExp) != -1)
DATA.queuedEvent = queuedEvent + event->groupSize;
} while (FALSE);
break;
}
}
static s32 TryMessage(s32 i, s32 n, const u8 *string)
{
s32 j, k;
struct QueuedMessageEvent *event;
s32 iMax = i + n;
for (; i < iMax; i++)
{
if (DATA.queuedEvents[i].type != QUEUED_MESSAGE_EVENT)
continue;
event = &DATA.queuedEvents[i].as.message;
// Test_MgbaPrintf("Looking for: %S Found: %S\n", event->pattern, string); // Useful for debugging.
for (j = k = 0; ; j++, k++)
{
if (event->pattern[k] == CHAR_SPACE)
{
switch (string[j])
{
case CHAR_SPACE:
case CHAR_PROMPT_SCROLL:
case CHAR_PROMPT_CLEAR:
case CHAR_NEWLINE:
j++;
k++;
break;
}
}
if (event->pattern[k] == EOS)
{
// Consume any trailing '\p'.
if (string[j] == CHAR_PROMPT_CLEAR)
j++;
}
if (string[j] != event->pattern[k])
{
break;
}
else if (string[j] == EOS)
{
return i;
}
}
}
return -1;
}
void TestRunner_Battle_RecordMessage(const u8 *string)
{
s32 queuedEvent;
s32 match;
struct QueuedEvent *event;
if (DATA.queuedEvent == DATA.queuedEventsCount)
return;
event = &DATA.queuedEvents[DATA.queuedEvent];
switch (event->groupType)
{
case QUEUE_GROUP_NONE:
case QUEUE_GROUP_ONE_OF:
if (TryMessage(DATA.queuedEvent, event->groupSize, string) != -1)
DATA.queuedEvent += event->groupSize;
break;
case QUEUE_GROUP_NONE_OF:
queuedEvent = DATA.queuedEvent;
do
{
if ((match = TryMessage(queuedEvent, event->groupSize, string)) != -1)
{
const char *filename = gTestRunnerState.test->filename;
u32 line = SourceLine(DATA.queuedEvents[match].sourceLineOffset);
Test_ExitWithResult(TEST_RESULT_FAIL, line, ":L%s:%d: Matched MESSAGE", filename, line);
}
queuedEvent += event->groupSize;
if (queuedEvent == DATA.queuedEventsCount)
break;
event = &DATA.queuedEvents[queuedEvent];
if (event->groupType == QUEUE_GROUP_NONE_OF)
continue;
if (TryMessage(queuedEvent, event->groupSize, string) != -1)
DATA.queuedEvent = queuedEvent + event->groupSize;
} while (FALSE);
break;
}
}
static s32 TryStatus(s32 i, s32 n, u32 battlerId, u32 status1)
{
struct QueuedStatusEvent *event;
s32 iMax = i + n;
for (; i < iMax; i++)
{
if (DATA.queuedEvents[i].type != QUEUED_STATUS_EVENT)
continue;
event = &DATA.queuedEvents[i].as.status;
if (event->battlerId == battlerId)
{
if (event->mask == 0 && status1 == STATUS1_NONE)
return i;
else if (event->mask & status1)
return i;
}
}
return -1;
}
void TestRunner_Battle_RecordStatus1(u32 battlerId, u32 status1)
{
s32 queuedEvent;
s32 match;
struct QueuedEvent *event;
if (DATA.queuedEvent == DATA.queuedEventsCount)
return;
event = &DATA.queuedEvents[DATA.queuedEvent];
switch (event->groupType)
{
case QUEUE_GROUP_NONE:
case QUEUE_GROUP_ONE_OF:
if (TryStatus(DATA.queuedEvent, event->groupSize, battlerId, status1) != -1)
DATA.queuedEvent += event->groupSize;
break;
case QUEUE_GROUP_NONE_OF:
queuedEvent = DATA.queuedEvent;
do
{
if ((match = TryStatus(queuedEvent, event->groupSize, battlerId, status1)) != -1)
{
const char *filename = gTestRunnerState.test->filename;
u32 line = SourceLine(DATA.queuedEvents[match].sourceLineOffset);
Test_ExitWithResult(TEST_RESULT_FAIL, line, ":L%s:%d: Matched STATUS_ICON", filename, line);
}
queuedEvent += event->groupSize;
if (queuedEvent == DATA.queuedEventsCount)
break;
event = &DATA.queuedEvents[queuedEvent];
if (event->groupType == QUEUE_GROUP_NONE_OF)
continue;
if (TryStatus(queuedEvent, event->groupSize, battlerId, status1) != -1)
DATA.queuedEvent = queuedEvent + event->groupSize;
} while (FALSE);
break;
}
}
static const char *const sEventTypeMacros[] =
{
[QUEUED_ABILITY_POPUP_EVENT] = "ABILITY_POPUP",
[QUEUED_ANIMATION_EVENT] = "ANIMATION",
[QUEUED_HP_EVENT] = "HP_BAR",
[QUEUED_EXP_EVENT] = "EXPERIENCE_BAR",
[QUEUED_MESSAGE_EVENT] = "MESSAGE",
[QUEUED_STATUS_EVENT] = "STATUS_ICON",
};
void TestRunner_Battle_AfterLastTurn(void)
{
const struct BattleTest *test = GetBattleTest();
if (DATA.turns - 1 != DATA.lastActionTurn)
{
const char *filename = gTestRunnerState.test->filename;
Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: %d TURNs specified, but %d ran", filename, SourceLine(0), DATA.turns, DATA.lastActionTurn + 1);
}
while (DATA.queuedEvent < DATA.queuedEventsCount
&& DATA.queuedEvents[DATA.queuedEvent].groupType == QUEUE_GROUP_NONE_OF)
{
DATA.queuedEvent += DATA.queuedEvents[DATA.queuedEvent].groupSize;
}
if (DATA.queuedEvent != DATA.queuedEventsCount)
{
const char *filename = gTestRunnerState.test->filename;
u32 line = SourceLine(DATA.queuedEvents[DATA.queuedEvent].sourceLineOffset);
const char *macro = sEventTypeMacros[DATA.queuedEvents[DATA.queuedEvent].type];
Test_ExitWithResult(TEST_RESULT_FAIL, line, ":L%s:%d: Unmatched %s", filename, line, macro);
}
STATE->runThen = TRUE;
STATE->runFinally = STATE->runParameter + 1 == STATE->parameters && STATE->runTrial + 1 >= STATE->trials;
InvokeTestFunction(test);
STATE->runThen = FALSE;
STATE->runFinally = FALSE;
}
2023-06-22 15:08:15 +01:00
static void TearDownBattle(void)
{
FreeMonSpritesGfx();
FreeBattleSpritesData();
FreeBattleResources();
FreeAllWindowBuffers();
}
static void CB2_BattleTest_NextParameter(void)
{
if (++STATE->runParameter >= STATE->parameters)
2023-06-20 19:46:02 +01:00
{
SetMainCallback2(CB2_TestRunner);
ClearFlagAfterTest();
2023-06-20 19:46:02 +01:00
}
else
2023-06-20 19:46:02 +01:00
{
STATE->trials = 0;
BattleTest_Run(gTestRunnerState.test->data);
2023-06-20 19:46:02 +01:00
}
}
static inline rng_value_t MakeRngValue(const u16 seed)
{
#if HQ_RANDOM == TRUE
int i;
rng_value_t result = {0, 0, seed, 1};
for (i = 0; i < 16; i++)
{
_SFC32_Next(&result);
}
return result;
#else
return ISO_RANDOMIZE1(seed);
#endif
}
static void CB2_BattleTest_NextTrial(void)
{
ClearFlagAfterTest();
2023-06-22 15:08:15 +01:00
TearDownBattle();
SetMainCallback2(CB2_BattleTest_NextParameter);
switch (gTestRunnerState.result)
{
case TEST_RESULT_FAIL:
break;
case TEST_RESULT_PASS:
STATE->observedRatio += STATE->trialRatio;
break;
default:
return;
}
if (STATE->rngTag)
STATE->trialRatio = 0;
if (++STATE->runTrial < STATE->trials)
{
PrintTestName();
gTestRunnerState.result = TEST_RESULT_PASS;
DATA.recordedBattle.rngSeed = MakeRngValue(STATE->runTrial);
DATA.queuedEvent = 0;
DATA.lastActionTurn = 0;
SetVariablesForRecordedBattle(&DATA.recordedBattle);
SetMainCallback2(CB2_InitBattle);
}
else
{
// This is a tolerance of +/- ~2%.
if (abs(STATE->observedRatio - STATE->expectedRatio) <= Q_4_12(0.02))
gTestRunnerState.result = TEST_RESULT_PASS;
else
Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: Expected %q passes/successes, observed %q", gTestRunnerState.test->filename, SourceLine(0), STATE->expectedRatio, STATE->observedRatio);
}
}
static void BattleTest_TearDown(void *data)
{
// Free resources that aren't cleaned up when the battle was
// aborted unexpectedly.
ClearFlagAfterTest();
if (STATE->tearDownBattle)
TearDownBattle();
}
static bool32 BattleTest_CheckProgress(void *data)
{
bool32 madeProgress
= STATE->checkProgressParameter < STATE->runParameter
|| STATE->checkProgressTrial < STATE->runTrial
|| STATE->checkProgressTurn < gBattleResults.battleTurnCounter;
STATE->checkProgressParameter = STATE->runParameter;
STATE->checkProgressTrial = STATE->runTrial;
STATE->checkProgressTurn = gBattleResults.battleTurnCounter;
return madeProgress;
}
static bool32 BattleTest_HandleExitWithResult(void *data, enum TestResult result)
{
if (result != TEST_RESULT_ASSUMPTION_FAIL
&& result != TEST_RESULT_INVALID
&& result != TEST_RESULT_ERROR
&& result != TEST_RESULT_TIMEOUT
&& STATE->runTrial < STATE->trials)
{
SetMainCallback2(CB2_BattleTest_NextTrial);
return TRUE;
}
else
{
STATE->tearDownBattle = TRUE;
return FALSE;
}
}
void Randomly(u32 sourceLine, u32 passes, u32 trials, struct RandomlyContext ctx)
{
const struct BattleTest *test = GetBattleTest();
2023-06-20 19:46:02 +01:00
INVALID_IF(STATE->trials != 0, "PASSES_RANDOMLY can only be used once per test");
INVALID_IF(test->resultsSize > 0 && STATE->parametersCount > 1, "PASSES_RANDOMLY is incompatible with results");
INVALID_IF(passes > trials, "%d passes specified, but only %d trials", passes, trials);
STATE->rngTag = ctx.tag;
2023-07-19 16:44:21 +01:00
STATE->rngTrialOffset = 0;
STATE->runTrial = 0;
STATE->expectedRatio = Q_4_12(passes) / trials;
STATE->observedRatio = 0;
if (STATE->rngTag)
{
STATE->trials = 1;
STATE->trialRatio = Q_4_12(1);
}
else
{
const rng_value_t defaultSeed = RNG_SEED_DEFAULT;
INVALID_IF(RngSeedNotDefault(&DATA.recordedBattle.rngSeed), "RNG seed already set");
STATE->trials = 50;
STATE->trialRatio = Q_4_12(1) / STATE->trials;
DATA.recordedBattle.rngSeed = defaultSeed;
}
}
void RNGSeed_(u32 sourceLine, rng_value_t seed)
{
INVALID_IF(RngSeedNotDefault(&DATA.recordedBattle.rngSeed), "RNG seed already set");
DATA.recordedBattle.rngSeed = seed;
}
void AIFlags_(u32 sourceLine, u32 flags)
{
INVALID_IF(!IsAITest(), "AI_FLAGS is usable only in AI_SINGLE_BATTLE_TEST & AI_DOUBLE_BATTLE_TEST");
DATA.recordedBattle.AI_scripts = flags;
DATA.hasAI = TRUE;
}
void AILogScores(u32 sourceLine)
{
INVALID_IF(!IsAITest(), "AI_LOG is usable only in AI_SINGLE_BATTLE_TEST & AI_DOUBLE_BATTLE_TEST");
DATA.logAI = TRUE;
}
const struct TestRunner gBattleTestRunner =
{
.estimateCost = BattleTest_EstimateCost,
.setUp = BattleTest_SetUp,
.run = BattleTest_Run,
.tearDown = BattleTest_TearDown,
.checkProgress = BattleTest_CheckProgress,
.handleExitWithResult = BattleTest_HandleExitWithResult,
};
void SetFlagForTest(u32 sourceLine, u16 flagId)
{
INVALID_IF(DATA.flagId != 0, "FLAG can only be set once per test");
DATA.flagId = flagId;
FlagSet(flagId);
}
void ClearFlagAfterTest(void)
{
if (DATA.flagId != 0)
{
FlagClear(DATA.flagId);
DATA.flagId = 0;
}
}
void OpenPokemon(u32 sourceLine, u32 side, u32 species)
{
s32 i, data;
u8 *partySize;
struct Pokemon *party;
INVALID_IF(species >= SPECIES_EGG, "Invalid species: %d", species);
Species Simplifier™ - Part 3 (#3562) * Added SpeciesInfo fields # Conflicts: # include/pokemon.h * Added missing comments * Renamed gigantamax to isGigantamax * Converted species flags to agbcc bit flags * Added padding after flags * Pikachu form config * Dex text * Reordered Paradox flag * Missing Pikachu ifdef comments * Moved P_GEN_x_POKEMON settings to the species_families file * Gen 1 Dex entries * Renamed file to species_enabled.h * Prevent Pokémon from evolving if their evolution is disabled * Prevent Pokémon from being affected by Eviolite if their evolution is disabled * Prevent Pokémon offspring if they're disabled * Corrected imprementation of evolution prevention It could potentially overwrite items by accident * Cross-evo configs * Gen 2 dex entries * Changed cry table to use assignable IDs # Conflicts: # include/pokemon.h * Form table pointers * Beginning support for moving data to gSpeciesInfo * Pokédexes reads text from gSpeciesInfo * Reading species names from gSpeciesInfo * Fixed erroneous copy-paste * Moved Bulbasaur to Raichu Family data do gSpeciesinfo * Added female pic sizes * Macros for Front, Back, Palette and Learnsets * Sandshrew to Dugtrio Families * Obtains Height/Weight from gSpeciesInfo * Read category from gSpeciesInfo * Removed unused data from pokedex_entries.h * Moved Gen 1 data to gSpeciesInfo * Moved Gen 2 data to gSpeciesInfo + fixed Ursaluna Bloodmoon not producing Teddiursa eggs * Moved Gen 3 data to gSpeciesInfo * Moved Gen 4 data to gSpeciesInfo * HGSS dex properly shows form sprites * Fixed data type for GetSpeciesHeight/Weight * Reading Pokédex Size data from gSpeciesInfo * Cleaned up mon graphic decompression function * Reading Front Pics gSpeciesInfo * Reading Back Pics gSpeciesInfo * Updated uses of GetMonSpritePalStruct to allow loading the data directly instead of reading the struct * Reading palettes from gSpeciesInfo * Reading animation delay from gSpeciesInfo * Fixed Pokémon Debug Menu not reading animation delay * Reading front anim Id from gSpeciesInfo * Reading back anim Id from gSpeciesInfo * Reading Animation Frames from gSpeciesInfo * Reading Icon Sprites from gSpeciesInfo * Reading Icon Palette IDs from gSpeciesInfo * Reading Footprints from gSpeciesInfo * Reading Mon Pic sizes from gSpeciesInfo * Separate mon pic sizes for female mon * Reading Level Up Learnsets from gSpeciesInfo * Reading Teachable Learnsets from gSpeciesInfo * Reading National Dex Number from gSpeciesInfo * Reading Enemy Mon Elevation from gSpeciesInfo * Reading Evolutions from gSpeciesInfo * Reading form table from gSpeciesInfo * Fixed button label inconsistency in pokémon debug menu * Reading form change table from gSpeciesInfo * Removed need to define mon graphics in include/graphics.h * Reordered mon graphics by family * Fixed National dex number offset * gFrontierBannedSpecies now checks IsSpeciesEnabled * Added 3 more toggles for Fusions, Primal Reversions and Ultra Burst * Gen 3 & 4 family toggles for dex entries * Fixed missing Mr. Rime data * Gen 1 dex descriptions as compound strings * Gen 2 dex descriptions as compound strings * Gen 3 dex descriptions as compound strings * Gen 4 dex descriptions as compound strings * Fixed compile in agbcc * Moved Gen 5 data to gSpeciesInfo * Using PLACEHOLDER_ANIM_SINGLE_FRAME when needed (same hash) * Renamed PLACEHOLDER_TWO_FRAME_ANIMATION to PLACEHOLDER_ANIM_TWO_FRAMES * Fixed capitalization for front pic anims * Fixed `CanEvolve` * Moved Gen 6 data to gSpeciesInfo * Gen 7 without macros * Gen 8 without macros * Gen 9 without macros * Fixed Gen 9 sound data * Finished moving Gen 7 data in macros (by Alex) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> * Ultra Necrozma is now included in the fusion forms define * Cries are now properly excluded from the rom when disabling species * Removed P_GEN_x_POKEMON from form_change_tables.h * Fixed Yanmega's cry ignoring the P_GEN_4_CROSS_EVOS setting * Added P_NEW_EVOS_IN_REGIONAL_DEX setting * Automatic ASSUMPTION_FAIL when used species are disabled * Finished moving Gen 8 data in macros (by Alex) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> * Expanded Alcremie macro * Finished moving Gen 9 data in macros (by Alex) * Fixed missing data for Komala and Marshadow * Form species id tables use family toggles + added missing Ursaluna form species id table * Fixed Genesect palettes * Fixed Flabébé family graphics * Fixed Minior Graphics * Fixed missing Calyrex data * Family toggles in mon graphcis (By Jasper/Bassoonian) Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> * Fixed missing Ogerpon data * Ordered Level Up Learnsets (missing toggles) Also added White Basculin learnset * Added more Minior synonyms * Fixed enamorus order * Reordered teachable learnsets * Removed sSpeciesXXX level up learnsets + reordered * Removed unneeded assumes * Removed unused extra animations * Family toggles in mon level up learnsets (By Jasper/Bassoonian) Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> * Fixed Phione graphics inclusion * Family toggles for Teachable Learnsets Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> * Ordered Front Pic Anims * Family toggles for front pic animations Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> * Fixed HGSS dex incorrectly showing the wrong mon when catching it * Family toggles for Egg Moves + reodered Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> * Fixed HGSS not checking for null footprints * Review changes * Fixed Female difference mon not using their macros * Fixed Alcremie Macro * Fixed compile error when disabling the Bulbasaur family * Fixed Turtwig and Chimchar family + Houndour and Mareep cries not being excluded properly * Re-added missing Egg graphical data * Fixed crash during the Birch Intro when Lotad is disabled * Renamed PALLETE to PALETTES * Removed extra spacing for YOffsets * Fixed compile when only disabling cross-gen evolutions * Added Tera Forms define (Ogerpon and Terapagos) * Fixed Necrozma macro * Removed .noFlip = FALSE * Fixed dex number in summary screen for forms when nat dex is disabled * Fixed Paldean form paths --------- Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com>
2023-12-08 14:05:10 +00:00
ASSUMPTION_FAIL_IF(!IsSpeciesEnabled(species), "Species disabled: %d", species);
if (side == B_SIDE_PLAYER)
{
partySize = &DATA.playerPartySize;
party = DATA.recordedBattle.playerParty;
}
else
{
partySize = &DATA.opponentPartySize;
party = DATA.recordedBattle.opponentParty;
}
Species Simplifier™ - Part 3 (#3562) * Added SpeciesInfo fields # Conflicts: # include/pokemon.h * Added missing comments * Renamed gigantamax to isGigantamax * Converted species flags to agbcc bit flags * Added padding after flags * Pikachu form config * Dex text * Reordered Paradox flag * Missing Pikachu ifdef comments * Moved P_GEN_x_POKEMON settings to the species_families file * Gen 1 Dex entries * Renamed file to species_enabled.h * Prevent Pokémon from evolving if their evolution is disabled * Prevent Pokémon from being affected by Eviolite if their evolution is disabled * Prevent Pokémon offspring if they're disabled * Corrected imprementation of evolution prevention It could potentially overwrite items by accident * Cross-evo configs * Gen 2 dex entries * Changed cry table to use assignable IDs # Conflicts: # include/pokemon.h * Form table pointers * Beginning support for moving data to gSpeciesInfo * Pokédexes reads text from gSpeciesInfo * Reading species names from gSpeciesInfo * Fixed erroneous copy-paste * Moved Bulbasaur to Raichu Family data do gSpeciesinfo * Added female pic sizes * Macros for Front, Back, Palette and Learnsets * Sandshrew to Dugtrio Families * Obtains Height/Weight from gSpeciesInfo * Read category from gSpeciesInfo * Removed unused data from pokedex_entries.h * Moved Gen 1 data to gSpeciesInfo * Moved Gen 2 data to gSpeciesInfo + fixed Ursaluna Bloodmoon not producing Teddiursa eggs * Moved Gen 3 data to gSpeciesInfo * Moved Gen 4 data to gSpeciesInfo * HGSS dex properly shows form sprites * Fixed data type for GetSpeciesHeight/Weight * Reading Pokédex Size data from gSpeciesInfo * Cleaned up mon graphic decompression function * Reading Front Pics gSpeciesInfo * Reading Back Pics gSpeciesInfo * Updated uses of GetMonSpritePalStruct to allow loading the data directly instead of reading the struct * Reading palettes from gSpeciesInfo * Reading animation delay from gSpeciesInfo * Fixed Pokémon Debug Menu not reading animation delay * Reading front anim Id from gSpeciesInfo * Reading back anim Id from gSpeciesInfo * Reading Animation Frames from gSpeciesInfo * Reading Icon Sprites from gSpeciesInfo * Reading Icon Palette IDs from gSpeciesInfo * Reading Footprints from gSpeciesInfo * Reading Mon Pic sizes from gSpeciesInfo * Separate mon pic sizes for female mon * Reading Level Up Learnsets from gSpeciesInfo * Reading Teachable Learnsets from gSpeciesInfo * Reading National Dex Number from gSpeciesInfo * Reading Enemy Mon Elevation from gSpeciesInfo * Reading Evolutions from gSpeciesInfo * Reading form table from gSpeciesInfo * Fixed button label inconsistency in pokémon debug menu * Reading form change table from gSpeciesInfo * Removed need to define mon graphics in include/graphics.h * Reordered mon graphics by family * Fixed National dex number offset * gFrontierBannedSpecies now checks IsSpeciesEnabled * Added 3 more toggles for Fusions, Primal Reversions and Ultra Burst * Gen 3 & 4 family toggles for dex entries * Fixed missing Mr. Rime data * Gen 1 dex descriptions as compound strings * Gen 2 dex descriptions as compound strings * Gen 3 dex descriptions as compound strings * Gen 4 dex descriptions as compound strings * Fixed compile in agbcc * Moved Gen 5 data to gSpeciesInfo * Using PLACEHOLDER_ANIM_SINGLE_FRAME when needed (same hash) * Renamed PLACEHOLDER_TWO_FRAME_ANIMATION to PLACEHOLDER_ANIM_TWO_FRAMES * Fixed capitalization for front pic anims * Fixed `CanEvolve` * Moved Gen 6 data to gSpeciesInfo * Gen 7 without macros * Gen 8 without macros * Gen 9 without macros * Fixed Gen 9 sound data * Finished moving Gen 7 data in macros (by Alex) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> * Ultra Necrozma is now included in the fusion forms define * Cries are now properly excluded from the rom when disabling species * Removed P_GEN_x_POKEMON from form_change_tables.h * Fixed Yanmega's cry ignoring the P_GEN_4_CROSS_EVOS setting * Added P_NEW_EVOS_IN_REGIONAL_DEX setting * Automatic ASSUMPTION_FAIL when used species are disabled * Finished moving Gen 8 data in macros (by Alex) Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> * Expanded Alcremie macro * Finished moving Gen 9 data in macros (by Alex) * Fixed missing data for Komala and Marshadow * Form species id tables use family toggles + added missing Ursaluna form species id table * Fixed Genesect palettes * Fixed Flabébé family graphics * Fixed Minior Graphics * Fixed missing Calyrex data * Family toggles in mon graphcis (By Jasper/Bassoonian) Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> * Fixed missing Ogerpon data * Ordered Level Up Learnsets (missing toggles) Also added White Basculin learnset * Added more Minior synonyms * Fixed enamorus order * Reordered teachable learnsets * Removed sSpeciesXXX level up learnsets + reordered * Removed unneeded assumes * Removed unused extra animations * Family toggles in mon level up learnsets (By Jasper/Bassoonian) Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> * Fixed Phione graphics inclusion * Family toggles for Teachable Learnsets Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> * Ordered Front Pic Anims * Family toggles for front pic animations Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> * Fixed HGSS dex incorrectly showing the wrong mon when catching it * Family toggles for Egg Moves + reodered Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> * Fixed HGSS not checking for null footprints * Review changes * Fixed Female difference mon not using their macros * Fixed Alcremie Macro * Fixed compile error when disabling the Bulbasaur family * Fixed Turtwig and Chimchar family + Houndour and Mareep cries not being excluded properly * Re-added missing Egg graphical data * Fixed crash during the Birch Intro when Lotad is disabled * Renamed PALLETE to PALETTES * Removed extra spacing for YOffsets * Fixed compile when only disabling cross-gen evolutions * Added Tera Forms define (Ogerpon and Terapagos) * Fixed Necrozma macro * Removed .noFlip = FALSE * Fixed dex number in summary screen for forms when nat dex is disabled * Fixed Paldean form paths --------- Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com>
2023-12-08 14:05:10 +00:00
INVALID_IF(*partySize >= PARTY_SIZE, "Too many Pokemon in party");
DATA.currentSide = side;
DATA.currentPartyIndex = *partySize;
DATA.currentMon = &party[DATA.currentPartyIndex];
DATA.gender = 0xFF; // Male
DATA.nature = NATURE_HARDY;
(*partySize)++;
CreateMon(DATA.currentMon, species, 100, 0, TRUE, 0, OT_ID_PRESET, 0);
data = MOVE_NONE;
for (i = 0; i < MAX_MON_MOVES; i++)
SetMonData(DATA.currentMon, MON_DATA_MOVE1 + i, &data);
}
// (sNaturePersonalities[i] % NUM_NATURES) == i
// (sNaturePersonalities[i] & 0xFF) == 0
// NOTE: Using 25 << 8 rather than 0 << 8 to prevent shiny females.
static const u16 sNaturePersonalities[NUM_NATURES] =
{
25 << 8, 21 << 8, 17 << 8, 13 << 8, 9 << 8,
5 << 8, 1 << 8, 22 << 8, 18 << 8, 14 << 8,
10 << 8, 6 << 8, 2 << 8, 23 << 8, 19 << 8,
15 << 8, 11 << 8, 7 << 8, 3 << 8, 24 << 8,
20 << 8, 16 << 8, 12 << 8, 8 << 8, 4 << 8,
};
static u32 GenerateNature(u32 nature, u32 offset)
{
if (offset <= nature)
nature -= offset;
else
nature = nature + NUM_NATURES - offset;
return sNaturePersonalities[nature];
}
void ClosePokemon(u32 sourceLine)
{
s32 i;
u32 data;
INVALID_IF(DATA.hasExplicitSpeeds && !(DATA.explicitSpeeds[DATA.currentSide] & (1 << DATA.currentPartyIndex)), "Speed required");
for (i = 0; i < STATE->battlersCount; i++)
{
if ((i & BIT_SIDE) == DATA.currentSide
&& DATA.currentMonIndexes[i] == DATA.currentPartyIndex)
{
INVALID_IF(GetMonData(DATA.currentMon, MON_DATA_HP) == 0, "Battlers cannot be fainted");
}
}
data = FALSE;
SetMonData(DATA.currentMon, MON_DATA_IS_SHINY, &data);
UpdateMonPersonality(&DATA.currentMon->box, GenerateNature(DATA.nature, DATA.gender % NUM_NATURES) | DATA.gender);
DATA.currentMon = NULL;
}
void Gender_(u32 sourceLine, u32 gender)
{
const struct SpeciesInfo *info;
INVALID_IF(!DATA.currentMon, "Gender outside of PLAYER/OPPONENT");
info = &gSpeciesInfo[GetMonData(DATA.currentMon, MON_DATA_SPECIES)];
switch (gender)
{
case MON_MALE:
DATA.gender = 0xFF;
INVALID_IF(info->genderRatio == MON_GENDERLESS || info->genderRatio == MON_FEMALE, "Illegal male");
break;
case MON_FEMALE:
DATA.gender = 0x00;
INVALID_IF(info->genderRatio == MON_GENDERLESS || info->genderRatio == MON_MALE, "Illegal female");
break;
case MON_GENDERLESS:
INVALID_IF(info->genderRatio != gender, "Illegal genderless");
break;
}
}
void Nature_(u32 sourceLine, u32 nature)
{
INVALID_IF(!DATA.currentMon, "Nature outside of PLAYER/OPPONENT");
INVALID_IF(nature >= NUM_NATURES, "Illegal nature: %d", nature);
DATA.nature = nature;
}
void Ability_(u32 sourceLine, u32 ability)
{
s32 i;
u32 species;
const struct SpeciesInfo *info;
INVALID_IF(!DATA.currentMon, "Ability outside of PLAYER/OPPONENT");
INVALID_IF(ability >= ABILITIES_COUNT, "Illegal ability id: %d", ability);
species = GetMonData(DATA.currentMon, MON_DATA_SPECIES);
info = &gSpeciesInfo[species];
for (i = 0; i < NUM_ABILITY_SLOTS; i++)
{
if (info->abilities[i] == ability)
{
SetMonData(DATA.currentMon, MON_DATA_ABILITY_NUM, &i);
break;
}
}
// Store forced ability to be set when the battle starts if invalid.
if (i == NUM_ABILITY_SLOTS)
{
DATA.forcedAbilities[DATA.currentSide][DATA.currentPartyIndex] = ability;
}
}
void Level_(u32 sourceLine, u32 level)
{
// TODO: Preserve any explicitly-set stats.
u32 species = GetMonData(DATA.currentMon, MON_DATA_SPECIES);
INVALID_IF(!DATA.currentMon, "Level outside of PLAYER/OPPONENT");
INVALID_IF(level == 0 || level > MAX_LEVEL, "Illegal level: %d", level);
SetMonData(DATA.currentMon, MON_DATA_LEVEL, &level);
SetMonData(DATA.currentMon, MON_DATA_EXP, &gExperienceTables[gSpeciesInfo[species].growthRate][level]);
2023-03-24 13:04:38 +00:00
CalculateMonStats(DATA.currentMon);
}
void MaxHP_(u32 sourceLine, u32 maxHP)
{
INVALID_IF(!DATA.currentMon, "MaxHP outside of PLAYER/OPPONENT");
INVALID_IF(maxHP == 0, "Illegal max HP: %d", maxHP);
SetMonData(DATA.currentMon, MON_DATA_MAX_HP, &maxHP);
}
void HP_(u32 sourceLine, u32 hp)
{
INVALID_IF(!DATA.currentMon, "HP outside of PLAYER/OPPONENT");
if (hp > GetMonData(DATA.currentMon, MON_DATA_MAX_HP))
SetMonData(DATA.currentMon, MON_DATA_MAX_HP, &hp);
SetMonData(DATA.currentMon, MON_DATA_HP, &hp);
}
void Attack_(u32 sourceLine, u32 attack)
{
INVALID_IF(!DATA.currentMon, "Attack outside of PLAYER/OPPONENT");
INVALID_IF(attack == 0, "Illegal attack: %d", attack);
SetMonData(DATA.currentMon, MON_DATA_ATK, &attack);
}
void Defense_(u32 sourceLine, u32 defense)
{
INVALID_IF(!DATA.currentMon, "Defense outside of PLAYER/OPPONENT");
INVALID_IF(defense == 0, "Illegal defense: %d", defense);
SetMonData(DATA.currentMon, MON_DATA_DEF, &defense);
}
void SpAttack_(u32 sourceLine, u32 spAttack)
{
INVALID_IF(!DATA.currentMon, "SpAttack outside of PLAYER/OPPONENT");
INVALID_IF(spAttack == 0, "Illegal special attack: %d", spAttack);
SetMonData(DATA.currentMon, MON_DATA_SPATK, &spAttack);
}
void SpDefense_(u32 sourceLine, u32 spDefense)
{
INVALID_IF(!DATA.currentMon, "SpDefense outside of PLAYER/OPPONENT");
INVALID_IF(spDefense == 0, "Illegal special defense: %d", spDefense);
SetMonData(DATA.currentMon, MON_DATA_SPDEF, &spDefense);
}
void Speed_(u32 sourceLine, u32 speed)
{
INVALID_IF(!DATA.currentMon, "Speed outside of PLAYER/OPPONENT");
INVALID_IF(speed == 0, "Illegal speed: %d", speed);
SetMonData(DATA.currentMon, MON_DATA_SPEED, &speed);
DATA.hasExplicitSpeeds = TRUE;
DATA.explicitSpeeds[DATA.currentSide] |= 1 << DATA.currentPartyIndex;
}
void HPIV_(u32 sourceLine, u32 hpIV)
{
INVALID_IF(!DATA.currentMon, "HP IV outside of PLAYER/OPPONENT");
INVALID_IF(hpIV > MAX_PER_STAT_IVS, "Illegal HP IV: %d", hpIV);
SetMonData(DATA.currentMon, MON_DATA_HP_IV, &hpIV);
}
void AttackIV_(u32 sourceLine, u32 attackIV)
{
INVALID_IF(!DATA.currentMon, "Attack IV outside of PLAYER/OPPONENT");
INVALID_IF(attackIV > MAX_PER_STAT_IVS, "Illegal attack IV: %d", attackIV);
SetMonData(DATA.currentMon, MON_DATA_ATK_IV, &attackIV);
}
void DefenseIV_(u32 sourceLine, u32 defenseIV)
{
INVALID_IF(!DATA.currentMon, "Defense IV outside of PLAYER/OPPONENT");
INVALID_IF(defenseIV > MAX_PER_STAT_IVS, "Illegal defense IV: %d", defenseIV);
SetMonData(DATA.currentMon, MON_DATA_DEF_IV, &defenseIV);
}
void SpAttackIV_(u32 sourceLine, u32 spAttackIV)
{
INVALID_IF(!DATA.currentMon, "SpAttack IV outside of PLAYER/OPPONENT");
INVALID_IF(spAttackIV > MAX_PER_STAT_IVS, "Illegal special attack IV: %d", spAttackIV);
SetMonData(DATA.currentMon, MON_DATA_SPATK_IV, &spAttackIV);
}
void SpDefenseIV_(u32 sourceLine, u32 spDefenseIV)
{
INVALID_IF(!DATA.currentMon, "SpDefense IV outside of PLAYER/OPPONENT");
INVALID_IF(spDefenseIV > MAX_PER_STAT_IVS, "Illegal special defense IV: %d", spDefenseIV);
SetMonData(DATA.currentMon, MON_DATA_SPDEF_IV, &spDefenseIV);
}
void SpeedIV_(u32 sourceLine, u32 speedIV)
{
INVALID_IF(!DATA.currentMon, "Speed IV outside of PLAYER/OPPONENT");
INVALID_IF(speedIV > MAX_PER_STAT_IVS, "Illegal speed IV: %d", speedIV);
SetMonData(DATA.currentMon, MON_DATA_SPEED_IV, &speedIV);
}
void Item_(u32 sourceLine, u32 item)
{
INVALID_IF(!DATA.currentMon, "Item outside of PLAYER/OPPONENT");
INVALID_IF(item >= ITEMS_COUNT, "Illegal item: %d", item);
SetMonData(DATA.currentMon, MON_DATA_HELD_ITEM, &item);
}
void Moves_(u32 sourceLine, u16 moves[MAX_MON_MOVES])
{
s32 i;
INVALID_IF(!DATA.currentMon, "Moves outside of PLAYER/OPPONENT");
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (moves[i] == MOVE_NONE)
break;
INVALID_IF(moves[i] >= MOVES_COUNT, "Illegal move: %d", moves[i]);
SetMonData(DATA.currentMon, MON_DATA_MOVE1 + i, &moves[i]);
Move data unification (#3999) * Made gBattleMoves handle the InGame name and description of battle moves No more multiple arrays in separate, individual files. Note: -Keep an eye on Task_LearnedMove. * Reintroduced move names Misc: -Fixed Trick-or-Treat and Light of Ruin's expanded names. -Introduced a new field for Z-Move names, and a constant for their name length. -Added a few TODOs to GetBattleMoveName. -Updated GetMaxMoveName and GetZMoveName. There's no reason not to let GetBattleMoveName handle everything on its own. * Updated GetBattleMoveName to handle Z-Move Names Misc: -Removed pointless TODO about MOVE_NAME_LENGTH. -The compiler doesn't allow to have a move name with a value higher than MOVE_NAME_LENGTH, therefore it's pointless to worry about it. * Fixed a couple of expanded move names * Removed zMoveName variable of struct BattleMove and extended the name variable's size * Ditched no longer used MOVE_NAME_LENGTH constant * Corrected the names of the max moves I should have done this after updating the size of the name variable of the struct BattleMove, but I didn't think about it at all until Cancer Fairy indirectly gave me the idea. * Fixed U-turn's name * Brought back MOVE_NAME_LENGTH I think it doesn't make sense to have a Z_MOVE_NAME_LENGTH because the length in question is used for all battle moves, not just the Z-Moves. * Introduced a union for Move/Z-Move names in the struct BattleMove * Fixed the union for gBattleMoves move names Also updated GetBattleMoveName to properly handle Max Move names. Also also renamed the "zMoveName" variable to "bigMoveName" which better reflects its purpose. Z-Move names weren't the only thing it covered, since it also handles Max Move names. * Removed deprecated GetZMoveName and GetMaxMoveName * Reintroduced mention to gMoveNames in sGFRomHeader * Fixed move names and ported move descriptions * Fused the struct ContestMove into the struct BattleMove * Removed no longer used Z_MOVE_NAME_LENGTH constant * Renamed the struct BattleMove's bigMoveName variable and introduced macros to prettify move names * Reintroduced the contest parameters for Pokémon moves * Renamed gBattleMoves to gMovesInfo This is consistent with gSpeciesInfo, the array that contains most of the species data. * Renamed the BattleMove struct to MovesInfo This is consistent with the struct SpeciesInfo, which contains the variables used by the gSpeciesInfo array. * Removed empty lines separating battle params from contest params in gMovesInfo * Renamed MovesInfo to MoveInfo * Added Cancer Fairy's HANDLE_EXPANDED_MOVE_NAME macro Used to handle moves with expanded names in a more comfortable manner. Also fixed Trick-or-Treat's expanded name. * Renamed GetBattleMoveName to GetMoveName * Added a comment pointing out that the shared move descriptions are shared move descriptions * Re-aligned one of the escape characters of CHECK_MOVE_FLAG * Renamed the battle_moves.h file to moves_info.h instead for consistency's sake * Applied Eduardo's adjustments * Using compound string for regular move names as well, saving 1180 bytes and making their use consistent * Move description formatting * Updated Pursuit test after merge * Renamed the BATTLE_CATEGORY constants to DAMAGE_CATEGORY --------- Co-authored-by: Nephrite <thechurchofcage@gmail.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
2024-01-29 11:51:32 +00:00
SetMonData(DATA.currentMon, MON_DATA_PP1 + i, &gMovesInfo[moves[i]].pp);
}
DATA.explicitMoves[DATA.currentSide] |= 1 << DATA.currentPartyIndex;
}
2023-04-25 19:35:36 +01:00
void MovesWithPP_(u32 sourceLine, struct moveWithPP moveWithPP[MAX_MON_MOVES])
{
s32 i;
INVALID_IF(!DATA.currentMon, "Moves outside of PLAYER/OPPONENT");
for (i = 0; i < MAX_MON_MOVES; i++)
{
2023-04-25 19:35:36 +01:00
if (moveWithPP[i].moveId == MOVE_NONE)
break;
2023-04-25 19:35:36 +01:00
INVALID_IF(moveWithPP[i].moveId >= MOVES_COUNT, "Illegal move: %d", &moveWithPP[i].moveId);
SetMonData(DATA.currentMon, MON_DATA_MOVE1 + i, &moveWithPP[i].moveId);
SetMonData(DATA.currentMon, MON_DATA_PP1 + i, &moveWithPP[i].pp);
}
DATA.explicitMoves[DATA.currentSide] |= 1 << DATA.currentPartyIndex;
}
void Friendship_(u32 sourceLine, u32 friendship)
{
INVALID_IF(!DATA.currentMon, "Friendship outside of PLAYER/OPPONENT");
SetMonData(DATA.currentMon, MON_DATA_FRIENDSHIP, &friendship);
}
void Status1_(u32 sourceLine, u32 status1)
{
INVALID_IF(!DATA.currentMon, "Status1 outside of PLAYER/OPPONENT");
INVALID_IF(status1 & STATUS1_TOXIC_COUNTER, "Illegal status1: has TOXIC_TURN");
SetMonData(DATA.currentMon, MON_DATA_STATUS, &status1);
}
void OTName_(u32 sourceLine, const u8 *otName)
{
INVALID_IF(!DATA.currentMon, "OTName outside of PLAYER/OPPONENT");
SetMonData(DATA.currentMon, MON_DATA_OT_NAME, &otName);
}
void DynamaxLevel_(u32 sourceLine, u32 dynamaxLevel)
{
INVALID_IF(!DATA.currentMon, "DynamaxLevel outside of PLAYER/OPPONENT");
SetMonData(DATA.currentMon, MON_DATA_DYNAMAX_LEVEL, &dynamaxLevel);
}
void GigantamaxFactor_(u32 sourceLine, bool32 gigantamaxFactor)
{
INVALID_IF(!DATA.currentMon, "GigantamaxFactor outside of PLAYER/OPPONENT");
SetMonData(DATA.currentMon, MON_DATA_GIGANTAMAX_FACTOR, &gigantamaxFactor);
}
void TeraType_(u32 sourceLine, u32 teraType)
{
INVALID_IF(!DATA.currentMon, "TeraType outside of PLAYER/OPPONENT");
SetMonData(DATA.currentMon, MON_DATA_TERA_TYPE, &teraType);
}
void Shadow_(u32 sourceLine, bool32 isShadow)
{
INVALID_IF(!DATA.currentMon, "Shadow outside of PLAYER/OPPONENT");
SetMonData(DATA.currentMon, MON_DATA_IS_SHADOW, &isShadow);
}
static const char *const sBattlerIdentifiersSingles[] =
{
"player",
"opponent",
};
static const char *const sBattlerIdentifiersDoubles[] =
{
"playerLeft",
"opponentLeft",
"playerRight",
"opponentRight",
};
static const char *BattlerIdentifier(s32 battlerId)
{
const struct BattleTest *test = GetBattleTest();
switch (test->type)
{
case BATTLE_TEST_SINGLES:
case BATTLE_TEST_WILD:
case BATTLE_TEST_AI_SINGLES:
return sBattlerIdentifiersSingles[battlerId];
case BATTLE_TEST_DOUBLES:
case BATTLE_TEST_AI_DOUBLES:
return sBattlerIdentifiersDoubles[battlerId];
}
return "<unknown>";
}
static void PushBattlerAction(u32 sourceLine, s32 battlerId, u32 actionType, u32 byte)
{
u32 recordIndex = DATA.recordIndexes[battlerId]++;
if (recordIndex >= BATTLER_RECORD_SIZE)
Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":LToo many actions");
DATA.battleRecordTypes[battlerId][recordIndex] = actionType;
DATA.battleRecordSourceLineOffsets[battlerId][recordIndex] = SourceLineOffset(sourceLine);
DATA.recordedBattle.battleRecord[battlerId][recordIndex] = byte;
}
void TestRunner_Battle_CheckBattleRecordActionType(u32 battlerId, u32 recordIndex, u32 actionType)
{
// An illegal move choice will cause the battle to request a new
// move slot and target. This detects the move slot.
if (actionType == RECORDED_MOVE_SLOT
&& recordIndex > 0
&& DATA.battleRecordTypes[battlerId][recordIndex-1] != RECORDED_ACTION_TYPE)
{
s32 i;
const char *filename = gTestRunnerState.test->filename;
for (i = recordIndex; i > 0; i--)
{
if (DATA.battleRecordTypes[battlerId][i-1] == RECORDED_ACTION_TYPE
&& DATA.recordedBattle.battleRecord[battlerId][i-1] == B_ACTION_USE_MOVE)
{
u32 line = SourceLine(DATA.battleRecordSourceLineOffsets[battlerId][i-1]);
Test_ExitWithResult(TEST_RESULT_INVALID, line, ":L%s:%d: Illegal MOVE", filename, line);
}
}
Test_ExitWithResult(TEST_RESULT_INVALID, SourceLine(0), ":L%s:%d: Illegal MOVE", filename, SourceLine(0));
}
if (DATA.battleRecordTypes[battlerId][recordIndex] != RECORDED_BYTE)
{
DATA.lastActionTurn = gBattleResults.battleTurnCounter;
if (actionType != DATA.battleRecordTypes[battlerId][recordIndex])
{
const char *actualMacro = NULL;
const char *filename = gTestRunnerState.test->filename;
u32 line = SourceLine(DATA.battleRecordSourceLineOffsets[battlerId][recordIndex]);
switch (DATA.battleRecordTypes[battlerId][recordIndex])
{
case RECORDED_ACTION_TYPE:
actualMacro = sBattleActionNames[DATA.recordedBattle.battleRecord[battlerId][recordIndex]];
break;
case RECORDED_PARTY_INDEX:
actualMacro = "SEND_OUT";
break;
}
if (actualMacro)
{
switch (actionType)
{
case RECORDED_ACTION_TYPE:
Test_ExitWithResult(TEST_RESULT_INVALID, line, ":L%s:%d: Expected MOVE/SWITCH, got %s", filename, line, actualMacro);
case RECORDED_PARTY_INDEX:
Test_ExitWithResult(TEST_RESULT_INVALID, line, ":L%s:%d: Expected SEND_OUT, got %s", filename, line, actualMacro);
}
}
Test_ExitWithResult(TEST_RESULT_ERROR, line, ":L%s:%d: Illegal battle record", filename, line);
}
}
else
{
if (DATA.lastActionTurn == gBattleResults.battleTurnCounter)
{
const char *filename = gTestRunnerState.test->filename;
Test_ExitWithResult(TEST_RESULT_FAIL, SourceLine(0), ":L%s:%d: TURN %d incomplete", filename, SourceLine(0), gBattleResults.battleTurnCounter + 1);
}
}
}
void OpenTurn(u32 sourceLine)
{
INVALID_IF(DATA.turnState != TURN_CLOSED, "Nested TURN");
if (DATA.turns == MAX_TURNS)
Test_ExitWithResult(TEST_RESULT_ERROR, sourceLine, ":L%s:%d: TURN exceeds MAX_TURNS", gTestRunnerState.test->filename, sourceLine);
DATA.turnState = TURN_OPEN;
DATA.actionBattlers = 0x00;
DATA.moveBattlers = 0x00;
}
static void SetSlowerThan(s32 battlerId)
{
s32 i, slowerThan;
slowerThan = 0;
for (i = 0; i < STATE->battlersCount; i++)
{
if (i == battlerId)
continue;
if (DATA.moveBattlers & (1 << i))
{
if ((i & BIT_SIDE) == B_SIDE_PLAYER)
slowerThan |= 1 << DATA.currentMonIndexes[i];
else
slowerThan |= (1 << 6) << DATA.currentMonIndexes[i];
}
}
DATA.slowerThan[battlerId & BIT_SIDE][DATA.currentMonIndexes[battlerId]] |= slowerThan;
}
static void SetAiActionToPass(u32 sourceLine, s32 battlerId)
{
DATA.expectedAiActions[battlerId][DATA.expectedAiActionIndex[battlerId]].actionSet = TRUE;
DATA.expectedAiActions[battlerId][DATA.expectedAiActionIndex[battlerId]].sourceLine = sourceLine;
DATA.expectedAiActions[battlerId][DATA.expectedAiActionIndex[battlerId]].pass = TRUE;
DATA.expectedAiActionIndex[battlerId]++;
}
void CloseTurn(u32 sourceLine)
{
s32 i;
INVALID_IF(DATA.turnState != TURN_OPEN, "Nested TURN");
DATA.turnState = TURN_CLOSING;
// If Move was not specified always use Celebrate. In AI Tests allow any taken action.
for (i = 0; i < STATE->battlersCount; i++)
{
if (!(DATA.actionBattlers & (1 << i)))
{
if (IsAITest() && (i & BIT_SIDE) == B_SIDE_OPPONENT) // If Move was not specified, allow any move used.
SetAiActionToPass(sourceLine, i);
else
Move(sourceLine, &gBattleMons[i], (struct MoveContext) { move: MOVE_CELEBRATE, explicitMove: TRUE });
}
}
DATA.turnState = TURN_CLOSED;
DATA.turns++;
}
static struct Pokemon *CurrentMon(s32 battlerId)
{
struct Pokemon *party;
if ((battlerId & BIT_SIDE) == B_SIDE_PLAYER)
party = DATA.recordedBattle.playerParty;
else
party = DATA.recordedBattle.opponentParty;
return &party[DATA.currentMonIndexes[battlerId]];
}
s32 MoveGetTarget(s32 battlerId, u32 moveId, struct MoveContext *ctx, u32 sourceLine)
{
s32 target = battlerId;
if (ctx->explicitTarget)
{
target = ctx->target - gBattleMons;
}
else
{
Move data unification (#3999) * Made gBattleMoves handle the InGame name and description of battle moves No more multiple arrays in separate, individual files. Note: -Keep an eye on Task_LearnedMove. * Reintroduced move names Misc: -Fixed Trick-or-Treat and Light of Ruin's expanded names. -Introduced a new field for Z-Move names, and a constant for their name length. -Added a few TODOs to GetBattleMoveName. -Updated GetMaxMoveName and GetZMoveName. There's no reason not to let GetBattleMoveName handle everything on its own. * Updated GetBattleMoveName to handle Z-Move Names Misc: -Removed pointless TODO about MOVE_NAME_LENGTH. -The compiler doesn't allow to have a move name with a value higher than MOVE_NAME_LENGTH, therefore it's pointless to worry about it. * Fixed a couple of expanded move names * Removed zMoveName variable of struct BattleMove and extended the name variable's size * Ditched no longer used MOVE_NAME_LENGTH constant * Corrected the names of the max moves I should have done this after updating the size of the name variable of the struct BattleMove, but I didn't think about it at all until Cancer Fairy indirectly gave me the idea. * Fixed U-turn's name * Brought back MOVE_NAME_LENGTH I think it doesn't make sense to have a Z_MOVE_NAME_LENGTH because the length in question is used for all battle moves, not just the Z-Moves. * Introduced a union for Move/Z-Move names in the struct BattleMove * Fixed the union for gBattleMoves move names Also updated GetBattleMoveName to properly handle Max Move names. Also also renamed the "zMoveName" variable to "bigMoveName" which better reflects its purpose. Z-Move names weren't the only thing it covered, since it also handles Max Move names. * Removed deprecated GetZMoveName and GetMaxMoveName * Reintroduced mention to gMoveNames in sGFRomHeader * Fixed move names and ported move descriptions * Fused the struct ContestMove into the struct BattleMove * Removed no longer used Z_MOVE_NAME_LENGTH constant * Renamed the struct BattleMove's bigMoveName variable and introduced macros to prettify move names * Reintroduced the contest parameters for Pokémon moves * Renamed gBattleMoves to gMovesInfo This is consistent with gSpeciesInfo, the array that contains most of the species data. * Renamed the BattleMove struct to MovesInfo This is consistent with the struct SpeciesInfo, which contains the variables used by the gSpeciesInfo array. * Removed empty lines separating battle params from contest params in gMovesInfo * Renamed MovesInfo to MoveInfo * Added Cancer Fairy's HANDLE_EXPANDED_MOVE_NAME macro Used to handle moves with expanded names in a more comfortable manner. Also fixed Trick-or-Treat's expanded name. * Renamed GetBattleMoveName to GetMoveName * Added a comment pointing out that the shared move descriptions are shared move descriptions * Re-aligned one of the escape characters of CHECK_MOVE_FLAG * Renamed the battle_moves.h file to moves_info.h instead for consistency's sake * Applied Eduardo's adjustments * Using compound string for regular move names as well, saving 1180 bytes and making their use consistent * Move description formatting * Updated Pursuit test after merge * Renamed the BATTLE_CATEGORY constants to DAMAGE_CATEGORY --------- Co-authored-by: Nephrite <thechurchofcage@gmail.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
2024-01-29 11:51:32 +00:00
const struct MoveInfo *move = &gMovesInfo[moveId];
if (move->target == MOVE_TARGET_RANDOM
|| move->target == MOVE_TARGET_BOTH
|| move->target == MOVE_TARGET_DEPENDS
|| move->target == MOVE_TARGET_FOES_AND_ALLY
|| move->target == MOVE_TARGET_OPPONENTS_FIELD
|| move->target == MOVE_TARGET_ALL_BATTLERS)
{
target = BATTLE_OPPOSITE(battlerId);
}
else if (move->target == MOVE_TARGET_SELECTED)
{
// In AI Doubles not specified target allows any target for EXPECT_MOVE.
if (GetBattleTest()->type != BATTLE_TEST_AI_DOUBLES)
{
Move data unification (#3999) * Made gBattleMoves handle the InGame name and description of battle moves No more multiple arrays in separate, individual files. Note: -Keep an eye on Task_LearnedMove. * Reintroduced move names Misc: -Fixed Trick-or-Treat and Light of Ruin's expanded names. -Introduced a new field for Z-Move names, and a constant for their name length. -Added a few TODOs to GetBattleMoveName. -Updated GetMaxMoveName and GetZMoveName. There's no reason not to let GetBattleMoveName handle everything on its own. * Updated GetBattleMoveName to handle Z-Move Names Misc: -Removed pointless TODO about MOVE_NAME_LENGTH. -The compiler doesn't allow to have a move name with a value higher than MOVE_NAME_LENGTH, therefore it's pointless to worry about it. * Fixed a couple of expanded move names * Removed zMoveName variable of struct BattleMove and extended the name variable's size * Ditched no longer used MOVE_NAME_LENGTH constant * Corrected the names of the max moves I should have done this after updating the size of the name variable of the struct BattleMove, but I didn't think about it at all until Cancer Fairy indirectly gave me the idea. * Fixed U-turn's name * Brought back MOVE_NAME_LENGTH I think it doesn't make sense to have a Z_MOVE_NAME_LENGTH because the length in question is used for all battle moves, not just the Z-Moves. * Introduced a union for Move/Z-Move names in the struct BattleMove * Fixed the union for gBattleMoves move names Also updated GetBattleMoveName to properly handle Max Move names. Also also renamed the "zMoveName" variable to "bigMoveName" which better reflects its purpose. Z-Move names weren't the only thing it covered, since it also handles Max Move names. * Removed deprecated GetZMoveName and GetMaxMoveName * Reintroduced mention to gMoveNames in sGFRomHeader * Fixed move names and ported move descriptions * Fused the struct ContestMove into the struct BattleMove * Removed no longer used Z_MOVE_NAME_LENGTH constant * Renamed the struct BattleMove's bigMoveName variable and introduced macros to prettify move names * Reintroduced the contest parameters for Pokémon moves * Renamed gBattleMoves to gMovesInfo This is consistent with gSpeciesInfo, the array that contains most of the species data. * Renamed the BattleMove struct to MovesInfo This is consistent with the struct SpeciesInfo, which contains the variables used by the gSpeciesInfo array. * Removed empty lines separating battle params from contest params in gMovesInfo * Renamed MovesInfo to MoveInfo * Added Cancer Fairy's HANDLE_EXPANDED_MOVE_NAME macro Used to handle moves with expanded names in a more comfortable manner. Also fixed Trick-or-Treat's expanded name. * Renamed GetBattleMoveName to GetMoveName * Added a comment pointing out that the shared move descriptions are shared move descriptions * Re-aligned one of the escape characters of CHECK_MOVE_FLAG * Renamed the battle_moves.h file to moves_info.h instead for consistency's sake * Applied Eduardo's adjustments * Using compound string for regular move names as well, saving 1180 bytes and making their use consistent * Move description formatting * Updated Pursuit test after merge * Renamed the BATTLE_CATEGORY constants to DAMAGE_CATEGORY --------- Co-authored-by: Nephrite <thechurchofcage@gmail.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
2024-01-29 11:51:32 +00:00
INVALID_IF(STATE->battlersCount > 2, "%S requires explicit target", GetMoveName(moveId));
}
target = BATTLE_OPPOSITE(battlerId);
}
else if (move->target == MOVE_TARGET_USER)
{
target = battlerId;
}
else if (move->target == MOVE_TARGET_ALLY)
{
target = BATTLE_PARTNER(battlerId);
}
else
{
// In AI Doubles not specified target allows any target for EXPECT_MOVE.
if (GetBattleTest()->type != BATTLE_TEST_AI_DOUBLES)
{
Move data unification (#3999) * Made gBattleMoves handle the InGame name and description of battle moves No more multiple arrays in separate, individual files. Note: -Keep an eye on Task_LearnedMove. * Reintroduced move names Misc: -Fixed Trick-or-Treat and Light of Ruin's expanded names. -Introduced a new field for Z-Move names, and a constant for their name length. -Added a few TODOs to GetBattleMoveName. -Updated GetMaxMoveName and GetZMoveName. There's no reason not to let GetBattleMoveName handle everything on its own. * Updated GetBattleMoveName to handle Z-Move Names Misc: -Removed pointless TODO about MOVE_NAME_LENGTH. -The compiler doesn't allow to have a move name with a value higher than MOVE_NAME_LENGTH, therefore it's pointless to worry about it. * Fixed a couple of expanded move names * Removed zMoveName variable of struct BattleMove and extended the name variable's size * Ditched no longer used MOVE_NAME_LENGTH constant * Corrected the names of the max moves I should have done this after updating the size of the name variable of the struct BattleMove, but I didn't think about it at all until Cancer Fairy indirectly gave me the idea. * Fixed U-turn's name * Brought back MOVE_NAME_LENGTH I think it doesn't make sense to have a Z_MOVE_NAME_LENGTH because the length in question is used for all battle moves, not just the Z-Moves. * Introduced a union for Move/Z-Move names in the struct BattleMove * Fixed the union for gBattleMoves move names Also updated GetBattleMoveName to properly handle Max Move names. Also also renamed the "zMoveName" variable to "bigMoveName" which better reflects its purpose. Z-Move names weren't the only thing it covered, since it also handles Max Move names. * Removed deprecated GetZMoveName and GetMaxMoveName * Reintroduced mention to gMoveNames in sGFRomHeader * Fixed move names and ported move descriptions * Fused the struct ContestMove into the struct BattleMove * Removed no longer used Z_MOVE_NAME_LENGTH constant * Renamed the struct BattleMove's bigMoveName variable and introduced macros to prettify move names * Reintroduced the contest parameters for Pokémon moves * Renamed gBattleMoves to gMovesInfo This is consistent with gSpeciesInfo, the array that contains most of the species data. * Renamed the BattleMove struct to MovesInfo This is consistent with the struct SpeciesInfo, which contains the variables used by the gSpeciesInfo array. * Removed empty lines separating battle params from contest params in gMovesInfo * Renamed MovesInfo to MoveInfo * Added Cancer Fairy's HANDLE_EXPANDED_MOVE_NAME macro Used to handle moves with expanded names in a more comfortable manner. Also fixed Trick-or-Treat's expanded name. * Renamed GetBattleMoveName to GetMoveName * Added a comment pointing out that the shared move descriptions are shared move descriptions * Re-aligned one of the escape characters of CHECK_MOVE_FLAG * Renamed the battle_moves.h file to moves_info.h instead for consistency's sake * Applied Eduardo's adjustments * Using compound string for regular move names as well, saving 1180 bytes and making their use consistent * Move description formatting * Updated Pursuit test after merge * Renamed the BATTLE_CATEGORY constants to DAMAGE_CATEGORY --------- Co-authored-by: Nephrite <thechurchofcage@gmail.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
2024-01-29 11:51:32 +00:00
INVALID("%S requires explicit target", GetMoveName(moveId));
}
}
}
return target;
}
void MoveGetIdAndSlot(s32 battlerId, struct MoveContext *ctx, u32 *moveId, u32 *moveSlot, u32 sourceLine)
{
u32 i;
struct Pokemon *mon = CurrentMon(battlerId);
if (ctx->explicitMove)
{
INVALID_IF(ctx->move == MOVE_NONE || ctx->move >= MOVES_COUNT, "Illegal move: %d", ctx->move);
for (i = 0; i < MAX_MON_MOVES; i++)
{
*moveId = GetMonData(mon, MON_DATA_MOVE1 + i);
if (*moveId == ctx->move)
{
*moveSlot = i;
break;
}
else if (*moveId == MOVE_NONE)
{
Move data unification (#3999) * Made gBattleMoves handle the InGame name and description of battle moves No more multiple arrays in separate, individual files. Note: -Keep an eye on Task_LearnedMove. * Reintroduced move names Misc: -Fixed Trick-or-Treat and Light of Ruin's expanded names. -Introduced a new field for Z-Move names, and a constant for their name length. -Added a few TODOs to GetBattleMoveName. -Updated GetMaxMoveName and GetZMoveName. There's no reason not to let GetBattleMoveName handle everything on its own. * Updated GetBattleMoveName to handle Z-Move Names Misc: -Removed pointless TODO about MOVE_NAME_LENGTH. -The compiler doesn't allow to have a move name with a value higher than MOVE_NAME_LENGTH, therefore it's pointless to worry about it. * Fixed a couple of expanded move names * Removed zMoveName variable of struct BattleMove and extended the name variable's size * Ditched no longer used MOVE_NAME_LENGTH constant * Corrected the names of the max moves I should have done this after updating the size of the name variable of the struct BattleMove, but I didn't think about it at all until Cancer Fairy indirectly gave me the idea. * Fixed U-turn's name * Brought back MOVE_NAME_LENGTH I think it doesn't make sense to have a Z_MOVE_NAME_LENGTH because the length in question is used for all battle moves, not just the Z-Moves. * Introduced a union for Move/Z-Move names in the struct BattleMove * Fixed the union for gBattleMoves move names Also updated GetBattleMoveName to properly handle Max Move names. Also also renamed the "zMoveName" variable to "bigMoveName" which better reflects its purpose. Z-Move names weren't the only thing it covered, since it also handles Max Move names. * Removed deprecated GetZMoveName and GetMaxMoveName * Reintroduced mention to gMoveNames in sGFRomHeader * Fixed move names and ported move descriptions * Fused the struct ContestMove into the struct BattleMove * Removed no longer used Z_MOVE_NAME_LENGTH constant * Renamed the struct BattleMove's bigMoveName variable and introduced macros to prettify move names * Reintroduced the contest parameters for Pokémon moves * Renamed gBattleMoves to gMovesInfo This is consistent with gSpeciesInfo, the array that contains most of the species data. * Renamed the BattleMove struct to MovesInfo This is consistent with the struct SpeciesInfo, which contains the variables used by the gSpeciesInfo array. * Removed empty lines separating battle params from contest params in gMovesInfo * Renamed MovesInfo to MoveInfo * Added Cancer Fairy's HANDLE_EXPANDED_MOVE_NAME macro Used to handle moves with expanded names in a more comfortable manner. Also fixed Trick-or-Treat's expanded name. * Renamed GetBattleMoveName to GetMoveName * Added a comment pointing out that the shared move descriptions are shared move descriptions * Re-aligned one of the escape characters of CHECK_MOVE_FLAG * Renamed the battle_moves.h file to moves_info.h instead for consistency's sake * Applied Eduardo's adjustments * Using compound string for regular move names as well, saving 1180 bytes and making their use consistent * Move description formatting * Updated Pursuit test after merge * Renamed the BATTLE_CATEGORY constants to DAMAGE_CATEGORY --------- Co-authored-by: Nephrite <thechurchofcage@gmail.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
2024-01-29 11:51:32 +00:00
INVALID_IF(DATA.explicitMoves[battlerId & BIT_SIDE] & (1 << DATA.currentMonIndexes[battlerId]), "Missing explicit %S", GetMoveName(ctx->move));
SetMonData(mon, MON_DATA_MOVE1 + i, &ctx->move);
Move data unification (#3999) * Made gBattleMoves handle the InGame name and description of battle moves No more multiple arrays in separate, individual files. Note: -Keep an eye on Task_LearnedMove. * Reintroduced move names Misc: -Fixed Trick-or-Treat and Light of Ruin's expanded names. -Introduced a new field for Z-Move names, and a constant for their name length. -Added a few TODOs to GetBattleMoveName. -Updated GetMaxMoveName and GetZMoveName. There's no reason not to let GetBattleMoveName handle everything on its own. * Updated GetBattleMoveName to handle Z-Move Names Misc: -Removed pointless TODO about MOVE_NAME_LENGTH. -The compiler doesn't allow to have a move name with a value higher than MOVE_NAME_LENGTH, therefore it's pointless to worry about it. * Fixed a couple of expanded move names * Removed zMoveName variable of struct BattleMove and extended the name variable's size * Ditched no longer used MOVE_NAME_LENGTH constant * Corrected the names of the max moves I should have done this after updating the size of the name variable of the struct BattleMove, but I didn't think about it at all until Cancer Fairy indirectly gave me the idea. * Fixed U-turn's name * Brought back MOVE_NAME_LENGTH I think it doesn't make sense to have a Z_MOVE_NAME_LENGTH because the length in question is used for all battle moves, not just the Z-Moves. * Introduced a union for Move/Z-Move names in the struct BattleMove * Fixed the union for gBattleMoves move names Also updated GetBattleMoveName to properly handle Max Move names. Also also renamed the "zMoveName" variable to "bigMoveName" which better reflects its purpose. Z-Move names weren't the only thing it covered, since it also handles Max Move names. * Removed deprecated GetZMoveName and GetMaxMoveName * Reintroduced mention to gMoveNames in sGFRomHeader * Fixed move names and ported move descriptions * Fused the struct ContestMove into the struct BattleMove * Removed no longer used Z_MOVE_NAME_LENGTH constant * Renamed the struct BattleMove's bigMoveName variable and introduced macros to prettify move names * Reintroduced the contest parameters for Pokémon moves * Renamed gBattleMoves to gMovesInfo This is consistent with gSpeciesInfo, the array that contains most of the species data. * Renamed the BattleMove struct to MovesInfo This is consistent with the struct SpeciesInfo, which contains the variables used by the gSpeciesInfo array. * Removed empty lines separating battle params from contest params in gMovesInfo * Renamed MovesInfo to MoveInfo * Added Cancer Fairy's HANDLE_EXPANDED_MOVE_NAME macro Used to handle moves with expanded names in a more comfortable manner. Also fixed Trick-or-Treat's expanded name. * Renamed GetBattleMoveName to GetMoveName * Added a comment pointing out that the shared move descriptions are shared move descriptions * Re-aligned one of the escape characters of CHECK_MOVE_FLAG * Renamed the battle_moves.h file to moves_info.h instead for consistency's sake * Applied Eduardo's adjustments * Using compound string for regular move names as well, saving 1180 bytes and making their use consistent * Move description formatting * Updated Pursuit test after merge * Renamed the BATTLE_CATEGORY constants to DAMAGE_CATEGORY --------- Co-authored-by: Nephrite <thechurchofcage@gmail.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
2024-01-29 11:51:32 +00:00
SetMonData(DATA.currentMon, MON_DATA_PP1 + i, &gMovesInfo[ctx->move].pp);
*moveSlot = i;
*moveId = ctx->move;
break;
}
}
INVALID_IF(i == MAX_MON_MOVES, "Too many different moves for %s", BattlerIdentifier(battlerId));
}
else if (ctx->explicitMoveSlot)
{
*moveSlot = ctx->moveSlot;
*moveId = GetMonData(mon, MON_DATA_MOVE1 + *moveSlot);
INVALID_IF(moveId == MOVE_NONE, "Empty moveSlot: %d", ctx->moveSlot);
}
else
{
INVALID("No move or moveSlot");
}
Gimmick Refactor (#4449) * consolidated gimmick checks, triggers, communication, and activation; updated test runner * fixed improper use of .usableGimmick * cleaning up battle_dynamax.c, changing function args to u32s * fixed '#ifdef TESTING' causing errors * updated z-moves to use gimmick interface, pared down redundancies; no AI/tests * added support for z-moves in tests, consolidated gimmick fields * removed ShouldUseMaxMove and .usingMaxMove * renamed TryChangeZIndicator, updated z move display * added several z-move tests and fixed various z-move interactions; fixed z-move category calc * fixed useless battler arg in GetTypeBasedZMove * added basic test check for bad Z-Move or Mega usage * reworked test runner gimmick functionality; added support for Ultra Burst + Z-Move to test Light That Burns the Sky * fixed gimmick test logic; fixed damage category override * fixed mega rayquaza test fail * consolidated gimmick indicator logic; added graphics to gGimmicksInfo * removed TeraData struct * reimplemented AI logic for Z-Moves; no changes * updated Z-Move and Ultra Burst trigger gfx * added testrunner check for multiple gimmick use * fixed duplicate z-move call in test * reorganized data/graphics/gimmicks.h * added signature Z-Move ability tests; implemented Guardian of Alola * fixed bad test update * fixed Thousand Arrows not affecting Tera Flying; clean-up * fixed -ate tests * fixed tera tests * fixed tera tests really * fixed last batch of tests * fixed -ate mega test again * code review * code review pt.2 * tweaked CanTera again * dynamax flag only required for player
2024-06-22 21:25:40 +01:00
if (ctx->explicitGimmick && ctx->gimmick != GIMMICK_NONE)
{
u32 item = GetMonData(mon, MON_DATA_HELD_ITEM);
u32 holdEffect = ItemId_GetHoldEffect(item);
u32 species = GetMonData(mon, MON_DATA_SPECIES);
u32 side = GetBattlerSide(battlerId);
// Check invalid item usage.
INVALID_IF(ctx->gimmick == GIMMICK_MEGA && holdEffect != HOLD_EFFECT_MEGA_STONE && species != SPECIES_RAYQUAZA, "Cannot Mega Evolve without a Mega Stone");
INVALID_IF(ctx->gimmick == GIMMICK_Z_MOVE && holdEffect != HOLD_EFFECT_Z_CRYSTAL, "Cannot use a Z-Move without a Z-Crystal");
INVALID_IF(ctx->gimmick == GIMMICK_Z_MOVE && ItemId_GetSecondaryId(item) != gMovesInfo[*moveId].type
&& GetSignatureZMove(*moveId, species, item) == MOVE_NONE
&& *moveId != MOVE_PHOTON_GEYSER, // exception because test won't recognize Ultra Necrozma pre-Burst
"Cannot turn %S into a Z-Move with %S", GetMoveName(ctx->move), ItemId_GetName(item));
INVALID_IF(ctx->gimmick != GIMMICK_MEGA && holdEffect == HOLD_EFFECT_MEGA_STONE, "Cannot use another gimmick while holding a Mega Stone");
INVALID_IF(ctx->gimmick != GIMMICK_Z_MOVE && ctx->gimmick != GIMMICK_ULTRA_BURST && holdEffect == HOLD_EFFECT_Z_CRYSTAL, "Cannot use another gimmick while holding a Z-Crystal");
Gimmick Refactor (#4449) * consolidated gimmick checks, triggers, communication, and activation; updated test runner * fixed improper use of .usableGimmick * cleaning up battle_dynamax.c, changing function args to u32s * fixed '#ifdef TESTING' causing errors * updated z-moves to use gimmick interface, pared down redundancies; no AI/tests * added support for z-moves in tests, consolidated gimmick fields * removed ShouldUseMaxMove and .usingMaxMove * renamed TryChangeZIndicator, updated z move display * added several z-move tests and fixed various z-move interactions; fixed z-move category calc * fixed useless battler arg in GetTypeBasedZMove * added basic test check for bad Z-Move or Mega usage * reworked test runner gimmick functionality; added support for Ultra Burst + Z-Move to test Light That Burns the Sky * fixed gimmick test logic; fixed damage category override * fixed mega rayquaza test fail * consolidated gimmick indicator logic; added graphics to gGimmicksInfo * removed TeraData struct * reimplemented AI logic for Z-Moves; no changes * updated Z-Move and Ultra Burst trigger gfx * added testrunner check for multiple gimmick use * fixed duplicate z-move call in test * reorganized data/graphics/gimmicks.h * added signature Z-Move ability tests; implemented Guardian of Alola * fixed bad test update * fixed Thousand Arrows not affecting Tera Flying; clean-up * fixed -ate tests * fixed tera tests * fixed tera tests really * fixed last batch of tests * fixed -ate mega test again * code review * code review pt.2 * tweaked CanTera again * dynamax flag only required for player
2024-06-22 21:25:40 +01:00
// Check multiple gimmick use.
INVALID_IF(DATA.chosenGimmick[side][DATA.currentMonIndexes[battlerId]] != GIMMICK_NONE
&& !(DATA.chosenGimmick[side][DATA.currentMonIndexes[battlerId]] == GIMMICK_ULTRA_BURST
&& ctx->gimmick == GIMMICK_Z_MOVE), "Cannot use multiple gimmicks on the same battler");
Gimmick Refactor (#4449) * consolidated gimmick checks, triggers, communication, and activation; updated test runner * fixed improper use of .usableGimmick * cleaning up battle_dynamax.c, changing function args to u32s * fixed '#ifdef TESTING' causing errors * updated z-moves to use gimmick interface, pared down redundancies; no AI/tests * added support for z-moves in tests, consolidated gimmick fields * removed ShouldUseMaxMove and .usingMaxMove * renamed TryChangeZIndicator, updated z move display * added several z-move tests and fixed various z-move interactions; fixed z-move category calc * fixed useless battler arg in GetTypeBasedZMove * added basic test check for bad Z-Move or Mega usage * reworked test runner gimmick functionality; added support for Ultra Burst + Z-Move to test Light That Burns the Sky * fixed gimmick test logic; fixed damage category override * fixed mega rayquaza test fail * consolidated gimmick indicator logic; added graphics to gGimmicksInfo * removed TeraData struct * reimplemented AI logic for Z-Moves; no changes * updated Z-Move and Ultra Burst trigger gfx * added testrunner check for multiple gimmick use * fixed duplicate z-move call in test * reorganized data/graphics/gimmicks.h * added signature Z-Move ability tests; implemented Guardian of Alola * fixed bad test update * fixed Thousand Arrows not affecting Tera Flying; clean-up * fixed -ate tests * fixed tera tests * fixed tera tests really * fixed last batch of tests * fixed -ate mega test again * code review * code review pt.2 * tweaked CanTera again * dynamax flag only required for player
2024-06-22 21:25:40 +01:00
DATA.chosenGimmick[side][DATA.currentMonIndexes[battlerId]] = ctx->gimmick;
*moveSlot |= RET_GIMMICK;
}
}
void Move(u32 sourceLine, struct BattlePokemon *battler, struct MoveContext ctx)
{
s32 battlerId = battler - gBattleMons;
u32 moveId, moveSlot;
s32 target;
INVALID_IF(DATA.turnState == TURN_CLOSED, "MOVE outside TURN");
INVALID_IF(IsAITest() && (battlerId & BIT_SIDE) == B_SIDE_OPPONENT, "MOVE is not allowed for opponent in AI tests. Use EXPECT_MOVE instead");
MoveGetIdAndSlot(battlerId, &ctx, &moveId, &moveSlot, sourceLine);
target = MoveGetTarget(battlerId, moveId, &ctx, sourceLine);
if (ctx.explicitHit)
DATA.battleRecordTurns[DATA.turns][battlerId].hit = 1 + ctx.hit;
if (ctx.explicitCriticalHit)
DATA.battleRecordTurns[DATA.turns][battlerId].criticalHit = 1 + ctx.criticalHit;
if (ctx.explicitSecondaryEffect)
DATA.battleRecordTurns[DATA.turns][battlerId].secondaryEffect = 1 + ctx.secondaryEffect;
if (ctx.explicitRNG)
DATA.battleRecordTurns[DATA.turns][battlerId].rng = ctx.rng;
if (!(DATA.actionBattlers & (1 << battlerId)))
{
PushBattlerAction(sourceLine, battlerId, RECORDED_ACTION_TYPE, B_ACTION_USE_MOVE);
}
if (!ctx.explicitAllowed || ctx.allowed)
{
PushBattlerAction(sourceLine, battlerId, RECORDED_MOVE_SLOT, moveSlot);
PushBattlerAction(sourceLine, battlerId, RECORDED_MOVE_TARGET, target);
}
if (DATA.turnState == TURN_OPEN)
{
if (!DATA.hasExplicitSpeeds)
SetSlowerThan(battlerId);
DATA.actionBattlers |= 1 << battlerId;
DATA.moveBattlers |= 1 << battlerId;
}
}
void ForcedMove(u32 sourceLine, struct BattlePokemon *battler)
{
s32 battlerId = battler - gBattleMons;
INVALID_IF(DATA.turnState == TURN_CLOSED, "SKIP_TURN outside TURN");
PushBattlerAction(sourceLine, battlerId, RECORDED_ACTION_TYPE, B_ACTION_USE_MOVE);
if (DATA.turnState == TURN_OPEN)
{
if (!DATA.hasExplicitSpeeds)
SetSlowerThan(battlerId);
DATA.actionBattlers |= 1 << battlerId;
DATA.moveBattlers |= 1 << battlerId;
}
}
static void TryMarkExpectMove(u32 sourceLine, struct BattlePokemon *battler, struct MoveContext *ctx)
{
s32 battlerId = battler - gBattleMons;
u32 moveId, moveSlot, id;
s32 target;
INVALID_IF(DATA.turnState == TURN_CLOSED, "EXPECT_MOVE outside TURN");
INVALID_IF(!IsAITest(), "EXPECT_MOVE is usable only in AI_SINGLE_BATTLE_TEST & AI_DOUBLE_BATTLE_TEST");
MoveGetIdAndSlot(battlerId, ctx, &moveId, &moveSlot, sourceLine);
target = MoveGetTarget(battlerId, moveId, ctx, sourceLine);
id = DATA.expectedAiActionIndex[battlerId];
DATA.expectedAiActions[battlerId][id].type = B_ACTION_USE_MOVE;
DATA.expectedAiActions[battlerId][id].moveSlots |= gBitTable[moveSlot];
DATA.expectedAiActions[battlerId][id].target = target;
DATA.expectedAiActions[battlerId][id].explicitTarget = ctx->explicitTarget;
DATA.expectedAiActions[battlerId][id].sourceLine = sourceLine;
DATA.expectedAiActions[battlerId][id].actionSet = TRUE;
if (ctx->explicitNotExpected)
DATA.expectedAiActions[battlerId][id].notMove = ctx->notExpected;
DATA.actionBattlers |= 1 << battlerId;
DATA.moveBattlers |= 1 << battlerId;
}
void ExpectMove(u32 sourceLine, struct BattlePokemon *battler, struct MoveContext ctx)
{
s32 battlerId = battler - gBattleMons;
TryMarkExpectMove(sourceLine, battler, &ctx);
DATA.expectedAiActionIndex[battlerId]++;
}
void ExpectSendOut(u32 sourceLine, struct BattlePokemon *battler, u32 partyIndex)
{
s32 i, id;
s32 battlerId = battler - gBattleMons;
INVALID_IF(DATA.turnState == TURN_CLOSED, "EXPECT_SEND_OUT outside TURN");
INVALID_IF(!IsAITest(), "EXPECT_SEND_OUT is usable only in AI_SINGLE_BATTLE_TEST & AI_DOUBLE_BATTLE_TEST");
INVALID_IF(partyIndex >= ((battlerId & BIT_SIDE) == B_SIDE_PLAYER ? DATA.playerPartySize : DATA.opponentPartySize), "EXPECT_SEND_OUT to invalid party index");
for (i = 0; i < STATE->battlersCount; i++)
{
if (battlerId != i && (battlerId & BIT_SIDE) == (i & BIT_SIDE))
INVALID_IF(DATA.currentMonIndexes[i] == partyIndex, "EXPECT_SEND_OUT to battler");
}
if (!(DATA.actionBattlers & (1 << battlerId)))
{
if (IsAITest() && (battlerId & BIT_SIDE) == B_SIDE_OPPONENT) // If Move was not specified, allow any move used.
SetAiActionToPass(sourceLine, battlerId);
else
Move(sourceLine, battler, (struct MoveContext) { move: MOVE_CELEBRATE, explicitMove: TRUE });
}
DATA.currentMonIndexes[battlerId] = partyIndex;
DATA.actionBattlers |= 1 << battlerId;
id = DATA.expectedAiActionIndex[battlerId];
DATA.expectedAiActions[battlerId][id].type = B_ACTION_SWITCH;
DATA.expectedAiActions[battlerId][id].target = partyIndex;
DATA.expectedAiActions[battlerId][id].sourceLine = sourceLine;
DATA.expectedAiActions[battlerId][id].actionSet = TRUE;
DATA.expectedAiActionIndex[battlerId]++;
}
s32 GetAiMoveTargetForScoreCompare(u32 battlerId, u32 moveId, struct MoveContext *ctx, u32 sourceLine)
{
s32 target;
// In Single Battles ai always targets the opposing mon.
if (GetBattleTest()->type == BATTLE_TEST_AI_SINGLES)
{
target = BATTLE_OPPOSITE(battlerId);
}
else
{
// TODO: Fix ai targeting self in double battles.
Move data unification (#3999) * Made gBattleMoves handle the InGame name and description of battle moves No more multiple arrays in separate, individual files. Note: -Keep an eye on Task_LearnedMove. * Reintroduced move names Misc: -Fixed Trick-or-Treat and Light of Ruin's expanded names. -Introduced a new field for Z-Move names, and a constant for their name length. -Added a few TODOs to GetBattleMoveName. -Updated GetMaxMoveName and GetZMoveName. There's no reason not to let GetBattleMoveName handle everything on its own. * Updated GetBattleMoveName to handle Z-Move Names Misc: -Removed pointless TODO about MOVE_NAME_LENGTH. -The compiler doesn't allow to have a move name with a value higher than MOVE_NAME_LENGTH, therefore it's pointless to worry about it. * Fixed a couple of expanded move names * Removed zMoveName variable of struct BattleMove and extended the name variable's size * Ditched no longer used MOVE_NAME_LENGTH constant * Corrected the names of the max moves I should have done this after updating the size of the name variable of the struct BattleMove, but I didn't think about it at all until Cancer Fairy indirectly gave me the idea. * Fixed U-turn's name * Brought back MOVE_NAME_LENGTH I think it doesn't make sense to have a Z_MOVE_NAME_LENGTH because the length in question is used for all battle moves, not just the Z-Moves. * Introduced a union for Move/Z-Move names in the struct BattleMove * Fixed the union for gBattleMoves move names Also updated GetBattleMoveName to properly handle Max Move names. Also also renamed the "zMoveName" variable to "bigMoveName" which better reflects its purpose. Z-Move names weren't the only thing it covered, since it also handles Max Move names. * Removed deprecated GetZMoveName and GetMaxMoveName * Reintroduced mention to gMoveNames in sGFRomHeader * Fixed move names and ported move descriptions * Fused the struct ContestMove into the struct BattleMove * Removed no longer used Z_MOVE_NAME_LENGTH constant * Renamed the struct BattleMove's bigMoveName variable and introduced macros to prettify move names * Reintroduced the contest parameters for Pokémon moves * Renamed gBattleMoves to gMovesInfo This is consistent with gSpeciesInfo, the array that contains most of the species data. * Renamed the BattleMove struct to MovesInfo This is consistent with the struct SpeciesInfo, which contains the variables used by the gSpeciesInfo array. * Removed empty lines separating battle params from contest params in gMovesInfo * Renamed MovesInfo to MoveInfo * Added Cancer Fairy's HANDLE_EXPANDED_MOVE_NAME macro Used to handle moves with expanded names in a more comfortable manner. Also fixed Trick-or-Treat's expanded name. * Renamed GetBattleMoveName to GetMoveName * Added a comment pointing out that the shared move descriptions are shared move descriptions * Re-aligned one of the escape characters of CHECK_MOVE_FLAG * Renamed the battle_moves.h file to moves_info.h instead for consistency's sake * Applied Eduardo's adjustments * Using compound string for regular move names as well, saving 1180 bytes and making their use consistent * Move description formatting * Updated Pursuit test after merge * Renamed the BATTLE_CATEGORY constants to DAMAGE_CATEGORY --------- Co-authored-by: Nephrite <thechurchofcage@gmail.com> Co-authored-by: Bassoonian <iasperbassoonian@gmail.com> Co-authored-by: Eduardo Quezada D'Ottone <eduardo602002@gmail.com>
2024-01-29 11:51:32 +00:00
INVALID_IF(!ctx->explicitTarget, "%S requires explicit target for score comparison in doubles", GetMoveName(moveId));
target = MoveGetTarget(battlerId, moveId, ctx, sourceLine);
}
return target;
}
void Score(u32 sourceLine, struct BattlePokemon *battler, u32 cmp, bool32 toValue, struct TestAIScoreStruct cmpCtx)
{
u32 moveSlot1, moveSlot2;
s32 i, target;
struct MoveContext moveCtx = {0};
s32 battlerId = battler - gBattleMons;
s32 turn = DATA.turns;
INVALID_IF(!IsAITest(), "SCORE_%s%s is usable only in AI_SINGLE_BATTLE_TEST & AI_DOUBLE_BATTLE_TEST", sCmpToStringTable[cmp], (toValue == TRUE) ? "_VAL" : "");
for (i = 0; i < MAX_AI_SCORE_COMPARISION_PER_TURN; i++)
{
if (!DATA.expectedAiScores[battlerId][turn][i].set)
break;
}
INVALID_IF(i == MAX_AI_SCORE_COMPARISION_PER_TURN, "Too many EXPECTs in TURN");
moveCtx.move = cmpCtx.move1;
moveCtx.explicitMove = cmpCtx.explicitMove1;
moveCtx.target = cmpCtx.target;
moveCtx.explicitTarget = cmpCtx.explicitTarget;
MoveGetIdAndSlot(battlerId, &moveCtx, &cmpCtx.move1, &moveSlot1, sourceLine);
// For ai moves, target is never self.
target = GetAiMoveTargetForScoreCompare(battlerId, cmpCtx.move1, &moveCtx, sourceLine);
DATA.expectedAiScores[battlerId][turn][i].target = target;
DATA.expectedAiScores[battlerId][turn][i].moveSlot1 = moveSlot1;
DATA.expectedAiScores[battlerId][turn][i].cmp = cmp;
DATA.expectedAiScores[battlerId][turn][i].toValue = toValue;
if (toValue)
{
DATA.expectedAiScores[battlerId][turn][i].value = cmpCtx.valueOrMoveId2;
}
else
{
moveCtx.move = cmpCtx.valueOrMoveId2;
moveCtx.explicitMove = cmpCtx.explicitValueOrMoveId2;
moveCtx.target = cmpCtx.target;
moveCtx.explicitTarget = cmpCtx.explicitTarget;
MoveGetIdAndSlot(battlerId, &moveCtx, &cmpCtx.valueOrMoveId2, &moveSlot2, sourceLine);
DATA.expectedAiScores[battlerId][turn][i].moveSlot2 = moveSlot2;
}
DATA.expectedAiScores[battlerId][turn][i].sourceLine = sourceLine;
DATA.expectedAiScores[battlerId][turn][i].set = TRUE;
}
void ExpectMoves(u32 sourceLine, struct BattlePokemon *battler, bool32 notExpected, struct FourMoves moves)
{
s32 battlerId = battler - gBattleMons;
u32 i;
for (i = 0; i < MAX_BATTLERS_COUNT; i++)
{
if (moves.moves[i] != MOVE_NONE)
{
struct MoveContext ctx = {0};
ctx.move = moves.moves[i];
ctx.explicitMove = ctx.explicitNotExpected = TRUE;
ctx.notExpected = notExpected;
TryMarkExpectMove(sourceLine, battler, &ctx);
}
}
DATA.expectedAiActionIndex[battlerId]++;
}
void Switch(u32 sourceLine, struct BattlePokemon *battler, u32 partyIndex)
{
s32 i;
s32 battlerId = battler - gBattleMons;
INVALID_IF(DATA.turnState == TURN_CLOSED, "SWITCH outside TURN");
INVALID_IF(DATA.actionBattlers & (1 << battlerId), "Multiple battler actions");
INVALID_IF(partyIndex >= ((battlerId & BIT_SIDE) == B_SIDE_PLAYER ? DATA.playerPartySize : DATA.opponentPartySize), "SWITCH to invalid party index");
INVALID_IF(IsAITest() && (battlerId & BIT_SIDE) == B_SIDE_OPPONENT, "SWITCH is not allowed for opponent in AI tests. Use EXPECT_SWITCH instead");
for (i = 0; i < STATE->battlersCount; i++)
{
if (battlerId != i && (battlerId & BIT_SIDE) == (i & BIT_SIDE))
INVALID_IF(DATA.currentMonIndexes[i] == partyIndex, "SWITCH to battler");
}
PushBattlerAction(sourceLine, battlerId, RECORDED_ACTION_TYPE, B_ACTION_SWITCH);
PushBattlerAction(sourceLine, battlerId, RECORDED_PARTY_INDEX, partyIndex);
DATA.currentMonIndexes[battlerId] = partyIndex;
DATA.actionBattlers |= 1 << battlerId;
}
void ExpectSwitch(u32 sourceLine, struct BattlePokemon *battler, u32 partyIndex)
{
s32 i, id;
s32 battlerId = battler - gBattleMons;
INVALID_IF(DATA.turnState == TURN_CLOSED, "EXPECT_SWITCH outside TURN");
INVALID_IF(!IsAITest(), "EXPECT_SWITCH is usable only in AI_SINGLE_BATTLE_TEST & AI_DOUBLE_BATTLE_TEST");
INVALID_IF(DATA.actionBattlers & (1 << battlerId), "Multiple battler actions");
INVALID_IF(partyIndex >= ((battlerId & BIT_SIDE) == B_SIDE_PLAYER ? DATA.playerPartySize : DATA.opponentPartySize), "EXPECT_SWITCH to invalid party index");
for (i = 0; i < STATE->battlersCount; i++)
{
if (battlerId != i && (battlerId & BIT_SIDE) == (i & BIT_SIDE))
INVALID_IF(DATA.currentMonIndexes[i] == partyIndex, "EXPECT_SWITCH to battler");
}
DATA.currentMonIndexes[battlerId] = partyIndex;
DATA.actionBattlers |= 1 << battlerId;
id = DATA.expectedAiActionIndex[battlerId];
DATA.expectedAiActions[battlerId][id].type = B_ACTION_SWITCH;
DATA.expectedAiActions[battlerId][id].target = partyIndex;
DATA.expectedAiActions[battlerId][id].sourceLine = sourceLine;
DATA.expectedAiActions[battlerId][id].actionSet = TRUE;
DATA.expectedAiActionIndex[battlerId]++;
}
void SkipTurn(u32 sourceLine, struct BattlePokemon *battler)
{
s32 battlerId = battler - gBattleMons;
INVALID_IF(DATA.turnState == TURN_CLOSED, "SKIP_TURN outside TURN");
DATA.actionBattlers |= 1 << battlerId;
}
void SendOut(u32 sourceLine, struct BattlePokemon *battler, u32 partyIndex)
{
s32 i;
s32 battlerId = battler - gBattleMons;
INVALID_IF(DATA.turnState == TURN_CLOSED, "SEND_OUT outside TURN");
INVALID_IF(partyIndex >= ((battlerId & BIT_SIDE) == B_SIDE_PLAYER ? DATA.playerPartySize : DATA.opponentPartySize), "SWITCH to invalid party index");
INVALID_IF(IsAITest() && (battlerId & BIT_SIDE) == B_SIDE_OPPONENT, "SEND_OUT is not allowed for opponent in AI tests. Use EXPECT_SEND_OUT instead");
for (i = 0; i < STATE->battlersCount; i++)
{
if (battlerId != i && (battlerId & BIT_SIDE) == (i & BIT_SIDE))
INVALID_IF(DATA.currentMonIndexes[i] == partyIndex, "SEND_OUT to battler");
}
if (!(DATA.actionBattlers & (1 << battlerId)))
Move(sourceLine, battler, (struct MoveContext) { move: MOVE_CELEBRATE, explicitMove: TRUE });
PushBattlerAction(sourceLine, battlerId, RECORDED_PARTY_INDEX, partyIndex);
DATA.currentMonIndexes[battlerId] = partyIndex;
}
void UseItem(u32 sourceLine, struct BattlePokemon *battler, struct ItemContext ctx)
{
s32 i;
s32 battlerId = battler - gBattleMons;
bool32 requirePartyIndex = ItemId_GetType(ctx.itemId) == ITEM_USE_PARTY_MENU || ItemId_GetType(ctx.itemId) == ITEM_USE_PARTY_MENU_MOVES;
// Check general bad use.
INVALID_IF(DATA.turnState == TURN_CLOSED, "USE_ITEM outside TURN");
INVALID_IF(DATA.actionBattlers & (1 << battlerId), "Multiple battler actions");
INVALID_IF(ctx.itemId >= ITEMS_COUNT, "Illegal item: %d", ctx.itemId);
// Check party menu items.
INVALID_IF(requirePartyIndex && !ctx.explicitPartyIndex, "%S requires explicit party index", ItemId_GetName(ctx.itemId));
INVALID_IF(requirePartyIndex && ctx.partyIndex >= ((battlerId & BIT_SIDE) == B_SIDE_PLAYER ? DATA.playerPartySize : DATA.opponentPartySize), \
"USE_ITEM to invalid party index");
// Check move slot items.
if (ItemId_GetType(ctx.itemId) == ITEM_USE_PARTY_MENU_MOVES)
{
INVALID_IF(!ctx.explicitMove, "%S requires an explicit move", ItemId_GetName(ctx.itemId));
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (GetMonData(CurrentMon(battlerId), MON_DATA_MOVE1 + i, NULL) == ctx.move)
break;
}
INVALID_IF(i == MAX_MON_MOVES, "USE_ITEM on invalid move: %d", ctx.move);
}
2023-04-20 21:45:16 +01:00
else
{
i = 0;
}
PushBattlerAction(sourceLine, battlerId, RECORDED_ACTION_TYPE, B_ACTION_USE_ITEM);
PushBattlerAction(sourceLine, battlerId, RECORDED_ITEM_ID, (ctx.itemId >> 8) & 0xFF);
PushBattlerAction(sourceLine, battlerId, RECORDED_ITEM_ID, ctx.itemId & 0xFF);
PushBattlerAction(sourceLine, battlerId, RECORDED_ITEM_TARGET, ctx.partyIndex);
PushBattlerAction(sourceLine, battlerId, RECORDED_ITEM_MOVE, i);
DATA.actionBattlers |= 1 << battlerId;
}
static const char *const sQueueGroupTypeMacros[] =
{
[QUEUE_GROUP_NONE] = NULL,
[QUEUE_GROUP_ONE_OF] = "ONE_OF",
[QUEUE_GROUP_NONE_OF] = "NONE_OF",
};
void OpenQueueGroup(u32 sourceLine, enum QueueGroupType type)
{
INVALID_IF(DATA.queueGroupType, "%s inside %s", sQueueGroupTypeMacros[type], sQueueGroupTypeMacros[DATA.queueGroupType]);
if (DATA.queuedEventsCount > 0
&& DATA.queuedEvents[DATA.queueGroupStart].groupType == QUEUE_GROUP_NONE_OF
&& DATA.queuedEvents[DATA.queueGroupStart].groupSize == DATA.queuedEventsCount - DATA.queueGroupStart
&& type == QUEUE_GROUP_NONE_OF)
{
INVALID("'NOT x; NOT y;', did you mean 'NONE_OF { x; y; }'?");
}
else
{
DATA.queueGroupType = type;
DATA.queueGroupStart = DATA.queuedEventsCount;
}
}
void CloseQueueGroup(u32 sourceLine)
{
u32 groupSize = DATA.queuedEventsCount - DATA.queueGroupStart;
if (groupSize > 0)
{
DATA.queuedEvents[DATA.queueGroupStart].groupType = DATA.queueGroupType;
DATA.queuedEvents[DATA.queueGroupStart].groupSize = groupSize;
DATA.queueGroupType = QUEUE_GROUP_NONE;
}
}
void QueueAbility(u32 sourceLine, struct BattlePokemon *battler, struct AbilityEventContext ctx)
{
#if B_ABILITY_POP_UP
s32 battlerId = battler - gBattleMons;
INVALID_IF(!STATE->runScene, "ABILITY_POPUP outside of SCENE");
if (DATA.queuedEventsCount == MAX_QUEUED_EVENTS)
Test_ExitWithResult(TEST_RESULT_ERROR, sourceLine, ":L%s:%d: ABILITY exceeds MAX_QUEUED_EVENTS", gTestRunnerState.test->filename, sourceLine);
DATA.queuedEvents[DATA.queuedEventsCount++] = (struct QueuedEvent) {
.type = QUEUED_ABILITY_POPUP_EVENT,
.sourceLineOffset = SourceLineOffset(sourceLine),
.groupType = QUEUE_GROUP_NONE,
.groupSize = 1,
.as = { .ability = {
.battlerId = battlerId,
.ability = ctx.ability,
}},
};
#endif
}
void QueueAnimation(u32 sourceLine, u32 type, u32 id, struct AnimationEventContext ctx)
{
s32 attackerId, targetId;
INVALID_IF(!STATE->runScene, "ANIMATION outside of SCENE");
if (DATA.queuedEventsCount == MAX_QUEUED_EVENTS)
Test_ExitWithResult(TEST_RESULT_ERROR, sourceLine, ":L%s:%d: ANIMATION exceeds MAX_QUEUED_EVENTS", gTestRunnerState.test->filename, sourceLine);
attackerId = ctx.attacker ? ctx.attacker - gBattleMons : 0xF;
if (type == ANIM_TYPE_MOVE)
{
targetId = ctx.target ? ctx.target - gBattleMons : 0xF;
}
else
{
INVALID_IF(ctx.target, "ANIMATION target set for non-ANIM_TYPE_MOVE");
targetId = 0xF;
}
DATA.queuedEvents[DATA.queuedEventsCount++] = (struct QueuedEvent) {
.type = QUEUED_ANIMATION_EVENT,
.sourceLineOffset = SourceLineOffset(sourceLine),
.groupType = QUEUE_GROUP_NONE,
.groupSize = 1,
.as = { .animation = {
.type = type,
.id = id,
.attacker = attackerId,
.target = targetId,
}},
};
}
void QueueHP(u32 sourceLine, struct BattlePokemon *battler, struct HPEventContext ctx)
{
s32 battlerId = battler - gBattleMons;
u32 type;
uintptr_t address;
INVALID_IF(!STATE->runScene, "HP_BAR outside of SCENE");
if (DATA.queuedEventsCount == MAX_QUEUED_EVENTS)
Test_ExitWithResult(TEST_RESULT_ERROR, sourceLine, ":L%s:%d: HP_BAR exceeds MAX_QUEUED_EVENTS", gTestRunnerState.test->filename, sourceLine);
if (ctx.explicitHP)
{
type = HP_EVENT_NEW_HP;
address = (u16)ctx.hp;
}
else if (ctx.explicitDamage)
{
INVALID_IF(ctx.damage == 0, "damage is 0");
type = HP_EVENT_DELTA_HP;
address = (u16)ctx.damage;
}
else if (ctx.explicitCaptureHP)
{
INVALID_IF(ctx.captureHP == NULL, "captureHP is NULL");
type = HP_EVENT_NEW_HP;
address = (uintptr_t)ctx.captureHP;
}
else if (ctx.explicitCaptureDamage)
{
INVALID_IF(ctx.captureDamage == NULL, "captureDamage is NULL");
type = HP_EVENT_DELTA_HP;
*ctx.captureDamage = 0;
address = (uintptr_t)ctx.captureDamage;
}
else
{
type = HP_EVENT_DELTA_HP;
address = 0;
}
DATA.queuedEvents[DATA.queuedEventsCount++] = (struct QueuedEvent) {
.type = QUEUED_HP_EVENT,
.sourceLineOffset = SourceLineOffset(sourceLine),
.groupType = QUEUE_GROUP_NONE,
.groupSize = 1,
.as = { .hp = {
.battlerId = battlerId,
.type = type,
.address = address,
}},
};
}
void QueueExp(u32 sourceLine, struct BattlePokemon *battler, struct ExpEventContext ctx)
{
s32 battlerId = battler - gBattleMons;
u32 type;
uintptr_t address;
INVALID_IF(!STATE->runScene, "EXPERIENCE_BAR outside of SCENE");
if (DATA.queuedEventsCount == MAX_QUEUED_EVENTS)
Test_ExitWithResult(TEST_RESULT_ERROR, sourceLine, ":L%s:%d: EXPERIENCE_BAR exceeds MAX_QUEUED_EVENTS", gTestRunnerState.test->filename, sourceLine);
if (ctx.explicitExp)
{
type = EXP_EVENT_NEW_EXP;
address = (u32)ctx.exp;
}
else if (ctx.explicitCaptureGainedExp)
{
INVALID_IF(ctx.captureGainedExp == NULL, "captureGainedExp is NULL");
type = EXP_EVENT_DELTA_EXP;
*ctx.captureGainedExp = 0;
address = (uintptr_t)ctx.captureGainedExp;
}
else
{
type = EXP_EVENT_DELTA_EXP;
address = 0;
}
DATA.queuedEvents[DATA.queuedEventsCount++] = (struct QueuedEvent) {
.type = QUEUED_EXP_EVENT,
.sourceLineOffset = SourceLineOffset(sourceLine),
.groupType = QUEUE_GROUP_NONE,
.groupSize = 1,
.as = { .exp = {
.battlerId = battlerId,
.type = type,
.address = address,
}},
};
}
void QueueMessage(u32 sourceLine, const u8 *pattern)
{
INVALID_IF(!STATE->runScene, "MESSAGE outside of SCENE");
if (DATA.queuedEventsCount == MAX_QUEUED_EVENTS)
Test_ExitWithResult(TEST_RESULT_ERROR, sourceLine, ":L%s:%d: MESSAGE exceeds MAX_QUEUED_EVENTS", gTestRunnerState.test->filename, sourceLine);
DATA.queuedEvents[DATA.queuedEventsCount++] = (struct QueuedEvent) {
.type = QUEUED_MESSAGE_EVENT,
.sourceLineOffset = SourceLineOffset(sourceLine),
.groupType = QUEUE_GROUP_NONE,
.groupSize = 1,
.as = { .message = {
.pattern = pattern,
}},
};
}
void QueueStatus(u32 sourceLine, struct BattlePokemon *battler, struct StatusEventContext ctx)
{
s32 battlerId = battler - gBattleMons;
u32 mask;
INVALID_IF(!STATE->runScene, "STATUS_ICON outside of SCENE");
if (DATA.queuedEventsCount == MAX_QUEUED_EVENTS)
Test_ExitWithResult(TEST_RESULT_ERROR, sourceLine, ":L%s:%d: STATUS_ICON exceeds MAX_QUEUED_EVENTS", gTestRunnerState.test->filename, sourceLine);
if (ctx.none)
mask = 0;
else if (ctx.sleep)
mask = STATUS1_SLEEP;
else if (ctx.poison)
mask = STATUS1_POISON;
else if (ctx.burn)
mask = STATUS1_BURN;
else if (ctx.freeze)
mask = STATUS1_FREEZE;
else if (ctx.paralysis)
mask = STATUS1_PARALYSIS;
else if (ctx.badPoison)
mask = STATUS1_TOXIC_POISON;
2023-04-28 11:38:34 +01:00
else if (ctx.frostbite)
mask = STATUS1_FROSTBITE;
else
mask = ctx.status1;
DATA.queuedEvents[DATA.queuedEventsCount++] = (struct QueuedEvent) {
.type = QUEUED_STATUS_EVENT,
.sourceLineOffset = SourceLineOffset(sourceLine),
.groupType = QUEUE_GROUP_NONE,
.groupSize = 1,
.as = { .status = {
.battlerId = battlerId,
.mask = mask,
}},
};
}
2023-04-26 11:12:41 +01:00
void ValidateFinally(u32 sourceLine)
{
// Defer this error until after estimating the cost.
INVALID_IF(STATE->parametersCount == 0, "FINALLY without PARAMETRIZE");
}
u32 TestRunner_Battle_GetForcedAbility(u32 side, u32 partyIndex)
{
return DATA.forcedAbilities[side][partyIndex];
}
Gimmick Refactor (#4449) * consolidated gimmick checks, triggers, communication, and activation; updated test runner * fixed improper use of .usableGimmick * cleaning up battle_dynamax.c, changing function args to u32s * fixed '#ifdef TESTING' causing errors * updated z-moves to use gimmick interface, pared down redundancies; no AI/tests * added support for z-moves in tests, consolidated gimmick fields * removed ShouldUseMaxMove and .usingMaxMove * renamed TryChangeZIndicator, updated z move display * added several z-move tests and fixed various z-move interactions; fixed z-move category calc * fixed useless battler arg in GetTypeBasedZMove * added basic test check for bad Z-Move or Mega usage * reworked test runner gimmick functionality; added support for Ultra Burst + Z-Move to test Light That Burns the Sky * fixed gimmick test logic; fixed damage category override * fixed mega rayquaza test fail * consolidated gimmick indicator logic; added graphics to gGimmicksInfo * removed TeraData struct * reimplemented AI logic for Z-Moves; no changes * updated Z-Move and Ultra Burst trigger gfx * added testrunner check for multiple gimmick use * fixed duplicate z-move call in test * reorganized data/graphics/gimmicks.h * added signature Z-Move ability tests; implemented Guardian of Alola * fixed bad test update * fixed Thousand Arrows not affecting Tera Flying; clean-up * fixed -ate tests * fixed tera tests * fixed tera tests really * fixed last batch of tests * fixed -ate mega test again * code review * code review pt.2 * tweaked CanTera again * dynamax flag only required for player
2024-06-22 21:25:40 +01:00
u32 TestRunner_Battle_GetChosenGimmick(u32 side, u32 partyIndex)
{
return DATA.chosenGimmick[side][partyIndex];
}
// TODO: Consider storing the last successful i and searching from i+1
// to improve performance.
struct AILogLine *GetLogLine(u32 battlerId, u32 moveIndex)
{
s32 i;
for (i = 0; i < MAX_AI_LOG_LINES; i++)
{
struct AILogLine *log = &DATA.aiLogLines[battlerId][moveIndex][i];
if (log->file == NULL)
{
return log;
}
}
Test_ExitWithResult(TEST_RESULT_ERROR, SourceLine(0), ":LToo many AI log lines");
return NULL;
}
void TestRunner_Battle_AILogScore(const char *file, u32 line, u32 battlerId, u32 moveIndex, s32 score, bool32 setScore)
{
struct AILogLine *log;
if (!DATA.logAI) return;
log = GetLogLine(battlerId, moveIndex);
log->file = file;
log->line = line;
log->score = score;
log->set = setScore;
}
void TestRunner_Battle_AISetScore(const char *file, u32 line, u32 battlerId, u32 moveIndex, s32 score)
{
TestRunner_Battle_AILogScore(file, line, battlerId, moveIndex, score, TRUE);
}
void TestRunner_Battle_AIAdjustScore(const char *file, u32 line, u32 battlerId, u32 moveIndex, s32 score)
{
TestRunner_Battle_AILogScore(file, line, battlerId, moveIndex, score, FALSE);
}