Stat Change (Animation) Refactor (#7144)

This commit is contained in:
PhallenTree 2025-06-18 13:24:35 +01:00 committed by GitHub
parent ab1838ffd8
commit 340d815c6d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 398 additions and 687 deletions

View File

@ -408,15 +408,12 @@
.4byte \argPtr
.endm
.macro setgraphicalstatchangevalues
.macro unused_0x47
.byte 0x47
.endm
.macro playstatchangeanimation battler:req, stats:req, flags:req
.macro unused_0x48
.byte 0x48
.byte \battler
.byte \stats
.byte \flags
.endm
.macro moveend endMode:req, endState:req
@ -770,10 +767,11 @@
.byte 0x88
.endm
.macro statbuffchange flags:req, failInstr:req
.macro statbuffchange flags:req, failInstr:req, stats=0
.byte 0x89
.2byte \flags
.4byte \failInstr
.byte \stats
.endm
.macro normalisebuffs
@ -2516,11 +2514,10 @@
ANIM_ON = TRUE
setstatchanger \stat, \amount, \mode
statbuffchange STAT_CHANGE_ALLOW_PTR, \script
setgraphicalstatchangevalues
.if \animation == TRUE
playanimation \battler, B_ANIM_STATS_CHANGE, sB_ANIM_ARG1
.if \animation == FALSE
setbyte sSTAT_ANIM_PLAYED, TRUE
.endif
statbuffchange STAT_CHANGE_ALLOW_PTR, \script
.ifnb \customString
printstring \customString
.else

File diff suppressed because it is too large Load Diff

View File

@ -105,8 +105,6 @@ BattleScript_ItemIncreaseStat::
call BattleScript_UseItemMessage
itemincreasestat
statbuffchange MOVE_EFFECT_AFFECTS_USER | STAT_CHANGE_NOT_PROTECT_AFFECTED | STAT_CHANGE_ALLOW_PTR, BattleScript_ItemEnd
setgraphicalstatchangevalues
playanimation BS_ATTACKER, B_ANIM_STATS_CHANGE, sB_ANIM_ARG1
printfromtable gStatUpStringIds
waitmessage B_WAIT_TIME_LONG
end

View File

@ -339,7 +339,6 @@ extern const u8 BattleScript_GrassySurgeActivates[];
extern const u8 BattleScript_MistySurgeActivates[];
extern const u8 BattleScript_ElectricSurgeActivates[];
extern const u8 BattleScript_EffectSpectralThief[];
extern const u8 BattleScript_StatUpMsg[];
extern const u8 BattleScript_AbilityRaisesDefenderStat[];
extern const u8 BattleScript_PowderMoveNoEffect[];
extern const u8 BattleScript_GrassyTerrainHeals[];

View File

@ -223,6 +223,7 @@ void MarkBattlerForControllerExec(u32 battler);
void MarkBattlerReceivedLinkData(u32 battler);
const u8 *CancelMultiTurnMoves(u32 battler, enum SkyDropState skyDropState);
bool32 WasUnableToUseMove(u32 battler);
bool32 ShouldDefiantCompetitiveActivate(u32 battler, u32 ability);
void PrepareStringBattle(enum StringID stringId, u32 battler);
void ResetSentPokesToOpponentValue(void);
void OpponentSwitchInResetSentPokesToOpponentValue(u32 battler);

View File

@ -161,6 +161,7 @@
#define B_MAGIC_GUARD GEN_LATEST // In Gen4 only, Magic Guard ignores immobilization caused by paralysis
#define B_BATTLE_BOND GEN_LATEST // In Gen9+, Battle Bond increases Atk, SpAtk and Speed by one stage, once per battle
#define B_ATE_MULTIPLIER GEN_LATEST // In Gen7+, -ate abilities (Aerilate, Galvanize, Normalize, Pixilate, Refrigerate) multiply damage by 1.2. Otherwise, it's 1.3, except Normalize which has no multiplier.
#define B_DEFIANT_STICKY_WEB GEN_LATEST // In Gen9+, Defiant activates on Sticky Web regardless of who set it up. In Gen8, Defiant does not activate on Sticky Web set up by an ally after Court Change swaps its side.
// Item settings
#define B_HP_BERRIES GEN_LATEST // In Gen4+, berries which restore HP activate immediately after HP drops to half. In Gen3, the effect occurs at the end of the turn.

View File

@ -221,16 +221,11 @@ enum CmdVarious
// Cmd_statbuffchange
#define STAT_CHANGE_ALLOW_PTR (1 << 0) // If set, allow use of jumpptr. If not set and unable to raise/lower stats, jump to failInstr.
#define STAT_CHANGE_MIRROR_ARMOR (1 << 1) // Stat change redirection caused by Mirror Armor ability.
#define STAT_CHANGE_ONLY_CHECKING (1 << 2) // Checks if the stat change can occur. Does not change stats or play stat change animation.
#define STAT_CHANGE_NOT_PROTECT_AFFECTED (1 << 5)
#define STAT_CHANGE_UPDATE_MOVE_EFFECT (1 << 6)
// stat change flags for Cmd_playstatchangeanimation
#define STAT_CHANGE_NEGATIVE (1 << 0)
#define STAT_CHANGE_BY_TWO (1 << 1)
#define STAT_CHANGE_MULTIPLE_STATS (1 << 2)
#define STAT_CHANGE_CANT_PREVENT (1 << 3)
// stat flags for Cmd_playstatchangeanimation
// stat flags for TryPlayStatChangeAnimation
#define BIT_HP (1 << 0)
#define BIT_ATK (1 << 1)
#define BIT_DEF (1 << 2)

View File

@ -17,6 +17,7 @@ enum GenConfigTag
GEN_CONFIG_BATTLE_BOND,
GEN_CONFIG_ATE_MULTIPLIER,
GEN_CONFIG_FELL_STINGER_STAT_RAISE,
GEN_CONFIG_DEFIANT_STICKY_WEB,
GEN_CONFIG_COUNT
};

