Merge branch '_RHH/upcoming' into _RHH/pr/upcoming/lighting-expansion-v2

This commit is contained in:
Eduardo Quezada 2025-02-20 19:46:02 -03:00
commit 8c57172f2a
23 changed files with 623 additions and 192 deletions

View File

@ -640,7 +640,7 @@ BattleScript_AffectionBasedStatus_HealFreezeString:
printstring STRINGID_ATTACKERMELTEDTHEICE
goto BattleScript_AffectionBasedStatusHeal_Continue
BattleScript_AffectionBasedStatus_HealFrostbiteString:
printstring STRINGID_ATTACKERMELTEDTHEICE
printstring STRINGID_ATTACKERHEALEDITSFROSTBITE
BattleScript_AffectionBasedStatusHeal_Continue:
waitmessage B_WAIT_TIME_LONG
clearstatus BS_ATTACKER
@ -1563,6 +1563,7 @@ BattleScript_RototillerMoveTargetEnd:
moveendto MOVEEND_NEXT_TARGET
addbyte gBattlerTarget, 1
jumpifbytenotequal gBattlerTarget, gBattlersCount, BattleScript_RototillerLoop
restoretarget
end
BattleScript_RototillerCantRaiseMultipleStats:
@ -1743,8 +1744,6 @@ BattleScript_EffectCopycat::
trycopycat BattleScript_CopycatFail
attackanimation
waitanimation
setbyte sB_ANIM_TURN, 0
setbyte sB_ANIM_TARGETS_HIT, 0
jumptocalledmove TRUE
BattleScript_CopycatFail:
ppreduce
@ -1762,8 +1761,6 @@ BattleScript_EffectInstruct::
copybyte gBattlerTarget, gEffectBattler
printstring STRINGID_USEDINSTRUCTEDMOVE
waitmessage B_WAIT_TIME_LONG
setbyte sB_ANIM_TURN, 0
setbyte sB_ANIM_TARGETS_HIT, 0
jumptocalledmove TRUE
BattleScript_EffectAutotomize::
@ -2187,8 +2184,6 @@ BattleScript_EffectMeFirst::
trymefirst BattleScript_FailedFromPpReduce
attackanimation
waitanimation
setbyte sB_ANIM_TURN, 0
setbyte sB_ANIM_TARGETS_HIT, 0
jumptocalledmove TRUE
BattleScript_EffectAttackSpAttackUp::
@ -3657,12 +3652,12 @@ BattleScript_EffectParalyze::
jumpifmovehadnoeffect BattleScript_ButItFailed
jumpifstatus BS_TARGET, STATUS1_PARALYSIS, BattleScript_AlreadyParalyzed
jumpifelectricabilityaffected BS_TARGET, ABILITY_VOLT_ABSORB, BattleScript_VoltAbsorbHeal
clearmoveresultflags MOVE_RESULT_SUPER_EFFECTIVE | MOVE_RESULT_NOT_VERY_EFFECTIVE
tryparalyzetype BS_ATTACKER, BS_TARGET, BattleScript_NotAffected
jumpifstatus BS_TARGET, STATUS1_ANY, BattleScript_ButItFailed
jumpifterrainaffected BS_TARGET, STATUS_FIELD_MISTY_TERRAIN, BattleScript_MistyTerrainPrevents
accuracycheck BattleScript_ButItFailed, ACC_CURR_MOVE
jumpifsafeguard BattleScript_SafeguardProtected
clearmoveresultflags MOVE_RESULT_SUPER_EFFECTIVE | MOVE_RESULT_NOT_VERY_EFFECTIVE
attackanimation
waitanimation
seteffectprimary MOVE_EFFECT_PARALYSIS
@ -3843,8 +3838,6 @@ BattleScript_EffectMetronome::
pause B_WAIT_TIME_SHORT
attackanimation
waitanimation
setbyte sB_ANIM_TURN, 0
setbyte sB_ANIM_TARGETS_HIT, 0
metronome
BattleScript_EffectLeechSeed::
@ -4051,8 +4044,6 @@ BattleScript_SleepTalkIsAsleep::
BattleScript_SleepTalkUsingMove::
attackanimation
waitanimation
setbyte sB_ANIM_TURN, 0
setbyte sB_ANIM_TARGETS_HIT, 0
jumptocalledmove TRUE
BattleScript_EffectDestinyBond::
@ -4167,11 +4158,15 @@ BattleScript_CurseTrySpeed::
setbyte sB_ANIM_TURN, 1
attackanimation
waitanimation
setbyte sSTAT_ANIM_PLAYED, FALSE
playstatchangeanimation BS_ATTACKER, BIT_SPEED, STAT_CHANGE_NEGATIVE
setstatchanger STAT_SPEED, 1, TRUE
statbuffchange MOVE_EFFECT_AFFECTS_USER | STAT_CHANGE_ALLOW_PTR, BattleScript_CurseTryAttack
printfromtable gStatDownStringIds
waitmessage B_WAIT_TIME_LONG
BattleScript_CurseTryAttack::
setbyte sSTAT_ANIM_PLAYED, FALSE
playstatchangeanimation BS_ATTACKER, BIT_ATK | BIT_DEF, 0
setstatchanger STAT_ATK, 1, FALSE
statbuffchange MOVE_EFFECT_AFFECTS_USER | STAT_CHANGE_ALLOW_PTR, BattleScript_CurseTryDefense
printfromtable gStatUpStringIds
@ -5070,8 +5065,6 @@ BattleScript_EffectAssist::
assistattackselect BattleScript_FailedFromPpReduce
attackanimation
waitanimation
setbyte sB_ANIM_TURN, 0
setbyte sB_ANIM_TARGETS_HIT, 0
jumptocalledmove TRUE
BattleScript_EffectIngrain::
@ -5112,6 +5105,7 @@ BattleScript_EffectBrickBreak::
accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE
attackstring
ppreduce
typecalc
removelightscreenreflect
critcalc
damagecalc
@ -8651,8 +8645,6 @@ BattleScript_BattleBondActivatesOnMoveEndAttacker::
BattleScript_DancerActivates::
call BattleScript_AbilityPopUp
waitmessage B_WAIT_TIME_SHORT
setbyte sB_ANIM_TURN, 0
setbyte sB_ANIM_TARGETS_HIT, 0
orword gHitMarker, HITMARKER_ALLOW_NO_PP
jumptocalledmove TRUE

View File

