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 2 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
5 changes: 5 additions & 0 deletions data/battle_scripts_1.s
Original file line number Diff line number Diff line change
Expand Up @@ -6631,6 +6631,11 @@ BattleScript_FutureAttackMiss::
sethword gMoveResultFlags, 0
end2

BattleScript_FutureAttackFail::
printstring STRINGID_FUTURESIGHTNOHIT
waitmessage B_WAIT_TIME_LONG
end2

BattleScript_NoMovesLeft::
printselectionstring STRINGID_PKMNHASNOMOVESLEFT
endselectionscript
Expand Down
4 changes: 3 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 Expand Up @@ -766,6 +767,7 @@ struct BattleStruct
u8 trainerSlideDynamaxMsgDone:1;
u8 pledgeMove:1;
u8 isSkyBattle:1;
u8 partyMonFutureSightAttack:1;
u32 aiDelayTimer; // Counts number of frames AI takes to choose an action.
u32 aiDelayFrames; // Number of frames it took to choose an action.
u8 timesGotHit[NUM_BATTLE_SIDES][PARTY_SIZE];
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
3 changes: 2 additions & 1 deletion include/constants/battle_string_ids.h
Original file line number Diff line number Diff line change
Expand Up @@ -707,8 +707,9 @@
#define STRINGID_BIZARREARENACREATED 705
#define STRINGID_BIZARREAREACREATED 706
#define STRINGID_TIDYINGUPCOMPLETE 707
#define STRINGID_FUTURESIGHTNOHIT 708

#define BATTLESTRINGS_COUNT 708
#define BATTLESTRINGS_COUNT 709

// This is the string id that gBattleStringsTable starts with.
// String ids before this (e.g. STRINGID_INTROMSG) are not in the table,
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
4 changes: 3 additions & 1 deletion src/battle_message.c
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ static const u8 sText_PkmnBlewAwaySpikes[] = _("{B_ATK_NAME_WITH_PREFIX} blew aw
static const u8 sText_PkmnFledFromBattle[] = _("{B_ATK_NAME_WITH_PREFIX} fled from\nbattle!");
static const u8 sText_PkmnForesawAttack[] = _("{B_ATK_NAME_WITH_PREFIX} foresaw\nan attack!");
static const u8 sText_PkmnTookAttack[] = _("{B_DEF_NAME_WITH_PREFIX} took the\n{B_BUFF1} attack!");
static const u8 sText_FutureSightNoHit[] = _("{B_CURRENT_MOVE} did not hit because\nthe target is fainted.");
AlexOn1ine marked this conversation as resolved.
Show resolved Hide resolved
static const u8 sText_PkmnChoseXAsDestiny[] = _("{B_ATK_NAME_WITH_PREFIX} chose\n{B_CURRENT_MOVE} as its destiny!");
static const u8 sText_PkmnAttack[] = _("{B_BUFF1}'s attack!");
static const u8 sText_PkmnCenterAttention[] = _("{B_DEF_NAME_WITH_PREFIX} became the\ncenter of attention!");
Expand Down Expand Up @@ -1142,6 +1143,7 @@ const u8 *const gBattleStringsTable[BATTLESTRINGS_COUNT] =
[STRINGID_PKMNFLEDFROMBATTLE - BATTLESTRINGS_TABLE_START] = sText_PkmnFledFromBattle,
[STRINGID_PKMNFORESAWATTACK - BATTLESTRINGS_TABLE_START] = sText_PkmnForesawAttack,
[STRINGID_PKMNTOOKATTACK - BATTLESTRINGS_TABLE_START] = sText_PkmnTookAttack,
[STRINGID_FUTURESIGHTNOHIT - BATTLESTRINGS_TABLE_START] = sText_FutureSightNoHit,
[STRINGID_PKMNATTACK - BATTLESTRINGS_TABLE_START] = sText_PkmnAttack,
[STRINGID_PKMNCENTERATTENTION - BATTLESTRINGS_TABLE_START] = sText_PkmnCenterAttention,
[STRINGID_PKMNCHARGINGPOWER - BATTLESTRINGS_TABLE_START] = sText_PkmnChargingPower,
Expand Down Expand Up @@ -1571,7 +1573,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
66 changes: 55 additions & 11 deletions src/battle_util.c
Original file line number Diff line number Diff line change
Expand Up @@ -2964,17 +2964,12 @@ 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)
if (gWishFutureKnock.futureSightCounter[battler] != 0 && --gWishFutureKnock.futureSightCounter[battler] == 0)
{
struct Pokemon *party;

if (gWishFutureKnock.futureSightMove[battler] == MOVE_FUTURE_SIGHT)
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_FUTURE_SIGHT;
else
Expand All @@ -2983,17 +2978,27 @@ 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);
BattleScriptExecute(BattleScript_MonTookFutureAttack);

party = GetSideParty(GetBattlerSide(gBattlerAttacker));
if (&party[gWishFutureKnock.futureSightPartyIndex[gBattlerTarget]] != &party[gBattlerPartyIndexes[gBattlerAttacker]])
gBattleStruct->partyMonFutureSightAttack = TRUE;
else
SetTypeBeforeUsingMove(gCurrentMove, gBattlerAttacker);

if (gAbsentBattlerFlags & gBitTable[battler])
BattleScriptExecute(BattleScript_FutureAttackFail);
else
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 @@ -9784,6 +9789,45 @@ static inline s32 DoMoveDamageCalcVars(u32 move, u32 battlerAtk, u32 battlerDef,
u32 userFinalAttack;
u32 targetFinalDefense;

if (gBattleStruct->partyMonFutureSightAttack)
{
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;
gBattleStruct->partyMonFutureSightAttack = FALSE;

return dmg;
}

if (fixedBasePower)
gBattleMovePower = fixedBasePower;
else
Expand Down
133 changes: 133 additions & 0 deletions test/battle/move_effect/future_sight.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
#include "global.h"
#include "test/battle.h"

ASSUMPTIONS
{
ASSUME(gMovesInfo[MOVE_SEED_FLARE].power == gMovesInfo[MOVE_FUTURE_SIGHT].power);
}

SINGLE_BATTLE_TEST("Future Sight uses the Attack stat of the original user without modifiers")
AlexOn1ine marked this conversation as resolved.
Show resolved Hide resolved
{
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 is not on the field")
AlexOn1ine marked this conversation as resolved.
Show resolved Hide resolved
{
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 from STAB party mon")
AlexOn1ine marked this conversation as resolved.
Show resolved Hide resolved
{
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("Future Sight did not hit because the target is fainted.");
MESSAGE("2 sent out Wynaut!");
}
}
Loading