Merge branch '_RHH/upcoming' into _RHH/pr/upcoming/messages

This commit is contained in:
Eduardo Quezada 2025-08-11 12:39:28 -04:00
commit db0ca2ce49
59 changed files with 745 additions and 441 deletions

View File

@ -179,6 +179,6 @@ If you targeted a specific version that is not the latest version listed on the
# Useful additional tools
* [porymap](https://github.com/huderlem/porymap) for viewing and editing maps
* [porytiles](https://github.com/gruntlucas/porytiles) for add new metatiles for maps
* [porytiles](https://github.com/grunt-lucas/porytiles) for add new metatiles for maps
* [poryscript](https://github.com/huderlem/poryscript) for scripting ([VS Code extension](https://marketplace.visualstudio.com/items?itemName=karathan.poryscript))
* [Tilemap Studio](https://github.com/Rangi42/tilemap-studio) for viewing and editing tilemaps

View File

@ -2827,13 +2827,6 @@ BattleScript_AlreadyAsleep::
setmoveresultflags MOVE_RESULT_FAILED
goto BattleScript_MoveEnd
BattleScript_WasntAffected::
pause B_WAIT_TIME_SHORT
printstring STRINGID_PKMNWASNTAFFECTED
waitmessage B_WAIT_TIME_LONG
setmoveresultflags MOVE_RESULT_FAILED
goto BattleScript_MoveEnd
BattleScript_CantMakeAsleep::
pause B_WAIT_TIME_SHORT
printfromtable gUproarAwakeStringIds
@ -2878,44 +2871,10 @@ BattleScript_MaxHp50Recoil::
BattleScript_EffectDreamEater::
attackcanceler
jumpifsubstituteblocks BattleScript_DreamEaterNoEffect
jumpifstatus BS_TARGET, STATUS1_SLEEP, BattleScript_DreamEaterWorked
jumpifability BS_TARGET, ABILITY_COMATOSE, BattleScript_DreamEaterWorked
BattleScript_DreamEaterNoEffect:
attackstring
ppreduce
waitmessage B_WAIT_TIME_LONG
goto BattleScript_WasntAffected
BattleScript_DreamEaterWorked:
accuracycheck BattleScript_PrintMoveMissed, ACC_CURR_MOVE
attackstring
ppreduce
critcalc
damagecalc
adjustdamage
attackanimation
waitanimation
effectivenesssound
hitanimation BS_TARGET
waitstate
healthbarupdate BS_TARGET
datahpupdate BS_TARGET
critmessage
waitmessage B_WAIT_TIME_LONG
resultmessage
waitmessage B_WAIT_TIME_LONG
jumpifstatus3 BS_ATTACKER, STATUS3_HEAL_BLOCK, BattleScript_DreamEaterTryFaintEnd
setdrainedhp
manipulatedamage DMG_BIG_ROOT
orword gHitMarker, HITMARKER_IGNORE_SUBSTITUTE
healthbarupdate BS_ATTACKER
datahpupdate BS_ATTACKER
jumpifmovehadnoeffect BattleScript_DreamEaterTryFaintEnd
printstring STRINGID_PKMNENERGYDRAINED
waitmessage B_WAIT_TIME_LONG
BattleScript_DreamEaterTryFaintEnd:
tryfaintmon BS_TARGET
goto BattleScript_MoveEnd
jumpifsubstituteblocks BattleScript_DoesntAffectTargetAtkString
jumpifstatus BS_TARGET, STATUS1_SLEEP, BattleScript_HitFromAccCheck
jumpifability BS_TARGET, ABILITY_COMATOSE, BattleScript_HitFromAccCheck
goto BattleScript_DoesntAffectTargetAtkString
BattleScript_EffectMirrorMove::
attackcanceler
@ -5611,6 +5570,7 @@ BattleScript_LeechSeedTurnDrainHealBlock::
BattleScript_LeechSeedTurnDrainRecovery::
call BattleScript_LeechSeedTurnDrain
BattleScript_LeechSeedTurnDrainGainHp:
orword gHitMarker, HITMARKER_IGNORE_SUBSTITUTE | HITMARKER_PASSIVE_DAMAGE
healthbarupdate BS_TARGET
datahpupdate BS_TARGET
printfromtable gLeechSeedStringIds
@ -9156,7 +9116,7 @@ BattleScript_EjectPackActivates::
jumpifcantswitch BS_SCRIPTING, BattleScript_EjectButtonEnd
goto BattleScript_EjectPackActivate_Ret
BattleScript_DarkTypePreventsPrankster::
BattleScript_DoesntAffectTargetAtkString::
attackstring
ppreduce
pause B_WAIT_TIME_SHORT

View File

@ -527,9 +527,9 @@ Edit [src/data/graphics/pokemon.h](https://github.com/rh-hideout/pokeemerald-exp
```diff
#if P_FAMILY_PECHARUNT
const u32 gMonFrontPic_Pecharunt[] = INCBIN_U32("graphics/pokemon/pecharunt/front.4bpp.lz");
const u32 gMonPalette_Pecharunt[] = INCBIN_U32("graphics/pokemon/pecharunt/normal.gbapal.lz");
const u16 gMonPalette_Pecharunt[] = INCBIN_U16("graphics/pokemon/pecharunt/normal.gbapal");
const u32 gMonBackPic_Pecharunt[] = INCBIN_U32("graphics/pokemon/pecharunt/back.4bpp.lz");
const u32 gMonShinyPalette_Pecharunt[] = INCBIN_U32("graphics/pokemon/pecharunt/shiny.gbapal.lz");
const u16 gMonShinyPalette_Pecharunt[] = INCBIN_U16("graphics/pokemon/pecharunt/shiny.gbapal");
const u8 gMonIcon_Pecharunt[] = INCBIN_U8("graphics/pokemon/pecharunt/icon.4bpp");
#if P_FOOTPRINTS
const u8 gMonFootprint_Pecharunt[] = INCBIN_U8("graphics/pokemon/pecharunt/footprint.1bpp");
@ -537,20 +537,20 @@ Edit [src/data/graphics/pokemon.h](https://github.com/rh-hideout/pokeemerald-exp
#if OW_POKEMON_OBJECT_EVENTS
const u32 gObjectEventPic_Pecharunt[] = INCBIN_COMP("graphics/pokemon/pecharunt/overworld.4bpp");
#if OW_PKMN_OBJECTS_SHARE_PALETTES == FALSE
const u32 gOverworldPalette_Pecharunt[] = INCBIN_U32("graphics/pokemon/pecharunt/overworld_normal.gbapal.lz");
const u32 gShinyOverworldPalette_Pecharunt[] = INCBIN_U32("graphics/pokemon/pecharunt/overworld_shiny.gbapal.lz");
const u16 gOverworldPalette_Pecharunt[] = INCBIN_U16("graphics/pokemon/pecharunt/overworld_normal.gbapal");
const u16 gShinyOverworldPalette_Pecharunt[] = INCBIN_U16("graphics/pokemon/pecharunt/overworld_shiny.gbapal");
#endif //OW_PKMN_OBJECTS_SHARE_PALETTES
#endif //OW_POKEMON_OBJECT_EVENTS
#endif //P_FAMILY_PECHARUNT
const u32 gMonFrontPic_Egg[] = INCBIN_U32("graphics/pokemon/egg/anim_front.4bpp.lz");
const u32 gMonPalette_Egg[] = INCBIN_U32("graphics/pokemon/egg/normal.gbapal.lz");
const u16 gMonPalette_Egg[] = INCBIN_U16("graphics/pokemon/egg/normal.gbapal");
const u8 gMonIcon_Egg[] = INCBIN_U8("graphics/pokemon/egg/icon.4bpp");
+ const u32 gMonFrontPic_Mewthree[] = INCBIN_U32("graphics/pokemon/mewthree/anim_front.4bpp.lz");
+ const u32 gMonBackPic_Mewthree[] = INCBIN_U32("graphics/pokemon/mewthree/back.4bpp.lz");
+ const u32 gMonPalette_Mewthree[] = INCBIN_U32("graphics/pokemon/mewthree/normal.gbapal.lz");
+ const u32 gMonShinyPalette_Mewthree[] = INCBIN_U32("graphics/pokemon/mewthree/shiny.gbapal.lz");
+ const u16 gMonPalette_Mewthree[] = INCBIN_U16("graphics/pokemon/mewthree/normal.gbapal");
+ const u16 gMonShinyPalette_Mewthree[] = INCBIN_U16("graphics/pokemon/mewthree/shiny.gbapal");
+ const u8 gMonIcon_Mewthree[] = INCBIN_U8("graphics/pokemon/mewthree/icon.4bpp");
+ const u8 gMonFootprint_Mewthree[] = INCBIN_U8("graphics/pokemon/mewthree/footprint.1bpp");
```

View File

@ -428,7 +428,7 @@ Spaces in pattern match newlines (\n, \l, and \p) in the message.
Often used to check that a battler took its turn but it failed, e.g.:
```
MESSAGE("Wobbuffet used Dream Eater!");
MESSAGE("The opposing Wobbuffet wasn't affected!");
MESSAGE("It doesn't affect the opposing Wobbuffet…");
```
### `STATUS_ICON`

View File

@ -166,7 +166,8 @@ struct ProtectStruct
u16 activateOpportunist:2; // 2 - to copy stats. 1 - stats copied (do not repeat). 0 - no stats to copy
u16 usedAllySwitch:1;
u16 lashOutAffected:1;
u16 padding:4;
u16 assuranceDoubled:1;
u16 padding:3;
// End of 16-bit bitfield
u16 physicalDmg;
u16 specialDmg;
@ -328,12 +329,12 @@ struct AiLogicData
u8 ejectButtonSwitch:1; // Tracks whether current switch out was from Eject Button
u8 ejectPackSwitch:1; // Tracks whether current switch out was from Eject Pack
u8 predictingSwitch:1; // Determines whether AI will use switch predictions this turn or not
u8 predictingMove:1; // Determines whether AI will use move predictions this turn or not
u8 aiPredictionInProgress:1; // Tracks whether the AI is in the middle of running prediction calculations
u8 padding:2;
u8 shouldSwitch; // Stores result of ShouldSwitch, which decides whether a mon should be switched out
u8 aiCalcInProgress:1;
u8 battlerDoingPrediction; // Stores which battler is currently running its prediction calcs
u8 predictingMove:1; // Determines whether AI will use move predictions this turn or not
u8 padding1:1;
u8 shouldSwitch:4; // Stores result of ShouldSwitch, which decides whether a mon should be switched out
u8 padding2:4;
u16 predictedMove[MAX_BATTLERS_COUNT];
};

View File

@ -80,6 +80,7 @@ u32 AI_GetDamage(u32 battlerAtk, u32 battlerDef, u32 moveIndex, enum DamageCalcC
bool32 IsAiVsAiBattle(void);
bool32 BattlerHasAi(u32 battlerId);
bool32 IsAiBattlerAware(u32 battlerId);
bool32 CanAiPredictMove(void);
bool32 IsAiBattlerAssumingStab(void);
bool32 IsAiBattlerAssumingStatusMoves(void);
bool32 ShouldRecordStatusMove(u32 move);
@ -297,7 +298,6 @@ bool32 IsBattlerPredictedToSwitch(u32 battler);
u32 GetIncomingMove(u32 battler, u32 opposingBattler, struct AiLogicData *aiData);
bool32 HasLowAccuracyMove(u32 battlerAtk, u32 battlerDef);
bool32 HasBattlerSideAbility(u32 battlerDef, u32 ability, struct AiLogicData *aiData);
u32 GetThinkingBattler(u32 battler);
bool32 IsNaturalEnemy(u32 speciesAttacker, u32 speciesTarget);
// These are for the purpose of not doubling up on moves during double battles.

View File

@ -426,7 +426,7 @@ extern const u8 BattleScript_MentalHerbCureEnd2[];
extern const u8 BattleScript_TerrainPreventsEnd2[];
extern const u8 BattleScript_MistyTerrainPrevents[];
extern const u8 BattleScript_ElectricTerrainPrevents[];
extern const u8 BattleScript_DarkTypePreventsPrankster[];
extern const u8 BattleScript_DoesntAffectTargetAtkString[];
extern const u8 BattleScript_GulpMissileGorging[];
extern const u8 BattleScript_GulpMissileGulping[];
extern const u8 BattleScript_GulpMissileFormChange[];

View File

@ -52,7 +52,7 @@
#define AI_GOOD_SCORE_THRESHOLD 100 // Move scores above this threshold are considered "good" when deciding switching
// AI held item-based move scoring
#define LOW_ACCURACY_THRESHOLD 75 // Moves with accuracy equal OR below this value are considered low accuracy
#define LOW_ACCURACY_THRESHOLD 75 // Moves with accuracy equal OR below this value are considered low accuracy
// AI move scoring
#define STATUS_MOVE_FOCUS_PUNCH_CHANCE 50 // Chance the AI will use a status move if the player's best move is Focus Punch
@ -73,7 +73,7 @@
// AI Terastalization chances
#define AI_CONSERVE_TERA_CHANCE_PER_MON 10 // Chance for AI with smart tera flag to decide not to tera before considering defensive benefit is this*(X-1), where X is the number of alive pokemon that could tera
#define AI_TERA_PREDICT_CHANCE 40 // Chance for AI with smart tera flag to tera in the situation where tera would save it from a KO, but could be punished by a KO from a different move.
#define AI_TERA_PREDICT_CHANCE 40 // Chance for AI with smart tera flag to tera in the situation where tera would save it from a KO, but could be punished by a KO from a different move.
// AI_FLAG_PP_STALL_PREVENTION settings
#define PP_STALL_DISREGARD_MOVE_PERCENTAGE 50 // Detection chance per roll

View File

@ -131,7 +131,8 @@
#define B_SKIP_RECHARGE GEN_LATEST // In Gen1, recharging moves such as Hyper Beam skip the recharge if the target gets KO'd
#define B_ENCORE_TARGET GEN_LATEST // In Gen5+, encored moves are allowed to choose a target
#define B_TIME_OF_DAY_HEALING_MOVES GEN_LATEST // In Gen2, Morning Sun, Moonlight, and Synthesis heal twice as much HP based off the time of day. Also changes how much they heal. Evening affects Moonlight.
// If OW_TIMES_OF_DAY is set to Gen 3, then Morning Sun is boosted during the day.
// If OW_TIMES_OF_DAY is set to Gen 3, then Morning Sun is boosted during the day.
#define B_DREAM_EATER_LIQUID_OOZE GEN_LATEST // In Gen5+, Dream Eater is affected by Liquid Ooze.
// Ability settings
#define B_GALE_WINGS GEN_LATEST // In Gen7+ requires full HP to trigger.

View File

@ -3,44 +3,41 @@
// AI Flags. Most run specific functions to update score, new flags are used for internal logic in other scripts
// See docs/ai_flags.md for more details.
#define AI_FLAG_CHECK_BAD_MOVE (1 << 0) // AI will avoid using moves that are likely to fail or be ineffective in the current situation.
#define AI_FLAG_TRY_TO_FAINT (1 << 1) // AI will prioritize KOing the player's mon if able.
#define AI_FLAG_CHECK_VIABILITY (1 << 2) // AI damaging moves and move effects to determine the best available move in the current situation.
#define AI_FLAG_FORCE_SETUP_FIRST_TURN (1 << 3) // AI will prioritize using setup moves on the first turn at the expensve of all else. AI_FLAG_CHECK_VIABILITY will instead do this when the AI determines it makes sense.
#define AI_FLAG_RISKY (1 << 4) // AI will generally behave more recklessly, prioritizing damage over accuracy, explosions, etc.
#define AI_FLAG_TRY_TO_2HKO (1 << 5) // AI adds score bonus to any move the AI has that either OHKOs or 2HKOs the player.
#define AI_FLAG_PREFER_BATON_PASS (1 << 6) // AI prefers raising its own stats and setting for / using Baton Pass.
#define AI_FLAG_DOUBLE_BATTLE (1 << 7) // Automatically set for double battles, handles AI behaviour with partner.
#define AI_FLAG_HP_AWARE (1 << 8) // AI will favour certain move effects based on how much remaining HP it and the player's mon have.
#define AI_FLAG_POWERFUL_STATUS (1 << 9) // AI prefers moves that set up field effects or side statuses, even if the user can faint the target.
#define AI_FLAG(x) ((u64)1 << x)
#define AI_FLAG_CHECK_BAD_MOVE AI_FLAG(0) // AI will avoid using moves that are likely to fail or be ineffective in the current situation.
#define AI_FLAG_TRY_TO_FAINT AI_FLAG(1) // AI will prioritize KOing the player's mon if able.
#define AI_FLAG_CHECK_VIABILITY AI_FLAG(2) // AI damaging moves and move effects to determine the best available move in the current situation.
#define AI_FLAG_FORCE_SETUP_FIRST_TURN AI_FLAG(3) // AI will prioritize using setup moves on the first turn at the expensve of all else. AI_FLAG_CHECK_VIABILITY will instead do this when the AI determines it makes sense.
#define AI_FLAG_RISKY AI_FLAG(4) // AI will generally behave more recklessly, prioritizing damage over accuracy, explosions, etc.
#define AI_FLAG_TRY_TO_2HKO AI_FLAG(5) // AI adds score bonus to any move the AI has that either OHKOs or 2HKOs the player.
#define AI_FLAG_PREFER_BATON_PASS AI_FLAG(6) // AI prefers raising its own stats and setting for / using Baton Pass.
#define AI_FLAG_DOUBLE_BATTLE AI_FLAG(7) // Automatically set for double battles, handles AI behaviour with partner.
#define AI_FLAG_HP_AWARE AI_FLAG(8) // AI will favour certain move effects based on how much remaining HP it and the player's mon have.
#define AI_FLAG_POWERFUL_STATUS AI_FLAG(9) // AI prefers moves that set up field effects or side statuses, even if the user can faint the target.
// New, Trainer Handicap Flags
#define AI_FLAG_NEGATE_UNAWARE (1 << 10) // AI is NOT aware of negating effects like wonder room, mold breaker, etc.
#define AI_FLAG_WILL_SUICIDE (1 << 11) // AI will use explosion / self destruct / final gambit / etc.
#define AI_FLAG_NEGATE_UNAWARE AI_FLAG(10) // AI is NOT aware of negating effects like wonder room, mold breaker, etc.
#define AI_FLAG_WILL_SUICIDE AI_FLAG(11) // AI will use explosion / self destruct / final gambit / etc.
// New, Trainer Strategy Flags
#define AI_FLAG_PREFER_STATUS_MOVES (1 << 12) // AI gets a score bonus for status moves. Should be combined with AI_FLAG_CHECK_BAD_MOVE to prevent using only status moves.
#define AI_FLAG_STALL (1 << 13) // AI stalls battle and prefers secondary damage/trapping/etc. TODO not finished.
#define AI_FLAG_SMART_SWITCHING (1 << 14) // AI includes a lot more switching checks. Automatically includes AI_FLAG_SMART_MON_CHOICES.
#define AI_FLAG_ACE_POKEMON (1 << 15) // AI has an Ace Pokemon. The last Pokemon in the party will not be used until it's the last one remaining.
#define AI_FLAG_OMNISCIENT (1 << 16) // AI has full knowledge of player moves, abilities, hold items.
#define AI_FLAG_SMART_MON_CHOICES (1 << 17) // AI will make smarter decisions when choosing which mon to send out mid-battle and after a KO, which are separate decisions. Automatically included by AI_FLAG_SMART_SWITCHING.
#define AI_FLAG_CONSERVATIVE (1 << 18) // AI assumes all moves will low roll damage.
#define AI_FLAG_SEQUENCE_SWITCHING (1 << 19) // AI switches in mons in exactly party order, and never switches mid-battle.
#define AI_FLAG_DOUBLE_ACE_POKEMON (1 << 20) // AI has *two* Ace Pokémon. The last two Pokémons in the party won't be used unless they're the last ones remaining. Goes well in battles where the trainer ID equals to twins, couples, etc.
#define AI_FLAG_WEIGH_ABILITY_PREDICTION (1 << 21) // AI will predict player's ability based on aiRating
#define AI_FLAG_PREFER_HIGHEST_DAMAGE_MOVE (1 << 22) // AI adds score to highest damage move regardless of accuracy or secondary effect
#define AI_FLAG_PREDICT_SWITCH (1 << 23) // AI will predict the player's switches and switchins based on how it would handle the situation. Recommend using AI_FLAG_OMNISCIENT
#define AI_FLAG_PREDICT_INCOMING_MON (1 << 24) // AI will score against the predicting incoming mon if it predicts the player to switch. Requires AI_FLAG_PREDICT_SWITCH
#define AI_FLAG_PP_STALL_PREVENTION (1 << 25) // AI keeps track of the player's switches where the incoming mon is immune to the chosen move
#define AI_FLAG_PREDICT_MOVE (1 << 26) // AI will predict the player's move based on what move it would use in the same situation. Recommend using AI_FLAG_OMNISCIENT
#define AI_FLAG_SMART_TERA (1 << 27) // AI will make smarter decisions when choosing whether to terrastalize (default is to always tera whenever available).
#define AI_FLAG_ASSUME_STAB (1 << 28) // AI knows player's STAB moves, but nothing else. Restricted version of AI_FLAG_OMNISCIENT.
#define AI_FLAG_ASSUME_STATUS_MOVES (1 << 29) // AI has a chance to know certain non-damaging moves, and also Fake Out and Super Fang. Restricted version of AI_FLAG_OMNISCIENT.
#define AI_FLAG_ATTACKS_PARTNER (1 << 30) // AI specific to double battles; AI can deliberately attack its 'partner.'
#define AI_FLAG_COUNT 31
// Flags at and after 32 need different formatting, as in
// #define AI_FLAG_PLACEHOLDER ((u64)1 << 32)
#define AI_FLAG_PREFER_STATUS_MOVES AI_FLAG(12) // AI gets a score bonus for status moves. Should be combined with AI_FLAG_CHECK_BAD_MOVE to prevent using only status moves.
#define AI_FLAG_STALL AI_FLAG(13) // AI stalls battle and prefers secondary damage/trapping/etc. TODO not finished.
#define AI_FLAG_SMART_SWITCHING AI_FLAG(14) // AI includes a lot more switching checks. Automatically includes AI_FLAG_SMART_MON_CHOICES.
#define AI_FLAG_ACE_POKEMON AI_FLAG(15) // AI has an Ace Pokemon. The last Pokemon in the party will not be used until it's the last one remaining.
#define AI_FLAG_OMNISCIENT AI_FLAG(16) // AI has full knowledge of player moves, abilities, hold items.
#define AI_FLAG_SMART_MON_CHOICES AI_FLAG(17) // AI will make smarter decisions when choosing which mon to send out mid-battle and after a KO, which are separate decisions. Automatically included by AI_FLAG_SMART_SWITCHING.
#define AI_FLAG_CONSERVATIVE AI_FLAG(18) // AI assumes all moves will low roll damage.
#define AI_FLAG_SEQUENCE_SWITCHING AI_FLAG(19) // AI switches in mons in exactly party order, and never switches mid-battle.
#define AI_FLAG_DOUBLE_ACE_POKEMON AI_FLAG(20) // AI has *two* Ace Pokémon. The last two Pokémons in the party won't be used unless they're the last ones remaining. Goes well in battles where the trainer ID equals to twins, couples, etc.
#define AI_FLAG_WEIGH_ABILITY_PREDICTION AI_FLAG(21) // AI will predict player's ability based on aiRating
#define AI_FLAG_PREFER_HIGHEST_DAMAGE_MOVE AI_FLAG(22) // AI adds score to highest damage move regardless of accuracy or secondary effect
#define AI_FLAG_PREDICT_SWITCH AI_FLAG(23) // AI will predict the player's switches and switchins based on how it would handle the situation. Recommend using AI_FLAG_OMNISCIENT
#define AI_FLAG_PREDICT_INCOMING_MON AI_FLAG(24) // AI will score against the predicting incoming mon if it predicts the player to switch. Requires AI_FLAG_PREDICT_SWITCH
#define AI_FLAG_PP_STALL_PREVENTION AI_FLAG(25) // AI keeps track of the player's switches where the incoming mon is immune to the chosen move
#define AI_FLAG_PREDICT_MOVE AI_FLAG(26) // AI will predict the player's move based on what move it would use in the same situation. Recommend using AI_FLAG_OMNISCIENT
#define AI_FLAG_SMART_TERA AI_FLAG(27) // AI will make smarter decisions when choosing whether to terrastalize (default is to always tera whenever available).
#define AI_FLAG_ASSUME_STAB AI_FLAG(28) // AI knows player's STAB moves, but nothing else. Restricted version of AI_FLAG_OMNISCIENT.
#define AI_FLAG_ASSUME_STATUS_MOVES AI_FLAG(29) // AI has a chance to know certain non-damaging moves, and also Fake Out and Super Fang. Restricted version of AI_FLAG_OMNISCIENT.
#define AI_FLAG_ATTACKS_PARTNER AI_FLAG(30) // AI specific to double battles; AI can deliberately attack its 'partner.'
// The following options are enough to have a basic/smart trainer. Any other addtion could make the trainer worse/better depending on the flag
#define AI_FLAG_BASIC_TRAINER (AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY)
@ -49,10 +46,10 @@
#define AI_FLAG_ASSUMPTIONS (AI_FLAG_ASSUME_STAB | AI_FLAG_ASSUME_STATUS_MOVES | AI_FLAG_WEIGH_ABILITY_PREDICTION)
// 'other' ai logic flags
#define AI_FLAG_DYNAMIC_FUNC ((u64)1 << 60) // Create custom AI functions for specific battles via "setdynamicaifunc" cmd
#define AI_FLAG_ROAMING ((u64)1 << 61)
#define AI_FLAG_SAFARI ((u64)1 << 62)
#define AI_FLAG_FIRST_BATTLE ((u64)1 << 63)
#define AI_FLAG_DYNAMIC_FUNC AI_FLAG(60) // Create custom AI functions for specific battles via "setdynamicaifunc" cmd
#define AI_FLAG_ROAMING AI_FLAG(61)
#define AI_FLAG_SAFARI AI_FLAG(62)
#define AI_FLAG_FIRST_BATTLE AI_FLAG(63)
#define AI_SCORE_DEFAULT 100 // Default score for all AI moves.

View File

@ -9,7 +9,7 @@ enum __attribute__((packed)) BattleMoveEffects
EFFECT_ABSORB,
EFFECT_EXPLOSION,
EFFECT_MISTY_EXPLOSION, // Same as EFFECT_EXPLOSION but it's boosted on Misty Terrain
EFFECT_DREAM_EATER,
EFFECT_DREAM_EATER, // Same as EFFECT_ABSORB but it can only be used on sleeping targets
EFFECT_MIRROR_MOVE,
EFFECT_ATTACK_UP,
EFFECT_DEFENSE_UP,

View File

@ -41,7 +41,6 @@ enum StringID
STRINGID_PKMNMADESLEEP,
STRINGID_PKMNALREADYASLEEP,
STRINGID_PKMNALREADYASLEEP2,
STRINGID_PKMNWASNTAFFECTED,
STRINGID_PKMNWASPOISONED,
STRINGID_PKMNPOISONEDBY,
STRINGID_PKMNHURTBYPOISON,

View File

@ -20,6 +20,15 @@ enum GenConfigTag
GEN_CONFIG_DEFIANT_STICKY_WEB,
GEN_CONFIG_ENCORE_TARGET,
GEN_CONFIG_TIME_OF_DAY_HEALING_MOVES,
GEN_PICKUP_WILD,
GEN_PROTEAN_LIBERO,
GEN_INTREPID_SWORD,
GEN_DAUNTLESS_SHIELD,
GEN_ILLUMINATE_EFFECT,
GEN_STEAL_WILD_ITEMS,
GEN_SNOW_WARNING,
GEN_ALLY_SWITCH_FAIL_CHANCE,
GEN_DREAM_EATER_LIQUID_OOZE,
GEN_CONFIG_COUNT
};

View File

@ -37,6 +37,7 @@ bool8 FieldEffectCmd_loadgfx_callnative(u8 **script, u32 *val);
bool8 FieldEffectCmd_loadtiles_callnative(u8 **script, u32 *val);
bool8 FieldEffectCmd_loadfadedpal_callnative(u8 **script, u32 *val);
void FieldCB_FallWarpExit(void);
void HideFollowerForFieldEffect(void);
void StartEscalatorWarp(u8 metatileBehavior, u8 priority);
void StartLavaridgeGymB1FWarp(u8 priority);
void StartLavaridgeGym1FWarp(u8 priority);

View File

@ -21,6 +21,7 @@ enum FollowerNPCDataTypes
FNPC_DATA_WARP_END,
FNPC_DATA_SURF_BLOB,
FNPC_DATA_COME_OUT_DOOR,
FNPC_DATA_FORCED_MOVEMENT,
FNPC_DATA_OBJ_ID,
FNPC_DATA_CURRENT_SPRITE,
FNPC_DATA_DELAYED_STATE,
@ -60,7 +61,8 @@ enum FollowerNPCSurfBlobStates
FNPC_SURF_BLOB_DESTROY
};
enum FollowerNPCOutOfDoorTaskStates{
enum FollowerNPCOutOfDoorTaskStates
{
OPEN_DOOR,
NPC_WALK_OUT,
CLOSE_DOOR,
@ -68,7 +70,8 @@ enum FollowerNPCOutOfDoorTaskStates{
REALLOW_MOVEMENT
};
enum FollowerNPCHandleEscalatorFinishTaskStates{
enum FollowerNPCHandleEscalatorFinishTaskStates
{
MOVE_TO_PLAYER_POS,
WAIT_FOR_PLAYER_MOVE,
SHOW_FOLLOWER_DOWN,

View File

@ -23,6 +23,15 @@ static const u8 sGenerationalChanges[GEN_CONFIG_COUNT] =
[GEN_CONFIG_DEFIANT_STICKY_WEB] = B_DEFIANT_STICKY_WEB,
[GEN_CONFIG_ENCORE_TARGET] = B_ENCORE_TARGET,
[GEN_CONFIG_TIME_OF_DAY_HEALING_MOVES] = B_TIME_OF_DAY_HEALING_MOVES,
[GEN_PICKUP_WILD] = B_PICKUP_WILD,
[GEN_PROTEAN_LIBERO] = B_PROTEAN_LIBERO,
[GEN_INTREPID_SWORD] = B_INTREPID_SWORD,
[GEN_DAUNTLESS_SHIELD] = B_DAUNTLESS_SHIELD,
[GEN_ILLUMINATE_EFFECT] = B_ILLUMINATE_EFFECT,
[GEN_STEAL_WILD_ITEMS] = B_STEAL_WILD_ITEMS,
[GEN_SNOW_WARNING] = B_SNOW_WARNING,
[GEN_ALLY_SWITCH_FAIL_CHANCE] = B_ALLY_SWITCH_FAIL_CHANCE,
[GEN_DREAM_EATER_LIQUID_OOZE] = B_DREAM_EATER_LIQUID_OOZE,
};
#if TESTING

View File

@ -219,7 +219,8 @@ struct NPCFollower
u8 inProgress:1;
u8 warpEnd:1;
u8 createSurfBlob:3;
u8 comeOutDoorStairs:3;
u8 comeOutDoorStairs:2;
u8 forcedMovement:1;
u8 objId;
u8 currentSprite;
u8 delayedState;

View File

@ -16,10 +16,12 @@ struct PartyMenu
s8 slotId2;
u8 action;
u16 bagItem;
s16 data1; // used variously as a move, counter, moveSlotId, or cursorPos
s16 data1; // used variously as a move, counter, moveSlotId, cursorPos, or indicator that the menu is opened from the field
s16 learnMoveState; // data2, used only as a learn move state
};
#define DATA1_PARTY_MENU_FROM_FIELD -1
extern struct PartyMenu gPartyMenu;
extern bool8 gPartyMenuUseExitCallback;
extern u8 gSelectedMonPartyId;

View File

@ -434,7 +434,7 @@
* Spaces in pattern match newlines (\n, \l, and \p) in the message.
* Often used to check that a battler took its turn but it failed, e.g.:
* MESSAGE("Wobbuffet used Dream Eater!");
* MESSAGE("The opposing Wobbuffet wasn't affected!");
* MESSAGE("It doesn't affect the opposing Wobbuffet…");
*
* STATUS_ICON(battler, status1 | none: | sleep: | poison: | burn: | freeze: | paralysis:, badPoison:)
* Causes the test to fail if the battler's status is not changed to the

View File

@ -51,7 +51,6 @@ asm(".set FALSE, 0\n"
".set VARS_END, " STR(VARS_END) "\n"
".set SPECIAL_VARS_START, " STR(SPECIAL_VARS_START) "\n"
".set SPECIAL_VARS_END, " STR(SPECIAL_VARS_END) "\n");
asm(".include \"constants/gba_constants.inc\"\n");
// Make overworld script macros available.
asm(".include \"constants/gba_constants.inc\"\n"

View File

@ -12,10 +12,10 @@ TOOLDIRS := $(TOOL_NAMES:%=$(TOOLS_DIR)/%)
CHECKTOOLDIRS := $(CHECK_TOOL_NAMES:%=$(TOOLS_DIR)/%)
# Tool making doesnt require a pokeemerald dependency scan.
RULES_NO_SCAN += tools check-tools clean-tools clean-check-tools $(TOOLDIRS) $(CHECKTOOLDIRS)
RULES_NO_SCAN += tools check-tools clean-tools clean-check-tools history $(TOOLDIRS) $(CHECKTOOLDIRS)
.PHONY: $(RULES_NO_SCAN)
tools: $(TOOLDIRS)
tools: history $(TOOLDIRS)
check-tools: $(CHECKTOOLDIRS)
@ -30,3 +30,6 @@ clean-tools:
clean-check-tools:
@$(foreach tooldir,$(CHECKTOOLDIRS),$(MAKE) clean -C $(tooldir);)
history:
@$(SHELL) ./check_history.sh

View File

@ -33,6 +33,7 @@
#define AI_ACTION_WATCH (1 << 2)
#define AI_ACTION_DO_NOT_ATTACK (1 << 3)
static u32 ChooseMoveOrAction(u32 battler);
static u32 ChooseMoveOrAction_Singles(u32 battler);
static u32 ChooseMoveOrAction_Doubles(u32 battler);
static inline void BattleAI_DoAIProcessing(struct AiThinkingStruct *aiThink, u32 battlerAtk, u32 battlerDef);
@ -278,16 +279,17 @@ void BattleAI_SetupFlags(void)
{
gAiThinkingStruct->aiFlags[B_POSITION_PLAYER_RIGHT] = gAiThinkingStruct->aiFlags[B_POSITION_PLAYER_LEFT];
}
else
else // Assign ai flags for player for prediction
{
gAiThinkingStruct->aiFlags[B_POSITION_PLAYER_RIGHT] = 0; // player
u64 aiFlags = GetAiFlags(TRAINER_BATTLE_PARAM.opponentA) | GetAiFlags(TRAINER_BATTLE_PARAM.opponentB);
gAiThinkingStruct->aiFlags[B_POSITION_PLAYER_RIGHT] = aiFlags;
gAiThinkingStruct->aiFlags[B_POSITION_PLAYER_LEFT] = aiFlags;
}
}
void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler)
{
u32 moveLimitations, moveLimitationsTarget;
u32 defaultScoreMovesTarget = defaultScoreMoves;
u32 moveLimitations;
u64 flags[MAX_BATTLERS_COUNT];
u32 moveIndex;
@ -313,25 +315,6 @@ void BattleAI_SetupAIData(u8 defaultScoreMoves, u32 battler)
gBattlerTarget = SetRandomTarget(battler);
gAiBattleData->chosenTarget[battler] = gBattlerTarget;
// Initialize move prediction scores
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_PREDICT_MOVE)
{
u32 opposingBattler = GetOppositeBattler(battler);
moveLimitationsTarget = gAiLogicData->moveLimitations[opposingBattler];
for (moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
{
if (moveLimitationsTarget & (1u << moveIndex))
SET_SCORE(opposingBattler, moveIndex, 0);
if (defaultScoreMovesTarget & 1)
SET_SCORE(opposingBattler, moveIndex, AI_SCORE_DEFAULT);
else
SET_SCORE(opposingBattler, moveIndex, 0);
defaultScoreMovesTarget >>= 1;
}
}
}
bool32 BattlerChoseNonMoveAction(void)
@ -355,7 +338,6 @@ void SetupAIPredictionData(u32 battler, enum SwitchType switchType)
{
s32 opposingBattler = GetOppositeBattler(battler);
gAiLogicData->aiPredictionInProgress = TRUE;
gAiLogicData->battlerDoingPrediction = battler;
// Switch prediction
if ((gAiThinkingStruct->aiFlags[battler] & AI_FLAG_PREDICT_SWITCH))
@ -364,31 +346,21 @@ void SetupAIPredictionData(u32 battler, enum SwitchType switchType)
if (ShouldSwitch(opposingBattler))
gAiLogicData->shouldSwitch |= (1u << opposingBattler);
gBattleStruct->prevTurnSpecies[opposingBattler] = gBattleMons[opposingBattler].species;
// Determine whether AI will use predictions this turn
gAiLogicData->predictingSwitch = RandomPercentage(RNG_AI_PREDICT_SWITCH, PREDICT_SWITCH_CHANCE);
}
// Move prediction
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_PREDICT_MOVE)
{
gAiLogicData->predictedMove[opposingBattler] = gBattleMons[opposingBattler].moves[BattleAI_ChooseMoveIndex(opposingBattler)];
ModifySwitchAfterMoveScoring(opposingBattler);
// Determine whether AI will use predictions this turn
gAiLogicData->predictingSwitch = RandomPercentage(RNG_AI_PREDICT_SWITCH, PREDICT_SWITCH_CHANCE);
// Determine whether AI will use predictions this turn
gAiLogicData->predictingMove = RandomPercentage(RNG_AI_PREDICT_MOVE, PREDICT_MOVE_CHANCE);
}
gAiLogicData->aiPredictionInProgress = FALSE;
}
void ComputeBattlerDecisions(u32 battler)
{
if ((gBattleTypeFlags & BATTLE_TYPE_HAS_AI || IsWildMonSmart())
&& (BattlerHasAi(battler)
&& !(gBattleTypeFlags & BATTLE_TYPE_PALACE)))
bool32 isAiBattler = (gBattleTypeFlags & BATTLE_TYPE_HAS_AI || IsWildMonSmart()) && (BattlerHasAi(battler) && !(gBattleTypeFlags & BATTLE_TYPE_PALACE));
if (isAiBattler || CanAiPredictMove())
{
// If ai is about to flee or chosen to watch player, no need to calc anything
if (BattlerChoseNonMoveAction())
if (isAiBattler && BattlerChoseNonMoveAction())
return;
// Risky AI switches aggressively even mid battle
@ -401,10 +373,13 @@ void ComputeBattlerDecisions(u32 battler)
SetupAIPredictionData(battler, switchType);
// AI's own switching data
gAiLogicData->mostSuitableMonId[battler] = GetMostSuitableMonToSwitchInto(battler, switchType);
if (ShouldSwitch(battler))
gAiLogicData->shouldSwitch |= (1u << battler);
gBattleStruct->prevTurnSpecies[battler] = gBattleMons[battler].species;
if (isAiBattler)
{
gAiLogicData->mostSuitableMonId[battler] = GetMostSuitableMonToSwitchInto(battler, switchType);
if (ShouldSwitch(battler))
gAiLogicData->shouldSwitch |= (1u << battler);
gBattleStruct->prevTurnSpecies[battler] = gBattleMons[battler].species;
}
// AI's move scoring
gAiBattleData->chosenMoveIndex[battler] = BattleAI_ChooseMoveIndex(battler); // Calculate score and chose move index
@ -425,6 +400,13 @@ void ReconsiderGimmick(u32 battlerAtk, u32 battlerDef, u16 move)
SetAIUsingGimmick(battlerAtk, NO_GIMMICK);
}
static u32 ChooseMoveOrAction(u32 battler)
{
if (IsDoubleBattle())
return ChooseMoveOrAction_Doubles(battler);
return ChooseMoveOrAction_Singles(battler);
}
u32 BattleAI_ChooseMoveIndex(u32 battler)
{
u32 chosenMoveIndex;
@ -434,16 +416,11 @@ u32 BattleAI_ChooseMoveIndex(u32 battler)
if (gBattleStruct->gimmick.usableGimmick[battler] == GIMMICK_TERA && (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_TERA))
DecideTerastal(battler);
if (!IsDoubleBattle())
chosenMoveIndex = ChooseMoveOrAction_Singles(battler);
else
chosenMoveIndex = ChooseMoveOrAction_Doubles(battler);
chosenMoveIndex = ChooseMoveOrAction(battler);
if (gBattleStruct->gimmick.usableGimmick[battler] != GIMMICK_NONE)
ReconsiderGimmick(battler, gBattlerTarget, gBattleMons[battler].moves[chosenMoveIndex]);
// Clear protect structures, some flags may be set during AI calcs
// e.g. pranksterElevated from GetBattleMovePriority
memset(&gProtectStructs, 0, MAX_BATTLERS_COUNT * sizeof(struct ProtectStruct));
@ -573,7 +550,6 @@ void RecordStatusMoves(u32 battler)
void SetBattlerAiData(u32 battler, struct AiLogicData *aiData)
{
u32 ability, holdEffect;
ability = aiData->abilities[battler] = AI_DecideKnownAbilityForTurn(battler);
aiData->items[battler] = gBattleMons[battler].item;
holdEffect = aiData->holdEffects[battler] = AI_DecideHoldEffectForTurn(battler);
@ -691,6 +667,20 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData)
SetBattlerAiMovesData(aiData, battlerAtk, battlersCount, weather);
}
for (battlerAtk = 0; battlerAtk < battlersCount; battlerAtk++)
{
// Prediction limited to player side but can be expanded to read partners move in the future
if (!IsOnPlayerSide(battlerAtk) || !CanAiPredictMove())
continue;
// This can potentially be cleaned up more
BattleAI_SetupAIData(0xF, battlerAtk);
u32 chosenMoveIndex = ChooseMoveOrAction(battlerAtk);
gAiLogicData->predictedMove[battlerAtk] = gBattleMons[battlerAtk].moves[chosenMoveIndex];
aiData->predictingMove = RandomPercentage(RNG_AI_PREDICT_MOVE, PREDICT_MOVE_CHANCE);
}
if (DEBUG_AI_DELAY_TIMER)
// We add to existing to compound multiple calls
gBattleStruct->aiDelayCycles += CycleCountEnd();
@ -749,7 +739,7 @@ static u32 ChooseMoveOrAction_Singles(u32 battler)
u8 consideredMoveArray[MAX_MON_MOVES];
u32 numOfBestMoves;
s32 i;
u64 flags = gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)];
u64 flags = gAiThinkingStruct->aiFlags[battler];
u32 opposingBattler = GetOppositeBattler(battler);
gAiLogicData->partnerMove = 0; // no ally
@ -831,7 +821,7 @@ static u32 ChooseMoveOrAction_Doubles(u32 battler)
gAiLogicData->partnerMove = GetAllyChosenMove(battler);
gAiThinkingStruct->aiLogicId = 0;
gAiThinkingStruct->movesetIndex = 0;
flags = gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)];
flags = gAiThinkingStruct->aiFlags[battler];
while (flags != 0)
{
@ -1139,7 +1129,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
abilityDef = ABILITY_NONE;
// If a pokemon can be guaranteed flinched, don't target the pokemon that can't be flinched.
if (hasTwoOpponents
if (hasTwoOpponents
&& !IsFlinchGuaranteed(battlerAtk, battlerDef, move) && IsFlinchGuaranteed(battlerAtk, BATTLE_PARTNER(battlerDef), move)
&& aiData->effectiveness[battlerAtk][BATTLE_PARTNER(battlerDef)][gAiThinkingStruct->movesetIndex] != UQ_4_12(0.0))
ADJUST_SCORE(-5);
@ -1975,7 +1965,7 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
case EFFECT_CHILLY_RECEPTION:
if (CountUsablePartyMons(battlerAtk) == 0)
ADJUST_SCORE(-10);
else if (weather & (B_WEATHER_ICY_ANY | B_WEATHER_PRIMAL_ANY)
else if (weather & (B_WEATHER_ICY_ANY | B_WEATHER_PRIMAL_ANY)
|| (HasPartner(battlerAtk) && AreMovesEquivalent(battlerAtk, BATTLE_PARTNER(battlerAtk), move, aiData->partnerMove)))
ADJUST_SCORE(-8);
break;
@ -2254,12 +2244,14 @@ static s32 AI_CheckBadMove(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
ADJUST_SCORE(-10);
decreased = TRUE;
}
break;
case PROTECT_WIDE_GUARD:
if(!(GetBattlerMoveTargetType(battlerAtk, predictedMove) & (MOVE_TARGET_FOES_AND_ALLY | MOVE_TARGET_BOTH)))
{
ADJUST_SCORE(-10);
decreased = TRUE;
}
break;
case PROTECT_CRAFTY_SHIELD:
if (!hasPartner)
{
@ -3000,7 +2992,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
SetTypeBeforeUsingMove(move, battlerAtk);
moveType = GetBattleMoveType(move);
bool32 hasPartner = HasPartner(battlerAtk);
u32 friendlyFireThreshold = GetFriendlyFireKOThreshold(battlerAtk);
u32 noOfHitsToKOPartner = GetNoOfHitsToKOBattler(battlerAtk, battlerAtkPartner, gAiThinkingStruct->movesetIndex, AI_ATTACKING);
@ -3095,7 +3087,7 @@ static s32 AI_DoubleBattle(u32 battlerAtk, u32 battlerDef, u32 move, s32 score)
// Both Pokemon use Trick Room on the final turn of Trick Room to anticipate both opponents Protecting to stall out.
// This unsets Trick Room and resets it with a full timer.
case EFFECT_TRICK_ROOM:
if (hasPartner && gFieldStatuses & STATUS_FIELD_TRICK_ROOM && gFieldTimers.trickRoomTimer == gBattleTurnCounter
if (hasPartner && gFieldStatuses & STATUS_FIELD_TRICK_ROOM && gFieldTimers.trickRoomTimer == gBattleTurnCounter
&& ShouldSetFieldStatus(battlerAtk, STATUS_FIELD_TRICK_ROOM)
&& HasMoveWithEffect(battlerAtkPartner, MOVE_TRICK_ROOM)
&& RandomPercentage(RNG_AI_REFRESH_TRICK_ROOM_ON_LAST_TURN, DOUBLE_TRICK_ROOM_ON_LAST_TURN_CHANCE))
@ -5741,8 +5733,8 @@ static s32 AI_AttacksPartner(u32 battlerAtk, u32 battlerDef, u32 move, s32 score
u32 hitsToKO = GetNoOfHitsToKOBattler(battlerAtk, battlerDef, gAiThinkingStruct->movesetIndex, AI_ATTACKING);
if (GetMoveTarget(move) == MOVE_TARGET_FOES_AND_ALLY && hitsToKO > 0 &&
(GetNoOfHitsToKOBattler(battlerAtk, FOE(battlerAtk), gAiThinkingStruct->movesetIndex, AI_ATTACKING) > 0 || GetNoOfHitsToKOBattler(battlerAtk, FOE(battlerDef), gAiThinkingStruct->movesetIndex, AI_ATTACKING) > 0))
if (GetMoveTarget(move) == MOVE_TARGET_FOES_AND_ALLY && hitsToKO > 0 &&
(GetNoOfHitsToKOBattler(battlerAtk, FOE(battlerAtk), gAiThinkingStruct->movesetIndex, AI_ATTACKING) > 0 || GetNoOfHitsToKOBattler(battlerAtk, FOE(battlerDef), gAiThinkingStruct->movesetIndex, AI_ATTACKING) > 0))
ADJUST_SCORE(BEST_EFFECT);
if (hitsToKO > 0)

View File

@ -111,11 +111,11 @@ u32 GetSwitchChance(enum ShouldSwitchScenario shouldSwitchScenario)
static bool32 IsAceMon(u32 battler, u32 monPartyId)
{
if (gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_ACE_POKEMON
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_ACE_POKEMON
&& !gBattleStruct->battlerState[battler].forcedSwitch
&& monPartyId == CalculateEnemyPartyCountInSide(battler)-1)
return TRUE;
if (gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_DOUBLE_ACE_POKEMON
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_DOUBLE_ACE_POKEMON
&& !gBattleStruct->battlerState[battler].forcedSwitch
&& (monPartyId == CalculateEnemyPartyCount()-1 || monPartyId == CalculateEnemyPartyCount()-2))
return TRUE;
@ -215,7 +215,7 @@ static bool32 ShouldSwitchIfHasBadOdds(u32 battler)
bool32 canBattlerWin1v1 = FALSE, isBattlerFirst, isBattlerFirstPriority;
// Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer
if (!(gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING))
if (!(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING))
return FALSE;
// Double Battles aren't included in AI_FLAG_SMART_MON_CHOICE. Defaults to regular switch in logic
@ -486,7 +486,7 @@ static bool32 FindMonThatAbsorbsOpponentsMove(u32 battler)
bool32 isOpposingBattlerChargingOrInvulnerable = !BreaksThroughSemiInvulnerablity(opposingBattler, incomingMove) || IsTwoTurnNotSemiInvulnerableMove(opposingBattler, incomingMove);
s32 i, j;
if (!(gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING))
if (!(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING))
return FALSE;
if (GetMoveEffect(incomingMove) == EFFECT_HIDDEN_POWER && RandomPercentage(RNG_AI_SWITCH_ABSORBING_HIDDEN_POWER, SHOULD_SWITCH_ABSORBS_HIDDEN_POWER_PERCENTAGE))
return FALSE;
@ -618,7 +618,7 @@ static bool32 ShouldSwitchIfOpponentChargingOrInvulnerable(u32 battler)
bool32 isOpposingBattlerChargingOrInvulnerable = !BreaksThroughSemiInvulnerablity(opposingBattler, incomingMove) || IsTwoTurnNotSemiInvulnerableMove(opposingBattler, incomingMove);
if (IsDoubleBattle() || !(gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING))
if (IsDoubleBattle() || !(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING))
return FALSE;
if (isOpposingBattlerChargingOrInvulnerable && gAiLogicData->mostSuitableMonId[battler] != PARTY_SIZE && RandomPercentage(RNG_AI_SWITCH_FREE_TURN, GetSwitchChance(SHOULD_SWITCH_FREE_TURN)))
@ -637,7 +637,7 @@ static bool32 ShouldSwitchIfTrapperInParty(u32 battler)
s32 opposingBattler = GetOppositeBattler(battler);
// Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer
if (!(gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING))
if (!(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING))
return FALSE;
// Check if current mon has an ability that traps opponent
@ -681,7 +681,7 @@ static bool32 ShouldSwitchIfBadlyStatused(u32 battler)
&& RandomPercentage(RNG_AI_SWITCH_PERISH_SONG, GetSwitchChance(SHOULD_SWITCH_PERISH_SONG)))
return SetSwitchinAndSwitch(battler, PARTY_SIZE);
if (gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING)
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING)
{
//Yawn
if (gStatuses3[battler] & STATUS3_YAWN
@ -717,7 +717,7 @@ static bool32 ShouldSwitchIfBadlyStatused(u32 battler)
&& gAiLogicData->abilities[opposingBattler] != ABILITY_UNAWARE
&& gAiLogicData->abilities[opposingBattler] != ABILITY_KEEN_EYE
&& gAiLogicData->abilities[opposingBattler] != ABILITY_MINDS_EYE
&& (B_ILLUMINATE_EFFECT >= GEN_9 && gAiLogicData->abilities[opposingBattler] != ABILITY_ILLUMINATE)
&& (GetGenConfig(GEN_ILLUMINATE_EFFECT) >= GEN_9 && gAiLogicData->abilities[opposingBattler] != ABILITY_ILLUMINATE)
&& !gBattleMons[battler].volatiles.foresight
&& !(gStatuses3[battler] & STATUS3_MIRACLE_EYED))
switchMon = FALSE;
@ -869,7 +869,7 @@ static bool32 FindMonWithFlagsAndSuperEffective(u32 battler, u16 flags, u32 perc
u16 move;
// Similar functionality handled more thoroughly by ShouldSwitchIfHasBadOdds
if (gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING)
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING)
return FALSE;
if (gLastLandedMoves[battler] == MOVE_NONE)
@ -1007,7 +1007,7 @@ static bool32 ShouldSwitchIfEncored(u32 battler)
u32 opposingBattler = GetOppositeBattler(battler);
// Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer
if (!(gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING))
if (!(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING))
return FALSE;
// If not Encore'd don't switch
@ -1057,7 +1057,7 @@ static bool32 ShouldSwitchIfAttackingStatsLowered(u32 battler)
s8 spAttackingStage = gBattleMons[battler].statStages[STAT_SPATK];
// Only use this if AI_FLAG_SMART_SWITCHING is set for the trainer
if (!(gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING))
if (!(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING))
return FALSE;
// Physical attacker
@ -1117,7 +1117,7 @@ bool32 ShouldSwitch(u32 battler)
return FALSE;
// Sequence Switching AI never switches mid-battle
if (gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SEQUENCE_SWITCHING)
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SEQUENCE_SWITCHING)
return FALSE;
availableToSwitch = 0;
@ -1169,7 +1169,7 @@ bool32 ShouldSwitch(u32 battler)
if (ShouldSwitchIfWonderGuard(battler))
return TRUE;
if ((gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING) && (CanMonSurviveHazardSwitchin(battler) == FALSE))
if ((gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING) && (CanMonSurviveHazardSwitchin(battler) == FALSE))
return FALSE;
if (ShouldSwitchIfTrapperInParty(battler))
return TRUE;
@ -1197,7 +1197,7 @@ bool32 ShouldSwitch(u32 battler)
// Removing switch capabilites under specific conditions
// These Functions prevent the "FindMonWithFlagsAndSuperEffective" from getting out of hand.
// We don't use FindMonWithFlagsAndSuperEffective with AI_FLAG_SMART_SWITCHING, so we can bail early.
if (gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING)
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING)
return FALSE;
if (CanUseSuperEffectiveMoveAgainstOpponents(battler))
return FALSE;
@ -1216,7 +1216,7 @@ bool32 ShouldSwitch(u32 battler)
bool32 ShouldSwitchIfAllScoresBad(u32 battler)
{
u32 i, score, opposingBattler = GetOppositeBattler(battler);
if (!(gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_SWITCHING))
if (!(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING))
return FALSE;
for (i = 0; i < MAX_MON_MOVES; i++)
@ -1268,7 +1268,7 @@ void ModifySwitchAfterMoveScoring(u32 battler)
return;
// Sequence Switching AI never switches mid-battle
if (gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SEQUENCE_SWITCHING)
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SEQUENCE_SWITCHING)
return;
availableToSwitch = 0;
@ -2329,14 +2329,14 @@ u32 GetMostSuitableMonToSwitchInto(u32 battler, enum SwitchType switchType)
GetAIPartyIndexes(battler, &firstId, &lastId);
party = GetBattlerParty(battler);
if (gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SEQUENCE_SWITCHING)
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SEQUENCE_SWITCHING)
{
bestMonId = GetNextMonInParty(party, firstId, lastId, battlerIn1, battlerIn2);
return bestMonId;
}
// Only use better mon selection if AI_FLAG_SMART_MON_CHOICES is set for the trainer.
if (gAiThinkingStruct->aiFlags[GetThinkingBattler(battler)] & AI_FLAG_SMART_MON_CHOICES && !IsDoubleBattle()) // Double Battles aren't included in AI_FLAG_SMART_MON_CHOICE. Defaults to regular switch in logic
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_MON_CHOICES && !IsDoubleBattle()) // Double Battles aren't included in AI_FLAG_SMART_MON_CHOICE. Defaults to regular switch in logic
{
bestMonId = GetBestMonIntegrated(party, firstId, lastId, battler, opposingBattler, battlerIn1, battlerIn2, switchType);
return bestMonId;

View File

@ -173,11 +173,17 @@ bool32 IsAiBattlerPredictingAbility(u32 battlerId)
return BattlerHasAi(battlerId);
}
bool32 CanAiPredictMove(void)
{
return gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_LEFT] & AI_FLAG_PREDICT_MOVE
|| gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_RIGHT] & AI_FLAG_PREDICT_MOVE;
}
bool32 IsBattlerPredictedToSwitch(u32 battler)
{
// Check for prediction flag on AI, whether they're using those predictions this turn, and whether the AI thinks the player should switch
if (gAiThinkingStruct->aiFlags[gAiLogicData->battlerDoingPrediction] & AI_FLAG_PREDICT_SWITCH
|| gAiThinkingStruct->aiFlags[gAiLogicData->battlerDoingPrediction] & AI_FLAG_PREDICT_SWITCH)
if (gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_LEFT] & AI_FLAG_PREDICT_SWITCH
|| gAiThinkingStruct->aiFlags[B_POSITION_OPPONENT_RIGHT] & AI_FLAG_PREDICT_SWITCH)
{
if (gAiLogicData->predictingSwitch && gAiLogicData->shouldSwitch & (1u << battler))
return TRUE;
@ -188,9 +194,8 @@ bool32 IsBattlerPredictedToSwitch(u32 battler)
// Either a predicted move or the last used move from an opposing battler
u32 GetIncomingMove(u32 battler, u32 opposingBattler, struct AiLogicData *aiData)
{
if (gAiThinkingStruct->aiFlags[battler] & AI_FLAG_PREDICT_MOVE && aiData->predictingMove)
if (aiData->predictingMove && CanAiPredictMove())
return aiData->predictedMove[opposingBattler];
return aiData->lastUsedMove[opposingBattler];
}
@ -2007,7 +2012,7 @@ bool32 CanLowerStat(u32 battlerAtk, u32 battlerDef, struct AiLogicData *aiData,
if (stat == STAT_DEF)
return FALSE;
case ABILITY_ILLUMINATE:
if (B_ILLUMINATE_EFFECT < GEN_9)
if (GetGenConfig(GEN_ILLUMINATE_EFFECT) < GEN_9)
break;
case ABILITY_KEEN_EYE:
case ABILITY_MINDS_EYE:
@ -5702,13 +5707,6 @@ s32 BattlerBenefitsFromAbilityScore(u32 battler, u32 ability, struct AiLogicData
return WEAK_EFFECT;
}
u32 GetThinkingBattler(u32 battler)
{
if (gAiLogicData->aiPredictionInProgress)
return gAiLogicData->battlerDoingPrediction;
return battler;
}
bool32 IsNaturalEnemy(u32 speciesAttacker, u32 speciesTarget)
{
if (B_WILD_NATURAL_ENEMIES != TRUE)

View File

@ -1265,8 +1265,8 @@ void SpriteCB_EnemyShadow(struct Sprite *shadowSprite)
}
else if (transformSpecies != SPECIES_NONE)
{
xOffset = gSpeciesInfo[transformSpecies].enemyShadowXOffset;
yOffset = gSpeciesInfo[transformSpecies].enemyShadowYOffset;
xOffset = gSpeciesInfo[transformSpecies].enemyShadowXOffset + (shadowSprite->tSpriteSide == SPRITE_SIDE_LEFT ? -16 : 16);
yOffset = gSpeciesInfo[transformSpecies].enemyShadowYOffset + 16;
size = gSpeciesInfo[transformSpecies].enemyShadowSize;
invisible = (B_ENEMY_MON_SHADOW_STYLE >= GEN_4 && P_GBA_STYLE_SPECIES_GFX == FALSE)

View File

@ -486,10 +486,9 @@ static void CB2_InitBattleInternal(void)
else
{
gBattle_WIN0V = WIN_RANGE(DISPLAY_HEIGHT / 2, DISPLAY_HEIGHT / 2 + 1);
ScanlineEffect_Clear();
if (B_FAST_INTRO_NO_SLIDE == FALSE && !gTestRunnerHeadless)
{
ScanlineEffect_Clear();
for (i = 0; i < DISPLAY_HEIGHT / 2; i++)
{
gScanlineEffectRegBuffers[0][i] = 0xF0;

View File

@ -200,7 +200,6 @@ const u8 *const gBattleStringsTable[STRINGID_COUNT] =
[STRINGID_PKMNMADESLEEP] = COMPOUND_STRING("{B_SCR_NAME_WITH_PREFIX}'s {B_BUFF1} made {B_EFF_NAME_WITH_PREFIX2} sleep!"), //not in gen 5+, ability popup
[STRINGID_PKMNALREADYASLEEP] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX} is already asleep!"),
[STRINGID_PKMNALREADYASLEEP2] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} is already asleep!"),
[STRINGID_PKMNWASNTAFFECTED] = COMPOUND_STRING("{B_DEF_NAME_WITH_PREFIX} wasn't affected!"), //not in gen 5+, ability popup
[STRINGID_PKMNWASPOISONED] = COMPOUND_STRING("{B_EFF_NAME_WITH_PREFIX} was poisoned!"),
[STRINGID_PKMNPOISONEDBY] = COMPOUND_STRING("{B_EFF_NAME_WITH_PREFIX} was poisoned by {B_SCR_NAME_WITH_PREFIX2}'s {B_BUFF1}!"), //not in gen 5+, ability popup
[STRINGID_PKMNHURTBYPOISON] = COMPOUND_STRING("{B_ATK_NAME_WITH_PREFIX} was hurt by its poisoning!"),

View File

@ -1962,6 +1962,9 @@ static void Cmd_adjustdamage(void)
gBattleStruct->moveDamage[battlerDef] = gBattleMons[battlerDef].hp - 1;
gSpecialStatuses[battlerDef].enduredDamage = TRUE;
}
if (gSpecialStatuses[battlerDef].enduredDamage)
gProtectStructs[battlerDef].assuranceDoubled = TRUE;
}
if (calcSpreadMoveDamage)
@ -2410,6 +2413,7 @@ static void Cmd_datahpupdate(void)
gProtectStructs[battler].physicalBattlerId = gBattlerAttacker;
else
gProtectStructs[battler].physicalBattlerId = gBattlerTarget;
gProtectStructs[battler].assuranceDoubled = TRUE;
}
else if (!IsBattleMovePhysical(gCurrentMove) && !(gHitMarker & HITMARKER_PASSIVE_DAMAGE) && effect != EFFECT_PAIN_SPLIT)
{
@ -2420,6 +2424,7 @@ static void Cmd_datahpupdate(void)
gProtectStructs[battler].specialBattlerId = gBattlerAttacker;
else
gProtectStructs[battler].specialBattlerId = gBattlerTarget;
gProtectStructs[battler].assuranceDoubled = TRUE;
}
}
gHitMarker &= ~HITMARKER_PASSIVE_DAMAGE;
@ -2816,7 +2821,7 @@ void StealTargetItem(u8 battlerStealer, u8 battlerItem)
gLastUsedItem = gBattleMons[battlerItem].item;
gBattleMons[battlerItem].item = ITEM_NONE;
if (B_STEAL_WILD_ITEMS >= GEN_9
if (GetGenConfig(GEN_STEAL_WILD_ITEMS) >= GEN_9
&& !(gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_PALACE))
&& GetMoveEffect(gCurrentMove) == EFFECT_STEAL_ITEM
&& battlerStealer == gBattlerAttacker) // ensure that Pickpocket isn't activating this
@ -3362,7 +3367,7 @@ void SetMoveEffect(u32 battler, u32 effectBattler, bool32 primary, bool32 certai
if (gBattleMons[gEffectBattler].statStages[i] != DEFAULT_STAT_STAGE)
break;
}
if ((gSpecialStatuses[gEffectBattler].physicalDmg || gSpecialStatuses[gEffectBattler].specialDmg) && i != NUM_BATTLE_STATS)
if (IsBattlerTurnDamaged(gEffectBattler) && i != NUM_BATTLE_STATS)
{
for (i = 0; i < NUM_BATTLE_STATS; i++)
gBattleMons[gEffectBattler].statStages[i] = DEFAULT_STAT_STAGE;
@ -5757,7 +5762,7 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect)
{
StealTargetItem(gBattlerAttacker, gBattlerTarget); // Attacker steals target item
if (!(B_STEAL_WILD_ITEMS >= GEN_9
if (!(GetGenConfig(GEN_STEAL_WILD_ITEMS) >= GEN_9
&& !(gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_PALACE))))
{
gBattleMons[gBattlerAttacker].item = ITEM_NONE; // Item assigned later on with thief (see MOVEEND_CHANGED_ITEMS)
@ -6061,6 +6066,7 @@ static void Cmd_moveend(void)
switch (moveEffect)
{
case EFFECT_ABSORB:
case EFFECT_DREAM_EATER:
if (!(gStatuses3[gBattlerAttacker] & STATUS3_HEAL_BLOCK)
&& gBattleStruct->moveDamage[gBattlerTarget] > 0
&& IsBattlerAlive(gBattlerAttacker))
@ -6070,18 +6076,19 @@ static void Cmd_moveend(void)
gHitMarker |= HITMARKER_IGNORE_SUBSTITUTE | HITMARKER_IGNORE_DISGUISE;
effect = TRUE;
if (GetBattlerAbility(gBattlerTarget) == ABILITY_LIQUID_OOZE)
if ((moveEffect == EFFECT_DREAM_EATER && GetGenConfig(GEN_DREAM_EATER_LIQUID_OOZE) < GEN_5)
|| GetBattlerAbility(gBattlerTarget) != ABILITY_LIQUID_OOZE)
{
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ABSORB;
BattleScriptCall(BattleScript_EffectAbsorb);
}
else
{
gBattleStruct->moveDamage[gBattlerAttacker] *= -1;
gHitMarker |= HITMARKER_PASSIVE_DAMAGE;
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ABSORB_OOZE;
BattleScriptCall(BattleScript_EffectAbsorbLiquidOoze);
}
else
{
gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_ABSORB;
BattleScriptCall(BattleScript_EffectAbsorb);
}
}
break;
case EFFECT_FINAL_GAMBIT:
@ -6249,6 +6256,7 @@ static void Cmd_moveend(void)
UpdateStallMons();
if ((gBattleStruct->moveResultFlags[gBattlerTarget] & (MOVE_RESULT_FAILED | MOVE_RESULT_DOESNT_AFFECT_FOE))
|| (gBattleMons[gBattlerAttacker].volatiles.flinched)
|| gBattleStruct->pledgeMove == TRUE // Is the battler that uses the first Pledge move in the combo
|| gProtectStructs[gBattlerAttacker].nonVolatileStatusImmobility)
gBattleStruct->battlerState[gBattlerAttacker].stompingTantrumTimer = 2;
@ -9530,8 +9538,8 @@ static void TryResetProtectUseCounter(u32 battler)
enum BattleMoveEffects lastEffect = GetMoveEffect(lastMove);
if (lastMove == MOVE_UNAVAILABLE
|| (!gBattleMoveEffects[lastEffect].usesProtectCounter
&& ((B_ALLY_SWITCH_FAIL_CHANCE >= GEN_9 && lastEffect != EFFECT_ALLY_SWITCH)
|| B_ALLY_SWITCH_FAIL_CHANCE < GEN_9)))
&& ((GetGenConfig(GEN_ALLY_SWITCH_FAIL_CHANCE) >= GEN_9 && lastEffect != EFFECT_ALLY_SWITCH)
|| GetGenConfig(GEN_ALLY_SWITCH_FAIL_CHANCE) < GEN_9)))
gDisableStructs[battler].protectUses = 0;
}
@ -10007,7 +10015,7 @@ void BS_RemoveStockpileCounters(void)
{
NATIVE_ARGS();
if (GetMoveEffect(gCurrentMove) == EFFECT_SWALLOW
if (GetMoveEffect(gCurrentMove) == EFFECT_SPIT_UP
&& gSpecialStatuses[gBattlerAttacker].parentalBondState == PARENTAL_BOND_1ST_HIT
&& IsBattlerAlive(gBattlerTarget))
{
@ -10129,7 +10137,7 @@ static void TryPlayStatChangeAnimation(u32 battler, u32 ability, u32 stats, s32
}
}
else if (!((ability == ABILITY_KEEN_EYE || ability == ABILITY_MINDS_EYE) && currStat == STAT_ACC)
&& !(B_ILLUMINATE_EFFECT >= GEN_9 && ability == ABILITY_ILLUMINATE && currStat == STAT_ACC)
&& !(GetGenConfig(GEN_ILLUMINATE_EFFECT) >= GEN_9 && ability == ABILITY_ILLUMINATE && currStat == STAT_ACC)
&& !(ability == ABILITY_HYPER_CUTTER && currStat == STAT_ATK)
&& !(ability == ABILITY_BIG_PECKS && currStat == STAT_DEF))
{
@ -10298,7 +10306,7 @@ static u32 ChangeStatBuffs(u32 battler, s8 statValue, u32 statId, union StatChan
}
else if (!flags.certain
&& (((battlerAbility == ABILITY_KEEN_EYE || battlerAbility == ABILITY_MINDS_EYE) && statId == STAT_ACC)
|| (B_ILLUMINATE_EFFECT >= GEN_9 && battlerAbility == ABILITY_ILLUMINATE && statId == STAT_ACC)
|| (GetGenConfig(GEN_ILLUMINATE_EFFECT) >= GEN_9 && battlerAbility == ABILITY_ILLUMINATE && statId == STAT_ACC)
|| (battlerAbility == ABILITY_HYPER_CUTTER && statId == STAT_ATK)
|| (battlerAbility == ABILITY_BIG_PECKS && statId == STAT_DEF)))
{
@ -14928,7 +14936,7 @@ static void TryUpdateRoundTurnOrder(void)
}
// update turn order for round users
for (i = 0; roundUsers[i] != 0xFF && i < 3; i++)
for (i = 0; i < 3 && roundUsers[i] != 0xFF; i++)
{
gBattlerByTurnOrder[currRounder] = roundUsers[i];
gProtectStructs[roundUsers[i]].quash = TRUE; // Make it so their turn order can't be changed again
@ -14936,7 +14944,7 @@ static void TryUpdateRoundTurnOrder(void)
}
// Update turn order for non-round users
for (i = 0; nonRoundUsers[i] != 0xFF && i < 3; i++)
for (i = 0; i < 3 && nonRoundUsers[i] != 0xFF; i++)
{
gBattlerByTurnOrder[currRounder] = nonRoundUsers[i];
currRounder++;
@ -15648,7 +15656,7 @@ void BS_TryAllySwitch(void)
{
gBattlescriptCurrInstr = cmd->failInstr;
}
else if (B_ALLY_SWITCH_FAIL_CHANCE >= GEN_9)
else if (GetGenConfig(GEN_ALLY_SWITCH_FAIL_CHANCE) >= GEN_9)
{
TryResetProtectUseCounter(gBattlerAttacker);
if (sProtectSuccessRates[gDisableStructs[gBattlerAttacker].protectUses] < Random())

View File

@ -1194,7 +1194,7 @@ void PrepareStringBattle(enum StringID stringId, u32 battler)
SET_STATCHANGER(STAT_SPEED, 1, FALSE);
}
if ((stringId == STRINGID_ITDOESNTAFFECT || stringId == STRINGID_PKMNWASNTAFFECTED || stringId == STRINGID_PKMNUNAFFECTED))
if ((stringId == STRINGID_ITDOESNTAFFECT || stringId == STRINGID_PKMNUNAFFECTED))
TryInitializeTrainerSlideEnemyMonUnaffected(gBattlerTarget);
BtlController_EmitPrintString(battler, B_COMM_TO_CONTROLLER, stringId);
@ -2379,7 +2379,7 @@ static enum MoveCanceller CancellerProtean(void)
u32 moveType = GetBattleMoveType(gCurrentMove);
if (ProteanTryChangeType(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), gCurrentMove, moveType))
{
if (B_PROTEAN_LIBERO == GEN_9)
if (GetGenConfig(GEN_PROTEAN_LIBERO) >= GEN_9)
gDisableStructs[gBattlerAttacker].usedProteanLibero = TRUE;
PREPARE_TYPE_BUFFER(gBattleTextBuff1, moveType);
gBattlerAbility = gBattlerAttacker;
@ -3046,7 +3046,7 @@ bool32 CanAbilityBlockMove(u32 battlerAtk, u32 battlerDef, u32 abilityAtk, u32 a
if (option == RUN_SCRIPT && !IsSpreadMove(GetBattlerMoveTargetType(battlerAtk, move)))
CancelMultiTurnMoves(battlerAtk, SKY_DROP_ATTACKCANCELLER_CHECK); // Don't cancel moves that can hit two targets bc one target might not be protected
battleScriptBlocksMove = BattleScript_DarkTypePreventsPrankster;
battleScriptBlocksMove = BattleScript_DoesntAffectTargetAtkString;
}
// Check def partner ability
@ -3856,12 +3856,12 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
}
break;
case ABILITY_SNOW_WARNING:
if (B_SNOW_WARNING >= GEN_9 && TryChangeBattleWeather(battler, BATTLE_WEATHER_SNOW, TRUE))
if (GetGenConfig(GEN_SNOW_WARNING) >= GEN_9 && TryChangeBattleWeather(battler, BATTLE_WEATHER_SNOW, TRUE))
{
BattleScriptPushCursorAndCallback(BattleScript_SnowWarningActivatesSnow);
effect++;
}
else if (B_SNOW_WARNING < GEN_9 && TryChangeBattleWeather(battler, BATTLE_WEATHER_HAIL, TRUE))
else if (GetGenConfig(GEN_SNOW_WARNING) < GEN_9 && TryChangeBattleWeather(battler, BATTLE_WEATHER_HAIL, TRUE))
{
BattleScriptPushCursorAndCallback(BattleScript_SnowWarningActivatesHail);
effect++;
@ -3959,7 +3959,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
if (!gSpecialStatuses[battler].switchInAbilityDone && CompareStat(battler, STAT_ATK, MAX_STAT_STAGE, CMP_LESS_THAN)
&& !GetBattlerPartyState(battler)->intrepidSwordBoost)
{
if (B_INTREPID_SWORD == GEN_9)
if (GetGenConfig(GEN_INTREPID_SWORD) == GEN_9)
GetBattlerPartyState(battler)->intrepidSwordBoost = TRUE;
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
SET_STATCHANGER(STAT_ATK, 1, FALSE);
@ -3971,7 +3971,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
if (!gSpecialStatuses[battler].switchInAbilityDone && CompareStat(battler, STAT_DEF, MAX_STAT_STAGE, CMP_LESS_THAN)
&& !GetBattlerPartyState(battler)->dauntlessShieldBoost)
{
if (B_DAUNTLESS_SHIELD == GEN_9)
if (GetGenConfig(GEN_DAUNTLESS_SHIELD) == GEN_9)
GetBattlerPartyState(battler)->dauntlessShieldBoost = TRUE;
gSpecialStatuses[battler].switchInAbilityDone = TRUE;
SET_STATCHANGER(STAT_DEF, 1, FALSE);
@ -7035,11 +7035,9 @@ u32 ItemBattleEffects(enum ItemCaseId caseID, u32 battler)
case HOLD_EFFECT_LIFE_ORB:
if (IsBattlerAlive(gBattlerAttacker)
&& !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)
&& !IsBattleMoveStatus(gCurrentMove)
&& (IsBattlerTurnDamaged(gBattlerTarget) || !(gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT)) // Needs the second check in case of Substitute
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg
&& !IsFutureSightAttackerInParty(gBattlerAttacker, gBattlerTarget, gCurrentMove)
&& !IsAbilityAndRecord(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), ABILITY_MAGIC_GUARD))
&& (IsBattlerTurnDamaged(gBattlerTarget) || gBattleScripting.savedDmg > 0)
&& !IsAbilityAndRecord(gBattlerAttacker, GetBattlerAbility(gBattlerAttacker), ABILITY_MAGIC_GUARD)
&& !IsFutureSightAttackerInParty(gBattlerAttacker, gBattlerTarget, gCurrentMove))
{
gBattleStruct->moveDamage[gBattlerAttacker] = GetNonDynamaxMaxHP(gBattlerAttacker) / 10;
if (gBattleStruct->moveDamage[gBattlerAttacker] == 0)
@ -7974,6 +7972,7 @@ static inline u32 CalcMoveBasePower(struct DamageContext *ctx)
u32 i;
u32 basePower = GetMovePower(move);
u32 moveEffect = GetMoveEffect(move);
u32 weight, hpFraction, speed;
if (GetActiveGimmick(battlerAtk) == GIMMICK_Z_MOVE)
@ -7982,7 +7981,7 @@ static inline u32 CalcMoveBasePower(struct DamageContext *ctx)
if (GetActiveGimmick(battlerAtk) == GIMMICK_DYNAMAX)
return GetMaxMovePower(move);
switch (GetMoveEffect(move))
switch (moveEffect)
{
case EFFECT_PLEDGE:
if (gBattleStruct->pledgeMove)
@ -8055,7 +8054,7 @@ static inline u32 CalcMoveBasePower(struct DamageContext *ctx)
basePower = gBattleMons[battlerDef].hp * basePower / gBattleMons[battlerDef].maxHP;
break;
case EFFECT_ASSURANCE:
if (gProtectStructs[battlerDef].physicalDmg != 0 || gProtectStructs[battlerDef].specialDmg != 0 || gProtectStructs[battlerDef].confusionSelfDmg)
if (gProtectStructs[battlerDef].assuranceDoubled)
basePower *= 2;
break;
case EFFECT_TRUMP_CARD:
@ -8141,18 +8140,15 @@ static inline u32 CalcMoveBasePower(struct DamageContext *ctx)
|| gDisableStructs[battlerDef].isFirstTurn == 2)
basePower *= 2;
break;
case EFFECT_ROUND:
for (i = 0; i < gBattlersCount; i++)
{
if (i != battlerAtk && IsBattlerAlive(i) && GetMoveEffect(gLastUsedMove) == EFFECT_ROUND)
{
basePower *= 2;
break;
}
}
break;
case EFFECT_FUSION_COMBO:
if (GetMoveEffect(gLastUsedMove) == EFFECT_FUSION_COMBO && move != gLastUsedMove)
if (move == gLastUsedMove)
break;
// fallthrough
case EFFECT_ROUND:
// don't double power due to previous turn's Round/Fusion move
if (gCurrentTurnActionNumber != 0
&& gActionsByTurnOrder[gCurrentTurnActionNumber - 1] == B_ACTION_USE_MOVE
&& GetMoveEffect(gLastUsedMove) == moveEffect)
basePower *= 2;
break;
case EFFECT_LASH_OUT:
@ -10793,7 +10789,7 @@ u16 GetUsedHeldItem(u32 battler)
bool32 CantPickupItem(u32 battler)
{
// Used by RandomUniformExcept() for RNG_PICKUP
if (battler == gBattlerAttacker && (B_PICKUP_WILD < GEN_9 || gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_LINK)))
if (battler == gBattlerAttacker && (GetGenConfig(GEN_PICKUP_WILD) < GEN_9 || gBattleTypeFlags & (BATTLE_TYPE_TRAINER | BATTLE_TYPE_LINK)))
return TRUE;
return !(IsBattlerAlive(battler) && GetUsedHeldItem(battler) && gBattleStruct->battlerState[battler].canPickupItem);
}
@ -11695,7 +11691,7 @@ u32 GetTotalAccuracy(u32 battlerAtk, u32 battlerDef, u32 move, u32 atkAbility, u
accStage = gBattleMons[battlerAtk].statStages[STAT_ACC];
evasionStage = gBattleMons[battlerDef].statStages[STAT_EVASION];
if (atkAbility == ABILITY_UNAWARE || atkAbility == ABILITY_KEEN_EYE || atkAbility == ABILITY_MINDS_EYE
|| (B_ILLUMINATE_EFFECT >= GEN_9 && atkAbility == ABILITY_ILLUMINATE))
|| (GetGenConfig(GEN_ILLUMINATE_EFFECT) >= GEN_9 && atkAbility == ABILITY_ILLUMINATE))
evasionStage = DEFAULT_STAT_STAGE;
if (MoveIgnoresDefenseEvasionStages(move))
evasionStage = DEFAULT_STAT_STAGE;

View File

@ -1661,7 +1661,7 @@ static bool8 FallWarpEffect_End(struct Task *task)
#define tState data[0]
#define tGoingUp data[1]
static void HideFollowerForFieldEffect(void)
void HideFollowerForFieldEffect(void)
{
struct ObjectEvent *followerObj = GetFollowerObject();
if (!followerObj || followerObj->invisible)

View File

@ -382,8 +382,16 @@ void PlayerStep(u8 direction, u16 newKeys, u16 heldKeys)
DoPlayerAvatarTransition();
if (TryDoMetatileBehaviorForcedMovement() == 0)
{
MovePlayerAvatarUsingKeypadInput(direction, newKeys, heldKeys);
PlayerAllowForcedMovementIfMovingSameDirection();
if (GetFollowerNPCData(FNPC_DATA_FORCED_MOVEMENT) != FALSE)
{
gPlayerAvatar.preventStep = TRUE;
CreateTask(Task_MoveNPCFollowerAfterForcedMovement, 1);
}
else
{
MovePlayerAvatarUsingKeypadInput(direction, newKeys, heldKeys);
PlayerAllowForcedMovementIfMovingSameDirection();
}
}
}
}
@ -510,7 +518,11 @@ static bool8 DoForcedMovement(u8 direction, void (*moveFunc)(u8))
else
{
if (collision == COLLISION_LEDGE_JUMP)
{
SetFollowerNPCData(FNPC_DATA_FORCED_MOVEMENT, FALSE);
PlayerJumpLedge(direction);
}
playerAvatar->flags |= PLAYER_AVATAR_FLAG_FORCED_MOVE;
playerAvatar->runningState = MOVING;
return TRUE;
@ -518,12 +530,11 @@ static bool8 DoForcedMovement(u8 direction, void (*moveFunc)(u8))
}
else
{
if (PlayerHasFollowerNPC())
SetFollowerNPCData(FNPC_DATA_FORCED_MOVEMENT, TRUE);
playerAvatar->runningState = MOVING;
moveFunc(direction);
if (PlayerHasFollowerNPC()
&& gObjectEvents[GetFollowerNPCObjectId()].invisible == FALSE
&& FindTaskIdByFunc(Task_MoveNPCFollowerAfterForcedMovement) == TASK_NONE)
CreateTask(Task_MoveNPCFollowerAfterForcedMovement, 3);
return TRUE;
}
}

View File

@ -1571,12 +1571,14 @@ static void Task_ExitStairs(u8 taskId)
tState++;
break;
}
gObjectEvents[gPlayerAvatar.objectEventId].noShadow = FALSE;
}
static void ForceStairsMovement(u32 metatileBehavior, s16 *speedX, s16 *speedY)
{
ObjectEventForceSetHeldMovement(&gObjectEvents[gPlayerAvatar.objectEventId], GetWalkInPlaceNormalMovementAction(GetPlayerFacingDirection()));
GetStairsMovementDirection(metatileBehavior, speedX, speedY);
gObjectEvents[gPlayerAvatar.objectEventId].noShadow = TRUE;
}
#undef tSpeedX
#undef tSpeedY
@ -1620,6 +1622,7 @@ static void Task_StairWarp(u8 taskId)
LockPlayerFieldControls();
FreezeObjectEvents();
CameraObjectFreeze();
HideFollowerForFieldEffect();
tState++;
break;
case 1:

