Skip to content

Commit

Permalink
Merge branch '_RHH/master' into _RHH/upcoming
Browse files Browse the repository at this point in the history
# Conflicts:
#	ld_script_modern.ld
#	src/battle_ai_switch_items.c
  • Loading branch information
AsparagusEduardo committed Feb 1, 2024
2 parents 71b49a1 + 1a65894 commit 09d12fb
Show file tree
Hide file tree
Showing 11 changed files with 139 additions and 58 deletions.
10 changes: 5 additions & 5 deletions graphics/pokedex/hgss/palette_search_results_dark.pal
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion include/battle.h
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,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
};

Expand Down
5 changes: 5 additions & 0 deletions ld_script_modern.ld
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ SECTIONS {
ALIGN(4)
{
__ewram_start = .;
/*
We link malloc.o here to prevent `gHeap` from landing in the middle of EWRAM.
Otherwise this causes corruption issues on some ld versions
*/
gflib/malloc.o(ewram_data);
*(.ewram*)
__ewram_end = .;
} > EWRAM
Expand Down
23 changes: 17 additions & 6 deletions src/battle_ai_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -455,11 +456,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;
Expand Down Expand Up @@ -496,7 +507,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
Expand All @@ -507,7 +518,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;
}

Expand All @@ -519,7 +530,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;
}
Expand Down
16 changes: 8 additions & 8 deletions src/battle_ai_switch_items.c
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ static bool32 HasBadOdds(u32 battler, bool32 emitResult)
}

// 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
Expand Down Expand Up @@ -603,12 +603,12 @@ static bool32 ShouldSwitchIfAbilityBenefit(u32 battler, bool32 emitResult)
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;

Expand All @@ -620,7 +620,7 @@ static bool32 ShouldSwitchIfAbilityBenefit(u32 battler, bool32 emitResult)
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;

Expand Down Expand Up @@ -856,7 +856,7 @@ static bool32 ShouldSwitchIfEncored(u32 battler, bool32 emitResult)
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
Expand Down Expand Up @@ -890,7 +890,7 @@ static bool32 AreAttackingStatsLowered(u32 battler, bool32 emitResult)
// 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);
Expand All @@ -915,7 +915,7 @@ static bool32 AreAttackingStatsLowered(u32 battler, bool32 emitResult)
// 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);
Expand Down Expand Up @@ -1064,7 +1064,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))
Expand Down
2 changes: 1 addition & 1 deletion src/battle_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -4075,7 +4075,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
Expand Down
2 changes: 1 addition & 1 deletion src/battle_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -6550,7 +6550,7 @@ static u8 ItemHealHp(u32 battler, u32 itemId, bool32 end2, bool32 percentHeal)
gBattlescriptCurrInstr = BattleScript_ItemHealHP_RemoveItemRet;
}
if (gBattleResources->flags->flags[battler] & RESOURCE_FLAG_EMERGENCY_EXIT
&& GetNonDynamaxMaxHP(battler) > gBattleMons[battler].maxHP / 2)
&& GetNonDynamaxHP(battler) >= GetNonDynamaxMaxHP(battler) / 2)
gBattleResources->flags->flags[battler] &= ~RESOURCE_FLAG_EMERGENCY_EXIT;

return ITEM_HP_CHANGE;
Expand Down
27 changes: 19 additions & 8 deletions test/battle/ability/emergency_exit.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,42 @@ SINGLE_BATTLE_TEST("Emergency Exit switches out when taking 50% max-hp damage")
OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); };
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN {
MOVE(player, MOVE_SUPER_FANG);
SEND_OUT(opponent, 1);
}
TURN { MOVE(player, MOVE_SUPER_FANG); SEND_OUT(opponent, 1); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player);
HP_BAR(opponent);
ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT);
}
}

SINGLE_BATTLE_TEST("Emergency Exit switches out when taking 50% max-hp damage after a restore hp hold effect was used")
SINGLE_BATTLE_TEST("Emergency Exit does not switch out when going below 50% max-HP but healed via held item back above the threshold")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET)
OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(262); Item(ITEM_SITRUS_BERRY); };
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN {
MOVE(player, MOVE_SUPER_FANG);
}
TURN { MOVE(player, MOVE_SUPER_FANG); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player);
HP_BAR(opponent);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent);
NOT ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT);
}
}

SINGLE_BATTLE_TEST("Emergency Exit switches out when going below 50% max-HP but healing via held item is not enough to go back above the threshold")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET)
OPPONENT(SPECIES_GOLISOPOD) { Ability(ABILITY_EMERGENCY_EXIT); MaxHP(263); HP(133); Item(ITEM_ORAN_BERRY); };
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_SUPER_FANG); SEND_OUT(opponent, 1); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SUPER_FANG, player);
HP_BAR(opponent);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent);
ABILITY_POPUP(opponent, ABILITY_EMERGENCY_EXIT);
}
}
28 changes: 28 additions & 0 deletions test/battle/ai.c
Original file line number Diff line number Diff line change
Expand Up @@ -702,3 +702,31 @@ AI_SINGLE_BATTLE_TEST("First Impression is not chosen if it's blocked by certain
TURN { EXPECT_MOVE(opponent, MOVE_LUNGE); }
}
}

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!");
}
}
}
24 changes: 10 additions & 14 deletions test/test_runner.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -293,12 +292,6 @@ void CB2_TestRunner(void)
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:
Expand All @@ -313,7 +306,10 @@ void CB2_TestRunner(void)
}
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";
Expand Down Expand Up @@ -341,7 +337,12 @@ void CB2_TestRunner(void)
}

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)
Expand Down Expand Up @@ -513,11 +514,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;
Expand Down
Loading

0 comments on commit 09d12fb

Please sign in to comment.