View File

@ -20,6 +20,7 @@ static const u8 sGenerationalChanges[GEN_CONFIG_COUNT] =
[GEN_CONFIG_BATTLE_BOND] = B_BATTLE_BOND,
[GEN_CONFIG_FELL_STINGER_STAT_RAISE] = B_FELL_STINGER_STAT_RAISE,
[GEN_CONFIG_ATE_MULTIPLIER] = B_ATE_MULTIPLIER,
[GEN_CONFIG_DEFIANT_STICKY_WEB] = B_DEFIANT_STICKY_WEB,
};
#if TESTING

View File

@ -319,7 +319,7 @@ enum GiveCaughtMonStates
#define TAG_LVLUP_BANNER_MON_ICON 55130
static void TrySetDestinyBondToHappen(void);
static u32 ChangeStatBuffs(s8 statValue, u32 statId, u32 flags, const u8 *BS_ptr);
static u32 ChangeStatBuffs(s8 statValue, u32 statId, u32 flags, u32 stats, const u8 *BS_ptr);
static bool32 IsMonGettingExpSentOut(void);
static void InitLevelUpBanner(void);
static bool8 SlideInLevelUpBanner(void);
@ -416,8 +416,8 @@ static void Cmd_jumpifabilitypresent(void);
static void Cmd_endselectionscript(void);
static void Cmd_playanimation(void);
static void Cmd_playanimation_var(void);
static void Cmd_setgraphicalstatchangevalues(void);
static void Cmd_playstatchangeanimation(void);
static void Cmd_unused_0x47(void);
static void Cmd_unused_0x48(void);
static void Cmd_moveend(void);
static void Cmd_sethealblock(void);
static void Cmd_returnatktoball(void);
@ -675,8 +675,8 @@ void (*const gBattleScriptingCommandsTable[])(void) =
Cmd_endselectionscript, //0x44
Cmd_playanimation, //0x45
Cmd_playanimation_var, //0x46
Cmd_setgraphicalstatchangevalues, //0x47
Cmd_playstatchangeanimation, //0x48
Cmd_unused_0x47, //0x47
Cmd_unused_0x48, //0x48
Cmd_moveend, //0x49
Cmd_sethealblock, //0x4A
Cmd_returnatktoball, //0x4B
@ -3499,7 +3499,8 @@ void SetMoveEffect(bool32 primary, bool32 certain)
if (NoAliveMonsForEitherParty()
|| ChangeStatBuffs(SET_STAT_BUFF_VALUE(1),
gBattleScripting.moveEffect - MOVE_EFFECT_ATK_PLUS_1 + 1,
affectsUser | STAT_CHANGE_UPDATE_MOVE_EFFECT, 0) == STAT_CHANGE_DIDNT_WORK)
affectsUser | STAT_CHANGE_UPDATE_MOVE_EFFECT,
0, 0) == STAT_CHANGE_DIDNT_WORK)
{
gBattlescriptCurrInstr++;
}
@ -3529,7 +3530,8 @@ void SetMoveEffect(bool32 primary, bool32 certain)
if (ChangeStatBuffs(SET_STAT_BUFF_VALUE(1) | STAT_BUFF_NEGATIVE,
gBattleScripting.moveEffect - MOVE_EFFECT_ATK_MINUS_1 + 1,
flags, gBattlescriptCurrInstr + 1) == STAT_CHANGE_DIDNT_WORK)
flags,
0, gBattlescriptCurrInstr + 1) == STAT_CHANGE_DIDNT_WORK)
{
if (!mirrorArmorReflected)
gBattlescriptCurrInstr++;
@ -3552,7 +3554,8 @@ void SetMoveEffect(bool32 primary, bool32 certain)
if (NoAliveMonsForEitherParty()
|| ChangeStatBuffs(SET_STAT_BUFF_VALUE(2),
gBattleScripting.moveEffect - MOVE_EFFECT_ATK_PLUS_2 + 1,
affectsUser | STAT_CHANGE_UPDATE_MOVE_EFFECT, 0) == STAT_CHANGE_DIDNT_WORK)
affectsUser | STAT_CHANGE_UPDATE_MOVE_EFFECT,
0, 0) == STAT_CHANGE_DIDNT_WORK)
{
gBattlescriptCurrInstr++;
}
@ -3582,7 +3585,8 @@ void SetMoveEffect(bool32 primary, bool32 certain)
if (ChangeStatBuffs(SET_STAT_BUFF_VALUE(2) | STAT_BUFF_NEGATIVE,
gBattleScripting.moveEffect - MOVE_EFFECT_ATK_MINUS_2 + 1,
flags, gBattlescriptCurrInstr + 1) == STAT_CHANGE_DIDNT_WORK)
flags,
0, gBattlescriptCurrInstr + 1) == STAT_CHANGE_DIDNT_WORK)
{
if (!mirrorArmorReflected)
gBattlescriptCurrInstr++;
@ -4003,6 +4007,7 @@ void SetMoveEffect(bool32 primary, bool32 certain)
|| ChangeStatBuffs(SET_STAT_BUFF_VALUE(1),
stat,
affectsUser | STAT_CHANGE_UPDATE_MOVE_EFFECT,
0,
0) == STAT_CHANGE_DIDNT_WORK)
{
gBattlescriptCurrInstr++;
@ -5876,171 +5881,12 @@ static void Cmd_playanimation_var(void)
PlayAnimation(battler, *(cmd->animIdPtr), cmd->argPtr, cmd->nextInstr);
}
static void Cmd_setgraphicalstatchangevalues(void)
static void Cmd_unused_0x47(void)
{
CMD_ARGS();
u8 value = GET_STAT_BUFF_VALUE_WITH_SIGN(gBattleScripting.statChanger);
switch (value)
{
case SET_STAT_BUFF_VALUE(1): // +1
value = STAT_ANIM_PLUS1 + 1;
break;
case SET_STAT_BUFF_VALUE(2): // +2
value = STAT_ANIM_PLUS2 + 1;
break;
case SET_STAT_BUFF_VALUE(3): // +3
value = STAT_ANIM_PLUS2 + 1;
break;
case SET_STAT_BUFF_VALUE(1) | STAT_BUFF_NEGATIVE: // -1
value = STAT_ANIM_MINUS1 + 1;
break;
case SET_STAT_BUFF_VALUE(2) | STAT_BUFF_NEGATIVE: // -2
value = STAT_ANIM_MINUS2 + 1;
break;
case SET_STAT_BUFF_VALUE(3) | STAT_BUFF_NEGATIVE: // -3
value = STAT_ANIM_MINUS2 + 1;
break;
default: // <-12,-4> and <4, 12>
if (value & STAT_BUFF_NEGATIVE)
value = STAT_ANIM_MINUS2 + 1;
else
value = STAT_ANIM_PLUS2 + 1;
break;
}
gBattleScripting.animArg1 = GET_STAT_BUFF_ID(gBattleScripting.statChanger) + value - 1;
gBattleScripting.animArg2 = 0;
gBattlescriptCurrInstr = cmd->nextInstr;
}
static void Cmd_playstatchangeanimation(void)
static void Cmd_unused_0x48(void)
{
CMD_ARGS(u8 battler, u8 stats, u8 flags);
u32 currStat = 0;
u32 statAnimId = 0;
u32 changeableStatsCount = 0;
u32 startingStatAnimId = 0;
u32 flags = cmd->flags;
u32 battler = GetBattlerForBattleScript(cmd->battler);
u32 ability = GetBattlerAbility(battler);
u32 stats = cmd->stats;
bool32 defiantCompetitiveAffected = FALSE;
if (gBattleScripting.statAnimPlayed)
{
gBattlescriptCurrInstr = cmd->nextInstr;
return;
}
// Handle Contrary and Simple
if (ability == ABILITY_CONTRARY)
{
flags ^= STAT_CHANGE_NEGATIVE;
RecordAbilityBattle(battler, ability);
}
else if (ability == ABILITY_SIMPLE)
{
flags |= STAT_CHANGE_BY_TWO;
RecordAbilityBattle(battler, ability);
}
else if (ability == ABILITY_DEFIANT || ability == ABILITY_COMPETITIVE)
{
defiantCompetitiveAffected = TRUE;
}
if (flags & STAT_CHANGE_NEGATIVE) // goes down
{
if (flags & STAT_CHANGE_BY_TWO)
startingStatAnimId = STAT_ANIM_MINUS2;
else
startingStatAnimId = STAT_ANIM_MINUS1;
while (stats != 0)
{
if (stats & 1)
{
if (flags & STAT_CHANGE_CANT_PREVENT)
{
if (gBattleMons[battler].statStages[currStat] > MIN_STAT_STAGE)
{
statAnimId = startingStatAnimId + currStat;
changeableStatsCount++;
}
}
else if (!gSideTimers[GetBattlerSide(battler)].mistTimer
&& GetBattlerHoldEffect(battler, TRUE) != HOLD_EFFECT_CLEAR_AMULET
&& ability != ABILITY_CLEAR_BODY
&& ability != ABILITY_FULL_METAL_BODY
&& ability != ABILITY_WHITE_SMOKE
&& !((ability == ABILITY_KEEN_EYE || ability == ABILITY_MINDS_EYE) && currStat == STAT_ACC)
&& !(B_ILLUMINATE_EFFECT >= GEN_9 && ability == ABILITY_ILLUMINATE && currStat == STAT_ACC)
&& !(ability == ABILITY_HYPER_CUTTER && currStat == STAT_ATK)
&& !(ability == ABILITY_BIG_PECKS && currStat == STAT_DEF))
{
if (gBattleMons[battler].statStages[currStat] > MIN_STAT_STAGE)
{
statAnimId = startingStatAnimId + currStat;
changeableStatsCount++;
if (defiantCompetitiveAffected) // Force single stat animations
break;
}
}
}
stats >>= 1, currStat++;
}
if (changeableStatsCount > 1) // more than one stat, so the color is gray
{
if (flags & STAT_CHANGE_BY_TWO)
statAnimId = STAT_ANIM_MULTIPLE_MINUS2;
else
statAnimId = STAT_ANIM_MULTIPLE_MINUS1;
}
}
else // goes up
{
if (flags & STAT_CHANGE_BY_TWO)
startingStatAnimId = STAT_ANIM_PLUS2;
else
startingStatAnimId = STAT_ANIM_PLUS1;
while (stats != 0)
{
if (stats & 1 && gBattleMons[battler].statStages[currStat] < MAX_STAT_STAGE)
{
statAnimId = startingStatAnimId + currStat;
changeableStatsCount++;
}
stats >>= 1, currStat++;
}
if (changeableStatsCount > 1) // more than one stat, so the color is gray
{
if (flags & STAT_CHANGE_BY_TWO)
statAnimId = STAT_ANIM_MULTIPLE_PLUS2;
else
statAnimId = STAT_ANIM_MULTIPLE_PLUS1;
}
}
if (flags & STAT_CHANGE_MULTIPLE_STATS && changeableStatsCount < 2)
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
else if (changeableStatsCount != 0)
{
BtlController_EmitBattleAnimation(battler, B_COMM_TO_CONTROLLER, B_ANIM_STATS_CHANGE, &gDisableStructs[battler], statAnimId);
MarkBattlerForControllerExec(battler);
if (flags & STAT_CHANGE_MULTIPLE_STATS && changeableStatsCount > 1)
gBattleScripting.statAnimPlayed = TRUE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
else
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
static bool32 TryKnockOffBattleScript(u32 battlerDef)
@ -12167,14 +12013,103 @@ static u16 ReverseStatChangeMoveEffect(u16 moveEffect)
}
}
static u32 ChangeStatBuffs(s8 statValue, u32 statId, u32 flags, const u8 *BS_ptr)
static void TryPlayStatChangeAnimation(u32 battler, u32 ability, u32 stats, s32 statValue, u32 statId, bool32 certain)
{
u32 currStat = 0;
u32 changeableStatsCount = 1; // current stat is counted automatically
u32 statAnimId = statId;
bool32 statChangeByTwo = statValue > 1 || statValue < -1;
if (statValue <= -1) // goes down
{
if (statChangeByTwo)
statAnimId += STAT_ANIM_MINUS2;
else
statAnimId += STAT_ANIM_MINUS1;
while (stats != 0)
{
if (stats & 1)
{
if (certain)
{
if (gBattleMons[battler].statStages[currStat] > MIN_STAT_STAGE)
{
changeableStatsCount++;
break;
}
}
else if (!((ability == ABILITY_KEEN_EYE || ability == ABILITY_MINDS_EYE) && currStat == STAT_ACC)
&& !(B_ILLUMINATE_EFFECT >= GEN_9 && ability == ABILITY_ILLUMINATE && currStat == STAT_ACC)
&& !(ability == ABILITY_HYPER_CUTTER && currStat == STAT_ATK)
&& !(ability == ABILITY_BIG_PECKS && currStat == STAT_DEF))
{
if (gBattleMons[battler].statStages[currStat] > MIN_STAT_STAGE)
{
changeableStatsCount++;
break;
}
}
}
stats >>= 1, currStat++;
}
if (changeableStatsCount > 1) // more than one stat, so the color is gray
{
if (statChangeByTwo)
statAnimId = STAT_ANIM_MULTIPLE_MINUS2;
else
statAnimId = STAT_ANIM_MULTIPLE_MINUS1;
}
}
else // goes up
{
if (statChangeByTwo)
statAnimId += STAT_ANIM_PLUS2;
else
statAnimId += STAT_ANIM_PLUS1;
while (stats != 0)
{
if (stats & 1 && gBattleMons[battler].statStages[currStat] < MAX_STAT_STAGE)
{
changeableStatsCount++;
break;
}
stats >>= 1, currStat++;
}
if (changeableStatsCount > 1) // more than one stat, so the color is gray
{
if (statChangeByTwo)
statAnimId = STAT_ANIM_MULTIPLE_PLUS2;
else
statAnimId = STAT_ANIM_MULTIPLE_PLUS1;
}
}
if (!gBattleScripting.statAnimPlayed)
{
BtlController_EmitBattleAnimation(battler, B_COMM_TO_CONTROLLER, B_ANIM_STATS_CHANGE, &gDisableStructs[battler], statAnimId);
MarkBattlerForControllerExec(battler);
if (changeableStatsCount > 1)
gBattleScripting.statAnimPlayed = TRUE;
}
else if (changeableStatsCount == 1) // final stat that can be changed
{
gBattleScripting.statAnimPlayed = FALSE;
}
}
static u32 ChangeStatBuffs(s8 statValue, u32 statId, u32 flags, u32 stats, const u8 *BS_ptr)
{
bool32 certain = FALSE;
bool32 notProtectAffected = FALSE;
u32 index, battler, battlerAbility;
enum ItemHoldEffect battlerHoldEffect;
bool32 certain = (flags & MOVE_EFFECT_CERTAIN);
bool32 notProtectAffected = (flags & STAT_CHANGE_NOT_PROTECT_AFFECTED);
bool32 affectsUser = (flags & MOVE_EFFECT_AFFECTS_USER);
bool32 mirrorArmored = (flags & STAT_CHANGE_MIRROR_ARMOR);
bool32 onlyChecking = (flags & STAT_CHANGE_ONLY_CHECKING);
if (affectsUser)
battler = gBattlerAttacker;
@ -12186,30 +12121,26 @@ static u32 ChangeStatBuffs(s8 statValue, u32 statId, u32 flags, const u8 *BS_ptr
gSpecialStatuses[battler].changedStatsBattlerId = gBattlerAttacker;
flags &= ~(MOVE_EFFECT_AFFECTS_USER | STAT_CHANGE_MIRROR_ARMOR);
if (flags & MOVE_EFFECT_CERTAIN)
certain = TRUE;
flags &= ~MOVE_EFFECT_CERTAIN;
if (flags & STAT_CHANGE_NOT_PROTECT_AFFECTED)
notProtectAffected++;
flags &= ~STAT_CHANGE_NOT_PROTECT_AFFECTED;
flags &= ~(STAT_CHANGE_ONLY_CHECKING | STAT_CHANGE_MIRROR_ARMOR | STAT_CHANGE_NOT_PROTECT_AFFECTED);
if (battlerAbility == ABILITY_CONTRARY)
{
statValue ^= STAT_BUFF_NEGATIVE;
gBattleScripting.statChanger ^= STAT_BUFF_NEGATIVE;
RecordAbilityBattle(battler, battlerAbility);
if (flags & STAT_CHANGE_UPDATE_MOVE_EFFECT)
if (!onlyChecking)
{
flags &= ~STAT_CHANGE_UPDATE_MOVE_EFFECT;
gBattleScripting.moveEffect = ReverseStatChangeMoveEffect(gBattleScripting.moveEffect);
gBattleScripting.statChanger ^= STAT_BUFF_NEGATIVE;
RecordAbilityBattle(battler, battlerAbility);
if (flags & STAT_CHANGE_UPDATE_MOVE_EFFECT)
{
flags &= ~STAT_CHANGE_UPDATE_MOVE_EFFECT;
gBattleScripting.moveEffect = ReverseStatChangeMoveEffect(gBattleScripting.moveEffect);
}
}
}
else if (battlerAbility == ABILITY_SIMPLE)
else if (battlerAbility == ABILITY_SIMPLE && !onlyChecking)
{
statValue = (SET_STAT_BUFF_VALUE(GET_STAT_BUFF_VALUE(statValue) * 2)) | ((statValue <= -1) ? STAT_BUFF_NEGATIVE : 0);
RecordAbilityBattle(battler, battlerAbility);
}
PREPARE_STAT_BUFFER(gBattleTextBuff1, statId);
@ -12220,7 +12151,7 @@ static u32 ChangeStatBuffs(s8 statValue, u32 statId, u32 flags, const u8 *BS_ptr
&& !certain && gCurrentMove != MOVE_CURSE
&& !(battler == gBattlerTarget && GetBattlerAbility(gBattlerAttacker) == ABILITY_INFILTRATOR))
{
if (flags == STAT_CHANGE_ALLOW_PTR)
if (flags & STAT_CHANGE_ALLOW_PTR)
{
if (gSpecialStatuses[battler].statLowered)
{
@ -12244,7 +12175,7 @@ static u32 ChangeStatBuffs(s8 statValue, u32 statId, u32 flags, const u8 *BS_ptr
else if ((battlerHoldEffect == HOLD_EFFECT_CLEAR_AMULET || CanAbilityPreventStatLoss(battlerAbility))
&& (!affectsUser || mirrorArmored) && !certain && gCurrentMove != MOVE_CURSE)
{
if (flags == STAT_CHANGE_ALLOW_PTR)
if (flags & STAT_CHANGE_ALLOW_PTR)
{
if (gSpecialStatuses[battler].statLowered)
{
@ -12275,7 +12206,7 @@ static u32 ChangeStatBuffs(s8 statValue, u32 statId, u32 flags, const u8 *BS_ptr
}
else if ((index = IsFlowerVeilProtected(battler)) && !certain)
{
if (flags == STAT_CHANGE_ALLOW_PTR)
if (flags & STAT_CHANGE_ALLOW_PTR)
{
if (gSpecialStatuses[battler].statLowered)
{
@ -12299,7 +12230,7 @@ static u32 ChangeStatBuffs(s8 statValue, u32 statId, u32 flags, const u8 *BS_ptr
|| (battlerAbility == ABILITY_HYPER_CUTTER && statId == STAT_ATK)
|| (battlerAbility == ABILITY_BIG_PECKS && statId == STAT_DEF)))
{
if (flags == STAT_CHANGE_ALLOW_PTR)
if (flags & STAT_CHANGE_ALLOW_PTR)
{
BattleScriptPush(BS_ptr);
gBattleScripting.battler = battler;
@ -12312,7 +12243,7 @@ static u32 ChangeStatBuffs(s8 statValue, u32 statId, u32 flags, const u8 *BS_ptr
}
else if (battlerAbility == ABILITY_MIRROR_ARMOR && !affectsUser && !mirrorArmored && gBattlerAttacker != gBattlerTarget && battler == gBattlerTarget)
{
if (flags == STAT_CHANGE_ALLOW_PTR)
if (flags & STAT_CHANGE_ALLOW_PTR)
{
SET_STATCHANGER(statId, GET_STAT_BUFF_VALUE(statValue) | STAT_BUFF_NEGATIVE, TRUE);
BattleScriptPush(BS_ptr);
@ -12351,15 +12282,16 @@ static u32 ChangeStatBuffs(s8 statValue, u32 statId, u32 flags, const u8 *BS_ptr
gBattleTextBuff2[index++] = STRINGID_STATFELL >> 8;
gBattleTextBuff2[index] = B_BUFF_EOS;
gBattleCommunication[MULTISTRING_CHOOSER] = (gBattlerTarget == battler); // B_MSG_ATTACKER_STAT_FELL or B_MSG_DEFENDER_STAT_FELL
if (gBattleMons[battler].statStages[statId] == MIN_STAT_STAGE)
{
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_STAT_WONT_DECREASE;
}
else
else if (!onlyChecking)
{
gProtectStructs[battler].statFell = TRUE;
gProtectStructs[battler].lashOutAffected = TRUE;
gBattleCommunication[MULTISTRING_CHOOSER] = (gBattlerTarget == battler); // B_MSG_ATTACKER_STAT_FELL or B_MSG_DEFENDER_STAT_FELL
}
}
}
@ -12391,11 +12323,13 @@ static u32 ChangeStatBuffs(s8 statValue, u32 statId, u32 flags, const u8 *BS_ptr
gBattleTextBuff2[index++] = STRINGID_STATROSE >> 8;
gBattleTextBuff2[index] = B_BUFF_EOS;
gBattleCommunication[MULTISTRING_CHOOSER] = (gBattlerTarget == battler); // B_MSG_ATTACKER_STAT_ROSE or B_MSG_DEFENDER_STAT_ROSE
if (gBattleMons[battler].statStages[statId] == MAX_STAT_STAGE)
{
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_STAT_WONT_INCREASE;
}
else
else if (!onlyChecking)
{
u32 statIncrease;
if ((statValue + gBattleMons[battler].statStages[statId]) > MAX_STAT_STAGE)
@ -12403,29 +12337,31 @@ static u32 ChangeStatBuffs(s8 statValue, u32 statId, u32 flags, const u8 *BS_ptr
else
statIncrease = statValue;
gBattleCommunication[MULTISTRING_CHOOSER] = (gBattlerTarget == battler);
gProtectStructs[battler].statRaised = TRUE;
// Check Mirror Herb / Opportunist
for (index = 0; index < gBattlersCount; index++)
if (statIncrease)
{
if (IsBattlerAlly(index, battler))
continue; // Only triggers on opposing side
// Check Mirror Herb / Opportunist
for (index = 0; index < gBattlersCount; index++)
{
if (IsBattlerAlly(index, battler))
continue; // Only triggers on opposing side
if (GetBattlerAbility(index) == ABILITY_OPPORTUNIST
&& gProtectStructs[battler].activateOpportunist == 0) // don't activate opportunist on other mon's opportunist raises
{
gProtectStructs[index].activateOpportunist = 2; // set stats to copy
}
if (GetBattlerHoldEffect(index, TRUE) == HOLD_EFFECT_MIRROR_HERB)
{
gProtectStructs[index].eatMirrorHerb = 1;
}
if (GetBattlerAbility(index) == ABILITY_OPPORTUNIST
&& gProtectStructs[battler].activateOpportunist == 0) // don't activate opportunist on other mon's opportunist raises
{
gProtectStructs[index].activateOpportunist = 2; // set stats to copy
}
if (GetBattlerHoldEffect(index, TRUE) == HOLD_EFFECT_MIRROR_HERB)
{
gProtectStructs[index].eatMirrorHerb = 1;
}
if (gProtectStructs[index].activateOpportunist == 2 || gProtectStructs[index].eatMirrorHerb == 1)
{
gQueuedStatBoosts[index].stats |= (1 << (statId - 1)); // -1 to start at atk
gQueuedStatBoosts[index].statChanges[statId - 1] += statIncrease;
if (gProtectStructs[index].activateOpportunist == 2 || gProtectStructs[index].eatMirrorHerb == 1)
{
gQueuedStatBoosts[index].stats |= (1 << (statId - 1)); // -1 to start at atk
gQueuedStatBoosts[index].statChanges[statId - 1] += statIncrease;
}
}
}
}
@ -12439,26 +12375,38 @@ static u32 ChangeStatBuffs(s8 statValue, u32 statId, u32 flags, const u8 *BS_ptr
return STAT_CHANGE_WORKED;
}
if (onlyChecking)
return STAT_CHANGE_WORKED;
gBattleMons[battler].statStages[statId] += statValue;
if (gBattleMons[battler].statStages[statId] < MIN_STAT_STAGE)
gBattleMons[battler].statStages[statId] = MIN_STAT_STAGE;
if (gBattleMons[battler].statStages[statId] > MAX_STAT_STAGE)
gBattleMons[battler].statStages[statId] = MAX_STAT_STAGE;
if (ShouldDefiantCompetitiveActivate(battler, battlerAbility))
stats = 0; // use single stat animations when Defiant/Competitive activate
else
stats &= ~(1u << statId);
TryPlayStatChangeAnimation(battler, battlerAbility, stats, statValue, statId, certain);
return STAT_CHANGE_WORKED;
}
static void Cmd_statbuffchange(void)
{
CMD_ARGS(u16 flags, const u8 *failInstr);
CMD_ARGS(u16 flags, const u8 *failInstr, u8 stats);
u16 flags = cmd->flags;
u32 stats = cmd->stats;
const u8 *ptrBefore = gBattlescriptCurrInstr;
const u8 *failInstr = cmd->failInstr;
if (ChangeStatBuffs(GET_STAT_BUFF_VALUE_WITH_SIGN(gBattleScripting.statChanger),
GET_STAT_BUFF_ID(gBattleScripting.statChanger),
flags,
stats,
failInstr) == STAT_CHANGE_WORKED)
gBattlescriptCurrInstr = cmd->nextInstr;
else if (gBattlescriptCurrInstr == ptrBefore) // Prevent infinite looping.
@ -18234,9 +18182,10 @@ void BS_SpectralThiefPrintStats(void)
SET_STATCHANGER(stat, gBattleStruct->stolenStats[stat], FALSE);
if (ChangeStatBuffs(GET_STAT_BUFF_VALUE_WITH_SIGN(gBattleScripting.statChanger),
stat,
MOVE_EFFECT_CERTAIN | MOVE_EFFECT_AFFECTS_USER, NULL) == STAT_CHANGE_WORKED)
MOVE_EFFECT_CERTAIN | MOVE_EFFECT_AFFECTS_USER,
0, NULL) == STAT_CHANGE_WORKED)
{
BattleScriptCall(BattleScript_StatUpMsg);
BattleScriptCall(BattleScript_StatUp);
return;
}
}

View File

@ -1144,9 +1144,23 @@ bool32 WasUnableToUseMove(u32 battler)
return FALSE;
}
bool32 ShouldDefiantCompetitiveActivate(u32 battler, u32 ability)
{
u32 side = GetBattlerSide(battler);
if (ability != ABILITY_DEFIANT && ability != ABILITY_COMPETITIVE)
return FALSE;
// if an ally dropped the stats (except for Sticky Web), don't activate
if (IsBattlerAlly(gSpecialStatuses[battler].changedStatsBattlerId, battler) && !gBattleScripting.stickyWebStatDrop)
return FALSE;
if (GetGenConfig(GEN_CONFIG_DEFIANT_STICKY_WEB) >= GEN_9 || !gBattleScripting.stickyWebStatDrop)
return TRUE;
// only activate Defiant/Competitive if Web was setup by foe
return gSideTimers[side].stickyWebBattlerSide != side;
}
void PrepareStringBattle(enum StringID stringId, u32 battler)
{
u32 targetSide = GetBattlerSide(gBattlerTarget);
u16 battlerAbility = GetBattlerAbility(battler);
u16 targetAbility = GetBattlerAbility(gBattlerTarget);
// Support for Contrary ability.
@ -1164,10 +1178,7 @@ void PrepareStringBattle(enum StringID stringId, u32 battler)
// Check Defiant and Competitive stat raise whenever a stat is lowered.
else if ((stringId == STRINGID_DEFENDERSSTATFELL || stringId == STRINGID_PKMNCUTSATTACKWITH)
&& (targetAbility == ABILITY_DEFIANT || targetAbility == ABILITY_COMPETITIVE)
&& gSpecialStatuses[gBattlerTarget].changedStatsBattlerId != BATTLE_PARTNER(gBattlerTarget)
&& ((gSpecialStatuses[gBattlerTarget].changedStatsBattlerId != gBattlerTarget) || gBattleScripting.stickyWebStatDrop == 1)
&& !(gBattleScripting.stickyWebStatDrop == 1 && gSideTimers[targetSide].stickyWebBattlerSide == targetSide)) // Sticky Web must have been set by the foe
&& ShouldDefiantCompetitiveActivate(gBattlerTarget, targetAbility))
{
gBattlerAbility = gBattlerTarget;
BattleScriptCall(BattleScript_AbilityRaisesDefenderStat);

View File

@ -19,13 +19,16 @@ SINGLE_BATTLE_TEST("Contrary raises Attack when Intimidated in a single battle",
} SCENE {
ABILITY_POPUP(player, ABILITY_INTIMIDATE);
if (ability == ABILITY_CONTRARY) {
ABILITY_POPUP(opponent, ABILITY_CONTRARY);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
MESSAGE("The opposing Spinda's Attack rose!");
} else {
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
MESSAGE("Mightyena's Intimidate cuts the opposing Spinda's Attack!");
}
HP_BAR(player, captureDamage: &results[i].damage);
}
FINALLY {
} THEN {
EXPECT_EQ(opponent->statStages[STAT_ATK], (ability == ABILITY_CONTRARY) ? DEFAULT_STAT_STAGE + 1 : DEFAULT_STAT_STAGE - 1);
} FINALLY {
EXPECT_MUL_EQ(results[1].damage, Q_4_12(2.25), results[0].damage);
}
}
@ -49,7 +52,6 @@ DOUBLE_BATTLE_TEST("Contrary raises Attack when Intimidated in a double battle",
} SCENE {
ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE);
if (abilityLeft == ABILITY_CONTRARY) {
ABILITY_POPUP(opponentLeft, ABILITY_CONTRARY);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft);
MESSAGE("The opposing Spinda's Attack rose!");
} else {
@ -57,7 +59,6 @@ DOUBLE_BATTLE_TEST("Contrary raises Attack when Intimidated in a double battle",
MESSAGE("Mightyena's Intimidate cuts the opposing Spinda's Attack!");
}
if (abilityRight == ABILITY_CONTRARY) {
ABILITY_POPUP(opponentRight, ABILITY_CONTRARY);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight);
MESSAGE("The opposing Spinda's Attack rose!");
} else {
@ -69,8 +70,7 @@ DOUBLE_BATTLE_TEST("Contrary raises Attack when Intimidated in a double battle",
} THEN {
EXPECT_EQ(opponentLeft->statStages[STAT_ATK], (abilityLeft == ABILITY_CONTRARY) ? DEFAULT_STAT_STAGE+1 : DEFAULT_STAT_STAGE-1);
EXPECT_EQ(opponentRight->statStages[STAT_ATK], (abilityRight == ABILITY_CONTRARY) ? DEFAULT_STAT_STAGE+1 : DEFAULT_STAT_STAGE-1);
}
FINALLY {
} FINALLY {
EXPECT_MUL_EQ(results[1].damageLeft, Q_4_12(2.25), results[0].damageLeft);
EXPECT_MUL_EQ(results[1].damageRight, Q_4_12(2.25), results[0].damageRight);
}

View File

@ -139,15 +139,19 @@ SINGLE_BATTLE_TEST("Defiant activates after Sticky Web lowers Speed")
}
}
SINGLE_BATTLE_TEST("Defiant doesn't activate after Sticky Web lowers Speed if Court Changed")
SINGLE_BATTLE_TEST("Defiant doesn't activate after Sticky Web lowers Speed if Court Changed (Gen8)")
{
GIVEN {
WITH_CONFIG(GEN_CONFIG_DEFIANT_STICKY_WEB, GEN_8);
ASSUME(GetMoveEffect(MOVE_GROWL) == EFFECT_ATTACK_DOWN);
ASSUME(GetMoveEffect(MOVE_STICKY_WEB) == EFFECT_STICKY_WEB);
ASSUME(GetMoveEffect(MOVE_COURT_CHANGE) == EFFECT_COURT_CHANGE);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_MANKEY) { Ability(ABILITY_DEFIANT); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_STICKY_WEB); MOVE(opponent, MOVE_COURT_CHANGE); }
TURN { SWITCH(player, 1); }
TURN { SWITCH(player, 1); MOVE(opponent, MOVE_GROWL);}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_COURT_CHANGE, opponent);
@ -162,19 +166,29 @@ SINGLE_BATTLE_TEST("Defiant doesn't activate after Sticky Web lowers Speed if Co
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("Mankey's Attack sharply rose!");
}
// Defiant triggers correctly after Sticky Web
ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, opponent);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("Mankey's Attack fell!");
ABILITY_POPUP(player, ABILITY_DEFIANT);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("Mankey's Attack sharply rose!");
}
}
SINGLE_BATTLE_TEST("Defiant correctly activates after Sticky Web lowers Speed if Court Changed")
SINGLE_BATTLE_TEST("Defiant activates after Sticky Web lowers Speed if Court Changed (Gen9)")
{
GIVEN {
WITH_CONFIG(GEN_CONFIG_DEFIANT_STICKY_WEB, GEN_9);
ASSUME(GetMoveEffect(MOVE_GROWL) == EFFECT_ATTACK_DOWN);
ASSUME(GetMoveEffect(MOVE_STICKY_WEB) == EFFECT_STICKY_WEB);
ASSUME(GetMoveEffect(MOVE_COURT_CHANGE) == EFFECT_COURT_CHANGE);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_MANKEY) { Ability(ABILITY_DEFIANT); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_STICKY_WEB); MOVE(opponent, MOVE_COURT_CHANGE); }
TURN { SWITCH(player, 1); }
TURN { MOVE(opponent, MOVE_GROWL);}
TURN { SWITCH(player, 1); MOVE(opponent, MOVE_GROWL);}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_STICKY_WEB, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_COURT_CHANGE, opponent);
@ -183,12 +197,9 @@ SINGLE_BATTLE_TEST("Defiant correctly activates after Sticky Web lowers Speed if
MESSAGE("Mankey was caught in a sticky web!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("Mankey's Speed fell!");
// Defiant doesn't activate
NONE_OF {
ABILITY_POPUP(player, ABILITY_DEFIANT);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("Mankey's Attack sharply rose!");
}
ABILITY_POPUP(player, ABILITY_DEFIANT);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
MESSAGE("Mankey's Attack sharply rose!");
// Defiant triggers correctly after Sticky Web
ANIMATION(ANIM_TYPE_MOVE, MOVE_GROWL, opponent);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);

View File

@ -59,7 +59,6 @@ DOUBLE_BATTLE_TEST("Opportunist raises Attack only once when partner has Intimid
} SCENE {
ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE);
if (abilityLeft == ABILITY_CONTRARY) {
ABILITY_POPUP(opponentLeft, ABILITY_CONTRARY);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft);
MESSAGE("The opposing Spinda's Attack rose!");
} else {
@ -67,7 +66,6 @@ DOUBLE_BATTLE_TEST("Opportunist raises Attack only once when partner has Intimid
MESSAGE("Mightyena's Intimidate cuts the opposing Spinda's Attack!");
}
if (abilityRight == ABILITY_CONTRARY) {
ABILITY_POPUP(opponentRight, ABILITY_CONTRARY);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight);
MESSAGE("The opposing Spinda's Attack rose!");
} else {

View File

@ -77,7 +77,7 @@ SINGLE_BATTLE_TEST("Weak Armor does not trigger when brought in by Dragon Tail a
}
}
SINGLE_BATTLE_TEST("Weak Armor still lowers boosts Speed if Defense can't go any lower")
SINGLE_BATTLE_TEST("Weak Armor still boosts Speed if Defense can't go any lower")
{
GIVEN {
PLAYER(SPECIES_SLUGMA) { Ability(ABILITY_WEAK_ARMOR); }

View File

@ -1154,6 +1154,55 @@ DOUBLE_BATTLE_TEST("Move Animations don't leak when used - Doubles (opponentRigh
}
*/
SINGLE_BATTLE_TEST("Move Animations occur before their stat change animations - Singles (player to opponent)")
{
u32 j = ANIM_TEST_START_MOVE, move = 0, species = 0;
u32 tempMove, tempSpecies;
FORCE_MOVE_ANIM(TRUE);
for (; j <= ANIM_TEST_END_MOVE; j++) {
ParametrizeMovesAndSpecies(j, &tempMove, &tempSpecies);
PARAMETRIZE { move = tempMove; species = tempSpecies; }
}
GIVEN {
PLAYER(species) {
HP(9997); MaxHP(9999); Item(ITEM_ORAN_BERRY);
if (species == SPECIES_WOBBUFFET) Gender(MON_FEMALE);
if (gMovesInfo[move].effect == EFFECT_LAST_RESORT) Moves(move, MOVE_POUND);
if (species == SPECIES_KLINKLANG) Ability(ABILITY_PLUS);
}
PLAYER(SPECIES_WOBBUFFET) {
Gender(MON_MALE); MaxHP(9999); Moves(MOVE_POUND);
HP(gMovesInfo[move].effect == EFFECT_REVIVAL_BLESSING ? 0 : 9998);
}
OPPONENT(SPECIES_WOBBUFFET) {
Gender(MON_MALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); Ability(ABILITY_TELEPATHY);
if (gMovesInfo[move].effect != EFFECT_BESTOW)
Item(ITEM_ORAN_BERRY);
}
OPPONENT(SPECIES_WOBBUFFET) { Gender(MON_FEMALE); HP(9998); MaxHP(9999); SpDefense(9999); Defense(9999); }
} WHEN {
WhenSingles(move, player, opponent);
} SCENE {
if (!(gMovesInfo[move].effect == EFFECT_RECYCLE
|| gMovesInfo[move].effect == EFFECT_BELCH
|| gMovesInfo[move].effect == EFFECT_SPIT_UP
|| gMovesInfo[move].effect == EFFECT_SWALLOW
|| gMovesInfo[move].effect == EFFECT_TOPSY_TURVY)) // require a move that boosts stats before using this move
{
NONE_OF {
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
}
}
SceneSingles(move, player);
} THEN {
FORCE_MOVE_ANIM(FALSE);
if (gLoadFail)
DebugPrintf("Move failed: %S (%u)", gMovesInfo[move].name, move);
EXPECT_EQ(gLoadFail, FALSE);
}
}
// Z-Moves
#define Z_MOVE_PARAMETERS PARAMETRIZE { zmove = MOVE_BREAKNECK_BLITZ; species = SPECIES_WOBBUFFET; move = MOVE_TACKLE; item = ITEM_NORMALIUM_Z; } \
PARAMETRIZE { zmove = MOVE_INFERNO_OVERDRIVE; species = SPECIES_WOBBUFFET; move = MOVE_EMBER; item = ITEM_FIRIUM_Z; } \