View File

@ -84,6 +84,9 @@ void SetFollowerNPCData(enum FollowerNPCDataTypes type, u32 value)
case FNPC_DATA_COME_OUT_DOOR:
gSaveBlock3Ptr->NPCfollower.comeOutDoorStairs = value;
break;
case FNPC_DATA_FORCED_MOVEMENT:
gSaveBlock3Ptr->NPCfollower.forcedMovement = value;
break;
case FNPC_DATA_OBJ_ID:
gSaveBlock3Ptr->NPCfollower.objId = value;
break;
@ -147,6 +150,8 @@ u32 GetFollowerNPCData(enum FollowerNPCDataTypes type)
return gSaveBlock3Ptr->NPCfollower.createSurfBlob;
case FNPC_DATA_COME_OUT_DOOR:
return gSaveBlock3Ptr->NPCfollower.comeOutDoorStairs;
case FNPC_DATA_FORCED_MOVEMENT:
return gSaveBlock3Ptr->NPCfollower.forcedMovement;
case FNPC_DATA_OBJ_ID:
return gSaveBlock3Ptr->NPCfollower.objId;
case FNPC_DATA_CURRENT_SPRITE:
@ -757,9 +762,7 @@ u32 DetermineFollowerNPCState(struct ObjectEvent *follower, u32 state, u32 direc
MoveCoords(direction, &followerX, &followerY);
nextBehavior = MapGridGetMetatileBehaviorAt(followerX, followerY);
if (FindTaskIdByFunc(Task_MoveNPCFollowerAfterForcedMovement) == TASK_NONE)
follower->facingDirectionLocked = FALSE;
follower->facingDirectionLocked = FALSE;
// Follower won't do delayed movement until player does a movement.
if (!IsStateMovement(state) && delayedState)
@ -814,10 +817,6 @@ u32 DetermineFollowerNPCState(struct ObjectEvent *follower, u32 state, u32 direc
RETURN_STATE(MOVEMENT_ACTION_WALK_NORMAL_DOWN, direction);
case MOVEMENT_ACTION_WALK_FAST_DOWN ... MOVEMENT_ACTION_WALK_FAST_RIGHT:
// Handle player on waterfall.
if (PlayerIsUnderWaterfall(&gObjectEvents[gPlayerAvatar.objectEventId]) && (state == MOVEMENT_ACTION_WALK_FAST_UP))
return MOVEMENT_INVALID;
// Handle ice tile (some walking animation).
if (MetatileBehavior_IsIce(follower->currentMetatileBehavior) || MetatileBehavior_IsTrickHouseSlipperyFloor(follower->currentMetatileBehavior))
follower->disableAnim = TRUE;
@ -826,6 +825,9 @@ u32 DetermineFollowerNPCState(struct ObjectEvent *follower, u32 state, u32 direc
if (GetFollowerNPCData(FNPC_DATA_CURRENT_SPRITE) == FOLLOWER_NPC_SPRITE_INDEX_SURF && GetFollowerNPCSprite() == GetFollowerNPCData(FNPC_DATA_GFX_ID))
RETURN_STATE(MOVEMENT_ACTION_SURF_STILL_DOWN, direction);
if (MetatileBehavior_IsMuddySlope(follower->currentMetatileBehavior))
follower->facingDirectionLocked = TRUE;
RETURN_STATE(MOVEMENT_ACTION_WALK_FAST_DOWN, direction);
case MOVEMENT_ACTION_WALK_FASTER_DOWN ... MOVEMENT_ACTION_WALK_FASTER_RIGHT:
@ -835,10 +837,6 @@ u32 DetermineFollowerNPCState(struct ObjectEvent *follower, u32 state, u32 direc
RETURN_STATE(MOVEMENT_ACTION_WALK_FASTER_DOWN, direction);
case MOVEMENT_ACTION_RIDE_WATER_CURRENT_DOWN ... MOVEMENT_ACTION_RIDE_WATER_CURRENT_RIGHT:
// Handle player on waterfall.
if (PlayerIsUnderWaterfall(&gObjectEvents[gPlayerAvatar.objectEventId]) && IsPlayerSurfingNorth())
return MOVEMENT_INVALID;
RETURN_STATE(MOVEMENT_ACTION_RIDE_WATER_CURRENT_DOWN, direction);
// Acro bike.
@ -1581,37 +1579,20 @@ void FollowerNPC_TryRemoveFollowerOnWhiteOut(void)
#undef tDoorY
// Task data
#define PREVENT_PLAYER_STEP 0
#define DO_ALL_FORCED_MOVEMENTS 1
#define NPC_INTO_PLAYER 2
#define ENABLE_PLAYER_STEP 3
#define NPC_INTO_PLAYER 0
#define ENABLE_PLAYER_STEP 1
void Task_MoveNPCFollowerAfterForcedMovement(u8 taskId)
{
struct ObjectEvent *follower = &gObjectEvents[GetFollowerNPCObjectId()];
struct ObjectEvent *player = &gObjectEvents[gPlayerAvatar.objectEventId];
// Prevent player input until all forced mmovements are done and the follower is hidden.
if (gTasks[taskId].tState == PREVENT_PLAYER_STEP)
// The NPC will take an extra step and be on the same tile as the player.
if (gTasks[taskId].tState == NPC_INTO_PLAYER && ObjectEventClearHeldMovementIfFinished(player) != 0 && ObjectEventClearHeldMovementIfFinished(follower) != 0)
{
gPlayerAvatar.preventStep = TRUE;
gTasks[taskId].tState = DO_ALL_FORCED_MOVEMENTS;
}
// The player will keep doing forced movments until they land on a non-forced-move metatile or hit collision.
else if (gTasks[taskId].tState == DO_ALL_FORCED_MOVEMENTS && ObjectEventClearHeldMovementIfFinished(player) != 0)
{
// Lock follower facing direction for muddy slope.
if (follower->currentMetatileBehavior == MB_MUDDY_SLOPE)
follower->facingDirectionLocked = TRUE;
if (TryDoMetatileBehaviorForcedMovement() == 0)
gTasks[taskId].tState = NPC_INTO_PLAYER;
return;
}
// The NPC will take an extra step and be on the same tile as the player.
else if (gTasks[taskId].tState == NPC_INTO_PLAYER && ObjectEventClearHeldMovementIfFinished(player) != 0 && ObjectEventClearHeldMovementIfFinished(follower) != 0)
{
ObjectEventSetHeldMovement(follower, GetWalkFastMovementAction(DetermineFollowerNPCDirection(player, follower)));
gTasks[taskId].tState = ENABLE_PLAYER_STEP;
return;
@ -1622,14 +1603,13 @@ void Task_MoveNPCFollowerAfterForcedMovement(u8 taskId)
follower->facingDirectionLocked = FALSE;
HideNPCFollower();
SetFollowerNPCData(FNPC_DATA_WARP_END, FNPC_WARP_REAPPEAR);
SetFollowerNPCData(FNPC_DATA_FORCED_MOVEMENT, FALSE);
gPlayerAvatar.preventStep = FALSE;
DestroyTask(taskId);
}
}
#undef tState
#undef PREVENT_PLAYER_STEP
#undef DO_ALL_FORCED_MOVEMENTS
#undef NPC_INTO_PLAYER
#undef ENABLE_PLAYER_STEP

View File

@ -49,6 +49,7 @@
static void SetUpItemUseCallback(u8);
static void FieldCB_UseItemOnField(void);
static void Task_CallItemUseOnFieldCallback(u8);
static void Task_PartyMenuItemUseFromField(u8);
static void Task_UseItemfinder(u8);
static void Task_CloseItemfinderMessage(u8);
static void Task_HiddenItemNearby(u8);
@ -127,15 +128,25 @@ static void SetUpItemUseCallback(u8 taskId)
type = gTasks[taskId].tEnigmaBerryType - 1;
else
type = GetItemType(gSpecialVar_ItemId) - 1;
if (CurrentBattlePyramidLocation() == PYRAMID_LOCATION_NONE)
if (gTasks[taskId].tUsingRegisteredKeyItem && type == (ITEM_USE_PARTY_MENU - 1))
{
gBagMenu->newScreenCallback = sItemUseCallbacks[type];
Task_FadeAndCloseBagMenu(taskId);
FadeScreen(FADE_TO_BLACK, 0);
gPartyMenu.data1 = DATA1_PARTY_MENU_FROM_FIELD;
gTasks[taskId].func = Task_PartyMenuItemUseFromField;
}
else
{
gPyramidBagMenu->newScreenCallback = sItemUseCallbacks[type];
CloseBattlePyramidBag(taskId);
if (CurrentBattlePyramidLocation() == PYRAMID_LOCATION_NONE)
{
gBagMenu->newScreenCallback = sItemUseCallbacks[type];
Task_FadeAndCloseBagMenu(taskId);
}
else
{
gPyramidBagMenu->newScreenCallback = sItemUseCallbacks[type];
CloseBattlePyramidBag(taskId);
}
}
}
@ -164,6 +175,16 @@ static void Task_CallItemUseOnFieldCallback(u8 taskId)
sItemUseOnFieldCB(taskId);
}
static void Task_PartyMenuItemUseFromField(u8 taskId)
{
if (!gPaletteFade.active)
{
CleanupOverworldWindowsAndTilemaps();
SetMainCallback2(CB2_ShowPartyMenuForItemUse);
DestroyTask(taskId);
}
}
static void DisplayCannotUseItemMessage(u8 taskId, bool8 isUsingRegisteredKeyItemOnField, const u8 *str)
{
StringExpandPlaceholders(gStringVar4, str);
@ -1392,77 +1413,37 @@ void ItemUseOutOfBattle_EnigmaBerry(u8 taskId)
void ItemUseOutOfBattle_FormChange(u8 taskId)
{
if (!gTasks[taskId].tUsingRegisteredKeyItem)
{
gItemUseCB = ItemUseCB_FormChange;
gTasks[taskId].data[0] = FALSE;
SetUpItemUseOnFieldCallback(taskId);
}
else
{
// TODO: handle key items with callbacks to menus allow to be used by registering them.
DisplayDadsAdviceCannotUseItemMessage(taskId, gTasks[taskId].tUsingRegisteredKeyItem);
}
gItemUseCB = ItemUseCB_FormChange;
gTasks[taskId].data[0] = FALSE;
SetUpItemUseCallback(taskId);
}
void ItemUseOutOfBattle_FormChange_ConsumedOnUse(u8 taskId)
{
if (!gTasks[taskId].tUsingRegisteredKeyItem)
{
gItemUseCB = ItemUseCB_FormChange_ConsumedOnUse;
gTasks[taskId].data[0] = TRUE;
SetUpItemUseOnFieldCallback(taskId);
}
else
{
// TODO: handle key items with callbacks to menus allow to be used by registering them.
DisplayDadsAdviceCannotUseItemMessage(taskId, gTasks[taskId].tUsingRegisteredKeyItem);
}
gItemUseCB = ItemUseCB_FormChange_ConsumedOnUse;
gTasks[taskId].data[0] = TRUE;
SetUpItemUseCallback(taskId);
}
void ItemUseOutOfBattle_RotomCatalog(u8 taskId)
{
if (!gTasks[taskId].tUsingRegisteredKeyItem)
{
gItemUseCB = ItemUseCB_RotomCatalog;
gTasks[taskId].data[0] = TRUE;
SetUpItemUseOnFieldCallback(taskId);
}
else
{
// TODO: handle key items with callbacks to menus allow to be used by registering them.
DisplayDadsAdviceCannotUseItemMessage(taskId, gTasks[taskId].tUsingRegisteredKeyItem);
}
gItemUseCB = ItemUseCB_RotomCatalog;
gTasks[taskId].data[0] = TRUE;
SetUpItemUseCallback(taskId);
}
void ItemUseOutOfBattle_ZygardeCube(u8 taskId)
{
if (!gTasks[taskId].tUsingRegisteredKeyItem)
{
gItemUseCB = ItemUseCB_ZygardeCube;
gTasks[taskId].data[0] = TRUE;
SetUpItemUseOnFieldCallback(taskId);
}
else
{
// TODO: handle key items with callbacks to menus allow to be used by registering them.
DisplayDadsAdviceCannotUseItemMessage(taskId, gTasks[taskId].tUsingRegisteredKeyItem);
}
gItemUseCB = ItemUseCB_ZygardeCube;
gTasks[taskId].data[0] = TRUE;
SetUpItemUseCallback(taskId);
}
void ItemUseOutOfBattle_Fusion(u8 taskId)
{
if (!gTasks[taskId].tUsingRegisteredKeyItem)
{
gItemUseCB = ItemUseCB_Fusion;
gTasks[taskId].data[0] = FALSE;
SetUpItemUseCallback(taskId);
}
else
{
// TODO: handle key items with callbacks to menus allow to be used by registering them.
DisplayDadsAdviceCannotUseItemMessage(taskId, gTasks[taskId].tUsingRegisteredKeyItem);
}
gItemUseCB = ItemUseCB_Fusion;
gTasks[taskId].data[0] = FALSE;
SetUpItemUseCallback(taskId);
}
void Task_UseHoneyOnField(u8 taskId)
@ -1621,8 +1602,7 @@ void ItemUseOutOfBattle_TownMap(u8 taskId)
}
else
{
// TODO: handle key items with callbacks to menus allow to be used by registering them.
DisplayDadsAdviceCannotUseItemMessage(taskId, gTasks[taskId].tUsingRegisteredKeyItem);
gTasks[taskId].func = ItemUseOnFieldCB_TownMap;
}
}

View File

@ -1558,7 +1558,7 @@ const struct BlendSettings gTimeOfDayBlend[] =
};
#define DEFAULT_WEIGHT 256
#define TIME_BLEND_WEIGHT(begin, end) (DEFAULT_WEIGHT - (DEFAULT_WEIGHT * ((hours - begin) * MINUTES_PER_HOUR + minutes) / ((end - begin) * MINUTES_PER_HOUR)))
#define TIME_BLEND_WEIGHT(begin, end) (DEFAULT_WEIGHT - (DEFAULT_WEIGHT * SAFE_DIV(((hours - begin) * MINUTES_PER_HOUR + minutes), ((end - begin) * MINUTES_PER_HOUR))))
#define MORNING_HOUR_MIDDLE (MORNING_HOUR_BEGIN + ((MORNING_HOUR_END - MORNING_HOUR_BEGIN) / 2))

