Merge branch 'master' of https://github.com/rh-hideout/pokeemerald-expansion into rh-hideout-master

This commit is contained in:
RoamerX 2025-09-29 17:23:05 +08:00
commit 2592b69f26
39 changed files with 886 additions and 124 deletions

View File

@ -216,7 +216,7 @@ BattleScript_EffectDoodle_AfterCopy:
printstring STRINGID_PKMNCOPIEDFOE printstring STRINGID_PKMNCOPIEDFOE
waitmessage B_WAIT_TIME_LONG waitmessage B_WAIT_TIME_LONG
switchinabilities BS_ATTACKER switchinabilities BS_ATTACKER
jumpifbyte CMP_NOT_EQUAL, gBattleCommunication, 0x0, BattleScript_MoveEnd jumpifbyte CMP_NOT_EQUAL, gBattleCommunication, 0x0, BattleScript_EffectDoodleMoveEnd
addbyte gBattleCommunication, 1 addbyte gBattleCommunication, 1
jumpifnoally BS_ATTACKER, BattleScript_EffectDoodleMoveEnd jumpifnoally BS_ATTACKER, BattleScript_EffectDoodleMoveEnd
setallytonextattacker BattleScript_EffectDoodle_CopyAbility setallytonextattacker BattleScript_EffectDoodle_CopyAbility
@ -402,7 +402,9 @@ BattleScript_MoveEffectSaltCure::
BattleScript_SaltCureExtraDamage:: BattleScript_SaltCureExtraDamage::
playanimation BS_ATTACKER, B_ANIM_SALT_CURE_DAMAGE, NULL playanimation BS_ATTACKER, B_ANIM_SALT_CURE_DAMAGE, NULL
waitanimation waitanimation
call BattleScript_HurtTarget_NoString orword gHitMarker, HITMARKER_IGNORE_SUBSTITUTE | HITMARKER_PASSIVE_HP_UPDATE
healthbarupdate BS_ATTACKER
datahpupdate BS_ATTACKER
printstring STRINGID_TARGETISHURTBYSALTCURE printstring STRINGID_TARGETISHURTBYSALTCURE
waitmessage B_WAIT_TIME_LONG waitmessage B_WAIT_TIME_LONG
tryfaintmon BS_ATTACKER tryfaintmon BS_ATTACKER
@ -790,13 +792,13 @@ BattleScript_FlingLightBall:
goto BattleScript_FlingEnd goto BattleScript_FlingEnd
BattleScript_FlingMentalHerb: BattleScript_FlingMentalHerb:
curecertainstatuses curecertainstatuses
savetarget saveattacker
copybyte gBattlerAttacker, gBattlerTarget copybyte gBattlerAttacker, gBattlerTarget
playanimation BS_ATTACKER, B_ANIM_HELD_ITEM_EFFECT, NULL playanimation BS_ATTACKER, B_ANIM_HELD_ITEM_EFFECT, NULL
printfromtable gMentalHerbCureStringIds printfromtable gMentalHerbCureStringIds
waitmessage B_WAIT_TIME_LONG waitmessage B_WAIT_TIME_LONG
updatestatusicon BS_ATTACKER updatestatusicon BS_ATTACKER
restoretarget restoreattacker
goto BattleScript_FlingEnd goto BattleScript_FlingEnd
BattleScript_FlingPoisonBarb: BattleScript_FlingPoisonBarb:
seteffectsecondary BS_ATTACKER, BS_TARGET, MOVE_EFFECT_POISON seteffectsecondary BS_ATTACKER, BS_TARGET, MOVE_EFFECT_POISON
@ -1475,14 +1477,17 @@ BattleScript_EffectFlowerShield::
ppreduce ppreduce
savetarget savetarget
selectfirstvalidtarget selectfirstvalidtarget
BattleScript_FlowerShieldIsAnyGrass: BattleScript_FlowerShieldIsAnyValidTarget:
jumpifvolatile BS_TARGET, VOLATILE_SEMI_INVULNERABLE, BattleScript_FlowerShieldCheckNextTarget
jumpiftype BS_TARGET, TYPE_GRASS, BattleScript_FlowerShieldLoopStart jumpiftype BS_TARGET, TYPE_GRASS, BattleScript_FlowerShieldLoopStart
jumpifnexttargetvalid BattleScript_FlowerShieldIsAnyGrass BattleScript_FlowerShieldCheckNextTarget:
jumpifnexttargetvalid BattleScript_FlowerShieldIsAnyValidTarget
goto BattleScript_RestoreTargetButItFailed goto BattleScript_RestoreTargetButItFailed
BattleScript_FlowerShieldLoopStart: BattleScript_FlowerShieldLoopStart:
selectfirstvalidtarget selectfirstvalidtarget
BattleScript_FlowerShieldLoop: BattleScript_FlowerShieldLoop:
movevaluescleanup movevaluescleanup
jumpifvolatile BS_TARGET, VOLATILE_SEMI_INVULNERABLE, BattleScript_FlowerShieldMoveTargetEnd
jumpiftype BS_TARGET, TYPE_GRASS, BattleScript_FlowerShieldLoop2 jumpiftype BS_TARGET, TYPE_GRASS, BattleScript_FlowerShieldLoop2
goto BattleScript_FlowerShieldMoveTargetEnd goto BattleScript_FlowerShieldMoveTargetEnd
BattleScript_FlowerShieldLoop2: BattleScript_FlowerShieldLoop2:
@ -1503,6 +1508,7 @@ BattleScript_FlowerShieldMoveTargetEnd:
moveendto MOVEEND_NEXT_TARGET moveendto MOVEEND_NEXT_TARGET
jumpifnexttargetvalid BattleScript_FlowerShieldLoop jumpifnexttargetvalid BattleScript_FlowerShieldLoop
restoretarget restoretarget
moveendfrom MOVEEND_ITEM_EFFECTS_ATTACKER
end end
BattleScript_EffectRototiller:: BattleScript_EffectRototiller::
@ -1578,7 +1584,6 @@ BattleScript_EffectAfterYou::
goto BattleScript_MoveEnd goto BattleScript_MoveEnd
BattleScript_MoveEffectFlameBurst:: BattleScript_MoveEffectFlameBurst::
tryfaintmon BS_TARGET
printstring STRINGID_BURSTINGFLAMESHIT printstring STRINGID_BURSTINGFLAMESHIT
waitmessage B_WAIT_TIME_LONG waitmessage B_WAIT_TIME_LONG
orword gHitMarker, HITMARKER_IGNORE_SUBSTITUTE | HITMARKER_PASSIVE_HP_UPDATE orword gHitMarker, HITMARKER_IGNORE_SUBSTITUTE | HITMARKER_PASSIVE_HP_UPDATE
@ -3215,7 +3220,6 @@ BattleScript_EffectOHKO::
attackcanceler attackcanceler
attackstring attackstring
ppreduce ppreduce
accuracycheck BattleScript_ButItFailed, ACC_CURR_MOVE
typecalc typecalc
jumpifmovehadnoeffect BattleScript_HitFromAtkAnimation jumpifmovehadnoeffect BattleScript_HitFromAtkAnimation
tryKO BattleScript_KOFail tryKO BattleScript_KOFail
@ -8507,15 +8511,15 @@ BattleScript_ItemHealHP_RemoveItemEnd2_Anim:
end2 end2
BattleScript_BerryPPHealRet:: BattleScript_BerryPPHealRet::
jumpifability BS_ATTACKER, ABILITY_RIPEN, BattleScript_BerryPPHeal_AbilityPopup jumpifability BS_SCRIPTING, ABILITY_RIPEN, BattleScript_BerryPPHeal_AbilityPopup
goto BattleScript_BerryPPHeal_Anim goto BattleScript_BerryPPHeal_Anim
BattleScript_BerryPPHeal_AbilityPopup: BattleScript_BerryPPHeal_AbilityPopup:
call BattleScript_AbilityPopUp call BattleScript_AbilityPopUpScripting
BattleScript_BerryPPHeal_Anim: BattleScript_BerryPPHeal_Anim:
playanimation BS_ATTACKER, B_ANIM_HELD_ITEM_EFFECT playanimation BS_SCRIPTING, B_ANIM_HELD_ITEM_EFFECT
printstring STRINGID_PKMNSITEMRESTOREDPP printstring STRINGID_PKMNSITEMRESTOREDPP
waitmessage B_WAIT_TIME_LONG waitmessage B_WAIT_TIME_LONG
removeitem BS_ATTACKER removeitem BS_SCRIPTING
return return
BattleScript_BerryPPHealEnd2:: BattleScript_BerryPPHealEnd2::

View File

@ -19,7 +19,7 @@ If you've done this before and just need a quick lookup, here's what files you n
## The Graphics ## The Graphics
### 1. Edit the sprites ### 1. Edit the sprites
We will start with a graphic that we want to use for our new trainer pic. Unlike with adding Pokémon, the trainer sprites aren't sorted in individual folders, but rather in one folder: [`graphics/trainers/front_pics`](./graphics/trainers/front_pics). **Trainers sprites cannot be more than 16 - this includes the color that will be transparent, which is the first slot of the palette.** We will start with a graphic that we want to use for our new trainer pic. Unlike with adding Pokémon, the trainer sprites aren't sorted in individual folders, but rather in one folder: [`graphics/trainers/front_pics`](./graphics/trainers/front_pics). **Trainers sprites cannot have more than 16 colors - this includes the color that will be transparent, which is the first slot of the palette.**
### 2. Register the sprites ### 2. Register the sprites
Sadly, just putting the image files into the graphics folder is not enough. To use the sprites we have to register them by linking the graphic files in [`src/data/graphics/trainers`](./data/graphics/trainers.h): Sadly, just putting the image files into the graphics folder is not enough. To use the sprites we have to register them by linking the graphic files in [`src/data/graphics/trainers`](./data/graphics/trainers.h):

View File

@ -141,7 +141,6 @@ struct ProtectStruct
{ {
u32 protected:7; // 126 protect options u32 protected:7; // 126 protect options
u32 noValidMoves:1; u32 noValidMoves:1;
u32 helpingHand:1;
u32 bounceMove:1; u32 bounceMove:1;
u32 stealMove:1; u32 stealMove:1;
u32 nonVolatileStatusImmobility:1; u32 nonVolatileStatusImmobility:1;
@ -163,13 +162,14 @@ struct ProtectStruct
u32 shellTrap:1; u32 shellTrap:1;
u32 eatMirrorHerb:1; u32 eatMirrorHerb:1;
u32 activateOpportunist:2; // 2 - to copy stats. 1 - stats copied (do not repeat). 0 - no stats to copy u32 activateOpportunist:2; // 2 - to copy stats. 1 - stats copied (do not repeat). 0 - no stats to copy
// End of 32-bit bitfield
u16 usedAllySwitch:1; u16 usedAllySwitch:1;
// End of 32-bit bitfield
u32 helpingHand:3;
u16 lashOutAffected:1; u16 lashOutAffected:1;
u16 assuranceDoubled:1; u16 assuranceDoubled:1;
u16 myceliumMight:1; u16 myceliumMight:1;
u16 laggingTail:1; u16 laggingTail:1;
u16 padding:11; u16 padding:9;
// End of 16-bit bitfield // End of 16-bit bitfield
u16 physicalDmg; u16 physicalDmg;
u16 specialDmg; u16 specialDmg;
@ -600,7 +600,8 @@ struct PartyState
u32 supersweetSyrup:1; u32 supersweetSyrup:1;
u32 timesGotHit:5; u32 timesGotHit:5;
u32 changedSpecies:11; // For forms when multiple mons can change into the same pokemon. u32 changedSpecies:11; // For forms when multiple mons can change into the same pokemon.
u32 padding:10; u32 sentOut:1;
u32 padding:9;
}; };
// Cleared at the beginning of the battle. Fields need to be cleared when needed manually otherwise. // Cleared at the beginning of the battle. Fields need to be cleared when needed manually otherwise.
@ -1124,7 +1125,7 @@ extern u8 gHealthboxSpriteIds[MAX_BATTLERS_COUNT];
extern u8 gMultiUsePlayerCursor; extern u8 gMultiUsePlayerCursor;
extern u8 gNumberOfMovesToChoose; extern u8 gNumberOfMovesToChoose;
extern bool8 gHasFetchedBall; extern bool8 gHasFetchedBall;
extern u8 gLastUsedBall; extern u16 gLastUsedBall;
extern u16 gLastThrownBall; extern u16 gLastThrownBall;
extern u16 gBallToDisplay; extern u16 gBallToDisplay;
extern bool8 gLastUsedBallMenuPresent; extern bool8 gLastUsedBallMenuPresent;

View File

