Merge branch 'master' into _RHH/pr/master/unused
This commit is contained in:
commit
5af733a391
@ -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
|
||||
|
||||
@ -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
|
||||
};
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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");
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user