View File

@ -4536,6 +4536,12 @@ void CB2_ShowPartyMenuForItemUse(void)
u8 msgId;
TaskFunc task;
if (gPartyMenu.data1 == DATA1_PARTY_MENU_FROM_FIELD)
{
callback = CB2_ReturnToField;
gPartyMenu.data1 = 0;
}
if (gMain.inBattle)
{
menuType = PARTY_MENU_TYPE_IN_BATTLE;

View File

@ -1,11 +1,6 @@
#include "global.h"
#include "test/battle.h"
ASSUMPTIONS
{
ASSUME(B_DAUNTLESS_SHIELD == GEN_9);
}
SINGLE_BATTLE_TEST("Dauntless Shield raises Defense by one stage")
{
GIVEN {
@ -22,9 +17,32 @@ SINGLE_BATTLE_TEST("Dauntless Shield raises Defense by one stage")
}
}
SINGLE_BATTLE_TEST("Dauntless Shield raises Defense by one stage only once per battle")
SINGLE_BATTLE_TEST("Dauntless Shield raises Defense by one stage every time it switches in (Gen8)")
{
GIVEN {
WITH_CONFIG(GEN_DAUNTLESS_SHIELD, GEN_8);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_ZAMAZENTA) { Ability(ABILITY_DAUNTLESS_SHIELD); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { SWITCH(opponent, 1); }
TURN { SWITCH(opponent, 0); }
} SCENE {
ABILITY_POPUP(opponent, ABILITY_DAUNTLESS_SHIELD);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
MESSAGE("The opposing Zamazenta's Dauntless Shield raised its Defense!");
ABILITY_POPUP(opponent, ABILITY_DAUNTLESS_SHIELD);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
MESSAGE("The opposing Zamazenta's Dauntless Shield raised its Defense!");
} THEN {
EXPECT_EQ(opponent->statStages[STAT_DEF], DEFAULT_STAT_STAGE + 1);
}
}
SINGLE_BATTLE_TEST("Dauntless Shield raises Defense by one stage only once per battle (Gen 9+)")
{
GIVEN {
WITH_CONFIG(GEN_DAUNTLESS_SHIELD, GEN_9);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_ZAMAZENTA) { Ability(ABILITY_DAUNTLESS_SHIELD); }
OPPONENT(SPECIES_WYNAUT);
@ -63,4 +81,3 @@ SINGLE_BATTLE_TEST("Dauntless Shield activates when it's no longer effected by N
MESSAGE("The opposing Zamazenta's Dauntless Shield raised its Defense!");
}
}

View File

@ -1,11 +1,6 @@
#include "global.h"
#include "test/battle.h"
ASSUMPTIONS
{
ASSUME(B_INTREPID_SWORD == GEN_9);
}
SINGLE_BATTLE_TEST("Intrepid Sword raises Attack by one stage")
{
GIVEN {
@ -22,9 +17,32 @@ SINGLE_BATTLE_TEST("Intrepid Sword raises Attack by one stage")
}
}
SINGLE_BATTLE_TEST("Intrepid Sword raises Attack by one stage only once per battle")
SINGLE_BATTLE_TEST("Intrepid Sword raises Attack by one stage every time it switches in (Gen8)")
{
GIVEN {
WITH_CONFIG(GEN_INTREPID_SWORD, GEN_8);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { SWITCH(opponent, 1); }
TURN { SWITCH(opponent, 0); }
} SCENE {
ABILITY_POPUP(opponent, ABILITY_INTREPID_SWORD);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
MESSAGE("The opposing Zacian's Intrepid Sword raised its Attack!");
ABILITY_POPUP(opponent, ABILITY_INTREPID_SWORD);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponent);
MESSAGE("The opposing Zacian's Intrepid Sword raised its Attack!");
} THEN {
EXPECT_EQ(opponent->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1);
}
}
SINGLE_BATTLE_TEST("Intrepid Sword raises Attack by one stage only once per battle (Gen9+)")
{
GIVEN {
WITH_CONFIG(GEN_INTREPID_SWORD, GEN_9);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_ZACIAN) { Ability(ABILITY_INTREPID_SWORD); }
OPPONENT(SPECIES_WYNAUT);

View File

@ -5,7 +5,6 @@ ASSUMPTIONS
{
ASSUME(GetMoveAccuracy(MOVE_SCRATCH) == 100);
ASSUME(GetMoveEffect(MOVE_SAND_ATTACK) == EFFECT_ACCURACY_DOWN);
ASSUME(B_ILLUMINATE_EFFECT >= GEN_9);
}
SINGLE_BATTLE_TEST("Keen Eye, Gen9+ Illuminate & Minds Eye prevent accuracy stage reduction from moves")
@ -19,6 +18,7 @@ SINGLE_BATTLE_TEST("Keen Eye, Gen9+ Illuminate & Minds Eye prevent accuracy stag
PASSES_RANDOMLY(100, 100, RNG_ACCURACY);
GIVEN {
WITH_CONFIG(GEN_ILLUMINATE_EFFECT, GEN_9);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(species) { Ability(ability); }
} WHEN {
@ -47,6 +47,7 @@ SINGLE_BATTLE_TEST("Keen Eye, Gen9+ Illuminate & Minds Eye ignore target's evasi
PASSES_RANDOMLY(100, 100, RNG_ACCURACY);
GIVEN {
WITH_CONFIG(GEN_ILLUMINATE_EFFECT, GEN_9);
ASSUME(GetMoveEffect(MOVE_DOUBLE_TEAM) == EFFECT_EVASION_UP);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(species) { Ability(ability); }
@ -80,6 +81,7 @@ SINGLE_BATTLE_TEST("Keen Eye, Gen9+ Illuminate & Minds Eye are ignored by Mold B
PASSES_RANDOMLY(GetMoveAccuracy(MOVE_SCRATCH) * 3 / 4, 100, RNG_ACCURACY);
GIVEN {
WITH_CONFIG(GEN_ILLUMINATE_EFFECT, GEN_9);
PLAYER(speciesPlayer) { Ability(abilityPlayer); }
OPPONENT(speciesOpponent) { Ability(abilityOpponent); }
} WHEN {
@ -102,6 +104,7 @@ SINGLE_BATTLE_TEST("Keen Eye, Gen9+ Illuminate & Minds Eye don't prevent Topsy-T
PARAMETRIZE { species = SPECIES_URSALUNA_BLOODMOON; ability = ABILITY_MINDS_EYE; }
GIVEN {
WITH_CONFIG(GEN_ILLUMINATE_EFFECT, GEN_9);
ASSUME(GetMoveEffect(MOVE_HONE_CLAWS) == EFFECT_ATTACK_ACCURACY_UP);
ASSUME(GetMoveEffect(MOVE_TOPSY_TURVY) == EFFECT_TOPSY_TURVY);
PLAYER(SPECIES_WOBBUFFET);
@ -141,6 +144,7 @@ SINGLE_BATTLE_TEST("Keen Eye, Gen9+ Illuminate & Minds Eye don't prevent receivi
PARAMETRIZE { species = SPECIES_URSALUNA_BLOODMOON; ability = ABILITY_MINDS_EYE; }
GIVEN {
WITH_CONFIG(GEN_ILLUMINATE_EFFECT, GEN_9);
ASSUME(GetMoveEffect(MOVE_BATON_PASS) == EFFECT_BATON_PASS);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
@ -173,6 +177,7 @@ SINGLE_BATTLE_TEST("Keen Eye & Gen9+ Illuminate don't prevent Spectral Thief fro
PARAMETRIZE { species = SPECIES_STARYU; ability = ABILITY_ILLUMINATE; }
GIVEN {
WITH_CONFIG(GEN_ILLUMINATE_EFFECT, GEN_9);
ASSUME(GetMoveEffect(MOVE_HONE_CLAWS) == EFFECT_ATTACK_ACCURACY_UP);
ASSUME(GetMoveEffect(MOVE_SPECTRAL_THIEF) == EFFECT_SPECTRAL_THIEF);
PLAYER(SPECIES_WOBBUFFET);

View File

@ -109,7 +109,7 @@ SINGLE_BATTLE_TEST("Liquid Ooze causes Strength Sap users to lose HP instead of
}
/* * https://bulbapedia.bulbagarden.net/wiki/Liquid_Ooze_(Ability)#In_battle:
* If the recipient of Leech Seed's effect were to faint due to Liquid Ooze on the same turn as the victim of Leech Seed, then the victim faints before the recipient. This means that the victim's team loses the battle if both teams had their final Pokémon sent out.
* If the recipient of Leech Seed's effect were to faint due to Liquid Ooze on the same turn as the victim of Leech Seed, then the victim faints before the recipient. This means that the victim's team loses the battle if both teams had their final Pokémon sent out.
*/
SINGLE_BATTLE_TEST("Liquid Ooze causes leech seed victim to faint before seeder")
{
@ -142,6 +142,7 @@ SINGLE_BATTLE_TEST("Liquid Ooze causes Dream Eater users to lose HP instead of h
{
s16 damage;
GIVEN {
WITH_CONFIG(GEN_DREAM_EATER_LIQUID_OOZE, GEN_5);
ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS);
ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP);
ASSUME(GetMoveEffect(MOVE_DREAM_EATER) == EFFECT_DREAM_EATER);
@ -158,8 +159,31 @@ SINGLE_BATTLE_TEST("Liquid Ooze causes Dream Eater users to lose HP instead of h
HP_BAR(opponent);
HP_BAR(player, captureDamage: &damage);
} THEN {
EXPECT_LT(damage, 0);
EXPECT_GT(damage, 0); // Positive damage
}
}
TO_DO_BATTLE_TEST("Liquid Ooze does not cause Dream Eater users to lose HP instead of heal (Gen 3-4")
SINGLE_BATTLE_TEST("Liquid Ooze does not cause Dream Eater users to lose HP instead of heal (Gen 3-4")
{
s16 damage;
GIVEN {
WITH_CONFIG(GEN_DREAM_EATER_LIQUID_OOZE, GEN_3);
ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS);
ASSUME(GetMoveNonVolatileStatus(MOVE_SPORE) == MOVE_EFFECT_SLEEP);
ASSUME(GetMoveEffect(MOVE_DREAM_EATER) == EFFECT_DREAM_EATER);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_TENTACRUEL) { Ability(ABILITY_LIQUID_OOZE); }
} WHEN {
TURN { MOVE(opponent, MOVE_SCRATCH); MOVE(player, MOVE_SPORE); }
TURN { MOVE(player, MOVE_DREAM_EATER); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent);
HP_BAR(player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SPORE, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_DREAM_EATER, player);
HP_BAR(opponent);
HP_BAR(player, captureDamage: &damage);
} THEN {
EXPECT_LT(damage, 0); // Negative damage = Heal
}
}

View File

@ -156,53 +156,29 @@ SINGLE_BATTLE_TEST("Normalize boosts power of affected moves by 20% (Gen7+)", s1
}
}
SINGLE_BATTLE_TEST("Normalize-affected moves become Electric-type under Electrify's effect", s16 damage)
SINGLE_BATTLE_TEST("Normalize-affected moves become Electric-type under Electrify's effect")
{
u32 ability, genConfig;
PARAMETRIZE { ability = ABILITY_CUTE_CHARM; genConfig = GEN_7; }
PARAMETRIZE { ability = ABILITY_CUTE_CHARM; genConfig = GEN_6; }
PARAMETRIZE { ability = ABILITY_NORMALIZE; genConfig = GEN_7; }
PARAMETRIZE { ability = ABILITY_NORMALIZE; genConfig = GEN_6; }
GIVEN {
ASSUME(GetMoveEffect(MOVE_ELECTRIFY) == EFFECT_ELECTRIFY);
WITH_CONFIG(GEN_CONFIG_ATE_MULTIPLIER, genConfig);
PLAYER(SPECIES_SKITTY) { Ability(ability); Moves(MOVE_WATER_GUN); }
OPPONENT(SPECIES_WOBBUFFET);
PLAYER(SPECIES_SKITTY) { Ability(ABILITY_NORMALIZE); }
OPPONENT(SPECIES_ROOKIDEE) { Item(ITEM_WACAN_BERRY); }
} WHEN {
TURN { MOVE(opponent, MOVE_ELECTRIFY); MOVE(player, MOVE_WATER_GUN); }
} SCENE {
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
if (genConfig >= GEN_7)
EXPECT_EQ(results[0].damage, results[2].damage);
else
EXPECT_EQ(results[1].damage, results[3].damage);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent);
}
}
SINGLE_BATTLE_TEST("Normalize-affected moves become Electric-type under Ion Deluge's effect", s16 damage)
SINGLE_BATTLE_TEST("Normalize-affected moves become Electric-type under Ion Deluge's effect")
{
u32 ability, genConfig;
PARAMETRIZE { ability = ABILITY_CUTE_CHARM; genConfig = GEN_7; }
PARAMETRIZE { ability = ABILITY_CUTE_CHARM; genConfig = GEN_6; }
PARAMETRIZE { ability = ABILITY_NORMALIZE; genConfig = GEN_7; }
PARAMETRIZE { ability = ABILITY_NORMALIZE; genConfig = GEN_6; }
GIVEN {
ASSUME(GetMoveEffect(MOVE_ION_DELUGE) == EFFECT_ION_DELUGE);
WITH_CONFIG(GEN_CONFIG_ATE_MULTIPLIER, genConfig);
PLAYER(SPECIES_SKITTY) { Ability(ability); Moves(MOVE_WATER_GUN); }
OPPONENT(SPECIES_WOBBUFFET);
PLAYER(SPECIES_SKITTY) { Ability(ABILITY_NORMALIZE); Moves(MOVE_WATER_GUN); }
OPPONENT(SPECIES_ROOKIDEE) { Item(ITEM_WACAN_BERRY); }
} WHEN {
TURN { MOVE(opponent, MOVE_ION_DELUGE); MOVE(player, MOVE_WATER_GUN); }
} SCENE {
HP_BAR(opponent, captureDamage: &results[i].damage);
} FINALLY {
if (genConfig >= GEN_7)
EXPECT_EQ(results[0].damage, results[2].damage);
else
EXPECT_EQ(results[1].damage, results[3].damage);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent);
}
}

View File

@ -23,10 +23,10 @@ SINGLE_BATTLE_TEST("Pickup grants an item used by another Pokémon")
}
}
WILD_BATTLE_TEST("Pickup grants an item used by itself in wild battles (Gen 9)")
WILD_BATTLE_TEST("Pickup grants an item used by itself in wild battles (Gen9+)")
{
GIVEN {
ASSUME(B_PICKUP_WILD >= GEN_9);
WITH_CONFIG(GEN_PICKUP_WILD, GEN_9);
PLAYER(SPECIES_ZIGZAGOON) { Ability(ABILITY_PICKUP); MaxHP(100); HP(51); Item(ITEM_SITRUS_BERRY); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {

View File

@ -1,14 +1,36 @@
#include "global.h"
#include "test/battle.h"
ASSUMPTIONS
{
ASSUME(B_PROTEAN_LIBERO == GEN_9);
}
SINGLE_BATTLE_TEST("Protean changes the type of the user only once per switch in")
SINGLE_BATTLE_TEST("Protean changes the type of the user to the move used every time (Gen6-8)")
{
GIVEN {
WITH_CONFIG(GEN_PROTEAN_LIBERO, GEN_6);
PLAYER(SPECIES_REGIROCK);
OPPONENT(SPECIES_KECLEON) { Ability(ABILITY_PROTEAN); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_WATER_GUN); }
TURN { MOVE(opponent, MOVE_SCRATCH); }
TURN { SWITCH(opponent, 1); }
TURN { SWITCH(opponent, 0); }
TURN { MOVE(opponent, MOVE_WATER_GUN); }
} SCENE {
ABILITY_POPUP(opponent, ABILITY_PROTEAN);
MESSAGE("The opposing Kecleon transformed into the Water type!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent);
ABILITY_POPUP(opponent, ABILITY_PROTEAN);
MESSAGE("The opposing Kecleon transformed into the Normal type!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, opponent);
ABILITY_POPUP(opponent, ABILITY_PROTEAN);
MESSAGE("The opposing Kecleon transformed into the Water type!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_WATER_GUN, opponent);
}
}
SINGLE_BATTLE_TEST("Protean changes the type of the user only once per switch in (Gen9+)")
{
GIVEN {
WITH_CONFIG(GEN_PROTEAN_LIBERO, GEN_9);
PLAYER(SPECIES_REGIROCK);
OPPONENT(SPECIES_KECLEON) { Ability(ABILITY_PROTEAN); }
OPPONENT(SPECIES_WOBBUFFET);

View File

@ -52,6 +52,7 @@ SINGLE_BATTLE_TEST("Refrigerate doesn't affect Weather Ball's type", s16 damage)
PARAMETRIZE { move = MOVE_CELEBRATE; ability = ABILITY_REFRIGERATE; }
PARAMETRIZE { move = MOVE_SUNNY_DAY; ability = ABILITY_REFRIGERATE; }
GIVEN {
WITH_CONFIG(GEN_SNOW_WARNING, GEN_9); //To prevent capturing hail damage
ASSUME(GetMoveEffect(MOVE_WEATHER_BALL) == EFFECT_WEATHER_BALL);
ASSUME(GetSpeciesType(SPECIES_PINSIR, 0) == TYPE_BUG);
PLAYER(SPECIES_AMAURA) { Ability(ability); }

View File

@ -1,24 +1,30 @@
#include "global.h"
#include "test/battle.h"
#if B_SNOW_WARNING < GEN_9
SINGLE_BATTLE_TEST("Snow Warning summons hail")
#elif B_SNOW_WARNING >= GEN_9
SINGLE_BATTLE_TEST("Snow Warning summons snow")
#endif
SINGLE_BATTLE_TEST("Snow Warning summons hail (Gen4-8)")
{
GIVEN {
WITH_CONFIG(GEN_SNOW_WARNING, GEN_8);
PLAYER(SPECIES_ABOMASNOW) { Ability(ABILITY_SNOW_WARNING); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN {}
} SCENE {
#if B_SNOW_WARNING < GEN_9
MESSAGE("It started to hail!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HAIL_CONTINUES);
#elif B_SNOW_WARNING >= GEN_9
MESSAGE("It started to snow!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SNOW_CONTINUES);
#endif
}
}
SINGLE_BATTLE_TEST("Snow Warning summons snow (Gen9+)")
{
GIVEN {
WITH_CONFIG(GEN_SNOW_WARNING, GEN_9);
PLAYER(SPECIES_ABOMASNOW) { Ability(ABILITY_SNOW_WARNING); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN {}
} SCENE {
MESSAGE("It started to snow!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_SNOW_CONTINUES);
}
}

View File

@ -339,3 +339,27 @@ AI_SINGLE_BATTLE_TEST("AI uses Skill Swap against Poison Heal")
TURN { NOT_EXPECT_MOVE(opponent, MOVE_SKILL_SWAP); }
}
}
AI_SINGLE_BATTLE_TEST("AI uses Quick Guard against Quick Attack when opponent would take poison damage")
{
PASSES_RANDOMLY(PREDICT_MOVE_CHANCE, 100, RNG_AI_PREDICT_MOVE);
GIVEN {
PLAYER(SPECIES_RATTATA) { Moves(MOVE_QUICK_ATTACK); Status1(STATUS1_TOXIC_POISON); }
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT | AI_FLAG_PREDICT_MOVE);
OPPONENT(SPECIES_RATTATA) { Moves(MOVE_QUICK_GUARD, MOVE_TACKLE); }
} WHEN {
TURN { MOVE(player, MOVE_QUICK_ATTACK); EXPECT_MOVE(opponent, MOVE_QUICK_GUARD); }
}
}
AI_SINGLE_BATTLE_TEST("AI uses Wide Guard against Earthquake when opponent would take poison damage")
{
PASSES_RANDOMLY(PREDICT_MOVE_CHANCE, 100, RNG_AI_PREDICT_MOVE);
GIVEN {
PLAYER(SPECIES_RATTATA) { Moves(MOVE_EARTHQUAKE); Status1(STATUS1_TOXIC_POISON); }
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT | AI_FLAG_PREDICT_MOVE);
OPPONENT(SPECIES_RATTATA) { Moves(MOVE_WIDE_GUARD, MOVE_TACKLE); }
} WHEN {
TURN { MOVE(player, MOVE_EARTHQUAKE); EXPECT_MOVE(opponent, MOVE_WIDE_GUARD); }
}
}

View File

@ -842,6 +842,7 @@ AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has an
PARAMETRIZE { aiMon = SPECIES_SHIFTRY; absorbingAbility = ABILITY_WIND_RIDER; move = MOVE_HURRICANE;}
GIVEN {
ASSUME(B_REDIRECT_ABILITY_IMMUNITY >= GEN_5);
ASSUME(P_UPDATED_ABILITIES >= GEN_9); //For the predicted ability for Shiftry
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
PLAYER(SPECIES_ZIGZAGOON) { Moves(move); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_SCRATCH); }

View File

@ -81,3 +81,53 @@ SINGLE_BATTLE_TEST("Life Orb doesn't cause any HP loss if user is unable to atta
}
}
}
SINGLE_BATTLE_TEST("Life Orb does not activate if on a confusion hit")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LIFE_ORB); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_CONFUSE_RAY); MOVE(player, MOVE_POUND, WITH_RNG(RNG_CONFUSION, TRUE)); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CONFUSE_RAY, opponent);
HP_BAR(player);
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_POUND, player);
HP_BAR(opponent);
HP_BAR(player);
MESSAGE("Wobbuffet was hurt by the Life Orb!");
}
}
}
SINGLE_BATTLE_TEST("Life Orb does not activate if move was absorbed by target")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LIFE_ORB); }
OPPONENT(SPECIES_RAICHU) { Ability(ABILITY_LIGHTNING_ROD); }
} WHEN {
TURN { MOVE(player, MOVE_SHOCK_WAVE); }
} SCENE {
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SHOCK_WAVE, player);
HP_BAR(opponent);
HP_BAR(player);
MESSAGE("Wobbuffet was hurt by the Life Orb!");
}
}
}
SINGLE_BATTLE_TEST("Life Orb activates if move connected but no damage was dealt")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_LIFE_ORB); }
OPPONENT(SPECIES_WOBBUFFET) { HP(1); }
} WHEN {
TURN { MOVE(player, MOVE_FALSE_SWIPE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_FALSE_SWIPE, player);
HP_BAR(player);
MESSAGE("Wobbuffet was hurt by the Life Orb!");
}
}