@ -44,7 +44,8 @@ s32 CalcCritChanceStage(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordA
s32 CalcCritChanceStageGen1(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, u32 abilityAtk, u32 abilityDef, enum ItemHoldEffect holdEffectAtk); s32 CalcCritChanceStageGen1(u32 battlerAtk, u32 battlerDef, u32 move, bool32 recordAbility, u32 abilityAtk, u32 abilityDef, enum ItemHoldEffect holdEffectAtk);
s32 GetCritHitOdds(s32 critChanceIndex); s32 GetCritHitOdds(s32 critChanceIndex);
u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, u32 atkAbility, u32 defAbility, u32 atkHoldEffect, u32 defHoldEffect); u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, u32 atkAbility, u32 defAbility, u32 atkHoldEffect, u32 defHoldEffect);
u8 GetBattlerTurnOrderNum(u8 battler); bool32 HasBattlerActedThisTurn(u32 battler);
u32 GetBattlerTurnOrderNum(u32 battler);
bool32 NoAliveMonsForBattlerSide(u32 battler); bool32 NoAliveMonsForBattlerSide(u32 battler);
bool32 NoAliveMonsForPlayer(void); bool32 NoAliveMonsForPlayer(void);
bool32 NoAliveMonsForEitherParty(void); bool32 NoAliveMonsForEitherParty(void);

View File

@ -311,6 +311,7 @@
#define B_TOXIC_REVERSAL GEN_LATEST // In Gen5+, bad poison will change to regular poison at the end of battles. #define B_TOXIC_REVERSAL GEN_LATEST // In Gen5+, bad poison will change to regular poison at the end of battles.
#define B_TRY_CATCH_TRAINER_BALL GEN_LATEST // In Gen4+, trying to catch a Trainer's Pokémon does not consume the Poké Ball. #define B_TRY_CATCH_TRAINER_BALL GEN_LATEST // In Gen4+, trying to catch a Trainer's Pokémon does not consume the Poké Ball.
#define B_SLEEP_CLAUSE FALSE // Enables Sleep Clause all the time in every case, overriding B_FLAG_SLEEP_CLAUSE. Use that for modularity. #define B_SLEEP_CLAUSE FALSE // Enables Sleep Clause all the time in every case, overriding B_FLAG_SLEEP_CLAUSE. Use that for modularity.
#define B_PARTNER_MONS_MARKED_SEEN FALSE // If TRUE, if your double battle partner sends out a Pokémon you haven't encountered yet, it will be marked as SEEN in your Pokédex.
#define NUM_BEEPS_GEN_LATEST 4 // Loops 4 times #define NUM_BEEPS_GEN_LATEST 4 // Loops 4 times
#define NUM_BEEPS_GEN_3 -1 // Loops infinitely #define NUM_BEEPS_GEN_3 -1 // Loops infinitely

View File

@ -155,14 +155,14 @@ enum MoveEndEffects
MOVEEND_ITEM_EFFECTS_ATTACKER, MOVEEND_ITEM_EFFECTS_ATTACKER,
MOVEEND_ABILITY_BLOCK, MOVEEND_ABILITY_BLOCK,
MOVEEND_SHEER_FORCE, // If move is Sheer Force affected, skip until Opportunist MOVEEND_SHEER_FORCE, // If move is Sheer Force affected, skip until Opportunist
MOVEEND_RED_CARD, // Red Card triggers before Eject Pack MOVEEND_RED_CARD,
MOVEEND_EJECT_BUTTON, MOVEEND_EJECT_BUTTON,
MOVEEND_LIFEORB_SHELLBELL, // Includes shell bell, throat spray, etc MOVEEND_LIFEORB_SHELLBELL,
MOVEEND_FORM_CHANGE, MOVEEND_FORM_CHANGE,
MOVEEND_EMERGENCY_EXIT, MOVEEND_EMERGENCY_EXIT,
MOVEEND_EJECT_PACK, MOVEEND_EJECT_PACK,
MOVEEND_HIT_ESCAPE, MOVEEND_HIT_ESCAPE,
MOVEEND_OPPORTUNIST, // Occurs after other stat change items/abilities to try and copy the boosts MOVEEND_OPPORTUNIST,
MOVEEND_PICKPOCKET, MOVEEND_PICKPOCKET,
MOVEEND_WHITE_HERB, MOVEEND_WHITE_HERB,
MOVEEND_THIRD_MOVE_BLOCK, MOVEEND_THIRD_MOVE_BLOCK,

View File

@ -849,6 +849,7 @@ u8 GetOpposingLinkMultiBattlerId(bool8 rightSide, u8 multiplayerId);
u16 FacilityClassToPicIndex(u16 facilityClass); u16 FacilityClassToPicIndex(u16 facilityClass);
u16 PlayerGenderToFrontTrainerPicId(u8 playerGender); u16 PlayerGenderToFrontTrainerPicId(u8 playerGender);
void HandleSetPokedexFlag(enum NationalDexOrder nationalNum, u8 caseId, u32 personality); void HandleSetPokedexFlag(enum NationalDexOrder nationalNum, u8 caseId, u32 personality);
void HandleSetPokedexFlagFromMon(struct Pokemon *mon, u32 caseId);
bool8 HasTwoFramesAnimation(u16 species); bool8 HasTwoFramesAnimation(u16 species);
struct MonSpritesGfxManager *CreateMonSpritesGfxManager(u8 managerId, u8 mode); struct MonSpritesGfxManager *CreateMonSpritesGfxManager(u8 managerId, u8 mode);
void DestroyMonSpritesGfxManager(u8 managerId); void DestroyMonSpritesGfxManager(u8 managerId);

View File

@ -215,6 +215,8 @@ enum RandomTag
RNG_AI_REFRESH_TRICK_ROOM_ON_LAST_TURN, RNG_AI_REFRESH_TRICK_ROOM_ON_LAST_TURN,
RNG_AI_APPLY_TAILWIND_ON_LAST_TURN_OF_TRICK_ROOM, RNG_AI_APPLY_TAILWIND_ON_LAST_TURN_OF_TRICK_ROOM,
RNG_WRAP, RNG_WRAP,
RNG_BALLTHROW_CRITICAL,
RNG_BALLTHROW_SHAKE,
}; };
#define RandomWeighted(tag, ...) \ #define RandomWeighted(tag, ...) \

View File

@ -1005,6 +1005,8 @@ struct ItemContext
u16 explicitPartyIndex:1; u16 explicitPartyIndex:1;
u16 move; u16 move;
u16 explicitMove:1; u16 explicitMove:1;
struct TurnRNG rng;
u16 explicitRNG:1;
}; };
void OpenTurn(u32 sourceLine); void OpenTurn(u32 sourceLine);

View File

@ -1522,7 +1522,7 @@ static u32 GetBestMonDmg(struct Pokemon *party, int firstId, int lastId, u8 inva
return bestMonId; return bestMonId;
} }
static u32 GetFirstNonIvalidMon(u32 firstId, u32 lastId, u32 invalidMons, u32 battlerIn1, u32 battlerIn2) static u32 GetFirstNonInvalidMon(u32 firstId, u32 lastId, u32 invalidMons, u32 battlerIn1, u32 battlerIn2)
{ {
if (!IsDoubleBattle()) if (!IsDoubleBattle())
return PARTY_SIZE; return PARTY_SIZE;
@ -2297,7 +2297,7 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int firstId, int lastId,
return aceMonId; return aceMonId;
// Fallback // Fallback
u32 bestMonId = GetFirstNonIvalidMon(firstId, lastId, invalidMons, battlerIn1, battlerIn2); u32 bestMonId = GetFirstNonInvalidMon(firstId, lastId, invalidMons, battlerIn1, battlerIn2);
if (bestMonId != PARTY_SIZE) if (bestMonId != PARTY_SIZE)
return bestMonId; return bestMonId;
@ -2419,7 +2419,7 @@ u32 GetMostSuitableMonToSwitchInto(u32 battler, enum SwitchType switchType)
return aceMonId; return aceMonId;
// Fallback // Fallback
bestMonId = GetFirstNonIvalidMon(firstId, lastId, invalidMons, battlerIn1, battlerIn2); bestMonId = GetFirstNonInvalidMon(firstId, lastId, invalidMons, battlerIn1, battlerIn2);
if (bestMonId != PARTY_SIZE) if (bestMonId != PARTY_SIZE)
return bestMonId; return bestMonId;

View File

@ -567,12 +567,14 @@ static void OpponentHandleChoosePokemon(u32 battler)
} }
} }
gBattleStruct->monToSwitchIntoId[battler] = chosenMonId; gBattleStruct->monToSwitchIntoId[battler] = chosenMonId;
GetBattlerPartyState(battler)->sentOut = TRUE;
} }
else else
{ {
chosenMonId = gBattleStruct->AI_monToSwitchIntoId[battler]; chosenMonId = gBattleStruct->AI_monToSwitchIntoId[battler];
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE; gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
gBattleStruct->monToSwitchIntoId[battler] = chosenMonId; gBattleStruct->monToSwitchIntoId[battler] = chosenMonId;
GetBattlerPartyState(battler)->sentOut = TRUE;
} }
#if TESTING #if TESTING
TestRunner_Battle_CheckSwitch(battler, chosenMonId); TestRunner_Battle_CheckSwitch(battler, chosenMonId);

View File

@ -285,7 +285,6 @@ static void PlayerPartnerHandleChoosePokemon(u32 battler)
else if (gBattleStruct->monToSwitchIntoId[battler] >= PARTY_SIZE || !IsValidForBattle(&gPlayerParty[gBattleStruct->monToSwitchIntoId[battler]])) else if (gBattleStruct->monToSwitchIntoId[battler] >= PARTY_SIZE || !IsValidForBattle(&gPlayerParty[gBattleStruct->monToSwitchIntoId[battler]]))
{ {
chosenMonId = GetMostSuitableMonToSwitchInto(battler, SWITCH_AFTER_KO); chosenMonId = GetMostSuitableMonToSwitchInto(battler, SWITCH_AFTER_KO);
if (chosenMonId == PARTY_SIZE || !IsValidForBattle(&gPlayerParty[chosenMonId])) // just switch to the next mon if (chosenMonId == PARTY_SIZE || !IsValidForBattle(&gPlayerParty[chosenMonId])) // just switch to the next mon
{ {
s32 firstId = (IsAiVsAiBattle()) ? 0 : (PARTY_SIZE / 2); s32 firstId = (IsAiVsAiBattle()) ? 0 : (PARTY_SIZE / 2);
@ -303,12 +302,14 @@ static void PlayerPartnerHandleChoosePokemon(u32 battler)
} }
} }
gBattleStruct->monToSwitchIntoId[battler] = chosenMonId; gBattleStruct->monToSwitchIntoId[battler] = chosenMonId;
GetBattlerPartyState(battler)->sentOut = TRUE;
} }
else // Mon to switch out has been already chosen. else // Mon to switch out has been already chosen.
{ {
chosenMonId = gBattleStruct->monToSwitchIntoId[battler]; chosenMonId = gBattleStruct->monToSwitchIntoId[battler];
gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE; gBattleStruct->AI_monToSwitchIntoId[battler] = PARTY_SIZE;
gBattleStruct->monToSwitchIntoId[battler] = chosenMonId; gBattleStruct->monToSwitchIntoId[battler] = chosenMonId;
GetBattlerPartyState(battler)->sentOut = TRUE;
} }
BtlController_EmitChosenMonReturnValue(battler, B_COMM_TO_ENGINE, chosenMonId, NULL); BtlController_EmitChosenMonReturnValue(battler, B_COMM_TO_ENGINE, chosenMonId, NULL);
BtlController_Complete(battler); BtlController_Complete(battler);

View File

