Future Sight fixes (#4350)

* Future Sight fixes

* handle life orb boost

* applied review

* Future Sight changes

* removed future sight no hit string

* agbcc

* Update battle_scripts.h
This commit is contained in:
Alex 2024-04-11 10:23:16 +02:00 committed by GitHub
parent 3fb52b6b0e
commit 62d054e135
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 224 additions and 13 deletions

View File

@ -278,7 +278,8 @@ struct FieldTimer
struct WishFutureKnock struct WishFutureKnock
{ {
u8 futureSightCounter[MAX_BATTLERS_COUNT]; u8 futureSightCounter[MAX_BATTLERS_COUNT];
u8 futureSightAttacker[MAX_BATTLERS_COUNT]; u8 futureSightBattlerIndex[MAX_BATTLERS_COUNT];
u8 futureSightPartyIndex[MAX_BATTLERS_COUNT];
u16 futureSightMove[MAX_BATTLERS_COUNT]; u16 futureSightMove[MAX_BATTLERS_COUNT];
u8 wishCounter[MAX_BATTLERS_COUNT]; u8 wishCounter[MAX_BATTLERS_COUNT];
u8 wishPartyId[MAX_BATTLERS_COUNT]; u8 wishPartyId[MAX_BATTLERS_COUNT];

View File

@ -4207,11 +4207,11 @@ void BattleTurnPassed(void)
if (DoBattlerEndTurnEffects()) if (DoBattlerEndTurnEffects())
return; return;
} }
if (HandleWishPerishSongOnTurnEnd())
return;
if (HandleFaintedMonActions()) if (HandleFaintedMonActions())
return; return;
gBattleStruct->faintedActionsState = 0; gBattleStruct->faintedActionsState = 0;
if (HandleWishPerishSongOnTurnEnd())
return;
TurnValuesCleanUp(FALSE); TurnValuesCleanUp(FALSE);
gHitMarker &= ~HITMARKER_NO_ATTACKSTRING; gHitMarker &= ~HITMARKER_NO_ATTACKSTRING;

View File

@ -13708,7 +13708,8 @@ static void Cmd_trysetfutureattack(void)
{ {
gSideStatuses[GetBattlerSide(gBattlerTarget)] |= SIDE_STATUS_FUTUREATTACK; gSideStatuses[GetBattlerSide(gBattlerTarget)] |= SIDE_STATUS_FUTUREATTACK;
gWishFutureKnock.futureSightMove[gBattlerTarget] = gCurrentMove; gWishFutureKnock.futureSightMove[gBattlerTarget] = gCurrentMove;
gWishFutureKnock.futureSightAttacker[gBattlerTarget] = gBattlerAttacker; gWishFutureKnock.futureSightBattlerIndex[gBattlerTarget] = gBattlerAttacker;
gWishFutureKnock.futureSightPartyIndex[gBattlerTarget] = gBattlerPartyIndexes[gBattlerAttacker];
gWishFutureKnock.futureSightCounter[gBattlerTarget] = 3; gWishFutureKnock.futureSightCounter[gBattlerTarget] = 3;
if (gCurrentMove == MOVE_DOOM_DESIRE) if (gCurrentMove == MOVE_DOOM_DESIRE)

View File

@ -2964,17 +2964,15 @@ bool32 HandleWishPerishSongOnTurnEnd(void)
while (gBattleStruct->wishPerishSongBattlerId < gBattlersCount) while (gBattleStruct->wishPerishSongBattlerId < gBattlersCount)
{ {
battler = gBattleStruct->wishPerishSongBattlerId; battler = gBattleStruct->wishPerishSongBattlerId;
if (gAbsentBattlerFlags & gBitTable[battler])
{
gBattleStruct->wishPerishSongBattlerId++;
continue;
}
gBattleStruct->wishPerishSongBattlerId++; gBattleStruct->wishPerishSongBattlerId++;
if (gWishFutureKnock.futureSightCounter[battler] != 0 if (gWishFutureKnock.futureSightCounter[battler] != 0
&& --gWishFutureKnock.futureSightCounter[battler] == 0 && --gWishFutureKnock.futureSightCounter[battler] == 0
&& gBattleMons[battler].hp != 0) && !(gAbsentBattlerFlags & gBitTable[battler]))
{ {
struct Pokemon *party;
if (gWishFutureKnock.futureSightMove[battler] == MOVE_FUTURE_SIGHT) if (gWishFutureKnock.futureSightMove[battler] == MOVE_FUTURE_SIGHT)
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_FUTURE_SIGHT; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_FUTURE_SIGHT;
else else
@ -2983,10 +2981,14 @@ bool32 HandleWishPerishSongOnTurnEnd(void)
PREPARE_MOVE_BUFFER(gBattleTextBuff1, gWishFutureKnock.futureSightMove[battler]); PREPARE_MOVE_BUFFER(gBattleTextBuff1, gWishFutureKnock.futureSightMove[battler]);
gBattlerTarget = battler; gBattlerTarget = battler;
gBattlerAttacker = gWishFutureKnock.futureSightAttacker[battler]; gBattlerAttacker = gWishFutureKnock.futureSightBattlerIndex[battler];
gSpecialStatuses[gBattlerTarget].shellBellDmg = IGNORE_SHELL_BELL; gSpecialStatuses[gBattlerTarget].shellBellDmg = IGNORE_SHELL_BELL;
gCurrentMove = gWishFutureKnock.futureSightMove[battler]; gCurrentMove = gWishFutureKnock.futureSightMove[battler];
SetTypeBeforeUsingMove(gCurrentMove, battler);
party = GetSideParty(GetBattlerSide(gBattlerAttacker));
if (&party[gWishFutureKnock.futureSightPartyIndex[gBattlerTarget]] == &party[gBattlerPartyIndexes[gBattlerAttacker]])
SetTypeBeforeUsingMove(gCurrentMove, gBattlerAttacker);
BattleScriptExecute(BattleScript_MonTookFutureAttack); BattleScriptExecute(BattleScript_MonTookFutureAttack);
if (gWishFutureKnock.futureSightCounter[battler] == 0 if (gWishFutureKnock.futureSightCounter[battler] == 0
@ -2994,6 +2996,7 @@ bool32 HandleWishPerishSongOnTurnEnd(void)
{ {
gSideStatuses[GetBattlerSide(gBattlerTarget)] &= ~SIDE_STATUS_FUTUREATTACK; gSideStatuses[GetBattlerSide(gBattlerTarget)] &= ~SIDE_STATUS_FUTUREATTACK;
} }
return TRUE; return TRUE;
} }
} }
@ -9846,6 +9849,66 @@ static inline s32 DoMoveDamageCalc(u32 move, u32 battlerAtk, u32 battlerDef, u32
updateFlags, typeEffectivenessModifier, weather, holdEffectAtk, holdEffectDef, abilityAtk, abilityDef); updateFlags, typeEffectivenessModifier, weather, holdEffectAtk, holdEffectDef, abilityAtk, abilityDef);
} }
static inline s32 DoFutureSightAttackDamageCalcVars(u32 move, u32 battlerAtk, u32 battlerDef, u32 moveType,
bool32 isCrit, bool32 randomFactor, bool32 updateFlags, uq4_12_t typeEffectivenessModifier, u32 weather,
u32 holdEffectDef, u32 abilityDef)
{
s32 dmg;
u32 userFinalAttack;
u32 targetFinalDefense;
struct Pokemon *party = GetSideParty(GetBattlerSide(battlerAtk));
struct Pokemon *partyMon = &party[gWishFutureKnock.futureSightPartyIndex[battlerDef]];
u32 partyMonLevel = GetMonData(partyMon, MON_DATA_LEVEL, NULL);
u32 partyMonSpecies = GetMonData(partyMon, MON_DATA_SPECIES, NULL);
gBattleMovePower = gMovesInfo[move].power;
if (IS_MOVE_PHYSICAL(move))
userFinalAttack = GetMonData(partyMon, MON_DATA_ATK, NULL);
else
userFinalAttack = GetMonData(partyMon, MON_DATA_SPATK, NULL);
targetFinalDefense = CalcDefenseStat(move, battlerAtk, battlerDef, moveType, isCrit, updateFlags, ABILITY_NONE, abilityDef, holdEffectDef, weather);
dmg = CalculateBaseDamage(gBattleMovePower, userFinalAttack, partyMonLevel, targetFinalDefense);
DAMAGE_APPLY_MODIFIER(GetCriticalModifier(isCrit));
if (randomFactor)
{
dmg *= 100 - RandomUniform(RNG_DAMAGE_MODIFIER, 0, 15);
dmg /= 100;
}
// Same type attack bonus
if (gSpeciesInfo[partyMonSpecies].types[0] == moveType || gSpeciesInfo[partyMonSpecies].types[1] == moveType)
DAMAGE_APPLY_MODIFIER(UQ_4_12(1.5));
else
DAMAGE_APPLY_MODIFIER(UQ_4_12(1.0));
DAMAGE_APPLY_MODIFIER(typeEffectivenessModifier);
if (dmg == 0)
dmg = 1;
gSpecialStatuses[battlerAtk].preventLifeOrbDamage = TRUE;
return dmg;
}
static inline s32 DoFutureSightAttackDamageCalc(u32 move, u32 battlerAtk, u32 battlerDef, u32 moveType,
bool32 isCrit, bool32 randomFactor, bool32 updateFlags, uq4_12_t typeEffectivenessModifier, u32 weather)
{
u32 holdEffectDef, abilityDef;
if (typeEffectivenessModifier == UQ_4_12(0.0))
return 0;
holdEffectDef = GetBattlerHoldEffect(battlerDef, TRUE);
abilityDef = GetBattlerAbility(battlerDef);
return DoFutureSightAttackDamageCalcVars(move, battlerAtk, battlerDef, moveType, isCrit, randomFactor,
updateFlags, typeEffectivenessModifier, weather, holdEffectDef, abilityDef);
}
#undef DAMAGE_APPLY_MODIFIER #undef DAMAGE_APPLY_MODIFIER
static u32 GetWeather(void) static u32 GetWeather(void)
@ -9858,9 +9921,21 @@ static u32 GetWeather(void)
s32 CalculateMoveDamage(u32 move, u32 battlerAtk, u32 battlerDef, u32 moveType, s32 fixedBasePower, bool32 isCrit, bool32 randomFactor, bool32 updateFlags) s32 CalculateMoveDamage(u32 move, u32 battlerAtk, u32 battlerDef, u32 moveType, s32 fixedBasePower, bool32 isCrit, bool32 randomFactor, bool32 updateFlags)
{ {
return DoMoveDamageCalc(move, battlerAtk, battlerDef, moveType, fixedBasePower, isCrit, randomFactor, struct Pokemon *party = GetSideParty(GetBattlerSide(gBattlerAttacker));
if (gMovesInfo[move].effect == EFFECT_FUTURE_SIGHT
&& (&party[gWishFutureKnock.futureSightPartyIndex[battlerDef]] != &party[gBattlerPartyIndexes[battlerAtk]]) )
{
return DoFutureSightAttackDamageCalc(move, battlerAtk, battlerDef, moveType, isCrit, randomFactor,
updateFlags, CalcTypeEffectivenessMultiplier(move, moveType, battlerAtk, battlerDef, GetBattlerAbility(battlerDef), updateFlags),
GetWeather());
}
else
{
return DoMoveDamageCalc(move, battlerAtk, battlerDef, moveType, fixedBasePower, isCrit, randomFactor,
updateFlags, CalcTypeEffectivenessMultiplier(move, moveType, battlerAtk, battlerDef, GetBattlerAbility(battlerDef), updateFlags), updateFlags, CalcTypeEffectivenessMultiplier(move, moveType, battlerAtk, battlerDef, GetBattlerAbility(battlerDef), updateFlags),
GetWeather()); GetWeather());
}
} }
// for AI so that typeEffectivenessModifier, weather, abilities and holdEffects are calculated only once // for AI so that typeEffectivenessModifier, weather, abilities and holdEffects are calculated only once

View File

@ -0,0 +1,134 @@
#include "global.h"
#include "test/battle.h"
ASSUMPTIONS
{
ASSUME(gMovesInfo[MOVE_SEED_FLARE].power == gMovesInfo[MOVE_FUTURE_SIGHT].power);
ASSUME(gMovesInfo[MOVE_SEED_FLARE].category == gMovesInfo[MOVE_FUTURE_SIGHT].category);
}
SINGLE_BATTLE_TEST("Future Sight uses Sp. Atk stat of the original user without modifiers")
{
u32 item;
s16 seedFlareDmg;
s16 futureSightDmg;
PARAMETRIZE { item = ITEM_TWISTED_SPOON; }
PARAMETRIZE { item = ITEM_PSYCHIC_GEM; }
GIVEN {
PLAYER(SPECIES_PIKACHU) { Item(item); }
PLAYER(SPECIES_RAICHU) { Item(item); }
OPPONENT(SPECIES_REGICE);
} WHEN {
TURN { MOVE(player, MOVE_SEED_FLARE, WITH_RNG(RNG_SECONDARY_EFFECT, FALSE)); }
TURN { MOVE(player, MOVE_FUTURE_SIGHT); }
TURN { SWITCH(player, 1); }
TURN { }
TURN { }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SEED_FLARE, player);
HP_BAR(opponent, captureDamage: &seedFlareDmg);
ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player);
MESSAGE("Foe Regice took the Future Sight attack!");
HP_BAR(opponent, captureDamage: &futureSightDmg);
} THEN {
EXPECT_EQ(seedFlareDmg, futureSightDmg);
}
}
SINGLE_BATTLE_TEST("Future Sight is not boosted by Life Orb is original user if not on the field")
{
s16 seedFlareDmg;
s16 futureSightDmg;
GIVEN {
PLAYER(SPECIES_PIKACHU);
PLAYER(SPECIES_RAICHU) { Item(ITEM_LIFE_ORB); }
OPPONENT(SPECIES_REGICE);
} WHEN {
TURN { MOVE(player, MOVE_SEED_FLARE, WITH_RNG(RNG_SECONDARY_EFFECT, FALSE)); }
TURN { MOVE(player, MOVE_FUTURE_SIGHT); }
TURN { SWITCH(player, 1); }
TURN { }
TURN { }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SEED_FLARE, player);
HP_BAR(opponent, captureDamage: &seedFlareDmg);
ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player);
MESSAGE("Foe Regice took the Future Sight attack!");
HP_BAR(opponent, captureDamage: &futureSightDmg);
NOT MESSAGE("Raichu was hurt by its Life Orb!");
} THEN {
EXPECT_EQ(seedFlareDmg, futureSightDmg);
}
}
SINGLE_BATTLE_TEST("Future Sight receives STAB from party mon")
{
s16 seedFlareDmg;
s16 futureSightDmg;
GIVEN {
PLAYER(SPECIES_RALTS);
PLAYER(SPECIES_RAICHU);
OPPONENT(SPECIES_REGICE);
} WHEN {
TURN { MOVE(player, MOVE_SEED_FLARE, WITH_RNG(RNG_SECONDARY_EFFECT, FALSE)); }
TURN { MOVE(player, MOVE_FUTURE_SIGHT); }
TURN { SWITCH(player, 1); }
TURN { }
TURN { }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SEED_FLARE, player);
HP_BAR(opponent, captureDamage: &seedFlareDmg);
ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player);
HP_BAR(opponent, captureDamage: &futureSightDmg);
} THEN {
EXPECT_MUL_EQ(seedFlareDmg, Q_4_12(1.5), futureSightDmg);
}
}
SINGLE_BATTLE_TEST("Future Sight is affected by type effectiveness")
{
GIVEN {
PLAYER(SPECIES_PIKACHU);
PLAYER(SPECIES_RAICHU);
OPPONENT(SPECIES_HOUNDOOM);
} WHEN {
TURN { MOVE(player, MOVE_SEED_FLARE, WITH_RNG(RNG_SECONDARY_EFFECT, FALSE)); }
TURN { MOVE(player, MOVE_FUTURE_SIGHT); }
TURN { SWITCH(player, 1); }
TURN { }
TURN { }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SEED_FLARE, player);
HP_BAR(opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player);
MESSAGE("Foe Houndoom took the Future Sight attack!");
MESSAGE("It doesn't affect Foe Houndoom…");
NOT HP_BAR(opponent);
}
}
SINGLE_BATTLE_TEST("Future Sight will miss timing if target faints before it is about to get hit")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(player, MOVE_FUTURE_SIGHT); }
TURN { MOVE(player, MOVE_CELEBRATE); }
TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_MEMENTO); SEND_OUT(opponent, 1); }
TURN { }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FUTURE_SIGHT, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_MEMENTO, opponent);
MESSAGE("Foe Wobbuffet fainted!");
MESSAGE("2 sent out Wynaut!");
NOT MESSAGE("Foe Wynaut took the Future Sight attack!");
}
}