diff --git a/INSTALL.md b/INSTALL.md index d2e511dace..d265d588a1 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -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 diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index 65b0a1a066..bde00b4cda 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -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 diff --git a/docs/tutorials/how_to_new_pokemon.md b/docs/tutorials/how_to_new_pokemon.md index 100741de52..da6add17bc 100644 --- a/docs/tutorials/how_to_new_pokemon.md +++ b/docs/tutorials/how_to_new_pokemon.md @@ -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"); ``` diff --git a/docs/tutorials/how_to_testing_system.md b/docs/tutorials/how_to_testing_system.md index c5fbb945aa..b663ad54b4 100644 --- a/docs/tutorials/how_to_testing_system.md +++ b/docs/tutorials/how_to_testing_system.md @@ -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` diff --git a/include/battle.h b/include/battle.h index 78eed713f9..086eb68f51 100644 --- a/include/battle.h +++ b/include/battle.h @@ -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]; }; diff --git a/include/battle_ai_util.h b/include/battle_ai_util.h index 448fbf71c5..118fbf5984 100644 --- a/include/battle_ai_util.h +++ b/include/battle_ai_util.h @@ -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. diff --git a/include/battle_scripts.h b/include/battle_scripts.h index ef6206cf45..46326a42df 100644 --- a/include/battle_scripts.h +++ b/include/battle_scripts.h @@ -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[]; diff --git a/include/config/ai.h b/include/config/ai.h index 8d015ce9e4..b6eb7bfe21 100644 --- a/include/config/ai.h +++ b/include/config/ai.h @@ -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 diff --git a/include/config/battle.h b/include/config/battle.h index d09698c634..3cfd319304 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -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. diff --git a/include/constants/battle_ai.h b/include/constants/battle_ai.h index f3a62398b3..c6277bf9b5 100644 --- a/include/constants/battle_ai.h +++ b/include/constants/battle_ai.h @@ -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. diff --git a/include/constants/battle_move_effects.h b/include/constants/battle_move_effects.h index 53c9843d5b..2ddb75adf6 100644 --- a/include/constants/battle_move_effects.h +++ b/include/constants/battle_move_effects.h @@ -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, diff --git a/include/constants/battle_string_ids.h b/include/constants/battle_string_ids.h index 360a212257..b9313eba18 100644 --- a/include/constants/battle_string_ids.h +++ b/include/constants/battle_string_ids.h @@ -41,7 +41,6 @@ enum StringID STRINGID_PKMNMADESLEEP, STRINGID_PKMNALREADYASLEEP, STRINGID_PKMNALREADYASLEEP2, - STRINGID_PKMNWASNTAFFECTED, STRINGID_PKMNWASPOISONED, STRINGID_PKMNPOISONEDBY, STRINGID_PKMNHURTBYPOISON, diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index 1cf2d80072..e51d57915c 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -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 }; diff --git a/include/field_effect.h b/include/field_effect.h index 2b933e0c1f..0bfe15308b 100644 --- a/include/field_effect.h +++ b/include/field_effect.h @@ -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); diff --git a/include/follower_npc.h b/include/follower_npc.h index 458961bfad..5a2ab0e545 100644 --- a/include/follower_npc.h +++ b/include/follower_npc.h @@ -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, diff --git a/include/generational_changes.h b/include/generational_changes.h index a734ee49e8..53991bab1f 100644 --- a/include/generational_changes.h +++ b/include/generational_changes.h @@ -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 diff --git a/include/global.h b/include/global.h index a7fa69c372..74a993679f 100644 --- a/include/global.h +++ b/include/global.h @@ -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; diff --git a/include/party_menu.h b/include/party_menu.h index c450ab02c0..38ba5202b5 100644 --- a/include/party_menu.h +++ b/include/party_menu.h @@ -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; diff --git a/include/test/battle.h b/include/test/battle.h index 2ec2b1da0a..b95038b09e 100644 --- a/include/test/battle.h +++ b/include/test/battle.h @@ -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 diff --git a/include/test/overworld_script.h b/include/test/overworld_script.h index e849f87e0c..a5bee797b2 100644 --- a/include/test/overworld_script.h +++ b/include/test/overworld_script.h @@ -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" diff --git a/make_tools.mk b/make_tools.mk index 6a7de5b24e..8251768401 100644 --- a/make_tools.mk +++ b/make_tools.mk @@ -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 diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 5e75f1dfb3..5460e31dee 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -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) diff --git a/src/battle_ai_switch_items.c b/src/battle_ai_switch_items.c index 1c436e81cf..6f470d3c84 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -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; diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 9e4f4e35fd..e044f7b3e9 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -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) diff --git a/src/battle_gfx_sfx_util.c b/src/battle_gfx_sfx_util.c index 37c5b12d60..8689af1fe0 100644 --- a/src/battle_gfx_sfx_util.c +++ b/src/battle_gfx_sfx_util.c @@ -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) diff --git a/src/battle_main.c b/src/battle_main.c index cd9544ef28..ac60bab82a 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -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; diff --git a/src/battle_message.c b/src/battle_message.c index 83c1210cf4..19738969dd 100644 --- a/src/battle_message.c +++ b/src/battle_message.c @@ -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!"), diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 943e7db979..8bd6d3ab6a 100755 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -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()) diff --git a/src/battle_util.c b/src/battle_util.c index d324463af7..09bbc5f8c5 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -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; diff --git a/src/field_effect.c b/src/field_effect.c index 427bac13a3..45b4d61d17 100644 --- a/src/field_effect.c +++ b/src/field_effect.c @@ -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) diff --git a/src/field_player_avatar.c b/src/field_player_avatar.c index d02ecbd9fc..55542a3588 100644 --- a/src/field_player_avatar.c +++ b/src/field_player_avatar.c @@ -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; } } diff --git a/src/field_screen_effect.c b/src/field_screen_effect.c index fde3036730..74d2008ac9 100644 --- a/src/field_screen_effect.c +++ b/src/field_screen_effect.c @@ -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: diff --git a/src/follower_npc.c b/src/follower_npc.c index 5489f6cd57..11b066c12c 100644 --- a/src/follower_npc.c +++ b/src/follower_npc.c @@ -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 diff --git a/src/item_use.c b/src/item_use.c index af1395f011..5895016399 100644 --- a/src/item_use.c +++ b/src/item_use.c @@ -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; } } diff --git a/src/overworld.c b/src/overworld.c index 7fb339966b..f9c949a571 100644 --- a/src/overworld.c +++ b/src/overworld.c @@ -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)) diff --git a/src/party_menu.c b/src/party_menu.c index f820c63147..63517a110d 100644 --- a/src/party_menu.c +++ b/src/party_menu.c @@ -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; diff --git a/test/battle/ability/dauntless_shield.c b/test/battle/ability/dauntless_shield.c index ada4ace786..6fed2f0b5c 100644 --- a/test/battle/ability/dauntless_shield.c +++ b/test/battle/ability/dauntless_shield.c @@ -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!"); } } - diff --git a/test/battle/ability/intrepid_sword.c b/test/battle/ability/intrepid_sword.c index 58fd9883eb..a260e78d0d 100644 --- a/test/battle/ability/intrepid_sword.c +++ b/test/battle/ability/intrepid_sword.c @@ -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); diff --git a/test/battle/ability/keen_eye.c b/test/battle/ability/keen_eye.c index 6874e5a6bf..ee10c446fb 100644 --- a/test/battle/ability/keen_eye.c +++ b/test/battle/ability/keen_eye.c @@ -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); diff --git a/test/battle/ability/liquid_ooze.c b/test/battle/ability/liquid_ooze.c index fa8ca8e3b2..2c89702911 100644 --- a/test/battle/ability/liquid_ooze.c +++ b/test/battle/ability/liquid_ooze.c @@ -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 + } +} diff --git a/test/battle/ability/normalize.c b/test/battle/ability/normalize.c index ad171663ab..23ecbee592 100644 --- a/test/battle/ability/normalize.c +++ b/test/battle/ability/normalize.c @@ -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); } } diff --git a/test/battle/ability/pickup.c b/test/battle/ability/pickup.c index 9db7c0f2de..cc9ae37e9d 100644 --- a/test/battle/ability/pickup.c +++ b/test/battle/ability/pickup.c @@ -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 { diff --git a/test/battle/ability/protean.c b/test/battle/ability/protean.c index 88144670a1..c5d141d244 100644 --- a/test/battle/ability/protean.c +++ b/test/battle/ability/protean.c @@ -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); diff --git a/test/battle/ability/refrigerate.c b/test/battle/ability/refrigerate.c index 9577c6f4b7..c5e7f5687a 100644 --- a/test/battle/ability/refrigerate.c +++ b/test/battle/ability/refrigerate.c @@ -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); } diff --git a/test/battle/ability/snow_warning.c b/test/battle/ability/snow_warning.c index 17f18814b8..171ad23b49 100644 --- a/test/battle/ability/snow_warning.c +++ b/test/battle/ability/snow_warning.c @@ -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); } } diff --git a/test/battle/ai/ai_check_viability.c b/test/battle/ai/ai_check_viability.c index 1407023ece..f18f7aceb1 100644 --- a/test/battle/ai/ai_check_viability.c +++ b/test/battle/ai/ai_check_viability.c @@ -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); } + } +} diff --git a/test/battle/ai/ai_switching.c b/test/battle/ai/ai_switching.c index a858c33ded..79ffccc618 100644 --- a/test/battle/ai/ai_switching.c +++ b/test/battle/ai/ai_switching.c @@ -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); } diff --git a/test/battle/hold_effect/life_orb.c b/test/battle/hold_effect/life_orb.c index 92c545dfad..2f1514538c 100644 --- a/test/battle/hold_effect/life_orb.c +++ b/test/battle/hold_effect/life_orb.c @@ -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!"); + } +} diff --git a/test/battle/move_effect/ally_switch.c b/test/battle/move_effect/ally_switch.c index a8e9944e34..59844879d4 100644 --- a/test/battle/move_effect/ally_switch.c +++ b/test/battle/move_effect/ally_switch.c @@ -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 { diff --git a/test/battle/move_effect/assurance.c b/test/battle/move_effect/assurance.c index e6cbf72b9f..6eccb48f3f 100644 --- a/test/battle/move_effect/assurance.c +++ b/test/battle/move_effect/assurance.c @@ -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]); + } +} diff --git a/test/battle/move_effect/chloroblast.c b/test/battle/move_effect/chloroblast.c index 6452736478..d60449324f 100644 --- a/test/battle/move_effect/chloroblast.c +++ b/test/battle/move_effect/chloroblast.c @@ -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 { diff --git a/test/battle/move_effect/dream_eater.c b/test/battle/move_effect/dream_eater.c index caa365a17a..baefd2e4ff 100644 --- a/test/battle/move_effect/dream_eater.c +++ b/test/battle/move_effect/dream_eater.c @@ -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 diff --git a/test/battle/move_effect/leech_seed.c b/test/battle/move_effect/leech_seed.c index 96ecf205da..2d5fd34766 100644 --- a/test/battle/move_effect/leech_seed.c +++ b/test/battle/move_effect/leech_seed.c @@ -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") diff --git a/test/battle/move_effect/synthesis.c b/test/battle/move_effect/synthesis.c index d9651a509e..7afbb7f87d 100644 --- a/test/battle/move_effect/synthesis.c +++ b/test/battle/move_effect/synthesis.c @@ -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)") diff --git a/test/battle/move_effect_secondary/clear_smog.c b/test/battle/move_effect_secondary/clear_smog.c new file mode 100644 index 0000000000..f8d7b0eb2f --- /dev/null +++ b/test/battle/move_effect_secondary/clear_smog.c @@ -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); + } +} diff --git a/test/battle/move_effect_secondary/steal_item.c b/test/battle/move_effect_secondary/steal_item.c index 7dd922e8b2..79c293757f 100644 --- a/test/battle/move_effect_secondary/steal_item.c +++ b/test/battle/move_effect_secondary/steal_item.c @@ -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 { diff --git a/test/pokemon.c b/test/pokemon.c index c83b42e478..60058407c4 100644 --- a/test/pokemon.c +++ b/test/pokemon.c @@ -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); +} diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index 51f0ca02dd..c009219e26 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -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(); diff --git a/tools/preproc/asm_file.cpp b/tools/preproc/asm_file.cpp index 83e84a6fde..10330f1774 100644 --- a/tools/preproc/asm_file.cpp +++ b/tools/preproc/asm_file.cpp @@ -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++;