@ -236,7 +236,7 @@ EWRAM_DATA u16 gBattleTurnCounter = 0;
EWRAM_DATA u8 gBattlerAbility = 0; EWRAM_DATA u8 gBattlerAbility = 0;
EWRAM_DATA struct QueuedStatBoost gQueuedStatBoosts[MAX_BATTLERS_COUNT] = {0}; EWRAM_DATA struct QueuedStatBoost gQueuedStatBoosts[MAX_BATTLERS_COUNT] = {0};
EWRAM_DATA bool8 gHasFetchedBall = FALSE; EWRAM_DATA bool8 gHasFetchedBall = FALSE;
EWRAM_DATA u8 gLastUsedBall = 0; EWRAM_DATA u16 gLastUsedBall = 0;
EWRAM_DATA u16 gLastThrownBall = 0; EWRAM_DATA u16 gLastThrownBall = 0;
EWRAM_DATA u16 gBallToDisplay = 0; EWRAM_DATA u16 gBallToDisplay = 0;
EWRAM_DATA bool8 gLastUsedBallMenuPresent = FALSE; EWRAM_DATA bool8 gLastUsedBallMenuPresent = FALSE;
@ -3325,7 +3325,7 @@ const u8* FaintClearSetData(u32 battler)
gProtectStructs[battler].quash = FALSE; gProtectStructs[battler].quash = FALSE;
gProtectStructs[battler].noValidMoves = FALSE; gProtectStructs[battler].noValidMoves = FALSE;
gProtectStructs[battler].helpingHand = FALSE; gProtectStructs[battler].helpingHand = 0;
gProtectStructs[battler].bounceMove = FALSE; gProtectStructs[battler].bounceMove = FALSE;
gProtectStructs[battler].stealMove = FALSE; gProtectStructs[battler].stealMove = FALSE;
gProtectStructs[battler].nonVolatileStatusImmobility = FALSE; gProtectStructs[battler].nonVolatileStatusImmobility = FALSE;
@ -3735,6 +3735,10 @@ static void DoBattleIntro(void)
gBattleStruct->overworldWeatherDone = FALSE; gBattleStruct->overworldWeatherDone = FALSE;
Ai_InitPartyStruct(); // Save mons party counts, and first 2/4 mons on the battlefield. Ai_InitPartyStruct(); // Save mons party counts, and first 2/4 mons on the battlefield.
// mark all battlers as sent out
for (battler = 0; battler < gBattlersCount; battler++)
GetBattlerPartyState(battler)->sentOut = TRUE;
// Try to set a status to start the battle with // Try to set a status to start the battle with
gBattleStruct->startingStatus = 0; gBattleStruct->startingStatus = 0;
if (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS && GetTrainerStartingStatusFromId(TRAINER_BATTLE_PARAM.opponentB)) if (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS && GetTrainerStartingStatusFromId(TRAINER_BATTLE_PARAM.opponentB))
@ -5562,14 +5566,31 @@ static void HandleEndTurn_FinishBattle(void)
GetMonData(GetBattlerMon(battler), MON_DATA_NICKNAME, gBattleResults.playerMon2Name); GetMonData(GetBattlerMon(battler), MON_DATA_NICKNAME, gBattleResults.playerMon2Name);
} }
} }
else if (!IsOnPlayerSide(battler))
{
HandleSetPokedexFlag(SpeciesToNationalPokedexNum(gBattleMons[battler].species), FLAG_SET_SEEN, gBattleMons[battler].personality);
}
} }
TryPutPokemonTodayOnAir(); TryPutPokemonTodayOnAir();
} }
if (!(gBattleTypeFlags & (BATTLE_TYPE_LINK
| BATTLE_TYPE_EREADER_TRAINER
| BATTLE_TYPE_RECORDED_LINK
| BATTLE_TYPE_TRAINER_HILL
| BATTLE_TYPE_FRONTIER)))
{
for (u32 side = 0; side < NUM_BATTLE_SIDES; side++)
{
struct Pokemon *party = GetSideParty(side);
if (side == B_SIDE_PLAYER && !B_PARTNER_MONS_MARKED_SEEN)
continue;
for (u32 partySlot = 0; partySlot < PARTY_SIZE; partySlot++)
{
if (gBattleStruct->partyState[side][partySlot].sentOut)
HandleSetPokedexFlagFromMon(&party[partySlot], FLAG_SET_SEEN);
}
}
}
if (!(gBattleTypeFlags & (BATTLE_TYPE_LINK if (!(gBattleTypeFlags & (BATTLE_TYPE_LINK
| BATTLE_TYPE_RECORDED_LINK | BATTLE_TYPE_RECORDED_LINK
| BATTLE_TYPE_TRAINER | BATTLE_TYPE_TRAINER

View File

@ -827,7 +827,7 @@ const u8 *const gBattleStringsTable[STRINGID_COUNT] =
[STRINGID_TEAMGAINEDEXP] = COMPOUND_STRING("通过学习装置,\n各自都获得了经验值!\p"), [STRINGID_TEAMGAINEDEXP] = COMPOUND_STRING("通过学习装置,\n各自都获得了经验值!\p"),
[STRINGID_CURRENTMOVECANTSELECT] = COMPOUND_STRING("无法使用{B_BUFF1}!"), [STRINGID_CURRENTMOVECANTSELECT] = COMPOUND_STRING("无法使用{B_BUFF1}!"),
[STRINGID_TARGETISBEINGSALTCURED] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX}\n陷入了盐腌状态!"), [STRINGID_TARGETISBEINGSALTCURED] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX}\n陷入了盐腌状态!"),
[STRINGID_TARGETISHURTBYSALTCURE] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX}受到了\n{B_BUFF1}的伤害。"), [STRINGID_TARGETISHURTBYSALTCURE] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX}受到了\n{B_BUFF1}的伤害。"),
[STRINGID_TARGETCOVEREDINSTICKYCANDYSYRUP] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX}陷入了\n满身糖状态!"), [STRINGID_TARGETCOVEREDINSTICKYCANDYSYRUP] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX}陷入了\n满身糖状态!"),
[STRINGID_SHARPSTEELFLOATS] = COMPOUND_STRING("{B_DEF_TEAM2} 周围\n开始悬浮起尖锐的钢刺!"), [STRINGID_SHARPSTEELFLOATS] = COMPOUND_STRING("{B_DEF_TEAM2} 周围\n开始悬浮起尖锐的钢刺!"),
[STRINGID_SHARPSTEELDMG] = COMPOUND_STRING("尖锐的钢刺扎进了\n{B_DEF_NAME_WITH_PREFIX2}体内!"), [STRINGID_SHARPSTEELDMG] = COMPOUND_STRING("尖锐的钢刺扎进了\n{B_DEF_NAME_WITH_PREFIX2}体内!"),

View File