View File

@ -185,10 +185,25 @@ DOUBLE_BATTLE_TEST("Ally Switch doesn't make self-targeting status moves fail")
}
}
DOUBLE_BATTLE_TEST("Ally Switch increases the Protect-like moves counter")
DOUBLE_BATTLE_TEST("Ally Switch doesn't increase the Protect-like moves counter (Gen5-8)")
{
GIVEN {
ASSUME(B_ALLY_SWITCH_FAIL_CHANCE >= GEN_9);
WITH_CONFIG(GEN_ALLY_SWITCH_FAIL_CHANCE, GEN_8);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(playerLeft, MOVE_ALLY_SWITCH); }
} THEN {
EXPECT(gDisableStructs[B_POSITION_PLAYER_RIGHT].protectUses == 0);
}
}
DOUBLE_BATTLE_TEST("Ally Switch increases the Protect-like moves counter (Gen9+)")
{
GIVEN {
WITH_CONFIG(GEN_ALLY_SWITCH_FAIL_CHANCE, GEN_9);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
@ -319,7 +334,7 @@ DOUBLE_BATTLE_TEST("Ally switch updates last used moves for Mimic")
OPPONENT(SPECIES_FEAROW) { Speed(20); }
OPPONENT(SPECIES_ARON) { Speed(30); }
} WHEN {
TURN { MOVE(playerRight, MOVE_FAKE_OUT, target: opponentRight); MOVE(playerLeft, MOVE_ALLY_SWITCH);
TURN { MOVE(playerRight, MOVE_FAKE_OUT, target: opponentRight); MOVE(playerLeft, MOVE_ALLY_SWITCH);
MOVE(opponentLeft, MOVE_MIMIC, target: playerLeft);
}
} SCENE {

View File

@ -6,3 +6,30 @@ TO_DO_BATTLE_TEST("Assurance doubles in power if the target has been damaged in
TO_DO_BATTLE_TEST("Assurance doubles in power if the target has been damaged in the same turn - Crash");
TO_DO_BATTLE_TEST("Assurance doubles in power if the target has been damaged in the same turn - Confusion");
TO_DO_BATTLE_TEST("Assurance doubles in power if the target has been damaged in the same turn - Rocky Helmet");
DOUBLE_BATTLE_TEST("Assurance doubles in power if False Swipe connected but didn't do any damage")
{
s16 hits[2];
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponentLeft, MOVE_ASSURANCE, target: playerRight); }
TURN {
MOVE(opponentLeft, MOVE_FALSE_SWIPE, target: playerLeft);
MOVE(playerLeft, MOVE_RECOVER);
MOVE(opponentRight, MOVE_ASSURANCE, target: playerLeft);
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_ASSURANCE, opponentLeft);
HP_BAR(playerRight, captureDamage: &hits[0]);
ANIMATION(ANIM_TYPE_MOVE, MOVE_FALSE_SWIPE, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_ASSURANCE, opponentRight);
HP_BAR(playerLeft, captureDamage: &hits[1]);
} THEN {
EXPECT_MUL_EQ(hits[0], Q_4_12(2.0), hits[1]);
}
}

View File

@ -137,9 +137,14 @@ SINGLE_BATTLE_TEST("Chloroblast is not affected by Reckless", s16 damage)
u32 move;
PARAMETRIZE { move = MOVE_CHLOROBLAST; }
PARAMETRIZE { move = MOVE_FRENZY_PLANT; }
if (B_UPDATED_MOVE_DATA >= GEN_9) {
PARAMETRIZE { move = MOVE_FRENZY_PLANT; } // 150 power
} else {
PARAMETRIZE { move = MOVE_SEED_FLARE; } // 120 power
}
GIVEN {
ASSUME(GetMovePower(MOVE_CHLOROBLAST) == GetMovePower(move));
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {

View File

@ -33,7 +33,7 @@ SINGLE_BATTLE_TEST("Dream Eater fails on awake targets")
TURN { MOVE(player, MOVE_DREAM_EATER); }
} SCENE {
MESSAGE("Wobbuffet used Dream Eater!");
MESSAGE("The opposing Wobbuffet wasn't affected!");
MESSAGE("It doesn't affect the opposing Wobbuffet…");
}
}
@ -88,7 +88,7 @@ SINGLE_BATTLE_TEST("Dream Eater fails if the target is behind a Substitute (Gen
TURN { MOVE(opponent, MOVE_DREAM_EATER); }
} SCENE {
MESSAGE("The opposing Wobbuffet used Dream Eater!");
MESSAGE("Wobbuffet wasn't affected!");
MESSAGE("It doesn't affect Wobbuffet…");
}
}
#else

