Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Future Sight fixes #4350

Merged
merged 7 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion include/battle.h
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,8 @@ struct FieldTimer
struct WishFutureKnock
{
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];
Bassoonian marked this conversation as resolved.
Show resolved Hide resolved
u8 wishCounter[MAX_BATTLERS_COUNT];
u8 wishPartyId[MAX_BATTLERS_COUNT];
Expand Down
1 change: 1 addition & 0 deletions include/battle_scripts.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ extern const u8 BattleScript_WrapFree[];
extern const u8 BattleScript_LeechSeedFree[];
extern const u8 BattleScript_SpikesFree[];
extern const u8 BattleScript_MonTookFutureAttack[];
extern const u8 BattleScript_FutureAttackFail[];
AlexOn1ine marked this conversation as resolved.
Show resolved Hide resolved
extern const u8 BattleScript_NoMovesLeft[];
extern const u8 BattleScript_SelectingMoveWithNoPP[];
extern const u8 BattleScript_NoPPForMove[];
Expand Down
4 changes: 2 additions & 2 deletions src/battle_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -4207,11 +4207,11 @@ void BattleTurnPassed(void)
if (DoBattlerEndTurnEffects())
return;
}
if (HandleWishPerishSongOnTurnEnd())
return;
if (HandleFaintedMonActions())
return;
gBattleStruct->faintedActionsState = 0;
if (HandleWishPerishSongOnTurnEnd())
return;

TurnValuesCleanUp(FALSE);
gHitMarker &= ~HITMARKER_NO_ATTACKSTRING;
Expand Down
2 changes: 1 addition & 1 deletion src/battle_message.c
Original file line number Diff line number Diff line change
Expand Up @@ -1571,7 +1571,7 @@ const u16 gMentalHerbCureStringIds[] =
[B_MSG_MENTALHERBCURE_DISABLE] = STRINGID_PKMNMOVEDISABLEDNOMORE,
};

const u16 gStartingStatusStringIds[B_MSG_STARTING_STATUS_COUNT] =
const u16 gStartingStatusStringIds[B_MSG_STARTING_STATUS_COUNT] =
{
[B_MSG_TERRAIN_SET_MISTY] = STRINGID_TERRAINBECOMESMISTY,
[B_MSG_TERRAIN_SET_ELECTRIC] = STRINGID_TERRAINBECOMESELECTRIC,
Expand Down
3 changes: 2 additions & 1 deletion src/battle_script_commands.c
Original file line number Diff line number Diff line change
Expand Up @@ -13705,7 +13705,8 @@ static void Cmd_trysetfutureattack(void)
{
gSideStatuses[GetBattlerSide(gBattlerTarget)] |= SIDE_STATUS_FUTUREATTACK;
gWishFutureKnock.futureSightMove[gBattlerTarget] = gCurrentMove;
gWishFutureKnock.futureSightAttacker[gBattlerTarget] = gBattlerAttacker;
gWishFutureKnock.futureSightBattlerIndex[gBattlerTarget] = gBattlerAttacker;
gWishFutureKnock.futureSightPartyIndex[gBattlerTarget] = gBattlerPartyIndexes[gBattlerAttacker];
gWishFutureKnock.futureSightCounter[gBattlerTarget] = 3;

if (gCurrentMove == MOVE_DOOM_DESIRE)
Expand Down
93 changes: 84 additions & 9 deletions src/battle_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -2964,17 +2964,15 @@ bool32 HandleWishPerishSongOnTurnEnd(void)
while (gBattleStruct->wishPerishSongBattlerId < gBattlersCount)
{
battler = gBattleStruct->wishPerishSongBattlerId;
if (gAbsentBattlerFlags & gBitTable[battler])
{
gBattleStruct->wishPerishSongBattlerId++;
continue;
}

gBattleStruct->wishPerishSongBattlerId++;

if (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)
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_FUTURE_SIGHT;
else
Expand All @@ -2983,17 +2981,22 @@ bool32 HandleWishPerishSongOnTurnEnd(void)
PREPARE_MOVE_BUFFER(gBattleTextBuff1, gWishFutureKnock.futureSightMove[battler]);

gBattlerTarget = battler;
gBattlerAttacker = gWishFutureKnock.futureSightAttacker[battler];
gBattlerAttacker = gWishFutureKnock.futureSightBattlerIndex[battler];
gSpecialStatuses[gBattlerTarget].shellBellDmg = IGNORE_SHELL_BELL;
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);

if (gWishFutureKnock.futureSightCounter[battler] == 0
&& gWishFutureKnock.futureSightCounter[BATTLE_PARTNER(battler)] == 0)
{
gSideStatuses[GetBattlerSide(gBattlerTarget)] &= ~SIDE_STATUS_FUTUREATTACK;
}

return TRUE;
}
}
Expand Down Expand Up @@ -9833,6 +9836,66 @@ static inline s32 DoMoveDamageCalc(u32 move, u32 battlerAtk, u32 battlerDef, u32
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

static u32 GetWeather(void)
Expand All @@ -9845,9 +9908,21 @@ static u32 GetWeather(void)

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),
GetWeather());
}
}

// for AI so that typeEffectivenessModifier, weather, abilities and holdEffects are calculated only once
Expand Down
134 changes: 134 additions & 0 deletions test/battle/move_effect/future_sight.c
Original file line number Diff line number Diff line change
@@ -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!");
}
}
Loading