Merge branch 'master' into _RHH/pr/master/unused

This commit is contained in:
Eduardo Quezada D'Ottone 2024-01-31 08:18:55 -03:00 committed by GitHub
commit 5af733a391
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 142 additions and 77 deletions

View File

@ -7,11 +7,11 @@ JASC-PAL
201 201 201
169 169 169
129 129 129
249 153 161
233 49 49
193 33 41
145 17 33
249 153 161
106 106 106
37 37 37
106 106 106
0 0 0
106 106 106
193 33 41
141 251 184
52 66 162

View File

@ -323,7 +323,7 @@ struct AiLogicData
bool8 shouldSwitchMon; // Because all available moves have no/little effect. Each bit per battler.
u8 monToSwitchId[MAX_BATTLERS_COUNT]; // ID of the mon to switch.
bool8 weatherHasEffect; // The same as WEATHER_HAS_EFFECT. Stored here, so it's called only once.
u8 mostSuitableMonId; // Stores result of GetMostSuitableMonToSwitchInto, which decides which generic mon the AI would switch into if they decide to switch. This can be overruled by specific mons found in ShouldSwitch; the final resulting mon is stored in AI_monToSwitchIntoId.
u8 mostSuitableMonId[MAX_BATTLERS_COUNT]; // Stores result of GetMostSuitableMonToSwitchInto, which decides which generic mon the AI would switch into if they decide to switch. This can be overruled by specific mons found in ShouldSwitch; the final resulting mon is stored in AI_monToSwitchIntoId.
struct SwitchinCandidate switchinCandidate; // Struct used for deciding which mon to switch to in battle_ai_switch_items.c
};

View File

@ -5,6 +5,7 @@
#include "battle_anim.h"
#include "battle_ai_util.h"
#include "battle_ai_main.h"
#include "battle_controllers.h"
#include "battle_factory.h"
#include "battle_setup.h"
#include "battle_z_move.h"
@ -414,11 +415,21 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData)
}
}
static bool32 AI_SwitchMonIfSuitable(u32 battler)
static bool32 AI_SwitchMonIfSuitable(u32 battler, bool32 doubleBattle)
{
u32 monToSwitchId = AI_DATA->mostSuitableMonId;
if (monToSwitchId != PARTY_SIZE)
u32 monToSwitchId = AI_DATA->mostSuitableMonId[battler];
if (monToSwitchId != PARTY_SIZE && IsValidForBattle(&GetBattlerParty(battler)[monToSwitchId]))
{
gBattleMoveDamage = monToSwitchId;
// Edge case: See if partner already chose to switch into the same mon
if (doubleBattle)
{
u32 partner = BATTLE_PARTNER(battler);
if (AI_DATA->shouldSwitchMon & gBitTable[partner] && AI_DATA->monToSwitchId[partner] == monToSwitchId)
{
return FALSE;
}
}
AI_DATA->shouldSwitchMon |= gBitTable[battler];
AI_DATA->monToSwitchId[battler] = monToSwitchId;
return TRUE;
@ -455,7 +466,7 @@ static bool32 AI_ShouldSwitchIfBadMoves(u32 battler, bool32 doubleBattle)
break;
}
}
if (i == MAX_BATTLERS_COUNT && AI_SwitchMonIfSuitable(battler))
if (i == MAX_BATTLERS_COUNT && AI_SwitchMonIfSuitable(battler, doubleBattle))
return TRUE;
}
else
@ -466,7 +477,7 @@ static bool32 AI_ShouldSwitchIfBadMoves(u32 battler, bool32 doubleBattle)
break;
}
if (i == MAX_MON_MOVES && AI_SwitchMonIfSuitable(battler))
if (i == MAX_MON_MOVES && AI_SwitchMonIfSuitable(battler, doubleBattle))
return TRUE;
}
@ -478,7 +489,7 @@ static bool32 AI_ShouldSwitchIfBadMoves(u32 battler, bool32 doubleBattle)
&& IsTruantMonVulnerable(battler, gBattlerTarget)
&& gDisableStructs[battler].truantCounter
&& gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2
&& AI_SwitchMonIfSuitable(battler))
&& AI_SwitchMonIfSuitable(battler, doubleBattle))
{
return TRUE;
}

View File

