Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions include/test/battle.h
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,7 @@
#include "constants/items.h"
#include "constants/moves.h"
#include "constants/species.h"
#include "constants/songs.h"
#include "test/test.h"

// NOTE: If the stack is too small the test runner will probably crash
Expand Down Expand Up @@ -556,6 +557,13 @@ enum
QUEUED_EXP_EVENT,
QUEUED_MESSAGE_EVENT,
QUEUED_STATUS_EVENT,
QUEUED_EFFECTIVENESS_EVENT,
};

struct QueuedEffectiveness
{
u8 battlerId;
u16 soundId;
};

struct QueuedAbilityEvent
Expand Down Expand Up @@ -614,6 +622,7 @@ struct QueuedEvent
struct QueuedExpEvent exp;
struct QueuedMessageEvent message;
struct QueuedStatusEvent status;
struct QueuedEffectiveness eff_se;
} as;
};

Expand Down Expand Up @@ -1049,6 +1058,7 @@ void SendOut(u32 sourceLine, struct BattlePokemon *, u32 partyIndex);
MESSAGE("Go for it, " name "!"); \
MESSAGE("Your opponent's weak! Get 'em, " name "!"); \
}
#define EFFECTIVENESS_SE(battler, ...) QueueEffectivenessSound(__LINE__, battler, (struct EffectivenessEventContext) { __VA_ARGS__ })

enum QueueGroupType
{
Expand All @@ -1057,6 +1067,11 @@ enum QueueGroupType
QUEUE_GROUP_NONE_OF,
};

struct EffectivenessEventContext
{
u16 soundId;
};

