master to upcoming, 2025-04-09 (#6563)

Co-authored-by: AlexOn1ine <alexthenotes@gmail.com>
Co-authored-by: Isaac Aronson <i@pingas.org>
Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com>
Co-authored-by: Hedara <hedara90@gmail.com>
Co-authored-by: PhallenTree <168426989+PhallenTree@users.noreply.github.com>
Co-authored-by: Eduardo Quezada <eduardo602002@gmail.com>
This commit is contained in:
Alex 2025-04-09 12:16:52 +02:00 committed by GitHub
commit 73d2b5d940
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 528 additions and 3397 deletions

View File

@ -15,6 +15,7 @@ jobs:
count: 1
labels: |
General
General Merge
category: ability
category: battle-ai
category: battle-mechanic

View File

@ -162,8 +162,16 @@ MAPJSON := $(TOOLS_DIR)/mapjson/mapjson$(EXE)
JSONPROC := $(TOOLS_DIR)/jsonproc/jsonproc$(EXE)
TRAINERPROC := $(TOOLS_DIR)/trainerproc/trainerproc$(EXE)
PATCHELF := $(TOOLS_DIR)/patchelf/patchelf$(EXE)
ROMTEST ?= $(shell { command -v mgba-rom-test || command -v $(TOOLS_DIR)/mgba/mgba-rom-test$(EXE); } 2>/dev/null)
ROMTESTHYDRA := $(TOOLS_DIR)/mgba-rom-test-hydra/mgba-rom-test-hydra$(EXE)
ifeq ($(shell uname),Darwin)
ROMTEST ?= $(shell command -v mgba-rom-test-mac 2>/dev/null || echo $(TOOLS_DIR)/mgba/mgba-rom-test-mac)
ROMTESTHYDRA := $(shell command -v mgba-rom-test-hydra 2>/dev/null || echo $(TOOLS_DIR)/mgba-rom-test-hydra/mgba-rom-test-hydra)
else ifeq ($(shell uname),Linux)
ROMTEST ?= $(shell command -v mgba-rom-test 2>/dev/null || echo $(TOOLS_DIR)/mgba/mgba-rom-test)
ROMTESTHYDRA := $(shell command -v mgba-rom-test-hydra 2>/dev/null || echo $(TOOLS_DIR)/mgba-rom-test-hydra/mgba-rom-test-hydra)
else
ROMTEST ?= $(TOOLS_DIR)/mgba/mgba-rom-test$(EXE)
ROMTESTHYDRA := $(TOOLS_DIR)/mgba-rom-test-hydra/mgba-rom-test-hydra$(EXE)
endif
# Learnset helper is a Python script
LEARNSET_HELPERS_DIR := $(TOOLS_DIR)/learnset_helpers

View File

@ -1743,10 +1743,6 @@
.byte \battler
.endm
.macro hitswitchtargetfailed
callnative BS_HitSwitchTargetFailed
.endm
.macro tryrevivalblessing, failInstr:req
callnative BS_TryRevivalBlessing
.4byte \failInstr

View File

@ -1824,7 +1824,6 @@ BattleScript_HitSwitchTargetDynamaxed::
printstring STRINGID_MOVEBLOCKEDBYDYNAMAX
waitmessage B_WAIT_TIME_LONG
BattleScript_HitSwitchTargetForceRandomSwitchFailed:
hitswitchtargetfailed
setbyte sSWITCH_CASE, B_SWITCH_NORMAL
return
@ -2222,13 +2221,13 @@ BattleScript_AttackAccUpDoMoveAnim::
attackanimation
waitanimation
setbyte sSTAT_ANIM_PLAYED, FALSE
playstatchangeanimation BS_ATTACKER, BIT_SPATK | BIT_SPDEF, 0
playstatchangeanimation BS_ATTACKER, BIT_ATK | BIT_ACC, 0
setstatchanger STAT_ATK, 1, FALSE
statbuffchange MOVE_EFFECT_AFFECTS_USER | STAT_CHANGE_ALLOW_PTR, BattleScript_AttackAccUpTrySpDef
jumpifbyte CMP_EQUAL, cMULTISTRING_CHOOSER, B_MSG_STAT_WONT_INCREASE, BattleScript_AttackAccUpTrySpDef
statbuffchange MOVE_EFFECT_AFFECTS_USER | STAT_CHANGE_ALLOW_PTR, BattleScript_AttackAccUpTryAcc
jumpifbyte CMP_EQUAL, cMULTISTRING_CHOOSER, B_MSG_STAT_WONT_INCREASE, BattleScript_AttackAccUpTryAcc
printfromtable gStatUpStringIds
waitmessage B_WAIT_TIME_LONG
BattleScript_AttackAccUpTrySpDef::
BattleScript_AttackAccUpTryAcc::
setstatchanger STAT_ACC, 1, FALSE
statbuffchange MOVE_EFFECT_AFFECTS_USER | STAT_CHANGE_ALLOW_PTR, BattleScript_AttackAccUpEnd
jumpifbyte CMP_EQUAL, cMULTISTRING_CHOOSER, B_MSG_STAT_WONT_INCREASE, BattleScript_AttackAccUpEnd
@ -4731,7 +4730,7 @@ BattleScript_MoveEffectStockpileWoreOff::
return
BattleScript_StockpileStatChangeDown:
statbuffchange MOVE_EFFECT_AFFECTS_USER, BattleScript_StockpileStatChangeDown_Ret
statbuffchange MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_CERTAIN, BattleScript_StockpileStatChangeDown_Ret
setgraphicalstatchangevalues
playanimation BS_ATTACKER, B_ANIM_STATS_CHANGE, sB_ANIM_ARG1
printfromtable gStatDownStringIds
@ -4833,6 +4832,7 @@ BattleScript_EffectWillOWisp::
jumpifability BS_TARGET, ABILITY_WATER_BUBBLE, BattleScript_WaterVeilPrevents
jumpifability BS_TARGET, ABILITY_COMATOSE, BattleScript_AbilityProtectsDoesntAffect
jumpifability BS_TARGET, ABILITY_PURIFYING_SALT, BattleScript_AbilityProtectsDoesntAffect
jumpifability BS_TARGET, ABILITY_THERMAL_EXCHANGE, BattleScript_AbilityProtectsDoesntAffect
jumpifflowerveil BattleScript_FlowerVeilProtects
jumpifleafguardprotected BS_TARGET, BattleScript_AbilityProtectsDoesntAffect
jumpifshieldsdown BS_TARGET, BattleScript_AbilityProtectsDoesntAffect

View File

@ -13,12 +13,8 @@
- [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 Pokémon]()
- [v1.10.x](tutorials/how_to_new_pokemon_1_10_0.md)
- [v1.9.x](tutorials/how_to_new_pokemon_1_9_0.md)
- [v1.8.x](tutorials/how_to_new_pokemon_1_8_0.md)
- [v1.7.x](tutorials/how_to_new_pokemon_1_7_0.md)
- [v1.6.x](tutorials/how_to_new_pokemon_1_6_0.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)
- [Changelog](./CHANGELOG.md)

View File

@ -10,6 +10,18 @@
- If devkitARM is **not installed**, then go to [Installing devkitARM (macOS)](#installing-devkitarm-macos).
- Otherwise, **open the Terminal** and go to [Choosing where to store pokeemerald-expansion (macOS)](#choosing-where-to-store-pokeemerald-expansion-macos)
3. **Optional: To run tests**, if the homebrew environment is not installed, install the package manager using [this reference](https://brew.sh). Open your terminal and run the following commands:
```bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install coreutils
```
4. **Optional: To run tests via Rosetta**
- You probably don't want to do this as it's much slower. Most users can use native tools, but some may have other reasons to use this setup such as working with Intel-only custom tooling.
- You will need an Intel-compatible homebrew installation. Understanding how to get one can be found [here](https://github.com/Homebrew/brew/issues/9173#issuecomment-729206868).
- Install `coreutils` like in step 3, but using your Intel-compatible installation of homebrew.
### Installing libpng (macOS)
<details>
<summary><i>Note for advanced users...</i></summary>

View File

@ -1,14 +1,10 @@
<!--1.10.x-->
This is a modified version of [the original tutorial about adding new Pokémon species available in Pokeemerald's wiki](https://github.com/pret/pokeemerald/wiki/How-to-add-a-new-Pokémon-species).
Despite the persistent rumors about an incredibly strong third form of Mew hiding somewhere, it actually wasn't possible to catch it... OR WAS IT?
In this tutorial, we will add a new Pokémon species to the game.
## IMPORTANT: This tutorial applies to 1.10.x versions.
- [Version 1.9.x](how_to_new_pokemon_1_9_0.md)
- [Version 1.8.x](how_to_new_pokemon_1_8_0.md)
- [Version 1.7.x](how_to_new_pokemon_1_7_0.md)
- [Version 1.6.x](how_to_new_pokemon_1_6_0.md)
## IMPORTANT: This tutorial applies to 1.7.x versions onward.
- [v1.6.x and earlier](how_to_new_pokemon_1_6_0.md)
# Changes compared to vanilla
The main things that the Expansion changes are listed here.
@ -40,13 +36,13 @@ The main things that the Expansion changes are listed here.
* [1. Form tables](#1-form-tables)
* [2. Form change tables](#2-form-change-tables)
* [3. Gender differences](#3-gender-differences)
* [4. Overworld Data](#4-overworld-data)
* [4. Overworld Data (v1.9 onwards)](#4-overworld-data-v19-onwards)
* [5. In-battle shadows (v1.10 onwards)](#5-in-battle-shadows-v110-onwards)
# Useful resources
You can open a sprite debug menu by pressing `Select` in a Pokémon's summary screen outside of battle.
![mGBA_6WOo1TSlsn](https://github.com/rh-hideout/pokeemerald-expansion/assets/2904965/0c625cd8-8f89-4bc8-a285-b10a420a8f6d)
![visualizer1](/docs/tutorials/img/add_pokemon/visualizer1.gif)
# The Data - Part 1
@ -77,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!
![image](https://github.com/rh-hideout/pokeemerald-expansion/assets/2904965/dc15b0ba-a4bd-4f4e-9658-2dff73a11f79)
![visualizer2](/docs/tutorials/img/add_pokemon/visualizer2.png)
Hmmm, something's not right...
@ -87,7 +83,7 @@ Now, let's see what needs to be done.
## 2. `SpeciesInfo`'s structure
Now, to better understand Mewthree, we also need to understand Mew. Let's look at its data.
```diff
```c
[SPECIES_MEW] =
{
.baseHP = 100,
@ -133,7 +129,11 @@ Now, to better understand Mewthree, we also need to understand Mew. Let's look a
.frontPic = gMonFrontPic_Mew,
.frontPicSize = P_GBA_STYLE_SPECIES_GFX ? MON_COORDS_SIZE(40, 40) : MON_COORDS_SIZE(64, 48),
.frontPicYOffset = P_GBA_STYLE_SPECIES_GFX ? 13 : 9,
.frontAnimFrames = sAnims_Mew,
.frontAnimFrames = ANIM_FRAMES(
ANIMCMD_FRAME(1, 50),
ANIMCMD_FRAME(1, 40),
ANIMCMD_FRAME(0, 10),
),
.frontAnimId = P_GBA_STYLE_SPECIES_GFX ? ANIM_SWING_CONVEX : ANIM_ZIGZAG_SLOW,
.enemyMonElevation = P_GBA_STYLE_SPECIES_GFX ? 8 : 11,
.backPic = gMonBackPic_Mew,
@ -151,6 +151,7 @@ Now, to better understand Mewthree, we also need to understand Mew. Let's look a
SIZE_32x32,
SHADOW_SIZE_M,
TRACKS_NONE,
sAnimTable_Following,
gOverworldPalette_Mew,
gShinyOverworldPalette_Mew
)
@ -165,6 +166,10 @@ Now, to better understand Mewthree, we also need to understand Mew. Let's look a
That's a lot of stuff! But don't worry, we'll go through it step by step throughout the tutorial
(and it's miles better than having this same data through 20+ files like it used to be).
Across the species files you'll see preprocessor instructions such as `#if/endif P_FAMILY_MEW`. These are used by expansion in order to allow users to disable species via config. Since we're making a new species from scratch, you DON'T need to add them as part of the process.
You can also ignore switch cases for `P_GBA_STYLE_SPECIES_GFX`, as those are only used to switching to GBA-styled sprites.
We'll start by adding the self-explanatory data that's also present in pret's vanilla structure:
## 3. Define its basic species information
@ -215,6 +220,13 @@ The `.` is the structure reference operator in C to refer to the member object o
- `baseHP`, `baseAttack`, `baseDefense`, `baseSpeed`, `baseSpAttack` and `baseSpDefense` are the base stats. They can't go higher than 255.
- `types` is using the macro `MON_TYPES` as a helper function for formatting so that only one type has to be input for species with a single type.
- To add a species with 2 types, use the format `MON_TYPES(TYPE_PSYCHIC, TYPE_NORMAL)`.
- ***1.9 and earlier:*** The format for setting types is the following:
```c
// Mono-type
.types = { TYPE_PSYCHIC, TYPE_PSYCHIC },
// Dual-type
.types = { TYPE_PSYCHIC, TYPE_DARK },
```
- `catchRate` is how likely it is to catch a Pokémon, the lower the value, the harder it is to catch. Legendaries generally have a catch rate of 3, so we put that here.
- `expYield` is the base amount of experience that a Pokémon gives when defeated/caught. In vanilla, this value caps at 255, but we've increased it to a maximum of 65535 accomodate later gen's higher experience yields. (The highest official value is Blissey's with 608, so going beyond this point may cause exponential gains that could break the system 😱)
- If you noticed, Mew's had some `#if`s, `#elif`s and `#endif` around it. This is because its yield has changed over time, and we let you choose which ones you want. This is not relevant to our Mewthree however, so we can just put a single `.expYield = 255,` line here.
@ -237,6 +249,13 @@ The `.` is the structure reference operator in C to refer to the member object o
- This should be consistent across evolution lines, otherwise levels could change upon evolution.
- `eggGroups` are used for breed compatibility. Most Legendaries and Mythicals have the `EGG_GROUP_NO_EGGS_DISCOVERED` group, and so does our Mewthree. Go [here](https://bulbapedia.bulbagarden.net/wiki/Egg_Group) for more info.
- This is using the helper macro `MON_EGG_GROUPS`.
- ***1.9 and earlier:*** The format for setting egg groups is the following:
```c
// Mono-group
.eggGroups = { EGG_GROUP_MONSTER, EGG_GROUP_MONSTER },
// Dual-group
.eggGroups = { EGG_GROUP_MONSTER, EGG_GROUP_MINERAL },
```
- `abilities` determines the potential abilites of our species. Notice how I also set the ability to `ABILITY_INSOMNIA`, so our little monster doesn't even need to sleep anymore. You can find the abilities for example here [include/constants/abilities.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/include/constants/abilities.h).
- When both slot 1 and 2 are defined as not being `ABILITY_NONE`, their starting ability will be decided on a coin flip using their personality. They can later be changed using an Ability Capsule.
- Certain Pokémon such as Zygarde and Rockruff have different forms to add additional abilities. As such, they cannot be changed using an Ability Capsule (though the Zygarde Cube can change Zygarde's ability by changing them to their corresponding form)
@ -424,7 +443,7 @@ Now we can add the number and entry to our Mewthree:
},
};
```
![image](https://github.com/rh-hideout/pokeemerald-expansion/assets/2904965/3759dd4c-8da5-4b1c-9a50-b9e9d0815e7f)
![image](/docs/tutorials/img/add_pokemon/dex1.png)
The values `pokemonScale`, `pokemonOffset`, `trainerScale` and `trainerOffset` are used for the height comparison figure in the Pokédex.
@ -475,7 +494,7 @@ Edit [src/data/pokemon/pokedex_orders.h](https://github.com/rh-hideout/pokeemera
...
};
```
![mGBA_lUBfmFEKUx](https://github.com/rh-hideout/pokeemerald-expansion/assets/2904965/3a8b8a17-759b-486b-9831-deb2f494bd71)
![mGBA_lUBfmFEKUx](/docs/tutorials/img/add_pokemon/dex2.gif)
# The Graphics
@ -539,6 +558,10 @@ Please note that Pecharunt, the Pokémon that should be above your insertion for
You can define the animation order, in which the sprites will be shown. The first number is the sprite index (so 0 or 1) and the second number is the number of frames the sprite will be visible.
### Version 1.11.0 or later
We add this data directly to the entry, so go to section 4.
### Version 1.10.3 or earlier
Edit [src/data/pokemon_graphics/front_pic_anims.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/pokemon_graphics/front_pic_anims.h):
```diff
@ -597,7 +620,9 @@ Now that we have all the external data ready, we just need to add it to `gSpecie
+ .frontPic = gMonFrontPic_Mewthree,
+ .frontPicSize = MON_COORDS_SIZE(64, 64),
+ .frontPicYOffset = 0,
+ .frontAnimFrames = sAnims_Mewthree,
+ .frontAnimFrames = ANIM_FRAMES(
+ ANIMCMD_FRAME(0, 1),
+ ),
+ .frontAnimId = ANIM_GROW_VIBRATE,
+ .frontAnimDelay = 15,
+ .enemyMonElevation = 6,
@ -621,7 +646,10 @@ Let's explain each of these:
- `frontPicYOffset`:
- Used to define what Y position the sprite sits at. This is used to set where they'd be "grounded". For the shadow, see `enemyMonElevation`.
- `frontAnimFrames`:
- We link our animation frame animations that we defined earlier here.
- We define our animation frame animations directly here. In version `1.10.3 and earlier`, we add the reference to the table that we defined earlier here like this instead:
```diff
+ .frontAnimFrames = sAnims_Mewthree,
```
- `frontAnimId`:
- Because you are limited to two frames, there are already [predefined front sprite animations](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/include/pokemon_animation.h), describing translations, rotations, scalings or color changes.
- `frontAnimDelay`:
@ -655,6 +683,12 @@ Let's explain each of these:
#endif
```
***NOTE:** In v1.7.x only, there were macros that set multiple of these fields. However, they were considered clunky to use, so they were removed in v1.8.
- `FRONT_PIC`: For `frontPic` and `frontPicSize`.
- `BACK_PIC`: For `backPic` and `backPicSize`.
- `PALETTES`: For `palette` and `shinyPalette`.
- `ICON`: For `iconSprite` and `iconPalIndex`.
# The Data - Part 2
We're almost there just a bit left!
@ -676,19 +710,26 @@ We're almost there just a bit left!
};
```
Each species flag provides properties to the species:
- `perfectIVCount` ***(1.10 onwards)***:
- Guarantees that the number of IVs specified here will be perfect.
- `isLegendary`:
- Does nothing.
- ***1.10 onwards:*** Does nothing.
- ***1.9 and earlier:*** Guaranteed 3 perfect IVs for the species.
- `isMythical`:
- Is skipped during Pokédex evaluations.
- Unless it also has the `dexForceRequired` flag.
- Cannot obtain Gigantamax factor via `ToggleGigantamaxFactor`.
- ***1.9 and earlier:*** Guaranteed 3 perfect IVs for the species.
- `isUltraBeast`:
- Beast Ball's multiplier is set to x5 for this species.
- All other ball multipliers are set to x0.1.
- `isParadox` (previously `isParadoxForm`):
- Makes it so that Booster Energy cannot be knocked off.
- ***1.9 and earlier:*** Guaranteed 3 perfect IVs for the species.
- `isParadox` (`isParadoxForm` previous to 1.9):
- ***1.10 onwards:*** Makes it so that Booster Energy cannot be knocked off.
- ***1.9 and earlier:*** Does nothing.
- `isTotem`:
- Does nothing.
- ***1.10 onwards:*** Does nothing.
- ***1.9 and earlier:*** Guaranteed 3 perfect IVs for the species.
- `isMegaEvolution`:
- A Mega indicator is added to the battle box indicating that they're Mega Evolved.
- The species doesn't receive affection benefits.
@ -702,14 +743,13 @@ Each species flag provides properties to the species:
- Used to determine if Gigantamax forms should have their GMax moves or not.
- Required when adding new Gigantamax forms.
- `isAlolanForm`, `isGalarianForm`, `isHisuianForm`, `isPaldeanForm`:
- In the future, these will be used to determine breeding offspring from different based on their region.
- **1.10.3 onwards:** Used to determine breeding offspring from different parents based on their region.
- **1.10.2 and earlier:** Does nothing.
- `cannotBeTraded`:
- This species cannot be traded away (like Black/White Kyurem).
- `perfectIVCount`:
- Guarantees that the number of IVs specified here will be perfect.
- `tmIlliterate`:
- This species will be unable to learn the universal moves.
- `isFrontierBanned`:
- This species will be unable to learn the universal TM or Tutor moves.
- `isFrontierBanned` ***(1.9 onwards)***:
- This species will be unable to enter Battle Frontier facilities. Replaces `gFrontierBannedSpecies`.
## 2. Delimit the moveset
@ -864,7 +904,7 @@ _NOTE: At the top of this file, you will probably see this warning:_
// DO NOT MODIFY THIS FILE! It is auto-generated from tools/learnset_helpers/teachable.py`
//
```
The expansion includes a tool called the learnset helper, which aims to automate the generation of valid teachable moves. At the time of writing, this tool only supports generating TM and Tutor learnsets. However, in the future it may be expanded to deal with level up learnsets and egg moves.
From version 1.9 onwards, pokeemerald-expansion includes a tool called the learnset helper, which aims to automate the generation of valid teachable moves. At the time of writing, this tool only supports generating TM and Tutor learnsets. However, in the future it may be expanded to deal with level up learnsets and egg moves.
Ignore the warning shown above the first time you're adding your teachable moves (as otherwise the compiler will complain about the array not existing), but in the future (if you're using the learnset helper) simply edit what teachable moves your Pokémon can learn in one of the JSON files found in `tools/learnset_helpers/porymoves_files`. It doesn't really matter which one you add your new Pokémon to, as the tool pulls from all of the files in this folder.
@ -990,7 +1030,7 @@ static const u16 sPikachuFormSpeciesIdTable[] = {
};
#endif //P_FAMILY_PIKACHU
```
We register the table each form entry in `gSpeciesInfo`.
We register the table for each form in `gSpeciesInfo`.
```diff
[SPECIES_PIKACHU] =
@ -1015,7 +1055,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.:
![image](https://github.com/rh-hideout/pokeemerald-expansion/assets/2904965/a1a90b79-46a1-4cd6-97d6-ec5d741bfdc8) ![image](https://github.com/rh-hideout/pokeemerald-expansion/assets/2904965/7cffc6be-0b5c-4074-b689-736a97297843)
![hgssdex1](/docs/tutorials/img/add_pokemon/hgssdex1.png) ![image](/docs/tutorials/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
@ -1046,7 +1086,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
![mGBA_Wq5cbDkNZG](https://github.com/rh-hideout/pokeemerald-expansion/assets/2904965/45256192-b451-4baa-af06-f57ca16e1e46)
![gender_diffs](/docs/tutorials/img/add_pokemon/gender_diffs.gif)
You may have seen that there's a couple of duplicate fields with a "Female" suffix.
```diff
@ -1054,26 +1094,33 @@ You may have seen that there's a couple of duplicate fields with a "Female" suff
{
...
.frontPic = gMonFrontPic_Frillish,
+ .frontPicFemale = gMonFrontPic_FrillishF,
.frontPicSize = MON_COORDS_SIZE(56, 56),
+ .frontPicSizeFemale = MON_COORDS_SIZE(56, 56),
.frontPicYOffset = 5,
.frontAnimFrames = sAnims_Frillish,
.frontAnimFrames = ANIM_FRAMES(
ANIMCMD_FRAME(1, 30),
ANIMCMD_FRAME(0, 30),
ANIMCMD_FRAME(1, 30),
ANIMCMD_FRAME(0, 30),
),
.frontAnimId = ANIM_RISING_WOBBLE,
.backPic = gMonBackPic_Frillish,
+ .backPicFemale = gMonBackPic_FrillishF,
.backPicSize = MON_COORDS_SIZE(40, 56),
+ .backPicSizeFemale = MON_COORDS_SIZE(40, 56),
.backPicYOffset = 7,
.backAnimId = BACK_ANIM_CONVEX_DOUBLE_ARC,
.palette = gMonPalette_Frillish,
+ .paletteFemale = gMonPalette_FrillishF,
.shinyPalette = gMonShinyPalette_Frillish,
+ .shinyPaletteFemale = gMonShinyPalette_FrillishF,
.iconSprite = gMonIcon_Frillish,
+ .iconSpriteFemale = gMonIcon_FrillishF,
.iconPalIndex = 0,
+#if P_GENDER_DIFFERENCES
+ .frontPicFemale = gMonFrontPic_FrillishF,
+ .frontPicSizeFemale = MON_COORDS_SIZE(56, 56),
+ .backPicFemale = gMonBackPic_FrillishF,
+ .backPicSizeFemale = MON_COORDS_SIZE(40, 56),
+ .paletteFemale = gMonPalette_FrillishF,
+ .shinyPaletteFemale = gMonShinyPalette_FrillishF,
+ .iconSpriteFemale = gMonIcon_FrillishF,
+ .iconPalIndexFemale = 1,
+#endif //P_GENDER_DIFFERENCES
FOOTPRINT(Frillish)
.levelUpLearnset = sFrillishLevelUpLearnset,
.teachableLearnset = sFrillishTeachableLearnset,
@ -1084,10 +1131,8 @@ These are used to change the graphics of the Pokémon if they're female. If they
However, `iconPalIndexFemale` is a special case, where it's *doesn't* read the male icon palette if its `iconSpriteFemale` is set, so if you're setting a female icon, be sure to set their palette index as well.
## 4. Overworld Data
![mGBA_4iqvhhSltK](https://github.com/rh-hideout/pokeemerald-expansion/assets/2904965/e59238dc-9779-4f26-a9e7-159a32caa3d9)
If you have `OW_POKEMON_OBJECT_EVENTS` in your hack, you can add Overworld of your new species by following these steps:
## 4. Overworld Data (v1.9 onwards)
If you have `OW_POKEMON_OBJECT_EVENTS` in your hack, you can add Overworld sprite data of your new species. Naturally, these can also be used for followers.
First, since you copied the contents from Mew's folder previously, you should also have copied its overworld sprites. Edit those to your liking, as we have done before, making sure to update the palettes
@ -1105,13 +1150,13 @@ Thirdly, in [spritesheet_rules.mk](https://github.com/rh-hideout/pokeemerald-exp
```diff
$(POKEMONGFXDIR)/mewtwo/overworld.4bpp: %.4bpp: %.png
$(GFX) $< $@ -mwidth 4 -mheight 4
$(GFX) $< $@ -mwidth 4 -mheight 4
+$(POKEMONGFXDIR)/mewthree/overworld.4bpp: %.4bpp: %.png
+ $(GFX) $< $@ -mwidth 4 -mheight 4
$(POKEMONGFXDIR)/mew/overworld.4bpp: %.4bpp: %.png
$(GFX) $< $@ -mwidth 4 -mheight 4
$(GFX) $< $@ -mwidth 4 -mheight 4
```
Fourthly, in [src/data/object_events/object_event_pic_tables_followers.h](https://github.com/rh-hideout/pokeemerald-expansion/blob/master/src/data/object_events/object_event_pic_tables_followers.h):
@ -1142,6 +1187,7 @@ And finally, in `gSpeciesInfo`:
+ SIZE_32x32,
+ SHADOW_SIZE_M,
+ TRACKS_FOOT,
+ sAnimTable_Following,
+ gOverworldPalette_Mewthree,
+ gShinyOverworldPalette_Mewthree
+ )
@ -1150,6 +1196,7 @@ And finally, in `gSpeciesInfo`:
},
};
```
**Note: In versions previous to 1.11, `sAnimTable_Following` is not added here.**
### Sprite Size
Depending on your species, you might want to use different sizes for it. For example, certain species known for being big like Steelix use sprites that fit a 64x64 frame instead of 32x32, and as such have `SIZE_64x64` in their data instead of `SIZE_32x32` to accomodate for them.
@ -1157,25 +1204,77 @@ Depending on your species, you might want to use different sizes for it. For exa
Also, in `spritesheet_rules.mk`, `-mwidth` and `-mheight` need to be set to 8 instead of 4 for such cases.
### Shadows
Gen 4 style shadows are defined by the `SHADOW` macro which takes the following arguments:
- X offset
- Y offset
- Shadow size
You have 4 options for their shadow, between Small, Medium, Large and Extra Large:
- `SHADOW_SIZE_S`
- `SHADOW_SIZE_M`
- `SHADOW_SIZE_L`
- `SHADOW_SIZE_XL_BATTLE_ONLY`
To make the Pokémon have no shadow, use the `NO_SHADOW` macro instead of `SHADOW`.
You have 4 options for their shadow, between Small, Medium, Large and None:
- `SHADOW_SIZE_NONE`
- `SHADOW_SIZE_S` ![shadow_small](/graphics/field_effects/pics/shadow_small.png)
- `SHADOW_SIZE_M` ![shadow_medium](/graphics/field_effects/pics/shadow_medium.png)
- `SHADOW_SIZE_L` ![shadow_large](/graphics/field_effects/pics/shadow_large.png)
### Tracks
You have 4 options for the tracks that your species will leave behind on sand.
- `TRACKS_NONE`
- `TRACKS_FOOT` ![sand_footprints](https://github.com/user-attachments/assets/8b8c34d6-72e9-4b9d-839d-0a5cc1ae1a4c)
- `TRACKS_SLITHER` ![slither_tracks](https://github.com/user-attachments/assets/28219c05-61e0-48b3-9aeb-43f48e4ffdd4)
- `TRACKS_SPOT` ![spot_tracks](https://github.com/user-attachments/assets/f7a24887-c5ca-47f2-8825-01f3df61deca)
- `TRACKS_BUG` ![bug_tracks](https://github.com/user-attachments/assets/8cd1dea4-4123-4af8-a558-992874a6d589)
- `TRACKS_FOOT` ![sand_footprints](/graphics/field_effects/pics/sand_footprints.png)
- `TRACKS_SLITHER` ![slither_tracks](/graphics/field_effects/pics/slither_tracks.png)
- `TRACKS_SPOT` ![spot_tracks](/graphics/field_effects/pics/spot_tracks.png)
- `TRACKS_BUG` ![bug_tracks](/graphics/field_effects/pics/bug_tracks.png)
...though technically you can also use `TRACKS_BIKE_TIRE` if you wish to.
![bike_tire_tracks](https://github.com/user-attachments/assets/ac81d211-85e5-443a-ac54-c2976f1f0b82)
![bike_tire_tracks](/graphics/field_effects/pics/bike_tire_tracks.png)
### Asymmetric sprites (Version 1.10.0 onwards)
![scovillain](/graphics/pokemon/scovillain/overworld.png)
You can set up an east-west asymetric overworld sprite by adding the East frames at the end of the sheet and changing the following:
#### Version 1.11.0 onwards
```diff
OVERWORLD(
sPicTable_Mewthree,
SIZE_32x32,
SHADOW_SIZE_M,
TRACKS_FOOT,
- sAnimTable_Following,
+ sAnimTable_Following_Asym,
gOverworldPalette_Mewthree,
gShinyOverworldPalette_Mewthree
)
```
#### Version 1.10.x
```diff
- OVERWORLD(
+ OVERWORLD_SET_ANIM(
sPicTable_Mewthree,
SIZE_32x32,
SHADOW_SIZE_M,
TRACKS_FOOT,
+ sAnimTable_Following_Asym,
gOverworldPalette_Mewthree,
gShinyOverworldPalette_Mewthree
)
```
Either way, you may also create custom animation tables and use them here appropiately.
### 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)
If you want to use their shiny and/or female versions, use one of the following macros:
- `OBJ_EVENT_GFX_SPECIES_SHINY(name)`
- `OBJ_EVENT_GFX_SPECIES_FEMALE(name)`
- `OBJ_EVENT_GFX_SPECIES_SHINY_FEMALE(name)`
## 5. In-battle shadows (v1.10 onwards)
Gen 4-style shadows are defined by the `SHADOW` macro which takes the following arguments:
- X offset
- Y offset
- Shadow size: you have 4 options for their shadow, between Small, Medium, Large and Extra Large:
- `SHADOW_SIZE_S`
- `SHADOW_SIZE_M`
- `SHADOW_SIZE_L`
- `SHADOW_SIZE_XL_BATTLE_ONLY`
To make the Pokémon have no shadow, use the `NO_SHADOW` macro instead of `SHADOW`.

View File

@ -4,9 +4,7 @@ Despite the persistent rumors about an incredibly strong third form of Mew hidin
In this tutorial, we will add a new Pokémon species to the game.
## IMPORTANT: This tutorial applies to Version 1.6.2 and lower.
- [Version 1.9.x](how_to_new_pokemon_1_9_0.md)
- [Version 1.8.x](how_to_new_pokemon_1_8_0.md)
- [Version 1.7.x](how_to_new_pokemon_1_7_0.md)
- [Version 1.7.x onwards](how_to_new_pokemon.md)
# Changes compared to vanilla
The main things that the Expansion changes are listed here.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -771,17 +771,17 @@ struct BattleStruct
// When using a move which hits multiple opponents which is then bounced by a target, we need to make sure, the move hits both opponents, the one with bounce, and the one without.
u8 attackerBeforeBounce:2;
u8 beatUpSlot:3;
u8 hitSwitchTargetFailed:1;
u8 pledgeMove:1;
u8 effectsBeforeUsingMoveDone:1; // Mega Evo and Focus Punch/Shell Trap effects.
u8 spriteIgnore0Hp:1;
u8 battleBondBoost[NUM_BATTLE_SIDES]; // Bitfield for each party.
u8 bonusCritStages[MAX_BATTLERS_COUNT]; // G-Max Chi Strike boosts crit stages of allies.
u8 itemPartyIndex[MAX_BATTLERS_COUNT];
u8 itemMoveIndex[MAX_BATTLERS_COUNT];
u8 pledgeMove:1;
u8 isSkyBattle:1;
u32 aiDelayTimer; // Counts number of frames AI takes to choose an action.
u32 aiDelayFrames; // Number of frames it took to choose an action.
s32 aiDelayTimer; // Counts number of frames AI takes to choose an action.
s32 aiDelayFrames; // Number of frames it took to choose an action.
s32 aiDelayCycles; // Number of cycles it took to choose an action.
u8 timesGotHit[NUM_BATTLE_SIDES][PARTY_SIZE];
u8 transformZeroToHero[NUM_BATTLE_SIDES];
u8 stickySyrupdBy[MAX_BATTLERS_COUNT];

View File

@ -131,7 +131,7 @@
#define FEATURE_FLAG_ASSERT(flag, id) STATIC_ASSERT(flag > TEMP_FLAGS_END || flag == 0, id)
#ifndef NDEBUG
// NOTE: This uses hardware timers 2 and 3; this will not work during active link connections or with the eReader
static inline void CycleCountStart()
{
REG_TM2CNT_H = 0;
@ -154,7 +154,6 @@ static inline u32 CycleCountEnd()
// return result
return REG_TM2CNT_L | (REG_TM3CNT_L << 16u);
}
#endif
struct Coords8
{

View File

@ -813,5 +813,6 @@ u32 CheckDynamicMoveType(struct Pokemon *mon, u32 move, u32 battler);
uq4_12_t GetDynamaxLevelHPMultiplier(u32 dynamaxLevel, bool32 inverseMultiplier);
u32 GetRegionalFormByRegion(u32 species, u32 region);
bool32 IsSpeciesForeignRegionalForm(u32 species, u32 currentRegion);
u32 GetTeraTypeFromPersonality(struct Pokemon *mon);
#endif // GUARD_POKEMON_H

View File

@ -466,6 +466,8 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData)
battlersCount = gBattlersCount;
AI_DATA->aiCalcInProgress = TRUE;
if (DEBUG_AI_DELAY_TIMER)
CycleCountStart();
for (battlerAtk = 0; battlerAtk < battlersCount; battlerAtk++)
{
if (!IsBattlerAlive(battlerAtk))
@ -481,6 +483,9 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData)
SetBattlerAiMovesData(aiData, battlerAtk, battlersCount, weather);
}
if (DEBUG_AI_DELAY_TIMER)
// We add to existing to compound multiple calls
gBattleStruct->aiDelayCycles += CycleCountEnd();
AI_DATA->aiCalcInProgress = FALSE;
}

View File

@ -2011,14 +2011,18 @@ static void HandleChooseActionAfterDma3(u32 battler)
{
gBattleStruct->aiDelayFrames = gMain.vblankCounter1 - gBattleStruct->aiDelayTimer;
gBattleStruct->aiDelayTimer = 0;
#if DEBUG_AI_DELAY_TIMER
if (DEBUG_AI_DELAY_TIMER)
{
static const u8 sText_AIDelay[] = _("AI delay:\n{B_BUFF1} frames");
PREPARE_HWORD_NUMBER_BUFFER(gBattleTextBuff1, 3, gBattleStruct->aiDelayFrames);
BattleStringExpandPlaceholdersToDisplayedString(sText_AIDelay);
static const u8 sFramesText[] = _(" frames thinking\n");
static const u8 sCyclesText[] = _(" cycles");
ConvertIntToDecimalStringN(gDisplayedStringBattle, gBattleStruct->aiDelayFrames, STR_CONV_MODE_RIGHT_ALIGN, 3);
u8* end = StringAppend(gDisplayedStringBattle, sFramesText);
ConvertIntToDecimalStringN(end, gBattleStruct->aiDelayCycles, STR_CONV_MODE_RIGHT_ALIGN, 8);
// Clear old result once read out
gBattleStruct->aiDelayCycles = 0;
StringAppend(gDisplayedStringBattle, sCyclesText);
BattlePutTextOnWindow(gDisplayedStringBattle, B_WIN_ACTION_PROMPT);
}
#endif // DEBUG_AI_DELAY_TIMER
}
gBattlerControllerFuncs[battler] = HandleInputChooseAction;
}

View File

@ -421,6 +421,7 @@ static const u8 sText_SubstituteHp[] = _("Substitute HP");
static const u8 sText_InLove[] = _("In Love");
static const u8 sText_Unknown[] = _("Unknown");
static const u8 sText_EmptyString[] = _("");
static const u8 sText_IsSwitching[] = _("Switching to ");
static const struct BitfieldInfo sStatus1Bitfield[] =
{
@ -979,6 +980,14 @@ static void PutMovesPointsText(struct BattleDebugMenu *data)
}
}
if (AI_DATA->shouldSwitch & (1u << data->aiBattlerId))
{
u32 switchMon = GetMonData(&gEnemyParty[AI_DATA->mostSuitableMonId[data->aiBattlerId]], MON_DATA_SPECIES);
AddTextPrinterParameterized(data->aiMovesWindowId, FONT_NORMAL, sText_IsSwitching, 74, 64, 0, NULL);
AddTextPrinterParameterized(data->aiMovesWindowId, FONT_NORMAL, gSpeciesInfo[switchMon].speciesName, 74 + 68, 64, 0, NULL);
}
CopyWindowToVram(data->aiMovesWindowId, COPYWIN_FULL);
Free(text);
}

View File

@ -7364,9 +7364,7 @@ static void Cmd_moveend(void)
if (GetBattlerHoldEffect(i, TRUE) == HOLD_EFFECT_RED_CARD)
redCardBattlers |= (1u << i);
}
if (redCardBattlers
&& (moveEffect != EFFECT_HIT_SWITCH_TARGET || gBattleStruct->hitSwitchTargetFailed)
&& IsBattlerAlive(gBattlerAttacker))
if (redCardBattlers && IsBattlerAlive(gBattlerAttacker))
{
// Since we check if battler was damaged, we don't need to check move result.
// In fact, doing so actually prevents multi-target moves from activating red card properly
@ -7381,7 +7379,8 @@ static void Cmd_moveend(void)
&& IsBattlerAlive(battler)
&& !DoesSubstituteBlockMove(gBattlerAttacker, battler, gCurrentMove)
&& IsBattlerTurnDamaged(battler)
&& CanBattlerSwitch(gBattlerAttacker))
&& CanBattlerSwitch(gBattlerAttacker)
&& !(moveEffect == EFFECT_HIT_SWITCH_TARGET && CanBattlerSwitch(battler)))
{
effect = TRUE;
gBattleScripting.moveendState = MOVEEND_OPPORTUNIST;
@ -7395,7 +7394,8 @@ static void Cmd_moveend(void)
gBattlescriptCurrInstr = BattleScript_MoveEnd; // Prevent user switch-in selection
BattleScriptPushCursor();
if (gBattleStruct->commanderActive[gBattlerAttacker] != SPECIES_NONE
|| GetBattlerAbility(gBattlerAttacker) == ABILITY_GUARD_DOG)
|| GetBattlerAbility(gBattlerAttacker) == ABILITY_GUARD_DOG
|| GetActiveGimmick(gBattlerAttacker) == GIMMICK_DYNAMAX)
gBattlescriptCurrInstr = BattleScript_RedCardActivationNoSwitch;
else
gBattlescriptCurrInstr = BattleScript_RedCardActivates;
@ -7569,7 +7569,6 @@ static void Cmd_moveend(void)
gSpecialStatuses[gBattlerTarget].berryReduced = FALSE;
gSpecialStatuses[gBattlerTarget].distortedTypeMatchups = FALSE;
gBattleScripting.moveEffect = 0;
gBattleStruct->hitSwitchTargetFailed = FALSE;
gBattleStruct->isAtkCancelerForCalledMove = FALSE;
gBattleStruct->swapDamageCategory = FALSE;
gBattleStruct->categoryOverride = FALSE;
@ -17227,7 +17226,7 @@ void BS_ItemRestoreHP(void)
// Heal is applied as move damage if battler is active.
if (battler != MAX_BATTLERS_COUNT && hp != 0)
{
gBattleStruct->moveDamage[gBattlerAttacker] = -healAmount;
gBattleStruct->moveDamage[battler] = -healAmount;
gBattlescriptCurrInstr = cmd->restoreBattlerInstr;
}
else
@ -18316,13 +18315,6 @@ void BS_StoreHealingWish(void)
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_HitSwitchTargetFailed(void)
{
NATIVE_ARGS();
gBattleStruct->hitSwitchTargetFailed = TRUE;
gBattlescriptCurrInstr = cmd->nextInstr;
}
void BS_TryRevivalBlessing(void)
{
NATIVE_ARGS(const u8 *failInstr);

View File

@ -62,7 +62,10 @@ void ApplyBattlerVisualsForTeraAnim(u32 battler)
bool32 CanTerastallize(u32 battler)
{
u32 holdEffect = GetBattlerHoldEffect(battler, FALSE);
if (gBattleMons[battler].status2 & STATUS2_TRANSFORMED && GET_BASE_SPECIES_ID(gBattleMons[battler].species) == SPECIES_TERAPAGOS)
return FALSE;
// Prevents Zigzagoon from terastalizing in vanilla.
if (gBattleTypeFlags & BATTLE_TYPE_FIRST_BATTLE && GetBattlerSide(battler) == B_SIDE_OPPONENT)
return FALSE;

View File

@ -6357,6 +6357,7 @@ u32 AbilityBattleEffects(u32 caseID, u32 battler, u32 ability, u32 special, u32
break;
case ABILITY_WATER_VEIL:
case ABILITY_WATER_BUBBLE:
case ABILITY_THERMAL_EXCHANGE:
if (gBattleMons[battler].status1 & STATUS1_BURN)
{
StringCopy(gBattleTextBuff1, gStatusConditionString_BurnJpn);

View File

@ -3416,8 +3416,8 @@ static void DebugAction_Give_Pokemon_ComplexCreateMon(u8 taskId) //https://githu
SetMonData(&mon, MON_DATA_DYNAMAX_LEVEL, &dmaxLevel);
// tera type
if (teraType >= NUMBER_OF_MON_TYPES)
teraType = TYPE_NONE;
if (teraType == TYPE_NONE || teraType == TYPE_MYSTERY || teraType >= NUMBER_OF_MON_TYPES)
teraType = GetTeraTypeFromPersonality(&mon);
SetMonData(&mon, MON_DATA_TERA_TYPE, &teraType);
//IVs

View File

@ -1198,6 +1198,9 @@ void CreateBoxMon(struct BoxPokemon *boxMon, u16 species, u8 level, u8 fixedIV,
SetBoxMonData(boxMon, MON_DATA_POKEBALL, &value);
SetBoxMonData(boxMon, MON_DATA_OT_GENDER, &gSaveBlock2Ptr->playerGender);
u32 teraType = (boxMon->personality & 0x1) == 0 ? gSpeciesInfo[species].types[0] : gSpeciesInfo[species].types[1];
SetBoxMonData(boxMon, MON_DATA_TERA_TYPE, &teraType);
if (fixedIV < USE_RANDOM_IVS)
{
SetBoxMonData(boxMon, MON_DATA_HP_IV, &fixedIV);
@ -2002,16 +2005,22 @@ u16 MonTryLearningNewMove(struct Pokemon *mon, bool8 firstMove)
}
}
// Handler for if Zacian or Zamazenta should learn Iron Head
// since it transforms in the Behemoth Blade/Bash move in
// battle in the Crowned forms.
if (learnset[sLearningMoveTableID].move == MOVE_IRON_HEAD && (species == SPECIES_ZAMAZENTA_CROWNED || species == SPECIES_ZACIAN_CROWNED))
// Handler for Pokémon whose moves change upon form change.
// For example, if Zacian or Zamazenta should learn Iron Head,
// they're prevented from doing if they have Behemoth Blade/Bash,
// since it transforms into them while in their Crowned forms.
const struct FormChange *formChanges = GetSpeciesFormChanges(species);
for (u32 i = 0; formChanges != NULL && formChanges[i].method != FORM_CHANGE_TERMINATOR; i++)
{
for (u32 accessor = MON_DATA_MOVE1; accessor <= MON_DATA_MOVE4; accessor++)
if (formChanges[i].method == FORM_CHANGE_END_BATTLE
&& learnset[sLearningMoveTableID].move == formChanges[i].param3)
{
u32 move = GetMonData(mon, accessor);
if (move == MOVE_BEHEMOTH_BLADE || move == MOVE_BEHEMOTH_BASH)
return MOVE_NONE;
for (u32 j = 0; j < MAX_MON_MOVES; j++)
{
if (formChanges[i].param2 == GetMonData(mon, MON_DATA_MOVE1 + j))
return MOVE_NONE;
}
}
}
@ -3640,10 +3649,10 @@ const u16 *GetSpeciesFormTable(u16 species)
const struct FormChange *GetSpeciesFormChanges(u16 species)
{
const struct FormChange *evolutions = gSpeciesInfo[SanitizeSpeciesId(species)].formChangeTable;
if (evolutions == NULL)
const struct FormChange *formChanges = gSpeciesInfo[SanitizeSpeciesId(species)].formChangeTable;
if (formChanges == NULL)
return gSpeciesInfo[SPECIES_NONE].formChangeTable;
return evolutions;
return formChanges;
}
u8 CalculatePPWithBonus(u16 move, u8 ppBonuses, u8 moveIndex)
@ -7080,3 +7089,9 @@ bool32 IsSpeciesForeignRegionalForm(u32 species, u32 currentRegion)
}
return FALSE;
}
u32 GetTeraTypeFromPersonality(struct Pokemon *mon)
{
const u8 *types = gSpeciesInfo[GetMonData(mon, MON_DATA_SPECIES)].types;
return (GetMonData(mon, MON_DATA_PERSONALITY) & 0x1) == 0 ? types[0] : types[1];
}

View File

@ -373,8 +373,8 @@ static u32 ScriptGiveMonParameterized(u8 side, u8 slot, u16 species, u8 level, u
SetMonData(&mon, MON_DATA_DYNAMAX_LEVEL, &dmaxLevel);
// tera type
if (teraType >= NUMBER_OF_MON_TYPES)
teraType = TYPE_NONE;
if (teraType == TYPE_NONE || teraType == TYPE_MYSTERY || teraType >= NUMBER_OF_MON_TYPES)
teraType = GetTeraTypeFromPersonality(&mon);
SetMonData(&mon, MON_DATA_TERA_TYPE, &teraType);
// EV and IV

View File

@ -0,0 +1,88 @@
#include "global.h"
#include "test/battle.h"
SINGLE_BATTLE_TEST("Thermal Exchange makes Will-O-Wisp fail")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_WILL_O_WISP].effect == EFFECT_WILL_O_WISP);
PLAYER(SPECIES_BAXCALIBUR) { Ability(ABILITY_THERMAL_EXCHANGE); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_WILL_O_WISP); MOVE(player, MOVE_CELEBRATE); }
} SCENE {
NONE_OF {
ANIMATION(ANIM_TYPE_MOVE, MOVE_WILL_O_WISP, opponent);
STATUS_ICON(player, burn: TRUE);
}
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player);
}
}
SINGLE_BATTLE_TEST("Thermal Exchange prevents the user from getting burned when hitting Flame Body")
{
GIVEN {
PLAYER(SPECIES_BAXCALIBUR) { Ability(ABILITY_THERMAL_EXCHANGE); }
OPPONENT(SPECIES_PONYTA) { Ability(ABILITY_FLAME_BODY); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, player);
NONE_OF {
ABILITY_POPUP(opponent, ABILITY_FLAME_BODY);
STATUS_ICON(player, burn: TRUE);
}
}
}
SINGLE_BATTLE_TEST("Thermal Exchange cures burns when acquired")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_WILL_O_WISP].effect == EFFECT_WILL_O_WISP);
ASSUME(gMovesInfo[MOVE_SKILL_SWAP].effect == EFFECT_SKILL_SWAP);
PLAYER(SPECIES_BAXCALIBUR) { Ability(ABILITY_THERMAL_EXCHANGE); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(player, MOVE_WILL_O_WISP); MOVE(opponent, MOVE_SKILL_SWAP); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_WILL_O_WISP, player);
STATUS_ICON(opponent, burn: TRUE);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SKILL_SWAP, opponent);
ABILITY_POPUP(opponent, ABILITY_THERMAL_EXCHANGE);
STATUS_ICON(opponent, burn: FALSE);
NOT HP_BAR(opponent);
}
}
SINGLE_BATTLE_TEST("Thermal Exchange burn prevention can be bypassed with Mold Breaker but is cured after")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_WILL_O_WISP].effect == EFFECT_WILL_O_WISP);
PLAYER(SPECIES_BAXCALIBUR) { Ability(ABILITY_THERMAL_EXCHANGE); }
OPPONENT(SPECIES_RAMPARDOS) { Ability(ABILITY_MOLD_BREAKER); }
} WHEN {
TURN { MOVE(opponent, MOVE_WILL_O_WISP); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_WILL_O_WISP, opponent);
STATUS_ICON(player, burn: TRUE);
ABILITY_POPUP(player, ABILITY_THERMAL_EXCHANGE);
STATUS_ICON(player, burn: FALSE);
NOT HP_BAR(player);
}
}
SINGLE_BATTLE_TEST("Thermal Exchange boosts attack if hit by a damaging fire type move")
{
GIVEN {
ASSUME(gMovesInfo[MOVE_EMBER].type == TYPE_FIRE);
PLAYER(SPECIES_BAXCALIBUR) { Ability(ABILITY_THERMAL_EXCHANGE); }
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {
TURN { MOVE(opponent, MOVE_EMBER); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_EMBER, opponent);
HP_BAR(player);
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, player);
} THEN {
EXPECT_EQ(player->statStages[STAT_ATK], DEFAULT_STAT_STAGE + 1);
}
}

View File

@ -243,25 +243,6 @@ SINGLE_BATTLE_TEST("Dynamax: Dynamaxed Pokemon are not affected by phazing moves
}
}
SINGLE_BATTLE_TEST("Dynamax: Dynamaxed Pokemon are not affected by Red Card")
{
GIVEN {
ASSUME(gItemsInfo[ITEM_RED_CARD].holdEffect == HOLD_EFFECT_RED_CARD);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX); MOVE(opponent, MOVE_CELEBRATE); }
} SCENE {
MESSAGE("Wobbuffet used Max Strike!");
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent);
MESSAGE("The opposing Wobbuffet held up its Red Card against Wobbuffet!");
MESSAGE("The move was blocked by the power of Dynamax!");
} THEN {
EXPECT_EQ(opponent->item, ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Dynamax: Dynamaxed Pokemon can be switched out by Eject Button")
{
GIVEN {
@ -1626,9 +1607,10 @@ SINGLE_BATTLE_TEST("Dynamax: Dynamax is reverted before switch out")
}
}
SINGLE_BATTLE_TEST("Dynamax: Destiny Bond if a dynamaxed battler is present on field")
SINGLE_BATTLE_TEST("Dynamax: Destiny Bond fails if a dynamaxed battler is present on field")
{
GIVEN {
ASSUME(GetMoveEffect(MOVE_DESTINY_BOND) == EFFECT_DESTINY_BOND);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET);
} WHEN {

View File

@ -497,7 +497,25 @@ SINGLE_BATTLE_TEST("Red Card prevents Emergency Exit activation when triggered")
}
}
TO_DO_BATTLE_TEST("Red Card activates but fails if the attacker has Dynamaxed");
SINGLE_BATTLE_TEST("Red Card activates and is consumed but fails if the attacker is Dynamaxed")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); }
} WHEN {
TURN {
MOVE(opponent, MOVE_TACKLE);
MOVE(player, MOVE_TACKLE, gimmick: GIMMICK_DYNAMAX);
}
} SCENE {
ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_HELD_ITEM_EFFECT, opponent);
MESSAGE("The opposing Wobbuffet held up its Red Card against Wobbuffet!");
NOT MESSAGE("Wobbuffet is switched out with the Eject Button!");
} THEN {
EXPECT_EQ(opponent->item, ITEM_NONE);
}
}
SINGLE_BATTLE_TEST("Red Card activates before Eject Pack")
{

View File

@ -1,7 +1,7 @@
#include "global.h"
#include "test/battle.h"
SINGLE_BATTLE_TEST("Items can restore a battler's HP by a fixed amount")
SINGLE_BATTLE_TEST("Items can restore a battler's HP by a fixed amount (singles, player)")
{
u32 item, hp;
PARAMETRIZE { item = ITEM_POTION; hp = 20; }
@ -29,6 +29,162 @@ SINGLE_BATTLE_TEST("Items can restore a battler's HP by a fixed amount")
}
}
SINGLE_BATTLE_TEST("Items can restore a battler's HP by a fixed amount (singles, opponent)")
{
u32 item, hp;
PARAMETRIZE { item = ITEM_POTION; hp = 20; }
PARAMETRIZE { item = ITEM_SUPER_POTION; hp = I_HEALTH_RECOVERY >= GEN_7 ? 60 : 50; }
PARAMETRIZE { item = ITEM_HYPER_POTION; hp = I_HEALTH_RECOVERY >= GEN_7 ? 120 : 200; }
PARAMETRIZE { item = ITEM_FRESH_WATER; hp = I_HEALTH_RECOVERY >= GEN_7 ? 30 : 50; }
PARAMETRIZE { item = ITEM_SODA_POP; hp = I_HEALTH_RECOVERY >= GEN_7 ? 50 : 60; }
PARAMETRIZE { item = ITEM_LEMONADE; hp = I_HEALTH_RECOVERY >= GEN_7 ? 70 : 80; }
PARAMETRIZE { item = ITEM_MOOMOO_MILK; hp = 100; }
PARAMETRIZE { item = ITEM_ENERGY_POWDER; hp = I_HEALTH_RECOVERY >= GEN_7 ? 60 : 50; }
PARAMETRIZE { item = ITEM_ENERGY_ROOT; hp = I_HEALTH_RECOVERY >= GEN_7 ? 120 : 200; }
PARAMETRIZE { item = ITEM_SWEET_HEART; hp = 20; }
PARAMETRIZE { item = ITEM_ORAN_BERRY; hp = 10; }
#if I_SITRUS_BERRY_HEAL < GEN_4
PARAMETRIZE { item = ITEM_SITRUS_BERRY; hp = 30; }
#endif
GIVEN {
ASSUME(gItemsInfo[item].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET) { HP(1); MaxHP(400); }
} WHEN {
TURN { USE_ITEM(opponent, item, partyIndex: 0); }
} SCENE {
HP_BAR(opponent, damage: -hp);
}
}
DOUBLE_BATTLE_TEST("Items can restore a battler's HP (doubles, playerLeft)")
{
s32 hp = gItemsInfo[ITEM_POTION].holdEffectParam;
GIVEN {
ASSUME(gItemsInfo[ITEM_POTION].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WOBBUFFET) { HP(1); MaxHP(400); }
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { USE_ITEM(playerLeft, ITEM_POTION, partyIndex: 0); }
} SCENE {
HP_BAR(playerLeft, damage: -hp);
}
}
DOUBLE_BATTLE_TEST("Items can restore a battler's HP (doubles, playerRight)")
{
s32 hp = gItemsInfo[ITEM_POTION].holdEffectParam;
GIVEN {
ASSUME(gItemsInfo[ITEM_POTION].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WYNAUT);
PLAYER(SPECIES_WOBBUFFET) { HP(1); MaxHP(400); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { USE_ITEM(playerRight, ITEM_POTION, partyIndex: 1); }
} SCENE {
HP_BAR(playerRight, damage: -hp);
}
}
DOUBLE_BATTLE_TEST("Items can restore a battler's HP (doubles, opponentLeft)")
{
s32 hp = gItemsInfo[ITEM_POTION].holdEffectParam;
GIVEN {
ASSUME(gItemsInfo[ITEM_POTION].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET) { HP(1); MaxHP(400); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { USE_ITEM(opponentLeft, ITEM_POTION, partyIndex: 0); }
} SCENE {
HP_BAR(opponentLeft, damage: -hp);
}
}
DOUBLE_BATTLE_TEST("Items can restore a battler's HP (doubles, opponentRight)")
{
s32 hp = gItemsInfo[ITEM_POTION].holdEffectParam;
GIVEN {
ASSUME(gItemsInfo[ITEM_POTION].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET) { HP(1); MaxHP(400); }
} WHEN {
TURN { USE_ITEM(opponentRight, ITEM_POTION, partyIndex: 1); }
} SCENE {
HP_BAR(opponentRight, damage: -hp);
}
}
DOUBLE_BATTLE_TEST("Items can restore a partner battler's HP (playerRight to playerLeft)")
{
s32 hp = gItemsInfo[ITEM_POTION].holdEffectParam;
GIVEN {
ASSUME(gItemsInfo[ITEM_POTION].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WOBBUFFET) { HP(1); MaxHP(400); }
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { USE_ITEM(playerRight, ITEM_POTION, partyIndex: 0); }
} SCENE {
HP_BAR(playerLeft, damage: -hp);
}
}
DOUBLE_BATTLE_TEST("Items can restore a partner battler's HP (playerLeft to playerRight)")
{
s32 hp = gItemsInfo[ITEM_POTION].holdEffectParam;
GIVEN {
ASSUME(gItemsInfo[ITEM_POTION].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WYNAUT);
PLAYER(SPECIES_WOBBUFFET) { HP(1); MaxHP(400); }
OPPONENT(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { USE_ITEM(playerLeft, ITEM_POTION, partyIndex: 1); }
} SCENE {
HP_BAR(playerRight, damage: -hp);
}
}
DOUBLE_BATTLE_TEST("Items can restore a partner battler's HP (opponentRight to opponentLeft)")
{
s32 hp = gItemsInfo[ITEM_POTION].holdEffectParam;
GIVEN {
ASSUME(gItemsInfo[ITEM_POTION].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET) { HP(1); MaxHP(400); }
OPPONENT(SPECIES_WYNAUT);
} WHEN {
TURN { USE_ITEM(opponentRight, ITEM_POTION, partyIndex: 0); }
} SCENE {
HP_BAR(opponentLeft, damage: -hp);
}
}
DOUBLE_BATTLE_TEST("Items can restore a partner battler's HP (opponentLeft to opponentRight)")
{
s32 hp = gItemsInfo[ITEM_POTION].holdEffectParam;
GIVEN {
ASSUME(gItemsInfo[ITEM_POTION].battleUsage == EFFECT_ITEM_RESTORE_HP);
PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT);
OPPONENT(SPECIES_WYNAUT);
OPPONENT(SPECIES_WOBBUFFET) { HP(1); MaxHP(400); }
} WHEN {
TURN { USE_ITEM(opponentLeft, ITEM_POTION, partyIndex: 1); }
} SCENE {
HP_BAR(opponentRight, damage: -hp);
}
}
SINGLE_BATTLE_TEST("Items can restore a battler's HP by a percentage")
{
u32 item, percentage;

View File

@ -57,6 +57,8 @@ TEST("Terastallization type is reset to the default types when setting Tera Type
CreateMon(&mon, SPECIES_PIDGEY, 100, 0, FALSE, 0, OT_ID_PRESET, 0);
SetMonData(&mon, MON_DATA_TERA_TYPE, &teraType);
EXPECT_EQ(teraType, GetMonData(&mon, MON_DATA_TERA_TYPE));
if (typeNone == TYPE_NONE)
typeNone = GetTeraTypeFromPersonality(&mon);
SetMonData(&mon, MON_DATA_TERA_TYPE, &typeNone);
typeNone = GetMonData(&mon, MON_DATA_TERA_TYPE);
EXPECT(typeNone == gSpeciesInfo[SPECIES_PIDGEY].types[0]

View File

@ -5,3 +5,4 @@ The source code for these specific builds is available from:
- Windows: <https://github.com/mgba-emu/mgba/tree/7ee2be6c96222dca12a9a579b747fe5ff1829def>
- Linux: <https://github.com/mgba-emu/mgba/tree/dbffb46c4e7d2e7a2cbed7c3488cece4c2176d4c>
- Mac: <https://github.com/mgba-emu/mgba/tree/daf01b03d5316dac966acd4b05318a225cab12f5>

BIN
tools/mgba/mgba-rom-test-mac Executable file

Binary file not shown.