View File

@ -66,11 +66,11 @@ DOUBLE_BATTLE_TEST("Leech Seed will drain HP based on speed of the drained mon")
OPPONENT(SPECIES_WYNAUT) { Speed(3); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(4); }
} WHEN {
TURN {
MOVE(playerLeft, MOVE_LEECH_SEED, target: opponentLeft);
MOVE(playerRight, MOVE_LEECH_SEED, target: opponentRight);
MOVE(opponentLeft, MOVE_LEECH_SEED, target: playerLeft);
MOVE(opponentRight, MOVE_LEECH_SEED, target: playerRight);
TURN {
MOVE(playerLeft, MOVE_LEECH_SEED, target: opponentLeft);
MOVE(playerRight, MOVE_LEECH_SEED, target: opponentRight);
MOVE(opponentLeft, MOVE_LEECH_SEED, target: playerLeft);
MOVE(opponentRight, MOVE_LEECH_SEED, target: playerRight);
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_LEECH_SEED, opponentRight);
@ -88,6 +88,24 @@ DOUBLE_BATTLE_TEST("Leech Seed will drain HP based on speed of the drained mon")
}
}
SINGLE_BATTLE_TEST("Leech Seeded recovers health through Substitute")
{
GIVEN {
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_SUBSTITUTE); }
TURN { MOVE(player, MOVE_LEECH_SEED); }
TURN {}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SUBSTITUTE, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_LEECH_SEED, player);
HP_BAR(player);
HP_BAR(opponent);
HP_BAR(player);
}
}
TO_DO_BATTLE_TEST("Leech Seed doesn't affect already seeded targets")
TO_DO_BATTLE_TEST("Leech Seed's effect is paused until a new battler replaces the original user's position") // Faint, can't be replaced, then revived.
TO_DO_BATTLE_TEST("Leech Seed's effect pause still prevents it from being seeded again")