@ -2804,9 +2804,20 @@ static void Cmd_printselectionstringfromtable(void)
} }
} }
u8 GetBattlerTurnOrderNum(u8 battler) bool32 HasBattlerActedThisTurn(u32 battler)
{ {
s32 i; u32 i;
for (i = 0; i < gCurrentTurnActionNumber; i++)
{
if (gBattlerByTurnOrder[i] == battler)
return TRUE;
}
return FALSE;
}
u32 GetBattlerTurnOrderNum(u32 battler)
{
u32 i;
for (i = 0; i < gBattlersCount; i++) for (i = 0; i < gBattlersCount; i++)
{ {
if (gBattlerByTurnOrder[i] == battler) if (gBattlerByTurnOrder[i] == battler)
@ -3091,8 +3102,8 @@ void SetMoveEffect(u32 battler, u32 effectBattler, bool32 primary, bool32 certai
{ {
gBattlescriptCurrInstr++; gBattlescriptCurrInstr++;
} }
else if (GetBattlerTurnOrderNum(gEffectBattler) > gCurrentTurnActionNumber else if (!HasBattlerActedThisTurn(gEffectBattler)
&& !(GetActiveGimmick(gEffectBattler) == GIMMICK_DYNAMAX)) && GetActiveGimmick(gEffectBattler) != GIMMICK_DYNAMAX)
{ {
gBattleMons[gEffectBattler].volatiles.flinched = TRUE; gBattleMons[gEffectBattler].volatiles.flinched = TRUE;
gBattlescriptCurrInstr++; gBattlescriptCurrInstr++;
@ -3425,7 +3436,7 @@ void SetMoveEffect(u32 battler, u32 effectBattler, bool32 primary, bool32 certai
} }
break; break;
case MOVE_EFFECT_CORE_ENFORCER: case MOVE_EFFECT_CORE_ENFORCER:
if (GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget) if (HasBattlerActedThisTurn(gBattlerTarget)
&& !NoAliveMonsForEitherParty()) && !NoAliveMonsForEitherParty())
{ {
BattleScriptPush(gBattlescriptCurrInstr + 1); BattleScriptPush(gBattlescriptCurrInstr + 1);
@ -5687,6 +5698,7 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect)
case EFFECT_KNOCK_OFF: case EFFECT_KNOCK_OFF:
if (gBattleStruct->battlerState[gBattlerTarget].itemCanBeKnockedOff if (gBattleStruct->battlerState[gBattlerTarget].itemCanBeKnockedOff
&& gBattleMons[gBattlerTarget].item != ITEM_NONE && gBattleMons[gBattlerTarget].item != ITEM_NONE
&& IsBattlerTurnDamaged(gBattlerTarget)
&& IsBattlerAlive(gBattlerAttacker)) && IsBattlerAlive(gBattlerAttacker))
{ {
u32 side = GetBattlerSide(gBattlerTarget); u32 side = GetBattlerSide(gBattlerTarget);
@ -5862,7 +5874,9 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect)
} }
break; break;
case EFFECT_STONE_AXE: case EFFECT_STONE_AXE:
if (!IsHazardOnSide(GetBattlerSide(gBattlerTarget), HAZARDS_STEALTH_ROCK) && IsBattlerAlive(gBattlerAttacker)) if (!IsHazardOnSide(GetBattlerSide(gBattlerTarget), HAZARDS_STEALTH_ROCK)
&& IsBattlerTurnDamaged(gBattlerTarget)
&& IsBattlerAlive(gBattlerAttacker))
{ {
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_POINTEDSTONESFLOAT; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_POINTEDSTONESFLOAT;
BattleScriptPushCursor(); BattleScriptPushCursor();
@ -5871,7 +5885,9 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect)
} }
break; break;
case EFFECT_CEASELESS_EDGE: case EFFECT_CEASELESS_EDGE:
if (gSideTimers[GetBattlerSide(gBattlerTarget)].spikesAmount < 3 && IsBattlerAlive(gBattlerAttacker)) if (gSideTimers[GetBattlerSide(gBattlerTarget)].spikesAmount < 3
&& IsBattlerTurnDamaged(gBattlerTarget)
&& IsBattlerAlive(gBattlerAttacker))
{ {
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SPIKESSCATTERED; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_SPIKESSCATTERED;
BattleScriptPush(gBattlescriptCurrInstr + 1); BattleScriptPush(gBattlescriptCurrInstr + 1);
@ -6865,20 +6881,19 @@ static void Cmd_moveend(void)
if (gBattleStruct->savedAttackerCount > 0) if (gBattleStruct->savedAttackerCount > 0)
{ {
// #if TESTING if (TESTING)
// Test_ExitWithResult(TEST_RESULT_ERROR, "savedAttackerCount is greater than 0! More calls to SaveBattlerAttacker than RestoreBattlerAttacker!"); Test_ExitWithResult(TEST_RESULT_ERROR, 0, "savedAttackerCount is greater than 0! More calls to SaveBattlerAttacker than RestoreBattlerAttacker!", __FILE__, __LINE__);
// #else else
DebugPrintfLevel(MGBA_LOG_WARN, "savedAttackerCount is greater than 0! More calls to SaveBattlerAttacker than RestoreBattlerAttacker!"); DebugPrintfLevel(MGBA_LOG_WARN, "savedAttackerCount is greater than 0! More calls to SaveBattlerAttacker than RestoreBattlerAttacker!");
// #endif
} }
if (gBattleStruct->savedTargetCount > 0) if (gBattleStruct->savedTargetCount > 0)
{ {
// #if TESTING if (TESTING)
// Test_ExitWithResult(TEST_RESULT_ERROR, "savedTargetCount is greater than 0! More calls to SaveBattlerTarget than RestoreBattlerTarget!"); Test_ExitWithResult(TEST_RESULT_ERROR, 0, "savedTargetCount is greater than 0! More calls to SaveBattlerTarget than RestoreBattlerTarget!", __FILE__, __LINE__);
// #else else
DebugPrintfLevel(MGBA_LOG_WARN, "savedTargetCount is greater than 0! More calls to SaveBattlerTarget than RestoreBattlerTarget!"); DebugPrintfLevel(MGBA_LOG_WARN, "savedTargetCount is greater than 0! More calls to SaveBattlerTarget than RestoreBattlerTarget!");
// #endif
} }
gProtectStructs[gBattlerAttacker].shellTrap = FALSE; gProtectStructs[gBattlerAttacker].shellTrap = FALSE;
gBattleStruct->battlerState[gBattlerAttacker].ateBoost = FALSE; gBattleStruct->battlerState[gBattlerAttacker].ateBoost = FALSE;
gSpecialStatuses[gBattlerAttacker].gemBoost = FALSE; gSpecialStatuses[gBattlerAttacker].gemBoost = FALSE;
@ -7160,13 +7175,7 @@ static void Cmd_switchinanim(void)
battler = GetBattlerForBattleScript(cmd->battler); battler = GetBattlerForBattleScript(cmd->battler);
if (!IsOnPlayerSide(battler) GetBattlerPartyState(battler)->sentOut = TRUE;
&& !(gBattleTypeFlags & (BATTLE_TYPE_LINK
| BATTLE_TYPE_EREADER_TRAINER
| BATTLE_TYPE_RECORDED_LINK
| BATTLE_TYPE_TRAINER_HILL
| BATTLE_TYPE_FRONTIER)))
HandleSetPokedexFlag(SpeciesToNationalPokedexNum(gBattleMons[battler].species), FLAG_SET_SEEN, gBattleMons[battler].personality);
gAbsentBattlerFlags &= ~(1u << battler); gAbsentBattlerFlags &= ~(1u << battler);
@ -9455,10 +9464,12 @@ static bool32 ChangeOrderTargetAfterAttacker(void)
u32 i; u32 i;
u8 data[MAX_BATTLERS_COUNT]; u8 data[MAX_BATTLERS_COUNT];
u8 actionsData[MAX_BATTLERS_COUNT]; u8 actionsData[MAX_BATTLERS_COUNT];
u32 attackerTurnOrderNum = GetBattlerTurnOrderNum(gBattlerAttacker);
u32 targetTurnOrderNum = GetBattlerTurnOrderNum(gBattlerTarget);
if (GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget)) if (attackerTurnOrderNum > targetTurnOrderNum)
return FALSE; return FALSE;
if (GetBattlerTurnOrderNum(gBattlerAttacker) + 1 == GetBattlerTurnOrderNum(gBattlerTarget)) if (attackerTurnOrderNum + 1 == targetTurnOrderNum)
return GetGenConfig(GEN_CONFIG_AFTER_YOU_TURN_ORDER) >= GEN_8; return GetGenConfig(GEN_CONFIG_AFTER_YOU_TURN_ORDER) >= GEN_8;
for (i = 0; i < MAX_BATTLERS_COUNT; i++) for (i = 0; i < MAX_BATTLERS_COUNT; i++)
@ -9466,14 +9477,14 @@ static bool32 ChangeOrderTargetAfterAttacker(void)
data[i] = gBattlerByTurnOrder[i]; data[i] = gBattlerByTurnOrder[i];
actionsData[i] = gActionsByTurnOrder[i]; actionsData[i] = gActionsByTurnOrder[i];
} }
if (GetBattlerTurnOrderNum(gBattlerAttacker) == 0 && GetBattlerTurnOrderNum(gBattlerTarget) == 2) if (attackerTurnOrderNum == 0 && targetTurnOrderNum == 2)
{ {
gBattlerByTurnOrder[1] = gBattlerTarget; gBattlerByTurnOrder[1] = gBattlerTarget;
gActionsByTurnOrder[1] = actionsData[2]; gActionsByTurnOrder[1] = actionsData[2];
gBattlerByTurnOrder[2] = data[1]; gBattlerByTurnOrder[2] = data[1];
gActionsByTurnOrder[2] = actionsData[1]; gActionsByTurnOrder[2] = actionsData[1];
} }
else if (GetBattlerTurnOrderNum(gBattlerAttacker) == 0 && GetBattlerTurnOrderNum(gBattlerTarget) == 3) else if (attackerTurnOrderNum == 0 && targetTurnOrderNum == 3)
{ {
gBattlerByTurnOrder[1] = gBattlerTarget; gBattlerByTurnOrder[1] = gBattlerTarget;
gActionsByTurnOrder[1] = actionsData[3]; gActionsByTurnOrder[1] = actionsData[3];
@ -9482,7 +9493,7 @@ static bool32 ChangeOrderTargetAfterAttacker(void)
gBattlerByTurnOrder[3] = data[2]; gBattlerByTurnOrder[3] = data[2];
gActionsByTurnOrder[3] = actionsData[2]; gActionsByTurnOrder[3] = actionsData[2];
} }
else // Attacker == 1, Target == 3 else // attackerTurnOrderNum == 1, targetTurnOrderNum == 3
{ {
gBattlerByTurnOrder[2] = gBattlerTarget; gBattlerByTurnOrder[2] = gBattlerTarget;
gActionsByTurnOrder[2] = actionsData[3]; gActionsByTurnOrder[2] = actionsData[3];
@ -10857,6 +10868,11 @@ static void Cmd_setlightscreen(void)
gBattlescriptCurrInstr = cmd->nextInstr; gBattlescriptCurrInstr = cmd->nextInstr;
} }
// for var lands
#define NO_HIT 0
#define CALC_ACC 1
#define SURE_HIT 2
// for var endured
#define NOT_ENDURED 0 #define NOT_ENDURED 0
#define FOCUS_SASHED 1 #define FOCUS_SASHED 1
#define FOCUS_BANDED 2 #define FOCUS_BANDED 2
@ -10865,7 +10881,6 @@ static void Cmd_tryKO(void)
{ {
CMD_ARGS(const u8 *failInstr); CMD_ARGS(const u8 *failInstr);
bool32 lands = FALSE;
enum BattleMoveEffects effect = GetMoveEffect(gCurrentMove); enum BattleMoveEffects effect = GetMoveEffect(gCurrentMove);
enum ItemHoldEffect holdEffect = GetBattlerHoldEffect(gBattlerTarget, TRUE); enum ItemHoldEffect holdEffect = GetBattlerHoldEffect(gBattlerTarget, TRUE);
u16 targetAbility = GetBattlerAbility(gBattlerTarget); u16 targetAbility = GetBattlerAbility(gBattlerTarget);
@ -10911,24 +10926,28 @@ static void Cmd_tryKO(void)
} }
else else
{ {
if (gBattleMons[gBattlerAttacker].level >= gBattleMons[gBattlerTarget].level u32 lands = NO_HIT;
&& ((gBattleMons[gBattlerTarget].volatiles.lockOn if (gBattleMons[gBattlerTarget].level > gBattleMons[gBattlerAttacker].level)
&& gDisableStructs[gBattlerTarget].battlerWithSureHit == gBattlerAttacker) lands = NO_HIT;
else if ((gBattleMons[gBattlerTarget].volatiles.lockOn && gDisableStructs[gBattlerTarget].battlerWithSureHit == gBattlerAttacker)
|| IsAbilityAndRecord(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), ABILITY_NO_GUARD) || IsAbilityAndRecord(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), ABILITY_NO_GUARD)
|| IsAbilityAndRecord(gBattlerTarget, targetAbility, ABILITY_NO_GUARD))) || IsAbilityAndRecord(gBattlerTarget, targetAbility, ABILITY_NO_GUARD))
{ lands = SURE_HIT;
lands = TRUE; else if (IsSemiInvulnerable(gBattlerTarget, CHECK_ALL))
} lands = NO_HIT;
else else if (!JumpIfMoveAffectedByProtect(gCurrentMove, gBattlerTarget, TRUE, cmd->failInstr))
lands = CALC_ACC;
if (lands == CALC_ACC)
{ {
u16 odds = GetMoveAccuracy(gCurrentMove) + (gBattleMons[gBattlerAttacker].level - gBattleMons[gBattlerTarget].level); u16 odds = GetMoveAccuracy(gCurrentMove) + (gBattleMons[gBattlerAttacker].level - gBattleMons[gBattlerTarget].level);
if (B_SHEER_COLD_ACC >= GEN_7 && effect == EFFECT_SHEER_COLD && !IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_ICE)) if (B_SHEER_COLD_ACC >= GEN_7 && effect == EFFECT_SHEER_COLD && !IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_ICE))
odds -= 10; odds -= 10;
if (RandomPercentage(RNG_ACCURACY, odds) && gBattleMons[gBattlerAttacker].level >= gBattleMons[gBattlerTarget].level) if (RandomPercentage(RNG_ACCURACY, odds) && gBattleMons[gBattlerAttacker].level >= gBattleMons[gBattlerTarget].level)
lands = TRUE; lands = SURE_HIT;
} }
if (lands) if (lands == SURE_HIT)
{ {
if (gDisableStructs[gBattlerTarget].endured) if (gDisableStructs[gBattlerTarget].endured)
{ {
@ -10964,6 +10983,9 @@ static void Cmd_tryKO(void)
} }
} }
} }
#undef NO_HIT
#undef CALC_ACC
#undef SURE_HIT
#undef NOT_ENDURED #undef NOT_ENDURED
#undef FOCUS_SASHED #undef FOCUS_SASHED
#undef FOCUS_BANDED #undef FOCUS_BANDED
@ -11425,7 +11447,7 @@ static void Cmd_trysetencore(void)
gDisableStructs[gBattlerTarget].encoredMove = gBattleMons[gBattlerTarget].moves[i]; gDisableStructs[gBattlerTarget].encoredMove = gBattleMons[gBattlerTarget].moves[i];
gDisableStructs[gBattlerTarget].encoredMovePos = i; gDisableStructs[gBattlerTarget].encoredMovePos = i;
// Encore always lasts 3 turns, but we need to account for a scenario where Encore changes the move during the same turn. // Encore always lasts 3 turns, but we need to account for a scenario where Encore changes the move during the same turn.
if (GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget)) if (HasBattlerActedThisTurn(gBattlerTarget))
gDisableStructs[gBattlerTarget].encoreTimer = 4; gDisableStructs[gBattlerTarget].encoreTimer = 4;
else else
gDisableStructs[gBattlerTarget].encoreTimer = 3; gDisableStructs[gBattlerTarget].encoreTimer = 3;
@ -12731,7 +12753,7 @@ static void Cmd_settaunt(void)
if (B_TAUNT_TURNS >= GEN_5) if (B_TAUNT_TURNS >= GEN_5)
{ {
turns = 4; turns = 4;
if (GetBattlerTurnOrderNum(gBattlerTarget) > GetBattlerTurnOrderNum(gBattlerAttacker)) if (!HasBattlerActedThisTurn(gBattlerTarget))
turns--; // If the target hasn't yet moved this turn, Taunt lasts for only three turns (source: Bulbapedia) turns--; // If the target hasn't yet moved this turn, Taunt lasts for only three turns (source: Bulbapedia)
} }
else if (B_TAUNT_TURNS >= GEN_4) else if (B_TAUNT_TURNS >= GEN_4)
@ -12760,10 +12782,9 @@ static void Cmd_trysethelpinghand(void)
if (IsDoubleBattle() if (IsDoubleBattle()
&& !(gAbsentBattlerFlags & (1u << gBattlerTarget)) && !(gAbsentBattlerFlags & (1u << gBattlerTarget))
&& !gProtectStructs[gBattlerAttacker].helpingHand && !HasBattlerActedThisTurn(gBattlerTarget))
&& !gProtectStructs[gBattlerTarget].helpingHand)
{ {
gProtectStructs[gBattlerTarget].helpingHand = TRUE; gProtectStructs[gBattlerTarget].helpingHand++;
gBattlescriptCurrInstr = cmd->nextInstr; gBattlescriptCurrInstr = cmd->nextInstr;
} }
else else
@ -13944,7 +13965,11 @@ static void Cmd_handleballthrow(void)
{ {
odds = Sqrt(Sqrt(16711680 / odds)); odds = Sqrt(Sqrt(16711680 / odds));
odds = 1048560 / odds; odds = 1048560 / odds;
for (shakes = 0; shakes < maxShakes && Random() < odds; shakes++); for (shakes = 0; shakes < maxShakes; shakes++)
{
if (RandomUniform(RNG_BALLTHROW_SHAKE, 0, MAX_u16) >= odds)
break;
}
} }
BtlController_EmitBallThrowAnim(gBattlerAttacker, B_COMM_TO_CONTROLLER, shakes); BtlController_EmitBallThrowAnim(gBattlerAttacker, B_COMM_TO_CONTROLLER, shakes);
@ -14746,8 +14771,7 @@ static bool32 CriticalCapture(u32 odds)
odds = (odds * (100 + B_CATCHING_CHARM_BOOST)) / 100; odds = (odds * (100 + B_CATCHING_CHARM_BOOST)) / 100;
odds /= 6; odds /= 6;
if (RandomUniform(RNG_BALLTHROW_CRITICAL, 0, MAX_u8) < odds)
if ((Random() % 255) < odds)
return TRUE; return TRUE;
return FALSE; return FALSE;
@ -15168,7 +15192,7 @@ void BS_ItemRestorePP(void)
void BS_TryRevertWeatherForm(void) void BS_TryRevertWeatherForm(void)
{ {
NATIVE_ARGS(); NATIVE_ARGS();
if (TryBattleFormChange(gBattlerTarget, FORM_CHANGE_BATTLE_WEATHER)) if (IsBattlerAlive(gBattlerTarget) && TryBattleFormChange(gBattlerTarget, FORM_CHANGE_BATTLE_WEATHER))
{ {
gBattleScripting.battler = gBattlerTarget; gBattleScripting.battler = gBattlerTarget;
BattleScriptPush(cmd->nextInstr); BattleScriptPush(cmd->nextInstr);
@ -15388,7 +15412,7 @@ void BS_SetPledge(void)
else if ((gChosenActionByBattler[partner] == B_ACTION_USE_MOVE) else if ((gChosenActionByBattler[partner] == B_ACTION_USE_MOVE)
&& IsDoubleBattle() && IsDoubleBattle()
&& IsBattlerAlive(partner) && IsBattlerAlive(partner)
&& GetBattlerTurnOrderNum(gBattlerAttacker) < GetBattlerTurnOrderNum(partner) && !HasBattlerActedThisTurn(partner)
&& !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) && !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)
&& gCurrentMove != partnerMove && gCurrentMove != partnerMove
&& GetMoveEffect(partnerMove) == EFFECT_PLEDGE) && GetMoveEffect(partnerMove) == EFFECT_PLEDGE)
@ -15587,7 +15611,7 @@ void BS_TryUpperHand(void)
u32 abilityDef = GetBattlerAbility(gBattlerTarget); u32 abilityDef = GetBattlerAbility(gBattlerTarget);
u32 prio = GetChosenMovePriority(gBattlerTarget, abilityDef); u32 prio = GetChosenMovePriority(gBattlerTarget, abilityDef);
if (GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget) if (HasBattlerActedThisTurn(gBattlerTarget)
|| gChosenMoveByBattler[gBattlerTarget] == MOVE_NONE || gChosenMoveByBattler[gBattlerTarget] == MOVE_NONE
|| IsBattleMoveStatus(gChosenMoveByBattler[gBattlerTarget]) || IsBattleMoveStatus(gChosenMoveByBattler[gBattlerTarget])
|| prio < 1 || prio < 1
@ -15804,7 +15828,7 @@ void BS_TryQuash(void)
u32 i, j; u32 i, j;
// It's true if foe is faster, has a bigger priority, or switches // It's true if foe is faster, has a bigger priority, or switches
if (GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget)) if (HasBattlerActedThisTurn(gBattlerTarget))
{ {
gBattlescriptCurrInstr = cmd->failInstr; gBattlescriptCurrInstr = cmd->failInstr;
return; return;
@ -17333,7 +17357,7 @@ void BS_SuckerPunchCheck(void)
NATIVE_ARGS(const u8 *failInstr); NATIVE_ARGS(const u8 *failInstr);
if (gProtectStructs[gBattlerTarget].protected == PROTECT_OBSTRUCT) if (gProtectStructs[gBattlerTarget].protected == PROTECT_OBSTRUCT)
gBattlescriptCurrInstr = cmd->failInstr; gBattlescriptCurrInstr = cmd->failInstr;
else if (GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget)) else if (HasBattlerActedThisTurn(gBattlerTarget))
gBattlescriptCurrInstr = cmd->failInstr; gBattlescriptCurrInstr = cmd->failInstr;
else if (IsBattleMoveStatus(gBattleMons[gBattlerTarget].moves[gBattleStruct->chosenMovePositions[gBattlerTarget]]) && !gProtectStructs[gBattlerTarget].noValidMoves) else if (IsBattleMoveStatus(gBattleMons[gBattlerTarget].moves[gBattleStruct->chosenMovePositions[gBattlerTarget]]) && !gProtectStructs[gBattlerTarget].noValidMoves)
gBattlescriptCurrInstr = cmd->failInstr; gBattlescriptCurrInstr = cmd->failInstr;
@ -17420,7 +17444,7 @@ void BS_TryMeFirst(void)
NATIVE_ARGS(const u8 *failInstr); NATIVE_ARGS(const u8 *failInstr);
u16 move = gBattleMons[gBattlerTarget].moves[gBattleStruct->chosenMovePositions[gBattlerTarget]]; u16 move = gBattleMons[gBattlerTarget].moves[gBattleStruct->chosenMovePositions[gBattlerTarget]];
if (IsBattleMoveStatus(move) || IsMoveMeFirstBanned(move) if (IsBattleMoveStatus(move) || IsMoveMeFirstBanned(move)
|| GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget)) || HasBattlerActedThisTurn(gBattlerTarget))
{ {
gBattlescriptCurrInstr = cmd->failInstr; gBattlescriptCurrInstr = cmd->failInstr;
} }
@ -17444,7 +17468,7 @@ void BS_TryMeFirst(void)
void BS_TryElectrify(void) void BS_TryElectrify(void)
{ {
NATIVE_ARGS(const u8 *failInstr); NATIVE_ARGS(const u8 *failInstr);
if (GetBattlerTurnOrderNum(gBattlerAttacker) > GetBattlerTurnOrderNum(gBattlerTarget)) if (HasBattlerActedThisTurn(gBattlerTarget))
{ {
gBattlescriptCurrInstr = cmd->failInstr; gBattlescriptCurrInstr = cmd->failInstr;
} }

View File

@ -1004,7 +1004,7 @@ void TrainerBattleLoadArgsSecondTrainer(const u8 *data)
void SetMapVarsToTrainerA(void) void SetMapVarsToTrainerA(void)
{ {
if (TRAINER_BATTLE_PARAM.objEventLocalIdA != 0) if (TRAINER_BATTLE_PARAM.objEventLocalIdA != LOCALID_NONE)
{ {
gSpecialVar_LastTalked = TRAINER_BATTLE_PARAM.objEventLocalIdA; gSpecialVar_LastTalked = TRAINER_BATTLE_PARAM.objEventLocalIdA;
gSelectedObjectEvent = GetObjectEventIdByLocalIdAndMap(TRAINER_BATTLE_PARAM.objEventLocalIdA, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup); gSelectedObjectEvent = GetObjectEventIdByLocalIdAndMap(TRAINER_BATTLE_PARAM.objEventLocalIdA, gSaveBlock1Ptr->location.mapNum, gSaveBlock1Ptr->location.mapGroup);

View File

@ -4398,14 +4398,15 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
break; break;
case ABILITY_BALL_FETCH: case ABILITY_BALL_FETCH:
if (gBattleMons[battler].item == ITEM_NONE if (gBattleMons[battler].item == ITEM_NONE
&& gBattleResults.catchAttempts[gLastUsedBall - ITEM_ULTRA_BALL] >= 1 && gBattleResults.catchAttempts[ItemIdToBallId(gLastUsedBall)] >= 1
&& !gHasFetchedBall) && !gHasFetchedBall)
{ {
gLastUsedItem = gLastUsedBall;
gBattleScripting.battler = battler; gBattleScripting.battler = battler;
BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, 2, &gLastUsedBall); gBattleMons[battler].item = gLastUsedItem;
BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, REQUEST_HELDITEM_BATTLE, 0, 2, &gLastUsedItem);
MarkBattlerForControllerExec(battler); MarkBattlerForControllerExec(battler);
gHasFetchedBall = TRUE; gHasFetchedBall = TRUE;
gLastUsedItem = gLastUsedBall;
BattleScriptPushCursorAndCallback(BattleScript_BallFetch); BattleScriptPushCursorAndCallback(BattleScript_BallFetch);
effect++; effect++;
} }
@ -6144,6 +6145,7 @@ static u32 ItemRestorePp(u32 battler, u32 itemId, enum ItemCaseId caseID)
else else
BattleScriptCall(BattleScript_BerryPPHealRet); BattleScriptCall(BattleScript_BerryPPHealRet);
gBattleScripting.battler = battler;
BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, i + REQUEST_PPMOVE1_BATTLE, 0, 1, &changedPP); BtlController_EmitSetMonData(battler, B_COMM_TO_CONTROLLER, i + REQUEST_PPMOVE1_BATTLE, 0, 1, &changedPP);
MarkBattlerForControllerExec(battler); MarkBattlerForControllerExec(battler);
if (MOVE_IS_PERMANENT(battler, i)) if (MOVE_IS_PERMANENT(battler, i))
@ -6499,6 +6501,21 @@ static u8 ItemEffectMoveEnd(u32 battler, enum ItemHoldEffect holdEffect)
case HOLD_EFFECT_MIRROR_HERB: case HOLD_EFFECT_MIRROR_HERB:
effect = TryConsumeMirrorHerb(battler, ITEMEFFECT_NONE); effect = TryConsumeMirrorHerb(battler, ITEMEFFECT_NONE);
break; break;
case HOLD_EFFECT_THROAT_SPRAY:
if (IsSoundMove(gCurrentMove)
&& !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)
&& IsBattlerAlive(gBattlerAttacker)
&& IsAnyTargetAffected(gBattlerAttacker)
&& CompareStat(gBattlerAttacker, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN)
&& !NoAliveMonsForEitherParty()) // Don't activate if battle will end
{
gLastUsedItem = gBattleMons[gBattlerAttacker].item;
gBattleScripting.battler = gBattlerAttacker;
SET_STATCHANGER(STAT_SPATK, 1, FALSE);
effect = ITEM_STATS_CHANGE;
BattleScriptCall(BattleScript_AttackerItemStatRaise);
}
break;
default: default:
break; break;
} }
@ -6728,6 +6745,9 @@ u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler)
if (B_BERRIES_INSTANT >= GEN_4) if (B_BERRIES_INSTANT >= GEN_4)
effect = ItemHealHp(battler, gLastUsedItem, caseID, TRUE); effect = ItemHealHp(battler, gLastUsedItem, caseID, TRUE);
break; break;
case HOLD_EFFECT_RESTORE_PP:
effect = ItemRestorePp(battler, gLastUsedItem, caseID);
break;
case HOLD_EFFECT_AIR_BALLOON: case HOLD_EFFECT_AIR_BALLOON:
effect = ITEM_EFFECT_OTHER; effect = ITEM_EFFECT_OTHER;
gBattleScripting.battler = battler; gBattleScripting.battler = battler;
@ -7084,20 +7104,6 @@ u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler)
gLastUsedItem = atkItem; gLastUsedItem = atkItem;
} }
break; break;
case HOLD_EFFECT_THROAT_SPRAY: // Does NOT need to be a damaging move
if (IsSoundMove(gCurrentMove)
&& IsBattlerAlive(gBattlerAttacker)
&& IsAnyTargetAffected(gBattlerAttacker)
&& CompareStat(gBattlerAttacker, STAT_SPATK, MAX_STAT_STAGE, CMP_LESS_THAN)
&& !NoAliveMonsForEitherParty()) // Don't activate if battle will end
{
gLastUsedItem = atkItem;
gBattleScripting.battler = gBattlerAttacker;
SET_STATCHANGER(STAT_SPATK, 1, FALSE);
effect = ITEM_STATS_CHANGE;
BattleScriptCall(BattleScript_AttackerItemStatRaise);
}
break;
default: default:
break; break;
} }
@ -8168,12 +8174,12 @@ static inline u32 CalcMoveBasePower(struct DamageContext *ctx)
} }
break; break;
case EFFECT_PAYBACK: case EFFECT_PAYBACK:
if (GetBattlerTurnOrderNum(battlerAtk) > GetBattlerTurnOrderNum(battlerDef) if (HasBattlerActedThisTurn(battlerDef)
&& (B_PAYBACK_SWITCH_BOOST < GEN_5 || gDisableStructs[battlerDef].isFirstTurn != 2)) && (B_PAYBACK_SWITCH_BOOST < GEN_5 || gDisableStructs[battlerDef].isFirstTurn != 2))
basePower *= 2; basePower *= 2;
break; break;
case EFFECT_BOLT_BEAK: case EFFECT_BOLT_BEAK:
if (GetBattlerTurnOrderNum(battlerAtk) < GetBattlerTurnOrderNum(battlerDef) if (!HasBattlerActedThisTurn(battlerDef)
|| gDisableStructs[battlerDef].isFirstTurn == 2) || gDisableStructs[battlerDef].isFirstTurn == 2)
basePower *= 2; basePower *= 2;
break; break;
@ -8328,8 +8334,9 @@ static inline u32 CalcMoveBasePowerAfterModifiers(struct DamageContext *ctx)
} }
// various effects // various effects
if (gProtectStructs[battlerAtk].helpingHand) for (u32 i = 0; i < gProtectStructs[battlerAtk].helpingHand; i++)
modifier = uq4_12_multiply(modifier, UQ_4_12(1.5)); modifier = uq4_12_multiply(modifier, UQ_4_12(1.5));
if (gSpecialStatuses[battlerAtk].gemBoost) if (gSpecialStatuses[battlerAtk].gemBoost)
modifier = uq4_12_multiply(modifier, uq4_12_add(UQ_4_12(1.0), PercentToUQ4_12(gSpecialStatuses[battlerAtk].gemParam))); modifier = uq4_12_multiply(modifier, uq4_12_add(UQ_4_12(1.0), PercentToUQ4_12(gSpecialStatuses[battlerAtk].gemParam)));
if (gBattleMons[battlerAtk].volatiles.charge && moveType == TYPE_ELECTRIC) if (gBattleMons[battlerAtk].volatiles.charge && moveType == TYPE_ELECTRIC)
@ -11816,7 +11823,7 @@ u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, u32 atkAbility, u
calc = (calc * (100 + atkParam)) / 100; calc = (calc * (100 + atkParam)) / 100;
break; break;
case HOLD_EFFECT_ZOOM_LENS: case HOLD_EFFECT_ZOOM_LENS:
if (GetBattlerTurnOrderNum(battlerAtk) > GetBattlerTurnOrderNum(battlerDef)) if (HasBattlerActedThisTurn(battlerDef))
calc = (calc * (100 + atkParam)) / 100; calc = (calc * (100 + atkParam)) / 100;
break; break;
} }

