diff --git a/.all-contributorsrc b/.all-contributorsrc index 7d2e4d4bbf..aa13a3864d 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -357,6 +357,90 @@ "contributions": [ "design" ] + }, + { + "login": "Syreldar", + "name": "Enrico Drago", + "avatar_url": "https://avatars.githubusercontent.com/u/42327659?v=4", + "profile": "https://metin2.dev/index.php", + "contributions": [ + "doc", + "userTesting" + ] + }, + { + "login": "Pyredrid", + "name": "Pyredrid", + "avatar_url": "https://avatars.githubusercontent.com/u/8324784?v=4", + "profile": "https://github.com/Pyredrid", + "contributions": [ + "userTesting", + "maintenance" + ] + }, + { + "login": "mvit", + "name": "mv", + "avatar_url": "https://avatars.githubusercontent.com/u/128863?v=4", + "profile": "https://github.com/mvit", + "contributions": [ + "code", + "design" + ] + }, + { + "login": "Mother-Of-Dragons", + "name": "Avara", + "avatar_url": "https://avatars.githubusercontent.com/u/31101124?v=4", + "profile": "https://github.com/Mother-Of-Dragons", + "contributions": [ + "data" + ] + }, + { + "login": "Doesnty", + "name": "Doesnty", + "avatar_url": "https://avatars.githubusercontent.com/u/6163136?v=4", + "profile": "https://github.com/Doesnty", + "contributions": [ + "design" + ] + }, + { + "login": "FosterProgramming", + "name": "FosterProgramming", + "avatar_url": "https://avatars.githubusercontent.com/u/178871164?v=4", + "profile": "https://github.com/FosterProgramming", + "contributions": [ + "code" + ] + }, + { + "login": "Squeetz", + "name": "Squeetz", + "avatar_url": "https://avatars.githubusercontent.com/u/21145213?v=4", + "profile": "https://github.com/Squeetz", + "contributions": [ + "maintenance" + ] + }, + { + "login": "ghostyboyy97", + "name": "ghostyboyy97", + "avatar_url": "https://avatars.githubusercontent.com/u/106448956?v=4", + "profile": "https://github.com/ghostyboyy97", + "contributions": [ + "code" + ] + }, + { + "login": "HashtagMarky", + "name": "Marky", + "avatar_url": "https://avatars.githubusercontent.com/u/143505183?v=4", + "profile": "http://hashtagmarky.github.io", + "contributions": [ + "code" + ] } ], "contributorsPerLine": 7, diff --git a/CREDITS.md b/CREDITS.md index 4c0b6a3d3e..d3d46d0361 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -62,6 +62,19 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d tustin2121
tustin2121

📖 💻 Phantonomy
Phantonomy

🎨 + + Enrico Drago
Enrico Drago

📖 📓 + Pyredrid
Pyredrid

📓 🚧 + mv
mv

💻 🎨 + Avara
Avara

🔣 + Doesnty
Doesnty

🎨 + FosterProgramming
FosterProgramming

💻 + Squeetz
Squeetz

🚧 + + + ghostyboyy97
ghostyboyy97

💻 + Marky
Marky