View File

@ -50,14 +50,27 @@ SINGLE_BATTLE_TEST("Synthesis recovers 1/4 of the user's max HP in Rain, Sandsto
}
}
SINGLE_BATTLE_TEST("Synthesis recovers regular amount in sandstorm if holding utility umbrella")
{
u32 item;
PARAMETRIZE { item = ITEM_LIFE_ORB; }
PARAMETRIZE { item = ITEM_UTILITY_UMBRELLA; }
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { HP(1); MaxHP(400); Item(item); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_SANDSTORM); MOVE(player, MOVE_SYNTHESIS); }
} SCENE {
if (item != ITEM_UTILITY_UMBRELLA)
HP_BAR(player, damage: -(400 / 4));
else
HP_BAR(player, damage: -(400 / 2));
}
}
TO_DO_BATTLE_TEST("TODO: Synthesis recovers 1/4 of the user's max HP while it is not day (Gen2)")
TO_DO_BATTLE_TEST("TODO: Synthesis recovers 1/2 of the user's max HP in Sunlight while it is not day (Gen2)")
TO_DO_BATTLE_TEST("TODO: Synthesis recovers 1/8 of the user's max HP in Rain, Sandstorm, Hail, and Snow while it is not day (Gen2)")
TO_DO_BATTLE_TEST("TODO: Synthesis recovers 2/4 of the user's max HP while it is day (Gen2)")
TO_DO_BATTLE_TEST("TODO: Synthesis recovers 2/2 of the user's max HP in Sunlight while it is day (Gen2)")
TO_DO_BATTLE_TEST("TODO: Synthesis recovers 2/8 of the user's max HP in Rain, Sandstorm, Hail, and Snow while it is day (Gen2)")