@ -554,6 +554,10 @@ Causes the test to fail if a and b compare incorrectly, e.g.
EXPECT_EQ(results[0].damage, Q_4_12(1.5), results[1].damage);
```
### `FORCE_MOVE_ANIM`
`FORCE_MOVE_ANIM(TRUE)`
Forces the moves in the current test to do their animations in headless mode. Useful for debugging animations.
## Overworld Command Reference
### `OVERWORLD_SCRIPT`

View File

@ -14,6 +14,6 @@ JASC-PAL
96 168 120
64 136 96
232 232 248
152 40 64
39 44 149
0 0 0
0 0 0

View File

@ -835,6 +835,7 @@ struct BattleStruct
u8 padding3:2;
struct MessageStatus slideMessageStatus;
u8 trainerSlideSpriteIds[MAX_BATTLERS_COUNT];
u8 embodyAspectBoost[NUM_BATTLE_SIDES];
};
// The palaceFlags member of struct BattleStruct contains 1 flag per move to indicate which moves the AI should consider,

View File

@ -172,6 +172,7 @@ enum SleepClauseBlock
void HandleAction_ThrowBall(void);
bool32 IsAffectedByFollowMe(u32 battlerAtk, u32 defSide, u32 move);
bool32 HandleMoveTargetRedirection(void);
void HandleAction_UseMove(void);
void HandleAction_Switch(void);
void HandleAction_UseItem(void);

View File

@ -533,6 +533,20 @@ static inline s32 SetFixedMoveBasePower(u32 battlerAtk, u32 move)
return fixedBasePower;
}
static inline void AI_StoreBattlerTypes(u32 battlerAtk, u32 *types)
{
types[0] = gBattleMons[battlerAtk].types[0];
types[1] = gBattleMons[battlerAtk].types[1];
types[2] = gBattleMons[battlerAtk].types[2];
}
static inline void AI_RestoreBattlerTypes(u32 battlerAtk, u32 *types)
{
gBattleMons[battlerAtk].types[0] = types[0];
gBattleMons[battlerAtk].types[1] = types[1];
gBattleMons[battlerAtk].types[2] = types[2];
}
static inline void CalcDynamicMoveDamage(struct DamageCalculationData *damageCalcData, s32 *expectedDamage, s32 *minimumDamage, u32 holdEffectAtk, u32 abilityAtk)
{
u32 move = damageCalcData->move;
@ -658,6 +672,9 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u
{
s32 critChanceIndex, fixedBasePower;
u32 types[3];
AI_StoreBattlerTypes(battlerAtk, types);
ProteanTryChangeType(battlerAtk, aiData->abilities[battlerAtk], move, moveType);
fixedBasePower = SetFixedMoveBasePower(battlerAtk, move);
@ -735,6 +752,8 @@ struct SimulatedDamage AI_CalcDamage(u32 move, u32 battlerAtk, u32 battlerDef, u
aiData->holdEffects[battlerAtk],
aiData->abilities[battlerAtk]);
}
AI_RestoreBattlerTypes(battlerAtk, types);
}
else
{
@ -1068,8 +1087,15 @@ s32 AI_WhoStrikesFirst(u32 battlerAI, u32 battler, u32 moveConsidered)
u32 abilityAI = AI_DATA->abilities[battlerAI];
u32 abilityPlayer = AI_DATA->abilities[battler];
if (GetBattleMovePriority(battlerAI, moveConsidered) > 0)
u32 predictedMove = AI_DATA->lastUsedMove[battler]; // TODO update for move prediction
s8 aiPriority = GetBattleMovePriority(battlerAI, moveConsidered);
s8 playerPriority = GetBattleMovePriority(battler, predictedMove);
if (aiPriority > playerPriority)
return AI_IS_FASTER;
else if (aiPriority < playerPriority)
return AI_IS_SLOWER;
speedBattlerAI = GetBattlerTotalSpeedStatArgs(battlerAI, abilityAI, holdEffectAI);
speedBattler = GetBattlerTotalSpeedStatArgs(battler, abilityPlayer, holdEffectPlayer);

View File

@ -344,6 +344,7 @@ static bool8 CanBurnHitThaw(u16 move);
static u32 GetNextTarget(u32 moveTarget, bool32 excludeCurrent);
static void TryUpdateEvolutionTracker(u32 evolutionMethod, u32 upAmount, u16 usedMove);
static void AccuracyCheck(bool32 recalcDragonDarts, const u8 *nextInstr, const u8 *failInstr, u16 move);
static void ResetValuesForCalledMove(void);
static void Cmd_attackcanceler(void);
static void Cmd_accuracycheck(void);
@ -8746,6 +8747,18 @@ static void Cmd_hidepartystatussummary(void)
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void ResetValuesForCalledMove(void)
{
if (gBattlerByTurnOrder[gCurrentTurnActionNumber] != gBattlerAttacker)
gBattleStruct->atkCancellerTracker = 0;
else
SetAtkCancellerForCalledMove();
gBattleScripting.animTurn = 0;
gBattleScripting.animTargetsHit = 0;
SetTypeBeforeUsingMove(gCurrentMove, gBattlerAttacker);
HandleMoveTargetRedirection();
}
static void Cmd_jumptocalledmove(void)
{
CMD_ARGS(bool8 notChosenMove);
@ -8755,6 +8768,8 @@ static void Cmd_jumptocalledmove(void)
else
gChosenMove = gCurrentMove = gCalledMove;
ResetValuesForCalledMove();
gBattlescriptCurrInstr = GetMoveBattleScript(gCurrentMove);
}
@ -10811,10 +10826,8 @@ static void Cmd_various(void)
gBattlescriptCurrInstr = cmd->failInstr;
else
{
SetTypeBeforeUsingMove(gCalledMove, gBattlerTarget);
gEffectBattler = gBattleStruct->lastMoveTarget[gBattlerTarget];
gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED;
gBattleStruct->atkCancellerTracker = 0;
PREPARE_MON_NICK_WITH_PREFIX_BUFFER(gBattleTextBuff1, battler, gBattlerPartyIndexes[battler]);
gBattlescriptCurrInstr = cmd->nextInstr;
}
@ -11804,8 +11817,8 @@ static void SetMoveForMirrorMove(u32 move)
gCurrentMove = move;
}
SetAtkCancellerForCalledMove();
gBattlerTarget = GetBattleMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE);
ResetValuesForCalledMove();
gBattlescriptCurrInstr = GetMoveBattleScript(gCurrentMove);
}
@ -13333,10 +13346,10 @@ static void Cmd_metronome(void)
#endif
gCurrentMove = RandomUniformExcept(RNG_METRONOME, 1, moveCount - 1, InvalidMetronomeMove);
SetAtkCancellerForCalledMove();
PrepareStringBattle(STRINGID_WAGGLINGAFINGER, gBattlerAttacker);
gBattlescriptCurrInstr = GetMoveBattleScript(gCurrentMove);
gBattlerTarget = GetBattleMoveTarget(gCurrentMove, NO_TARGET_OVERRIDE);
ResetValuesForCalledMove();
}
static void Cmd_dmgtolevel(void)
@ -18046,14 +18059,10 @@ void BS_JumpIfBlockedBySoundproof(void)
void BS_SetMagicCoatTarget(void)
{
NATIVE_ARGS();
u32 side;
gBattleStruct->attackerBeforeBounce = gBattleScripting.battler = gBattlerAttacker;
gBattlerAttacker = gBattlerTarget;
side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker));
if (IsAffectedByFollowMe(gBattlerAttacker, side, gCurrentMove))
gBattlerTarget = gSideTimers[side].followmeTarget;
else
gBattlerTarget = gBattleStruct->attackerBeforeBounce;
gBattlerTarget = gBattleStruct->attackerBeforeBounce;
HandleMoveTargetRedirection();
gBattlescriptCurrInstr = cmd->nextInstr;
}

View File

@ -234,11 +234,69 @@ bool32 IsAffectedByFollowMe(u32 battlerAtk, u32 defSide, u32 move)
return TRUE;
}
bool32 HandleMoveTargetRedirection(void)
{
u32 redirectorOrderNum = MAX_BATTLERS_COUNT;
u16 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove);
u32 moveType = GetBattleMoveType(gCurrentMove);
u32 moveEffect = GetMoveEffect(gCurrentMove);
u32 side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker));
u32 ability = GetBattlerAbility(gBattleStruct->moveTarget[gBattlerAttacker]);
if (IsAffectedByFollowMe(gBattlerAttacker, side, gCurrentMove)
&& moveTarget == MOVE_TARGET_SELECTED
&& GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gSideTimers[side].followmeTarget))
{
gBattleStruct->moveTarget[gBattlerAttacker] = gBattlerTarget = gSideTimers[side].followmeTarget; // follow me moxie fix
return TRUE;
}
else if (IsDoubleBattle()
&& gSideTimers[side].followmeTimer == 0
&& (!IsBattleMoveStatus(gCurrentMove) || (moveTarget != MOVE_TARGET_USER && moveTarget != MOVE_TARGET_ALL_BATTLERS))
&& ((ability != ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC)
|| (ability != ABILITY_STORM_DRAIN && moveType == TYPE_WATER)))
{
// Find first battler that redirects the move (in turn order)
u32 battler;
for (battler = 0; battler < gBattlersCount; battler++)
{
ability = GetBattlerAbility(battler);
if ((B_REDIRECT_ABILITY_ALLIES >= GEN_4 || !IsAlly(gBattlerAttacker, battler))
&& battler != gBattlerAttacker
&& gBattleStruct->moveTarget[gBattlerAttacker] != battler
&& ((ability == ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC)
|| (ability == ABILITY_STORM_DRAIN && moveType == TYPE_WATER))
&& GetBattlerTurnOrderNum(battler) < redirectorOrderNum
&& moveEffect != EFFECT_SNIPE_SHOT
&& moveEffect != EFFECT_PLEDGE
&& GetBattlerAbility(gBattlerAttacker) != ABILITY_PROPELLER_TAIL
&& GetBattlerAbility(gBattlerAttacker) != ABILITY_STALWART)
{
redirectorOrderNum = GetBattlerTurnOrderNum(battler);
}
}
if (redirectorOrderNum != MAX_BATTLERS_COUNT)
{
u16 battlerAbility;
battler = gBattlerByTurnOrder[redirectorOrderNum];
battlerAbility = GetBattlerAbility(battler);
RecordAbilityBattle(battler, gBattleMons[battler].ability);
if (battlerAbility == ABILITY_LIGHTNING_ROD && gCurrentMove != MOVE_TEATIME)
gSpecialStatuses[battler].lightningRodRedirected = TRUE;
else if (battlerAbility == ABILITY_STORM_DRAIN)
gSpecialStatuses[battler].stormDrainRedirected = TRUE;
gBattlerTarget = battler;
return TRUE;
}
}
return FALSE;
}
// Functions
void HandleAction_UseMove(void)
{
u32 battler, i, side, moveType, ability, var = MAX_BATTLERS_COUNT;
u16 moveTarget;
u32 i;
gBattlerAttacker = gBattlerByTurnOrder[gCurrentTurnActionNumber];
if (gBattleStruct->battlerState[gBattlerAttacker].absentBattlerFlags
@ -308,7 +366,6 @@ void HandleAction_UseMove(void)
// Set dynamic move type.
SetTypeBeforeUsingMove(gChosenMove, gBattlerAttacker);
moveType = GetBattleMoveType(gCurrentMove);
// check Z-Move used
if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_Z_MOVE && !IsBattleMoveStatus(gCurrentMove) && !IsZMove(gCurrentMove))
@ -323,117 +380,44 @@ void HandleAction_UseMove(void)
gCurrentMove = gChosenMove = GetMaxMove(gBattlerAttacker, gCurrentMove);
}
moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove);
u32 moveEffect = GetMoveEffect(gCurrentMove);
// choose target
side = BATTLE_OPPOSITE(GetBattlerSide(gBattlerAttacker));
ability = GetBattlerAbility(gBattleStruct->moveTarget[gBattlerAttacker]);
if (IsAffectedByFollowMe(gBattlerAttacker, side, gCurrentMove)
&& moveTarget == MOVE_TARGET_SELECTED
&& GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gSideTimers[side].followmeTarget))
if (!HandleMoveTargetRedirection())
{
gBattleStruct->moveTarget[gBattlerAttacker] = gBattlerTarget = gSideTimers[side].followmeTarget; // follow me moxie fix
}
else if (IsDoubleBattle()
&& gSideTimers[side].followmeTimer == 0
&& !gBattleStruct->battlerState[gBattleStruct->moveTarget[gBattlerAttacker]].pursuitTarget
&& (!IsBattleMoveStatus(gCurrentMove) || (moveTarget != MOVE_TARGET_USER && moveTarget != MOVE_TARGET_ALL_BATTLERS))
&& ((ability != ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC)
|| (ability != ABILITY_STORM_DRAIN && moveType == TYPE_WATER)))
{
// Find first battler that redirects the move (in turn order)
for (battler = 0; battler < gBattlersCount; battler++)
u32 moveTarget = GetBattlerMoveTargetType(gBattlerAttacker, gCurrentMove);
if (IsDoubleBattle() && moveTarget & MOVE_TARGET_RANDOM)
{
ability = GetBattlerAbility(battler);
if ((B_REDIRECT_ABILITY_ALLIES >= GEN_4 || !IsBattlerAlly(gBattlerAttacker, battler))
&& battler != gBattlerAttacker
&& gBattleStruct->moveTarget[gBattlerAttacker] != battler
&& ((ability == ABILITY_LIGHTNING_ROD && moveType == TYPE_ELECTRIC)
|| (ability == ABILITY_STORM_DRAIN && moveType == TYPE_WATER))
&& GetBattlerTurnOrderNum(battler) < var
&& moveEffect != EFFECT_SNIPE_SHOT
&& moveEffect != EFFECT_PLEDGE
&& GetBattlerAbility(gBattlerAttacker) != ABILITY_PROPELLER_TAIL
&& GetBattlerAbility(gBattlerAttacker) != ABILITY_STALWART)
{
var = GetBattlerTurnOrderNum(battler);
}
}
if (var == MAX_BATTLERS_COUNT)
{
if (moveTarget & MOVE_TARGET_RANDOM)
{
gBattlerTarget = SetRandomTarget(gBattlerAttacker);
}
else if (moveTarget & MOVE_TARGET_FOES_AND_ALLY)
{
for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount; gBattlerTarget++)
{
if (gBattlerTarget == gBattlerAttacker)
continue;
if (IsBattlerAlive(gBattlerTarget))
break;
}
}
else
{
gBattlerTarget = gBattleStruct->moveTarget[gBattlerAttacker];
}
if (!IsBattlerAlive(gBattlerTarget) && GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget))
gBattlerTarget = SetRandomTarget(gBattlerAttacker);
if (gAbsentBattlerFlags & (1u << gBattlerTarget)
&& GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget))
{
gBattlerTarget = GetPartnerBattler(gBattlerTarget);
}
}
else if (moveTarget == MOVE_TARGET_ALLY)
{
if (IsBattlerAlive(BATTLE_PARTNER(gBattlerAttacker)))
gBattlerTarget = BATTLE_PARTNER(gBattlerAttacker);
else
gBattlerTarget = gBattlerAttacker;
}
else if (IsDoubleBattle() && moveTarget == MOVE_TARGET_FOES_AND_ALLY)
{
for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount; gBattlerTarget++)
{
if (gBattlerTarget == gBattlerAttacker)
continue;
if (IsBattlerAlive(gBattlerTarget))
break;
}
}
else
{
u16 battlerAbility;
battler = gBattlerByTurnOrder[var];
battlerAbility = GetBattlerAbility(battler);
RecordAbilityBattle(battler, gBattleMons[battler].ability);
if (battlerAbility == ABILITY_LIGHTNING_ROD && gCurrentMove != MOVE_TEATIME)
gSpecialStatuses[battler].lightningRodRedirected = TRUE;
else if (battlerAbility == ABILITY_STORM_DRAIN)
gSpecialStatuses[battler].stormDrainRedirected = TRUE;
gBattlerTarget = battler;
}
}
else if (IsDoubleBattle() && moveTarget & MOVE_TARGET_RANDOM)
{
gBattlerTarget = SetRandomTarget(gBattlerAttacker);
if (gAbsentBattlerFlags & (1u << gBattlerTarget)
&& GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget))
{
gBattlerTarget = GetPartnerBattler(gBattlerTarget);
}
}
else if (moveTarget == MOVE_TARGET_ALLY)
{
if (IsBattlerAlive(BATTLE_PARTNER(gBattlerAttacker)))
gBattlerTarget = BATTLE_PARTNER(gBattlerAttacker);
else
gBattlerTarget = gBattlerAttacker;
}
else if (IsDoubleBattle() && moveTarget == MOVE_TARGET_FOES_AND_ALLY)
{
for (gBattlerTarget = 0; gBattlerTarget < gBattlersCount; gBattlerTarget++)
{
if (gBattlerTarget == gBattlerAttacker)
continue;
if (IsBattlerAlive(gBattlerTarget))
break;
}
}
else
{
gBattlerTarget = gBattleStruct->moveTarget[gBattlerAttacker];
if (!IsBattlerAlive(gBattlerTarget)
&& moveTarget != MOVE_TARGET_OPPONENTS_FIELD
&& (GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget)))
{
gBattlerTarget = GetPartnerBattler(gBattlerTarget);
gBattlerTarget = *(gBattleStruct->moveTarget + gBattlerAttacker);
if (!IsBattlerAlive(gBattlerTarget)
&& moveTarget != MOVE_TARGET_OPPONENTS_FIELD
&& (GetBattlerSide(gBattlerAttacker) != GetBattlerSide(gBattlerTarget)))
{
gBattlerTarget = GetBattlerAtPosition(BATTLE_PARTNER(GetBattlerPosition(gBattlerTarget)));
}
}
}
@ -3215,6 +3199,8 @@ static void CancellerAsleep(u32 *effect)
gBattleMons[gBattlerAttacker].status1 -= toSub;
if (gBattleMons[gBattlerAttacker].status1 & STATUS1_SLEEP)
{
u32 moveEffect = GetMoveEffect(gChosenMove);
if (moveEffect != EFFECT_SNORE && moveEffect != EFFECT_SLEEP_TALK)
if (gChosenMove != MOVE_SNORE && gChosenMove != MOVE_SLEEP_TALK)
{
gBattlescriptCurrInstr = BattleScript_MoveUsedIsAsleep;
@ -3551,7 +3537,7 @@ static void CancellerThaw(u32 *effect)
static void CancellerStanceChangeTwo(u32 *effect)
{
if (B_STANCE_CHANGE_FAIL >= GEN_7 && TryFormChangeBeforeMove())
if (B_STANCE_CHANGE_FAIL >= GEN_7 && !gBattleStruct->isAtkCancelerForCalledMove && TryFormChangeBeforeMove())
*effect = 1;
}
@ -5306,7 +5292,8 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
case ABILITY_EMBODY_ASPECT_HEARTHFLAME_MASK:
case ABILITY_EMBODY_ASPECT_WELLSPRING_MASK:
case ABILITY_EMBODY_ASPECT_CORNERSTONE_MASK:
if (!gSpecialStatuses[battler].switchInAbilityDone)
if (!gSpecialStatuses[battler].switchInAbilityDone
&& !(gBattleStruct->embodyAspectBoost[GetBattlerSide(battler)] & (1u << gBattlerPartyIndexes[battler])))
{
u32 stat;
@ -5325,6 +5312,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
gBattleScripting.savedBattler = gBattlerAttacker;
gBattlerAttacker = battler;
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
gBattleStruct->embodyAspectBoost[GetBattlerSide(battler)] |= 1u << gBattlerPartyIndexes[battler];
SET_STATCHANGER(stat, 1, FALSE);
BattleScriptPushCursorAndCallback(BattleScript_BattlerAbilityStatRaiseOnSwitchIn);
effect++;
@ -6297,7 +6285,6 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
// Set bit and save Dancer mon's original target
gSpecialStatuses[battler].dancerUsedMove = TRUE;
gSpecialStatuses[battler].dancerOriginalTarget = gBattleStruct->moveTarget[battler] | 0x4;
gBattleStruct->atkCancellerTracker = 0;
gBattlerAttacker = gBattlerAbility = battler;
gCalledMove = gCurrentMove;
@ -6306,7 +6293,6 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
// Edge case for dance moves that hit multiply targets
gHitMarker &= ~HITMARKER_NO_ATTACKSTRING;
SetTypeBeforeUsingMove(gCalledMove, battler);
// Make sure that the target isn't an ally - if it is, target the original user
if (IsBattlerAlly(gBattlerTarget, gBattlerAttacker))

View File

@ -105,7 +105,7 @@ static const u8 sFeintDescription[] = _(
"An attack that hits foes\n"
"using moves like Protect.");
const struct MoveInfo gMovesInfo[MOVES_COUNT_DYNAMAX] =
const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] =
{
[MOVE_NONE] =
{

View File

@ -3439,7 +3439,7 @@ static void DebugAction_Give_Pokemon_ComplexCreateMon(u8 taskId) //https://githu
//Moves
for (i = 0; i < MAX_MON_MOVES; i++)
{
if (moves[i] == MOVE_NONE || moves[i] == 0xFF || moves[i] >= MOVES_COUNT)
if (moves[i] == MOVE_NONE || moves[i] >= MOVES_COUNT)
continue;
SetMonMoveSlot(&mon, moves[i], i);

View File

@ -38,8 +38,6 @@ static void Task_DrawFieldMessage(u8 taskId)
LoadMessageBoxAndBorderGfx();
task->tState++;
break;
task->tState++;
break;
case 1:
DrawDialogueFrame(0, TRUE);
task->tState++;

View File

@ -6074,15 +6074,8 @@ const u8 *GetTrainerPartnerName(void)
{
if (gBattleTypeFlags & BATTLE_TYPE_INGAME_PARTNER)
{
if (gPartnerTrainerId == TRAINER_PARTNER(PARTNER_STEVEN))
{
return GetTrainerNameFromId(PARTNER_STEVEN);
}
else
{
GetFrontierTrainerName(gStringVar1, gPartnerTrainerId);
return gStringVar1;
}
GetFrontierTrainerName(gStringVar1, gPartnerTrainerId);
return gStringVar1;
}
else
{

View File

@ -57,3 +57,26 @@ SINGLE_BATTLE_TEST("Embody Aspect activates when it's no longer effected by Neut
MESSAGE("The opposing Ogerpon's Embody Aspect raised its Speed!");
}
}
SINGLE_BATTLE_TEST("Embody Aspect raises Speed only once per battle")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_OGERPON_TEAL_TERA) { Ability(ABILITY_EMBODY_ASPECT_TEAL_MASK); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { SWITCH(opponent, 1); }
TURN { SWITCH(opponent, 0); }
} SCENE {
ABILITY_POPUP(opponent, ABILITY_EMBODY_ASPECT_TEAL_MASK);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
MESSAGE("The opposing Ogerpon's Embody Aspect raised its Speed!");
NONE_OF {
ABILITY_POPUP(opponent, ABILITY_EMBODY_ASPECT_TEAL_MASK);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
MESSAGE("The opposing Ogerpon's Embody Aspect raised its Speed!");
}
} THEN {
EXPECT_EQ(opponent->statStages[STAT_SPEED], DEFAULT_STAT_STAGE);
}
}

View File

@ -831,3 +831,17 @@ AI_SINGLE_BATTLE_TEST("AI stays choice locked into moves in spite of the player'
TURN { EXPECT_MOVE(opponent, aiMove); }
}
}
AI_SINGLE_BATTLE_TEST("AI won't use Sucker Punch if it expects a move of the same priority bracket and the opponent is faster")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_QUICK_ATTACK].priority == 1);
ASSUME(gMovesInfo[MOVE_SUCKER_PUNCH].priority == 1);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET) { Speed(300); Moves(MOVE_QUICK_ATTACK); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(100); Moves(MOVE_SUCKER_PUNCH, MOVE_TACKLE); }
} WHEN {
TURN { MOVE(player, MOVE_QUICK_ATTACK); EXPECT_MOVE(opponent, MOVE_SUCKER_PUNCH); }
TURN { MOVE(player, MOVE_QUICK_ATTACK); EXPECT_MOVE(opponent, MOVE_TACKLE); }
}
}

View File

@ -4,119 +4,139 @@
ASSUMPTIONS
{
ASSUME(GetMoveEffect(MOVE_BRICK_BREAK) == EFFECT_BRICK_BREAK);
ASSUME(GetMoveEffect(MOVE_PSYCHIC_FANGS) == EFFECT_BRICK_BREAK);
ASSUME(GetMoveEffect(MOVE_SNOWSCAPE) == EFFECT_SNOWSCAPE);
ASSUME(GetMoveEffect(MOVE_LIGHT_SCREEN) == EFFECT_LIGHT_SCREEN);
ASSUME(GetMoveEffect(MOVE_REFLECT) == EFFECT_REFLECT);
ASSUME(GetMoveEffect(MOVE_AURORA_VEIL) == EFFECT_AURORA_VEIL);
}
SINGLE_BATTLE_TEST("Brick Break removes Light Screen, Reflect and Aurora Veil from the target's side of the field")
SINGLE_BATTLE_TEST("Brick Break and Psychic Fangs remove Light Screen, Reflect and Aurora Veil from the target's side of the field")
{
u16 move;
u32 move;
u32 breakingMove;
PARAMETRIZE { move = MOVE_LIGHT_SCREEN; }
PARAMETRIZE { move = MOVE_REFLECT; }
PARAMETRIZE { move = MOVE_AURORA_VEIL; }
PARAMETRIZE { move = MOVE_LIGHT_SCREEN; breakingMove = MOVE_BRICK_BREAK; }
PARAMETRIZE { move = MOVE_REFLECT; breakingMove = MOVE_BRICK_BREAK; }
PARAMETRIZE { move = MOVE_AURORA_VEIL; breakingMove = MOVE_BRICK_BREAK; }
PARAMETRIZE { move = MOVE_LIGHT_SCREEN; breakingMove = MOVE_PSYCHIC_FANGS; }
PARAMETRIZE { move = MOVE_REFLECT; breakingMove = MOVE_PSYCHIC_FANGS; }
PARAMETRIZE { move = MOVE_AURORA_VEIL; breakingMove = MOVE_PSYCHIC_FANGS; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_SNOWSCAPE); }
TURN { MOVE(opponent, move); MOVE(player, MOVE_BRICK_BREAK); }
TURN { MOVE(opponent, move); MOVE(player, breakingMove); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SNOWSCAPE, player);
ANIMATION(ANIM_TYPE_MOVE, move, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_BRICK_BREAK, player);
ANIMATION(ANIM_TYPE_MOVE, breakingMove, player);
MESSAGE("The wall shattered!");
HP_BAR(opponent);
}
}
SINGLE_BATTLE_TEST("Brick Break doesn't remove Light Screen, Reflect and Aurora Veil if the target is immune")
SINGLE_BATTLE_TEST("Brick Break and Psychic Fangs don't remove Light Screen, Reflect and Aurora Veil if the target is immune")
{
u16 move;
u32 move;
u32 breakingMove;
PARAMETRIZE { move = MOVE_LIGHT_SCREEN; }
PARAMETRIZE { move = MOVE_REFLECT; }
PARAMETRIZE { move = MOVE_AURORA_VEIL; }
PARAMETRIZE { move = MOVE_LIGHT_SCREEN; breakingMove = MOVE_BRICK_BREAK; }
PARAMETRIZE { move = MOVE_REFLECT; breakingMove = MOVE_BRICK_BREAK; }
PARAMETRIZE { move = MOVE_AURORA_VEIL; breakingMove = MOVE_BRICK_BREAK; }
PARAMETRIZE { move = MOVE_LIGHT_SCREEN; breakingMove = MOVE_PSYCHIC_FANGS; }
PARAMETRIZE { move = MOVE_REFLECT; breakingMove = MOVE_PSYCHIC_FANGS; }
PARAMETRIZE { move = MOVE_AURORA_VEIL; breakingMove = MOVE_PSYCHIC_FANGS; }
KNOWN_FAILING;
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_GASTLY);
OPPONENT(SPECIES_SABLEYE);
} WHEN {
TURN { MOVE(player, MOVE_SNOWSCAPE); }
TURN { MOVE(opponent, move); MOVE(player, MOVE_BRICK_BREAK); }
TURN { MOVE(opponent, move); MOVE(player, breakingMove); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SNOWSCAPE, player);
ANIMATION(ANIM_TYPE_MOVE, move, opponent);
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_BRICK_BREAK, player);
ANIMATION(ANIM_TYPE_MOVE, breakingMove, player);
MESSAGE("The wall shattered!");
HP_BAR(opponent);
}
}
}
SINGLE_BATTLE_TEST("Brick Break doesn't remove Light Screen, Reflect and Aurora Veil if the target Protected")
SINGLE_BATTLE_TEST("Brick Break and Psychic Fangs don't remove Light Screen, Reflect and Aurora Veil if the target Protected")
{
u16 move;
u32 move;
u32 breakingMove;
PARAMETRIZE { move = MOVE_LIGHT_SCREEN; }
PARAMETRIZE { move = MOVE_REFLECT; }
PARAMETRIZE { move = MOVE_AURORA_VEIL; }
PARAMETRIZE { move = MOVE_LIGHT_SCREEN; breakingMove = MOVE_BRICK_BREAK; }
PARAMETRIZE { move = MOVE_REFLECT; breakingMove = MOVE_BRICK_BREAK; }
PARAMETRIZE { move = MOVE_AURORA_VEIL; breakingMove = MOVE_BRICK_BREAK; }
PARAMETRIZE { move = MOVE_LIGHT_SCREEN; breakingMove = MOVE_PSYCHIC_FANGS; }
PARAMETRIZE { move = MOVE_REFLECT; breakingMove = MOVE_PSYCHIC_FANGS; }
PARAMETRIZE { move = MOVE_AURORA_VEIL; breakingMove = MOVE_PSYCHIC_FANGS; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_SNOWSCAPE); MOVE(opponent, move); }
TURN { MOVE(player, MOVE_BRICK_BREAK); MOVE(opponent, MOVE_PROTECT); }
TURN { MOVE(player, breakingMove); MOVE(opponent, MOVE_PROTECT); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SNOWSCAPE, player);
ANIMATION(ANIM_TYPE_MOVE, move, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponent);
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_BRICK_BREAK, player);
ANIMATION(ANIM_TYPE_MOVE, breakingMove, player);
MESSAGE("The wall shattered!");
HP_BAR(opponent);
}
}
}
SINGLE_BATTLE_TEST("Brick Break doesn't remove Light Screen, Reflect and Aurora Veil if it misses")
SINGLE_BATTLE_TEST("Brick Break and Psychic Fangs don't remove Light Screen, Reflect and Aurora Veil if it misses")
{
u16 move;
u32 move;
u32 breakingMove;
PARAMETRIZE { move = MOVE_LIGHT_SCREEN; }
PARAMETRIZE { move = MOVE_REFLECT; }
PARAMETRIZE { move = MOVE_AURORA_VEIL; }
PARAMETRIZE { move = MOVE_LIGHT_SCREEN; breakingMove = MOVE_BRICK_BREAK; }
PARAMETRIZE { move = MOVE_REFLECT; breakingMove = MOVE_BRICK_BREAK; }
PARAMETRIZE { move = MOVE_AURORA_VEIL; breakingMove = MOVE_BRICK_BREAK; }
PARAMETRIZE { move = MOVE_LIGHT_SCREEN; breakingMove = MOVE_PSYCHIC_FANGS; }
PARAMETRIZE { move = MOVE_REFLECT; breakingMove = MOVE_PSYCHIC_FANGS; }
PARAMETRIZE { move = MOVE_AURORA_VEIL; breakingMove = MOVE_PSYCHIC_FANGS; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_BRIGHT_POWDER); }
} WHEN {
TURN { MOVE(player, MOVE_SNOWSCAPE); MOVE(opponent, move); }
TURN { MOVE(player, MOVE_BRICK_BREAK, hit: FALSE); }
TURN { MOVE(player, breakingMove, hit: FALSE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SNOWSCAPE, player);
ANIMATION(ANIM_TYPE_MOVE, move, opponent);
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_BRICK_BREAK, player);
ANIMATION(ANIM_TYPE_MOVE, breakingMove, player);
MESSAGE("The wall shattered!");
HP_BAR(opponent);
}
}
}
DOUBLE_BATTLE_TEST("Brick Break can remove Light Screen, Reflect and Aurora Veil on users side")
DOUBLE_BATTLE_TEST("Brick Break and Psychic Fangs can remove Light Screen, Reflect and Aurora Veil on users side")
{
u16 move;
u32 move;
u32 breakingMove;
PARAMETRIZE { move = MOVE_LIGHT_SCREEN; }
PARAMETRIZE { move = MOVE_REFLECT; }
PARAMETRIZE { move = MOVE_AURORA_VEIL; }
PARAMETRIZE { move = MOVE_LIGHT_SCREEN; breakingMove = MOVE_BRICK_BREAK; }
PARAMETRIZE { move = MOVE_REFLECT; breakingMove = MOVE_BRICK_BREAK; }
PARAMETRIZE { move = MOVE_AURORA_VEIL; breakingMove = MOVE_BRICK_BREAK; }
PARAMETRIZE { move = MOVE_LIGHT_SCREEN; breakingMove = MOVE_PSYCHIC_FANGS; }
PARAMETRIZE { move = MOVE_REFLECT; breakingMove = MOVE_PSYCHIC_FANGS; }
PARAMETRIZE { move = MOVE_AURORA_VEIL; breakingMove = MOVE_PSYCHIC_FANGS; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
@ -127,12 +147,12 @@ DOUBLE_BATTLE_TEST("Brick Break can remove Light Screen, Reflect and Aurora Veil
TURN {
MOVE(opponentLeft, MOVE_SNOWSCAPE);
MOVE(playerLeft, move);
MOVE(playerRight, MOVE_BRICK_BREAK, target: playerLeft);
MOVE(playerRight, breakingMove, target: playerLeft);
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SNOWSCAPE, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, move, playerLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_BRICK_BREAK, playerRight);
ANIMATION(ANIM_TYPE_MOVE, breakingMove, playerRight);
MESSAGE("The wall shattered!");
HP_BAR(playerLeft);
}

View File

@ -15,7 +15,9 @@ SINGLE_BATTLE_TEST("Curse lowers Speed, raises Attack, and raises Defense when u
TURN { MOVE(player, MOVE_CURSE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CURSE, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("Wobbuffet's Speed fell!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("Wobbuffet's Attack rose!");
MESSAGE("Wobbuffet's Defense rose!");
}

View File

@ -0,0 +1,71 @@
#include "global.h"
#include "test/battle.h"
ASSUMPTIONS
{
ASSUME(gMovesInfo[MOVE_FOLLOW_ME].effect == EFFECT_FOLLOW_ME);
ASSUME(gMovesInfo[MOVE_SPOTLIGHT].effect == EFFECT_FOLLOW_ME);
}
DOUBLE_BATTLE_TEST("Follow Me redirects single target moves used by opponents to user")
{
struct BattlePokemon *moveUser = NULL;
struct BattlePokemon *partner = NULL;
PARAMETRIZE { moveUser = opponentLeft; partner = opponentRight; }
PARAMETRIZE { moveUser = opponentRight; partner = opponentLeft; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(playerLeft, MOVE_TACKLE, target: moveUser);
MOVE(playerRight, MOVE_TACKLE, target: partner);
MOVE(moveUser, MOVE_FOLLOW_ME);
MOVE(partner, MOVE_TACKLE, target: playerLeft); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FOLLOW_ME, moveUser);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft);
HP_BAR(moveUser);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight);
HP_BAR(moveUser);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, partner);
HP_BAR(playerLeft);
}
}
DOUBLE_BATTLE_TEST("Spotlight redirects single target moves used by the opposing side to Spotlight's target")
{
struct BattlePokemon *moveTarget = NULL;
PARAMETRIZE { moveTarget = playerRight; }
PARAMETRIZE { moveTarget = opponentLeft; }
PARAMETRIZE { moveTarget = opponentRight; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(playerLeft, MOVE_SPOTLIGHT, target: moveTarget);
MOVE(playerRight, MOVE_TACKLE, target: opponentRight);
MOVE(opponentLeft, MOVE_TACKLE, target: playerLeft);
MOVE(opponentRight, MOVE_TACKLE, target: playerLeft); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPOTLIGHT, playerLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight);
if (moveTarget != playerRight)
HP_BAR(moveTarget);
else
HP_BAR(opponentRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentLeft);
if (moveTarget == playerRight)
HP_BAR(moveTarget);
else
HP_BAR(playerLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, opponentRight);
if (moveTarget == playerRight)
HP_BAR(moveTarget);
else
HP_BAR(playerLeft);
}
}

View File

@ -217,8 +217,11 @@ DOUBLE_BATTLE_TEST("Instruct-called moves keep their priority")
}
}
DOUBLE_BATTLE_TEST("Instructed move will be absorbed by Lightning Rod if it turns into an Electric Type move")
DOUBLE_BATTLE_TEST("Instructed move will be redirected and absorbed by Lightning Rod if it turns into an Electric Type move")
{
struct BattlePokemon *moveTarget = NULL;
PARAMETRIZE { moveTarget = opponentLeft; }
PARAMETRIZE { moveTarget = opponentRight; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
@ -226,7 +229,7 @@ DOUBLE_BATTLE_TEST("Instructed move will be absorbed by Lightning Rod if it turn
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN {
MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft);
MOVE(playerLeft, MOVE_TACKLE, target: moveTarget);
MOVE(opponentLeft, MOVE_PLASMA_FISTS, target: playerLeft);
MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft);
MOVE(opponentRight, MOVE_CELEBRATE);
@ -239,3 +242,65 @@ DOUBLE_BATTLE_TEST("Instructed move will be absorbed by Lightning Rod if it turn
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft);
}
}
DOUBLE_BATTLE_TEST("Instructed move will be redirected by Follow Me after instructed target loses Stalwart")
{
struct BattlePokemon *moveTarget = NULL;
PARAMETRIZE { moveTarget = opponentLeft; }
PARAMETRIZE { moveTarget = opponentRight; }
GIVEN {
ASSUME(gMovesInfo[MOVE_FOLLOW_ME].effect == EFFECT_FOLLOW_ME);
ASSUME(gMovesInfo[MOVE_SKILL_SWAP].effect == EFFECT_SKILL_SWAP);
PLAYER(SPECIES_DURALUDON) { Ability(ABILITY_STALWART); }
PLAYER(SPECIES_DURALUDON) { Ability(ABILITY_STALWART); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN {
MOVE(playerLeft, MOVE_TACKLE, target: moveTarget);
MOVE(opponentLeft, MOVE_FOLLOW_ME);
MOVE(opponentRight, MOVE_SKILL_SWAP, target: playerLeft);
MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft);
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FOLLOW_ME, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft);
HP_BAR(moveTarget);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponentRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft);
HP_BAR(opponentLeft);
}
}
DOUBLE_BATTLE_TEST("Instructed move will be redirected by Rage Powder after instructed target loses Grass typing")
{
struct BattlePokemon *moveTarget = NULL;
PARAMETRIZE { moveTarget = opponentLeft; }
PARAMETRIZE { moveTarget = opponentRight; }
GIVEN {
ASSUME(gMovesInfo[MOVE_RAGE_POWDER].effect == EFFECT_FOLLOW_ME);
ASSUME(gMovesInfo[MOVE_RAGE_POWDER].powderMove == TRUE);
ASSUME(gMovesInfo[MOVE_SOAK].effect == EFFECT_SOAK);
PLAYER(SPECIES_TREECKO);
PLAYER(SPECIES_SCEPTILE);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN {
MOVE(playerLeft, MOVE_TACKLE, target: moveTarget);
MOVE(opponentLeft, MOVE_RAGE_POWDER);
MOVE(opponentRight, MOVE_SOAK, target: playerLeft);
MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft);
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_RAGE_POWDER, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft);
HP_BAR(moveTarget);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SOAK, opponentRight);
MESSAGE("Treecko transformed into the Water type!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft);
HP_BAR(opponentLeft);
}
}

View File

@ -41,7 +41,6 @@ SINGLE_BATTLE_TEST("Raging Bull doesn't remove Light Screen, Reflect and Aurora
PARAMETRIZE { move = MOVE_REFLECT; }
PARAMETRIZE { move = MOVE_AURORA_VEIL; }
KNOWN_FAILING;
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_GASTLY);

View File

@ -0,0 +1,98 @@
#include "global.h"
#include "test/battle.h"
ASSUMPTIONS
{
ASSUME(gMovesInfo[MOVE_ROTOTILLER].effect == EFFECT_ROTOTILLER);
}
DOUBLE_BATTLE_TEST("Rototiller boosts Attack and Special Attack of all Grass types on the field")
{
GIVEN {
ASSUME(gSpeciesInfo[SPECIES_TANGELA].types[0] == TYPE_GRASS);
ASSUME(gSpeciesInfo[SPECIES_SNIVY].types[0] == TYPE_GRASS);
PLAYER(SPECIES_TANGELA);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_SNIVY);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerRight, MOVE_ROTOTILLER); MOVE(playerLeft, MOVE_CELEBRATE); MOVE(opponentLeft, MOVE_CELEBRATE); MOVE(opponentRight, MOVE_CELEBRATE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_ROTOTILLER, playerRight);
NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft);
NOT ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight);
} THEN {
EXPECT_EQ(playerLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1);
EXPECT_EQ(playerLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1);
EXPECT_EQ(opponentLeft->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1);
EXPECT_EQ(opponentLeft->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1);
EXPECT_EQ(playerRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE);
EXPECT_EQ(playerRight->statStages[STAT_SPATK], DEFAULT_STAT_STAGE);
EXPECT_EQ(opponentRight->statStages[STAT_ATK], DEFAULT_STAT_STAGE);
EXPECT_EQ(opponentRight->statStages[STAT_SPATK], DEFAULT_STAT_STAGE);
}
}
SINGLE_BATTLE_TEST("Rototiller fails if there are no valid targets")
{
GIVEN {
ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[0] != TYPE_GRASS);
ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[1] != TYPE_GRASS);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_ROTOTILLER); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_ROTOTILLER, player);
MESSAGE("Wobbuffet used Rototiller!");
MESSAGE("But it failed!");
}
}
SINGLE_BATTLE_TEST("Rototiller doesn't affect pokemon that are semi-invulnerable")
{
GIVEN {
ASSUME(gSpeciesInfo[SPECIES_TANGELA].types[0] == TYPE_GRASS);
ASSUME(gMovesInfo[MOVE_DIG].effect == EFFECT_SEMI_INVULNERABLE);
PLAYER(SPECIES_TANGELA);
OPPONENT(SPECIES_TANGELA);
} WHEN {
TURN { MOVE(opponent, MOVE_DIG); MOVE(player, MOVE_ROTOTILLER); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_DIG, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_ROTOTILLER, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("It won't have any effect on the opposing Tangela!");
} THEN {
EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1);
EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE + 1);
EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE);
EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE);
}
}
SINGLE_BATTLE_TEST("Rototiller fails if the only valid target is semi-invulnerable")
{
GIVEN {
ASSUME(gSpeciesInfo[SPECIES_TANGELA].types[0] == TYPE_GRASS);
ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[0] != TYPE_GRASS);
ASSUME(gSpeciesInfo[SPECIES_WOBBUFFET].types[1] != TYPE_GRASS);
ASSUME(gMovesInfo[MOVE_DIG].effect == EFFECT_SEMI_INVULNERABLE);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_TANGELA);
} WHEN {
TURN { MOVE(opponent, MOVE_DIG); MOVE(player, MOVE_ROTOTILLER); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_DIG, opponent);
MESSAGE("Wobbuffet used Rototiller!");
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_ROTOTILLER, player);
MESSAGE("But it failed!");
} THEN {
EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE);
EXPECT_EQ(player->statStages[STAT_SPATK], DEFAULT_STAT_STAGE);
EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE);
EXPECT_EQ(opponent->statStages[STAT_SPATK], DEFAULT_STAT_STAGE);
}
}

View File

@ -32,7 +32,6 @@ SINGLE_BATTLE_TEST("Sleep Talk fails if not asleep")
}
}
SINGLE_BATTLE_TEST("Sleep Talk works if user has Comatose")
{
@ -91,3 +90,59 @@ SINGLE_BATTLE_TEST("Sleep Talk can use moves while choiced into Sleep Talk")
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE);
}
}
SINGLE_BATTLE_TEST("Sleep Talk fails if user is taunted")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_TAUNT].effect == EFFECT_TAUNT);
ASSUME(gMovesInfo[MOVE_SLEEP_TALK].category == DAMAGE_CATEGORY_STATUS);
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); Moves(MOVE_SLEEP_TALK, MOVE_TACKLE, MOVE_FLY, MOVE_DIG); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_TAUNT); MOVE(player, MOVE_SLEEP_TALK); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TAUNT, opponent);
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SLEEP_TALK, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player);
}
}
}
DOUBLE_BATTLE_TEST("Sleep Talk calls move and that move may be redirected by Lightning Rod")
{
PASSES_RANDOMLY(1, 2, RNG_RANDOM_TARGET);
GIVEN {
ASSUME(gMovesInfo[MOVE_SPARK].type == TYPE_ELECTRIC);
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); Moves(MOVE_SLEEP_TALK, MOVE_SPARK, MOVE_FLY, MOVE_DIG); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_RAICHU) { Ability(ABILITY_LIGHTNING_ROD); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_SLEEP_TALK); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SLEEP_TALK, playerLeft);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SPARK, playerLeft);
MESSAGE("The opposing Raichu's Lightning Rod took the attack!");
ABILITY_POPUP(opponentRight, ABILITY_LIGHTNING_ROD);
}
}
DOUBLE_BATTLE_TEST("Sleep Talk calls move and that move may be redirected by Storm Drain")
{
PASSES_RANDOMLY(1, 2, RNG_RANDOM_TARGET);
GIVEN {
ASSUME(gMovesInfo[MOVE_WATER_GUN].type == TYPE_WATER);
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); Moves(MOVE_SLEEP_TALK, MOVE_WATER_GUN, MOVE_FLY, MOVE_DIG); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_GASTRODON) { Ability(ABILITY_STORM_DRAIN); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_SLEEP_TALK); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SLEEP_TALK, playerLeft);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, playerLeft);
MESSAGE("The opposing Gastrodon's Storm Drain took the attack!");
ABILITY_POPUP(opponentRight, ABILITY_STORM_DRAIN);
}
}

View File

@ -0,0 +1,59 @@
#include "global.h"
#include "test/battle.h"
ASSUMPTIONS
{
ASSUME(gMovesInfo[MOVE_SNORE].effect == EFFECT_SNORE);
}
SINGLE_BATTLE_TEST("Snore fails if not asleep")
{
u32 status;
PARAMETRIZE { status = STATUS1_SLEEP; }
PARAMETRIZE { status = STATUS1_NONE; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Status1(status); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_SNORE); }
} SCENE {
if (status == STATUS1_SLEEP) {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SNORE, player);
NOT MESSAGE("But it failed!");
}
else {
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SNORE, player);
MESSAGE("But it failed!");
}
}
}
SINGLE_BATTLE_TEST("Snore works if user has Comatose")
{
GIVEN {
PLAYER(SPECIES_KOMALA);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_SNORE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SNORE, player);
NOT MESSAGE("But it failed!");
}
}
SINGLE_BATTLE_TEST("Snore fails if user is throat chopped")
{
GIVEN {
ASSUME(MoveHasAdditionalEffect(MOVE_THROAT_CHOP, MOVE_EFFECT_THROAT_CHOP));
ASSUME(gMovesInfo[MOVE_SNORE].soundMove == TRUE);
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_SLEEP); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_THROAT_CHOP); MOVE(player, MOVE_SNORE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_THROAT_CHOP, opponent);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SNORE, player);
}
}

View File

@ -62,3 +62,18 @@ AI_SINGLE_BATTLE_TEST("AI avoids Thunder Wave when it can not paralyse target")
TURN { SCORE_EQ(opponent, MOVE_CELEBRATE, MOVE_THUNDER_WAVE); } // Both get -10
}
}
SINGLE_BATTLE_TEST("Thunder Wave doesn't affect Electric types in Gen6+")
{
GIVEN {
ASSUME(gSpeciesInfo[SPECIES_PIKACHU].types[0] == TYPE_ELECTRIC);
ASSUME(B_PARALYZE_ELECTRIC >= GEN_6);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_PIKACHU);
} WHEN {
TURN { MOVE(player, MOVE_THUNDER_WAVE); }
} SCENE {
MESSAGE("Wobbuffet used Thunder Wave!");
MESSAGE("It doesn't affect the opposing Pikachu…");
}
}