View File

@ -36,10 +36,10 @@ void BreakStringAutomatic(u8 *src, u32 maxWidth, u32 screenLines, u8 fontId, enu
{ {
if (src[currIndex] == CHAR_PROMPT_CLEAR) if (src[currIndex] == CHAR_PROMPT_CLEAR)
{ {
u8 replacedChar = src[currIndex + 1]; u8 replacedChar = src[currIndex];
src[currIndex + 1] = EOS; src[currIndex] = EOS;
BreakSubStringAutomatic(currSrc, maxWidth, screenLines, fontId, toggleScrollPrompt); BreakSubStringAutomatic(currSrc, maxWidth, screenLines, fontId, toggleScrollPrompt);
src[currIndex + 1] = replacedChar; src[currIndex] = replacedChar;
currSrc = &src[currIndex + 1]; currSrc = &src[currIndex + 1];
} }
currIndex++; currIndex++;

View File

@ -4477,7 +4477,7 @@ bool32 DoesMonMeetAdditionalConditions(struct Pokemon *mon, const struct Evoluti
{ {
// Gen 2 // Gen 2
case IF_GENDER: case IF_GENDER:
if (gender == GetMonGender(mon)) if (gender == params[i].arg1)
currentCondition = TRUE; currentCondition = TRUE;
break; break;
case IF_MIN_FRIENDSHIP: case IF_MIN_FRIENDSHIP:
@ -6349,6 +6349,14 @@ void HandleSetPokedexFlag(enum NationalDexOrder nationalNum, u8 caseId, u32 pers
} }
} }
void HandleSetPokedexFlagFromMon(struct Pokemon *mon, u32 caseId)
{
u32 personality = GetMonData(mon, MON_DATA_PERSONALITY);
enum NationalDexOrder nationalNum = SpeciesToNationalPokedexNum(GetMonData(mon, MON_DATA_SPECIES));
HandleSetPokedexFlag(nationalNum, caseId, personality);
}
bool8 HasTwoFramesAnimation(u16 species) bool8 HasTwoFramesAnimation(u16 species)
{ {
return P_TWO_FRAME_FRONT_SPRITES return P_TWO_FRAME_FRONT_SPRITES

View File

@ -10086,7 +10086,7 @@ void UpdateSpeciesSpritePSS(struct BoxPokemon *boxMon)
DestroyBoxMonIconAtPosition(sCursorPosition); DestroyBoxMonIconAtPosition(sCursorPosition);
CreateBoxMonIconAtPos(sCursorPosition); CreateBoxMonIconAtPos(sCursorPosition);
if (sStorage->boxOption == OPTION_MOVE_ITEMS) if (sStorage->boxOption == OPTION_MOVE_ITEMS)
SetBoxMonIconObjMode(sCursorPosition, (GetBoxMonData(boxMon, MON_DATA_HELD_ITEM) == ITEM_NONE ? ST_OAM_OBJ_NORMAL : ST_OAM_OBJ_BLEND)); SetBoxMonIconObjMode(sCursorPosition, (GetBoxMonData(boxMon, MON_DATA_HELD_ITEM) == ITEM_NONE ? ST_OAM_OBJ_BLEND : ST_OAM_OBJ_NORMAL));
} }
} }
sJustOpenedBag = FALSE; sJustOpenedBag = FALSE;

View File

@ -373,7 +373,7 @@ bool8 CheckForTrainersWantingBattle(void)
if (!gObjectEvents[i].active) if (!gObjectEvents[i].active)
continue; continue;
if (gObjectEvents[i].trainerType != TRAINER_TYPE_NORMAL && gObjectEvents[i].trainerType != TRAINER_TYPE_BURIED) if (gObjectEvents[i].trainerType != TRAINER_TYPE_NORMAL && gObjectEvents[i].trainerType != TRAINER_TYPE_SEE_ALL_DIRECTIONS && gObjectEvents[i].trainerType != TRAINER_TYPE_BURIED)
continue; continue;
numTrainers = CheckTrainer(i); numTrainers = CheckTrainer(i);

View File

@ -1,7 +1,121 @@
#include "global.h" #include "global.h"
#include "test/battle.h" #include "test/battle.h"
TO_DO_BATTLE_TEST("Ball Fetch causes the Pokémon to pick up the last failed Ball at the end of the turn"); WILD_BATTLE_TEST("Ball Fetch causes the Pokémon to pick up the last failed Ball at the end of the turn")
TO_DO_BATTLE_TEST("Ball Fetch doesn't trigger if the Pokémon is already holding an item"); {
TO_DO_BATTLE_TEST("Ball Fetch only picks up the first failed ball, once per battle"); // Bestow can help test this u32 item = 0;
PARAMETRIZE { item = ITEM_POKE_BALL; }
PARAMETRIZE { item = ITEM_GREAT_BALL; }
PARAMETRIZE { item = ITEM_ULTRA_BALL; }
PARAMETRIZE { item = ITEM_STRANGE_BALL; }
PARAMETRIZE { item = ITEM_X_ACCURACY; }
GIVEN {
PLAYER(SPECIES_YAMPER) { Ability(ABILITY_BALL_FETCH); }
OPPONENT(SPECIES_METAGROSS);
} WHEN {
TURN { USE_ITEM(player, item, WITH_RNG(RNG_BALLTHROW_SHAKE, MAX_u16) );}
TURN {}
} SCENE {
if (item != ITEM_X_ACCURACY)
ABILITY_POPUP(player, ABILITY_BALL_FETCH);
else
NOT ABILITY_POPUP(player, ABILITY_BALL_FETCH);
} THEN {
if (item != ITEM_X_ACCURACY)
EXPECT_EQ(player->item, item);
else
EXPECT_EQ(player->item, ITEM_NONE);
}
}
WILD_BATTLE_TEST("Ball Fetch doesn't trigger if the Pokémon is already holding an item")
{
u32 item = 0;
PARAMETRIZE { item = ITEM_NONE; }
PARAMETRIZE { item = ITEM_NUGGET; }
GIVEN {
PLAYER(SPECIES_YAMPER) { Ability(ABILITY_BALL_FETCH); Item(item); }
OPPONENT(SPECIES_METAGROSS);
} WHEN {
TURN { USE_ITEM(player, ITEM_GREAT_BALL, WITH_RNG(RNG_BALLTHROW_SHAKE, MAX_u16)); }
} SCENE {
if (item == ITEM_NONE)
{
MESSAGE("You used Great Ball!");
ABILITY_POPUP(player, ABILITY_BALL_FETCH);
MESSAGE("Yamper found a Great Ball!");
}
else
{
NONE_OF
{
ABILITY_POPUP(player, ABILITY_BALL_FETCH);
MESSAGE("Yamper found a Great Ball!");
}
}
} THEN {
if (item == ITEM_NONE)
EXPECT_EQ(player->item, ITEM_GREAT_BALL);
else
EXPECT_EQ(player->item, item);
}
}
WILD_BATTLE_TEST("Ball Fetch only picks up the first failed ball, once per battle")
{
u32 item = 0;
u32 item2 = 0;
PARAMETRIZE { item = ITEM_GREAT_BALL; item2 = ITEM_X_ACCURACY; }
PARAMETRIZE { item = ITEM_GREAT_BALL; item2 = ITEM_ULTRA_BALL; }
PARAMETRIZE { item = ITEM_GREAT_BALL; item2 = ITEM_FAST_BALL; }
PARAMETRIZE { item = ITEM_GREAT_BALL; item2 = ITEM_STRANGE_BALL; }
GIVEN {
PLAYER(SPECIES_YAMPER) { Ability(ABILITY_BALL_FETCH); }
OPPONENT(SPECIES_METAGROSS);
} WHEN {
TURN { USE_ITEM(player, item, WITH_RNG(RNG_BALLTHROW_SHAKE, MAX_u16)); }
TURN { MOVE(player, MOVE_BESTOW); }
TURN { USE_ITEM(player, item2, WITH_RNG(RNG_BALLTHROW_SHAKE, MAX_u16)); }
} SCENE {
MESSAGE("You used Great Ball!");
ABILITY_POPUP(player, ABILITY_BALL_FETCH);
MESSAGE("Yamper found a Great Ball!");
MESSAGE("Yamper used Bestow!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_BESTOW, player);
MESSAGE("The wild Metagross received Great Ball from Yamper!");
NOT ABILITY_POPUP(player, ABILITY_BALL_FETCH);
} THEN {
EXPECT_EQ(player->item, ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Ball Fetch doesn't trigger in Trainer Battles")
{
u32 item = 0;
PARAMETRIZE { item = ITEM_POKE_BALL; }
PARAMETRIZE { item = ITEM_GREAT_BALL; }
PARAMETRIZE { item = ITEM_ULTRA_BALL; }
PARAMETRIZE { item = ITEM_STRANGE_BALL; }
PARAMETRIZE { item = ITEM_X_ACCURACY; }
GIVEN {
PLAYER(SPECIES_YAMPER) { Ability(ABILITY_BALL_FETCH); }
OPPONENT(SPECIES_METAGROSS);
} WHEN {
TURN { USE_ITEM(player, item, WITH_RNG(RNG_BALLTHROW_SHAKE, 0)); }
} SCENE {
NOT ABILITY_POPUP(player, ABILITY_BALL_FETCH);
} THEN {
EXPECT_EQ(player->item, ITEM_NONE);
}
}
TO_DO_BATTLE_TEST("Ball Fetch doesn't trigger in Max Raid Battles"); TO_DO_BATTLE_TEST("Ball Fetch doesn't trigger in Max Raid Battles");

View File

@ -40,6 +40,7 @@ DOUBLE_BATTLE_TEST("Overcoat blocks damage from sandstorm")
DOUBLE_BATTLE_TEST("Overcoat blocks damage from hail") DOUBLE_BATTLE_TEST("Overcoat blocks damage from hail")
{ {
GIVEN { GIVEN {
ASSUME(GetMoveEffect(MOVE_HAIL) == EFFECT_HAIL);
PLAYER(SPECIES_WYNAUT) { Speed(50); Ability(ABILITY_SNOW_CLOAK); } PLAYER(SPECIES_WYNAUT) { Speed(50); Ability(ABILITY_SNOW_CLOAK); }
PLAYER(SPECIES_SOLOSIS) { Speed(40); Ability(ABILITY_RUN_AWAY); } PLAYER(SPECIES_SOLOSIS) { Speed(40); Ability(ABILITY_RUN_AWAY); }
OPPONENT(SPECIES_PINECO) { Speed(30); Ability(ABILITY_OVERCOAT); } OPPONENT(SPECIES_PINECO) { Speed(30); Ability(ABILITY_OVERCOAT); }

View File

@ -54,6 +54,7 @@ SINGLE_BATTLE_TEST("Slush Rush doesn't prevent non-Ice types from taking damage
GIVEN { GIVEN {
ASSUME(GetSpeciesType(SPECIES_WOBBUFFET, 0) != TYPE_ICE); ASSUME(GetSpeciesType(SPECIES_WOBBUFFET, 0) != TYPE_ICE);
ASSUME(GetSpeciesType(SPECIES_WOBBUFFET, 1) != TYPE_ICE); ASSUME(GetSpeciesType(SPECIES_WOBBUFFET, 1) != TYPE_ICE);
ASSUME(GetMoveEffect(MOVE_HAIL) == EFFECT_HAIL);
PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_CETITAN) { Ability(ABILITY_SLUSH_RUSH); } OPPONENT(SPECIES_CETITAN) { Ability(ABILITY_SLUSH_RUSH); }
} WHEN { } WHEN {

View File

@ -249,7 +249,11 @@ AI_DOUBLE_BATTLE_TEST("AI can use all moves, 201-300")
case EFFECT_HEAL_BELL: case EFFECT_HEAL_BELL:
case EFFECT_SUNNY_DAY: case EFFECT_SUNNY_DAY:
case EFFECT_RAIN_DANCE: case EFFECT_RAIN_DANCE:
#if B_PREFERRED_ICE_WEATHER == B_ICE_WEATHER_SNOW
case EFFECT_SNOWSCAPE:
#else
case EFFECT_HAIL: case EFFECT_HAIL:
#endif
case EFFECT_ROLE_PLAY: case EFFECT_ROLE_PLAY:
case EFFECT_REFRESH: case EFFECT_REFRESH:

View File

@ -3,8 +3,9 @@
SINGLE_BATTLE_TEST("Aegislash reverts to Shield Form upon fainting") SINGLE_BATTLE_TEST("Aegislash reverts to Shield Form upon fainting")
{ {
KNOWN_FAILING;
GIVEN { GIVEN {
PLAYER(SPECIES_AEGISLASH_SHIELD) { HP(1); } PLAYER(SPECIES_AEGISLASH_BLADE) { HP(1); }
PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET);
} WHEN { } WHEN {
@ -16,3 +17,26 @@ SINGLE_BATTLE_TEST("Aegislash reverts to Shield Form upon fainting")
EXPECT_EQ(GetMonData(&PLAYER_PARTY[0], MON_DATA_SPECIES), SPECIES_AEGISLASH_SHIELD); EXPECT_EQ(GetMonData(&PLAYER_PARTY[0], MON_DATA_SPECIES), SPECIES_AEGISLASH_SHIELD);
} }
} }
DOUBLE_BATTLE_TEST("Causing a Forecast or Flower Gift Pokémon to faint should not cause a message") // issue 7795
{
u32 species;
PARAMETRIZE { species = SPECIES_CASTFORM; }
PARAMETRIZE { species = SPECIES_CHERRIM; }
GIVEN {
PLAYER(SPECIES_WYNAUT);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_VULPIX) { Ability(ABILITY_DROUGHT); }
OPPONENT(species) { HP(1); }
} WHEN {
TURN { MOVE(playerRight, MOVE_GYRO_BALL, target: opponentRight); }
} SCENE {
if (species == SPECIES_CASTFORM) {
MESSAGE("The opposing Castform fainted!");
NOT MESSAGE("The opposing Castform transformed!");
} else {
MESSAGE("The opposing Cherrim fainted!");
NOT MESSAGE("The opposing Cherrim transformed!");
}
}
}

View File

@ -838,7 +838,11 @@ SINGLE_BATTLE_TEST("Dynamax: Max Geyser sets up heavy rain")
} }
} }
#if B_PREFERRED_ICE_WEATHER == B_ICE_WEATHER_SNOW
SINGLE_BATTLE_TEST("Dynamax: Max Hailstorm sets up snow")
#else
SINGLE_BATTLE_TEST("Dynamax: Max Hailstorm sets up hail") SINGLE_BATTLE_TEST("Dynamax: Max Hailstorm sets up hail")
#endif
{ {
GIVEN { GIVEN {
ASSUME(MoveHasAdditionalEffect(MOVE_MAX_HAILSTORM, MOVE_EFFECT_HAIL)); ASSUME(MoveHasAdditionalEffect(MOVE_MAX_HAILSTORM, MOVE_EFFECT_HAIL));
@ -848,9 +852,15 @@ SINGLE_BATTLE_TEST("Dynamax: Max Hailstorm sets up hail")
TURN { MOVE(player, MOVE_POWDER_SNOW, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_CELEBRATE); } TURN { MOVE(player, MOVE_POWDER_SNOW, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_CELEBRATE); }
} SCENE { } SCENE {
MESSAGE("Wobbuffet used Max Hailstorm!"); MESSAGE("Wobbuffet used Max Hailstorm!");
#if B_PREFERRED_ICE_WEATHER == B_ICE_WEATHER_SNOW
MESSAGE("It started to snow!");
MESSAGE("The opposing Wobbuffet used Celebrate!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SNOW_CONTINUES);
#else
MESSAGE("It started to hail!"); MESSAGE("It started to hail!");
MESSAGE("The opposing Wobbuffet used Celebrate!"); MESSAGE("The opposing Wobbuffet used Celebrate!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HAIL_CONTINUES); ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HAIL_CONTINUES);
#endif
} }
} }

View File

@ -0,0 +1,23 @@
#include "global.h"
#include "test/battle.h"
ASSUMPTIONS
{
ASSUME(gItemsInfo[ITEM_LEPPA_BERRY].holdEffect == HOLD_EFFECT_RESTORE_PP);
}
SINGLE_BATTLE_TEST("Restore PP berry activates immediately on switch in")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LEPPA_BERRY); MovesWithPP({MOVE_SCRATCH, 0}, {MOVE_CELEBRATE, 20}); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(opponent, MOVE_POUND); MOVE(player, MOVE_CELEBRATE); }
} SCENE {
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player);
} THEN {
EXPECT(player->item == ITEM_NONE);
}
}

View File

@ -88,3 +88,32 @@ SINGLE_BATTLE_TEST("Throat Spray does not activate if move fails")
} }
} }
} }
SINGLE_BATTLE_TEST("Throat Spray does not activate if user flinches")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_THROAT_SPRAY); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_FAKE_OUT); MOVE(player, MOVE_HYPER_VOICE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FAKE_OUT, opponent);
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPER_VOICE, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player);
}
}
}
SINGLE_BATTLE_TEST("Throat Spray is not blocked by Sheer Force")
{
GIVEN {
PLAYER(SPECIES_NIDOKING) { Ability(ABILITY_SHEER_FORCE); Item(ITEM_THROAT_SPRAY); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_BUG_BUZZ); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_BUG_BUZZ, player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, player);
}
}