@ -65,7 +65,7 @@ void GetAIPartyIndexes(u32 battler, s32 *firstId, s32 *lastId)
}
// Note that as many return statements as possible are INTENTIONALLY put after all of the loops;
// the function can take a max of about 0.06s to run, and this prevents the player from identifying
// the function can take a max of about 0.06s to run, and this prevents the player from identifying
// whether the mon will switch or not by seeing how long the delay is before they select a move
static bool8 HasBadOdds(u32 battler)
@ -82,12 +82,12 @@ static bool8 HasBadOdds(u32 battler)
return FALSE;
// Won't bother configuring this for double battles
if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE)
if (gBattleTypeFlags & BATTLE_TYPE_DOUBLE)
return FALSE;
opposingPosition = BATTLE_OPPOSITE(GetBattlerPosition(battler));
opposingBattler = GetBattlerAtPosition(opposingPosition);
// Gets types of player (opposingBattler) and computer (battler)
atkType1 = gBattleMons[opposingBattler].type1;
atkType2 = gBattleMons[opposingBattler].type2;
@ -102,7 +102,7 @@ static bool8 HasBadOdds(u32 battler)
if (aiMove != MOVE_NONE)
{
// Check if mon has an "important" status move
if (aiMoveEffect == EFFECT_REFLECT || aiMoveEffect == EFFECT_LIGHT_SCREEN
if (aiMoveEffect == EFFECT_REFLECT || aiMoveEffect == EFFECT_LIGHT_SCREEN
|| aiMoveEffect == EFFECT_SPIKES || aiMoveEffect == EFFECT_TOXIC_SPIKES || aiMoveEffect == EFFECT_STEALTH_ROCK || aiMoveEffect == EFFECT_STICKY_WEB || aiMoveEffect == EFFECT_LEECH_SEED
|| aiMoveEffect == EFFECT_EXPLOSION
|| aiMoveEffect == EFFECT_SLEEP || aiMoveEffect == EFFECT_YAWN || aiMoveEffect == EFFECT_TOXIC || aiMoveEffect == EFFECT_WILL_O_WISP || aiMoveEffect == EFFECT_PARALYZE
@ -168,23 +168,23 @@ static bool8 HasBadOdds(u32 battler)
}
// If we don't have any other viable options, don't switch out
if (AI_DATA->mostSuitableMonId == PARTY_SIZE)
if (AI_DATA->mostSuitableMonId[battler] == PARTY_SIZE)
return FALSE;
// Start assessing whether or not mon has bad odds
// Jump straight to swtiching out in cases where mon gets OHKO'd
if (((getsOneShot && gBattleMons[opposingBattler].speed > gBattleMons[battler].speed) // If the player OHKOs and outspeeds OR OHKOs, doesn't outspeed but isn't 2HKO'd
|| (getsOneShot && gBattleMons[opposingBattler].speed <= gBattleMons[battler].speed && maxDamageDealt < gBattleMons[opposingBattler].hp / 2))
|| (getsOneShot && gBattleMons[opposingBattler].speed <= gBattleMons[battler].speed && maxDamageDealt < gBattleMons[opposingBattler].hp / 2))
&& (gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2 // And the current mon has at least 1/2 their HP, or 1/4 HP and Regenerator
|| (aiAbility == ABILITY_REGENERATOR
&& gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 4)))
|| (aiAbility == ABILITY_REGENERATOR
&& gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 4)))
{
// 50% chance to stay in regardless
if (Random() % 2 == 0)
if (Random() % 2 == 0)
return FALSE;
// Switch mon out
*(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE;
*(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE;
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
}
@ -194,19 +194,19 @@ static bool8 HasBadOdds(u32 battler)
{
if (!hasSuperEffectiveMove // If the AI doesn't have a super effective move
&& (gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2 // And the current mon has at least 1/2 their HP, or 1/4 HP and Regenerator
|| (aiAbility == ABILITY_REGENERATOR
&& gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 4)))
|| (aiAbility == ABILITY_REGENERATOR
&& gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 4)))
{
// Then check if they have an important status move, which is worth using even in a bad matchup
if(hasStatusMove)
return FALSE;
// 50% chance to stay in regardless
if (Random() % 2 == 0)
if (Random() % 2 == 0)
return FALSE;
// Switch mon out
*(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE;
*(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE;
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
return TRUE;
}
@ -593,12 +593,12 @@ static bool8 ShouldSwitchIfAbilityBenefit(u32 battler)
moduloChance = 4; //25%
//Attempt to cure bad ailment
if (gBattleMons[battler].status1 & (STATUS1_SLEEP | STATUS1_FREEZE | STATUS1_TOXIC_POISON)
&& AI_DATA->mostSuitableMonId != PARTY_SIZE)
&& AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE)
break;
//Attempt to cure lesser ailment
if ((gBattleMons[battler].status1 & STATUS1_ANY)
&& (gBattleMons[battler].hp >= gBattleMons[battler].maxHP / 2)
&& AI_DATA->mostSuitableMonId != PARTY_SIZE
&& AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE
&& Random() % (moduloChance*chanceReducer) == 0)
break;
@ -610,7 +610,7 @@ static bool8 ShouldSwitchIfAbilityBenefit(u32 battler)
if (gBattleMons[battler].status1 & STATUS1_ANY)
return FALSE;
if ((gBattleMons[battler].hp <= ((gBattleMons[battler].maxHP * 2) / 3))
&& AI_DATA->mostSuitableMonId != PARTY_SIZE
&& AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE
&& Random() % (moduloChance*chanceReducer) == 0)
break;
@ -785,7 +785,7 @@ static bool32 CanMonSurviveHazardSwitchin(u32 battler)
if (ability == ABILITY_REGENERATOR)
battlerHp = (battlerHp * 133) / 100; // Account for Regenerator healing
hazardDamage = GetSwitchinHazardsDamage(battler, &gBattleMons[battler]);
// Battler will faint to hazards, check to see if another mon can clear them
@ -840,13 +840,13 @@ static bool32 CanMonSurviveHazardSwitchin(u32 battler)
}
static bool32 ShouldSwitchIfEncored(u32 battler)
{
{
// Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer
if (!(AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_SWITCHING))
return FALSE;
// If not Encored or if no good switchin, don't switch
if (gDisableStructs[battler].encoredMove == MOVE_NONE || AI_DATA->mostSuitableMonId == PARTY_SIZE)
if (gDisableStructs[battler].encoredMove == MOVE_NONE || AI_DATA->mostSuitableMonId[battler] == PARTY_SIZE)
return FALSE;
// Otherwise 50% chance to switch out
@ -879,7 +879,7 @@ static bool8 AreAttackingStatsLowered(u32 battler)
// 50% chance if attack at -2 and have a good candidate mon
else if (attackingStage == DEFAULT_STAT_STAGE - 2)
{
if (AI_DATA->mostSuitableMonId != PARTY_SIZE && (Random() & 1))
if (AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && (Random() & 1))
{
*(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE;
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
@ -904,7 +904,7 @@ static bool8 AreAttackingStatsLowered(u32 battler)
// 50% chance if attack at -2 and have a good candidate mon
else if (spAttackingStage == DEFAULT_STAT_STAGE - 2)
{
if (AI_DATA->mostSuitableMonId != PARTY_SIZE && (Random() & 1))
if (AI_DATA->mostSuitableMonId[battler] != PARTY_SIZE && (Random() & 1))
{
*(gBattleStruct->AI_monToSwitchIntoId + battler) = PARTY_SIZE;
BtlController_EmitTwoReturnValues(battler, 1, B_ACTION_SWITCH, 0);
@ -1053,7 +1053,7 @@ void AI_TrySwitchOrUseItem(u32 battler)
{
if (*(gBattleStruct->AI_monToSwitchIntoId + battler) == PARTY_SIZE)
{
s32 monToSwitchId = AI_DATA->mostSuitableMonId;
s32 monToSwitchId = AI_DATA->mostSuitableMonId[battler];
if (monToSwitchId == PARTY_SIZE)
{
if (!(gBattleTypeFlags & BATTLE_TYPE_DOUBLE))
@ -1367,7 +1367,7 @@ static s32 GetSwitchinWeatherImpact(void)
// Gets one turn of recurring healing
static u32 GetSwitchinRecurringHealing(void)
{
{
u32 recurringHealing = 0, maxHP = AI_DATA->switchinCandidate.battleMon.maxHP, ability = AI_DATA->switchinCandidate.battleMon.ability;
u16 item = AI_DATA->switchinCandidate.battleMon.item;
@ -1439,9 +1439,9 @@ static u32 GetSwitchinStatusDamage(u32 battler)
u32 statusDamage = 0;
// Status condition damage
if ((status != 0) && AI_DATA->switchinCandidate.battleMon.ability != ABILITY_MAGIC_GUARD)
if ((status != 0) && AI_DATA->switchinCandidate.battleMon.ability != ABILITY_MAGIC_GUARD)
{
if (status & STATUS1_BURN)
if (status & STATUS1_BURN)
{
#if B_BURN_DAMAGE >= GEN_7
statusDamage = maxHP / 16;
@ -1484,7 +1484,7 @@ static u32 GetSwitchinStatusDamage(u32 battler)
if (tSpikesLayers != 0 && (defType1 != TYPE_POISON && defType2 != TYPE_POISON
&& ability != ABILITY_IMMUNITY && ability != ABILITY_POISON_HEAL
&& status == 0
&& !(heldItemEffect == HOLD_EFFECT_HEAVY_DUTY_BOOTS
&& !(heldItemEffect == HOLD_EFFECT_HEAVY_DUTY_BOOTS
&& (((gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) || ability == ABILITY_KLUTZ)))
&& heldItemEffect != HOLD_EFFECT_CURE_PSN && heldItemEffect != HOLD_EFFECT_CURE_STATUS
&& IsMonGrounded(heldItemEffect, ability, defType1, defType2)))
@ -1559,7 +1559,7 @@ static u32 GetSwitchinHitsToKO(s32 damageTaken, u32 battler)
singleUseItemHeal = 1;
}
}
else if (currentHP < maxHP / CONFUSE_BERRY_HP_FRACTION
else if (currentHP < maxHP / CONFUSE_BERRY_HP_FRACTION
&& opposingAbility != ABILITY_UNNERVE
&& (item == ITEM_AGUAV_BERRY || item == ITEM_FIGY_BERRY || item == ITEM_IAPAPA_BERRY || item == ITEM_MAGO_BERRY || item == ITEM_WIKI_BERRY))
{
@ -1608,7 +1608,7 @@ static u16 GetSwitchinTypeMatchup(u32 opposingBattler, struct BattlePokemon batt
// Check type matchup
u16 typeEffectiveness = UQ_4_12(1.0);
u8 atkType1 = gSpeciesInfo[gBattleMons[opposingBattler].species].types[0], atkType2 = gSpeciesInfo[gBattleMons[opposingBattler].species].types[1],
u8 atkType1 = gSpeciesInfo[gBattleMons[opposingBattler].species].types[0], atkType2 = gSpeciesInfo[gBattleMons[opposingBattler].species].types[1],
defType1 = battleMon.type1, defType2 = battleMon.type2;
// Multiply type effectiveness by a factor depending on type matchup
@ -1669,7 +1669,7 @@ static s32 GetMaxDamagePlayerCouldDealToSwitchin(u32 battler, u32 opposingBattle
// the Type Matchup code will prioritize switching into a mon with the best type matchup and also a super effective move, or just best type matchup if no super effective move is found
// the Most Defensive code will prioritize switching into the mon that takes the most hits to KO, with a minimum of 4 hits required to be considered a valid option
// the Baton Pass code will prioritize switching into a mon with Baton Pass if it can get in, boost, and BP out without being KO'd, and randomizes between multiple valid options
// the Revenge Killer code will prioritize, in order, OHKO and outspeeds / OHKO, slower but not 2HKO'd / 2HKO, outspeeds and not OHKO'd / 2HKO, slower but not 3HKO'd
// the Revenge Killer code will prioritize, in order, OHKO and outspeeds / OHKO, slower but not 2HKO'd / 2HKO, outspeeds and not OHKO'd / 2HKO, slower but not 3HKO'd
// the Most Damage code will prioritize switching into whatever mon deals the most damage, which is generally not as good as having a good Type Matchup
// Everything runs in the same loop to minimize computation time. This makes it harder to read, but hopefully the comments can guide you!
@ -1712,7 +1712,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
InitializeSwitchinCandidate(&party[i]);
// While not really invalid per say, not really wise to switch into this mon
if (AI_DATA->switchinCandidate.battleMon.ability == ABILITY_TRUANT && IsTruantMonVulnerable(battler, opposingBattler))
if (AI_DATA->switchinCandidate.battleMon.ability == ABILITY_TRUANT && IsTruantMonVulnerable(battler, opposingBattler))
continue;
// Get max number of hits for player to KO AI mon
@ -1748,7 +1748,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
// Only do damage calc if switching after KO, don't need it otherwise and saves ~0.02s per turn
if (isSwitchAfterKO && aiMove != MOVE_NONE && gBattleMoves[aiMove].power != 0)
damageDealt = AI_CalcPartyMonDamage(aiMove, battler, opposingBattler, AI_DATA->switchinCandidate.battleMon, TRUE);
// Check for Baton Pass; hitsToKO requirements mean mon can boost and BP without dying whether it's slower or not
if (aiMove == MOVE_BATON_PASS && ((hitsToKO > hitsToKOThreshold + 1 && AI_DATA->switchinCandidate.battleMon.speed < playerMonSpeed) || (hitsToKO > hitsToKOThreshold && AI_DATA->switchinCandidate.battleMon.speed > playerMonSpeed)))
bits |= gBitTable[i];
@ -1857,7 +1857,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
else if (typeMatchupId != PARTY_SIZE)
return typeMatchupId;
else if (batonPassId != PARTY_SIZE)
return batonPassId;
@ -1932,7 +1932,7 @@ u8 GetMostSuitableMonToSwitchInto(u32 battler, bool32 switchAfterMonKOd)
if (AI_THINKING_STRUCT->aiFlags & AI_FLAG_SMART_MON_CHOICES)
{
bestMonId = GetBestMonIntegrated(party, firstId, lastId, battler, opposingBattler, battlerIn1, battlerIn2, switchAfterMonKOd);
return bestMonId;
return bestMonId;
}
// This all handled by the GetBestMonIntegrated function if the AI_FLAG_SMART_MON_CHOICES flag is set
@ -2138,4 +2138,4 @@ static bool32 AI_OpponentCanFaintAiWithMod(u32 battler, u32 healAmount)
}
}
return FALSE;
}
}

View File

@ -4037,7 +4037,7 @@ static void HandleTurnActionSelectionState(void)
if ((gBattleTypeFlags & BATTLE_TYPE_HAS_AI || IsWildMonSmart())
&& (BattlerHasAi(battler) && !(gBattleTypeFlags & BATTLE_TYPE_PALACE)))
{
AI_DATA->mostSuitableMonId = GetMostSuitableMonToSwitchInto(battler, FALSE);
AI_DATA->mostSuitableMonId[battler] = GetMostSuitableMonToSwitchInto(battler, FALSE);
gBattleStruct->aiMoveOrAction[battler] = ComputeBattleAiScores(battler);
}
// fallthrough

View File

@ -660,3 +660,31 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will not switch out if Pokemo
}
}
}
AI_DOUBLE_BATTLE_TEST("AI will not try to switch for the same pokemon for 2 spots in a double battle")
{
u32 flags;
PARAMETRIZE {flags = AI_FLAG_SMART_SWITCHING; }
PARAMETRIZE {flags = 0; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | flags);
PLAYER(SPECIES_RATTATA);
PLAYER(SPECIES_RATTATA);
// No moves to damage player.
OPPONENT(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL); }
OPPONENT(SPECIES_HAUNTER) { Moves(MOVE_SHADOW_BALL); }
OPPONENT(SPECIES_GENGAR) { Moves(MOVE_SHADOW_BALL); }
OPPONENT(SPECIES_RATICATE) { Moves(MOVE_HEADBUTT); }
} WHEN {
TURN { EXPECT_SWITCH(opponentLeft, 3); };
} SCENE {
MESSAGE("{PKMN} TRAINER LEAF withdrew Gengar!");
MESSAGE("{PKMN} TRAINER LEAF sent out Raticate!");
NONE_OF {
MESSAGE("{PKMN} TRAINER LEAF withdrew Haunter!");
MESSAGE("{PKMN} TRAINER LEAF sent out Raticate!");
}
}
}

View File

@ -30,7 +30,6 @@ void TestRunner_Battle(const struct Test *);
static bool32 MgbaOpen_(void);
static void MgbaExit_(u8 exitCode);
static s32 MgbaPuts_(const char *s);
static s32 MgbaVPrintf_(const char *fmt, va_list va);
static void Intr_Timer2(void);
@ -293,12 +292,6 @@ top:
color = "";
}
if (gTestRunnerState.result == TEST_RESULT_PASS
&& gTestRunnerState.result != gTestRunnerState.expectedResult)
{
MgbaPuts_("\e[31mPlease remove KNOWN_FAILING if this test intentionally PASSes\e[0m");
}
switch (gTestRunnerState.result)
{
case TEST_RESULT_FAIL:
@ -313,7 +306,10 @@ top:
}
break;
case TEST_RESULT_PASS:
result = "PASS";
if (gTestRunnerState.result != gTestRunnerState.expectedResult)
result = "KNOWN_FAILING_PASS";
else
result = "PASS";
break;
case TEST_RESULT_ASSUMPTION_FAIL:
result = "ASSUMPTION_FAIL";
@ -341,7 +337,12 @@ top:
}
if (gTestRunnerState.result == TEST_RESULT_PASS)
MgbaPrintf_(":P%s%s\e[0m", color, result);
{
if (gTestRunnerState.result != gTestRunnerState.expectedResult)
MgbaPrintf_(":U%s%s\e[0m", color, result);
else
MgbaPrintf_(":P%s%s\e[0m", color, result);
}
else if (gTestRunnerState.result == TEST_RESULT_ASSUMPTION_FAIL)
MgbaPrintf_(":A%s%s\e[0m", color, result);
else if (gTestRunnerState.result == TEST_RESULT_TODO)
@ -503,11 +504,6 @@ static void MgbaExit_(u8 exitCode)
asm("swi 0x3" :: "r" (_exitCode));
}
static s32 MgbaPuts_(const char *s)
{
return MgbaPrintf_("%s", s);
}
s32 MgbaPrintf_(const char *fmt, ...)
{
va_list va;

View File

@ -35,7 +35,7 @@
#define min(a, b) ((a) < (b) ? (a) : (b))
#define MAX_PROCESSES 32 // See also test/test.h
#define MAX_FAILED_TESTS_TO_LIST 100
#define MAX_SUMMARY_TESTS_TO_LIST 50
#define MAX_TEST_LIST_BUFFER_LENGTH 256
#define ARRAY_COUNT(arr) (sizeof((arr)) / sizeof((arr)[0]))
@ -54,11 +54,13 @@ struct Runner
char *output_buffer;
int passes;
int knownFails;
int knownFailsPassing;
int todos;
int assumptionFails;
int fails;
int results;
char failedTestNames[MAX_FAILED_TESTS_TO_LIST][MAX_TEST_LIST_BUFFER_LENGTH];
char failedTestNames[MAX_SUMMARY_TESTS_TO_LIST][MAX_TEST_LIST_BUFFER_LENGTH];
char knownFailingPassedTestNames[MAX_SUMMARY_TESTS_TO_LIST][MAX_TEST_LIST_BUFFER_LENGTH];
};
static unsigned nrunners = 0;
@ -107,6 +109,11 @@ static void handle_read(int i, struct Runner *runner)
case 'K':
runner->knownFails++;
goto add_to_results;
case 'U':
if (runner->knownFailsPassing < MAX_SUMMARY_TESTS_TO_LIST)
strcpy(runner->knownFailingPassedTestNames[runner->knownFailsPassing], runner->test_name);
runner->knownFailsPassing++;
goto add_to_results;
case 'T':
runner->todos++;
goto add_to_results;
@ -114,7 +121,7 @@ static void handle_read(int i, struct Runner *runner)
runner->assumptionFails++;
goto add_to_results;
case 'F':
if (runner->fails < MAX_FAILED_TESTS_TO_LIST)
if (runner->fails < MAX_SUMMARY_TESTS_TO_LIST)
strcpy(runner->failedTestNames[runner->fails], runner->test_name);
runner->fails++;
add_to_results:
@ -519,12 +526,14 @@ int main(int argc, char *argv[])
int exit_code = 0;
int passes = 0;
int knownFails = 0;
int knownFailsPassing = 0;
int todos = 0;
int assumptionFails = 0;
int fails = 0;
int results = 0;
char failedTestNames[MAX_FAILED_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH];
char failedTestNames[MAX_SUMMARY_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH];
char knownFailingPassedTestNames[MAX_SUMMARY_TESTS_TO_LIST * MAX_PROCESSES][MAX_TEST_LIST_BUFFER_LENGTH];
for (int i = 0; i < nrunners; i++)
{
@ -540,18 +549,25 @@ int main(int argc, char *argv[])
exit_code = WEXITSTATUS(wstatus);
passes += runners[i].passes;
knownFails += runners[i].knownFails;
for (int j = 0; j < runners[i].knownFailsPassing; j++)
{
if (j < MAX_SUMMARY_TESTS_TO_LIST)
strcpy(knownFailingPassedTestNames[fails], runners[i].knownFailingPassedTestNames[j]);
knownFailsPassing++;
}
todos += runners[i].todos;
assumptionFails += runners[i].assumptionFails;
for (int j = 0; j < runners[i].fails; j++)
{
if (j < MAX_FAILED_TESTS_TO_LIST)
if (j < MAX_SUMMARY_TESTS_TO_LIST)
strcpy(failedTestNames[fails], runners[i].failedTestNames[j]);
fails++;
}
results += runners[i].results;
}
qsort(failedTestNames, min(fails, MAX_FAILED_TESTS_TO_LIST), sizeof(char) * MAX_TEST_LIST_BUFFER_LENGTH, compare_strings);
qsort(failedTestNames, min(fails, MAX_SUMMARY_TESTS_TO_LIST), sizeof(char) * MAX_TEST_LIST_BUFFER_LENGTH, compare_strings);
qsort(knownFailingPassedTestNames, min(fails, MAX_SUMMARY_TESTS_TO_LIST), sizeof(char) * MAX_TEST_LIST_BUFFER_LENGTH, compare_strings);
if (results == 0)
{
@ -559,28 +575,42 @@ int main(int argc, char *argv[])
}
else
{
fprintf(stdout, "\n");
if (fails > 0)
{
fprintf(stdout, "\n- Tests \e[31mFAILED\e[0m : %d Add TESTS='X' to run tests with the defined prefix.\n", fails);
fprintf(stdout, "- Tests \e[31mFAILED\e[0m : %d Add TESTS='X' to run tests with the defined prefix.\n", fails);
for (int i = 0; i < fails; i++)
{
if (i >= MAX_FAILED_TESTS_TO_LIST)
if (i >= MAX_SUMMARY_TESTS_TO_LIST)
{
fprintf(stdout, " - \e[31mand %d more...\e[0m\n", fails - MAX_FAILED_TESTS_TO_LIST);
fprintf(stdout, " - \e[31mand %d more...\e[0m\n", fails - MAX_SUMMARY_TESTS_TO_LIST);
break;
}
fprintf(stdout, " - \e[31m%s\e[0m.\n", failedTestNames[i]);
}
}
fprintf(stdout, "- Tests \e[32mPASSED\e[0m: %d\n", passes);
if (knownFailsPassing > 0)
{
fprintf(stdout, "- \e[31mKNOWN_FAILING_PASSED\e[0m: %d \e[31mPlease remove KNOWN_FAILING if these tests intentionally PASS\e[0m\n", knownFailsPassing);
for (int i = 0; i < knownFailsPassing; i++)
{
if (i >= MAX_SUMMARY_TESTS_TO_LIST)
{
fprintf(stdout, " - \e[31mand %d more...\e[0m\n", knownFailsPassing - MAX_SUMMARY_TESTS_TO_LIST);
break;
}
fprintf(stdout, " - \e[31m%s\e[0m.\n", knownFailingPassedTestNames[i]);
}
}
fprintf(stdout, "- Tests \e[32mPASSED\e[0m: %d\n", passes);
if (knownFails > 0)
fprintf(stdout, "- Tests \e[33mKNOWN_FAILING\e[0m: %d\n", knownFails);
fprintf(stdout, "- Tests \e[33mKNOWN_FAILING\e[0m: %d\n", knownFails);
if (todos > 0)
fprintf(stdout, "- Tests \e[33mTO_DO\e[0m: %d\n", todos);
fprintf(stdout, "- Tests \e[33mTO_DO\e[0m: %d\n", todos);
if (assumptionFails > 0)
fprintf(stdout, "- \e[33mASSUMPTIONS_FAILED\e[0m: %d\n", assumptionFails);
fprintf(stdout, "- \e[33mASSUMPTIONS_FAILED\e[0m: %d\n", assumptionFails);
fprintf(stdout, "- Tests \e[34mTOTAL\e[0m: %d\n", results);
fprintf(stdout, "- Tests \e[34mTOTAL\e[0m: %d\n", results);
}
fprintf(stdout, "\n");