Add SUB_HIT check to tests (#8413)
Co-authored-by: Hedara <hedara90@gmail.com>
This commit is contained in:
parent
3feeebce9b
commit
ec1a283b1b
@ -464,6 +464,20 @@ If the expected status icon is parametrized the corresponding `STATUS1` constant
|
||||
STATUS_ICON(player, status1);
|
||||
```
|
||||
|
||||
### `SUB_HIT`
|
||||
`SUB_HIT(battler, captureDamage: | subBreak:)`
|
||||
Causes the test to fail the test to fail if a Substitute for the specified battler doesn't take damage.
|
||||
If `captureDamage` is used, the damage the substitute takes is written to the supplied pointer.
|
||||
```
|
||||
u16 damage;
|
||||
...
|
||||
SUB_HIT(player, captureDamage: &damage);
|
||||
```
|
||||
If `subBreak` is set to `TRUE`, the test will fail unless the substitute breaks. And if set to `FALSE`, the test will fail unless the substitute survives.
|
||||
```
|
||||
SUB_HIT(player, subBreak: TRUE);
|
||||
```
|
||||
|
||||
### `NOT`
|
||||
`NOT sceneCommand`
|
||||
Causes the test to fail if the `SCENE` command succeeds before the following command succeeds.
|
||||
|
||||
@ -606,6 +606,7 @@ enum
|
||||
QUEUED_ABILITY_POPUP_EVENT,
|
||||
QUEUED_ANIMATION_EVENT,
|
||||
QUEUED_HP_EVENT,
|
||||
QUEUED_SUB_HIT_EVENT,
|
||||
QUEUED_EXP_EVENT,
|
||||
QUEUED_MESSAGE_EVENT,
|
||||
QUEUED_STATUS_EVENT,
|
||||
@ -635,6 +636,14 @@ struct QueuedHPEvent
|
||||
u32 address:28;
|
||||
};
|
||||
|
||||
struct QueuedSubHitEvent
|
||||
{
|
||||
u32 battlerId:3;
|
||||
u32 checkBreak:1;
|
||||
u32 breakSub:1;
|
||||
u32 address:27;
|
||||
};
|
||||
|
||||
struct QueuedExpEvent
|
||||
{
|
||||
u32 battlerId:3;
|
||||
@ -664,6 +673,7 @@ struct QueuedEvent
|
||||
struct QueuedAbilityEvent ability;
|
||||
struct QueuedAnimationEvent animation;
|
||||
struct QueuedHPEvent hp;
|
||||
struct QueuedSubHitEvent subHit;
|
||||
struct QueuedExpEvent exp;
|
||||
struct QueuedMessageEvent message;
|
||||
struct QueuedStatusEvent status;
|
||||
@ -1173,6 +1183,7 @@ void SendOut(u32 sourceLine, struct BattlePokemon *, u32 partyIndex);
|
||||
#define ABILITY_POPUP(battler, ...) QueueAbility(__LINE__, battler, (struct AbilityEventContext) { __VA_ARGS__ })
|
||||
#define ANIMATION(type, id, ...) QueueAnimation(__LINE__, type, id, (struct AnimationEventContext) { __VA_ARGS__ })
|
||||
#define HP_BAR(battler, ...) QueueHP(__LINE__, battler, (struct HPEventContext) { R_APPEND_TRUE(__VA_ARGS__) })
|
||||
#define SUB_HIT(battler, ...) QueueSubHit(__LINE__, battler, (struct SubHitEventContext) { R_APPEND_TRUE(__VA_ARGS__) })
|
||||
#define EXPERIENCE_BAR(battler, ...) QueueExp(__LINE__, battler, (struct ExpEventContext) { R_APPEND_TRUE(__VA_ARGS__) })
|
||||
// Static const is needed to make the modern compiler put the pattern variable in the .rodata section, instead of putting it on stack(which can break the game).
|
||||
#define MESSAGE(pattern) do {static const u8 msg[] = _(pattern); QueueMessage(__LINE__, msg);} while (0)
|
||||
@ -1225,6 +1236,15 @@ struct HPEventContext
|
||||
bool8 explicitCaptureDamage;
|
||||
};
|
||||
|
||||
struct SubHitEventContext
|
||||
{
|
||||
u8 _;
|
||||
bool8 subBreak;
|
||||
bool8 explicitSubBreak;
|
||||
u16 *captureDamage;
|
||||
bool8 explicitCaptureDamage;
|
||||
};
|
||||
|
||||
struct ExpEventContext
|
||||
{
|
||||
u8 _;
|
||||
@ -1253,6 +1273,7 @@ void CloseQueueGroup(u32 sourceLine);
|
||||
void QueueAbility(u32 sourceLine, struct BattlePokemon *battler, struct AbilityEventContext);
|
||||
void QueueAnimation(u32 sourceLine, u32 type, u32 id, struct AnimationEventContext);
|
||||
void QueueHP(u32 sourceLine, struct BattlePokemon *battler, struct HPEventContext);
|
||||
void QueueSubHit(u32 sourceLine, struct BattlePokemon *battler, struct SubHitEventContext);
|
||||
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);
|
||||
|
||||
@ -16,6 +16,7 @@ enum Gimmick;
|
||||
void TestRunner_Battle_RecordAbilityPopUp(u32 battlerId, enum Ability ability);
|
||||
void TestRunner_Battle_RecordAnimation(u32 animType, u32 animId);
|
||||
void TestRunner_Battle_RecordHP(u32 battlerId, u32 oldHP, u32 newHP);
|
||||
void TestRunner_Battle_RecordSubHit(u32 battlerId, u32 damage, bool32 broke);
|
||||
void TestRunner_Battle_RecordExp(u32 battlerId, u32 oldExp, u32 newExp);
|
||||
void TestRunner_Battle_RecordMessage(const u8 *message);
|
||||
void TestRunner_Battle_RecordStatus1(u32 battlerId, u32 status1);
|
||||
@ -39,6 +40,7 @@ u32 TestRunner_Battle_GetForcedEnvironment(void);
|
||||
#define TestRunner_Battle_RecordAbilityPopUp(...) (void)0
|
||||
#define TestRunner_Battle_RecordAnimation(...) (void)0
|
||||
#define TestRunner_Battle_RecordHP(...) (void)0
|
||||
#define TestRunner_Battle_RecordSubHit(...) (void)0
|
||||
#define TestRunner_Battle_RecordExp(...) (void)0
|
||||
#define TestRunner_Battle_RecordMessage(...) (void)0
|
||||
#define TestRunner_Battle_RecordStatus1(...) (void)0
|
||||
|
||||
@ -2365,10 +2365,12 @@ static void MoveDamageDataHpUpdate(u32 battler, u32 scriptBattler, const u8 *nex
|
||||
{
|
||||
if (gDisableStructs[battler].substituteHP >= gBattleStruct->moveDamage[battler])
|
||||
{
|
||||
TestRunner_Battle_RecordSubHit(battler, gBattleStruct->moveDamage[battler], FALSE);
|
||||
gDisableStructs[battler].substituteHP -= gBattleStruct->moveDamage[battler];
|
||||
}
|
||||
else
|
||||
{
|
||||
TestRunner_Battle_RecordSubHit(battler, gDisableStructs[battler].substituteHP, TRUE);
|
||||
gBattleStruct->moveDamage[battler] = gDisableStructs[battler].substituteHP;
|
||||
gDisableStructs[battler].substituteHP = 0;
|
||||
}
|
||||
|
||||
@ -69,4 +69,111 @@ SINGLE_BATTLE_TEST("Substitute's HP cost doesn't trigger effects that trigger on
|
||||
}
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Substitute hits are detected by SUB_HIT")
|
||||
{
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_SCRATCH); }
|
||||
} SCENE {
|
||||
SUB_HIT(player);
|
||||
}
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Substitute hits are detected by SUB_HIT, break TRUE")
|
||||
{
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WOBBUFFET) { Level(1); }
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_SCRATCH); }
|
||||
} SCENE {
|
||||
SUB_HIT(player, subBreak: TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Substitute hits are detected by SUB_HIT, break FALSE")
|
||||
{
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WOBBUFFET) { Level(100); }
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_SCRATCH); }
|
||||
} SCENE {
|
||||
SUB_HIT(player, subBreak: FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Substitute hits are detected by SUB_HIT, records damage")
|
||||
{
|
||||
u16 damage;
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_SCRATCH); }
|
||||
} SCENE {
|
||||
SUB_HIT(player, captureDamage: &damage);
|
||||
} THEN {
|
||||
EXPECT_GT(damage, 0);
|
||||
}
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Substitute hits are detected by SUB_HIT, records damage, break FALSE")
|
||||
{
|
||||
u16 damage;
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WOBBUFFET);
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_SCRATCH); }
|
||||
} SCENE {
|
||||
SUB_HIT(player, captureDamage: &damage, subBreak: FALSE);
|
||||
} THEN {
|
||||
EXPECT_GT(damage, 0);
|
||||
}
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Substitute hits are detected by SUB_HIT, records damage, break TRUE")
|
||||
{
|
||||
u16 damage;
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WOBBUFFET) { Level(1); }
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_SCRATCH); }
|
||||
} SCENE {
|
||||
SUB_HIT(player, captureDamage: &damage, subBreak: TRUE);
|
||||
} THEN {
|
||||
EXPECT_GT(damage, 0);
|
||||
}
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Substitute hits are detected by SUB_HIT, break TRUE, failing")
|
||||
{
|
||||
KNOWN_FAILING; // For testing purposes
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WOBBUFFET) { Level(100); }
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_SCRATCH); }
|
||||
} SCENE {
|
||||
SUB_HIT(player, subBreak: TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
SINGLE_BATTLE_TEST("Substitute hits are detected by SUB_HIT, break FALSE, failing")
|
||||
{
|
||||
KNOWN_FAILING; // For testing purposes
|
||||
GIVEN {
|
||||
PLAYER(SPECIES_WOBBUFFET) { Level(1); }
|
||||
OPPONENT(SPECIES_WOBBUFFET);
|
||||
} WHEN {
|
||||
TURN { MOVE(player, MOVE_SUBSTITUTE); MOVE(opponent, MOVE_SCRATCH); }
|
||||
} SCENE {
|
||||
SUB_HIT(player, subBreak: FALSE);
|
||||
}
|
||||
}
|
||||
|
||||
TO_DO_BATTLE_TEST("Baton Pass passes Substitutes");
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
#undef TestRunner_Battle_RecordAbilityPopUp
|
||||
#undef TestRunner_Battle_RecordAnimation
|
||||
#undef TestRunner_Battle_RecordHP
|
||||
#undef TestRunner_Battle_RecordSubHit
|
||||
#undef TestRunner_Battle_RecordMessage
|
||||
#undef TestRunner_Battle_RecordStatus1
|
||||
#undef TestRunner_Battle_AfterLastTurn
|
||||
@ -946,6 +947,85 @@ void TestRunner_Battle_RecordHP(u32 battlerId, u32 oldHP, u32 newHP)
|
||||
}
|
||||
}
|
||||
|
||||
static s32 TrySubHit(s32 i, s32 n, u32 battlerId, u32 damage, bool32 broke)
|
||||
{
|
||||
struct QueuedSubHitEvent *event;
|
||||
s32 iMax = i + n;
|
||||
for (; i < iMax; i++)
|
||||
{
|
||||
if (DATA.queuedEvents[i].type != QUEUED_SUB_HIT_EVENT)
|
||||
continue;
|
||||
|
||||
event = &DATA.queuedEvents[i].as.subHit;
|
||||
|
||||
if (event->battlerId == battlerId)
|
||||
{
|
||||
if (event->checkBreak)
|
||||
{
|
||||
if (event->breakSub && !broke)
|
||||
return -1;
|
||||
else if (!event->breakSub && broke)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (event->address <= 0xFFFF)
|
||||
{
|
||||
event->address = damage;
|
||||
return i;
|
||||
}
|
||||
else
|
||||
{
|
||||
*(u16 *)(u32)(event->address) = damage;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void TestRunner_Battle_RecordSubHit(u32 battlerId, u32 damage, bool32 broke)
|
||||
{
|
||||
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 (TrySubHit(DATA.trial.queuedEvent, event->groupSize, battlerId, damage, broke) != -1)
|
||||
DATA.trial.queuedEvent += event->groupSize;
|
||||
break;
|
||||
case QUEUE_GROUP_NONE_OF:
|
||||
queuedEvent = DATA.trial.queuedEvent;
|
||||
do
|
||||
{
|
||||
if ((match = TrySubHit(queuedEvent, event->groupSize, battlerId, damage, broke)) != -1)
|
||||
{
|
||||
const char *filename = gTestRunnerState.test->filename;
|
||||
u32 line = SourceLine(DATA.queuedEvents[match].sourceLineOffset);
|
||||
Test_ExitWithResult(TEST_RESULT_FAIL, line, ":L%s:%d: Matched SUB_HIT", filename, line);
|
||||
}
|
||||
|
||||
queuedEvent += event->groupSize;
|
||||
if (queuedEvent == DATA.queuedEventsCount)
|
||||
break;
|
||||
|
||||
event = &DATA.queuedEvents[queuedEvent];
|
||||
if (event->groupType == QUEUE_GROUP_NONE_OF)
|
||||
continue;
|
||||
|
||||
if (TrySubHit(queuedEvent, event->groupSize, battlerId, damage, broke) != -1)
|
||||
DATA.trial.queuedEvent = queuedEvent + event->groupSize;
|
||||
} while (FALSE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const char *const sBattleActionNames[] =
|
||||
{
|
||||
[B_ACTION_USE_MOVE] = "MOVE",
|
||||
@ -1505,6 +1585,7 @@ static const char *const sEventTypeMacros[] =
|
||||
[QUEUED_ABILITY_POPUP_EVENT] = "ABILITY_POPUP",
|
||||
[QUEUED_ANIMATION_EVENT] = "ANIMATION",
|
||||
[QUEUED_HP_EVENT] = "HP_BAR",
|
||||
[QUEUED_SUB_HIT_EVENT] = "SUB_HIT",
|
||||
[QUEUED_EXP_EVENT] = "EXPERIENCE_BAR",
|
||||
[QUEUED_MESSAGE_EVENT] = "MESSAGE",
|
||||
[QUEUED_STATUS_EVENT] = "STATUS_ICON",
|
||||
@ -2995,6 +3076,46 @@ void QueueHP(u32 sourceLine, struct BattlePokemon *battler, struct HPEventContex
|
||||
};
|
||||
}
|
||||
|
||||
void QueueSubHit(u32 sourceLine, struct BattlePokemon *battler, struct SubHitEventContext ctx)
|
||||
{
|
||||
s32 battlerId = battler - gBattleMons;
|
||||
bool32 breakSub = FALSE;
|
||||
bool32 checkBreak = FALSE;
|
||||
uintptr_t address;
|
||||
|
||||
INVALID_IF(!STATE->runScene, "SUB_HIT outside of SCENE");
|
||||
if (DATA.queuedEventsCount == MAX_QUEUED_EVENTS)
|
||||
Test_ExitWithResult(TEST_RESULT_ERROR, sourceLine, ":L%s:%d: SUB_HIT exceeds MAX_QUEUED_EVENTS", gTestRunnerState.test->filename, sourceLine);
|
||||
|
||||
address = 0;
|
||||
if (ctx.explicitCaptureDamage)
|
||||
{
|
||||
INVALID_IF(ctx.captureDamage == NULL, "captureDamage is NULL");
|
||||
*ctx.captureDamage = 0;
|
||||
address = (uintptr_t)ctx.captureDamage;
|
||||
}
|
||||
|
||||
if (ctx.explicitSubBreak)
|
||||
{
|
||||
checkBreak = TRUE;
|
||||
if (ctx.subBreak)
|
||||
breakSub = TRUE;
|
||||
}
|
||||
|
||||
DATA.queuedEvents[DATA.queuedEventsCount++] = (struct QueuedEvent) {
|
||||
.type = QUEUED_SUB_HIT_EVENT,
|
||||
.sourceLineOffset = SourceLineOffset(sourceLine),
|
||||
.groupType = QUEUE_GROUP_NONE,
|
||||
.groupSize = 1,
|
||||
.as = { .subHit = {
|
||||
.battlerId = battlerId,
|
||||
.checkBreak = checkBreak,
|
||||
.breakSub = breakSub,
|
||||
.address = address,
|
||||
}},
|
||||
};
|
||||
}
|
||||
|
||||
void QueueExp(u32 sourceLine, struct BattlePokemon *battler, struct ExpEventContext ctx)
|
||||
{
|
||||
s32 battlerId = battler - gBattleMons;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user