View File

@ -78,3 +78,19 @@ SINGLE_BATTLE_TEST("Ceaseless Edge fails to set up hazards if user faints")
NOT MESSAGE("Spikes were scattered on the ground all around the opposing team!"); NOT MESSAGE("Spikes were scattered on the ground all around the opposing team!");
} }
} }
SINGLE_BATTLE_TEST("Ceaseless Edge does not set up hazards if target was not hit")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_PROTECT); MOVE(player, MOVE_CEASELESS_EDGE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponent);
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CEASELESS_EDGE, player);
MESSAGE("Spikes were scattered on the ground all around the opposing team!");
}
}
}

View File

@ -36,4 +36,38 @@ DOUBLE_BATTLE_TEST("Flower Shield raises the defense of all Grass-type Pokémon"
} }
} }
TO_DO_BATTLE_TEST("Flower Shield fails if there's no Grass-type Pokémon on the field") SINGLE_BATTLE_TEST("Flower Shield fails if there's no Grass-type Pokémon on the field")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_FLOWER_SHIELD); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_FLOWER_SHIELD, player);
}
}
DOUBLE_BATTLE_TEST("Flower Shield doesn't affect Grass-type Pokémon that are in a semi-invulnerable position")
{
GIVEN {
PLAYER(SPECIES_BULBASAUR);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_BULBASAUR);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN {
MOVE(opponentLeft, MOVE_FLY, target: playerLeft);
MOVE(playerLeft, MOVE_FLOWER_SHIELD);
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FLY, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_FLOWER_SHIELD, playerLeft);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerLeft);
NONE_OF {
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentLeft);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, playerRight);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight);
}
}
}

