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 aa4cfac123..53f6697aef 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -5622,6 +5622,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 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/include/battle.h b/include/battle.h index 78eed713f9..47739ca264 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; 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/generational_changes.h b/include/constants/generational_changes.h index 1cf2d80072..6892f70ee2 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -20,6 +20,14 @@ 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_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..9c24608e63 100644 --- a/include/generational_changes.h +++ b/include/generational_changes.h @@ -23,6 +23,14 @@ 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, }; #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/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_switch_items.c b/src/battle_ai_switch_items.c index 1c436e81cf..379ce723fd 100644 --- a/src/battle_ai_switch_items.c +++ b/src/battle_ai_switch_items.c @@ -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; diff --git a/src/battle_ai_util.c b/src/battle_ai_util.c index 9e4f4e35fd..624dd7cf5a 100644 --- a/src/battle_ai_util.c +++ b/src/battle_ai_util.c @@ -2007,7 +2007,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: 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_script_commands.c b/src/battle_script_commands.c index 943e7db979..109f044f2c 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) @@ -6249,6 +6254,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 +9536,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 +10013,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 +10135,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 +10304,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 +14934,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 +14942,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 +15654,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 bc5eafc56f..97753232c6 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -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; @@ -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); @@ -7040,11 +7040,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) @@ -7979,6 +7977,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) @@ -7987,7 +7986,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) @@ -8060,7 +8059,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: @@ -8146,18 +8145,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: @@ -10798,7 +10794,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); } @@ -11700,7 +11696,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..1bd3cbbdf8 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") { @@ -140,6 +140,7 @@ SINGLE_BATTLE_TEST("Liquid Ooze causes leech seed victim to faint before seeder" SINGLE_BATTLE_TEST("Liquid Ooze causes Dream Eater users to lose HP instead of heal (Gen 5+)") { + KNOWN_FAILING; s16 damage; GIVEN { ASSUME(GetMoveEffect(MOVE_SPORE) == EFFECT_NON_VOLATILE_STATUS); @@ -158,8 +159,30 @@ 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 { + 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_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/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();