struct AbilityEventContext
{
u16 ability;
Expand Down Expand Up @@ -1112,6 +1127,7 @@ void QueueHP(u32 sourceLine, struct BattlePokemon *battler, struct HPEventContex
void QueueExp(u32 sourceLine, struct BattlePokemon *battler, struct ExpEventContext);
void QueueMessage(u32 sourceLine, const u8 *pattern);
void QueueStatus(u32 sourceLine, struct BattlePokemon *battler, struct StatusEventContext);
void QueueEffectivenessSound(u32 sourceLine, struct BattlePokemon *battler, struct EffectivenessEventContext);

/* Then */

Expand Down
5 changes: 2 additions & 3 deletions include/test_runner.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ void TestRunner_Battle_CheckBattleRecordActionType(u32 battlerId, u32 recordInde

u32 TestRunner_Battle_GetForcedAbility(u32 side, u32 partyIndex);
u32 TestRunner_Battle_GetChosenGimmick(u32 side, u32 partyIndex);
void TestRunner_Battle_RecordEffectivenessSound(u32 battlerId, u32 soundId);

#else

Expand All @@ -42,12 +43,10 @@ u32 TestRunner_Battle_GetChosenGimmick(u32 side, u32 partyIndex);
#define TestRunner_Battle_AISetScore(...) (void)0
#define TestRunner_Battle_AIAdjustScore(...) (void)0
#define TestRunner_Battle_InvalidNoHPMon(...) (void)0

#define TestRunner_Battle_CheckBattleRecordActionType(...) (void)0

#define TestRunner_Battle_GetForcedAbility(...) (u32)0

#define TestRunner_Battle_GetChosenGimmick(...) (u32)0
#define TestRunner_Battle_RecordEffectivenessSound(...) (u32)0

#endif

Expand Down
56 changes: 25 additions & 31 deletions src/battle_script_commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -2334,38 +2334,26 @@ static inline bool32 DoesBattlerNegateDamage(u32 battler)
return FALSE;
}

static u32 UpdateEffectivenessResultFlagsForDoubleSpreadMoves(u32 resultFlags)
static u32 UpdateEffectivenessResultFlagsForDoubleSpreadMoves(u32 resultFlags, u32 moveTarget)
{
// Only play the "best" sound
for (u32 sound = 0; sound < 3; sound++)
u32 battlerDef;
u32 ret = resultFlags;
Copy link
Collaborator

@AlexOn1ine AlexOn1ine Jul 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
u32 ret = resultFlags;
u32 resultFlagsForSoundEffectiveness = resultFlags;

Doesn't have to be this verbose but maybe something more expressive? It's not clear from ret what we are returning without reading the code fully.

for (battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
{
for (u32 battlerDef = 0; battlerDef < gBattlersCount; battlerDef++)
{
if ((gBattleStruct->moveResultFlags[battlerDef] & (MOVE_RESULT_MISSED | MOVE_RESULT_NO_EFFECT)
|| gBattleStruct->noResultString[battlerDef]))
continue;

switch (sound)
{
case 0:
if (gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_SUPER_EFFECTIVE
&& !DoesBattlerNegateDamage(battlerDef))
return gBattleStruct->moveResultFlags[battlerDef];
break;
case 1:
if (gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_NOT_VERY_EFFECTIVE
&& !DoesBattlerNegateDamage(battlerDef))
return gBattleStruct->moveResultFlags[battlerDef];
break;
case 2:
if (DoesBattlerNegateDamage(battlerDef))
return 0; //Normal effectiveness
return gBattleStruct->moveResultFlags[battlerDef];
}
}
if ((gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_NO_EFFECT // (MOVE_RESULT_MISSED | MOVE_RESULT_NO_EFFECT)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment probably not needed

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually is this correct? Do we want to include MOVE_RESULT_DOESNT_AFFECT_FOE here?

|| gBattleStruct->noResultString[battlerDef])
|| !IsBattlerAlive(battlerDef)
|| IsBattlerInvalidForSpreadMove(gBattlerAttacker, battlerDef, moveTarget))
continue;

if (DoesBattlerNegateDamage(battlerDef))
continue; // doesnt contribute to SE
if (!(gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_NOT_VERY_EFFECTIVE))
ret &= ~MOVE_RESULT_NOT_VERY_EFFECTIVE; // any battler with 1x or better effectiveness removes NVE sound
if (gBattleStruct->moveResultFlags[battlerDef] & MOVE_RESULT_SUPER_EFFECTIVE)
ret |= MOVE_RESULT_SUPER_EFFECTIVE; // any super effective result will play SE_SUPER_EFFECTIVE
}

return resultFlags;
return ret;
}

static inline bool32 TryStrongWindsWeakenAttack(u32 battlerDef, u32 moveType)
Expand Down Expand Up @@ -2474,7 +2462,7 @@ static void Cmd_attackanimation(void)
u32 moveResultFlags = gBattleStruct->moveResultFlags[gBattlerTarget];

if (IsDoubleSpreadMove())
moveResultFlags = UpdateEffectivenessResultFlagsForDoubleSpreadMoves(gBattleStruct->moveResultFlags[gBattlerTarget]);
moveResultFlags = UpdateEffectivenessResultFlagsForDoubleSpreadMoves(gBattleStruct->moveResultFlags[gBattlerTarget], moveTarget);

if ((gHitMarker & (HITMARKER_NO_ANIMATIONS | HITMARKER_DISABLE_ANIMATION))
&& gCurrentMove != MOVE_TRANSFORM
Expand Down Expand Up @@ -2798,7 +2786,8 @@ static void Cmd_effectivenesssound(void)
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
moveResultFlags = UpdateEffectivenessResultFlagsForDoubleSpreadMoves(gBattleStruct->moveResultFlags[gBattlerTarget]);
moveResultFlags = UpdateEffectivenessResultFlagsForDoubleSpreadMoves(gBattleStruct->moveResultFlags[gBattlerTarget],
GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove));
}
else if (!(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT) && DoesBattlerNegateDamage(gBattlerTarget))
moveResultFlags = 0;
Expand All @@ -2808,10 +2797,12 @@ static void Cmd_effectivenesssound(void)
switch (moveResultFlags & ~MOVE_RESULT_MISSED)
{
case MOVE_RESULT_SUPER_EFFECTIVE:
TestRunner_Battle_RecordEffectivenessSound(gBattlerTarget, SE_SUPER_EFFECTIVE);
BtlController_EmitPlaySE(gBattlerTarget, B_COMM_TO_CONTROLLER, SE_SUPER_EFFECTIVE);
MarkBattlerForControllerExec(gBattlerTarget);
break;
case MOVE_RESULT_NOT_VERY_EFFECTIVE:
TestRunner_Battle_RecordEffectivenessSound(gBattlerTarget, SE_NOT_EFFECTIVE);
BtlController_EmitPlaySE(gBattlerTarget, B_COMM_TO_CONTROLLER, SE_NOT_EFFECTIVE);
MarkBattlerForControllerExec(gBattlerTarget);
break;
Expand All @@ -2826,16 +2817,19 @@ static void Cmd_effectivenesssound(void)
default:
if (moveResultFlags & MOVE_RESULT_SUPER_EFFECTIVE)
{
TestRunner_Battle_RecordEffectivenessSound(gBattlerTarget, SE_SUPER_EFFECTIVE);
BtlController_EmitPlaySE(gBattlerTarget, B_COMM_TO_CONTROLLER, SE_SUPER_EFFECTIVE);
MarkBattlerForControllerExec(gBattlerTarget);
}
else if (moveResultFlags & MOVE_RESULT_NOT_VERY_EFFECTIVE)
{
TestRunner_Battle_RecordEffectivenessSound(gBattlerTarget, SE_NOT_EFFECTIVE);
BtlController_EmitPlaySE(gBattlerTarget, B_COMM_TO_CONTROLLER, SE_NOT_EFFECTIVE);
MarkBattlerForControllerExec(gBattlerTarget);
}
else if (!(moveResultFlags & (MOVE_RESULT_DOESNT_AFFECT_FOE | MOVE_RESULT_FAILED)))
{
TestRunner_Battle_RecordEffectivenessSound(gBattlerTarget, SE_EFFECTIVE);
BtlController_EmitPlaySE(gBattlerTarget, B_COMM_TO_CONTROLLER, SE_EFFECTIVE);
MarkBattlerForControllerExec(gBattlerTarget);
}
Expand Down
7 changes: 7 additions & 0 deletions test/battle/spread_moves.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,12 @@ DOUBLE_BATTLE_TEST("Spread Moves: A spread move attack will activate both resist
MESSAGE("The Chilan Berry weakened the damage to the opposing Sandslash!");

ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, playerLeft);
EFFECTIVENESS_SE(opponentLeft, SE_EFFECTIVE); // effective against both
HP_BAR(opponentLeft, captureDamage: &opponentLeftDmg[0]);
HP_BAR(opponentRight, captureDamage: &opponentRightDmg[0]);

ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, playerLeft);
EFFECTIVENESS_SE(opponentLeft, SE_EFFECTIVE); // effective against both
HP_BAR(opponentLeft, captureDamage: &opponentLeftDmg[1]);
HP_BAR(opponentRight, captureDamage: &opponentRightDmg[1]);
} THEN {
Expand Down Expand Up @@ -129,10 +131,12 @@ DOUBLE_BATTLE_TEST("Spread Moves: A spread move attack will be weakened by stron
TURN { SWITCH(playerRight, 2); MOVE(opponentRight, MOVE_CELEBRATE); MOVE(playerLeft, MOVE_ROCK_SLIDE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_ROCK_SLIDE, playerLeft);
EFFECTIVENESS_SE(opponentLeft, SE_SUPER_EFFECTIVE); // SE on rayquaza
HP_BAR(opponentLeft, captureDamage: &opponentLeftDmg[0]);
HP_BAR(opponentRight, captureDamage: &opponentRightDmg[0]);

ANIMATION(ANIM_TYPE_MOVE, MOVE_ROCK_SLIDE, playerLeft);
EFFECTIVENESS_SE(opponentLeft, SE_EFFECTIVE);
HP_BAR(opponentLeft, captureDamage: &opponentLeftDmg[1]);
HP_BAR(opponentRight, captureDamage: &opponentRightDmg[1]);
} THEN {
Expand Down Expand Up @@ -336,6 +340,7 @@ DOUBLE_BATTLE_TEST("Spread Moves: Super Effective Message on both opposing mons"
TURN { MOVE(playerLeft, MOVE_PRECIPICE_BLADES); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_PRECIPICE_BLADES, playerLeft);
EFFECTIVENESS_SE(opponentLeft, SE_SUPER_EFFECTIVE);
HP_BAR(opponentLeft);
HP_BAR(opponentRight);
MESSAGE("It's super effective on the opposing Golem and Onix!");
Expand Down Expand Up @@ -424,10 +429,12 @@ DOUBLE_BATTLE_TEST("Spread Moves: Unless move hits every target user will not in
TURN { MOVE(opponentRight, MOVE_ICY_WIND); MOVE(playerLeft, MOVE_ROCK_SLIDE); SEND_OUT(playerRight, 2); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_ICY_WIND, opponentRight);
EFFECTIVENESS_SE(playerRight, SE_SUPER_EFFECTIVE); // SE against sandslash
HP_BAR(playerLeft);
HP_BAR(playerRight);

ANIMATION(ANIM_TYPE_MOVE, MOVE_ROCK_SLIDE, playerLeft);
EFFECTIVENESS_SE(opponentLeft, SE_SUPER_EFFECTIVE); // se against torkoal
HP_BAR(opponentLeft);
HP_BAR(opponentRight);
MESSAGE("It's super effective on the opposing Torkoal and Torkoal!");
Expand Down
82 changes: 82 additions & 0 deletions test/test_runner_battle.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#undef TestRunner_Battle_AfterLastTurn
#undef TestRunner_Battle_CheckBattleRecordActionType
#undef TestRunner_Battle_GetForcedAbility
#undef TestRunner_Battle_RecordEffectivenessSound
#endif

#define INVALID(fmt, ...) Test_ExitWithResult(TEST_RESULT_INVALID, sourceLine, ":L%s:%d: " fmt, gTestRunnerState.test->filename, sourceLine, ##__VA_ARGS__)
Expand Down Expand Up @@ -1307,6 +1308,7 @@ static const char *const sEventTypeMacros[] =
[QUEUED_EXP_EVENT] = "EXPERIENCE_BAR",
[QUEUED_MESSAGE_EVENT] = "MESSAGE",
[QUEUED_STATUS_EVENT] = "STATUS_ICON",
[QUEUED_EFFECTIVENESS_EVENT] = "EFFECTIVENESS_SE",
};

void TestRunner_Battle_AfterLastTurn(void)
Expand Down Expand Up @@ -2802,3 +2804,83 @@ void TestRunner_Battle_AIAdjustScore(const char *file, u32 line, u32 battlerId,
{
TestRunner_Battle_AILogScore(file, line, battlerId, moveIndex, score, FALSE);
}

void QueueEffectivenessSound(u32 sourceLine, struct BattlePokemon *battler, struct EffectivenessEventContext ctx)
{
s32 battlerId = battler - gBattleMons;
INVALID_IF(!STATE->runScene, "EFFECTIVENESS_SE outside of SCENE");
if (DATA.queuedEventsCount == MAX_QUEUED_EVENTS)
Test_ExitWithResult(TEST_RESULT_ERROR, sourceLine, ":L%s:%d: EFFECTIVENESS_SE exceeds MAX_QUEUED_EVENTS", gTestRunnerState.test->filename, sourceLine);
DATA.queuedEvents[DATA.queuedEventsCount++] = (struct QueuedEvent) {
.type = QUEUED_EFFECTIVENESS_EVENT,
.sourceLineOffset = SourceLineOffset(sourceLine),
.groupType = QUEUE_GROUP_NONE,
.groupSize = 1,
.as = { .eff_se = {
.battlerId = battlerId,
.soundId = ctx.soundId,
}},
};
}

static s32 TryEffectivenessSound(s32 i, s32 n, u32 battlerId, u32 soundId)
{
struct QueuedEffectiveness *event;
s32 iMax = i + n;
for (; i < iMax; i++)
{
if (DATA.queuedEvents[i].type != QUEUED_EFFECTIVENESS_EVENT) {
continue;
}

event = &DATA.queuedEvents[i].as.eff_se;
// Test_MgbaPrintf("Looking for: battler %d sound %d. Found battler %d sound %d", event->battlerId, event->soundId, battlerId, soundId);
if (event->battlerId == battlerId && event->soundId == soundId)
return i;
}
return -1;
}

void TestRunner_Battle_RecordEffectivenessSound(u32 battlerId, u32 soundId)
{
s32 queuedEvent;
s32 match;
struct QueuedEvent *event;

if (DATA.trial.queuedEvent == DATA.queuedEventsCount)
return;

event = &DATA.queuedEvents[DATA.trial.queuedEvent];
switch (event->groupType)
{
case QUEUE_GROUP_NONE:
case QUEUE_GROUP_ONE_OF:
if (TryEffectivenessSound(DATA.trial.queuedEvent, event->groupSize, battlerId, soundId) != -1)
DATA.trial.queuedEvent += event->groupSize;
break;
case QUEUE_GROUP_NONE_OF:
queuedEvent = DATA.trial.queuedEvent;
do
{
if ((match = TryEffectivenessSound(queuedEvent, event->groupSize, battlerId, soundId)) != -1)
{
const char *filename = gTestRunnerState.test->filename;
u32 line = SourceLine(DATA.queuedEvents[match].sourceLineOffset);
Test_ExitWithResult(TEST_RESULT_FAIL, line, ":L%s:%d: Matched EFFECTIVENESS_SE", filename, line);
}

queuedEvent += event->groupSize;
if (queuedEvent == DATA.queuedEventsCount)
break;

event = &DATA.queuedEvents[queuedEvent];
if (event->groupType == QUEUE_GROUP_NONE_OF)
continue;

if (TryEffectivenessSound(queuedEvent, event->groupSize, battlerId, soundId) != -1)
DATA.trial.queuedEvent = queuedEvent + event->groupSize;
} while (FALSE);
break;
}
}

Loading