View File

@ -1,4 +1,125 @@
#include "global.h" #include "global.h"
#include "test/battle.h" #include "test/battle.h"
ASSUMPTIONS
{
ASSUME(GetMoveEffect(MOVE_HELPING_HAND) == EFFECT_HELPING_HAND);
}
SINGLE_BATTLE_TEST("Helping Hand fails in a Single Battle")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_HELPING_HAND); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_HELPING_HAND, player);
MESSAGE("But it failed!");
}
}
DOUBLE_BATTLE_TEST("Helping Hand fails if ally already acted")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerLeft, MOVE_HELPING_HAND, target: playerRight); MOVE(playerRight, MOVE_HELPING_HAND, target: playerLeft); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_HELPING_HAND, playerLeft);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_HELPING_HAND, playerRight);
}
}
DOUBLE_BATTLE_TEST("Helping Hand boosts the power of attacking moves by 50%", s16 damage)
{
bool32 useHelpingHand;
PARAMETRIZE { useHelpingHand = FALSE; }
PARAMETRIZE { useHelpingHand = TRUE; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
if (useHelpingHand)
TURN { MOVE(playerRight, MOVE_HELPING_HAND, target: playerLeft); MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); }
else
TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerLeft);
HP_BAR(opponentLeft, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage);
}
}
DOUBLE_BATTLE_TEST("Helping Hand still boosts moves used due to Instruct", s16 damage)
{
bool32 useHelpingHand;
PARAMETRIZE { useHelpingHand = FALSE; }
PARAMETRIZE { useHelpingHand = TRUE; }
GIVEN {
ASSUME(GetMoveEffect(MOVE_INSTRUCT) == EFFECT_INSTRUCT);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
if (useHelpingHand)
{
TURN { MOVE(playerRight, MOVE_HELPING_HAND, target: playerLeft);
MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft);
MOVE(opponentLeft, MOVE_INSTRUCT, target: playerLeft); }
}
else
{
TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft);
MOVE(opponentLeft, MOVE_INSTRUCT, target: playerLeft); }
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerLeft);
HP_BAR(opponentLeft, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, Q_4_12(1.5), results[1].damage);
}
}
DOUBLE_BATTLE_TEST("Helping Hand boosts the power of attacking moves by 125% if Instructed into using it again", s16 damage)
{
bool32 useHelpingHandTwice;
PARAMETRIZE { useHelpingHandTwice = FALSE; }
PARAMETRIZE { useHelpingHandTwice = TRUE; }
GIVEN {
ASSUME(GetMoveEffect(MOVE_INSTRUCT) == EFFECT_INSTRUCT);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
if (useHelpingHandTwice)
TURN { MOVE(playerRight, MOVE_HELPING_HAND, target: playerLeft);
MOVE(opponentLeft, MOVE_INSTRUCT, target: playerRight);
MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); }
else
TURN { MOVE(playerLeft, MOVE_SCRATCH, target: opponentLeft); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, playerLeft);
HP_BAR(opponentLeft, captureDamage: &results[i].damage);
} FINALLY {
EXPECT_MUL_EQ(results[0].damage, Q_4_12(2.25), results[1].damage);
}
}
TO_DO_BATTLE_TEST("TODO: Write Helping Hand (Move Effect) test titles") TO_DO_BATTLE_TEST("TODO: Write Helping Hand (Move Effect) test titles")

View File