💻 + diff --git a/asm/macros/battle_script.inc b/asm/macros/battle_script.inc index 39ecce81a6..134a83a605 100644 --- a/asm/macros/battle_script.inc +++ b/asm/macros/battle_script.inc @@ -2368,8 +2368,8 @@ .4byte \jumpInstr .endm - .macro jumpifleafguardprotected battler:req, jumpInstr:req - callnative BS_JumpIfLeafGuardProtected + .macro jumpifabilitypreventsrest battler:req, jumpInstr:req + callnative BS_JumpIfAbilityPreventsRest .byte \battler .4byte \jumpInstr .endm diff --git a/asm/macros/event.inc b/asm/macros/event.inc index d56450b024..b15bf8deb3 100644 --- a/asm/macros/event.inc +++ b/asm/macros/event.inc @@ -2482,7 +2482,7 @@ @ Hides any follower Pokémon if present, putting them into their Poké Ball; by default waits for their movement to finish. .macro hidefollower wait=1 - callnative ScrFunc_hidefollower + .byte SCR_OP_HIDEFOLLOWER .2byte \wait .endm diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index ab00c5138f..25624a8ecd 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -384,6 +384,7 @@ BattleScript_SaltCureExtraDamage:: printstring STRINGID_TARGETISHURTBYSALTCURE waitmessage B_WAIT_TIME_LONG tryfaintmon BS_ATTACKER + tryactivateitem BS_ATTACKER, ON_ITEM_HP_THRESHOLD end2 BattleScript_EffectCorrosiveGas:: @@ -2813,9 +2814,13 @@ BattleScript_EffectLightScreen:: BattleScript_EffectRest:: attackcanceler -.if B_LEAF_GUARD_PREVENTS_REST >= GEN_5 - jumpifleafguardprotected BS_TARGET, BattleScript_LeafGuardPreventsRest -.endif + jumpifstatus BS_ATTACKER, STATUS1_SLEEP, BattleScript_RestIsAlreadyAsleep + jumpifability BS_ATTACKER, ABILITY_COMATOSE, BattleScript_RestIsAlreadyAsleep + jumpifuproarwakes BattleScript_RestCantSleep + jumpifability BS_TARGET, ABILITY_INSOMNIA, BattleScript_InsomniaProtects + jumpifability BS_TARGET, ABILITY_VITAL_SPIRIT, BattleScript_InsomniaProtects + jumpifability BS_ATTACKER, ABILITY_PURIFYING_SALT, BattleScript_InsomniaProtects + jumpifabilitypreventsrest BS_TARGET, BattleScript_AbilityPreventsRest trysetrest pause B_WAIT_TIME_SHORT printfromtable gRestUsedStringIds @@ -2837,7 +2842,7 @@ BattleScript_RestIsAlreadyAsleep:: waitmessage B_WAIT_TIME_LONG goto BattleScript_MoveEnd -BattleScript_LeafGuardPreventsRest:: +BattleScript_AbilityPreventsRest:: pause B_WAIT_TIME_SHORT printstring STRINGID_BUTITFAILED waitmessage B_WAIT_TIME_LONG @@ -5378,9 +5383,8 @@ BattleScript_ToxicDebrisActivates:: printstring STRINGID_POISONSPIKESSCATTERED waitmessage B_WAIT_TIME_LONG BattleScript_ToxicDebrisRet: - copybyte sBATTLER, gBattlerTarget - copybyte gBattlerTarget, gBattlerAttacker - copybyte gBattlerAttacker, sBATTLER + restoretarget + restoreattacker return BattleScript_EarthEaterActivates:: @@ -8068,14 +8072,28 @@ BattleScript_ActivateTeraformZero_RemoveWeather: removeweather printfromtable gWeatherEndsStringIds waitmessage B_WAIT_TIME_LONG - jumpifhalfword CMP_NO_COMMON_BITS, gFieldStatuses, STATUS_FIELD_TERRAIN_ANY, BattleScript_ActivateTeraformZero_End + call BattleScript_ActivateWeatherAbilities + jumpifhalfword CMP_NO_COMMON_BITS, gFieldStatuses, STATUS_FIELD_TERRAIN_ANY, BattleScript_ActivateTeraformZeroEffects BattleScript_ActivateTeraformZero_RemoveTerrain: removeterrain playanimation BS_ATTACKER, B_ANIM_RESTORE_BG printfromtable gTerrainStringIds waitmessage B_WAIT_TIME_LONG -BattleScript_ActivateTeraformZero_End: +BattleScript_ActivateTeraformZeroEffects: + saveattacker + savetarget tryboosterenergy ON_ANY + resetterrainabilityflags + setbyte gBattlerAttacker, 0 +BattleScript_ActivateTeraformZeroLoop: + copyarraywithindex gBattlerTarget, gBattlerByTurnOrder, gBattlerAttacker, 1 + activateterrainchangeabilities BS_TARGET + activateweatherchangeabilities BS_TARGET + addbyte gBattlerAttacker, 1 + jumpifbytenotequal gBattlerAttacker, gBattlersCount, BattleScript_ActivateTeraformZeroLoop + restoreattacker + restoretarget +BattleScript_ActivateTeraformZero_End: end3 BattleScript_QuickClawActivation:: diff --git a/data/maps/BattleFrontier_BattleDomePreBattleRoom/scripts.inc b/data/maps/BattleFrontier_BattleDomePreBattleRoom/scripts.inc index e06284c2ca..3406d4121c 100644 --- a/data/maps/BattleFrontier_BattleDomePreBattleRoom/scripts.inc +++ b/data/maps/BattleFrontier_BattleDomePreBattleRoom/scripts.inc @@ -18,6 +18,7 @@ BattleFrontier_BattleDomePreBattleRoom_OnFrame: BattleFrontier_BattleDomePreBattleRoom_EventScript_EnterRoom:: goto_if_eq VAR_0x8006, 1, BattleFrontier_BattleDomePreBattleRoom_EventScript_ReturnFromBattle + delay 1 frontier_set FRONTIER_DATA_RECORD_DISABLED, TRUE setvar VAR_TEMP_0, 1 applymovement LOCALID_PLAYER, BattleFrontier_BattleDomePreBattleRoom_Movement_PlayerEnter diff --git a/data/script_cmd_table.inc b/data/script_cmd_table.inc index e05260d3d4..a3defef83e 100644 --- a/data/script_cmd_table.inc +++ b/data/script_cmd_table.inc @@ -251,6 +251,7 @@ gScriptCmdTable:: script_cmd_table_entry SCR_OP_BUFFERITEMNAMEPLURAL ScrCmd_bufferitemnameplural, requests_effects=1 @ 0xe2 script_cmd_table_entry SCR_OP_DYNMULTICHOICE ScrCmd_dynmultichoice, requests_effects=1 @ 0xe3 script_cmd_table_entry SCR_OP_DYNMULTIPUSH ScrCmd_dynmultipush, requests_effects=1 @ 0xe4 + script_cmd_table_entry SCR_OP_HIDEFOLLOWER ScrCmd_hidefollower, requests_effects=1 @ 0xe5 .if ALLOCATE_SCRIPT_CMD_TABLE gScriptCmdTableEnd:: diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index c1e6a78752..7f20718f30 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -3,21 +3,40 @@ - [README](./README.md) - [Installation](./INSTALL.md) - [Setting up WSL1 (Legacy Portion)](./legacy_WSL1_INSTALL.md) + - [ChromeOS](./install/chromeos/CHROME_OS.md) + - [Linux]() + - [ARCH_LINUX](./install/linux/ARCH_LINUX.md) + - [DEBIAN](./install/linux/DEBIAN.md) + - [NIXOS](./install/linux/NIXOS.md) + - [OTHERS](./install/linux/OTHERS.md) + - [UBUNTU](./install/linux/UBUNTU.md) + - [macOS](./install/mac/MAC_OS.md) + - [Windows]() + - [CYGWIN](./install/windows/CYGWIN.md) + - [MSYS2](./install/windows/MSYS2.md) + - [WSL](./install/windows/WSL.md) - [Run documentation site locally](local_mdbook/index.md) - [Ubuntu WSL1/WSL2](local_mdbook/ubuntu_WSL.md) - [Contributing](./CONTRIBUTING.md) +- [Styleguide and Principles](./STYLEGUIDE.md) - [Credits](./CREDITS.md) - [Tutorials]() - [What are AI Flags?](tutorials/ai_flags.md) - [How to add new AI Flags](tutorials/ai_logic.md) - [How to add new battle script commands/macros](tutorials/how_to_battle_script_command_macro.md) - [How to add a new move](tutorials/how_to_new_move.md) - - [How to add a new trainer class](tutorials/how_to_trainer_class.md) + - [How to add a new trainer class]() + - [How to add a new trainer front pic](tutorials/how_to_trainer_front_pic.md) + - [How to add a new trainer back pic](tutorials/how_to_trainer_back_pic.md) - [How to add a new Pokémon](tutorials/how_to_new_pokemon.md) - [v1.6.x and earlier](tutorials/how_to_new_pokemon_1_6_0.md) - [How to use the Testing System](tutorials/how_to_testing_system.md) - [How to add new Trainer Slides](tutorials/how_to_new_trainer_slide.md) - [Day/Night System FAQ](tutorials/dns.md) + - [How to use the code entry system](tutorials/how_to_code_entry.md) + - [How to use Follower NPCs](tutorials/how_to_follower_npc.md) + - [Time-Based Encounters](tutorials/how_to_time_of_day_encounters.md) + - [How to use Trainer Party Pools](tutorials/how_to_trainer_party_pool.md) - [Changelog](./CHANGELOG.md) - [1.13.x]() - [Version 1.13.2](changelogs/1.13.x/1.13.2.md) @@ -39,6 +58,7 @@ - [Version 1.10.2](changelogs/1.10.x/1.10.2.md) - [Version 1.10.1](changelogs/1.10.x/1.10.1.md) - [Version 1.10.0](changelogs/1.10.x/1.10.0.md) + - [Megaman Battle Network Style Names](./mmbn_style_names.md) - [1.9.x]() - [Version 1.9.4](changelogs/1.9.x/1.9.4.md) - [Version 1.9.3](changelogs/1.9.x/1.9.3.md) @@ -87,4 +107,5 @@ - [Team Procedures]() - [How to make an Expansion version](team_procedures/expansion_versions.md) - [Release Schedule and Process](team_procedures/schedule.md) + - [Merge Checklist](team_procedures/merge_checklist.md) - [Scope Guidelines](team_procedures/scope.md) diff --git a/docs/fix_links.py b/docs/fix_links.py index 7b6b01b995..6e2eaec485 100644 --- a/docs/fix_links.py +++ b/docs/fix_links.py @@ -36,7 +36,6 @@ def proc_items(items): s = s.replace('](README.md)', '](./)') s = s.replace('](/INSTALL.md', '](INSTALL.md') s = s.replace('](docs/', '](') - s = s.replace('](/docs/', '](/') s = URL_RE.sub(handle_url, s) item['Chapter']['content'] = ANCHOR_RE.sub(handle_anchor, s) proc_items(item['Chapter']['sub_items']) diff --git a/docs/local_mdbook/index.md b/docs/local_mdbook/index.md index ef362dc45f..fa4b7f1e70 100644 --- a/docs/local_mdbook/index.md +++ b/docs/local_mdbook/index.md @@ -1,2 +1,2 @@ ## Running documentation website locally -- [Ubuntu WSL1/WSL2](/docs/local_mdbook/ubuntu_WSL.md) +- [Ubuntu WSL1/WSL2](ubuntu_WSL.md) diff --git a/docs/team_procedures/merge_checklist.md b/docs/team_procedures/merge_checklist.md index ae3ff60b19..53e8d7683f 100644 --- a/docs/team_procedures/merge_checklist.md +++ b/docs/team_procedures/merge_checklist.md @@ -7,7 +7,7 @@ This document is a guide for maintainers to account for all the reccomended step # Checklist ## Is the branch's theoretical functionality in scope? -If you're not sure if a branch's functionality is [in scope](docs/team_procedures/scope.md), start a conversation on Discord to resolve. +If you're not sure if a branch's functionality is [in scope](scope.md), start a conversation on Discord to resolve. ## Does the branch successfully compile? From `make clean`, the branch should locally compile. @@ -37,18 +37,18 @@ If you're not sure if something CAN be tested, start a discussion. Some contribu If any new tests are `KNOWN_FAILING`, issues should be opened describing each of the `KNOWN_FAILING` tests and our understanding of why they fail. -## Does the branch meet our [config philosophy](/docs/STYLEGUIDE.md#config-philosophy)? +## Does the branch meet our [config philosophy](../STYLEGUIDE.md#config-philosophy)? -## Does the branch meet our [saves philosophy](/docs/STYLEGUIDE.md#saves-philosophy)? +## Does the branch meet our [saves philosophy](../STYLEGUIDE.md#saves-philosophy)? -## Does the submitted code follow the [styleguide](/docs/STYLEGUIDE.md)? +## Does the submitted code follow the [styleguide](../STYLEGUIDE.md)? This applies to code that comes from other branches or games. ## Is the pull request appropriately labeled? Without labels, the CHANGELOG will not be properly formatted. For specifically the `bugfix` label, an additional label, detailing what area the bug exists in is required. ## Is `pokeemerald-expansion` free from a merge freeze? -Our [release schedule](/docs/team_procedures/schedule.md) prevents us from merging Big Features and non-bugfixes within certain dates close to a release. Please use `/release` in the RHH Discord to clarify when these are occuring. +Our [release schedule](schedule.md) prevents us from merging Big Features and non-bugfixes within certain dates close to a release. Please use `/release` in the RHH Discord to clarify when these are occuring. # Merging diff --git a/docs/team_procedures/schedule.md b/docs/team_procedures/schedule.md index c689e5b454..520c4af000 100644 --- a/docs/team_procedures/schedule.md +++ b/docs/team_procedures/schedule.md @@ -53,4 +53,4 @@ This designation should be reserved for instances where an existing feature on ` Blocking issues or PRs can be deferred to future releases but should be discussed with the Maintainers that assigned the designation in the first place. -If a version's milestone does not have any issues or PRs assigned to it, that version should be [released](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/docs/team_procedures/expansion_versions.md) as close to the goal date as possible. +If a version's milestone does not have any issues or PRs assigned to it, that version should be [released](expansion_versions.md) as close to the goal date as possible. diff --git a/docs/tutorials/dns.md b/docs/tutorials/dns.md index 908329761e..6fdd1c1d77 100644 --- a/docs/tutorials/dns.md +++ b/docs/tutorials/dns.md @@ -23,8 +23,8 @@ When writing map scripts, `fadescreenswapbuffers` should be preferred over `fade ### Q: How do I make lightbulbs glow? -![Rustboro before adding lamp object events](/docs/tutorials/img/dns/without_lamp.png) -![Rustboro after adding lamp object events](/docs/tutorials/img/dns/with_lamp.png) +![Rustboro before adding lamp object events](img/dns/without_lamp.png) +![Rustboro after adding lamp object events](img/dns/with_lamp.png) A: Making lamps glow is not part of the tileset itself. Instead, place certain object events on top of where you desire a glowing effect. @@ -48,7 +48,7 @@ on separate lines to mark those colors as being light-blended, i.e: During the day time, these color indices appear as normal, but will be blended with either yellow or the 0 index at night. These indices should only be used for things you expect to light up. If you are using [porytiles](https://github.com/grunt-lucas/porytiles/wiki), palette overrides and using slight alterations to a color will aid you in avoiding color conflicts where the wrong index is assigned. -![Rustboro gym after light-blending the windows](/docs/tutorials/img/dns/window_lights.png) +![Rustboro gym after light-blending the windows](img/dns/window_lights.png) The windows appear as normal during the day time (blue) and light up in the night. These use the default color. diff --git a/docs/tutorials/how_to_follower_npc.md b/docs/tutorials/how_to_follower_npc.md index 2e449adbc0..fbd0087859 100644 --- a/docs/tutorials/how_to_follower_npc.md +++ b/docs/tutorials/how_to_follower_npc.md @@ -2,7 +2,7 @@ *Written by Bivurnum* *gif by ghoulslash* -![follower-npc](/docs/tutorials/img/follower_npc/follower-npc.gif) +![follower-npc](img/follower_npc/follower-npc.gif) ## Configs The configs for follower NPCs can be found in [include/config/follower_npc.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/upcoming/include/config/follower_npc.h). diff --git a/docs/tutorials/how_to_new_pokemon.md b/docs/tutorials/how_to_new_pokemon.md index da6add17bc..79f01acbd2 100644 --- a/docs/tutorials/how_to_new_pokemon.md +++ b/docs/tutorials/how_to_new_pokemon.md @@ -42,7 +42,7 @@ The main things that the Expansion changes are listed here. # Useful resources You can open a sprite debug menu by pressing `Select` in a Pokémon's summary screen outside of battle. -![visualizer1](/docs/tutorials/img/add_pokemon/visualizer1.gif) +![visualizer1](img/add_pokemon/visualizer1.gif) # The Data - Part 1 @@ -73,7 +73,7 @@ We add this at the end so that no existing species change Id and so that we don' Now, let's see how it looks in-game! -![visualizer2](/docs/tutorials/img/add_pokemon/visualizer2.png) +![visualizer2](img/add_pokemon/visualizer2.png) Hmmm, something's not right... @@ -446,7 +446,7 @@ Now we can add the number and entry to our Mewthree: }, }; ``` -![image](/docs/tutorials/img/add_pokemon/dex1.png) +![image](img/add_pokemon/dex1.png) The values `pokemonScale`, `pokemonOffset`, `trainerScale` and `trainerOffset` are used for the height comparison figure in the Pokédex. @@ -497,7 +497,7 @@ Edit [src/data/pokemon/pokedex_orders.h](https://github.com/rh-hideout/pokeemera ... }; ``` -![mGBA_lUBfmFEKUx](/docs/tutorials/img/add_pokemon/dex2.gif) +![mGBA_lUBfmFEKUx](img/add_pokemon/dex2.gif) # The Graphics @@ -1058,7 +1058,7 @@ What this allows us to do is to be able to get all forms of a Pokémon in our co For example, in the HGSS dex, it lets us browse between the entries of every form available.: -![hgssdex1](/docs/tutorials/img/add_pokemon/hgssdex1.png) ![image](/docs/tutorials/img/add_pokemon/hgssdex2.png) +![hgssdex1](img/add_pokemon/hgssdex1.png) ![image](img/add_pokemon/hgssdex2.png) In addition, we have the `GET_BASE_SPECIES_ID` macro, which returns the first entry of the table (or return the species itself if it doesn't have a table registered). With this, you can check if a Pokémon is any form of a species. For example, making it so that the Light Ball affects all Pikachu forms: ```c @@ -1089,7 +1089,7 @@ The second value is the target form, to which the Pokémon will change into. Values after that are referred as arguments, and needs to be put there depends on the type of form change, detailed in `include/constants/form_change_types.h`. ## 3. Gender differences -![gender_diffs](/docs/tutorials/img/add_pokemon/gender_diffs.gif) +![gender_diffs](img/add_pokemon/gender_diffs.gif) You may have seen that there's a couple of duplicate fields with a "Female" suffix. ```diff @@ -1262,8 +1262,8 @@ Either way, you may also create custom animation tables and use them here approp ### How to add the Pokémon Object Events to map In Porymap, select the object you want to set the sprite to. Then, change the field "Sprite" to use `OBJ_EVENT_GFX_SPECIES(SPECIES)`, replacing SPECIES with the name of the species you want to use. If you get a compiler error, it's because it used the species define as part of the macro, so it needs to match how you defined it all the way back in [Declare a species constant](#1-Declare-a-species-constant). -![charizard](/docs/tutorials/img/add_pokemon/charizard.png) -![overworld_data](/docs/tutorials/img/add_pokemon/overworld_data.gif) +![charizard](img/add_pokemon/charizard.png) +![overworld_data](img/add_pokemon/overworld_data.gif) If you want to use their shiny and/or female versions, use one of the following macros: - `OBJ_EVENT_GFX_SPECIES_SHINY(name)` diff --git a/graphics/fonts/latin_small_narrower.png b/graphics/fonts/latin_small_narrower.png index 69bca4645a..ebb7c70d27 100644 Binary files a/graphics/fonts/latin_small_narrower.png and b/graphics/fonts/latin_small_narrower.png differ diff --git a/graphics_file_rules.mk b/graphics_file_rules.mk index 2b9cb9df8b..0b8a91415d 100644 --- a/graphics_file_rules.mk +++ b/graphics_file_rules.mk @@ -405,7 +405,7 @@ $(RAYQUAZAGFXDIR)/scene_2/bg.4bpp: %.4bpp: %.png $(GFX) $< $@ -num_tiles 313 -Wnum_tiles $(RAYQUAZAGFXDIR)/scene_3/rayquaza.4bpp: %.4bpp: %.png - $(GFX) $< $@ -num_tiles 124 -Wnum_tiles + $(GFX) $< $@ -num_tiles 128 -Wnum_tiles $(RAYQUAZAGFXDIR)/scene_4/streaks.4bpp: %.4bpp: %.png $(GFX) $< $@ -num_tiles 19 -Wnum_tiles diff --git a/include/battle.h b/include/battle.h index 4af433f60f..0c4f119f03 100644 --- a/include/battle.h +++ b/include/battle.h @@ -720,7 +720,7 @@ struct BattleStruct struct Illusion illusion[MAX_BATTLERS_COUNT]; u8 soulheartBattlerId; u8 friskedBattler; // Frisk needs to identify 2 battlers in double battles. - u8 sameMoveTurns[MAX_BATTLERS_COUNT]; // For Metronome, number of times the same moves has been SUCCESFULLY used. + u8 metronomeItemCounter[MAX_BATTLERS_COUNT]; // For Metronome, number of times the same moves has been SUCCESFULLY used. u8 quickClawBattlerId; struct LostItem itemLost[NUM_BATTLE_SIDES][PARTY_SIZE]; // Pokemon that had items consumed or stolen (two bytes per party member per side) u8 blunderPolicy:1; // should blunder policy activate @@ -781,6 +781,9 @@ struct BattleStruct enum SubmoveState submoveAnnouncement:2; u8 padding2:2; u16 flingItem; + u8 incrementEchoedVoice:1; + u8 echoedVoiceCounter:3; + u8 padding3:4; }; struct AiBattleData diff --git a/include/battle_util.h b/include/battle_util.h index 38f26be489..39ccea4797 100644 --- a/include/battle_util.h +++ b/include/battle_util.h @@ -158,7 +158,8 @@ struct DamageContext u32 isCrit:1; u32 randomFactor:1; u32 updateFlags:1; - u32 padding1:2; + u32 isAnticipation:1; + u32 padding1:1; u32 weather:16; u32 fixedBasePower:8; u32 padding2:8; @@ -251,7 +252,8 @@ bool32 HandleFaintedMonActions(void); void TryClearRageAndFuryCutter(void); enum MoveCanceller AtkCanceller_MoveSuccessOrder(struct BattleContext *ctx); bool32 HasNoMonsToSwitch(u32 battler, u8 partyIdBattlerOn1, u8 partyIdBattlerOn2); -bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, bool32 viaAbility); +bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, u32 ability); +bool32 TryChangeBattleTerrain(u32 battler, u32 statusFlag); bool32 CanAbilityBlockMove(u32 battlerAtk, u32 battlerDef, enum Ability abilityAtk, enum Ability abilityDef, u32 move, enum FunctionCallOption option); bool32 CanAbilityAbsorbMove(u32 battlerAtk, u32 battlerDef, enum Ability abilityDef, u32 move, u32 moveType, enum FunctionCallOption option); u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ability, u32 special, u32 moveArg); @@ -320,6 +322,7 @@ u32 GetIllusionMonPartyId(struct Pokemon *party, struct Pokemon *mon, struct Pok bool32 SetIllusionMon(struct Pokemon *mon, u32 battler); u32 TryImmunityAbilityHealStatus(u32 battler, enum AbilityEffect caseID); bool32 ShouldGetStatBadgeBoost(u16 flagId, u32 battler); +uq4_12_t GetBadgeBoostModifier(void); enum DamageCategory GetBattleMoveCategory(u32 move); void SetDynamicMoveCategory(u32 battlerAtk, u32 battlerDef, u32 move); bool32 CanFling(u32 battler); diff --git a/include/config/battle.h b/include/config/battle.h index c16f930950..906dbbd162 100644 --- a/include/config/battle.h +++ b/include/config/battle.h @@ -19,7 +19,7 @@ #define B_LEVEL_UP_NOTIFICATION GEN_LATEST // In Gen9+, if the Pokémon gets enough experience to level up multiple times, the message is only displayed once. // Stat settings -#define B_BADGE_BOOST GEN_LATEST // In Gen4+, Gym Badges no longer boost a Pokémon's stats. +#define B_BADGE_BOOST GEN_LATEST // In Gen4+, Gym Badges no longer boost a Pokémon's stats. (Gen2 does not include the additional boost to the type matching the gym the badge is from) #define B_FRIENDSHIP_BOOST FALSE // In LGPE only, all stats except HP are boosted up to 10% based on Friendship. Unlike B_BADGE_BOOST, these boosts are accounted when calculating base stats. #define B_MAX_LEVEL_EV_GAINS GEN_LATEST // In Gen5+, Lv100 Pokémon can obtain Effort Values normally. #define B_RECALCULATE_STATS GEN_LATEST // In Gen5+, the stats of the Pokémon who participate in battle are recalculated at the end of each battle. diff --git a/include/constants/generational_changes.h b/include/constants/generational_changes.h index 7a9aed85c3..7be7998d9e 100644 --- a/include/constants/generational_changes.h +++ b/include/constants/generational_changes.h @@ -45,6 +45,7 @@ enum GenConfigTag GEN_CONFIG_OBLIVIOUS_TAUNT, GEN_CONFIG_TOXIC_NEVER_MISS, GEN_CONFIG_PARALYZE_ELECTRIC, + GEN_CONFIG_BADGE_BOOST, GEN_CONFIG_COUNT }; diff --git a/include/generational_changes.h b/include/generational_changes.h index 30739a14f5..9b6b385c7e 100644 --- a/include/generational_changes.h +++ b/include/generational_changes.h @@ -48,6 +48,7 @@ static const u8 sGenerationalChanges[GEN_CONFIG_COUNT] = [GEN_CONFIG_OBLIVIOUS_TAUNT] = B_OBLIVIOUS_TAUNT, [GEN_CONFIG_TOXIC_NEVER_MISS] = B_TOXIC_NEVER_MISS, [GEN_CONFIG_PARALYZE_ELECTRIC] = B_PARALYZE_ELECTRIC, + [GEN_CONFIG_BADGE_BOOST] = B_BADGE_BOOST }; #if TESTING diff --git a/include/pokemon.h b/include/pokemon.h index 6264dbd2cb..f626d3c05e 100644 --- a/include/pokemon.h +++ b/include/pokemon.h @@ -793,6 +793,7 @@ u32 GetSpeciesBaseDefense(u16 species); u32 GetSpeciesBaseSpAttack(u16 species); u32 GetSpeciesBaseSpDefense(u16 species); u32 GetSpeciesBaseSpeed(u16 species); +u32 GetSpeciesBaseStat(u16 species, u32 statIndex); const struct LevelUpMove *GetSpeciesLevelUpLearnset(u16 species); const u16 *GetSpeciesTeachableLearnset(u16 species); const u16 *GetSpeciesEggMoves(u16 species); diff --git a/include/random.h b/include/random.h index 3da116a3f2..08650c0c78 100644 --- a/include/random.h +++ b/include/random.h @@ -217,6 +217,7 @@ enum RandomTag RNG_WRAP, RNG_BALLTHROW_CRITICAL, RNG_BALLTHROW_SHAKE, + RNG_PROTECT_FAIL, RNG_PRESENT, RNG_MAGNITUDE, RNG_FISHING_BITE, diff --git a/include/strings.h b/include/strings.h index 0b4227125a..7220214357 100644 --- a/include/strings.h +++ b/include/strings.h @@ -2428,5 +2428,7 @@ extern const u8 gText_Rename[]; // change nickname from summary screen // Switch Caught Mon into Party extern const u8 gText_CannotSendMonToBoxHM[]; +extern const u8 gText_CannotSendMonToBoxActive[]; +extern const u8 gText_CannotSendMonToBoxPartner[]; #endif // GUARD_STRINGS_H diff --git a/src/battle_ai_main.c b/src/battle_ai_main.c index 0c1baa016b..80f2f46abc 100644 --- a/src/battle_ai_main.c +++ b/src/battle_ai_main.c @@ -5267,6 +5267,7 @@ case EFFECT_GUARD_SPLIT: ADJUST_AND_RETURN_SCORE(GOOD_EFFECT); ADJUST_SCORE(WORST_EFFECT); + break; } case EFFECT_ELECTRIC_TERRAIN: if (ShouldSetFieldStatus(battlerAtk, STATUS_FIELD_ELECTRIC_TERRAIN)) diff --git a/src/battle_controller_player.c b/src/battle_controller_player.c index 6b6c5a65ba..7ade058d19 100644 --- a/src/battle_controller_player.c +++ b/src/battle_controller_player.c @@ -1725,6 +1725,14 @@ static void MoveSelectionDisplayMoveDescription(u32 battler) u16 move = moveInfo->moves[gMoveSelectionCursor[battler]]; u16 pwr = GetMovePower(move); u16 acc = GetMoveAccuracy(move); + enum DamageCategory cat = GetBattleMoveCategory(move); + + if (GetActiveGimmick(battler) == GIMMICK_DYNAMAX || IsGimmickSelected(battler, GIMMICK_DYNAMAX)) + { + pwr = GetMaxMovePower(move); + move = GetMaxMove(battler, move); + acc = 0; + } u8 pwr_num[3], acc_num[3]; u8 cat_desc[7] = _("CAT: "); @@ -1758,7 +1766,7 @@ static void MoveSelectionDisplayMoveDescription(u32 battler) if (gCategoryIconSpriteId == 0xFF) gCategoryIconSpriteId = CreateSprite(&gSpriteTemplate_CategoryIcons, 38, 64, 1); - StartSpriteAnim(&gSprites[gCategoryIconSpriteId], GetBattleMoveCategory(move)); + StartSpriteAnim(&gSprites[gCategoryIconSpriteId], cat); CopyWindowToVram(B_WIN_MOVE_DESCRIPTION, COPYWIN_FULL); } diff --git a/src/battle_end_turn.c b/src/battle_end_turn.c index 842a69078c..50072c2313 100644 --- a/src/battle_end_turn.c +++ b/src/battle_end_turn.c @@ -173,6 +173,17 @@ static bool32 HandleEndTurnVarious(u32 battler) gBattleStruct->hpBefore[i] = gBattleMons[i].hp; } + if (gBattleStruct->incrementEchoedVoice) + { + if (gBattleStruct->echoedVoiceCounter < 4) + gBattleStruct->echoedVoiceCounter++; + gBattleStruct->incrementEchoedVoice = FALSE; + } + else + { + gBattleStruct->echoedVoiceCounter = 0; + } + return effect; } @@ -764,9 +775,9 @@ static bool32 HandleEndTurnSaltCure(u32 battler) { s32 saltCureDamage = 0; if (IS_BATTLER_ANY_TYPE(battler, TYPE_STEEL, TYPE_WATER)) - saltCureDamage = gBattleMons[battler].maxHP / 4; + saltCureDamage = GetNonDynamaxMaxHP(battler) / 4; else - saltCureDamage = gBattleMons[battler].maxHP / 8; + saltCureDamage = GetNonDynamaxMaxHP(battler) / 8; SetPassiveDamageAmount(battler, saltCureDamage); PREPARE_MOVE_BUFFER(gBattleTextBuff1, MOVE_SALT_CURE); BattleScriptExecute(BattleScript_SaltCureExtraDamage); diff --git a/src/battle_interface.c b/src/battle_interface.c index bcdd56839b..f87a889641 100644 --- a/src/battle_interface.c +++ b/src/battle_interface.c @@ -2258,10 +2258,11 @@ static u8 CalcBarFilledPixels(s32 maxValue, s32 oldValue, s32 receivedValue, s32 for (i = 0; i < scale; i++) pixelsArray[i] = 0; + // Safe Div, because 2vs1 battles can have maxValue 0. if (maxValue < totalPixels) - pixels = (*currValue * totalPixels / maxValue) >> 8; + pixels = SAFE_DIV(*currValue * totalPixels, maxValue) >> 8; else - pixels = *currValue * totalPixels / maxValue; + pixels = SAFE_DIV(*currValue * totalPixels, maxValue); filledPixels = pixels; diff --git a/src/battle_main.c b/src/battle_main.c index edaa0f48c7..7f5179d969 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -3227,7 +3227,7 @@ void SwitchInClearSetData(u32 battler, struct Volatiles *volatilesCopy) gLastHitBy[battler] = 0xFF; gBattleStruct->lastTakenMove[battler] = 0; - gBattleStruct->sameMoveTurns[battler] = 0; + gBattleStruct->metronomeItemCounter[battler] = 0; gBattleStruct->lastTakenMoveFrom[battler][0] = 0; gBattleStruct->lastTakenMoveFrom[battler][1] = 0; gBattleStruct->lastTakenMoveFrom[battler][2] = 0; @@ -3347,7 +3347,7 @@ const u8* FaintClearSetData(u32 battler) gLastHitBy[battler] = 0xFF; gBattleStruct->choicedMove[battler] = MOVE_NONE; - gBattleStruct->sameMoveTurns[battler] = 0; + gBattleStruct->metronomeItemCounter[battler] = 0; gBattleStruct->lastTakenMove[battler] = MOVE_NONE; gBattleStruct->lastTakenMoveFrom[battler][0] = 0; gBattleStruct->lastTakenMoveFrom[battler][1] = 0; @@ -4798,7 +4798,7 @@ u32 GetBattlerTotalSpeedStat(u32 battler, enum Ability ability, enum HoldEffect && ShouldGetStatBadgeBoost(B_FLAG_BADGE_BOOST_SPEED, battler) && IsOnPlayerSide(battler)) { - speed = (speed * 110) / 100; + speed = uq4_12_multiply_by_int_half_down(GetBadgeBoostModifier(), speed); } // item effects diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 2be580e1ee..62604d43f5 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -3789,7 +3789,7 @@ void SetMoveEffect(u32 battler, u32 effectBattler, bool32 primary, bool32 certai } break; } - if (TryChangeBattleWeather(gBattlerAttacker, weather, FALSE)) + if (TryChangeBattleWeather(gBattlerAttacker, weather, ABILITY_NONE)) { gBattleCommunication[MULTISTRING_CHOOSER] = msg; BattleScriptPush(gBattlescriptCurrInstr + 1); @@ -4747,7 +4747,15 @@ static void Cmd_getexp(void) if (battler != 0xFF) { - CopyMonLevelAndBaseStatsToBattleMon(battler, &gPlayerParty[*expMonId]); + if (gBattleMons[battler].volatiles.transformed) + { + gBattleMons[battler].level = GetMonData(&gPlayerParty[*expMonId], MON_DATA_LEVEL); + gBattleMons[battler].hp = GetMonData(&gPlayerParty[*expMonId], MON_DATA_HP); + } + else + { + CopyMonLevelAndBaseStatsToBattleMon(battler, &gPlayerParty[*expMonId]); + } if (gBattleMons[battler].volatiles.powerTrick) SWAP(gBattleMons[battler].attack, gBattleMons[battler].defense, temp); } @@ -5817,6 +5825,7 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect) } break; case EFFECT_EXPLOSION: + case EFFECT_MISTY_EXPLOSION: if (!IsAbilityOnField(ABILITY_DAMP)) { gBattleStruct->passiveHpUpdate[gBattlerAttacker] = 0; @@ -5845,7 +5854,7 @@ static bool32 HandleMoveEndMoveBlock(u32 moveEffect) } break; case EFFECT_RAPID_SPIN: - if (IsBattlerTurnDamaged(gBattlerTarget)) + if (IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerAttacker)) { BattleScriptCall(BattleScript_RapidSpinAway); effect = TRUE; @@ -6906,9 +6915,9 @@ static void Cmd_moveend(void) if (gCurrentMove != gLastResultingMoves[gBattlerAttacker] || gBattleStruct->moveResultFlags[gBattlerTarget] & MOVE_RESULT_NO_EFFECT || gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE) - gBattleStruct->sameMoveTurns[gBattlerAttacker] = 0; + gBattleStruct->metronomeItemCounter[gBattlerAttacker] = 0; else if (gCurrentMove == gLastResultingMoves[gBattlerAttacker] && gSpecialStatuses[gBattlerAttacker].parentalBondState != PARENTAL_BOND_1ST_HIT) - gBattleStruct->sameMoveTurns[gBattlerAttacker]++; + gBattleStruct->metronomeItemCounter[gBattlerAttacker]++; gBattleScripting.moveendState++; break; case MOVEEND_CLEAR_BITS: // Clear/Set bits for things like using a move for all targets and all hits. @@ -6950,6 +6959,8 @@ static void Cmd_moveend(void) gBattleMons[gBattlerAttacker].volatiles.charge = FALSE; if (gBattleMons[gBattlerAttacker].volatiles.destinyBond > 0) gBattleMons[gBattlerAttacker].volatiles.destinyBond--; + if (moveEffect == EFFECT_ECHOED_VOICE && !(gHitMarker & HITMARKER_UNABLE_TO_USE_MOVE)) + gBattleStruct->incrementEchoedVoice = TRUE; // check if Stellar type boost should be used up if (GetActiveGimmick(gBattlerAttacker) == GIMMICK_TERA && GetBattlerTeraType(gBattlerAttacker) == TYPE_STELLAR @@ -9575,7 +9586,7 @@ static void Cmd_setprotectlike(void) if (gCurrentTurnActionNumber == (gBattlersCount - 1)) notLastTurn = FALSE; - if ((sProtectSuccessRates[gDisableStructs[gBattlerAttacker].protectUses] >= Random() && notLastTurn) + if ((sProtectSuccessRates[gDisableStructs[gBattlerAttacker].protectUses] >= RandomUniform(RNG_PROTECT_FAIL, 0, USHRT_MAX) && notLastTurn) || (protectMethod == PROTECT_WIDE_GUARD && B_WIDE_GUARD != GEN_5) || (protectMethod == PROTECT_QUICK_GUARD && B_QUICK_GUARD != GEN_5)) { @@ -9604,6 +9615,7 @@ static void Cmd_setprotectlike(void) gDisableStructs[gBattlerAttacker].protectUses = 0; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_PROTECT_FAILED; gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED; + gBattleStruct->battlerState[gBattlerAttacker].stompingTantrumTimer = 2; } gBattlescriptCurrInstr = cmd->nextInstr; @@ -9687,7 +9699,7 @@ static void Cmd_setfieldweather(void) u8 battleWeatherId = cmd->weather; - if (!TryChangeBattleWeather(gBattlerAttacker, battleWeatherId, FALSE)) + if (!TryChangeBattleWeather(gBattlerAttacker, battleWeatherId, ABILITY_NONE)) { gBattleStruct->moveResultFlags[gBattlerTarget] |= MOVE_RESULT_MISSED; gBattleCommunication[MULTISTRING_CHOOSER] = B_MSG_WEATHER_FAILED; @@ -11054,7 +11066,8 @@ static void Cmd_setfocusenergy(void) } else { - if (GetGenConfig(GEN_CONFIG_FOCUS_ENERGY_CRIT_RATIO) >= GEN_3) + if (GetGenConfig(GEN_CONFIG_FOCUS_ENERGY_CRIT_RATIO) >= GEN_3 + || GetGenConfig(GEN_CONFIG_CRIT_CHANCE) == GEN_1) gBattleMons[battler].volatiles.focusEnergy = TRUE; else gBattleMons[battler].volatiles.dragonCheer = TRUE; @@ -15037,12 +15050,13 @@ void BS_SetTerrain(void) default: break; } - if (statusFlag) + if (gBattleStruct->isSkyBattle) { - enum HoldEffect atkHoldEffect = GetBattlerHoldEffect(gBattlerAttacker); - gFieldStatuses &= ~STATUS_FIELD_TERRAIN_ANY; - gFieldStatuses |= statusFlag; - gFieldTimers.terrainTimer = gBattleTurnCounter + (atkHoldEffect == HOLD_EFFECT_TERRAIN_EXTENDER) ? 8 : 5; + gBattlescriptCurrInstr = cmd->jumpInstr; + } + else if (statusFlag) + { + TryChangeBattleTerrain(gBattlerAttacker, statusFlag); gBattlescriptCurrInstr = cmd->nextInstr; } else @@ -17917,19 +17931,17 @@ void BS_JumpIfSpecies(void) gBattlescriptCurrInstr = cmd->nextInstr; } -void BS_JumpIfLeafGuardProtected(void) +void BS_JumpIfAbilityPreventsRest(void) { NATIVE_ARGS(u8 battler, const u8 *jumpInstr); u32 battler = GetBattlerForBattleScript(cmd->battler); - if (IsLeafGuardProtected(battler, GetBattlerAbility(battler))) - { - gBattlerAbility = battler; + u32 ability = GetBattlerAbility(battler); + if (B_LEAF_GUARD_PREVENTS_REST >= GEN_5 && IsLeafGuardProtected(battler, ability)) + gBattlescriptCurrInstr = cmd->jumpInstr; + else if (IsShieldsDownProtected(battler, ability)) gBattlescriptCurrInstr = cmd->jumpInstr; - } else - { gBattlescriptCurrInstr = cmd->nextInstr; - } } void BS_SetAttackerToStickyWebUser(void) diff --git a/src/battle_util.c b/src/battle_util.c index b3c50a9e6c..79cc2b40da 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -2519,7 +2519,7 @@ static enum MoveCanceller CancellerPPDeduction(struct BattleContext *ctx) // For item Metronome, echoed voice if (ctx->currentMove != gLastResultingMoves[ctx->battlerAtk] || WasUnableToUseMove(ctx->battlerAtk)) - gBattleStruct->sameMoveTurns[ctx->battlerAtk] = 0; + gBattleStruct->metronomeItemCounter[ctx->battlerAtk] = 0; if (gBattleMons[ctx->battlerAtk].pp[movePosition] > ppToDeduct) gBattleMons[ctx->battlerAtk].pp[movePosition] -= ppToDeduct; @@ -3162,24 +3162,24 @@ bool32 HasNoMonsToSwitch(u32 battler, u8 partyIdBattlerOn1, u8 partyIdBattlerOn2 } } -bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, bool32 viaAbility) +bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, u32 ability) { - enum Ability battlerAbility = GetBattlerAbility(battler); - if (gBattleWeather & sBattleWeatherInfo[battleWeatherId].flag) { return FALSE; } else if (gBattleWeather & B_WEATHER_PRIMAL_ANY - && battlerAbility != ABILITY_DESOLATE_LAND - && battlerAbility != ABILITY_PRIMORDIAL_SEA - && battlerAbility != ABILITY_DELTA_STREAM) + && ability != ABILITY_DESOLATE_LAND + && ability != ABILITY_PRIMORDIAL_SEA + && ability != ABILITY_DELTA_STREAM) { return FALSE; } - else if (GetGenConfig(GEN_CONFIG_ABILITY_WEATHER) < GEN_6 && viaAbility) + else if (GetGenConfig(GEN_CONFIG_ABILITY_WEATHER) < GEN_6 && ability != ABILITY_NONE) { gBattleWeather = sBattleWeatherInfo[battleWeatherId].flag; + for (u32 i = 0; i < gBattlersCount; i++) + gDisableStructs[i].weatherAbilityDone = FALSE; return TRUE; } else @@ -3192,25 +3192,29 @@ bool32 TryChangeBattleWeather(u32 battler, u32 battleWeatherId, bool32 viaAbilit gWishFutureKnock.weatherDuration = 8; else gWishFutureKnock.weatherDuration = 5; + for (u32 i = 0; i < gBattlersCount; i++) + gDisableStructs[i].weatherAbilityDone = FALSE; return TRUE; } return FALSE; } -static bool32 TryChangeBattleTerrain(u32 battler, u32 statusFlag, u16 *timer) +bool32 TryChangeBattleTerrain(u32 battler, u32 statusFlag) { - if ((!(gFieldStatuses & statusFlag) && (!gBattleStruct->isSkyBattle))) + if (gBattleStruct->isSkyBattle) + return FALSE; + + if (!(gFieldStatuses & statusFlag)) { gFieldStatuses &= ~STATUS_FIELD_TERRAIN_ANY; gFieldStatuses |= statusFlag; - gDisableStructs[battler].terrainAbilityDone = FALSE; - + for (u32 i = 0; i < gBattlersCount; i++) + gDisableStructs[i].terrainAbilityDone = FALSE; if (GetBattlerHoldEffect(battler) == HOLD_EFFECT_TERRAIN_EXTENDER) - *timer = gBattleTurnCounter + 8; + gFieldTimers.terrainTimer = gBattleTurnCounter + 8; else - *timer = gBattleTurnCounter + 5; - + gFieldTimers.terrainTimer = gBattleTurnCounter + 5; gBattleScripting.battler = battler; return TRUE; } @@ -4013,8 +4017,8 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab case ABILITY_ANTICIPATION: if (!gSpecialStatuses[battler].switchInAbilityDone) { - u32 types[3]; - GetBattlerTypes(battler, FALSE, types); + struct DamageContext ctx = {0}; + uq4_12_t modifier = UQ_4_12(1.0); for (i = 0; i < MAX_BATTLERS_COUNT; i++) { if (IsBattlerAlive(i) && !IsBattlerAlly(i, battler)) @@ -4025,9 +4029,14 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab enum BattleMoveEffects moveEffect = GetMoveEffect(move); moveType = GetBattleMoveType(move); - if (GetTypeModifier(moveType, types[0]) >= UQ_4_12(2.0) - || (types[0] != types[1] && GetTypeModifier(moveType, types[1]) >= UQ_4_12(2.0)) - || (types[2] != TYPE_MYSTERY && GetTypeModifier(moveType, types[2]) >= UQ_4_12(2.0)) + ctx.battlerAtk = i; + ctx.battlerDef = battler; + ctx.move = move; + ctx.moveType = moveType; + ctx.isAnticipation = TRUE; + modifier = CalcTypeEffectivenessMultiplier(&ctx); + + if (modifier >= UQ_4_12(2.0) || moveEffect == EFFECT_OHKO || moveEffect == EFFECT_SHEER_COLD) { @@ -4158,7 +4167,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab } break; case ABILITY_DRIZZLE: - if (TryChangeBattleWeather(battler, BATTLE_WEATHER_RAIN, TRUE)) + if (TryChangeBattleWeather(battler, BATTLE_WEATHER_RAIN, gLastUsedAbility)) { BattleScriptPushCursorAndCallback(BattleScript_DrizzleActivates); effect++; @@ -4171,7 +4180,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab } break; case ABILITY_SAND_STREAM: - if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SANDSTORM, TRUE)) + if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SANDSTORM, gLastUsedAbility)) { BattleScriptPushCursorAndCallback(BattleScript_SandstreamActivates); effect++; @@ -4183,8 +4192,9 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab effect++; } break; + case ABILITY_ORICHALCUM_PULSE: case ABILITY_DROUGHT: - if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SUN, TRUE)) + if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SUN, gLastUsedAbility)) { BattleScriptPushCursorAndCallback(BattleScript_DroughtActivates); effect++; @@ -4197,12 +4207,12 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab } break; case ABILITY_SNOW_WARNING: - if (GetGenConfig(GEN_SNOW_WARNING) >= GEN_9 && TryChangeBattleWeather(battler, BATTLE_WEATHER_SNOW, TRUE)) + if (GetGenConfig(GEN_SNOW_WARNING) >= GEN_9 && TryChangeBattleWeather(battler, BATTLE_WEATHER_SNOW, gLastUsedAbility)) { BattleScriptPushCursorAndCallback(BattleScript_SnowWarningActivatesSnow); effect++; } - else if (GetGenConfig(GEN_SNOW_WARNING) < GEN_9 && TryChangeBattleWeather(battler, BATTLE_WEATHER_HAIL, TRUE)) + else if (GetGenConfig(GEN_SNOW_WARNING) < GEN_9 && TryChangeBattleWeather(battler, BATTLE_WEATHER_HAIL, gLastUsedAbility)) { BattleScriptPushCursorAndCallback(BattleScript_SnowWarningActivatesHail); effect++; @@ -4216,28 +4226,28 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab break; case ABILITY_ELECTRIC_SURGE: case ABILITY_HADRON_ENGINE: - if (TryChangeBattleTerrain(battler, STATUS_FIELD_ELECTRIC_TERRAIN, &gFieldTimers.terrainTimer)) + if (TryChangeBattleTerrain(battler, STATUS_FIELD_ELECTRIC_TERRAIN)) { BattleScriptPushCursorAndCallback(BattleScript_ElectricSurgeActivates); effect++; } break; case ABILITY_GRASSY_SURGE: - if (TryChangeBattleTerrain(battler, STATUS_FIELD_GRASSY_TERRAIN, &gFieldTimers.terrainTimer)) + if (TryChangeBattleTerrain(battler, STATUS_FIELD_GRASSY_TERRAIN)) { BattleScriptPushCursorAndCallback(BattleScript_GrassySurgeActivates); effect++; } break; case ABILITY_MISTY_SURGE: - if (TryChangeBattleTerrain(battler, STATUS_FIELD_MISTY_TERRAIN, &gFieldTimers.terrainTimer)) + if (TryChangeBattleTerrain(battler, STATUS_FIELD_MISTY_TERRAIN)) { BattleScriptPushCursorAndCallback(BattleScript_MistySurgeActivates); effect++; } break; case ABILITY_PSYCHIC_SURGE: - if (TryChangeBattleTerrain(battler, STATUS_FIELD_PSYCHIC_TERRAIN, &gFieldTimers.terrainTimer)) + if (TryChangeBattleTerrain(battler, STATUS_FIELD_PSYCHIC_TERRAIN)) { BattleScriptPushCursorAndCallback(BattleScript_PsychicSurgeActivates); effect++; @@ -4338,21 +4348,21 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab } break; case ABILITY_DESOLATE_LAND: - if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SUN_PRIMAL, TRUE)) + if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SUN_PRIMAL, gLastUsedAbility)) { BattleScriptPushCursorAndCallback(BattleScript_DesolateLandActivates); effect++; } break; case ABILITY_PRIMORDIAL_SEA: - if (TryChangeBattleWeather(battler, BATTLE_WEATHER_RAIN_PRIMAL, TRUE)) + if (TryChangeBattleWeather(battler, BATTLE_WEATHER_RAIN_PRIMAL, gLastUsedAbility)) { BattleScriptPushCursorAndCallback(BattleScript_PrimordialSeaActivates); effect++; } break; case ABILITY_DELTA_STREAM: - if (TryChangeBattleWeather(battler, BATTLE_WEATHER_STRONG_WINDS, TRUE)) + if (TryChangeBattleWeather(battler, BATTLE_WEATHER_STRONG_WINDS, gLastUsedAbility)) { BattleScriptPushCursorAndCallback(BattleScript_DeltaStreamActivates); effect++; @@ -4398,13 +4408,6 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab effect++; } break; - case ABILITY_ORICHALCUM_PULSE: - if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SUN, TRUE)) - { - BattleScriptPushCursorAndCallback(BattleScript_DroughtActivates); - effect++; - } - break; case ABILITY_SUPREME_OVERLORD: if (!gSpecialStatuses[battler].switchInAbilityDone) { @@ -5176,7 +5179,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab BattleScriptCall(BattleScript_BlockedByPrimalWeatherRet); effect++; } - else if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SANDSTORM, TRUE)) + else if (TryChangeBattleWeather(battler, BATTLE_WEATHER_SANDSTORM, gLastUsedAbility)) { gBattleScripting.battler = battler; BattleScriptCall(BattleScript_SandSpitActivates); @@ -5230,7 +5233,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab if (!gProtectStructs[gBattlerAttacker].confusionSelfDmg && IsBattlerTurnDamaged(gBattlerTarget) && IsBattlerAlive(gBattlerTarget) - && TryChangeBattleTerrain(gBattlerTarget, STATUS_FIELD_GRASSY_TERRAIN, &gFieldTimers.terrainTimer)) + && TryChangeBattleTerrain(gBattlerTarget, STATUS_FIELD_GRASSY_TERRAIN)) { BattleScriptCall(BattleScript_SeedSowerActivates); effect++; @@ -5268,7 +5271,10 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, u32 battler, enum Ability ab && IsBattlerTurnDamaged(gBattlerTarget) && (gSideTimers[GetBattlerSide(gBattlerAttacker)].toxicSpikesAmount != 2)) { - SWAP(gBattlerAttacker, gBattlerTarget, i); + SaveBattlerTarget(gBattlerTarget); + SaveBattlerAttacker(gBattlerAttacker); + gBattlerAttacker = gBattlerTarget; + gBattlerTarget = BATTLE_OPPOSITE(gBattlerAttacker); BattleScriptCall(BattleScript_ToxicDebrisActivates); effect++; } @@ -6015,7 +6021,6 @@ bool32 CanSetNonVolatileStatus(u32 battlerAtk, u32 battlerDef, enum Ability abil // Checks that apply to all non volatile statuses if (abilityDef == ABILITY_COMATOSE - || abilityDef == ABILITY_SHIELDS_DOWN || abilityDef == ABILITY_PURIFYING_SALT) { abilityAffected = TRUE; @@ -6030,6 +6035,11 @@ bool32 CanSetNonVolatileStatus(u32 battlerAtk, u32 battlerDef, enum Ability abil abilityAffected = TRUE; battleScript = BattleScript_AbilityProtectsDoesntAffect; } + else if (IsShieldsDownProtected(battlerDef, abilityDef)) + { + abilityAffected = TRUE; + battleScript = BattleScript_AbilityProtectsDoesntAffect; + } else if ((sideBattler = IsFlowerVeilProtected(battlerDef))) { abilityAffected = TRUE; @@ -6528,11 +6538,11 @@ enum IronBallCheck }; // Only called directly when calculating damage type effectiveness, and Iron Ball's type effectiveness mechanics -bool32 IsBattlerGroundedInverseCheck(u32 battler, enum Ability ability, enum HoldEffect holdEffect, enum InverseBattleCheck checkInverse) +static bool32 IsBattlerGroundedInverseCheck(u32 battler, enum Ability ability, enum HoldEffect holdEffect, enum InverseBattleCheck checkInverse, bool32 isAnticipation) { if (holdEffect == HOLD_EFFECT_IRON_BALL) return TRUE; - if (gFieldStatuses & STATUS_FIELD_GRAVITY) + if (gFieldStatuses & STATUS_FIELD_GRAVITY && isAnticipation == FALSE) return TRUE; if (B_ROOTED_GROUNDING >= GEN_4 && gBattleMons[battler].volatiles.root) return TRUE; @@ -6553,7 +6563,7 @@ bool32 IsBattlerGroundedInverseCheck(u32 battler, enum Ability ability, enum Hol bool32 IsBattlerGrounded(u32 battler, enum Ability ability, enum HoldEffect holdEffect) { - return IsBattlerGroundedInverseCheck(battler, ability, holdEffect, NOT_INVERSE_BATTLE); + return IsBattlerGroundedInverseCheck(battler, ability, holdEffect, NOT_INVERSE_BATTLE, FALSE); } u32 GetMoveSlot(u16 *moves, u32 move) @@ -6953,10 +6963,10 @@ static inline u32 CalcMoveBasePower(struct DamageContext *ctx) break; } case EFFECT_ECHOED_VOICE: - // gBattleStruct->sameMoveTurns incremented in ppreduce - if (gBattleStruct->sameMoveTurns[battlerAtk] != 0 && GetMoveEffect(gLastResultingMoves[battlerAtk]) == EFFECT_ECHOED_VOICE) + // gBattleStruct->echoedVoiceCounter incremented in EndTurnVarious called by DoEndTurnEffects + if (gBattleStruct->echoedVoiceCounter != 0) { - basePower += (basePower * gBattleStruct->sameMoveTurns[battlerAtk]); + basePower += (basePower * gBattleStruct->echoedVoiceCounter); if (basePower > 200) basePower = 200; } @@ -7671,9 +7681,9 @@ static inline u32 CalcAttackStat(struct DamageContext *ctx) // The offensive stats of a Player's Pokémon are boosted by x1.1 (+10%) if they have the corresponding flags set (eg. Badges) if (ShouldGetStatBadgeBoost(B_FLAG_BADGE_BOOST_ATTACK, battlerAtk) && IsBattleMovePhysical(move)) - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.1)); + modifier = uq4_12_multiply_half_down(modifier, GetBadgeBoostModifier()); if (ShouldGetStatBadgeBoost(B_FLAG_BADGE_BOOST_SPATK, battlerAtk) && IsBattleMoveSpecial(move)) - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.1)); + modifier = uq4_12_multiply_half_down(modifier, GetBadgeBoostModifier()); return uq4_12_multiply_by_int_half_down(modifier, atkStat); } @@ -7851,11 +7861,11 @@ static inline u32 CalcDefenseStat(struct DamageContext *ctx) if (IS_BATTLER_OF_TYPE(battlerDef, TYPE_ICE) && IsBattlerWeatherAffected(battlerDef, B_WEATHER_SNOW) && usesDefStat) modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.5)); - // The offensive stats of a Player's Pokémon are boosted by x1.1 (+10%) if they have the corresponding flags set (eg. Badges) + // The defensive stats of a Player's Pokémon are boosted by x1.1 (+10%) if they have the corresponding flags set (eg. Badges) if (ShouldGetStatBadgeBoost(B_FLAG_BADGE_BOOST_DEFENSE, battlerDef) && IsBattleMovePhysical(move)) - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.1)); + modifier = uq4_12_multiply_half_down(modifier, GetBadgeBoostModifier()); if (ShouldGetStatBadgeBoost(B_FLAG_BADGE_BOOST_SPDEF, battlerDef) && IsBattleMoveSpecial(move)) - modifier = uq4_12_multiply_half_down(modifier, UQ_4_12(1.1)); + modifier = uq4_12_multiply_half_down(modifier, GetBadgeBoostModifier()); return uq4_12_multiply_by_int_half_down(modifier, defStat); } @@ -8122,7 +8132,7 @@ static inline uq4_12_t GetAttackerItemsModifier(u32 battlerAtk, uq4_12_t typeEff { case HOLD_EFFECT_METRONOME: metronomeBoostBase = PercentToUQ4_12(GetBattlerHoldEffectParam(battlerAtk)); - metronomeTurns = min(gBattleStruct->sameMoveTurns[battlerAtk], 5); + metronomeTurns = min(gBattleStruct->metronomeItemCounter[battlerAtk], 5); // according to bulbapedia this is the "correct" way to calculate the metronome boost // due to the limited domain of damage numbers it will never really matter whether this is off by one return uq4_12_add(UQ_4_12(1.0), metronomeBoostBase * metronomeTurns); @@ -8445,7 +8455,7 @@ static inline void MulByTypeEffectiveness(struct DamageContext *ctx, uq4_12_t *m if (ctx->moveType == TYPE_PSYCHIC && defType == TYPE_DARK && gBattleMons[ctx->battlerDef].volatiles.miracleEye && mod == UQ_4_12(0.0)) mod = UQ_4_12(1.0); - if (GetMoveEffect(ctx->move) == EFFECT_SUPER_EFFECTIVE_ON_ARG && defType == GetMoveArgType(ctx->move)) + if (GetMoveEffect(ctx->move) == EFFECT_SUPER_EFFECTIVE_ON_ARG && defType == GetMoveArgType(ctx->move) && !ctx->isAnticipation) mod = UQ_4_12(2.0); if (ctx->moveType == TYPE_GROUND && defType == TYPE_FLYING && IsBattlerGrounded(ctx->battlerDef, ctx->abilityDef, ctx->holdEffectDef) && mod == UQ_4_12(0.0)) mod = UQ_4_12(1.0); @@ -8453,7 +8463,7 @@ static inline void MulByTypeEffectiveness(struct DamageContext *ctx, uq4_12_t *m mod = UQ_4_12(2.0); // B_WEATHER_STRONG_WINDS weakens Super Effective moves against Flying-type Pokémon - if (gBattleWeather & B_WEATHER_STRONG_WINDS && HasWeatherEffect()) + if (gBattleWeather & B_WEATHER_STRONG_WINDS && HasWeatherEffect() && !ctx->isAnticipation) { if (defType == TYPE_FLYING && mod >= UQ_4_12(2.0)) mod = UQ_4_12(1.0); @@ -8544,7 +8554,7 @@ static inline uq4_12_t CalcTypeEffectivenessMultiplierInternal(struct DamageCont if (B_GLARE_GHOST < GEN_4 && ctx->move == MOVE_GLARE && IS_BATTLER_OF_TYPE(ctx->battlerDef, TYPE_GHOST)) modifier = UQ_4_12(0.0); } - else if (ctx->moveType == TYPE_GROUND && !IsBattlerGroundedInverseCheck(ctx->battlerDef, ctx->abilityDef, ctx->holdEffectDef, INVERSE_BATTLE) && !(MoveIgnoresTypeIfFlyingAndUngrounded(ctx->move))) + else if (ctx->moveType == TYPE_GROUND && !IsBattlerGroundedInverseCheck(ctx->battlerDef, ctx->abilityDef, ctx->holdEffectDef, INVERSE_BATTLE, ctx->isAnticipation) && !(MoveIgnoresTypeIfFlyingAndUngrounded(ctx->move))) { modifier = UQ_4_12(0.0); if (ctx->updateFlags && ctx->abilityDef == ABILITY_LEVITATE) @@ -8608,7 +8618,7 @@ uq4_12_t CalcTypeEffectivenessMultiplier(struct DamageContext *ctx) if (ctx->move != MOVE_STRUGGLE && ctx->moveType != TYPE_MYSTERY) { modifier = CalcTypeEffectivenessMultiplierInternal(ctx, modifier); - if (GetMoveEffect(ctx->move) == EFFECT_TWO_TYPED_MOVE) + if (GetMoveEffect(ctx->move) == EFFECT_TWO_TYPED_MOVE && !ctx->isAnticipation) { ctx->moveType = GetMoveArgType(ctx->move); modifier = CalcTypeEffectivenessMultiplierInternal(ctx, modifier); @@ -9347,9 +9357,17 @@ u32 TryImmunityAbilityHealStatus(u32 battler, enum AbilityEffect caseID) return 0; } +uq4_12_t GetBadgeBoostModifier(void) +{ + if (GetGenConfig(GEN_CONFIG_BADGE_BOOST) < GEN_3) + return UQ_4_12(1.125); + else + return UQ_4_12(1.1); +} + bool32 ShouldGetStatBadgeBoost(u16 badgeFlag, u32 battler) { - if (B_BADGE_BOOST == GEN_3 && badgeFlag != 0) + if (GetGenConfig(GEN_CONFIG_BADGE_BOOST) <= GEN_3 && badgeFlag != 0) { if (gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_RECORDED_LINK | BATTLE_TYPE_FRONTIER)) return FALSE; @@ -10084,6 +10102,8 @@ void SetShellSideArmCategory(void) statStage = gBattleMons[battlerDef].statStages[STAT_DEF]; targetDefStat *= gStatStageRatios[statStage][0]; targetDefStat /= gStatStageRatios[statStage][1]; + if (targetDefStat == 0) + targetDefStat = 1; physical = ((((2 * gBattleMons[battlerAtk].level / 5 + 2) * power * attackerAtkStat) / targetDefStat) / 50); @@ -10091,6 +10111,8 @@ void SetShellSideArmCategory(void) statStage = gBattleMons[battlerDef].statStages[STAT_SPDEF]; targetSpDefStat *= gStatStageRatios[statStage][0]; targetSpDefStat /= gStatStageRatios[statStage][1]; + if (targetSpDefStat == 0) + targetSpDefStat = 1; special = ((((2 * gBattleMons[battlerAtk].level / 5 + 2) * power * attackerSpAtkStat) / targetSpDefStat) / 50); diff --git a/src/data/items.h b/src/data/items.h index 937e2b0d79..a64bb6ace1 100644 --- a/src/data/items.h +++ b/src/data/items.h @@ -9826,7 +9826,7 @@ const struct Item gItemsInfo[] = "heals confusion\n" "in battle."), .pocket = POCKET_BERRIES, - .type = ITEM_USE_BAG_MENU, + .type = ITEM_USE_PARTY_MENU, .fieldUseFunc = ItemUseOutOfBattle_CannotUse, .battleUsage = EFFECT_ITEM_CURE_STATUS, .effect = gItemEffect_PersimBerry, diff --git a/src/data/moves_info.h b/src/data/moves_info.h index 1f2ee5f6a8..d92e669776 100644 --- a/src/data/moves_info.h +++ b/src/data/moves_info.h @@ -17103,6 +17103,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_SPECIAL, + .argument = { .damagePercentage = 50 }, .metronomeBanned = B_UPDATED_MOVE_FLAGS >= GEN_8, .contestEffect = CONTEST_EFFECT_BADLY_STARTLE_MONS_WITH_GOOD_APPEALS, .contestCategory = CONTEST_CATEGORY_CUTE, @@ -20171,6 +20172,7 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] = .target = MOVE_TARGET_SELECTED, .priority = 0, .category = DAMAGE_CATEGORY_SPECIAL, + .argument = { .damagePercentage = 50 }, .metronomeBanned = TRUE, .battleAnimScript = gBattleAnimMove_Ruination, }, diff --git a/src/data/pokemon/form_change_tables.h b/src/data/pokemon/form_change_tables.h index ad8a7e90ca..fd443458c7 100644 --- a/src/data/pokemon/form_change_tables.h +++ b/src/data/pokemon/form_change_tables.h @@ -925,6 +925,7 @@ static const struct FormChange sSilvallyFormChangeTable[] = { #if P_FAMILY_MINIOR static const struct FormChange sMiniorRedFormChangeTable[] = { + {FORM_CHANGE_BEGIN_BATTLE, SPECIES_MINIOR_CORE_RED}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_METEOR_RED, ABILITY_SHIELDS_DOWN, HP_HIGHER_THAN, 50}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_CORE_RED, ABILITY_SHIELDS_DOWN, HP_LOWER_EQ_THAN, 50}, {FORM_CHANGE_BATTLE_SWITCH, SPECIES_MINIOR_CORE_RED}, @@ -933,6 +934,7 @@ static const struct FormChange sMiniorRedFormChangeTable[] = { {FORM_CHANGE_TERMINATOR}, }; static const struct FormChange sMiniorBlueFormChangeTable[] = { + {FORM_CHANGE_BEGIN_BATTLE, SPECIES_MINIOR_CORE_BLUE}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_METEOR_BLUE, ABILITY_SHIELDS_DOWN, HP_HIGHER_THAN, 50}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_CORE_BLUE, ABILITY_SHIELDS_DOWN, HP_LOWER_EQ_THAN, 50}, {FORM_CHANGE_BATTLE_SWITCH, SPECIES_MINIOR_CORE_BLUE}, @@ -941,6 +943,7 @@ static const struct FormChange sMiniorBlueFormChangeTable[] = { {FORM_CHANGE_TERMINATOR}, }; static const struct FormChange sMiniorGreenFormChangeTable[] = { + {FORM_CHANGE_BEGIN_BATTLE, SPECIES_MINIOR_CORE_GREEN}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_METEOR_GREEN, ABILITY_SHIELDS_DOWN, HP_HIGHER_THAN, 50}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_CORE_GREEN, ABILITY_SHIELDS_DOWN, HP_LOWER_EQ_THAN, 50}, {FORM_CHANGE_BATTLE_SWITCH, SPECIES_MINIOR_CORE_GREEN}, @@ -949,6 +952,7 @@ static const struct FormChange sMiniorGreenFormChangeTable[] = { {FORM_CHANGE_TERMINATOR}, }; static const struct FormChange sMiniorIndigoFormChangeTable[] = { + {FORM_CHANGE_BEGIN_BATTLE, SPECIES_MINIOR_CORE_INDIGO}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_METEOR_INDIGO, ABILITY_SHIELDS_DOWN, HP_HIGHER_THAN, 50}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_CORE_INDIGO, ABILITY_SHIELDS_DOWN, HP_LOWER_EQ_THAN, 50}, {FORM_CHANGE_BATTLE_SWITCH, SPECIES_MINIOR_CORE_INDIGO}, @@ -957,6 +961,7 @@ static const struct FormChange sMiniorIndigoFormChangeTable[] = { {FORM_CHANGE_TERMINATOR}, }; static const struct FormChange sMiniorOrangeFormChangeTable[] = { + {FORM_CHANGE_BEGIN_BATTLE, SPECIES_MINIOR_CORE_ORANGE}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_METEOR_ORANGE, ABILITY_SHIELDS_DOWN, HP_HIGHER_THAN, 50}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_CORE_ORANGE, ABILITY_SHIELDS_DOWN, HP_LOWER_EQ_THAN, 50}, {FORM_CHANGE_BATTLE_SWITCH, SPECIES_MINIOR_CORE_ORANGE}, @@ -965,6 +970,7 @@ static const struct FormChange sMiniorOrangeFormChangeTable[] = { {FORM_CHANGE_TERMINATOR}, }; static const struct FormChange sMiniorVioletFormChangeTable[] = { + {FORM_CHANGE_BEGIN_BATTLE, SPECIES_MINIOR_CORE_VIOLET}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_METEOR_VIOLET, ABILITY_SHIELDS_DOWN, HP_HIGHER_THAN, 50}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_CORE_VIOLET, ABILITY_SHIELDS_DOWN, HP_LOWER_EQ_THAN, 50}, {FORM_CHANGE_BATTLE_SWITCH, SPECIES_MINIOR_CORE_VIOLET}, @@ -973,6 +979,7 @@ static const struct FormChange sMiniorVioletFormChangeTable[] = { {FORM_CHANGE_TERMINATOR}, }; static const struct FormChange sMiniorYellowFormChangeTable[] = { + {FORM_CHANGE_BEGIN_BATTLE, SPECIES_MINIOR_CORE_YELLOW}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_METEOR_YELLOW, ABILITY_SHIELDS_DOWN, HP_HIGHER_THAN, 50}, {FORM_CHANGE_BATTLE_HP_PERCENT, SPECIES_MINIOR_CORE_YELLOW, ABILITY_SHIELDS_DOWN, HP_LOWER_EQ_THAN, 50}, {FORM_CHANGE_BATTLE_SWITCH, SPECIES_MINIOR_CORE_YELLOW}, diff --git a/src/event_object_movement.c b/src/event_object_movement.c index 4ea82b8c05..53d17bfaef 100644 --- a/src/event_object_movement.c +++ b/src/event_object_movement.c @@ -1927,8 +1927,15 @@ u8 CreateVirtualObject(u16 graphicsId, u8 virtualObjId, s16 x, s16 y, u8 elevati x += MAP_OFFSET; y += MAP_OFFSET; SetSpritePosToOffsetMapCoords(&x, &y, 8, 16); - if (spriteTemplate.paletteTag != TAG_NONE) + if (spriteTemplate.paletteTag == OBJ_EVENT_PAL_TAG_DYNAMIC) + { + u32 paletteNum = LoadDynamicFollowerPaletteFromGraphicsId(graphicsId, &spriteTemplate); + spriteTemplate.paletteTag = GetSpritePaletteTagByPaletteNum(paletteNum); + } + else if (spriteTemplate.paletteTag != TAG_NONE) + { LoadObjectEventPalette(spriteTemplate.paletteTag); + } spriteId = CreateSpriteAtEnd(&spriteTemplate, x, y, 0); if (spriteId != MAX_SPRITES) @@ -1942,6 +1949,9 @@ u8 CreateVirtualObject(u16 graphicsId, u8 virtualObjId, s16 x, s16 y, u8 elevati sprite->sVirtualObjId = virtualObjId; sprite->sVirtualObjElev = elevation; + if (OW_GFX_COMPRESS && graphicsInfo->compressed) + spriteTemplate.tileTag = LoadSheetGraphicsInfo(graphicsInfo, graphicsId, sprite); + if (subspriteTables != NULL) { SetSubspriteTables(sprite, subspriteTables); @@ -6591,7 +6601,7 @@ bool8 ObjectEventIsHeldMovementActive(struct ObjectEvent *objectEvent) static u8 TryUpdateMovementActionOnStairs(struct ObjectEvent *objectEvent, u8 movementActionId) { - if (objectEvent->isPlayer || objectEvent->localId == OBJ_EVENT_ID_FOLLOWER) + if (objectEvent->isPlayer || objectEvent->localId == OBJ_EVENT_ID_FOLLOWER || objectEvent->localId == OBJ_EVENT_ID_NPC_FOLLOWER) return movementActionId; // handled separately if (!ObjectMovingOnRockStairs(objectEvent, objectEvent->movementDirection)) diff --git a/src/fonts.c b/src/fonts.c index 46f3a335c4..3e69537113 100644 --- a/src/fonts.c +++ b/src/fonts.c @@ -229,7 +229,7 @@ ALIGNED(4) const u8 gFontSmallNarrowerLatinGlyphWidths[] = { 4, 5, 6, 7, 4, 5, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 7, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 4, 2, 4, 2, - 4, 4, 4, 2, 2, 4, 4, 8, 2, 8, 5, 4, 4, 4, 4, 4, + 4, 4, 4, 2, 2, 4, 4, 8, 2, 8, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 3, 4, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 7, diff --git a/src/overworld.c b/src/overworld.c index e07d48b78c..a3f2e6f007 100644 --- a/src/overworld.c +++ b/src/overworld.c @@ -1719,8 +1719,7 @@ static void OverworldBasic(void) || bld0[1] != bld1[1] || bld0[2] != bld1[2]) { - UpdateAltBgPalettes(PALETTES_BG); - UpdatePalettesWithTime(PALETTES_ALL); + ApplyWeatherColorMapIfIdle(gWeatherPtr->colorMapIndex); } } } @@ -1798,6 +1797,10 @@ void CB2_NewGame(void) SetFieldVBlankCallback(); SetMainCallback1(CB1_Overworld); SetMainCallback2(CB2_Overworld); +#if OW_USE_FAKE_RTC + // Wall clock now track local time so we set it to 10AM to match initial wall clock time + RtcCalcLocalTimeOffset(0, 10, 0, 0); +#endif } void CB2_WhiteOut(void) diff --git a/src/party_menu.c b/src/party_menu.c index 3b05551cb2..8a9532eaea 100644 --- a/src/party_menu.c +++ b/src/party_menu.c @@ -273,6 +273,7 @@ static bool8 IsMonAllowedInMinigame(u8); static void DisplayPartyPokemonDataToTeachMove(u8, u16); static u8 CanTeachMove(struct Pokemon *, u16); static void DisplayPartyPokemonBarDetail(u8, const u8 *, u8, const u8 *); +static void DisplayPartyPokemonBarDetailToFit(u8 windowId, const u8 *str, u8 color, const u8 *align, u32 width); static void DisplayPartyPokemonLevel(u8, struct PartyMenuBox *); static void DisplayPartyPokemonGender(u8, u16, u8 *, struct PartyMenuBox *); static void DisplayPartyPokemonHP(u16 hp, u16 maxHp, struct PartyMenuBox *menuBox); @@ -1167,7 +1168,7 @@ static void DisplayPartyPokemonDataForMultiBattle(u8 slot) StringCopy(gStringVar1, gMultiPartnerParty[actualSlot].nickname); StringGet_Nickname(gStringVar1); ConvertInternationalPlayerName(gStringVar1); - DisplayPartyPokemonBarDetail(menuBox->windowId, gStringVar1, 0, menuBox->infoRects->dimensions); + DisplayPartyPokemonBarDetailToFit(menuBox->windowId, gStringVar1, 0, menuBox->infoRects->dimensions, 50); DisplayPartyPokemonLevel(gMultiPartnerParty[actualSlot].level, menuBox); DisplayPartyPokemonGender(gMultiPartnerParty[actualSlot].gender, gMultiPartnerParty[actualSlot].species, gMultiPartnerParty[actualSlot].nickname, menuBox); DisplayPartyPokemonHP(gMultiPartnerParty[actualSlot].hp, gMultiPartnerParty[actualSlot].maxhp, menuBox); @@ -1511,11 +1512,21 @@ static void HandleChooseMonSelection(u8 taskId, s8 *slotPtr) case PARTY_ACTION_SEND_MON_TO_BOX: { u8 partyId = GetPartyIdFromBattleSlot((u8)*slotPtr); - if (partyId == 0 || ((gBattleTypeFlags & BATTLE_TYPE_DOUBLE) && partyId == 2) - || ((gBattleTypeFlags & BATTLE_TYPE_MULTI) && partyId >= (PARTY_SIZE / 2))) + if (partyId == 0 || ((gBattleTypeFlags & BATTLE_TYPE_DOUBLE) && partyId == 1)) { - // Can't select if mon is currently on the field, or doesn't belong to you + // Can't select if mon is currently on the field PlaySE(SE_FAILURE); + DisplayPartyMenuMessage(gText_CannotSendMonToBoxActive, FALSE); + ScheduleBgCopyTilemapToVram(2); + gTasks[taskId].func = Task_ReturnToChooseMonAfterText; + } + else if ((gBattleTypeFlags & BATTLE_TYPE_MULTI) && partyId >= (PARTY_SIZE / 2)) + { + // Can't select if mon doesn't belong to you + PlaySE(SE_FAILURE); + DisplayPartyMenuMessage(gText_CannotSendMonToBoxPartner, FALSE); + ScheduleBgCopyTilemapToVram(2); + gTasks[taskId].func = Task_ReturnToChooseMonAfterText; } else if (DoesSelectedMonKnowHM((u8 *)slotPtr)) { diff --git a/src/pokemon.c b/src/pokemon.c index 51449d7889..a7cdae8af7 100644 --- a/src/pokemon.c +++ b/src/pokemon.c @@ -1692,32 +1692,10 @@ static u16 CalculateBoxMonChecksumReencrypt(struct BoxPokemon *boxMon) return checksum; } -#define CALC_STAT(base, iv, ev, statIndex, field) \ -{ \ - u8 baseStat = gSpeciesInfo[species].base; \ - s32 n = (((2 * baseStat + iv + ev / 4) * level) / 100) + 5; \ - n = ModifyStatByNature(nature, n, statIndex); \ - if (B_FRIENDSHIP_BOOST == TRUE) \ - n = n + ((n * 10 * friendship) / (MAX_FRIENDSHIP * 100));\ - SetMonData(mon, field, &n); \ -} - void CalculateMonStats(struct Pokemon *mon) { s32 oldMaxHP = GetMonData(mon, MON_DATA_MAX_HP, NULL); s32 currentHP = GetMonData(mon, MON_DATA_HP, NULL); - s32 hpIV = GetMonData(mon, MON_DATA_HYPER_TRAINED_HP) ? MAX_PER_STAT_IVS : GetMonData(mon, MON_DATA_HP_IV, NULL); - s32 hpEV = GetMonData(mon, MON_DATA_HP_EV, NULL); - s32 attackIV = GetMonData(mon, MON_DATA_HYPER_TRAINED_ATK) ? MAX_PER_STAT_IVS : GetMonData(mon, MON_DATA_ATK_IV, NULL); - s32 attackEV = GetMonData(mon, MON_DATA_ATK_EV, NULL); - s32 defenseIV = GetMonData(mon, MON_DATA_HYPER_TRAINED_DEF) ? MAX_PER_STAT_IVS : GetMonData(mon, MON_DATA_DEF_IV, NULL); - s32 defenseEV = GetMonData(mon, MON_DATA_DEF_EV, NULL); - s32 speedIV = GetMonData(mon, MON_DATA_HYPER_TRAINED_SPEED) ? MAX_PER_STAT_IVS : GetMonData(mon, MON_DATA_SPEED_IV, NULL); - s32 speedEV = GetMonData(mon, MON_DATA_SPEED_EV, NULL); - s32 spAttackIV = GetMonData(mon, MON_DATA_HYPER_TRAINED_SPATK) ? MAX_PER_STAT_IVS : GetMonData(mon, MON_DATA_SPATK_IV, NULL); - s32 spAttackEV = GetMonData(mon, MON_DATA_SPATK_EV, NULL); - s32 spDefenseIV = GetMonData(mon, MON_DATA_HYPER_TRAINED_SPDEF) ? MAX_PER_STAT_IVS : GetMonData(mon, MON_DATA_SPDEF_IV, NULL); - s32 spDefenseEV = GetMonData(mon, MON_DATA_SPDEF_EV, NULL); u16 species = GetMonData(mon, MON_DATA_SPECIES, NULL); u8 friendship = GetMonData(mon, MON_DATA_FRIENDSHIP, NULL); s32 level = GetLevelFromMonExp(mon); @@ -1727,28 +1705,55 @@ void CalculateMonStats(struct Pokemon *mon) SetMonData(mon, MON_DATA_LEVEL, &level); + bool32 hyperTrained[NUM_STATS]; //In a battle test, hyper training flag indicates a fixed stat + s32 iv[NUM_STATS]; + s32 ev[NUM_STATS]; + for (u32 i = 0; i < NUM_STATS; i++) + { + hyperTrained[i] = GetMonData(mon, MON_DATA_HYPER_TRAINED_HP + i); + iv[i] = GetMonData(mon, MON_DATA_HP_IV + i); + ev[i] = GetMonData(mon, MON_DATA_HP_EV + i); + + if (hyperTrained[i]) + { + #if TESTING + if (gMain.inBattle) + continue; + #endif + iv[i] = MAX_PER_STAT_IVS; + } + + if (i == STAT_HP) + continue; + + u8 baseStat = GetSpeciesBaseStat(species, i); + s32 n = (((2 * baseStat + iv[i] + ev[i] / 4) * level) / 100) + 5; + n = ModifyStatByNature(nature, n, i); + if (B_FRIENDSHIP_BOOST == TRUE) + n = n + ((n * 10 * friendship) / (MAX_FRIENDSHIP * 100)); + SetMonData(mon, MON_DATA_MAX_HP + i, &n); + } + +#if TESTING + if (hyperTrained[STAT_HP] && gMain.inBattle) + return; +#endif + if (species == SPECIES_SHEDINJA) { newMaxHP = 1; } else { - s32 n = 2 * GetSpeciesBaseHP(species) + hpIV; - newMaxHP = (((n + hpEV / 4) * level) / 100) + level + 10; + s32 n = 2 * GetSpeciesBaseHP(species) + iv[STAT_HP]; + newMaxHP = (((n + ev[STAT_HP] / 4) * level) / 100) + level + 10; } gBattleScripting.levelUpHP = newMaxHP - oldMaxHP; if (gBattleScripting.levelUpHP == 0) gBattleScripting.levelUpHP = 1; - SetMonData(mon, MON_DATA_MAX_HP, &newMaxHP); - CALC_STAT(baseAttack, attackIV, attackEV, STAT_ATK, MON_DATA_ATK) - CALC_STAT(baseDefense, defenseIV, defenseEV, STAT_DEF, MON_DATA_DEF) - CALC_STAT(baseSpeed, speedIV, speedEV, STAT_SPEED, MON_DATA_SPEED) - CALC_STAT(baseSpAttack, spAttackIV, spAttackEV, STAT_SPATK, MON_DATA_SPATK) - CALC_STAT(baseSpDefense, spDefenseIV, spDefenseEV, STAT_SPDEF, MON_DATA_SPDEF) - // Since a pokemon's maxHP data could either not have // been initialized at this point or this pokemon is // just fainted, the check for oldMaxHP is important. @@ -3568,6 +3573,26 @@ u32 GetSpeciesBaseSpeed(u16 species) return gSpeciesInfo[SanitizeSpeciesId(species)].baseSpeed; } +u32 GetSpeciesBaseStat(u16 species, u32 statIndex) +{ + switch (statIndex) + { + case STAT_HP: + return GetSpeciesBaseHP(species); + case STAT_ATK: + return GetSpeciesBaseAttack(species); + case STAT_DEF: + return GetSpeciesBaseDefense(species); + case STAT_SPEED: + return GetSpeciesBaseSpeed(species); + case STAT_SPATK: + return GetSpeciesBaseSpAttack(species); + case STAT_SPDEF: + return GetSpeciesBaseSpDefense(species); + } + return 0; +} + const struct LevelUpMove *GetSpeciesLevelUpLearnset(u16 species) { const struct LevelUpMove *learnset = gSpeciesInfo[SanitizeSpeciesId(species)].levelUpLearnset; diff --git a/src/pokenav_ribbons_summary.c b/src/pokenav_ribbons_summary.c index 4b08b05cac..1e3d88b2dd 100644 --- a/src/pokenav_ribbons_summary.c +++ b/src/pokenav_ribbons_summary.c @@ -585,7 +585,11 @@ static u32 LoopedTask_OpenRibbonsSummaryMenu(s32 state) DecompressAndCopyTileDataToVram(1, sRibbonIconsSmall_Gfx, 0, 1, 0); SetBgTilemapBuffer(1, menu->tilemapBuffers[1]); FillBgTilemapBufferRect_Palette0(1, 0, 0, 0, 32, 20); - CopyPaletteIntoBufferUnfaded(sRibbonIcons1_Pal, BG_PLTT_ID(2), 5 * PLTT_SIZE_4BPP); + CopyPaletteIntoBufferUnfaded(sRibbonIcons1_Pal, BG_PLTT_ID(2), PLTT_SIZE_4BPP); + CopyPaletteIntoBufferUnfaded(sRibbonIcons2_Pal, BG_PLTT_ID(3), PLTT_SIZE_4BPP); + CopyPaletteIntoBufferUnfaded(sRibbonIcons3_Pal, BG_PLTT_ID(4), PLTT_SIZE_4BPP); + CopyPaletteIntoBufferUnfaded(sRibbonIcons4_Pal, BG_PLTT_ID(5), PLTT_SIZE_4BPP); + CopyPaletteIntoBufferUnfaded(sRibbonIcons5_Pal, BG_PLTT_ID(6), PLTT_SIZE_4BPP); CopyPaletteIntoBufferUnfaded(sMonInfo_Pal, BG_PLTT_ID(10), sizeof(sMonInfo_Pal)); CopyBgTilemapBufferToVram(1); return LT_INC_AND_PAUSE; diff --git a/src/rtc.c b/src/rtc.c index 34f84183cf..fb2518f210 100644 --- a/src/rtc.c +++ b/src/rtc.c @@ -348,6 +348,7 @@ void RtcCalcLocalTimeOffset(s32 days, s32 hours, s32 minutes, s32 seconds) gLocalTime.hours = hours; gLocalTime.minutes = minutes; gLocalTime.seconds = seconds; + FakeRtc_ManuallySetTime(gLocalTime.days, gLocalTime.hours, gLocalTime.minutes, seconds); RtcGetInfo(&sRtc); RtcCalcTimeDifference(&sRtc, &gSaveBlock2Ptr->localTimeOffset, &gLocalTime); } diff --git a/src/scrcmd.c b/src/scrcmd.c index 767f41c6d5..54edd76c61 100644 --- a/src/scrcmd.c +++ b/src/scrcmd.c @@ -3154,11 +3154,13 @@ bool8 Scrcmd_getobjectfacingdirection(struct ScriptContext *ctx) return FALSE; } -bool8 ScrFunc_hidefollower(struct ScriptContext *ctx) +bool8 ScrCmd_hidefollower(struct ScriptContext *ctx) { bool16 wait = VarGet(ScriptReadHalfword(ctx)); struct ObjectEvent *obj; + Script_RequestEffects(SCREFF_V1 | SCREFF_HARDWARE); + if ((obj = ScriptHideFollower()) != NULL && wait) { sMovingNpcId = obj->localId; diff --git a/src/strings.c b/src/strings.c index 372af24456..341bb6b780 100644 --- a/src/strings.c +++ b/src/strings.c @@ -1303,3 +1303,5 @@ const u8 gText_PM[] = _("PM"); const u8 gText_Relearn[] = _("{START_BUTTON} RELEARN"); // future note: don't decap this, because it mimics the summary screen BG graphics which will not get decapped const u8 gText_Rename[] = _("RENAME"); const u8 gText_CannotSendMonToBoxHM[] = _("Cannot send that mon to the box,\nbecause it knows a HM move.{PAUSE_UNTIL_PRESS}"); +const u8 gText_CannotSendMonToBoxActive[] = _("Cannot send an active battler\nto the box.{PAUSE_UNTIL_PRESS}"); +const u8 gText_CannotSendMonToBoxPartner[] = _("Cannot send a mon that doesn't,\nbelong to you to the box.{PAUSE_UNTIL_PRESS}"); diff --git a/src/trainer_see.c b/src/trainer_see.c index b982a1a2ea..7dbc55181f 100644 --- a/src/trainer_see.c +++ b/src/trainer_see.c @@ -448,7 +448,7 @@ bool8 CheckForTrainersWantingBattle(void) static u8 CheckTrainer(u8 objectEventId) { - const u8 *scriptPtr, *trainerBattlePtr; + const u8 *trainerBattlePtr; u8 numTrainers = 1; u8 approachDistance = GetTrainerApproachDistance(&gObjectEvents[objectEventId]); @@ -457,13 +457,13 @@ static u8 CheckTrainer(u8 objectEventId) if (InTrainerHill() == TRUE) { - trainerBattlePtr = scriptPtr = GetTrainerHillTrainerScript(); + trainerBattlePtr = GetTrainerHillTrainerScript(); } else { - trainerBattlePtr = scriptPtr = GetObjectEventScriptPointerByObjectEventId(objectEventId); + trainerBattlePtr = GetObjectEventScriptPointerByObjectEventId(objectEventId); struct ScriptContext ctx; - if (RunScriptImmediatelyUntilEffect(SCREFF_V1 | SCREFF_SAVE | SCREFF_HARDWARE | SCREFF_TRAINERBATTLE, scriptPtr, &ctx)) + if (RunScriptImmediatelyUntilEffect(SCREFF_V1 | SCREFF_SAVE | SCREFF_HARDWARE | SCREFF_TRAINERBATTLE, trainerBattlePtr, &ctx)) { if (*ctx.scriptPtr == 0x5c) // trainerbattle trainerBattlePtr = ctx.scriptPtr; @@ -511,7 +511,7 @@ static u8 CheckTrainer(u8 objectEventId) } gApproachingTrainers[gNoOfApproachingTrainers].objectEventId = objectEventId; - gApproachingTrainers[gNoOfApproachingTrainers].trainerScriptPtr = scriptPtr; + gApproachingTrainers[gNoOfApproachingTrainers].trainerScriptPtr = trainerBattlePtr; gApproachingTrainers[gNoOfApproachingTrainers].radius = approachDistance; InitTrainerApproachTask(&gObjectEvents[objectEventId], approachDistance - 1); gNoOfApproachingTrainers++; @@ -970,13 +970,17 @@ u8 FldEff_HeartIcon(void) return 0; } - u8 FldEff_DoubleExclMarkIcon(void) { u8 spriteId = CreateSpriteAtEnd(&sSpriteTemplate_ExclamationQuestionMark, 0, 0, 0x53); if (spriteId != MAX_SPRITES) - SetIconSpriteData(&gSprites[spriteId], FLDEFF_EXCLAMATION_MARK_ICON, 2); + { + struct Sprite *sprite = &gSprites[spriteId]; + + SetIconSpriteData(sprite, FLDEFF_DOUBLE_EXCL_MARK_ICON, 2); + UpdateSpritePaletteByTemplate(&sSpriteTemplate_ExclamationQuestionMark, sprite); + } return 0; } @@ -986,7 +990,12 @@ u8 FldEff_XIcon(void) u8 spriteId = CreateSpriteAtEnd(&sSpriteTemplate_ExclamationQuestionMark, 0, 0, 0x53); if (spriteId != MAX_SPRITES) - SetIconSpriteData(&gSprites[spriteId], FLDEFF_EXCLAMATION_MARK_ICON, 3); + { + struct Sprite *sprite = &gSprites[spriteId]; + + SetIconSpriteData(sprite, FLDEFF_X_ICON, 3); + UpdateSpritePaletteByTemplate(&sSpriteTemplate_ExclamationQuestionMark, sprite); + } return 0; } diff --git a/src/wallclock.c b/src/wallclock.c index 28c96fc972..bca008ac24 100644 --- a/src/wallclock.c +++ b/src/wallclock.c @@ -692,13 +692,13 @@ void CB2_StartWallClock(void) DecompressDataWithHeaderVram(gWallClockStart_Tilemap, (u16 *)BG_SCREEN_ADDR(7)); taskId = CreateTask(Task_SetClock_WaitFadeIn, 0); - gTasks[taskId].tHours = 10; - gTasks[taskId].tMinutes = 0; + gTasks[taskId].tHours = gLocalTime.hours; + gTasks[taskId].tMinutes = gLocalTime.minutes; gTasks[taskId].tMoveDir = 0; - gTasks[taskId].tPeriod = 0; + gTasks[taskId].tPeriod = gTasks[taskId].tHours / 12; gTasks[taskId].tMoveSpeed = 0; - gTasks[taskId].tMinuteHandAngle = 0; - gTasks[taskId].tHourHandAngle = 300; + gTasks[taskId].tMinuteHandAngle = gTasks[taskId].tMinutes * 6; + gTasks[taskId].tHourHandAngle = (gTasks[taskId].tHours % 12) * 30 + (gTasks[taskId].tMinutes / 10) * 5; spriteId = CreateSprite(&sSpriteTemplate_MinuteHand, 120, 80, 1); gSprites[spriteId].sTaskId = taskId; diff --git a/test/battle/ability/anticipation.c b/test/battle/ability/anticipation.c index 223512710f..a4a3bef77f 100644 --- a/test/battle/ability/anticipation.c +++ b/test/battle/ability/anticipation.c @@ -16,6 +16,18 @@ SINGLE_BATTLE_TEST("Anticipation causes notifies if an opponent has a super-effe } } +SINGLE_BATTLE_TEST("Anticipation does not trigger even when a move is super effective on only 1 type") +{ + GIVEN { + PLAYER(SPECIES_WHISCASH) { Ability(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_PIKACHU) { Moves(MOVE_CELEBRATE, MOVE_THUNDERBOLT); } + } WHEN { + TURN { } + } SCENE { + NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); + } +} + SINGLE_BATTLE_TEST("Anticipation causes notifies if an opponent has a One-hit KO move") { GIVEN { @@ -59,28 +71,21 @@ SINGLE_BATTLE_TEST("Anticipation doesn't consider Normalize into their effective SINGLE_BATTLE_TEST("Anticipation doesn't consider Scrappy into their effectiveness (Gen5+)") { - KNOWN_FAILING; GIVEN { ASSUME(GetMoveType(MOVE_CLOSE_COMBAT) == TYPE_FIGHTING); - ASSUME(GetSpeciesType(SPECIES_EEVEE, 0) == TYPE_NORMAL); - ASSUME(GetSpeciesType(SPECIES_EEVEE, 1) == TYPE_NORMAL); - PLAYER(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); } - OPPONENT(SPECIES_KANGASKHAN) { Ability(ABILITY_SCRAPPY); Moves(MOVE_CLOSE_COMBAT, MOVE_TRICK_OR_TREAT, MOVE_SKILL_SWAP, MOVE_CELEBRATE); } + ASSUME(GetSpeciesType(SPECIES_DOUBLADE, 0) == TYPE_STEEL); + ASSUME(GetSpeciesType(SPECIES_DOUBLADE, 1) == TYPE_GHOST); + PLAYER(SPECIES_DOUBLADE) { Ability(ABILITY_ANTICIPATION); } + OPPONENT(SPECIES_KANGASKHAN) { Ability(ABILITY_SCRAPPY); Moves(MOVE_CLOSE_COMBAT, MOVE_CELEBRATE); } } WHEN { - TURN { MOVE(opponent, MOVE_TRICK_OR_TREAT); MOVE(player, MOVE_SKILL_SWAP); } - TURN { MOVE(opponent, MOVE_SKILL_SWAP); } + TURN { } } SCENE { - ABILITY_POPUP(player, ABILITY_ANTICIPATION); - ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK_OR_TREAT, opponent); - ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, player); - ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent); NOT ABILITY_POPUP(player, ABILITY_ANTICIPATION); } } SINGLE_BATTLE_TEST("Anticipation doesn't consider Gravity into their effectiveness (Gen5+)") { - KNOWN_FAILING; GIVEN { PLAYER(SPECIES_SKARMORY); OPPONENT(SPECIES_EEVEE) { Ability(ABILITY_ANTICIPATION); Moves(MOVE_EARTHQUAKE, MOVE_GRAVITY, MOVE_SCRATCH, MOVE_POUND); } diff --git a/test/battle/ability/flower_gift.c b/test/battle/ability/flower_gift.c index da6dbc16b2..30cc7926e5 100644 --- a/test/battle/ability/flower_gift.c +++ b/test/battle/ability/flower_gift.c @@ -200,4 +200,23 @@ SINGLE_BATTLE_TEST("Flower Gift transforms Cherrim back when it uses a move that } } +DOUBLE_BATTLE_TEST("Flower Gift reverts Cherrim back after Teraform Zero clears weather") +{ + GIVEN { + PLAYER(SPECIES_TERAPAGOS_TERASTAL); + PLAYER(SPECIES_CHERRIM) { Ability(ABILITY_FLOWER_GIFT); } + OPPONENT(SPECIES_GROUDON) { Ability(ABILITY_DROUGHT); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_DROUGHT); + ABILITY_POPUP(playerRight, ABILITY_FLOWER_GIFT); + ABILITY_POPUP(playerLeft, ABILITY_TERAFORM_ZERO); + ABILITY_POPUP(playerRight, ABILITY_FLOWER_GIFT); + } THEN { + EXPECT_EQ(playerRight->species, SPECIES_CHERRIM); + } +} + TO_DO_BATTLE_TEST("Flower Gift does not transform Cherrim back to normal when suppressed if Cherrim is Dynamaxed"); diff --git a/test/battle/ability/flower_veil.c b/test/battle/ability/flower_veil.c index cb72cd33ae..78d15df1bd 100644 --- a/test/battle/ability/flower_veil.c +++ b/test/battle/ability/flower_veil.c @@ -15,7 +15,7 @@ ASSUMPTIONS ASSUME(GetMoveNonVolatileStatus(MOVE_HYPNOSIS) == MOVE_EFFECT_SLEEP); } -DOUBLE_BATTLE_TEST("Flower Veil prevents Toxic bad poison on partner - right target") +DOUBLE_BATTLE_TEST("Flower Veil prevents status on allied Grass-types - right target") { u32 move; @@ -39,7 +39,7 @@ DOUBLE_BATTLE_TEST("Flower Veil prevents Toxic bad poison on partner - right tar } } -DOUBLE_BATTLE_TEST("Flower Veil prevents Toxic bad poison on partner - left target") +DOUBLE_BATTLE_TEST("Flower Veil prevents status on allied Grass-types - left target") { u32 move; diff --git a/test/battle/ability/forecast.c b/test/battle/ability/forecast.c index 764c5bd98e..b6889620ab 100644 --- a/test/battle/ability/forecast.c +++ b/test/battle/ability/forecast.c @@ -427,3 +427,22 @@ SINGLE_BATTLE_TEST("Forecast transforms Castform when Cloud Nine ability user le MESSAGE("Castform transformed!"); } } + +DOUBLE_BATTLE_TEST("Forecast reverts Castform back after Teraform Zero clears weather") +{ + GIVEN { + PLAYER(SPECIES_TERAPAGOS_TERASTAL); + PLAYER(SPECIES_CASTFORM) { Ability(ABILITY_FORECAST); } + OPPONENT(SPECIES_KYOGRE) { Ability(ABILITY_DRIZZLE); } + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); } + } SCENE { + ABILITY_POPUP(opponentLeft, ABILITY_DRIZZLE); + ABILITY_POPUP(playerRight, ABILITY_FORECAST); + ABILITY_POPUP(playerLeft, ABILITY_TERAFORM_ZERO); + ABILITY_POPUP(playerRight, ABILITY_FORECAST); + } THEN { + EXPECT_EQ(playerRight->species, SPECIES_CASTFORM_NORMAL); + } +} diff --git a/test/battle/ability/protosynthesis.c b/test/battle/ability/protosynthesis.c index 4dd16f0c45..9bffb125e9 100644 --- a/test/battle/ability/protosynthesis.c +++ b/test/battle/ability/protosynthesis.c @@ -201,3 +201,21 @@ SINGLE_BATTLE_TEST("Protosynthesis doesn't activate if Cloud Nine/Air Lock is on NOT ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); } } + +SINGLE_BATTLE_TEST("Protosynthesis activates after weather was reset") +{ + GIVEN { + PLAYER(SPECIES_WALKING_WAKE) { Ability(ABILITY_PROTOSYNTHESIS); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SUNNY_DAY); } + TURN { MOVE(player, MOVE_RAIN_DANCE); } + TURN { MOVE(player, MOVE_SUNNY_DAY); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + ANIMATION(ANIM_TYPE_MOVE, MOVE_RAIN_DANCE, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SUNNY_DAY, player); + ABILITY_POPUP(player, ABILITY_PROTOSYNTHESIS); + } +} diff --git a/test/battle/ability/shields_down.c b/test/battle/ability/shields_down.c index f9def2991d..554e67b0d8 100644 --- a/test/battle/ability/shields_down.c +++ b/test/battle/ability/shields_down.c @@ -1,28 +1,30 @@ #include "global.h" #include "test/battle.h" -SINGLE_BATTLE_TEST("Minior Meteor transforms into Minior Core on switch-in if it has 1/2 or less health") +SINGLE_BATTLE_TEST("Minior Core doesn't transform into Minior Meteor on switch-in if it has 1/2 or less health") { GIVEN { PLAYER(SPECIES_WYNAUT); OPPONENT(SPECIES_WOBBUFFET) { HP(1); } - OPPONENT(SPECIES_MINIOR_METEOR) { Ability(ABILITY_SHIELDS_DOWN); HP(1); } + OPPONENT(SPECIES_MINIOR_CORE) { Ability(ABILITY_SHIELDS_DOWN); HP(50); MaxHP(100); } } WHEN { TURN { MOVE(player, MOVE_SCRATCH); SEND_OUT(opponent, 1); } } SCENE { - ABILITY_POPUP(opponent, ABILITY_SHIELDS_DOWN); - ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, opponent); + NONE_OF { + ABILITY_POPUP(opponent, ABILITY_SHIELDS_DOWN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, opponent); + } } THEN { EXPECT_EQ(opponent->species, SPECIES_MINIOR_CORE); } } -SINGLE_BATTLE_TEST("Minior Core transforms into Minior Meteor on switch-in if it more then 1/2 health") +SINGLE_BATTLE_TEST("Minior Core transforms into Minior Meteor on switch-in if it has more than 1/2 health") { GIVEN { PLAYER(SPECIES_WYNAUT); OPPONENT(SPECIES_WOBBUFFET) { HP(1); } - OPPONENT(SPECIES_MINIOR_CORE) { Ability(ABILITY_SHIELDS_DOWN); } + OPPONENT(SPECIES_MINIOR_CORE) { Ability(ABILITY_SHIELDS_DOWN); HP(51); MaxHP(101); } } WHEN { TURN { MOVE(player, MOVE_SCRATCH); SEND_OUT(opponent, 1); } } SCENE { @@ -32,3 +34,44 @@ SINGLE_BATTLE_TEST("Minior Core transforms into Minior Meteor on switch-in if it EXPECT_EQ(opponent->species, SPECIES_MINIOR_METEOR); } } + +SINGLE_BATTLE_TEST("Minior Core transforms into Minior Meteor on battle start if it has more than 1/2 health") +{ + GIVEN { + PLAYER(SPECIES_WYNAUT); + OPPONENT(SPECIES_MINIOR_CORE) { Ability(ABILITY_SHIELDS_DOWN); HP(51); MaxHP(101); } + } WHEN { + TURN { } + } SCENE { + ABILITY_POPUP(opponent, ABILITY_SHIELDS_DOWN); + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_FORM_CHANGE, opponent); + } THEN { + EXPECT_EQ(opponent->species, SPECIES_MINIOR_METEOR); + } +} + +SINGLE_BATTLE_TEST("Shields Down protects Minior Meteor from status conditions") +{ + u32 species, hp; + PARAMETRIZE { species = SPECIES_MINIOR_METEOR; hp = 300; } + PARAMETRIZE { species = SPECIES_MINIOR_CORE; hp = 100; } + + GIVEN { + ASSUME(GetMoveEffect(MOVE_WILL_O_WISP) == EFFECT_NON_VOLATILE_STATUS); + ASSUME(GetMoveNonVolatileStatus(MOVE_WILL_O_WISP) == MOVE_EFFECT_BURN); + PLAYER(SPECIES_WYNAUT); + OPPONENT(species) { Ability(ABILITY_SHIELDS_DOWN); HP(hp); MaxHP(300); } + } WHEN { + TURN { MOVE(player, MOVE_WILL_O_WISP); } + } SCENE { + if (species == SPECIES_MINIOR_METEOR) + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_WILL_O_WISP, player); + else + ANIMATION(ANIM_TYPE_MOVE, MOVE_WILL_O_WISP, player); + } THEN { + if (species == SPECIES_MINIOR_METEOR) + EXPECT_EQ(opponent->status1, STATUS1_NONE); + else + EXPECT(opponent->status1 & STATUS1_BURN); + } +} diff --git a/test/battle/ability/toxic_debris.c b/test/battle/ability/toxic_debris.c index 61e128e985..4661849905 100644 --- a/test/battle/ability/toxic_debris.c +++ b/test/battle/ability/toxic_debris.c @@ -120,3 +120,23 @@ SINGLE_BATTLE_TEST("Air Balloon is popped after Toxic Debris activates") MESSAGE("Glimmora's Air Balloon popped!"); } } + +DOUBLE_BATTLE_TEST("Toxic Debris sets Toxic Spikes on the opposing side even when hit by an ally") +{ + struct BattlePokemon *user = NULL; + + PARAMETRIZE{ user = opponentLeft; } + PARAMETRIZE{ user = opponentRight; } + PARAMETRIZE{ user = playerRight; } + GIVEN { + PLAYER(SPECIES_GLIMMORA) { Ability(ABILITY_TOXIC_DEBRIS); } + PLAYER(SPECIES_WYNAUT) { } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT) { } + } WHEN { + TURN { MOVE(user, MOVE_SCRATCH, target: playerLeft); } + } SCENE { + ABILITY_POPUP(playerLeft, ABILITY_TOXIC_DEBRIS); + MESSAGE("Poison spikes were scattered on the ground all around the opposing team!"); + } +} diff --git a/test/battle/badge_boost.c b/test/battle/badge_boost.c new file mode 100644 index 0000000000..eb8cecb240 --- /dev/null +++ b/test/battle/badge_boost.c @@ -0,0 +1,166 @@ +#include "global.h" +#include "event_data.h" +#include "test/battle.h" + +WILD_BATTLE_TEST("Badge boost: B_FLAG_BADGE_BOOST_ATTACK boost Attack", s16 dmg) +{ + u32 badge = 0; + u32 genConfig = 0; + for (u32 gen = GEN_1; gen <= GEN_LATEST; gen++) + { + PARAMETRIZE{badge = FALSE; genConfig = gen;} + PARAMETRIZE{badge = TRUE; genConfig = gen;} + } + GIVEN { + if (badge) + FlagSet(B_FLAG_BADGE_BOOST_ATTACK); + else + FlagClear(B_FLAG_BADGE_BOOST_ATTACK); + WITH_CONFIG(GEN_CONFIG_BADGE_BOOST, genConfig); + PLAYER(SPECIES_WOBBUFFET) {} + OPPONENT(SPECIES_WOBBUFFET) {} + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].dmg); + } FINALLY { + for (u32 gen = GEN_1; gen <= GEN_LATEST; gen++) + { + if (gen <= GEN_3) + EXPECT_GT(results[2 * gen + 1].dmg, results[2 * gen].dmg); + else + EXPECT_EQ(results[2 * gen + 1].dmg, results[2 * gen].dmg); + } + } +} + +WILD_BATTLE_TEST("Badge boost: B_FLAG_BADGE_BOOST_SPATK boost Special Attack", s16 dmg) +{ + u32 badge = 0; + u32 genConfig = 0; + for (u32 gen = GEN_1; gen <= GEN_LATEST; gen++) + { + PARAMETRIZE{badge = FALSE; genConfig = gen;} + PARAMETRIZE{badge = TRUE; genConfig = gen;} + } + GIVEN { + if (badge) + FlagSet(B_FLAG_BADGE_BOOST_SPATK); + else + FlagClear(B_FLAG_BADGE_BOOST_SPATK); + WITH_CONFIG(GEN_CONFIG_BADGE_BOOST, genConfig); + PLAYER(SPECIES_WOBBUFFET) {} + OPPONENT(SPECIES_WOBBUFFET) {} + } WHEN { + TURN { MOVE(player, MOVE_THUNDER_SHOCK); } + } SCENE { + HP_BAR(opponent, captureDamage: &results[i].dmg); + } FINALLY { + for (u32 gen = GEN_1; gen <= GEN_LATEST; gen++) + { + if (gen <= GEN_3) + EXPECT_GT(results[2 * gen + 1].dmg, results[2 * gen].dmg); + else + EXPECT_EQ(results[2 * gen + 1].dmg, results[2 * gen].dmg); + } + } +} + +WILD_BATTLE_TEST("Badge boost: B_FLAG_BADGE_BOOST_DEFENSE boost Defense", s16 dmg) +{ + u32 badge = 0; + u32 genConfig = 0; + for (u32 gen = GEN_1; gen <= GEN_LATEST; gen++) + { + PARAMETRIZE{badge = FALSE; genConfig = gen;} + PARAMETRIZE{badge = TRUE; genConfig = gen;} + } + + GIVEN { + if (badge) + FlagSet(B_FLAG_BADGE_BOOST_DEFENSE); + else + FlagClear(B_FLAG_BADGE_BOOST_DEFENSE); + WITH_CONFIG(GEN_CONFIG_BADGE_BOOST, genConfig); + PLAYER(SPECIES_WOBBUFFET) {} + OPPONENT(SPECIES_WOBBUFFET) {} + } WHEN { + TURN { MOVE(opponent, MOVE_SCRATCH); } + } SCENE { + HP_BAR(player, captureDamage: &results[i].dmg); + } FINALLY { + for (u32 gen = GEN_1; gen <= GEN_LATEST; gen++) + { + if (gen <= GEN_3) + EXPECT_LT(results[2 * gen + 1].dmg, results[2 * gen].dmg); + else + EXPECT_EQ(results[2 * gen + 1].dmg, results[2 * gen].dmg); + } + } +} + +WILD_BATTLE_TEST("Badge boost: B_FLAG_BADGE_BOOST_SPDEF boost Special Defense", s16 dmg) +{ + u32 badge = 0; + u32 genConfig = 0; + for (u32 gen = GEN_1; gen <= GEN_LATEST; gen++) + { + PARAMETRIZE{badge = FALSE; genConfig = gen;} + PARAMETRIZE{badge = TRUE; genConfig = gen;} + } + + GIVEN { + if (badge) + FlagSet(B_FLAG_BADGE_BOOST_SPDEF); + else + FlagClear(B_FLAG_BADGE_BOOST_SPDEF); + WITH_CONFIG(GEN_CONFIG_BADGE_BOOST, genConfig); + PLAYER(SPECIES_WOBBUFFET) {} + OPPONENT(SPECIES_WOBBUFFET) {} + } WHEN { + TURN { MOVE(opponent, MOVE_THUNDER_SHOCK); } + } SCENE { + HP_BAR(player, captureDamage: &results[i].dmg); + } FINALLY { + for (u32 gen = GEN_1; gen <= GEN_LATEST; gen++) + { + if (gen <= GEN_3) + EXPECT_LT(results[2 * gen + 1].dmg, results[2 * gen].dmg); + else + EXPECT_EQ(results[2 * gen + 1].dmg, results[2 * gen].dmg); + } + } +} + +WILD_BATTLE_TEST("Badge boost: B_FLAG_BADGE_BOOST_SPEED boost Speed", s16 dmg) +{ + u32 badge = 0; + u32 genConfig = 0; + for (u32 gen = GEN_1; gen <= GEN_LATEST; gen++) + { + PARAMETRIZE{badge = FALSE; genConfig = gen;} + PARAMETRIZE{badge = TRUE; genConfig = gen;} + } + GIVEN { + if (badge) + FlagSet(B_FLAG_BADGE_BOOST_SPEED); + else + FlagClear(B_FLAG_BADGE_BOOST_SPEED); + WITH_CONFIG(GEN_CONFIG_BADGE_BOOST, genConfig); + PLAYER(SPECIES_WOBBUFFET) { Speed(100); HP(1); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(101); HP(1); } + } WHEN { + TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_SCRATCH);} + } THEN { + if (badge && genConfig <= GEN_3) + { + EXPECT_EQ(opponent->hp, 0); + EXPECT_EQ(player->hp, 1); + } + else + { + EXPECT_EQ(opponent->hp, 1); + EXPECT_EQ(player->hp, 0); + } + } +} diff --git a/test/battle/form_change/mega_evolution.c b/test/battle/form_change/mega_evolution.c index 8cce1a084d..3e27a84608 100644 --- a/test/battle/form_change/mega_evolution.c +++ b/test/battle/form_change/mega_evolution.c @@ -75,10 +75,10 @@ SINGLE_BATTLE_TEST("Mega Evolution doesn't affect turn order (Gen6)") { GIVEN { WITH_CONFIG(GEN_CONFIG_MEGA_EVO_TURN_ORDER, GEN_6); - PLAYER(SPECIES_GARDEVOIR) { Item(ITEM_GARDEVOIRITE); Speed(105); } - OPPONENT(SPECIES_WOBBUFFET) { Speed(106); } + PLAYER(SPECIES_GARDEVOIR) { Item(ITEM_GARDEVOIRITE); } + OPPONENT(SPECIES_WOBBUFFET) {} } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } } SCENE { MESSAGE("The opposing Wobbuffet used Celebrate!"); MESSAGE("Gardevoir used Celebrate!"); @@ -91,10 +91,10 @@ SINGLE_BATTLE_TEST("Mega Evolution affects turn order (Gen7+)") { GIVEN { WITH_CONFIG(GEN_CONFIG_MEGA_EVO_TURN_ORDER, GEN_7); - PLAYER(SPECIES_GARDEVOIR) { Item(ITEM_GARDEVOIRITE); Speed(105); } - OPPONENT(SPECIES_WOBBUFFET) { Speed(106); } + PLAYER(SPECIES_GARDEVOIR) { Item(ITEM_GARDEVOIRITE);} + OPPONENT(SPECIES_WOBBUFFET) {} } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } } SCENE { MESSAGE("Gardevoir used Celebrate!"); MESSAGE("The opposing Wobbuffet used Celebrate!"); @@ -117,7 +117,7 @@ SINGLE_BATTLE_TEST("Abilities replaced by Mega Evolution do not affect turn orde MESSAGE("Sableye used Celebrate!"); MESSAGE("The opposing Wobbuffet used Celebrate!"); } THEN { - ASSUME(player->speed == 45); + ASSUME(player->speed == 105); } } diff --git a/test/battle/form_change/ultra_burst.c b/test/battle/form_change/ultra_burst.c index 9d7ec1c396..4b1bf6a8e3 100644 --- a/test/battle/form_change/ultra_burst.c +++ b/test/battle/form_change/ultra_burst.c @@ -59,10 +59,10 @@ SINGLE_BATTLE_TEST("Ultra Burst affects turn order") { GIVEN { WITH_CONFIG(GEN_CONFIG_MEGA_EVO_TURN_ORDER, GEN_7); - PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Item(ITEM_ULTRANECROZIUM_Z); Speed(105); } - OPPONENT(SPECIES_WOBBUFFET) { Speed(106); } + PLAYER(SPECIES_NECROZMA_DUSK_MANE) { Item(ITEM_ULTRANECROZIUM_Z);} + OPPONENT(SPECIES_WOBBUFFET) {} } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } + TURN { MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_ULTRA_BURST); } } SCENE { MESSAGE("Necrozma used Celebrate!"); MESSAGE("The opposing Wobbuffet used Celebrate!"); diff --git a/test/battle/move_effect/echoed_voice.c b/test/battle/move_effect/echoed_voice.c index 3c36270454..29a2b6e487 100644 --- a/test/battle/move_effect/echoed_voice.c +++ b/test/battle/move_effect/echoed_voice.c @@ -1,7 +1,169 @@ #include "global.h" #include "test/battle.h" -TO_DO_BATTLE_TEST("Echoed Voice's power is multiplied for every consecutive turn used, capped at 5"); -TO_DO_BATTLE_TEST("Echoed Voice's power is reset when using a different move"); -TO_DO_BATTLE_TEST("Echoed Voice's power is increased even if it misses"); -TO_DO_BATTLE_TEST("Echoed Voice's power is increased even if it's blocked by Protect"); +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_ECHOED_VOICE) == EFFECT_ECHOED_VOICE); +} + +SINGLE_BATTLE_TEST("Echoed Voice's power is multiplied for every consecutive turn used, capped at 5") +{ + s16 damage[6]; + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SOFT_BOILED) == EFFECT_SOFTBOILED); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_BLISSEY); + } WHEN { + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); MOVE(opponent, MOVE_SOFT_BOILED); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); MOVE(opponent, MOVE_SOFT_BOILED); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[1]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[2]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[3]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[4]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[5]); + } THEN { + EXPECT_MUL_EQ(damage[0], UQ_4_12(2.0), damage[1]); + EXPECT_MUL_EQ(damage[0], UQ_4_12(3.0), damage[2]); + EXPECT_MUL_EQ(damage[0], UQ_4_12(4.0), damage[3]); + EXPECT_MUL_EQ(damage[0], UQ_4_12(5.0), damage[4]); + EXPECT_EQ(damage[4], damage[5]); + } +} + +SINGLE_BATTLE_TEST("Echoed Voice's power increases even if used by another battler") +{ + s16 damage[2]; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_ECHOED_VOICE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, opponent); + HP_BAR(player, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[0], UQ_4_12(2.0), damage[1]); + } +} + +SINGLE_BATTLE_TEST("Echoed Voice's power does not change until the end of the turn") +{ + s16 damage[3]; + + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ECHOED_VOICE); MOVE(opponent, MOVE_ECHOED_VOICE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, opponent); + HP_BAR(player, captureDamage: &damage[1]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[2]); + } THEN { + EXPECT_EQ(damage[0], damage[1]); + EXPECT_MUL_EQ(damage[0], UQ_4_12(2.0), damage[2]); + } +} + +SINGLE_BATTLE_TEST("Echoed Voice's power increase is reset when no battler uses it successfully during a turn") +{ + s16 damage[3]; + + GIVEN { + ASSUME(MoveHasAdditionalEffect(MOVE_BITE, MOVE_EFFECT_FLINCH)); + PLAYER(SPECIES_WOBBUFFET) { Speed(5); } + OPPONENT(SPECIES_WOBBUFFET) { Speed(10); } + } WHEN { + TURN { MOVE(opponent, MOVE_ECHOED_VOICE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + TURN { MOVE(opponent, MOVE_BITE); MOVE(player, MOVE_ECHOED_VOICE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, opponent); + HP_BAR(player, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[1]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_BITE, opponent); + MESSAGE("Wobbuffet flinched and couldn't move!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[2]); + } THEN { + EXPECT_EQ(damage[0], damage[2]); + EXPECT_NE(damage[1], damage[2]); + } +} + +SINGLE_BATTLE_TEST("Echoed Voice's power is increased even if it misses") +{ + s16 damage[3]; + + GIVEN { + ASSUME(GetMoveEffect(MOVE_SAND_ATTACK) == EFFECT_ACCURACY_DOWN); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); MOVE(opponent, MOVE_SAND_ATTACK); } + TURN { MOVE(player, MOVE_ECHOED_VOICE, hit: FALSE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[1]); + MESSAGE("Wobbuffet's attack missed!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[2]); + } THEN { + EXPECT_MUL_EQ(damage[0], UQ_4_12(2.0), damage[1]); + EXPECT_MUL_EQ(damage[0], UQ_4_12(4.0), damage[2]); + } +} + +SINGLE_BATTLE_TEST("Echoed Voice's power is increased even if it's blocked by Protect") +{ + s16 damage[3]; + + GIVEN { + ASSUME(GetMoveEffect(MOVE_PROTECT) == EFFECT_PROTECT); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); MOVE(opponent, MOVE_PROTECT); } + TURN { MOVE(player, MOVE_ECHOED_VOICE); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[0]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[1]); + ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, opponent); + ANIMATION(ANIM_TYPE_MOVE, MOVE_ECHOED_VOICE, player); + HP_BAR(opponent, captureDamage: &damage[2]); + } THEN { + EXPECT_MUL_EQ(damage[0], UQ_4_12(2.0), damage[1]); + EXPECT_MUL_EQ(damage[0], UQ_4_12(4.0), damage[2]); + } +} diff --git a/test/battle/move_effect/focus_energy.c b/test/battle/move_effect/focus_energy.c index 48924132ef..17a08ba7f3 100644 --- a/test/battle/move_effect/focus_energy.c +++ b/test/battle/move_effect/focus_energy.c @@ -23,7 +23,34 @@ SINGLE_BATTLE_TEST("Focus Energy increases the user's critical hit ratio by 1 st } PASSES_RANDOMLY(1, chance, RNG_CRITICAL_HIT); GIVEN { - WITH_CONFIG(GEN_CONFIG_CRIT_CHANCE, genConfig); + WITH_CONFIG(GEN_CONFIG_CRIT_CHANCE, (genConfig == GEN_1)? GEN_2 : genConfig); + WITH_CONFIG(GEN_CONFIG_FOCUS_ENERGY_CRIT_RATIO, genConfig); + ASSUME(GetSpeciesBaseSpeed(SPECIES_WOBBUFFET) == 33); + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + if (useFocusEnergy) + TURN { MOVE(player, MOVE_FOCUS_ENERGY); } + TURN { MOVE(player, MOVE_SCRATCH); } + } SCENE { + if (useFocusEnergy) + ANIMATION(ANIM_TYPE_MOVE, MOVE_FOCUS_ENERGY, player); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player); + MESSAGE("A critical hit!"); + } +} + +SINGLE_BATTLE_TEST("Focus Energy multiplies crit chance by 4 with gen 1 crit chance") +{ + bool32 useFocusEnergy = 0; + u32 genConfig = 0, chance = 0; + for (u32 j = GEN_1; j <= GEN_9; j++) { + PARAMETRIZE { genConfig = j; useFocusEnergy = FALSE; chance = 16;} + PARAMETRIZE { genConfig = j; useFocusEnergy = TRUE; chance = 4;} + } + PASSES_RANDOMLY(1, chance, RNG_CRITICAL_HIT); + GIVEN { + WITH_CONFIG(GEN_CONFIG_CRIT_CHANCE, GEN_1); WITH_CONFIG(GEN_CONFIG_FOCUS_ENERGY_CRIT_RATIO, genConfig); ASSUME(GetSpeciesBaseSpeed(SPECIES_WOBBUFFET) == 33); PLAYER(SPECIES_WOBBUFFET); diff --git a/test/battle/move_effect/psychic_terrain.c b/test/battle/move_effect/psychic_terrain.c index 40df878a1f..e17964464b 100644 --- a/test/battle/move_effect/psychic_terrain.c +++ b/test/battle/move_effect/psychic_terrain.c @@ -114,6 +114,38 @@ SINGLE_BATTLE_TEST("Psychic Terrain doesn't block priority field moves") } } +SINGLE_BATTLE_TEST("Psychic Terrain doesn't block priority moves against semi-invulnerable targets") +{ + u32 move = 0, shouldWork = 0; + PARAMETRIZE { move = MOVE_SOLAR_BEAM; shouldWork = FALSE;} + PARAMETRIZE { move = MOVE_FLY; shouldWork = TRUE;} + GIVEN { + PLAYER(SPECIES_SHROODLE) { Ability(ABILITY_PRANKSTER); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_PSYCHIC_TERRAIN); MOVE(opponent,move);} + TURN { MOVE(player, MOVE_TOXIC); SKIP_TURN(opponent);} + } SCENE { + if (shouldWork) + { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + } + else + { + NONE_OF { + ANIMATION(ANIM_TYPE_MOVE, MOVE_TOXIC, player); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_PSN, opponent); + } + } + } THEN { + if (shouldWork) + EXPECT(opponent->status1 & STATUS1_TOXIC_POISON); + else + EXPECT(!(opponent->status1 & STATUS1_TOXIC_POISON)); + } +} + SINGLE_BATTLE_TEST("Psychic Terrain lasts for 5 turns") { GIVEN { diff --git a/test/battle/move_effect/rest.c b/test/battle/move_effect/rest.c index 4d0d85b560..fcf2b15567 100644 --- a/test/battle/move_effect/rest.c +++ b/test/battle/move_effect/rest.c @@ -1,4 +1,121 @@ #include "global.h" #include "test/battle.h" +ASSUMPTIONS +{ + ASSUME(GetMoveEffect(MOVE_REST) == EFFECT_REST); +} + +SINGLE_BATTLE_TEST("Rest causes the user to fall asleep and restores HP to full") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(1); MaxHP(300); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_REST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, player); + } THEN { + EXPECT(player->status1 & STATUS1_SLEEP); + EXPECT_EQ(player->hp, player->maxHP); + } +} + +SINGLE_BATTLE_TEST("Rest fails if the user is at full HP") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) { HP(300); MaxHP(300); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_REST); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, player); + } THEN { + EXPECT(!(player->status1 & STATUS1_SLEEP)); + } +} + +SINGLE_BATTLE_TEST("Rest fails if the user is protected by Leaf Guard") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SUNNY_DAY) == EFFECT_SUNNY_DAY); + ASSUME(B_LEAF_GUARD_PREVENTS_REST >= GEN_5); + PLAYER(SPECIES_CHIKORITA) { Ability(ABILITY_LEAF_GUARD); HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, MOVE_SUNNY_DAY); MOVE(player, MOVE_REST); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, player); + } THEN { + EXPECT(!(player->status1 & STATUS1_SLEEP)); + } +} + +SINGLE_BATTLE_TEST("Rest fails if the user is protected by Shields Down") +{ + GIVEN { + PLAYER(SPECIES_MINIOR_METEOR) { Ability(ABILITY_SHIELDS_DOWN); HP(299); MaxHP(300); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_REST); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, player); + } THEN { + EXPECT(!(player->status1 & STATUS1_SLEEP)); + } +} + +SINGLE_BATTLE_TEST("Rest fails if the user is protected by Electric/Misty Terrain") +{ + u32 move; + PARAMETRIZE { move = MOVE_ELECTRIC_TERRAIN; } + PARAMETRIZE { move = MOVE_MISTY_TERRAIN; } + GIVEN { + ASSUME(GetMoveEffect(MOVE_ELECTRIC_TERRAIN) == EFFECT_ELECTRIC_TERRAIN); + ASSUME(GetMoveEffect(MOVE_MISTY_TERRAIN) == EFFECT_MISTY_TERRAIN); + ASSUME(GetSpeciesType(SPECIES_WYNAUT, 0) != TYPE_FLYING && GetSpeciesType(SPECIES_WYNAUT, 1) != TYPE_FLYING); + PLAYER(SPECIES_WYNAUT) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(opponent, move); MOVE(player, MOVE_REST); } + } SCENE { + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, player); + } THEN { + EXPECT(!(player->status1 & STATUS1_SLEEP)); + } +} + +SINGLE_BATTLE_TEST("Rest doesn't fail if the user is protected by Safeguard") +{ + GIVEN { + ASSUME(GetMoveEffect(MOVE_SAFEGUARD) == EFFECT_SAFEGUARD); + PLAYER(SPECIES_WOBBUFFET) { HP(1); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SAFEGUARD); } + TURN { MOVE(player, MOVE_REST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, player); + } THEN { + EXPECT(player->status1 & STATUS1_SLEEP); + } +} + +DOUBLE_BATTLE_TEST("Rest doesn't fail if the user is protected by Flower Veil") +{ + GIVEN { + ASSUME(GetSpeciesType(SPECIES_CHIKORITA, 0) == TYPE_GRASS || GetSpeciesType(SPECIES_CHIKORITA, 1) == TYPE_GRASS); + PLAYER(SPECIES_CHIKORITA) { HP(1); } + PLAYER(SPECIES_FLORGES) { Ability(ABILITY_FLOWER_VEIL); } + OPPONENT(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WYNAUT); + } WHEN { + TURN { MOVE(playerLeft, MOVE_REST); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_REST, playerLeft); + } THEN { + EXPECT(playerLeft->status1 & STATUS1_SLEEP); + } +} + TO_DO_BATTLE_TEST("TODO: Write Rest (Move Effect) test titles") diff --git a/test/battle/move_effect/stomping_tantrum.c b/test/battle/move_effect/stomping_tantrum.c index 95d93e07e6..06b29334d7 100644 --- a/test/battle/move_effect/stomping_tantrum.c +++ b/test/battle/move_effect/stomping_tantrum.c @@ -90,6 +90,31 @@ SINGLE_BATTLE_TEST("Stomping Tantrum will not deal double damage if target prote } } +SINGLE_BATTLE_TEST("Stomping Tantrum will deal double damage if user failed a Protect") +{ + s16 damage[2]; + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_STOMPING_TANTRUM); } + TURN { MOVE(player, MOVE_PROTECT); } + TURN { MOVE(player, MOVE_PROTECT, WITH_RNG(RNG_PROTECT_FAIL, USHRT_MAX)); } + TURN { MOVE(player, MOVE_STOMPING_TANTRUM); } + } SCENE { + ANIMATION(ANIM_TYPE_MOVE, MOVE_STOMPING_TANTRUM, player); + HP_BAR(opponent, captureDamage: &damage[0]); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, player); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_PROTECT, player); + + ANIMATION(ANIM_TYPE_MOVE, MOVE_STOMPING_TANTRUM, player); + HP_BAR(opponent, captureDamage: &damage[1]); + } THEN { + EXPECT_MUL_EQ(damage[0], Q_4_12(2.0), damage[1]); + } +} + SINGLE_BATTLE_TEST("Stomping Tantrum will not deal double if it missed") { s16 damage[2]; diff --git a/test/battle/test_runner_features.c b/test/battle/test_runner_features.c index bb4840799f..2c6d73abcc 100644 --- a/test/battle/test_runner_features.c +++ b/test/battle/test_runner_features.c @@ -25,3 +25,58 @@ SINGLE_BATTLE_TEST("Forced abilities activate on switch-in") MESSAGE("Kadabra's Sp. Atk was heightened!"); } } + +SINGLE_BATTLE_TEST("Setting level doesn't overwrite set stats") +{ + u32 level = 0; + + PARAMETRIZE{level = 1;} + PARAMETRIZE{level = 10;} + PARAMETRIZE{level = 50;} + PARAMETRIZE{level = 99;} + GIVEN { + PLAYER(SPECIES_WOBBUFFET) {HP(5); MaxHP(10); Attack(10); Defense(10); Speed(10); SpAttack(10); SpDefense(10); Level(level); }; + OPPONENT(SPECIES_WOBBUFFET) {Speed(1);} + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE);} + } THEN { + EXPECT_EQ(player->hp, 5); + EXPECT_EQ(player->maxHP, 10); + EXPECT_EQ(player->attack, 10); + EXPECT_EQ(player->defense, 10); + EXPECT_EQ(player->speed, 10); + EXPECT_EQ(player->spAttack, 10); + EXPECT_EQ(player->spDefense, 10); + } +} + +SINGLE_BATTLE_TEST("Changing forms doesn't overwrite set stats (not HP)") +{ + GIVEN { + PLAYER(SPECIES_DIANCIE) {Attack(10); Defense(10); Speed(10); SpAttack(10); SpDefense(10); Item(ITEM_DIANCITE);} + OPPONENT(SPECIES_WOBBUFFET) {Speed(1);} + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_MEGA); } + } SCENE { + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_MEGA_EVOLUTION, player); + } THEN { + EXPECT_EQ(player->attack, 10); + EXPECT_EQ(player->defense, 10); + EXPECT_EQ(player->speed, 10); + EXPECT_EQ(player->spAttack, 10); + EXPECT_EQ(player->spDefense, 10); + } +} + +SINGLE_BATTLE_TEST("Changing forms doesn't overwrite set stats (HP)") +{ + GIVEN { + PLAYER(SPECIES_TERAPAGOS) {HP(5); MaxHP(10); TeraType(TYPE_STELLAR);} + OPPONENT(SPECIES_WOBBUFFET) {} + } WHEN { + TURN { MOVE(player, MOVE_CELEBRATE, gimmick: GIMMICK_TERA); MOVE(opponent, MOVE_CELEBRATE);} + } THEN { + EXPECT_EQ(player->hp, 5); + EXPECT_EQ(player->maxHP, 10); + } +} diff --git a/test/pokemon.c b/test/pokemon.c index 6079bd28bd..d6c0cb4d42 100644 --- a/test/pokemon.c +++ b/test/pokemon.c @@ -477,6 +477,24 @@ TEST("Optimised SetMonData") EXPECT_FASTER(optimised, vanilla); } +//Sanity check for a CalculateMonStats refactor (could be deleted or improved) +TEST("CalculateMonStats") +{ + ZeroPlayerPartyMons(); + + RUN_OVERWORLD_SCRIPT( + givemon SPECIES_WOBBUFFET, 100, item=ITEM_LEFTOVERS, ball=ITEM_MASTER_BALL, nature=NATURE_BOLD, abilityNum=2, gender=MON_MALE, hpEv=1, atkEv=2, defEv=3, speedEv=4, spAtkEv=5, spDefEv=6, hpIv=7, atkIv=8, defIv=9, speedIv=10, spAtkIv=11, spDefIv=12, move1=MOVE_SCRATCH, move2=MOVE_SPLASH, move3=MOVE_CELEBRATE, move4=MOVE_EXPLOSION, shinyMode=SHINY_MODE_ALWAYS, gmaxFactor=TRUE, teraType=TYPE_FIRE, dmaxLevel=7; + ); + + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_MAX_HP), 497); + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_ATK), 71); + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_DEF), 143); + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPEED), 82); + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPATK), 83); + EXPECT_EQ(GetMonData(&gPlayerParty[0], MON_DATA_SPDEF), 134); + +} + TEST("BoxPokemon encryption works") { u32 raw[20] = diff --git a/test/save.c b/test/save.c new file mode 100644 index 0000000000..4e3000a794 --- /dev/null +++ b/test/save.c @@ -0,0 +1,35 @@ +#include "global.h" +#include "pokemon_storage_system.h" +#include "test/test.h" + +// If you would like to ensure save compatibility, update the values below with those for your hack. You can find these through the debug menu. +// Please note that this simple check is not 100% foolproof, but should be able to catch most unintended shifts. +#define T_SAVEBLOCK1_SIZE 15568 +#define T_SAVEBLOCK2_SIZE 3884 +#define T_SAVEBLOCK3_SIZE 4 +#define T_POKEMONSTORAGE_SIZE 34144 + +TEST("SaveBlock1 is backwards compatible") +{ + EXPECT_EQ(sizeof(struct SaveBlock1), T_SAVEBLOCK1_SIZE); +} + +TEST("SaveBlock2 is backwards compatible") +{ + EXPECT_EQ(sizeof(struct SaveBlock2), T_SAVEBLOCK2_SIZE); +} + +TEST("SaveBlock3 is backwards compatible") +{ + EXPECT_EQ(sizeof(struct SaveBlock3), T_SAVEBLOCK3_SIZE); +} + +TEST("PokemonStorage is backwards compatible") +{ + EXPECT_EQ(sizeof(struct PokemonStorage), T_POKEMONSTORAGE_SIZE); +} + +#undef T_SAVEBLOCK1_SIZE +#undef T_SAVEBLOCK2_SIZE +#undef T_SAVEBLOCK3_SIZE +#undef T_POKEMONSTORAGE_SIZE diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index f51912d4f0..58acd022d5 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -1969,7 +1969,9 @@ void Level_(u32 sourceLine, u32 level) INVALID_IF(level == 0 || level > MAX_LEVEL, "Illegal level: %d", level); SetMonData(DATA.currentMon, MON_DATA_LEVEL, &level); SetMonData(DATA.currentMon, MON_DATA_EXP, &gExperienceTables[gSpeciesInfo[species].growthRate][level]); + gMain.inBattle = TRUE; CalculateMonStats(DATA.currentMon); + gMain.inBattle = FALSE; } void MaxHP_(u32 sourceLine, u32 maxHP) @@ -1977,6 +1979,8 @@ void MaxHP_(u32 sourceLine, u32 maxHP) INVALID_IF(!DATA.currentMon, "MaxHP outside of PLAYER/OPPONENT"); INVALID_IF(maxHP == 0, "Illegal max HP: %d", maxHP); SetMonData(DATA.currentMon, MON_DATA_MAX_HP, &maxHP); + bool32 hyperTrainingFlag = TRUE; + SetMonData(DATA.currentMon, MON_DATA_HYPER_TRAINED_HP, &hyperTrainingFlag); } void HP_(u32 sourceLine, u32 hp) @@ -1992,6 +1996,8 @@ void Attack_(u32 sourceLine, u32 attack) INVALID_IF(!DATA.currentMon, "Attack outside of PLAYER/OPPONENT"); INVALID_IF(attack == 0, "Illegal attack: %d", attack); SetMonData(DATA.currentMon, MON_DATA_ATK, &attack); + bool32 hyperTrainingFlag = TRUE; + SetMonData(DATA.currentMon, MON_DATA_HYPER_TRAINED_ATK, &hyperTrainingFlag); } void Defense_(u32 sourceLine, u32 defense) @@ -1999,6 +2005,8 @@ void Defense_(u32 sourceLine, u32 defense) INVALID_IF(!DATA.currentMon, "Defense outside of PLAYER/OPPONENT"); INVALID_IF(defense == 0, "Illegal defense: %d", defense); SetMonData(DATA.currentMon, MON_DATA_DEF, &defense); + bool32 hyperTrainingFlag = TRUE; + SetMonData(DATA.currentMon, MON_DATA_HYPER_TRAINED_DEF, &hyperTrainingFlag); } void SpAttack_(u32 sourceLine, u32 spAttack) @@ -2006,6 +2014,8 @@ void SpAttack_(u32 sourceLine, u32 spAttack) INVALID_IF(!DATA.currentMon, "SpAttack outside of PLAYER/OPPONENT"); INVALID_IF(spAttack == 0, "Illegal special attack: %d", spAttack); SetMonData(DATA.currentMon, MON_DATA_SPATK, &spAttack); + bool32 hyperTrainingFlag = TRUE; + SetMonData(DATA.currentMon, MON_DATA_HYPER_TRAINED_SPATK, &hyperTrainingFlag); } void SpDefense_(u32 sourceLine, u32 spDefense) @@ -2013,6 +2023,8 @@ void SpDefense_(u32 sourceLine, u32 spDefense) INVALID_IF(!DATA.currentMon, "SpDefense outside of PLAYER/OPPONENT"); INVALID_IF(spDefense == 0, "Illegal special defense: %d", spDefense); SetMonData(DATA.currentMon, MON_DATA_SPDEF, &spDefense); + bool32 hyperTrainingFlag = TRUE; + SetMonData(DATA.currentMon, MON_DATA_HYPER_TRAINED_SPDEF, &hyperTrainingFlag); } void Speed_(u32 sourceLine, u32 speed) @@ -2020,6 +2032,8 @@ void Speed_(u32 sourceLine, u32 speed) INVALID_IF(!DATA.currentMon, "Speed outside of PLAYER/OPPONENT"); INVALID_IF(speed == 0, "Illegal speed: %d", speed); SetMonData(DATA.currentMon, MON_DATA_SPEED, &speed); + bool32 hyperTrainingFlag = TRUE; + SetMonData(DATA.currentMon, MON_DATA_HYPER_TRAINED_SPEED, &hyperTrainingFlag); DATA.hasExplicitSpeeds = TRUE; DATA.explicitSpeeds[DATA.currentPosition] |= 1 << DATA.currentPartyIndex; }