View File

@ -0,0 +1,19 @@
#include "global.h"
#include "test/battle.h"
SINGLE_BATTLE_TEST("Clear Smog removes stat changes even if it did no damage")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { HP(1); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_SWORDS_DANCE); }
TURN { MOVE(player, MOVE_ENDURE); MOVE(opponent, MOVE_CLEAR_SMOG); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_SWORDS_DANCE, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_ENDURE, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CLEAR_SMOG, opponent);
} THEN {
EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE);
}
}

View File

@ -107,13 +107,13 @@ SINGLE_BATTLE_TEST("Thief and Covet don't steal target's held item if target has
}
// Test can't currently verify if the item is sent to Bag
WILD_BATTLE_TEST("Thief and Covet steal target's held item and it's added to Bag in wild battles (Gen 9)")
WILD_BATTLE_TEST("Thief and Covet steal target's held item and it's added to Bag in wild battles (Gen 9+)")
{
u32 move;
PARAMETRIZE { move = MOVE_THIEF; }
PARAMETRIZE { move = MOVE_COVET; }
GIVEN {
ASSUME(B_STEAL_WILD_ITEMS >= GEN_9);
WITH_CONFIG(GEN_STEAL_WILD_ITEMS, GEN_9);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_HYPER_POTION); }
} WHEN {

View File

@ -4,6 +4,7 @@
#include "pokemon.h"
#include "test/overworld_script.h"
#include "test/test.h"
#include "constants/characters.h"
TEST("Nature independent from Hidden Nature")
{
@ -475,3 +476,100 @@ TEST("Optimised SetMonData")
EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_EXP), exp);
EXPECT_FASTER(optimised, vanilla);
}
TEST("BoxPokemon encryption works")
{
u32 raw[20] =
{
990384375,
2948624514,
3907508686,
14410461,
35316705,
3907508686,
64742109,
718729,
3102307966,
2160206402,
49956971,
2495766612,
1424318580,
273408756,
2371630199,
2708871082,
3059937332,
2529190026,
2290634828,
2870614922
};
struct Pokemon mon;
BoxMonToMon((struct BoxPokemon *)&raw, &mon);
EXPECT_EQ(GetMonData(&mon, MON_DATA_SANITY_IS_BAD_EGG), 0);
EXPECT_EQ(GetMonData(&mon, MON_DATA_SPECIES), SPECIES_TORCHIC);
EXPECT_EQ(GetMonData(&mon, MON_DATA_MARKINGS), 3);
const u8 *actualNickname = COMPOUND_STRING("Testing mon");
u8 nickname[12];
GetMonData(&mon, MON_DATA_NICKNAME, nickname);
u32 charIndex = 0;
while (actualNickname[charIndex] != EOS)
{
EXPECT_EQ(actualNickname[charIndex], nickname[charIndex]);
charIndex++;
}
EXPECT_EQ(GetNature(&mon), NATURE_HARDY);
EXPECT_EQ(GetMonData(&mon, MON_DATA_HIDDEN_NATURE), NATURE_ADAMANT);
EXPECT_EQ(GetMonData(&mon, MON_DATA_HP_LOST), 10);
EXPECT_EQ(GetMonData(&mon, MON_DATA_HELD_ITEM), ITEM_ORAN_BERRY);
EXPECT_EQ(GetMonData(&mon, MON_DATA_MOVE1), MOVE_TACKLE);
EXPECT_EQ(GetMonData(&mon, MON_DATA_MOVE2), MOVE_SCRATCH);
EXPECT_EQ(GetMonData(&mon, MON_DATA_MOVE3), MOVE_POUND);
EXPECT_EQ(GetMonData(&mon, MON_DATA_MOVE4), MOVE_GROWL);
EXPECT_EQ(GetMonData(&mon, MON_DATA_PP1), 1);
EXPECT_EQ(GetMonData(&mon, MON_DATA_PP2), 2);
EXPECT_EQ(GetMonData(&mon, MON_DATA_PP3), 3);
EXPECT_EQ(GetMonData(&mon, MON_DATA_PP4), 4);
EXPECT_EQ(GetMonData(&mon, MON_DATA_PP_BONUSES), 255);
EXPECT_EQ(GetMonData(&mon, MON_DATA_COOL), 10);
EXPECT_EQ(GetMonData(&mon, MON_DATA_BEAUTY), 20);
EXPECT_EQ(GetMonData(&mon, MON_DATA_CUTE), 30);
EXPECT_EQ(GetMonData(&mon, MON_DATA_SMART), 40);
EXPECT_EQ(GetMonData(&mon, MON_DATA_TOUGH), 50);
EXPECT_EQ(GetMonData(&mon, MON_DATA_SHEEN), 150);
EXPECT_EQ(GetMonData(&mon, MON_DATA_EXP), 12345);
EXPECT_EQ(GetMonData(&mon, MON_DATA_MET_LEVEL), 20);
EXPECT_EQ(GetMonData(&mon, MON_DATA_HP_EV), 11);
EXPECT_EQ(GetMonData(&mon, MON_DATA_ATK_EV), 22);
EXPECT_EQ(GetMonData(&mon, MON_DATA_DEF_EV), 33);
EXPECT_EQ(GetMonData(&mon, MON_DATA_SPEED_EV), 44);
EXPECT_EQ(GetMonData(&mon, MON_DATA_SPATK_EV), 55);
EXPECT_EQ(GetMonData(&mon, MON_DATA_SPDEF_EV), 66);
EXPECT_EQ(GetMonData(&mon, MON_DATA_FRIENDSHIP), 123);
EXPECT_EQ(GetMonData(&mon, MON_DATA_POKERUS), 2);
EXPECT_EQ(GetMonData(&mon, MON_DATA_POKEBALL), BALL_FRIEND);
EXPECT_EQ(GetMonData(&mon, MON_DATA_HP_IV), 31);
EXPECT_EQ(GetMonData(&mon, MON_DATA_ATK_IV), 30);
EXPECT_EQ(GetMonData(&mon, MON_DATA_DEF_IV), 29);
EXPECT_EQ(GetMonData(&mon, MON_DATA_SPEED_IV), 28);
EXPECT_EQ(GetMonData(&mon, MON_DATA_SPATK_IV), 27);
EXPECT_EQ(GetMonData(&mon, MON_DATA_SPDEF_IV), 26);
EXPECT_EQ(GetMonData(&mon, MON_DATA_CUTE_RIBBON), 1);
EXPECT_EQ(GetMonData(&mon, MON_DATA_BEAUTY_RIBBON), 0);
EXPECT_EQ(GetMonData(&mon, MON_DATA_TOUGH_RIBBON), 1);
EXPECT_EQ(GetMonData(&mon, MON_DATA_SMART_RIBBON), 0);
EXPECT_EQ(GetMonData(&mon, MON_DATA_CHAMPION_RIBBON), 1);
EXPECT_EQ(GetMonData(&mon, MON_DATA_VICTORY_RIBBON), 1);
EXPECT_EQ(GetMonData(&mon, MON_DATA_EFFORT_RIBBON), 1);
EXPECT_EQ(GetMonData(&mon, MON_DATA_LAND_RIBBON), 1);
EXPECT_EQ(GetMonData(&mon, MON_DATA_COUNTRY_RIBBON), 1);
EXPECT_EQ(GetMonData(&mon, MON_DATA_EARTH_RIBBON), 1);
EXPECT_EQ(GetMonData(&mon, MON_DATA_HYPER_TRAINED_HP), 1);
EXPECT_EQ(GetMonData(&mon, MON_DATA_HYPER_TRAINED_ATK), 1);
EXPECT_EQ(GetMonData(&mon, MON_DATA_HYPER_TRAINED_DEF), 1);
EXPECT_EQ(GetMonData(&mon, MON_DATA_HYPER_TRAINED_SPEED), 1);
EXPECT_EQ(GetMonData(&mon, MON_DATA_HYPER_TRAINED_SPATK), 1);
EXPECT_EQ(GetMonData(&mon, MON_DATA_HYPER_TRAINED_SPDEF), 1);
EXPECT_EQ(GetMonData(&mon, MON_DATA_DYNAMAX_LEVEL), 3);
EXPECT_EQ(GetMonData(&mon, MON_DATA_OT_GENDER), 0);
}

View File

@ -1341,6 +1341,10 @@ void TestRunner_Battle_AfterLastTurn(void)
static void TearDownBattle(void)
{
// Zero out the parties, data in them could potentially carry over
ZeroPlayerPartyMons();
ZeroEnemyPartyMons();
FreeMonSpritesGfx();
FreeBattleSpritesData();
FreeBattleResources();

View File

@ -620,6 +620,10 @@ bool AsmFile::ParseEnum()
}
enumCounter = 0;
}
// HACK(#7394): Make the definitions global so that C 'asm'
// statements are able to reference them (if they happen to
// be available in an assembled object file).
std::printf(".global %s; ", currentIdentName.c_str());
std::printf(".equiv %s, (%s) + %ld\n", currentIdentName.c_str(), enumBase.c_str(), enumCounter);
enumCounter++;
symbolCount++;