@ -1,4 +1,188 @@
#include "global.h" #include "global.h"
#include "test/battle.h" #include "test/battle.h"
TO_DO_BATTLE_TEST("TODO: Write Lash Out (Move Effect) test titles") SINGLE_BATTLE_TEST("Lash Out damage is boosted if the user's stats are dropped the turn it is used")
{
s16 damage[2];
u32 move = MOVE_NONE;
PARAMETRIZE { move = MOVE_TACKLE; }
PARAMETRIZE { move = MOVE_GROWL; }
PARAMETRIZE { move = MOVE_LEER; }
PARAMETRIZE { move = MOVE_STRING_SHOT; }
PARAMETRIZE { move = MOVE_CONFIDE; }
PARAMETRIZE { move = MOVE_SAND_ATTACK; }
PARAMETRIZE { move = MOVE_SWEET_SCENT; }
PARAMETRIZE { move = MOVE_CHARM; }
PARAMETRIZE { move = MOVE_SCREECH; }
PARAMETRIZE { move = MOVE_SCARY_FACE; }
PARAMETRIZE { move = MOVE_CAPTIVATE; }
PARAMETRIZE { move = MOVE_EERIE_IMPULSE; }
PARAMETRIZE { move = MOVE_FAKE_TEARS; }
PARAMETRIZE { move = MOVE_NOBLE_ROAR; }
PARAMETRIZE { move = MOVE_LUNGE; }
PARAMETRIZE { move = MOVE_FIRE_LASH; }
PARAMETRIZE { move = MOVE_BULLDOZE; }
PARAMETRIZE { move = MOVE_MYSTICAL_FIRE; }
PARAMETRIZE { move = MOVE_BUG_BUZZ; }
GIVEN {
PLAYER(SPECIES_GRIMMSNARL) { Gender(MON_MALE); Speed(1); Moves(MOVE_LASH_OUT); }
OPPONENT(SPECIES_GOLEM) { Gender(MON_FEMALE); Speed(2); Moves(move, MOVE_CELEBRATE); }
} WHEN {
TURN { MOVE(player, MOVE_LASH_OUT); MOVE(opponent, MOVE_CELEBRATE); }
TURN { MOVE(player, MOVE_LASH_OUT); MOVE(opponent, move); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_LASH_OUT, player);
HP_BAR(opponent, captureDamage: &damage[0]);
ANIMATION(ANIM_TYPE_MOVE, move, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_LASH_OUT, player);
HP_BAR(opponent, captureDamage: &damage[1]);
} THEN {
if (move == MOVE_TACKLE || move == MOVE_CHARM)
EXPECT_EQ(damage[0], damage[1]);
else if (move == MOVE_GROWL || move == MOVE_LUNGE || move == MOVE_NOBLE_ROAR)
EXPECT_MUL_EQ(damage[0], UQ_4_12(1.33), damage[1]);
else
EXPECT_MUL_EQ(damage[0], UQ_4_12(2.00), damage[1]);
}
}
SINGLE_BATTLE_TEST("Lash Out damage is only boosted on the turn that Intimidate switches in")
{
s16 damage[3] = {0};
u32 move = MOVE_NONE;
PARAMETRIZE { move = MOVE_LASH_OUT; }
PARAMETRIZE { move = MOVE_SPLASH; }
GIVEN {
PLAYER(SPECIES_GRIMMSNARL) { Moves(move, MOVE_CELEBRATE, MOVE_LASH_OUT); }
OPPONENT(SPECIES_INCINEROAR) { Ability(ABILITY_BLAZE); Moves(MOVE_CELEBRATE); }
OPPONENT(SPECIES_INCINEROAR) { Ability(ABILITY_INTIMIDATE); Moves(MOVE_CELEBRATE); }
} WHEN {
TURN { MOVE(player, MOVE_LASH_OUT); MOVE(opponent, MOVE_CELEBRATE); }
TURN { MOVE(player, move); SWITCH(opponent, 1); }
TURN { MOVE(player, MOVE_LASH_OUT); MOVE(opponent, MOVE_CELEBRATE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_LASH_OUT, player);
HP_BAR(opponent, captureDamage: &damage[0]);
ANIMATION(ANIM_TYPE_MOVE, move, player);
if (move == MOVE_LASH_OUT)
HP_BAR(opponent, captureDamage: &damage[1]);
ANIMATION(ANIM_TYPE_MOVE, MOVE_LASH_OUT, player);
HP_BAR(opponent, captureDamage: &damage[2]);
} THEN {
EXPECT_EQ(damage[0], damage[2]);
EXPECT_MUL_EQ(damage[0], (move == MOVE_LASH_OUT ? UQ_4_12(1.33) : UQ_4_12(0.00)), damage[1]);
}
}
SINGLE_BATTLE_TEST("Lash Out damage is boosted on turn 1 by switch in abilities")
{
s16 damage[2] = {0};
u32 species = SPECIES_NONE, ability = ABILITY_NONE;
PARAMETRIZE { species = SPECIES_INCINEROAR, ability = ABILITY_BLAZE; }
PARAMETRIZE { species = SPECIES_INCINEROAR, ability = ABILITY_INTIMIDATE; }
PARAMETRIZE { species = SPECIES_HYDRAPPLE, ability = ABILITY_REGENERATOR; }
PARAMETRIZE { species = SPECIES_HYDRAPPLE, ability = ABILITY_SUPERSWEET_SYRUP; }
GIVEN {
PLAYER(SPECIES_GRIMMSNARL) { Moves(MOVE_LASH_OUT); }
OPPONENT(species) { Ability(ability); }
} WHEN {
TURN { MOVE(player, MOVE_LASH_OUT); }
TURN { MOVE(player, MOVE_LASH_OUT); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_LASH_OUT, player);
HP_BAR(opponent, captureDamage: &damage[0]);
ANIMATION(ANIM_TYPE_MOVE, MOVE_LASH_OUT, player);
HP_BAR(opponent, captureDamage: &damage[1]);
} THEN {
if (ability == ABILITY_INTIMIDATE)
EXPECT_MUL_EQ(damage[0], UQ_4_12(1.33) , damage[1]);
else if (ability == ABILITY_SUPERSWEET_SYRUP)
EXPECT_MUL_EQ(damage[0], UQ_4_12(2.00) , damage[1]);
else
EXPECT_EQ(damage[0], damage[1]);
}
}
DOUBLE_BATTLE_TEST("Lash Out damage is boosted by Cotton Down activation in doubles")
{
s16 damage[2] = {0};
u32 ability = ABILITY_NONE;
PARAMETRIZE { ability = ABILITY_REGENERATOR; }
PARAMETRIZE { ability = ABILITY_COTTON_DOWN; }
GIVEN {
PLAYER(SPECIES_GRIMMSNARL) { Speed(1); Moves(MOVE_LASH_OUT); }
PLAYER(SPECIES_RATTATA) { Speed(2); Moves(MOVE_TACKLE, MOVE_CELEBRATE); }
OPPONENT(SPECIES_ELDEGOSS) { Speed(3); Ability(ability); }
OPPONENT(SPECIES_GOLEM) { Speed(4); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_LASH_OUT, target:opponentRight); MOVE(playerRight, MOVE_TACKLE, target:opponentLeft); }
TURN { MOVE(playerLeft, MOVE_LASH_OUT, target:opponentRight); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerRight);
ANIMATION(ANIM_TYPE_MOVE, MOVE_LASH_OUT, playerLeft);
HP_BAR(opponentRight, captureDamage: &damage[0]);
ANIMATION(ANIM_TYPE_MOVE, MOVE_LASH_OUT, playerLeft);
HP_BAR(opponentRight, captureDamage: &damage[1]);
} THEN {
if (ability == ABILITY_COTTON_DOWN)
EXPECT_MUL_EQ(damage[0], UQ_4_12(2.00) , damage[1]);
else
EXPECT_EQ(damage[0], damage[1]);
}
}
DOUBLE_BATTLE_TEST("Lash Out damage is not boosted by Treasure of Ruin ability activation in doubles")
{
s16 damage[2] = {0};
u32 species = SPECIES_NONE, ability = ABILITY_NONE;
PARAMETRIZE { species = SPECIES_KANGASKHAN, ability = ABILITY_INNER_FOCUS; }
PARAMETRIZE { species = SPECIES_HYDRAPPLE, ability = ABILITY_SUPERSWEET_SYRUP; }
PARAMETRIZE { species = SPECIES_WO_CHIEN, ability = ABILITY_TABLETS_OF_RUIN; }
PARAMETRIZE { species = SPECIES_CHIEN_PAO, ability = ABILITY_SWORD_OF_RUIN; }
PARAMETRIZE { species = SPECIES_TING_LU, ability = ABILITY_VESSEL_OF_RUIN; }
PARAMETRIZE { species = SPECIES_CHI_YU, ability = ABILITY_BEADS_OF_RUIN; }
GIVEN {
PLAYER(SPECIES_GRIMMSNARL) { Moves(MOVE_LASH_OUT); }
PLAYER(SPECIES_RATTATA);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_GOLEM);
OPPONENT(species) { Ability(ability); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_LASH_OUT, target:opponentRight); }
TURN { MOVE(playerLeft, MOVE_LASH_OUT, target:opponentRight); SWITCH(opponentLeft, 2); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_LASH_OUT, playerLeft);
HP_BAR(opponentRight, captureDamage: &damage[0]);
ANIMATION(ANIM_TYPE_MOVE, MOVE_LASH_OUT, playerLeft);
HP_BAR(opponentRight, captureDamage: &damage[1]);
} THEN {
if (ability == ABILITY_SUPERSWEET_SYRUP)
EXPECT_MUL_EQ(damage[0], UQ_4_12(2.00) , damage[1]);
else if (ability == ABILITY_TABLETS_OF_RUIN)
EXPECT_MUL_EQ(damage[0], UQ_4_12(0.75) , damage[1]);
else if (ability == ABILITY_SWORD_OF_RUIN)
EXPECT_MUL_EQ(damage[0], UQ_4_12(1.33) , damage[1]);
else
EXPECT_EQ(damage[0], damage[1]);
}
}

View File

@ -21,6 +21,18 @@ SINGLE_BATTLE_TEST("OHKO moves can hit semi-invulnerable mons when the user has
} }
} }
SINGLE_BATTLE_TEST("OHKO moves can not hit semi-invulnerable")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_FLY); MOVE(player, MOVE_FISSURE); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_FISSURE, player);
}
}
SINGLE_BATTLE_TEST("OHKO moves can can be endured by Focus Sash") SINGLE_BATTLE_TEST("OHKO moves can can be endured by Focus Sash")
{ {
GIVEN { GIVEN {
@ -48,7 +60,29 @@ SINGLE_BATTLE_TEST("OHKO moves can can be endured by Sturdy")
} }
} }
SINGLE_BATTLE_TEST("OHKO moves always fails if the target has a higher level than the user")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Level(1); }
OPPONENT(SPECIES_WOBBUFFET) { Level(2); }
} WHEN {
TURN { MOVE(player, MOVE_FISSURE); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_FISSURE, player);
}
}
SINGLE_BATTLE_TEST("OHKO moves fail if target protects")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_PROTECT); MOVE(player, MOVE_FISSURE); }
} SCENE {
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_FISSURE, player);
}
}
TO_DO_BATTLE_TEST("OHKO moves faints the target, skipping regular damage calculations") TO_DO_BATTLE_TEST("OHKO moves faints the target, skipping regular damage calculations")
TO_DO_BATTLE_TEST("OHKO moves always fails if the target has a higher level than the user")
TO_DO_BATTLE_TEST("OHKO moves's accuracy increases by 1% for every level the user has over the target") TO_DO_BATTLE_TEST("OHKO moves's accuracy increases by 1% for every level the user has over the target")
TO_DO_BATTLE_TEST("OHKO moves's ignores non-stage accuracy modifiers") // Gravity, Wide Lens, Compound Eyes TO_DO_BATTLE_TEST("OHKO moves's ignores non-stage accuracy modifiers") // Gravity, Wide Lens, Compound Eyes

View File

@ -51,3 +51,22 @@ SINGLE_BATTLE_TEST("Sucker Punch doesn't hit targets that has already moved")
} }
} }
} }
DOUBLE_BATTLE_TEST("Sucker Punch fails if the target has attempted to act even if previously successful")
{
GIVEN {
ASSUME(GetMoveEffect(MOVE_INSTRUCT) == EFFECT_INSTRUCT);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponentLeft, MOVE_SCRATCH, target: playerLeft); MOVE(playerLeft, MOVE_SUCKER_PUNCH, target: opponentLeft); MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SUCKER_PUNCH, playerLeft);
HP_BAR(opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerRight);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_SUCKER_PUNCH, playerLeft);
}
}

View File

@ -132,3 +132,25 @@ AI_SINGLE_BATTLE_TEST("AI won't use Upper Hand unless it has seen a priority mov
TURN { MOVE(player, move); EXPECT_MOVE(opponent, move == MOVE_QUICK_ATTACK ? MOVE_UPPER_HAND : MOVE_KARATE_CHOP); } TURN { MOVE(player, move); EXPECT_MOVE(opponent, move == MOVE_QUICK_ATTACK ? MOVE_UPPER_HAND : MOVE_KARATE_CHOP); }
} }
} }
DOUBLE_BATTLE_TEST("Upper Hand fails if the target has attempted to act even if previously successful")
{
GIVEN {
ASSUME(GetMoveCategory(MOVE_EXTREME_SPEED) == DAMAGE_CATEGORY_PHYSICAL);
ASSUME(GetMovePriority(MOVE_EXTREME_SPEED) == 2);
ASSUME(GetMoveEffect(MOVE_INSTRUCT) == EFFECT_INSTRUCT);
PLAYER(SPECIES_MIENSHAO);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponentLeft, MOVE_EXTREME_SPEED, target: playerLeft); MOVE(playerLeft, MOVE_UPPER_HAND, target: opponentLeft); MOVE(playerRight, MOVE_INSTRUCT, target: playerLeft); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_UPPER_HAND, playerLeft);
HP_BAR(opponentLeft);
MESSAGE("The opposing Wobbuffet flinched and couldn't move!");
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_EXTREME_SPEED, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_INSTRUCT, playerRight);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_UPPER_HAND, playerLeft);
}
}

View File

@ -22,3 +22,35 @@ DOUBLE_BATTLE_TEST("Flame Burst Substitute")
NOT MESSAGE("The substitute took damage for the opposing Wynaut!"); NOT MESSAGE("The substitute took damage for the opposing Wynaut!");
} }
} }
DOUBLE_BATTLE_TEST("Flame Burst doesn't crash, opponent to player")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponentRight, MOVE_FLAME_BURST, target: playerLeft); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FLAME_BURST, opponentRight);
HP_BAR(playerRight);
MESSAGE("Wobbuffet fainted!");
}
}
DOUBLE_BATTLE_TEST("Flame Burst doesn't crash, player to opponent")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET)
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WYNAUT) { HP(1); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerLeft, MOVE_FLAME_BURST, target: opponentLeft); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FLAME_BURST, playerLeft);
HP_BAR(opponentRight);
MESSAGE("The opposing Wynaut fainted!");
}
}

View File

@ -132,3 +132,19 @@ SINGLE_BATTLE_TEST("If Salt Cure faints the target, messages will be applied in
MESSAGE("The opposing Wobbuffet fainted!"); MESSAGE("The opposing Wobbuffet fainted!");
} }
} }
DOUBLE_BATTLE_TEST("Salt Cure works in double battles")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { MOVE(playerLeft, MOVE_SALT_CURE, target: opponentLeft); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SALT_CURE, playerLeft);
HP_BAR(opponentLeft);
HP_BAR(opponentLeft);
}
}

View File

@ -2520,6 +2520,9 @@ void UseItem(u32 sourceLine, struct BattlePokemon *battler, struct ItemContext c
{ {
i = 0; i = 0;
} }
if (ctx.explicitRNG)
DATA.battleRecordTurns[DATA.turns][battlerId].rng = ctx.rng;
PushBattlerAction(sourceLine, battlerId, RECORDED_ACTION_TYPE, B_ACTION_USE_ITEM); PushBattlerAction(sourceLine, battlerId, RECORDED_ACTION_TYPE, B_ACTION_USE_ITEM);
PushBattlerAction(sourceLine, battlerId, RECORDED_ITEM_ID, (ctx.itemId >> 8) & 0xFF); PushBattlerAction(sourceLine, battlerId, RECORDED_ITEM_ID, (ctx.itemId >> 8) & 0xFF);
PushBattlerAction(sourceLine, battlerId, RECORDED_ITEM_ID, ctx.itemId & 0xFF); PushBattlerAction(sourceLine, battlerId, RECORDED_ITEM_ID, ctx.itemId & 0xFF);