From fafb0bba3bdc22994405a40c944666e7830189bd Mon Sep 17 00:00:00 2001 From: ghoulslash Date: Thu, 10 Jul 2025 16:51:38 -0400 Subject: [PATCH 1/2] cotton down use jumpifabsent --- data/battle_scripts_1.s | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 60432842f96a..a4d3264d1300 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -6913,7 +6913,7 @@ BattleScript_CottonDownActivates:: swapattackerwithtarget setbyte gBattlerTarget, 0 BattleScript_CottonDownLoop: - jumpiffainted BS_TARGET, TRUE, BattleScript_CottonDownLoopIncrement + jumpifabsent BS_TARGET, BattleScript_CottonDownLoopIncrement setstatchanger STAT_SPEED, 1, TRUE jumpifbyteequal gBattlerTarget, gEffectBattler, BattleScript_CottonDownLoopIncrement statbuffchange STAT_CHANGE_NOT_PROTECT_AFFECTED | STAT_CHANGE_ALLOW_PTR, BattleScript_CottonDownLoopIncrement From e39956fbbcf0c60576b9be8ec3d0eec5e1b4931d Mon Sep 17 00:00:00 2001 From: ghoulslash Date: Fri, 18 Jul 2025 09:49:43 -0400 Subject: [PATCH 2/2] add EFFECTIVENESS_SE test macro for checking spread move effectiveness sound effects (surrogate for result) --- include/test/battle.h | 16 +++++++ include/test_runner.h | 5 +-- src/battle_script_commands.c | 56 +++++++++++------------- test/battle/spread_moves.c | 7 +++ test/test_runner_battle.c | 82 ++++++++++++++++++++++++++++++++++++ 5 files changed, 132 insertions(+), 34 deletions(-) diff --git a/include/test/battle.h b/include/test/battle.h index c323207b30b4..703a646708a7 100644 --- a/include/test/battle.h +++ b/include/test/battle.h @@ -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 @@ -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 @@ -614,6 +622,7 @@ struct QueuedEvent struct QueuedExpEvent exp; struct QueuedMessageEvent message; struct QueuedStatusEvent status; + struct QueuedEffectiveness eff_se; } as; }; @@ -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 { @@ -1057,6 +1067,11 @@ enum QueueGroupType QUEUE_GROUP_NONE_OF, }; +struct EffectivenessEventContext +{ + u16 soundId; +}; + struct AbilityEventContext { u16 ability; @@ -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 */ diff --git a/include/test_runner.h b/include/test_runner.h index 9e0d96ff5bb7..fbe13f94b206 100644 --- a/include/test_runner.h +++ b/include/test_runner.h @@ -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 @@ -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 diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 664fa22f25ee..7a4cfc9a5937 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -2336,38 +2336,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; + 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) + || 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) @@ -2476,7 +2464,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 @@ -2792,7 +2780,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; @@ -2802,10 +2791,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; @@ -2820,16 +2811,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); } diff --git a/test/battle/spread_moves.c b/test/battle/spread_moves.c index a37fb847dbb2..5cdafcd7532f 100644 --- a/test/battle/spread_moves.c +++ b/test/battle/spread_moves.c @@ -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 { @@ -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 { @@ -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!"); @@ -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!"); diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index 20271a588d2d..eb573b3963ef 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -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__) @@ -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) @@ -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; + } +} +