Merge branch 'upcoming' into twelves

This commit is contained in:
grintoul 2026-03-21 10:13:37 +01:00 committed by GitHub
commit 7c1eaa54b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
76 changed files with 6320 additions and 4824 deletions

View File

@ -7,69 +7,217 @@ on:
- upcoming
pull_request:
env:
GAME_REVISION: 0
GAME_LANGUAGE: ENGLISH
COMPARE: 0
UNUSED_ERROR: 1
DEPRECATED_ERROR: 1
jobs:
build:
build-emerald:
if: github.actor != 'allcontributors[bot]'
runs-on: ubuntu-latest
env:
GAME_REVISION: 0
GAME_LANGUAGE: ENGLISH
COMPARE: 0
UNUSED_ERROR: 1
DEPRECATED_ERROR: 1
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Install binutils
run: |
sudo apt update
sudo apt install -y binutils-arm-none-eabi gcc-arm-none-eabi libnewlib-arm-none-eabi libpng-dev python3
# build-essential and git are already installed
sudo apt-get update
sudo apt-get install -y binutils-arm-none-eabi gcc-arm-none-eabi libnewlib-arm-none-eabi libpng-dev python3
- name: Cache build tools
uses: actions/cache@v4
with:
path: |
tools/bin2c/bin2c
tools/gbafix/gbafix
tools/gbagfx/gbagfx
tools/jsonproc/jsonproc
tools/mapjson/mapjson
tools/mid2agb/mid2agb
tools/preproc/preproc
tools/ramscrgen/ramscrgen
tools/rsfont/rsfont
tools/scaninc/scaninc
tools/trainerproc/trainerproc
tools/compresSmol/compresSmol
tools/compresSmol/compresSmolTilemap
tools/wav2agb/wav2agb
key: tools-${{ runner.os }}-${{ hashFiles('tools/*/Makefile', 'tools/**/*.c', 'tools/**/*.cpp', 'tools/**/*.h') }}
- name: ROM (Emerald)
env:
COMPARE: 0
GAME_VERSION: EMERALD
run: make -j${nproc} -O all
run: make -j$(nproc) -O all
build-firered:
if: github.actor != 'allcontributors[bot]'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install binutils
run: |
sudo apt-get update
sudo apt-get install -y binutils-arm-none-eabi gcc-arm-none-eabi libnewlib-arm-none-eabi libpng-dev python3
- name: Cache build tools
uses: actions/cache@v4
with:
path: |
tools/bin2c/bin2c
tools/gbafix/gbafix
tools/gbagfx/gbagfx
tools/jsonproc/jsonproc
tools/mapjson/mapjson
tools/mid2agb/mid2agb
tools/preproc/preproc
tools/ramscrgen/ramscrgen
tools/rsfont/rsfont
tools/scaninc/scaninc
tools/trainerproc/trainerproc
tools/compresSmol/compresSmol
tools/compresSmol/compresSmolTilemap
tools/wav2agb/wav2agb
key: tools-${{ runner.os }}-${{ hashFiles('tools/*/Makefile', 'tools/**/*.c', 'tools/**/*.cpp', 'tools/**/*.h') }}
- name: ROM (Firered)
env:
COMPARE: 0
run: make firered -j$(nproc) -O
build-leafgreen:
if: github.actor != 'allcontributors[bot]'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install binutils
run: |
make firered -j${nproc} -O
sudo apt-get update
sudo apt-get install -y binutils-arm-none-eabi gcc-arm-none-eabi libnewlib-arm-none-eabi libpng-dev python3
- name: Cache build tools
uses: actions/cache@v4
with:
path: |
tools/bin2c/bin2c
tools/gbafix/gbafix
tools/gbagfx/gbagfx
tools/jsonproc/jsonproc
tools/mapjson/mapjson
tools/mid2agb/mid2agb
tools/preproc/preproc
tools/ramscrgen/ramscrgen
tools/rsfont/rsfont
tools/scaninc/scaninc
tools/trainerproc/trainerproc
tools/compresSmol/compresSmol
tools/compresSmol/compresSmolTilemap
tools/wav2agb/wav2agb
key: tools-${{ runner.os }}-${{ hashFiles('tools/*/Makefile', 'tools/**/*.c', 'tools/**/*.cpp', 'tools/**/*.h') }}
- name: ROM (Leafgreen)
env:
COMPARE: 0
run: make leafgreen -j$(nproc) -O
release:
if: github.actor != 'allcontributors[bot]'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install binutils
run: |
make leafgreen -j${nproc} -O
sudo apt-get update
sudo apt-get install -y binutils-arm-none-eabi gcc-arm-none-eabi libnewlib-arm-none-eabi libpng-dev python3
- name: Cache build tools
uses: actions/cache@v4
with:
path: |
tools/bin2c/bin2c
tools/gbafix/gbafix
tools/gbagfx/gbagfx
tools/jsonproc/jsonproc
tools/mapjson/mapjson
tools/mid2agb/mid2agb
tools/preproc/preproc
tools/ramscrgen/ramscrgen
tools/rsfont/rsfont
tools/scaninc/scaninc
tools/trainerproc/trainerproc
tools/compresSmol/compresSmol
tools/compresSmol/compresSmolTilemap
tools/wav2agb/wav2agb
key: tools-${{ runner.os }}-${{ hashFiles('tools/*/Makefile', 'tools/**/*.c', 'tools/**/*.cpp', 'tools/**/*.h') }}
- name: Release
env:
GAME_VERSION: EMERALD
run: make -j$(nproc) release
test:
if: github.actor != 'allcontributors[bot]'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install binutils
run: |
make tidy
make -j${nproc} release
# make tidy to purge previous build
sudo apt-get update
sudo apt-get install -y binutils-arm-none-eabi gcc-arm-none-eabi libnewlib-arm-none-eabi libpng-dev python3
- name: Cache build tools
uses: actions/cache@v4
with:
path: |
tools/bin2c/bin2c
tools/gbafix/gbafix
tools/gbagfx/gbagfx
tools/jsonproc/jsonproc
tools/mapjson/mapjson
tools/mid2agb/mid2agb
tools/preproc/preproc
tools/ramscrgen/ramscrgen
tools/rsfont/rsfont
tools/scaninc/scaninc
tools/trainerproc/trainerproc
tools/compresSmol/compresSmol
tools/compresSmol/compresSmolTilemap
tools/wav2agb/wav2agb
tools/patchelf/patchelf
tools/mgba-rom-test-hydra/mgba-rom-test-hydra
key: tools-check-${{ runner.os }}-${{ hashFiles('tools/*/Makefile', 'tools/**/*.c', 'tools/**/*.cpp', 'tools/**/*.h') }}
- name: Test
env:
GAME_VERSION: EMERALD
TEST: 1
run: |
make -j${nproc} check
run: make -j$(nproc) check
# Gate job: satisfies the "build" branch protection rule.
# Passes only when all parallel build/test jobs succeed.
build:
if: github.actor != 'allcontributors[bot]'
runs-on: ubuntu-latest
needs: [build-emerald, build-firered, build-leafgreen, release, test]
steps:
- name: All builds passed
run: echo "All builds and tests passed."
docs_validate:
if: github.actor != 'allcontributors[bot]'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Check that SUMMARY.md includes markdown doc files
run: |
.github/docs_validate/inclusive_summary.py
run: .github/docs_validate/inclusive_summary.py
allcontributors:
if: github.actor == 'allcontributors[bot]'
@ -78,4 +226,3 @@ jobs:
steps:
- name: Automatically pass for allcontributors
run: echo "CI automatically passes for allcontributors" && exit 0

View File

@ -529,7 +529,7 @@ ifneq ($(NODEP),1)
endif
$(DATA_ASM_BUILDDIR)/%.o: $(DATA_ASM_SUBDIR)/%.s
$(PREPROC) $< charmap.txt | $(CPP) $(CPPFLAGS) $(INCLUDE_SCANINC_ARGS) - | $(PREPROC) -ie $< charmap.txt | $(AS) $(ASFLAGS) -o $@
$(PREPROC) -s $< charmap.txt | $(CPP) $(CPPFLAGS) $(INCLUDE_SCANINC_ARGS) - | $(PREPROC) -ie $< charmap.txt | $(AS) $(ASFLAGS) -o $@
$(DATA_ASM_BUILDDIR)/%.d: $(DATA_ASM_SUBDIR)/%.s
$(SCANINC) -M $@ $(INCLUDE_SCANINC_ARGS) -I "" $<

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,7 @@
#include "constants/game_stat.h"
#include "constants/trainers.h"
#include "constants/species.h"
#include "constants/generational_changes.h"
#include "constants/config_changes.h"
.include "asm/macros.inc"
.include "asm/macros/battle_script.inc"
.include "constants/constants.inc"
@ -4544,7 +4544,7 @@ BattleScript_SafeguardProtected::
pause B_WAIT_TIME_SHORT
printstring STRINGID_PKMNUSEDSAFEGUARD
waitmessage B_WAIT_TIME_LONG
end2
goto BattleScript_MoveEnd
BattleScript_SafeguardEnds::
pause B_WAIT_TIME_SHORT

View File

@ -1062,7 +1062,7 @@ EventScript_AfterWhiteOutHeal::
lockall
msgbox gText_FirstShouldRestoreMonsHealth
call EventScript_PkmnCenterNurse_TakeAndHealPkmn
call_if_unset FLAG_DEFEATED_RUSTBORO_GYM, EventScript_AfterWhiteOutHealMsgPreRoxanne
call_if_unset FLAG_DEFEATED_RUSTBORO_GYM, EventScript_AfterWhiteOutHealMsgPreFirstBoss
call_if_set FLAG_DEFEATED_RUSTBORO_GYM, EventScript_AfterWhiteOutHealMsg
applymovement VAR_LAST_TALKED, Movement_PkmnCenterNurse_Bow
waitmovement 0
@ -1070,7 +1070,7 @@ EventScript_AfterWhiteOutHeal::
releaseall
end
EventScript_AfterWhiteOutHealMsgPreRoxanne::
EventScript_AfterWhiteOutHealMsgPreFirstBoss::
msgbox gText_MonsHealedShouldBuyPotions
return
@ -1080,6 +1080,7 @@ EventScript_AfterWhiteOutHealMsg::
EventScript_AfterWhiteOutMomHeal::
lockall
textcolor NPC_TEXT_COLOR_FEMALE
applymovement LOCALID_PLAYERS_HOUSE_1F_MOM, Common_Movement_WalkInPlaceFasterDown
waitmovement 0
msgbox gText_HadQuiteAnExperienceTakeRest
@ -1432,7 +1433,7 @@ gText_HadQuiteAnExperienceTakeRest::
gText_MomExplainHPGetPotions::
.string "MOM: Oh, good! You and your\n"
.string "POKéMON are looking great.\p"
.string "I just heard from PROF. BIRCH.\p"
.string "I just heard from {STR_VAR_1}.\p"
.string "He said that POKéMON's energy is\n"
.string "measured in HP.\p"
.string "If your POKéMON lose their HP,\n"

View File

@ -17,7 +17,7 @@
"connections": null,
"object_events": [
{
"local_id": "LOCALID_MOM",
"local_id": "LOCALID_PLAYERS_HOUSE_1F_MOM",
"type": "object",
"graphics_id": "OBJ_EVENT_GFX_MOM_FRLG",
"x": 8,

View File

@ -9,7 +9,7 @@ PalletTown_PlayersHouse_1F_EventScript_Mom::
call_if_eq VAR_RESULT, MALE, PalletTown_PlayersHouse_1F_EventScript_MomOakLookingForYouMale
call_if_eq VAR_RESULT, FEMALE, PalletTown_PlayersHouse_1F_EventScript_MomOakLookingForYouFemale
closemessage
applymovement LOCALID_MOM, Common_Movement_FaceOriginalDirection
applymovement LOCALID_PLAYERS_HOUSE_1F_MOM, Common_Movement_FaceOriginalDirection
waitmovement 0
release
end

View File

@ -1,3 +1,16 @@
EventScript_AfterWhiteOutHeal_Frlg::
lockall
textcolor NPC_TEXT_COLOR_FEMALE
msgbox gText_FirstShouldRestoreMonsHealth
call EventScript_PkmnCenterNurse_TakeAndHealPkmn_Frlg
call_if_unset FLAG_DEFEATED_BROCK, EventScript_AfterWhiteOutHealMsgPreFirstBoss
call_if_set FLAG_DEFEATED_BROCK, EventScript_AfterWhiteOutHealMsg
applymovement VAR_LAST_TALKED, Movement_Bow
waitmovement 0
fadedefaultbgm
releaseall
end
EventScript_PkmnCenterNurse_Frlg::
message Text_WelcomeWantToHealPkmn_Frlg
waitmessage

View File

@ -1,138 +1,253 @@
# Generation of struct Pokemon instances
This document describes the ways you generate an instance of struct Pokemon through script or through code.
These Pokemon can be given to your players, be used as enemy trainer pokemon or as static wild pokemon.
This document describes the ways you generate an instance of `struct Pokemon` through script or through code.
These Pokemon can be given to your players, be used as enemy trainer Pokemon or as static wild Pokemon.
## Through script (`givemon` and `createmon`)
### `createmon`
`createmon` is a script command that allows you to generate a pokemon with any of the properties you might want.
It has a lot of arguments in order to offer this flexibility.
`side`, `slot`, `species` and `level` are the required arguments
`side` determines if the pokemon will be created as a player pokemon or an enemy pokemon: 0 will put the pokemon in the player party and 1 in the enemy party
`slot` determines the slot in the player or enemy party the pokemon will occupy. `slot` goes from 0 to 5 but if `side` is 0 (player pokemon), setting the slot to 6 will instead give the pokemon to player automatically putting it in the first empty slot or sending it to the PC when the party is the full. (Setting `slot` to 6 when trying to create an enemy pokemon will result in the `createmon` command being ignored)
`species` and `level` refers to the species id and the level of the pokemon you want to generate
`item`, `ball`, `nature`, `abilityNum`, `gender`, `hpEv`, `atkEv`, `defEv`, `speedEv`, `spAtkEv`, `spDefEv`, `hpIv`, `atkIv`, `defIv`, `speedIv`, `spAtkIv`, `spDefIv`, `move1`, `move2`, `move3`, `move4`, `shinyMode`, `gmaxFactor`, `teraType`, `dmaxLevel` are the optional arguments. They are pretty explicit in what they refer to but let's describe what they default to when they are not present.
`item` refers to the item the mon is holding. If the argument is missing, the mon won't be holding anything
`ball` refers to the type of ball the pokemon comes out of. This defaults to a PokeBall and if you are generating a wildmon, this will be overwritten if the pokemon is captured
`nature`, and `gender` will default to random values
`abilityNum` will default to the value corresponding to the personality they will roll (in practice, it's random but it will have correlations with other if the pokemon parameters)
`hpEv`, `atkEv`, `defEv`, `speedEv`, `spAtkEv`, `spDefEv` will default to 0
`hpIv`, `atkIv`, `defIv`, `speedIv`, `spAtkIv`, `spDefIv` will default to `USE_RANDOM_IVS` which tell the game to roll a random IV value (between 0 and 31). If the generated species has a `perfectIVCount`, only the random values will be eligible to be perfected.
`move1`, `move2`, `move3`, `move4` will default to `MOVE_DEFAULT` which tells the game to fill the slot with the last level up move available
`shinyMode` will default to SHINY_MODE_RANDOM doing random roll(s) to check if the mon is shiny. (The other possibile values for shinyMode are SHINY_MODE_ALWAYS and SHINY_MODE_NEVER to force the pokemon to be shiny or not be shiny respectively)
`gmaxFactor` default to FALSE
`teraType` will default to the value corresponding to the personality they will roll (in practice, it's random but it will have correlations with other if the pokemon parameters)
`dmaxLevel` will deafult to 0
`createmon` is a script command that allows you to generate a Pokemon with any of the properties you might want. It has a lot of arguments in order to offer this flexibility.
#### Required Arguments
> **side, slot, species, level** are the required arguments.
- **side** determines if the Pokemon will be created as a player Pokemon or an enemy Pokemon:
- `0` will put the Pokemon in the player party
- `1` in the enemy party
- **slot** determines the slot in the player or enemy party the Pokemon will occupy.
- `slot` goes from `0` to `5`
- If `side` is `0` (player Pokemon), setting the slot to `6` will instead give the Pokemon to player automatically putting it in the first empty slot or sending it to the PC when the party is the full.
- Setting `slot` to `6` when trying to create an enemy Pokemon will result in the `createmon` command being ignored.
- **species** and **level** refer to the species id and the level of the Pokemon you want to generate.
#### Optional Arguments
> `item`, `ball`, `nature`, `abilityNum`, `gender`, `hpEv`, `atkEv`, `defEv`, `speedEv`, `spAtkEv`, `spDefEv`, `hpIv`, `atkIv`, `defIv`, `speedIv`, `spAtkIv`, `spDefIv`, `move1`, `move2`, `move3`, `move4`, `shinyMode`, `gmaxFactor`, `teraType`, `dmaxLevel`.
The purpose of these arguments is largely self-explanatory but we will briefly discuss what they default to when nothing is explicitly specified.
- **item** refers to the item the Pokemon is holding. If the argument is missing, the Pokemon wont be holding anything.
- **ball** refers to the type of ball the Pokemon comes out of. The expected type is `enum Pokeball` defaults to a `BALL_POKE`. If the Pokemon being generated is a Wild Pokemon this will be overwritten if the Pokemon is captured.
- **nature** and **gender** will default to random values.
- **abilityNum** will default to the value corresponding to the personality rolled (This is essentially random but it will be have correlations to the other parameters of the Pokemon).
- **EVs**
`hpEv, atkEv, defEv, speedEv, spAtkEv, spDefEv` will default to `0`.
- **IVs**
`hpIv, atkIv, defIv, speedIv, spAtkIv, spDefIv` will default to `USE_RANDOM_IVS` which tell the game to roll a random IV value (between `0` and `31`). If the generated species has a `perfectIVCount`, only the random values will be eligible to be perfected.
- **moves**
`move1, move2, move3, move4` will default to `MOVE_DEFAULT` which tells the game to fill the slot with the last level up move available.
- **shinyMode** will default to `SHINY_MODE_RANDOM` doing random roll(s) to check if the Pokemon is shiny.
- `SHINY_MODE_ALWAYS` forces the Pokemon to be shiny
- `SHINY_MODE_NEVER` forces the Pokemon to not be shiny
- **gmaxFactor** defaults to `FALSE`.
- **teraType** will default to the value corresponding to the personality they will roll (in practice, its random but it will have correlations with other if the Pokemon parameters).
- **dmaxLevel** will default to `0`.
---
### `givemon`
`givemon` uses the same arguments as `createmon` minus `side` and `slot`. This is because `givemon` is almost equivalent to `createmon 0 6 ...` and just gives the mon to the player after generating it. The exception being that `givemon` interacts with the abilities `Synchronize` and `Cute Charm` slightly differently than `createmon` when `nature` or `gender` are not explicitly set.
`givemon` uses the same arguments as `createmon` minus `side` and `slot`.
### `setwildbattle (species:req, level:req, item=ITEM_NONE, species2=SPECIES_NONE, level2=0, item2=ITEM_NONE)`
This is because `givemon` is almost equivalent to `createmon 0 6 ...` and just gives the Pokemon to the player after generating it.
The exception being that `givemon` interacts with the abilities `Synchronize` and `Cute Charm` somewhat differently than `createmon` when `nature` or `gender` are not explicitly set.
---
### `setwildbattle`
##### Arguments
> `species:req`, `level:req`, `item=ITEM_NONE`, `species2=SPECIES_NONE`, `level2=0`, `item2=ITEM_NONE`
`setwildbattle` is a much simpler way to generate a Wild Pokemon ready for the player to fight.
`setwildbattle` is a much simpler way to generate a wildmon ready for the player to fight.
It only takes 3 arguments (or 6 if you want to make it a double wild battle)
`species` and `level` refers to the species id and the level of the pokemon you want to generate
`item` refers to the item the mon is holding. If the argument is missing, the mon won't be holding anything
`species2`, `level2`, and `item2` are relate to the species, level and item of the second mon generated in case you want to make a double wild battle
The other properties will like IVs and personality will be set random just like for a regular wild battle
Mons generated with `setwildbattle` will always be considered static encounters (STATIC_WILDMON_ORIGIN) and will thus be eligible to be affected by Synchronize and Cute Charm
### Synchronize and Cute Charm
- `species` and `level` refer to the `species` and the `level` of the Pokemon you want to generate.
- `item` refers to the item the Pokemon is holding. If no argument is provided the Pokemon will not be holding an item.
- `species2`, `level2`, and `item2` determine the `species`, `level` and `held item` respectively of the second Pokemon generated in case you want to make a double wild battle
- The other properties like IVs and personality will be set randomly the same way as they are set in regular wild battles.
`nature` and `gender` also accept `NATURE_MAY_SYNCHRONIZE` and `MON_GENDER_MAY_CUTE_CHARM` respectively as arguments
`NATURE_RANDOM` and `MON_GENDER_RANDOM` always return a random nature/gender and never check for Synchronize or Cute Charm. If you want the generated mon to have a chance to receive the effcets of Synchronize or Cute Charm, you need to use `NATURE_MAY_SYNCHRONIZE` and `MON_GENDER_MAY_CUTE_CHARM` respectively for nature and gender.
When you use `NATURE_MAY_SYNCHRONIZE` or `MON_GENDER_MAY_CUTE_CHARM`, you are telling the game can check if the player has a pokemon with Synchronize or Cute Charm in the first slot of its party and roll a die to see if the nature or gender should be fixed based on the ability or rolled normally.
The mon generated also need to be of the right "origin" to be eligible for Synchronize or Cute Charm. We don't want to "synchronize" a mon belonging to a trainer or change the gender of a gift mon with Cute Charm. So if a mon is generated for the player side, it will be considered a "gift mon" (GIFTMON_ORIGIN) and if a mon is generated on the enemy side, it will be considered a static wild encounter (STATIC_WILDMON_ORIGIN).
`givemon` will default to use `NATURE_MAY_SYNCHRONIZE` and `MON_GENDER_MAY_CUTE_CHARM` because we assume you will use givemon to create "gift mons" but if you don't want it to apply in a specific script, you can explicitly use `NATURE_RANDOM` and `MON_GENDER_RANDOM` instead
`createmon` on the other hand default to `NATURE_RANDOM` and `MON_GENDER_RANDOM` so you need to explicitly use `NATURE_MAY_SYNCHRONIZE` or `MON_GENDER_MAY_CUTE_CHARM` for the generated mon to be considered a gift mon or a static wild encounter
Static encounters and Gift mons eligibility to Synchronize vary through generations in the official games so you can use the config `OW_SYNCHRONIZE_NATURE` to match your preference or you can check the `src/ow_synchronize.c` to modify the Synchronize and Cute Charm eligibility of different origins however you like
Pokemon generated with `setwildbattle` will always be considered static encounters (`STATIC_WILDMON_ORIGIN`) and will thus be eligible to be affected by Synchronize and Cute Charm
#### Synchronize and Cute Charm
`nature` and `gender` also accept `NATURE_MAY_SYNCHRONIZE` and `MON_GENDER_MAY_CUTE_CHARM` respectively as arguments.
`NATURE_RANDOM` and `MON_GENDER_RANDOM` always return a random nature/gender and never check for Synchronize or Cute Charm. If you want the generated Pokemon to have a chance to receive the effects of Synchronize or Cute Charm, you need to use `NATURE_MAY_SYNCHRONIZE` and `MON_GENDER_MAY_CUTE_CHARM` respectively for nature and gender.
When you use `NATURE_MAY_SYNCHRONIZE` or `MON_GENDER_MAY_CUTE_CHARM`, you are telling the game can check if the player has a Pokemon with Synchronize or Cute Charm in the first slot of its party and roll a die to see if the nature or gender should be fixed based on the ability or rolled normally.
The Pokemon generated also need to be of the right "origin" to be eligible for Synchronize or Cute Charm. We don't want to "synchronize" a Pokemon belonging to a trainer or change the gender of a gift Pokemon with Cute Charm. So if a Pokemon is generated for the player side, it will be considered a "gift Pokemon" (`GIFTMON_ORIGIN`) and if a Pokemon is generated on the enemy side, it will be considered a static wild encounter (`STATIC_WILDMON_ORIGIN`).
`givemon` will default to use `NATURE_MAY_SYNCHRONIZE` and `MON_GENDER_MAY_CUTE_CHARM` because we assume you will use `givemon` to create "gift Pokemon" but if you don't want it to apply in a specific script, you can explicitly use `NATURE_RANDOM` and `MON_GENDER_RANDOM` instead
`createmon` on the other hand default to `NATURE_RANDOM` and `MON_GENDER_RANDOM` so you need to explicitly use `NATURE_MAY_SYNCHRONIZE` or `MON_GENDER_MAY_CUTE_CHARM` for the generated Pokemon to be considered a gift Pokemon or a static wild encounter
Static encounters and Gift Pokemon eligibility to Synchronize vary through generations in the official games so you can use the config `OW_SYNCHRONIZE_NATURE` to match your preference or you can check the `src/ow_abilities.c` to modify the Synchronize and Cute Charm eligibility of different origins however you like
---
## Through Code
A lot of places in the game generate mons, when you start a wild encounter, when a trainer generates its party from the data in trainers.party or even when you call one of the script described in the previous section. So let's go through some of the most common functions.
Note: None of the functions described here allocate memory for the Pokemon struct, they all expect a pointer they will fill the data with.
There are several instances of Pokemon generation throughout the game:
- When you start a wild encounter,
- When a trainer generates its party from the data in `trainers.party`,
- Or when you call one of the scripts described in the previous section.
In this section we will go through some of the most common functions.
> NOTE: None of the functions described here allocate memory for the Pokemon struct, they all expect a pointer they will fill the data with.
It means they are usually called with `&gParties[B_TRAINER_0][index]` or `&gParties[B_TRAINER_1][index]` because these are places in memory reserved for Pokemon struct.
### The basics
### The Basics
To generate a mon ready for battle, you usually need to go through these steps:
- generate a personality value
- fill the generic mon structure based on species, level and personality
- set IVs and EVs
- (re-)compute stats (this step does not happen if you are generating a `struct boxPokemon` instead of `struct Pokemon`)
- set the moves
To generate a Pokemon ready for battle, you usually need to go through the following steps:
1. Generate a personality value
2. Fill the generic mon structure based on `species`, `level` and `personality`.
3. Set IVs and EVs.
4. (Re-)Compute stats (this step does not happen if you are generating a `struct BoxPokemon` instead of `struct Pokemon`).
5. set the moves.
### `GetMonPersonality`
`GetMonPersonality` is the easiest way to make a personality value. It takes 4 arguments `species`, `gender`, `nature` and `unownLetter` then it rolls random personality values until it finds one that match all the selected criteria.
For example, if you want a personality for Wally's male Ralts, you would write:
`personality = GetMonPersonality(SPECIES_RALTS, MON_MALE, NATURE_RANDOM, RANDOM_UNOWN_LETTER);`
and if you want a personality for a brave J Unown, you would write
`personality = GetMonPersonality(SPECIES_UNOWN, MON_GENDER_RANDOM, NATURE_BRAVE, 9);` (J is the 10th letter of alphabet but for unown A starts at 0 so B is 1 and J is 9)
As you can see, you can use either a specific value or a special value `MON_GENDER_RANDOM`, `NATURE_RANDOM` or `RANDOM_UNOWN_LETTER` to tell the function to return any pokemon matching the other properties. This means, writing:
`personality = GetMonPersonality(SPECIES_X, MON_GENDER_RANDOM, NATURE_RANDOM, RANDOM_UNOWN_LETTER);`
id equivalent to
`personality = Random32();`
which is why `Random32()` is used throughout to generate a personality value in certain circumstances
```c
personality = GetMonPersonality(SPECIES_RALTS, MON_MALE, NATURE_RANDOM, RANDOM_UNOWN_LETTER);
```
And if you want a personality for a brave J Unown, you would write:
```c
personality = GetMonPersonality(SPECIES_UNOWN, MON_GENDER_RANDOM, NATURE_BRAVE, 9);
// J is the 10th letter of alphabet but for Unown A starts at 0 so B is 1 and J is 9
```
As you can see, you can use either a specific value or a special value `MON_GENDER_RANDOM`, `NATURE_RANDOM` or `RANDOM_UNOWN_LETTER` to tell the function to return any Pokemon matching the other properties. This means, writing:
```
personality = GetMonPersonality(SPECIES_X, MON_GENDER_RANDOM, NATURE_RANDOM, RANDOM_UNOWN_LETTER);
```
is equivalent to `personality = Random32();` which is why `Random32()` is used throughout the codebase to generate fully random personality values.
---
### `CreateMon` and `CreateBoxMon`
`CreateMon` is the most basic function to create a generic Pokemon struct. It takes 5 arguments:
`mon` is a pointer to the mon struct you want to set the data for
`species` and `level` are fairly explicit
`personality` is the personality value you want to use to create your mon, it will determine a lot of your mon properties and will usually be a number that you generated by following the instructions from the previous section
`trainerID` is a special type of struct that explain how the function should set up the otId of the pokemon. Usually you will want to use one of these 3 macros for the argument:
`OTID_STRUCT_PLAYER_ID` which means the pokemon should the player otId (used for wildmon so they the get the player id when captured)
`OTID_STRUCT_PRESET(value)` which set a specific otId that you pick/write yourself
`OTID_STRUCT_RANDOM_NO_SHINY` which picks a random otId and forces the mon to not be shiny even if the random otId and chosen personality would have made the mon shiny. It is used by NPC trainers.
Both `CreateMon` and `CreateBoxMon` erase the pokemon data in the pointer before they add the new data so every value they don't set will be zero-ed in some way. This is why they are considered "base" functions.
The values set by `CreateMon` and `CreateBoxMon` are the gender, ability num, tera type and nature (based on personality); the met info (location, level and game), the original trainer name, gender and language (always set to the player even for enemy trainer mons), the starting xp (based on the xp required to reach the level the pokemon is at), the starting friendship (based on the species info) and the shinyness.
`CreateMon` and `CreateBoxMon` are very similar. In fact, the `CreateMon` function calls `CreateBoxMon` to generate the boxmon part of its structure but it also sets the level and a mail object (to an empty value)
- `mon` is a pointer to the Pokemon struct you want to set the data for
- `species` and `level` are self-explanatory.
- `personality` is the personality value you want to use to create your Pokemon, it will determine a lot of your Pokemon properties and will usually be a number that you generated by following the instructions from the previous section.
- `trainerID` is a special type of struct that explain how the function should set up the `otId` of the Pokemon. Usually you will want to use one of these 3 macros for the argument:
- `OTID_STRUCT_PLAYER_ID` which means the Pokemon will use the player `otId` (used by Wild Pokemon so they the get the player id when captured)
- `OTID_STRUCT_PRESET(value)` which set a specific `otId` that you pick/write yourself
- `OTID_STRUCT_RANDOM_NO_SHINY` which picks a random `otId` and forces the Pokemon to not be shiny even if the random `otId` and chosen `personality` would have made the Pokemon shiny. It is used by NPC trainers.
- Both `CreateMon` and `CreateBoxMon` erase the Pokemon data in the pointer before they add the new data so every value they don't set will be zero-ed in some way. This is why they are considered "base" functions.
The values set by `CreateMon` and `CreateBoxMon` are the `gender`, `ability num`, `tera type` and `nature` (based on personality); the `met info` (location, level and game), the `OT name` , `gender` and `language` (always set to the player even for enemy trainer Pokemon), the `starting xp` (based on the xp required to reach the level the Pokemon is at), the `starting friendship` (based on the species info) and the `shinyness`.
`CreateMon` and `CreateBoxMon` are very similar. In fact, the `CreateMon` function calls `CreateBoxMon` to generate the BoxPokemon part of its structure but it also sets the level and a mail object (to an empty value)
### Setting IVs and EVs
Usually, you will want to use `SetBoxMonIVs(mon->box, ivs)` to set the ivs of the pokemon you are generating. The reason is that not only can you quickly set all ivs of your mon to a single value: `SetBoxMonIVs(mon->box, 15)` will set all IVs to 15. You can also use the special argument `USE_RANDOM_IVS`. When used with `USE_RANDOM_IVS`, `SetBoxMonIvs` will not only pick a random value between 0 and 31 for each stat, it will also allocate some perfect iv if the species of the mon has a perfectIvCount set in the species data. For example if you are generating a legendary with a perfectIvCount of 3, using `SetBoxMonIVs(mon->box, USE_RANDOM_IVS)` will guarantee that at least 3 IVs are set to 31. This is done using the function `SetBoxMonPerfectIVs`, which can also be used elsewhere to assign a number of random perfect IVs.
#### IVs
Usually, you will want to use `SetBoxMonIVs(mon->box, ivs)` to set the IVs of the Pokemon being generated. The reason is that not only can you quickly set all ivs of your Pokemon to a single value.
For instance `SetBoxMonIVs(mon->box, 15)` will set all IVs to 15.
You can also use the special argument `USE_RANDOM_IVS`. When used with `USE_RANDOM_IVS`, `SetBoxMonIvs` will not only pick a random value between 0 and 31 for each stat, it will also allocate some perfect iv if the species of the Pokemon has a `perfectIvCount` set in the species data.
For example if you are generating a legendary with a `perfectIvCount` of 3, using `SetBoxMonIVs(mon->box, USE_RANDOM_IVS)` will guarantee that at least 3 IVs are set to 31. This is done using the function `SetBoxMonPerfectIVs`, which can also be used elsewhere to assign a number of random perfect IVs.
The other way to assign IV is to use `SetMonData`, for example:
`SetMonData(mon, MON_DATA_HP_IV, 15)`
with the stats being in order `MON_DATA_HP_IV`, `MON_DATA_ATK_IV`, `MON_DATA_DEF_IV`, `MON_DATA_SPEED_IV`, `MON_DATA_SPATK_IV` and `MON_DATA_SPDEF_IV`
```c
SetMonData(mon, MON_DATA_HP_IV, 15)
```
with the stats being in the order `MON_DATA_HP_IV`, `MON_DATA_ATK_IV`, `MON_DATA_DEF_IV`, `MON_DATA_SPEED_IV`, `MON_DATA_SPATK_IV` and `MON_DATA_SPDEF_IV`
You can also use a loop like this:
```c
for (i = 0; i < NUM_STATS; i++)
SetMonData(mon, MON_DATA_HP_IV + i, iv_array[i])
```
if you happen to have an array containing the values you want but be careful about the order if you use this method
EVs default to 0 when a mon is generated with `CreateMon` or `CreateBoxMon` so you may not need to anything if you want to keep it that way. If you want to change them, there are currently no utilities to set EVs at the moment so you will need to use the `SetMonData` method as well:
`SetMonData(mon, MON_DATA_HP_EV, 252)`
The stats have the same names as the IVs with I cheange into an E : `MON_DATA_HP_EV`, `MON_DATA_ATK_EV`, `MON_DATA_DEF_EV`, `MON_DATA_SPEED_EV`, `MON_DATA_SPATK_EV` and `MON_DATA_SPDEF_EV` so the loop method works here too
if you happen to have an array containing the values you want. However, you must be careful about the order of the stats if you use this method.
#### EVs
EVs default to `0` when a Pokemon is generated with `CreateMon` or `CreateBoxMon`. You will not need to anything if you want to keep it that way.
If you want to change them, there are currently no utilities to set EVs. Thus you will need to use the `SetMonData` method:
```c
SetMonData(mon, MON_DATA_HP_EV, 252)
```
The stats have the same names as the IVs with I change into an E : `MON_DATA_HP_EV`, `MON_DATA_ATK_EV`, `MON_DATA_DEF_EV`, `MON_DATA_SPEED_EV`, `MON_DATA_SPATK_EV` and `MON_DATA_SPDEF_EV` so the loop method works here as well.
```c
for (i = 0; i < NUM_STATS; i++)
SetMonData(mon, MON_DATA_HP_EV + i, ev_array[i])
```
Just be careful when setting IVs and EVs with SetMonData because they are no check to make sure the IV and EV values you are setting are valid and this may cause some issues.
Just be careful when setting IVs and EVs with `SetMonData` because there is no check to make sure the IV and EV values you are setting are valid and this may cause some issues.
### `CalculateMonStats`
After all the IVs and EVs have been set for your mon, it's important to run `CalculateMonStats(mon)`. The function only has one argument so it's pretty simple but don't forget this step or you may have some isseus
After all the IVs and EVs have been set for your Pokemon, it's important to run `CalculateMonStats(mon)`. The function only has one argument so it's pretty simple but don't forget this step or you may have some issues.
### Setting moves
To set a move in slot `slot`, you would need to write:
```c
enum Move move = MOVE_X;
u32 pp = GetMovePP(move);
SetMonData(mon, MON_DATA_MOVE1 + slot, &move);
SetMonData(mon, MON_DATA_PP1 + slot, &spp);
```
where slot can be between 0 and 3 to represent the 1st to 4th move
You can also call the function `GiveMonInitialMoveset(mon)` that will give your pokemon its last 4 level-up moves available similarly to the wild pokemon you might enciunter
You can also call the function `GiveMonInitialMoveset(mon)` that will give your Pokemon its last 4 level-up moves available similarly to the wild Pokemon you might encounter.
## Going from 1.14 to 1.15
When going from 1.14 to 1.15, Expansion deleted multiple functions related to mon generation that were not used in Expansion anymore.
When going from 1.14 to 1.15, Expansion deleted multiple functions related to Pokemon generation that were not used in Expansion anymore.
The following functions were deleted
```c
void CreateMonWithNature(struct Pokemon *mon, u16 species, u8 level, u8 fixedIV, u8 nature);
void CreateMonWithGenderNatureLetter(struct Pokemon *mon, u16 species, u8 level, u8 fixedIV, u8 gender, u8 nature, u8 unownLetter);
@ -140,14 +255,18 @@ void CreateMonWithIVsOTID(struct Pokemon *mon, u16 species, u8 level, u8 *ivs, u
void CreateMonWithEVSpread(struct Pokemon *mon, u16 species, u8 level, u8 fixedIV, u8 evSpread);
void CreateMonWithEVSpreadNatureOTID(struct Pokemon *mon, u16 species, u8 level, u8 nature, u8 fixedIV, u8 evSpread, u32 otId);
```
If you add custom code relying on those functions, I would advise to simple recode them using the methods described in the previous section. If you follow the steps, you should be able to rewrite a function with the same effect easily.
However the move to 1.15 also completely rewrote these two functions:
```c
void CreateMon(struct Pokemon *mon, u16 species, u8 level, u8 fixedIV, u8 hasFixedPersonality, u32 fixedPersonality, u8 otIdType, u32 fixedOtId);
void CreateBoxMon(struct BoxPokemon *boxMon, u16 species, u8 level, u8 fixedIV, u8 hasFixedPersonality, u32 fixedPersonality, u8 otIdType, u32 fixedOtId);
```
`CreateMon` and `CreateBoxMon` now have different arguments and do less things than their 1.14 counterparts. If you have code that used those functions, we recommend you use these legacy version of `CreateMon` and `CreateBoxMon`:
```c
void CreateMonLegacy(struct Pokemon *mon, u16 species, u8 level, u8 fixedIV, u8 hasFixedPersonality, u32 fixedPersonality, u8 otIdType, u32 fixedOtId)
{
@ -166,4 +285,5 @@ void CreateBoxMonLegacy(struct BoxPokemon *boxMon, u16 species, u8 level, u8 fix
GiveBoxMonInitialMoveset(boxMon);
}
```
These two legacy functions use the 1.15 functions to recreate the 1.14 versions of `CreateMon` and `CreateBoxMon` with the same arguments and the same effects. Add these two legacy functions to your code then change your custom code that was relying on 1.14 `CreateMon` or `CreateBoxMon` to use `CreateMonLegacy` or `CreateBoxMonLegacy` instead and everything should work the same as before

View File

@ -0,0 +1,19 @@
JASC-PAL
0100
16
180 180 180
49 49 49
74 74 98
200 243 230
176 227 212
156 210 194
144 200 184
136 189 173
122 177 161
104 160 144
123 123 148
123 123 123
98 98 123
255 255 255
239 251 248
82 82 82

View File

@ -2,18 +2,18 @@ JASC-PAL
0100
16
180 180 180
164 226 197
238 242 230
139 170 180
8 113 115
0 157 156
106 222 172
41 182 189
90 206 172
90 182 180
8 0 0
65 190 189
115 194 189
0 117 131
0 129 131
0 12 0
64 128 115
92 163 114
101 169 99
69 171 140
115 191 132
72 141 162
83 204 163
72 176 192
49 49 49
149 210 138
137 225 177
190 237 195
143 224 228
255 255 255
0 0 0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 B

After

Width:  |  Height:  |  Size: 266 B

View File

@ -15,7 +15,7 @@ JASC-PAL
193 33 41
141 251 184
52 66 162
194 181 66
0 0 0
0 0 0
123 131 0
255 255 255

View File

@ -15,7 +15,7 @@ JASC-PAL
193 33 41
141 251 184
52 66 162
194 181 66
74 74 74
0 0 0
123 131 0
255 255 255

View File

@ -15,7 +15,7 @@ JASC-PAL
193 33 41
141 251 184
52 66 162
194 181 66
0 0 0
0 0 0
123 131 0
255 255 255

View File

@ -15,7 +15,7 @@ JASC-PAL
193 33 41
141 251 184
52 66 162
194 181 66
74 74 74
0 0 0
123 131 0
255 255 255

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -23,7 +23,7 @@
#include "battle_dynamax.h"
#include "battle_terastal.h"
#include "battle_gimmick.h"
#include "generational_changes.h"
#include "config_changes.h"
#include "item.h"
#include "move.h"
#include "random.h" // for rng_value_t
@ -201,6 +201,7 @@ struct SimulatedDamage
u16 minimum;
u16 median;
u16 maximum;
u16 random;
};
// Ai Data used when deciding which move to use, computed only once before each turn's start.
@ -234,7 +235,8 @@ struct AiLogicData
u32 shouldConsiderExplosion:1; // Determines whether AI should consider explosion moves this turn
u32 shouldSwitch:4; // Stores result of ShouldSwitch, which decides whether a mon should be switched out
u32 shouldConsiderFinalGambit:1; // Determines whether AI should consider Final Gambit this turn
u32 padding2:19;
u32 switchInCalc:1; // Indicates if we're doing switch in calcs, this is purely for Retaliate damage calcs
u32 padding2:18;
};
struct AiThinkingStruct

View File

@ -136,6 +136,8 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData);
void ResetDynamicAiFunctions(void);
void AI_TrySwitchOrUseItem(enum BattlerId battler);
void CalcBattlerAiMovesData(struct AiLogicData *aiData, enum BattlerId battlerAtk, enum BattlerId battlerDef, u32 weather, u32 fieldStatus);
void AIDebugTimerStart(void);
void AIDebugTimerEnd(void);
extern AiSwitchFunc gDynamicAiSwitchFunc;

View File

@ -27,6 +27,8 @@ enum ShouldSwitchScenario
SHOULD_SWITCH_NATURAL_CURE_WEAK_STATS_RAISED,
SHOULD_SWITCH_REGENERATOR,
SHOULD_SWITCH_REGENERATOR_STATS_RAISED,
SHOULD_SWITCH_INTIMIDATE,
SHOULD_SWITCH_INTIMIDATE_STATS_RAISED,
SHOULD_SWITCH_ENCORE_STATUS,
SHOULD_SWITCH_ENCORE_DAMAGE,
SHOULD_SWITCH_CHOICE_LOCKED,

View File

@ -7,19 +7,24 @@
// Roll boundaries used by AI when scoring. Doesn't affect actual damage dealt.
#define MAX_ROLL_PERCENTAGE DMG_ROLL_PERCENT_HI
#define MIN_ROLL_PERCENTAGE DMG_ROLL_PERCENT_LO
#define DMG_ROLL_PERCENTAGE ((MAX_ROLL_PERCENTAGE + MIN_ROLL_PERCENTAGE + 1) / 2) // Controls the damage roll the AI sees for the default roll. By default the 9th roll is seen
#define DMG_ROLL_PERCENTAGE ((MAX_ROLL_PERCENTAGE + MIN_ROLL_PERCENTAGE + 1) / 2) // Controls the damage roll the AI sees for the median roll. By default the 9th roll is seen
enum DamageRollType
{
DMG_ROLL_LOWEST,
DMG_ROLL_DEFAULT,
DMG_ROLL_MEDIAN,
DMG_ROLL_HIGHEST,
DMG_ROLL_RANDOM,
};
enum DamageCalcContext
{
AI_DEFENDING,
AI_ATTACKING,
AI_SWITCHIN_DEFENDING,
AI_SWITCHIN_ATTACKING,
AI_SHOULD_SETUP_DEFENDING,
AI_ATTACKING_PARTNER,
};
enum AiConsiderEndure
@ -104,7 +109,7 @@ bool32 AI_CanBattlerEscape(enum BattlerId battler);
bool32 IsBattlerTrapped(enum BattlerId battlerAtk, enum BattlerId battlerDef);
s32 AI_WhoStrikesFirst(enum BattlerId battlerAI, enum BattlerId battler, enum Move aiMoveConsidered, enum Move playerMoveConsidered, enum ConsiderPriority considerPriority);
bool32 CanTargetFaintAi(enum BattlerId battlerDef, enum BattlerId battlerAtk);
u32 NoOfHitsForTargetToFaintBattler(enum BattlerId battlerDef, enum BattlerId battlerAtk, enum AiConsiderEndure considerEndure);
u32 NoOfHitsForTargetToFaintBattler(enum BattlerId battlerDef, enum BattlerId battlerAtk, enum DamageCalcContext calcContext, enum AiConsiderEndure considerEndure);
void GetBestDmgMovesFromBattler(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum DamageCalcContext calcContext, enum Move *bestMoves);
u32 GetMoveIndex(enum BattlerId battler, enum Move move);
bool32 IsBestDmgMove(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum DamageCalcContext calcContext, enum Move move);
@ -256,6 +261,7 @@ bool32 AI_IsBattlerAsleepOrComatose(enum BattlerId battlerId);
// ability logic
bool32 IsMoxieTypeAbility(enum Ability ability);
bool32 DoesAbilityRaiseStatsWhenLowered(enum Ability ability);
bool32 DoesIntimidateRaiseStats(enum Ability ability);
bool32 ShouldTriggerAbility(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum Ability ability);
bool32 CanEffectChangeAbility(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum Move move, struct AiLogicData *aiData);
void AbilityChangeScore(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum Move move, s32 *score, struct AiLogicData *aiData);
@ -322,7 +328,6 @@ bool32 IsBattlerPredictedToSwitch(enum BattlerId battler);
enum Move GetIncomingMove(enum BattlerId battler, enum BattlerId opposingBattler, struct AiLogicData *aiData);
enum Move GetIncomingMoveSpeedCheck(enum BattlerId battler, enum BattlerId opposingBattler, struct AiLogicData *aiData);
bool32 AI_OpponentCanFaintAiWithMod(enum BattlerId battler, u32 healAmount);
void SetBattlerFieldStatusForSwitchin(enum BattlerId battler);
bool32 ShouldInstructPartner(enum BattlerId partner, enum Move move);
bool32 CanMoveBeBouncedBack(enum BattlerId battler, enum Move move);

View File

@ -121,6 +121,8 @@ struct BattleContext
enum HoldEffect holdEffectAtk;
enum HoldEffect holdEffectDef;
u8 aiTurnOrder[MAX_BATTLERS_COUNT];
// Flags
u32 isCrit:1;
u32 randomFactor:1;
@ -166,7 +168,6 @@ enum SubCheck
};
void HandleAction_ThrowBall(void);
uq4_12_t CalcTypeEffectivenessMultiplierHelper(enum Move move, enum Type moveType, enum BattlerId battlerAtk, enum BattlerId battlerDef, enum Ability abilityAtk, enum Ability abilityDef, bool32 recordAbilities);
u32 GetCurrentBattleWeather(void);
bool32 EndOrContinueWeather(void);
enum DamageCategory GetReflectDamageMoveDamageCategory(enum BattlerId battler, enum Move move);
@ -212,7 +213,6 @@ void TryClearRageAndFuryCutter(void);
bool32 HasNoMonsToSwitch(enum BattlerId battler, u8 partyIdBattlerOn1, u8 partyIdBattlerOn2);
bool32 TryChangeBattleWeather(enum BattlerId battler, u32 battleWeatherId, enum Ability ability);
bool32 TryChangeBattleTerrain(enum BattlerId battler, u32 statusFlag);
bool32 CanAbilityBlockMove(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum Ability abilityAtk, enum Ability abilityDef, u32 move, enum ResultOption option);
bool32 CanTargetBlockPranksterMove(struct BattleContext *ctx, s32 movePriority);
bool32 CanPsychicTerrainProtectTarget(struct BattleContext *ctx, s32 movePriority);
bool32 CanMoveBeBlockedByTarget(struct BattleContext *ctx, s32 movePriority);
@ -424,5 +424,6 @@ void TryResetConsecutiveUseCounter(enum BattlerId battler);
void SetOrClearRageVolatile(void);
enum BattlerId GetTargetBySlot(enum BattlerId battlerAtk, enum BattlerId battlerDef);
bool32 IsNaturalEnemy(enum Species speciesAttacker, enum Species speciesTarget);
enum Stat GetDownloadStat(enum BattlerId battler);
#endif // GUARD_BATTLE_UTIL_H

View File

@ -46,6 +46,8 @@
#define SHOULD_SWITCH_NATURAL_CURE_WEAK_STATS_RAISED_PERCENTAGE 10
#define SHOULD_SWITCH_REGENERATOR_PERCENTAGE 50
#define SHOULD_SWITCH_REGENERATOR_STATS_RAISED_PERCENTAGE 20
#define SHOULD_SWITCH_INTIMIDATE_PERCENTAGE 25
#define SHOULD_SWITCH_INTIMIDATE_STATS_RAISED_PERCENTAGE 10
// AI switchin considerations
#define ALL_MOVES_BAD_STATUS_MOVES_BAD FALSE // If the AI has no moves that affect the target, ShouldSwitchIfAllMovesBad can prompt a switch. Enabling this config will ignore status moves that can affect the target when making this decision.
@ -84,6 +86,21 @@
#define AI_DAMAGES_THROUGH_BERRIES TRUE // AI will see through resist berries when considering a certain KO threshold for the purposes damage calcs; this is considered when comparing best moves to KO to still pick the actual OHKO if needed
#define AI_IGNORE_BERRY_KO_THRESHOLD 2 // KO threshold AI must meet in order to treat it berry though it doesn't exist (ie. 2 means "If the AI can 2HKO with berry resisted attack + not-berry resisted next attack, ignore berry resistence when calcing first attack"). Requires AI_DAMAGES_THROUGH_BERRIES
// AI damage calc roll considerations
#define AI_ROLL_MIN 1
#define AI_ROLL_MEDIAN 2
#define AI_ROLL_MAX 3
#define AI_ROLL_RANDOM 4
#define AI_ROLL_TYPE_COUNT 5
// Define which roll type to use in each context; overridden by AI_FLAG_RISKY and AI_FLAG_CONSERVATIVE
#define AI_ROLL_ATTACKING AI_ROLL_MAX
#define AI_ROLL_DEFENDING AI_ROLL_MEDIAN
#define AI_ROLL_SWITCHIN_ATTACKING AI_ROLL_MEDIAN
#define AI_ROLL_SWITCHIN_DEFENDING AI_ROLL_MEDIAN
#define AI_ROLL_SHOULD_SETUP_DEFENDING AI_ROLL_MAX
#define AI_ROLL_ATTACKING_PARTNER AI_ROLL_MAX
// AI prediction chances
#define PREDICT_SWITCH_CHANCE 50
#define PREDICT_MOVE_CHANCE 100

View File

@ -56,6 +56,7 @@
#define B_TAILWIND_TURNS GEN_LATEST // In Gen5+, Tailwind lasts 4 turns instead of 3.
#define B_SLEEP_TURNS GEN_LATEST // In Gen5+, sleep lasts for 2-4 turns instead of 2-5 turns.
#define B_TAUNT_TURNS GEN_LATEST // In Gen5+, Taunt lasts 3 turns if the user acts before the target, or 4 turns if the target acted before the user. In Gen3, taunt lasts 2 turns and in Gen 4, 3-5 turns.
#define B_ENCORE_TURNS GEN_LATEST // In Gen5+, Encore lasts 3 turns if the target hasn't yet moved this turn, or 4 turns if it has. In Gen4, it lasts 3-7 turns. In Gen2-3, 2-6 turns.
#define B_SPORT_TURNS GEN_LATEST // In Gen6+, Water/Mud Sport last 5 turns, even if the user switches out.
#define B_MEGA_EVO_TURN_ORDER GEN_LATEST // In Gen7, a Pokémon's Speed after Mega Evolution is used to determine turn order, not its Speed before.
#define B_RECALC_TURN_AFTER_ACTIONS GEN_LATEST // In Gen8+, switching/using a move affects the current turn's order of actions, better known as dynamic speed.

View File

@ -1,7 +1,7 @@
#ifndef GUARD_GENERATIONAL_CHANGES_H
#define GUARD_GENERATIONAL_CHANGES_H
#ifndef GUARD_CONFIG_CHANGES_H
#define GUARD_CONFIG_CHANGES_H
#include "constants/generational_changes.h"
#include "constants/config_changes.h"
#include "config/battle.h"
#include "config/pokerus.h"
#include "config/ai.h"
@ -30,4 +30,4 @@ void TestInitConfigData(void);
void TestFreeConfigData(void);
#endif
#endif // GUARD_GENERATIONAL_CHANGES_H
#endif // GUARD_CONFIG_CHANGES_H

View File

@ -4,8 +4,6 @@
#define PARTNER_NONE 0
#define PARTNER_STEVEN 1
#define PARTNER_DUMMY 2
#define PARTNER_COUNT 3
//Tests need PARTNER_COUNT to be at least 3 so we add a dummy partner
#define PARTNER_COUNT 2
#endif // GUARD_CONSTANTS_BATTLE_PARTNERS_H

View File

@ -1,5 +1,5 @@
#ifndef GUARD_CONSTANTS_GENERATIONAL_CHANGES_H
#define GUARD_CONSTANTS_GENERATIONAL_CHANGES_H
#ifndef GUARD_CONSTANTS_CONFIG_CHANGES_H
#define GUARD_CONSTANTS_CONFIG_CHANGES_H
/* Config definitions */
#define BATTLE_CONFIG_DEFINITIONS(F) \
@ -55,6 +55,7 @@
F(B_TAILWIND_TURNS, tailwindTurns, (u32, GEN_COUNT - 1)) \
F(B_SLEEP_TURNS, sleepTurns, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \
F(B_TAUNT_TURNS, tauntTurns, (u32, GEN_COUNT - 1)) \
F(B_ENCORE_TURNS, encoreTurns, (u32, GEN_COUNT - 1)) \
F(B_SPORT_TURNS, sportTurns, (u32, GEN_COUNT - 1)) /* TODO: use in tests */ \
F(B_MEGA_EVO_TURN_ORDER, megaEvoTurnOrder, (u32, GEN_COUNT - 1)) \
F(B_RECALC_TURN_AFTER_ACTIONS, recalcTurnAfterActions, (u32, GEN_COUNT - 1)) \
@ -232,6 +233,7 @@
F(POKERUS_WEAK_VARIANT, pokerusWeakVariant, (u32, TRUE)) \
#define AI_CONFIG_DEFINITIONS(F) \
F(AI_ROLL_ATTACKING, aiRollAttacking, (u32, AI_ROLL_TYPE_COUNT - 1)) \
#define GET_CONFIG_MAXIMUM(_typeMaxValue, ...) INVOKE_WITH_B(GET_CONFIG_MAXIMUM_, _typeMaxValue)
#define GET_CONFIG_MAXIMUM_(_type, ...) FIRST(__VA_OPT__(FIRST(__VA_ARGS__),) MAX_BITS((sizeof(_type) * 8)))
@ -246,4 +248,4 @@ enum ConfigTag
CONFIG_COUNT
};
#endif // GUARD_CONSTANTS_GENERATIONAL_CHANGES_H
#endif // GUARD_CONSTANTS_CONFIG_CHANGES_H

View File

@ -6,6 +6,9 @@ enum DifficultyLevel
DIFFICULTY_EASY,
DIFFICULTY_NORMAL, //If you rename this, the word "Normal" in fprint_trainers must be replaced with the new difficulty name.
DIFFICULTY_HARD,
#if TESTING
DIFFICULTY_TEST,
#endif
DIFFICULTY_COUNT,
};

View File

@ -103,7 +103,6 @@ enum Language
#define MAX_REMATCH_ENTRIES 100 // only REMATCH_TABLE_ENTRIES (78) are used
#define NUM_CONTEST_WINNERS 13
#define UNION_ROOM_KB_ROW_COUNT 10
#define GIFT_RIBBONS_COUNT 11
#define SAVED_TRENDS_COUNT 5
#define PYRAMID_BAG_ITEMS_COUNT 10
#define ROAMER_COUNT 1 // Number of maximum concurrent active roamers

View File

@ -1690,7 +1690,11 @@ enum __attribute__((packed)) Species
SPECIES_TATSUGIRI_STRETCHY_MEGA = 1571,
SPECIES_GLIMMORA_MEGA = 1572,
SPECIES_EGG = (SPECIES_GLIMMORA_MEGA + 1),
SPECIES_CUSTOM_START = SPECIES_GLIMMORA_MEGA,
// Add any custom species between here and SPECIES_CUSTOM_END
SPECIES_CUSTOM_END,
SPECIES_EGG = SPECIES_CUSTOM_END,
NUM_SPECIES = SPECIES_EGG,
};

View File

@ -630,6 +630,8 @@ extern const u8 EventScript_SelectWithoutRegisteredItem[];
extern const u8 EventScript_WhiteOut[];
extern const u8 EventScript_AfterWhiteOutMomHeal[];
extern const u8 EventScript_AfterWhiteOutHeal[];
extern const u8 EventScript_AfterWhiteOutHeal_Frlg[];
extern const u8 EventScript_AfterWhiteOutHealMsgPreFirstBoss[];
extern const u8 EventScript_ResetMrBriney[];
extern const u8 EventScript_DoLinkRoomExit[];
extern const u8 CableClub_EventScript_TooBusyToNotice[];

View File

@ -1169,7 +1169,8 @@ struct SaveBlock1
#if FREE_LINK_BATTLE_RECORDS == FALSE
/*0x3150*/ struct LinkBattleRecords linkBattleRecords;
#endif //FREE_LINK_BATTLE_RECORDS
/*0x31A8*/ u8 giftRibbons[GIFT_RIBBONS_COUNT];
/*0x31A8*/ u8 giftRibbons[NUM_GIFT_RIBBONS];
u8 padding[4];
/*0x31B3*/ struct ExternalEventData externalEventData;
/*0x31C7*/ struct ExternalEventFlags externalEventFlags;
/*0x31DC*/ struct Roamer roamer[ROAMER_COUNT];

View File

@ -1525,6 +1525,7 @@ extern const u16 gItemIconPalette_GhostTMHM[];
extern const u16 gItemIconPalette_DragonTMHM[];
extern const u16 gItemIconPalette_DarkTMHM[];
extern const u16 gItemIconPalette_SteelTMHM[];
extern const u16 gItemIconPalette_MysteryTMHM[];
extern const u16 gItemIconPalette_FairyTMHM[];
// Charms
extern const u32 gItemIcon_OvalCharm[];

View File

@ -1,7 +1,7 @@
#ifndef GUARD_MOVES_H
#define GUARD_MOVES_H
#include "generational_changes.h"
#include "config_changes.h"
#include "contest_effect.h"
#include "constants/battle.h"
#include "constants/battle_factory.h"

View File

@ -161,6 +161,7 @@ enum RandomTag
RNG_SHED_SKIN,
RNG_SLEEP_TURNS,
RNG_TAUNT_TURNS,
RNG_ENCORE_TURNS,
RNG_SPEED_TIE,
RNG_STATIC,
RNG_STENCH,
@ -187,6 +188,7 @@ enum RandomTag
RNG_AI_SWITCH_ABSORBING_STAY_IN,
RNG_AI_SWITCH_NATURAL_CURE,
RNG_AI_SWITCH_REGENERATOR,
RNG_AI_SWITCH_INTIMIDATE,
RNG_AI_SWITCH_ENCORE,
RNG_AI_SWITCH_CHOICE_LOCKED,
RNG_AI_SWITCH_STATS_LOWERED,
@ -243,6 +245,7 @@ enum RandomTag
RNG_FISHING_GEN3_STICKY,
RNG_WILD_MON_TARGET,
RNG_AI_FAKE_OUT_SAVE_ALLY,
RNG_AI_DMG_ROLL_RANDOM,
};
#define RandomWeighted(tag, ...) \

View File

@ -540,7 +540,7 @@
#include "battle.h"
#include "battle_anim.h"
#include "data.h"
#include "generational_changes.h"
#include "config_changes.h"
#include "item.h"
#include "random.h"
#include "recorded_battle.h"

View File

@ -142,6 +142,20 @@ static s32 (*const sBattleAiFuncTable[])(enum BattlerId, enum BattlerId, enum Mo
};
// Functions
void AIDebugTimerStart()
{
// Set delay timer to count how long it takes for AI to choose action/move
gBattleStruct->aiDelayTimer = gMain.vblankCounter1;
CycleCountStart();
}
void AIDebugTimerEnd()
{
// We add to existing to compound multiple calls
gBattleStruct->aiDelayFrames += gMain.vblankCounter1 - gBattleStruct->aiDelayTimer;
gBattleStruct->aiDelayCycles += CycleCountEnd();
}
void BattleAI_SetupAIData(u8 defaultScoreMoves, enum BattlerId battler)
{
u32 moveLimitations;
@ -371,6 +385,9 @@ void ComputeBattlerDecisions(enum BattlerId battler)
gAiLogicData->aiCalcInProgress = TRUE;
if (DEBUG_AI_DELAY_TIMER)
AIDebugTimerStart();
// Setup battler and prediction data
BattleAI_SetupAIData(0xF, battler);
SetupAIPredictionData(battler, SWITCH_MID_BATTLE_OPTIONAL);
@ -390,6 +407,9 @@ void ComputeBattlerDecisions(enum BattlerId battler)
BattlerChooseNonMoveAction();
ModifySwitchAfterMoveScoring(battler);
if (DEBUG_AI_DELAY_TIMER)
AIDebugTimerEnd();
gAiLogicData->aiCalcInProgress = FALSE;
}
}
@ -714,8 +734,10 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData)
if (!(gBattleTypeFlags & BATTLE_TYPE_HAS_AI) && !IsWildMonSmart())
return;
// Set delay timer to count how long it takes for AI to choose action/move
gBattleStruct->aiDelayTimer = gMain.vblankCounter1;
gAiLogicData->aiCalcInProgress = TRUE;
if (DEBUG_AI_DELAY_TIMER)
AIDebugTimerStart();
aiData->weatherHasEffect = HasWeatherEffect();
weather = AI_GetWeather();
@ -723,9 +745,6 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData)
// get/assume all battler data and simulate AI damage
battlersCount = gBattlersCount;
gAiLogicData->aiCalcInProgress = TRUE;
if (DEBUG_AI_DELAY_TIMER)
CycleCountStart();
for (enum BattlerId battlerAtk = 0; battlerAtk < battlersCount; battlerAtk++)
{
if (!IsBattlerAlive(battlerAtk))
@ -756,8 +775,8 @@ void SetAiLogicDataForTurn(struct AiLogicData *aiData)
}
if (DEBUG_AI_DELAY_TIMER)
// We add to existing to compound multiple calls
gBattleStruct->aiDelayCycles += CycleCountEnd();
AIDebugTimerEnd();
gAiLogicData->aiCalcInProgress = FALSE;
}
@ -3208,8 +3227,8 @@ static s32 AI_DoubleBattle(enum BattlerId battlerAtk, enum BattlerId battlerDef,
bool32 hasTwoOpponents = HasTwoOpponents(battlerAtk);
bool32 hasPartner = HasPartner(battlerAtk);
u32 friendlyFireThreshold = GetFriendlyFireKOThreshold(battlerAtk);
u32 noOfHitsToKOPartner = GetNoOfHitsToKOBattler(battlerAtk, battlerAtkPartner, gAiThinkingStruct->movesetIndex, AI_ATTACKING, CONSIDER_ENDURE);
bool32 wouldPartnerFaint = hasPartner && CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, gAiThinkingStruct->movesetIndex, AI_ATTACKING) && !partnerProtecting;
u32 noOfHitsToKOPartner = GetNoOfHitsToKOBattler(battlerAtk, battlerAtkPartner, gAiThinkingStruct->movesetIndex, AI_ATTACKING_PARTNER, CONSIDER_ENDURE);
bool32 wouldPartnerFaint = hasPartner && CanIndexMoveFaintTarget(battlerAtk, battlerAtkPartner, gAiThinkingStruct->movesetIndex, AI_ATTACKING_PARTNER) && !partnerProtecting;
bool32 isFriendlyFireOK = !wouldPartnerFaint && (noOfHitsToKOPartner == 0 || noOfHitsToKOPartner > friendlyFireThreshold);
// check what effect partner is using
@ -4109,7 +4128,7 @@ static enum MoveComparisonResult CompareMoveTwoTurnEffect(enum BattlerId battler
static inline bool32 ShouldUseSpreadDamageMove(enum BattlerId battlerAtk, enum Move move, u32 moveIndex, u32 hitsToFaintOpposingBattler)
{
enum BattlerId partnerBattler = BATTLE_PARTNER(battlerAtk);
u32 noOfHitsToFaintPartner = GetNoOfHitsToKOBattler(battlerAtk, partnerBattler, moveIndex, AI_ATTACKING, CONSIDER_ENDURE);
u32 noOfHitsToFaintPartner = GetNoOfHitsToKOBattler(battlerAtk, partnerBattler, moveIndex, AI_ATTACKING_PARTNER, CONSIDER_ENDURE);
u32 friendlyFireThreshold = GetFriendlyFireKOThreshold(battlerAtk);
return (HasPartnerIgnoreFlags(battlerAtk)
&& noOfHitsToFaintPartner != 0 // Immunity check

View File

@ -35,11 +35,20 @@ struct IncomingHealInfo
static bool32 CanUseSuperEffectiveMoveAgainstOpponents(enum BattlerId battler);
static bool32 FindMonWithFlagsAndSuperEffective(enum BattlerId battler, u16 flags, u32 moduloPercent);
static u32 GetSwitchinHazardsDamage(enum BattlerId battler);
static u32 GetSwitchinSingleUseItemHealing(enum BattlerId battler, enum BattlerId opposingBattler, s32 currentHP);
static bool32 AI_CanSwitchinAbilityTrapOpponent(enum Ability ability, enum BattlerId opposingBattler);
static u32 GetBattlerTypeMatchup(enum BattlerId opposingBattler, enum BattlerId battler);
static u32 GetSwitchinHitsToKO(s32 damageTaken, enum BattlerId battler, const struct IncomingHealInfo *healInfo, u32 originalHp);
static void GetIncomingHealInfo(enum BattlerId battler, struct IncomingHealInfo *healInfo);
static u32 GetWishHealAmountForBattler(enum BattlerId battler);
static void SetBattlerStatusForSwitchin(enum BattlerId battler);
static void SetBattlerStatStagesForSwitchin(enum BattlerId battler, enum BattlerId opposingBattler, u32 fieldStatus);
static void SetBattlerHPChangeForSwitch(enum BattlerId battler, enum BattlerId opposingBattler);
static void SetBattlerVolatilesForSwitchin(enum BattlerId battler, u32 weather, u32 fieldStatus);
bool32 IsSwitchinTSpikesAffected(enum BattlerId battler);
static bool32 IsOpponentPhysicalAttacker(enum BattlerId battler, enum BattlerId opposingBattler);
static bool32 CanIntimidateLowerOpponentAtk(enum BattlerId battler, enum BattlerId opposingBattler);
static bool32 ShouldSwitchIfIntimidateBenefit(enum BattlerId battler);
static void InitializeSwitchinCandidate(enum BattlerId switchinBattler, u32 monIndex, struct Pokemon *mon)
{
@ -48,17 +57,24 @@ static void InitializeSwitchinCandidate(enum BattlerId switchinBattler, u32 monI
// Setup switchin battler data
gAiThinkingStruct->saved[switchinBattler].saved = TRUE;
SetBattlerAiData(switchinBattler, gAiLogicData);
SetBattlerFieldStatusForSwitchin(switchinBattler);
u32 switchinWeather = AI_GetSwitchinWeather(switchinBattler);
u32 switchinFieldStatus = AI_GetSwitchinFieldStatus(switchinBattler);
SetBattlerVolatilesForSwitchin(switchinBattler, switchinWeather, switchinFieldStatus);
SetBattlerStatusForSwitchin(switchinBattler);
gBattlerPartyIndexes[switchinBattler] = monIndex;
gAiLogicData->switchInCalc = TRUE;
for (enum BattlerId battlerIndex = 0; battlerIndex < gBattlersCount; battlerIndex++)
{
if (switchinBattler == battlerIndex || !IsBattlerAlive(battlerIndex))
continue;
CalcBattlerAiMovesData(gAiLogicData, switchinBattler, battlerIndex, AI_GetSwitchinWeather(switchinBattler), AI_GetSwitchinFieldStatus(switchinBattler));
CalcBattlerAiMovesData(gAiLogicData, battlerIndex, switchinBattler, AI_GetSwitchinWeather(switchinBattler), AI_GetSwitchinFieldStatus(switchinBattler));
SetBattlerStatStagesForSwitchin(switchinBattler, battlerIndex, switchinFieldStatus);
SetBattlerHPChangeForSwitch(switchinBattler, battlerIndex);
CalcBattlerAiMovesData(gAiLogicData, switchinBattler, battlerIndex, switchinWeather, switchinFieldStatus);
CalcBattlerAiMovesData(gAiLogicData, battlerIndex, switchinBattler, switchinWeather, switchinFieldStatus);
}
gAiLogicData->switchInCalc = FALSE;
gBattlerPartyIndexes[switchinBattler] = storeCurrBattlerPartyIndex;
gAiThinkingStruct->saved[switchinBattler].saved = FALSE;
}
@ -170,6 +186,10 @@ u32 GetSwitchChance(enum ShouldSwitchScenario shouldSwitchScenario)
return SHOULD_SWITCH_REGENERATOR_PERCENTAGE;
case SHOULD_SWITCH_REGENERATOR_STATS_RAISED:
return SHOULD_SWITCH_REGENERATOR_STATS_RAISED_PERCENTAGE;
case SHOULD_SWITCH_INTIMIDATE:
return SHOULD_SWITCH_INTIMIDATE_PERCENTAGE;
case SHOULD_SWITCH_INTIMIDATE_STATS_RAISED:
return SHOULD_SWITCH_INTIMIDATE_STATS_RAISED_PERCENTAGE;
case SHOULD_SWITCH_ENCORE_STATUS:
return SHOULD_SWITCH_ENCORE_STATUS_PERCENTAGE;
case SHOULD_SWITCH_ENCORE_DAMAGE:
@ -221,6 +241,35 @@ static bool32 AreStatsRaised(enum BattlerId battler)
return (buffedStatsValue > STAY_IN_STATS_RAISED);
}
bool32 IsSwitchinTSpikesAffected(enum BattlerId battler)
{
enum Ability ability = gAiLogicData->abilities[battler];
enum HoldEffect heldItemEffect = gAiLogicData->holdEffects[battler];
enum BattlerId opposingBattler = GetOppositeBattler(battler);
bool32 ignoreItem = ((gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) || ability == ABILITY_KLUTZ);
if (gBattleMons[battler].status1 & STATUS1_ANY)
return FALSE;
if (IS_BATTLER_ANY_TYPE(battler, TYPE_POISON, TYPE_STEEL))
return FALSE;
if (ability == ABILITY_IMMUNITY || IsAbilityOnSide(battler, ABILITY_PASTEL_VEIL))
return FALSE;
if ((heldItemEffect == HOLD_EFFECT_HEAVY_DUTY_BOOTS || heldItemEffect == HOLD_EFFECT_CURE_PSN || heldItemEffect == HOLD_EFFECT_CURE_STATUS) && !ignoreItem)
return FALSE;
if (!AI_IsBattlerGrounded(battler))
return FALSE;
if (IsMistyTerrainAffected(battler, ability, heldItemEffect, gFieldStatuses))
return FALSE;
if (IsLeafGuardProtected(battler, ability))
return FALSE;
if (IsShieldsDownProtected(battler, ability))
return FALSE;
if (IsFlowerVeilProtected(battler))
return FALSE;
if (IsSafeguardProtected(opposingBattler, battler, gAiLogicData->abilities[opposingBattler]))
return FALSE;
return TRUE;
}
static inline bool32 SetSwitchinAndSwitch(enum BattlerId battler, u32 switchinId)
{
gBattleStruct->AI_monToSwitchIntoId[battler] = switchinId;
@ -342,7 +391,7 @@ static bool32 ShouldSwitchIfHasBadOdds(enum BattlerId battler)
hasSuperEffectiveMove = TRUE;
// Check if can win 1v1
hitsToKOPlayer = GetNoOfHitsToKOBattler(battler, opposingBattler, moveIndex, AI_ATTACKING, CONSIDER_ENDURE);
hitsToKOPlayer = GetNoOfHitsToKOBattler(battler, opposingBattler, moveIndex, AI_SWITCHIN_ATTACKING, CONSIDER_ENDURE);
if (!canBattlerWin1v1 ) // Once we can win a 1v1 we don't need to track this, but want to run the rest of the function to keep the runtime the same regardless of when we find the winning move
{
isBattlerFirst = AI_IsFaster(battler, opposingBattler, aiMove, expectedMove, CONSIDER_PRIORITY);
@ -586,7 +635,7 @@ static bool32 FindMonThatAbsorbsOpponentsMove(enum BattlerId battler)
// Only check damage if it's a damaging move
if (!IsBattleMoveStatus(aiMove))
{
if (!AI_DoesChoiceEffectBlockMove(battler, aiMove) && AI_GetDamage(battler, opposingBattler, moveIndex, AI_ATTACKING, gAiLogicData) > gBattleMons[opposingBattler].hp)
if (!AI_DoesChoiceEffectBlockMove(battler, aiMove) && AI_GetDamage(battler, opposingBattler, moveIndex, AI_SWITCHIN_ATTACKING, gAiLogicData) > gBattleMons[opposingBattler].hp)
return FALSE;
}
}
@ -913,6 +962,103 @@ static bool32 GetHitEscapeTransformState(enum BattlerId battlerAtk, enum Move mo
return isFasterThanAll;
}
static bool32 IsOpponentPhysicalAttacker(enum BattlerId battler, enum BattlerId opposingBattler)
{
if (!IsBattlerAlive(opposingBattler))
return FALSE;
if (GetBestDmgFromBattler(opposingBattler, battler, AI_DEFENDING) > 0 && HasPhysicalBestMove(opposingBattler, battler, AI_DEFENDING))
return TRUE;
enum Move incomingMove = GetIncomingMove(battler, opposingBattler, gAiLogicData);
return incomingMove != MOVE_NONE
&& incomingMove != MOVE_UNAVAILABLE
&& GetBattleMoveCategory(incomingMove) == DAMAGE_CATEGORY_PHYSICAL;
}
static bool32 CanIntimidateLowerOpponentAtk(enum BattlerId battler, enum BattlerId opposingBattler)
{
enum Ability abilityDef = gAiLogicData->abilities[opposingBattler];
// If Attack is already at -2 or lower, repeated Intimidate cycles aren't worth it.
if (gBattleMons[opposingBattler].statStages[STAT_ATK] <= DEFAULT_STAT_STAGE - 2)
return FALSE;
if (gBattleMons[opposingBattler].volatiles.substitute)
return FALSE;
if (gAiLogicData->holdEffects[opposingBattler] == HOLD_EFFECT_CLEAR_AMULET)
return FALSE;
if (gSideStatuses[GetBattlerSide(opposingBattler)] & SIDE_STATUS_MIST)
return FALSE;
if (IS_BATTLER_OF_TYPE(opposingBattler, TYPE_GRASS) && AI_IsAbilityOnSide(opposingBattler, ABILITY_FLOWER_VEIL))
return FALSE;
switch (abilityDef)
{
case ABILITY_HYPER_CUTTER:
case ABILITY_CLEAR_BODY:
case ABILITY_FULL_METAL_BODY:
case ABILITY_WHITE_SMOKE:
return FALSE;
default:
break;
}
if (GetConfig(B_UPDATED_INTIMIDATE) >= GEN_8)
{
switch (abilityDef)
{
case ABILITY_INNER_FOCUS:
case ABILITY_SCRAPPY:
case ABILITY_OWN_TEMPO:
case ABILITY_OBLIVIOUS:
return FALSE;
default:
break;
}
}
return TRUE;
}
static bool32 ShouldSwitchIfIntimidateBenefit(enum BattlerId battler)
{
// Keep Intimidate cycling behavior restricted to smart-switching AI
if (!(gAiThinkingStruct->aiFlags[battler] & AI_FLAG_SMART_SWITCHING))
return FALSE;
enum BattlerId opposingBattler = GetOppositeBattler(battler);
enum BattlerId opposingPartner = BATTLE_PARTNER(opposingBattler);
bool32 hasValidTarget = FALSE;
if (IsBattlerAlive(opposingBattler))
{
enum Ability abilityDef = gAiLogicData->abilities[opposingBattler];
bool32 canLowerAtk = CanIntimidateLowerOpponentAtk(battler, opposingBattler);
if (canLowerAtk && (DoesIntimidateRaiseStats(abilityDef) || abilityDef == ABILITY_MIRROR_ARMOR))
return FALSE;
if (canLowerAtk && IsOpponentPhysicalAttacker(battler, opposingBattler))
hasValidTarget = TRUE;
}
if (IsDoubleBattle() && IsBattlerAlive(opposingPartner))
{
enum Ability abilityDef = gAiLogicData->abilities[opposingPartner];
bool32 canLowerAtk = CanIntimidateLowerOpponentAtk(battler, opposingPartner);
if (canLowerAtk && (DoesIntimidateRaiseStats(abilityDef) || abilityDef == ABILITY_MIRROR_ARMOR))
return FALSE;
if (canLowerAtk && IsOpponentPhysicalAttacker(battler, opposingPartner))
hasValidTarget = TRUE;
}
return hasValidTarget;
}
static bool32 ShouldSwitchIfAbilityBenefit(enum BattlerId battler)
{
bool32 hasStatRaised = AnyUsefulStatIsRaised(battler);
@ -950,20 +1096,21 @@ static bool32 ShouldSwitchIfAbilityBenefit(enum BattlerId battler)
return FALSE;
case ABILITY_INTIMIDATE:
// TODO: In ShouldSwitch cleanup, gate Intimidate cycling behind "stay in instead if the current mon wins the 1v1" to avoid duplicating Bad Odds logic here.
if (ShouldSwitchIfIntimidateBenefit(battler)
&& gAiLogicData->mostSuitableMonId[battler] != PARTY_SIZE
&& (hasStatRaised ? RandomPercentage(RNG_AI_SWITCH_INTIMIDATE, GetSwitchChance(SHOULD_SWITCH_INTIMIDATE_STATS_RAISED)) : RandomPercentage(RNG_AI_SWITCH_INTIMIDATE, GetSwitchChance(SHOULD_SWITCH_INTIMIDATE))))
break;
return FALSE;
case ABILITY_ZERO_TO_HERO:
{
enum Move hitEscapeMove = MOVE_NONE;
for (u32 moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
{
enum Move move = gBattleMons[battler].moves[moveIndex];
if (move != MOVE_NONE && GetMoveEffect(move) == EFFECT_HIT_ESCAPE)
{
hitEscapeMove = move;
break;
}
}
if (GetBattlerMoveIndexWithEffect(battler, EFFECT_HIT_ESCAPE) < MAX_MON_MOVES)
hitEscapeMove = gBattleMons[battler].moves[GetBattlerMoveIndexWithEffect(battler, EFFECT_HIT_ESCAPE)];
// Prefer to use a hit escape move if Palafin will move first and can hit
if (hitEscapeMove != MOVE_NONE && GetHitEscapeTransformState(battler, hitEscapeMove))
@ -1477,6 +1624,51 @@ bool32 IsSwitchinValid(enum BattlerId battler)
return TRUE;
}
static u32 GetSwitchinSingleUseItemHealing(enum BattlerId battler, enum BattlerId opposingBattler, s32 currentHP)
{
enum Item aiItem = gAiLogicData->items[battler];
u32 maxHP = gBattleMons[battler].maxHP;
s32 itemHeal = 0;
// Check if we're at a single use healing item threshold
if (currentHP <= 0
|| gAiLogicData->abilities[battler] == ABILITY_KLUTZ
|| (gAiLogicData->abilities[opposingBattler] == ABILITY_UNNERVE && GetItemPocket(aiItem) == POCKET_BERRIES))
return itemHeal;
switch (GetItemHoldEffect(aiItem))
{
case HOLD_EFFECT_RESTORE_HP:
if (currentHP < maxHP / 2)
itemHeal = GetItemHoldEffectParam(aiItem);
break;
case HOLD_EFFECT_RESTORE_PCT_HP:
if (currentHP < maxHP / 2)
{
itemHeal = maxHP / GetItemHoldEffectParam(aiItem);
if (itemHeal == 0)
itemHeal = 1;
}
break;
case HOLD_EFFECT_CONFUSE_SPICY:
case HOLD_EFFECT_CONFUSE_DRY:
case HOLD_EFFECT_CONFUSE_SWEET:
case HOLD_EFFECT_CONFUSE_BITTER:
case HOLD_EFFECT_CONFUSE_SOUR:
if (currentHP < maxHP / CONFUSE_BERRY_HP_FRACTION)
{
itemHeal = maxHP / GetItemHoldEffectParam(aiItem);
if (itemHeal == 0)
itemHeal = 1;
}
break;
default:
break;
}
return itemHeal;
}
// Gets hazard damage
static u32 GetSwitchinHazardsDamage(enum BattlerId battler)
{
@ -1670,7 +1862,6 @@ static u32 GetSwitchinRecurringDamage(enum BattlerId battler)
static u32 GetSwitchinStatusDamage(enum BattlerId battler)
{
u8 tSpikesLayers = gSideTimers[GetBattlerSide(battler)].toxicSpikesAmount;
enum HoldEffect heldItemEffect = gAiLogicData->holdEffects[battler];
u32 status = gBattleMons[battler].status1;
enum Ability ability = gAiLogicData->abilities[battler];
u32 maxHP = gBattleMons[battler].maxHP;
@ -1718,13 +1909,7 @@ static u32 GetSwitchinStatusDamage(enum BattlerId battler)
// Apply hypothetical poisoning from Toxic Spikes, which means the first turn of damage already added in GetSwitchinHazardsDamage
// Do this last to skip one iteration of Poison / Toxic damage, and start counting Toxic damage one turn later.
if (tSpikesLayers != 0 && (!IS_BATTLER_ANY_TYPE(battler, TYPE_POISON, TYPE_STEEL)
&& ability != ABILITY_IMMUNITY && ability != ABILITY_POISON_HEAL && ability != ABILITY_PASTEL_VEIL
&& status == 0
&& !(heldItemEffect == HOLD_EFFECT_HEAVY_DUTY_BOOTS
&& (((gFieldStatuses & STATUS_FIELD_MAGIC_ROOM) || ability == ABILITY_KLUTZ)))
&& heldItemEffect != HOLD_EFFECT_CURE_PSN && heldItemEffect != HOLD_EFFECT_CURE_STATUS
&& AI_IsBattlerGrounded(battler)))
if (tSpikesLayers != 0 && IsSwitchinTSpikesAffected(battler))
{
if (tSpikesLayers == 1)
{
@ -1766,7 +1951,7 @@ static u32 GetSwitchinHitsToKO(s32 damageTaken, enum BattlerId battler, const st
u32 statusDamage = GetSwitchinStatusDamage(battler);
u32 hitsToKO = 0;
u16 maxHP = gBattleMons[battler].maxHP, item = gAiLogicData->items[battler], heldItemEffect = GetItemHoldEffect(item);
u8 weatherDuration = gBattleStruct->weatherDuration, holdEffectParam = GetItemHoldEffectParam(item);
u8 weatherDuration = gBattleStruct->weatherDuration;
enum BattlerId opposingBattler = GetOppositeBattler(battler);
enum Ability opposingAbility = gAiLogicData->abilities[opposingBattler], ability = gAiLogicData->abilities[battler];
bool32 usedSingleUseHealingItem = FALSE, opponentCanBreakMold = IsMoldBreakerTypeAbility(opposingBattler, opposingAbility);
@ -1800,37 +1985,9 @@ static u32 GetSwitchinHitsToKO(s32 damageTaken, enum BattlerId battler, const st
currentHP = currentHP - weatherImpact;
// Check if we're at a single use healing item threshold
if (currentHP > 0 && gAiLogicData->abilities[battler] != ABILITY_KLUTZ && usedSingleUseHealingItem == FALSE
&& !(opposingAbility == ABILITY_UNNERVE && GetItemPocket(item) == POCKET_BERRIES))
if (usedSingleUseHealingItem == FALSE)
{
switch (heldItemEffect)
{
case HOLD_EFFECT_RESTORE_HP:
if (currentHP < maxHP / 2)
singleUseItemHeal = holdEffectParam;
break;
case HOLD_EFFECT_RESTORE_PCT_HP:
if (currentHP < maxHP / 2)
{
singleUseItemHeal = maxHP / holdEffectParam;
if (singleUseItemHeal == 0)
singleUseItemHeal = 1;
}
break;
case HOLD_EFFECT_CONFUSE_SPICY:
case HOLD_EFFECT_CONFUSE_DRY:
case HOLD_EFFECT_CONFUSE_SWEET:
case HOLD_EFFECT_CONFUSE_BITTER:
case HOLD_EFFECT_CONFUSE_SOUR:
if (currentHP < maxHP / CONFUSE_BERRY_HP_FRACTION)
{
singleUseItemHeal = maxHP / holdEffectParam;
if (singleUseItemHeal == 0)
singleUseItemHeal = 1;
}
break;
}
singleUseItemHeal = GetSwitchinSingleUseItemHealing(battler, opposingBattler, currentHP);
// If we used one, apply it without overcapping our maxHP
if (singleUseItemHeal > 0)
{
@ -1964,7 +2121,7 @@ static s32 GetMaxDamagePlayerCouldDealToSwitchin(enum BattlerId battler, enum Ba
playerMove = SMART_SWITCHING_OMNISCIENT ? gBattleMons[opposingBattler].moves[moveIndex] : playerMoves[moveIndex];
if (playerMove != MOVE_NONE && !IsBattleMoveStatus(playerMove) && GetMoveEffect(playerMove) != EFFECT_FOCUS_PUNCH && gBattleMons[opposingBattler].pp[moveIndex] > 0)
{
damageTaken = AI_GetDamage(opposingBattler, battler, moveIndex, AI_DEFENDING, gAiLogicData);
damageTaken = AI_GetDamage(opposingBattler, battler, moveIndex, AI_SWITCHIN_DEFENDING, gAiLogicData);
if (playerMove == gBattleStruct->choicedMove[opposingBattler]) // If player is choiced, only care about the choice locked move
{
*bestPlayerMove = playerMove;
@ -1995,7 +2152,7 @@ static s32 GetMaxPriorityDamagePlayerCouldDealToSwitchin(enum BattlerId battler,
if (GetBattleMovePriority(opposingBattler, gAiLogicData->abilities[opposingBattler], playerMove) > 0
&& playerMove != MOVE_NONE && !IsBattleMoveStatus(playerMove) && GetMoveEffect(playerMove) != EFFECT_FOCUS_PUNCH && gBattleMons[opposingBattler].pp[moveIndex] > 0)
{
damageTaken = AI_GetDamage(opposingBattler, battler, moveIndex, AI_DEFENDING, gAiLogicData);
damageTaken = AI_GetDamage(opposingBattler, battler, moveIndex, AI_SWITCHIN_DEFENDING, gAiLogicData);
if (playerMove == gBattleStruct->choicedMove[opposingBattler]) // If player is choiced, only care about the choice locked move
{
*bestPlayerPriorityMove = playerMove;
@ -2161,8 +2318,8 @@ static u32 GetBestMonIntegrated(struct Pokemon *party, int lastId, enum BattlerI
continue;
aiMove = gBattleMons[battler].moves[moveIndex];
damageDealt = AI_GetDamage(battler, opposingBattler, moveIndex, AI_ATTACKING, gAiLogicData);
hitsToKOPlayer = GetNoOfHitsToKOBattler(battler, opposingBattler, moveIndex, AI_ATTACKING, CONSIDER_ENDURE);
damageDealt = AI_GetDamage(battler, opposingBattler, moveIndex, AI_SWITCHIN_ATTACKING, gAiLogicData);
hitsToKOPlayer = GetNoOfHitsToKOBattler(battler, opposingBattler, moveIndex, AI_SWITCHIN_ATTACKING, CONSIDER_ENDURE);
// Offensive switchin decisions are based on which whether switchin moves first and whether it can win a 1v1
isSwitchinFirst = AI_IsFaster(battler, opposingBattler, aiMove, bestPlayerMove, CONSIDER_PRIORITY);
@ -2420,7 +2577,7 @@ static u32 GetBestMonVanilla(struct Pokemon *party, int lastId, enum BattlerId b
// Best damage
if (aiMove != MOVE_NONE && !IsBattleMoveStatus(aiMove))
{
u32 aiDmg = AI_GetDamage(battler, opposingBattler, moveIndex, AI_ATTACKING, gAiLogicData);
u32 aiDmg = AI_GetDamage(battler, opposingBattler, moveIndex, AI_SWITCHIN_ATTACKING, gAiLogicData);
if (aiDmg > bestDamage)
{
bestDamage = aiDmg;
@ -2561,7 +2718,7 @@ u32 AI_SelectRevivalBlessingMon(enum BattlerId battler)
if (aiMove == MOVE_NONE || gBattleMons[battler].pp[moveIndex] == 0)
continue;
s32 damage = AI_GetDamage(battler, opposingBattler, moveIndex, AI_ATTACKING, gAiLogicData);
s32 damage = AI_GetDamage(battler, opposingBattler, moveIndex, AI_SWITCHIN_ATTACKING, gAiLogicData);
if (damage > bestDamage)
bestDamage = damage;
}
@ -2596,3 +2753,196 @@ u32 AI_SelectRevivalBlessingMon(enum BattlerId battler)
return bestMonId;
}
static void SetBattlerStatusForSwitchin(enum BattlerId battler)
{
u32 tSpikesLayers = gSideTimers[GetBattlerSide(battler)].toxicSpikesAmount;
if (tSpikesLayers != 0 && IsSwitchinTSpikesAffected(battler))
{
if (tSpikesLayers == 1)
gBattleMons[battler].status1 = STATUS1_POISON;
if (tSpikesLayers == 2)
{
gBattleMons[battler].status1 = STATUS1_TOXIC_POISON;
gBattleMons[battler].status1 += STATUS1_TOXIC_TURN(1);
}
}
}
static void SetBattlerStatStagesForSwitchin(enum BattlerId battler, enum BattlerId opposingBattler, u32 fieldStatus)
{
u32 aiAbility = gAiLogicData->abilities[battler];
enum Item aiItem = gAiLogicData->items[battler];
bool32 isStickyWebsAffected = (IsHazardOnSide(GetBattlerSide(battler), HAZARDS_STICKY_WEB) && IsBattlerAffectedByHazards(battler, GetItemHoldEffect(aiItem), FALSE) && IsBattlerGrounded(battler, gAiLogicData->abilities[battler], GetItemHoldEffect(aiItem)));
bool32 opponentStatDrop = FALSE;
// Ability stat changes
switch(aiAbility)
{
case ABILITY_INTREPID_SWORD:
gBattleMons[battler].statStages[STAT_ATK] += 1;
break;
case ABILITY_DAUNTLESS_SHIELD:
gBattleMons[battler].statStages[STAT_DEF] += 1;
break;
case ABILITY_SUPREME_OVERLORD:
break;
case ABILITY_DOWNLOAD:
gBattleMons[battler].statStages[GetDownloadStat(battler)] += 1;
break;
case ABILITY_INTIMIDATE:
if (CanLowerStat(battler, opposingBattler, gAiLogicData, STAT_ATK))
{
if (gAiLogicData->abilities[opposingBattler] == ABILITY_CONTRARY)
{
gBattleMons[opposingBattler].statStages[STAT_ATK] += 1;
}
else
{
opponentStatDrop = TRUE;
gBattleMons[opposingBattler].statStages[STAT_ATK] -= 1;
if (gAiLogicData->abilities[opposingBattler] == ABILITY_DEFIANT)
gBattleMons[opposingBattler].statStages[STAT_ATK] += 2;
if (gAiLogicData->abilities[opposingBattler] == ABILITY_COMPETITIVE)
gBattleMons[opposingBattler].statStages[STAT_SPATK] += 2;
}
}
break;
case ABILITY_SUPERSWEET_SYRUP:
if (CanLowerStat(battler, opposingBattler, gAiLogicData, STAT_EVASION))
{
if (gAiLogicData->abilities[opposingBattler] == ABILITY_CONTRARY)
{
gBattleMons[opposingBattler].statStages[STAT_EVASION] += 1;
}
else
{
opponentStatDrop = TRUE;
gBattleMons[opposingBattler].statStages[STAT_EVASION] -= 1;
if (gAiLogicData->abilities[opposingBattler] == ABILITY_DEFIANT)
gBattleMons[opposingBattler].statStages[STAT_ATK] += 2;
if (gAiLogicData->abilities[opposingBattler] == ABILITY_COMPETITIVE)
gBattleMons[opposingBattler].statStages[STAT_SPATK] += 2;
}
}
break;
case ABILITY_WIND_RIDER:
if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_TAILWIND)
gBattleMons[battler].statStages[STAT_ATK] += 1;
break;
case ABILITY_DEFIANT:
if (isStickyWebsAffected)
gBattleMons[battler].statStages[STAT_ATK] += 2;
break;
case ABILITY_COMPETITIVE:
if (isStickyWebsAffected)
gBattleMons[battler].statStages[STAT_SPATK] += 2;
break;
case ABILITY_CONTRARY:
if (isStickyWebsAffected)
gBattleMons[battler].statStages[STAT_SPEED] += 1;
default:
break;
}
// Item stat changes
switch(GetItemHoldEffect(aiItem))
{
case HOLD_EFFECT_TERRAIN_SEED:
{
u32 seedParam = GetItemHoldEffectParam(aiItem);
if ((seedParam == HOLD_EFFECT_PARAM_ELECTRIC_TERRAIN && (fieldStatus & STATUS_FIELD_ELECTRIC_TERRAIN))
|| (seedParam == HOLD_EFFECT_PARAM_GRASSY_TERRAIN && (fieldStatus & STATUS_FIELD_GRASSY_TERRAIN))
|| (seedParam == HOLD_EFFECT_PARAM_MISTY_TERRAIN && (fieldStatus & STATUS_FIELD_MISTY_TERRAIN))
|| (seedParam == HOLD_EFFECT_PARAM_PSYCHIC_TERRAIN && (fieldStatus & STATUS_FIELD_PSYCHIC_TERRAIN)))
gBattleMons[battler].statStages[STAT_DEF] += 1;
break;
}
case HOLD_EFFECT_ATTACK_UP:
if (HasEnoughHpToEatBerry(battler, aiAbility, GetItemHoldEffectParam(aiItem), aiItem))
gBattleMons[battler].statStages[STAT_ATK] += 1;
break;
case HOLD_EFFECT_DEFENSE_UP:
if (HasEnoughHpToEatBerry(battler, aiAbility, GetItemHoldEffectParam(aiItem), aiItem))
gBattleMons[battler].statStages[STAT_DEF] += 1;
break;
case HOLD_EFFECT_SPEED_UP:
if (HasEnoughHpToEatBerry(battler, aiAbility, GetItemHoldEffectParam(aiItem), aiItem))
gBattleMons[battler].statStages[STAT_SPEED] += 1;
break;
case HOLD_EFFECT_SP_ATTACK_UP:
if (HasEnoughHpToEatBerry(battler, aiAbility, GetItemHoldEffectParam(aiItem), aiItem))
gBattleMons[battler].statStages[STAT_SPATK] += 1;
break;
case HOLD_EFFECT_SP_DEFENSE_UP:
if (HasEnoughHpToEatBerry(battler, aiAbility, GetItemHoldEffectParam(aiItem), aiItem))
gBattleMons[battler].statStages[STAT_SPDEF] += 1;
break;
case HOLD_EFFECT_ROOM_SERVICE:
if (gFieldStatuses & STATUS_FIELD_TRICK_ROOM)
gBattleMons[battler].statStages[STAT_SPEED] -= 1;
case HOLD_EFFECT_MIRROR_HERB:
if (opponentStatDrop && gAiLogicData->abilities[opposingBattler] == ABILITY_DEFIANT)
gBattleMons[battler].statStages[STAT_ATK] += 2;
if (opponentStatDrop && gAiLogicData->abilities[opposingBattler] == ABILITY_COMPETITIVE)
gBattleMons[battler].statStages[STAT_SPATK] += 2;
break;
default:
break;
}
// Hazard stat changes
if (isStickyWebsAffected && GetItemHoldEffect(aiItem) != HOLD_EFFECT_WHITE_HERB)
gBattleMons[battler].statStages[STAT_SPEED] -= 1;
}
static void SetBattlerHPChangeForSwitch(enum BattlerId battler, enum BattlerId opposingBattler)
{
s32 maxHP = gBattleMons[battler].maxHP;
s32 currentHP = gBattleMons[battler].hp - GetSwitchinHazardsDamage(battler);
s32 itemHeal = GetSwitchinSingleUseItemHealing(battler, opposingBattler, currentHP);
if (itemHeal > 0)
{
if ((currentHP + itemHeal) > maxHP)
currentHP = maxHP;
else
currentHP = currentHP + itemHeal;
}
gBattleMons[battler].hp = currentHP;
}
// Set potential field effect from ability for switch in
static void SetBattlerVolatilesForSwitchin(enum BattlerId battler, u32 weather, u32 fieldStatus)
{
enum Item aiItem = gAiLogicData->items[battler];
switch (gAiLogicData->abilities[battler])
{
case ABILITY_VESSEL_OF_RUIN:
gBattleMons[battler].volatiles.vesselOfRuin = TRUE;
break;
case ABILITY_SWORD_OF_RUIN:
gBattleMons[battler].volatiles.swordOfRuin = TRUE;
break;
case ABILITY_TABLETS_OF_RUIN:
gBattleMons[battler].volatiles.tabletsOfRuin = TRUE;
break;
case ABILITY_BEADS_OF_RUIN:
gBattleMons[battler].volatiles.beadsOfRuin = TRUE;
break;
case ABILITY_QUARK_DRIVE:
if ((fieldStatus & STATUS_FIELD_ELECTRIC_TERRAIN) || GetItemHoldEffect(aiItem) == HOLD_EFFECT_BOOSTER_ENERGY)
gBattleMons[battler].volatiles.boosterEnergyActivated = TRUE;
break;
case ABILITY_PROTOSYNTHESIS:
if (((weather & B_WEATHER_SUN) && HasWeatherEffect()) || GetItemHoldEffect(aiItem) == HOLD_EFFECT_BOOSTER_ENERGY)
gBattleMons[battler].volatiles.boosterEnergyActivated = TRUE;
break;
case ABILITY_WIND_POWER:
if (gSideStatuses[GetBattlerSide(battler)] & SIDE_STATUS_TAILWIND)
gBattleMons[battler].volatiles.chargeTimer = 2;
break;
default:
break;
}
}

View File

@ -23,6 +23,7 @@
#include "constants/moves.h"
#include "constants/items.h"
static void AI_SetBattlerTurnOrder(u8 *aiTurnOrder);
static u32 GetAIEffectGroup(enum BattleMoveEffects effect);
static u32 GetAIEffectGroupFromMove(enum BattlerId battler, enum Move move);
@ -90,6 +91,23 @@ enum MoveTarget AI_GetBattlerMoveTargetType(enum BattlerId battler, enum Move mo
return GetMoveTarget(move);
}
u32 AI_GetDefaultDamageRollForContext(enum BattlerId battlerAtk, enum BattlerId battlerDef, u32 moveIndex, struct AiLogicData *aiData, u32 aiRoll)
{
switch (aiRoll)
{
case AI_ROLL_MIN:
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].minimum;
case AI_ROLL_MEDIAN:
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].median;
case AI_ROLL_MAX:
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].maximum;
case AI_ROLL_RANDOM:
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].random;
default:
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].median; // Default assumes it deals median damage
}
}
u32 AI_GetDamage(enum BattlerId battlerAtk, enum BattlerId battlerDef, u32 moveIndex, enum DamageCalcContext calcContext, struct AiLogicData *aiData)
{
if (calcContext == AI_ATTACKING && BattlerHasAi(battlerAtk))
@ -98,7 +116,7 @@ u32 AI_GetDamage(enum BattlerId battlerAtk, enum BattlerId battlerDef, u32 moveI
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].maximum;
if ((gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE) && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY)) // Conservative assumes it deals min damage
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].minimum;
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].median; // Default assumes it deals median damage
return AI_GetDefaultDamageRollForContext(battlerAtk, battlerDef, moveIndex, aiData, GetConfig(AI_ROLL_ATTACKING));
}
else if (calcContext == AI_DEFENDING && BattlerHasAi(battlerDef))
{
@ -106,7 +124,39 @@ u32 AI_GetDamage(enum BattlerId battlerAtk, enum BattlerId battlerDef, u32 moveI
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].minimum;
if ((gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE) && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY)) // Conservative assumes it takes max damage
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].maximum;
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].median; // Default assumes it takes median damage
return AI_GetDefaultDamageRollForContext(battlerAtk, battlerDef, moveIndex, aiData, AI_ROLL_DEFENDING);
}
else if (calcContext == AI_SWITCHIN_ATTACKING && BattlerHasAi(battlerAtk))
{
if ((gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY) && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE)) // Risky assumes it deals max damage
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].maximum;
if ((gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE) && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY)) // Conservative assumes it deals min damage
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].minimum;
return AI_GetDefaultDamageRollForContext(battlerAtk, battlerDef, moveIndex, aiData, AI_ROLL_SWITCHIN_ATTACKING);
}
else if (calcContext == AI_SWITCHIN_DEFENDING && BattlerHasAi(battlerDef))
{
if ((gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY) && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE)) // Risky assumes it takes min damage
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].minimum;
if ((gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE) && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY)) // Conservative assumes it takes max damage
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].maximum;
return AI_GetDefaultDamageRollForContext(battlerAtk, battlerDef, moveIndex, aiData, AI_ROLL_SWITCHIN_DEFENDING);
}
else if (calcContext == AI_SHOULD_SETUP_DEFENDING && BattlerHasAi(battlerDef))
{
if ((gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY) && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE)) // Risky assumes it takes min damage
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].minimum;
if ((gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE) && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY)) // Conservative assumes it takes max damage
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].maximum;
return AI_GetDefaultDamageRollForContext(battlerAtk, battlerDef, moveIndex, aiData, AI_ROLL_SHOULD_SETUP_DEFENDING);
}
else if (calcContext == AI_ATTACKING_PARTNER && BattlerHasAi(battlerAtk))
{
if ((gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY) && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE)) // Risky assumes it deals max damage
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].maximum;
if ((gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_CONSERVATIVE) && !(gAiThinkingStruct->aiFlags[battlerAtk] & AI_FLAG_RISKY)) // Conservative assumes it deals min damage
return aiData->simulatedDmg[battlerAtk][battlerDef][moveIndex].minimum;
return AI_GetDefaultDamageRollForContext(battlerAtk, battlerDef, moveIndex, aiData, AI_ROLL_ATTACKING_PARTNER);
}
else
{
@ -571,6 +621,14 @@ static inline s32 DmgRoll(s32 dmg)
return dmg;
}
static inline s32 RandomRollDmg(s32 dmg)
{
u32 randomRollPercentage = RandomUniform(RNG_AI_DMG_ROLL_RANDOM, MIN_ROLL_PERCENTAGE, MAX_ROLL_PERCENTAGE);
dmg *= randomRollPercentage;
dmg /= 100;
return dmg;
}
bool32 IsDamageMoveUnusable(struct BattleContext *ctx)
{
enum Ability battlerDefAbility;
@ -681,6 +739,8 @@ static inline s32 GetDamageByRollType(s32 dmg, enum DamageRollType rollType)
return LowestRollDmg(dmg);
else if (rollType == DMG_ROLL_HIGHEST)
return HighestRollDmg(dmg);
else if (rollType == DMG_ROLL_RANDOM)
return RandomRollDmg(dmg);
else
return DmgRoll(dmg);
}
@ -699,12 +759,13 @@ static inline void AI_RestoreBattlerTypes(enum BattlerId battlerAtk, enum Type *
gBattleMons[battlerAtk].types[2] = types[2];
}
static inline void CalcDynamicMoveDamage(struct BattleContext *ctx, u16 *medianDamage, u16 *minimumDamage, u16 *maximumDamage)
static inline void CalcDynamicMoveDamage(struct BattleContext *ctx, u16 *medianDamage, u16 *minimumDamage, u16 *maximumDamage, u16 *randomDamage)
{
enum BattleMoveEffects effect = GetMoveEffect(ctx->move);
u16 median = *medianDamage;
u16 minimum = *minimumDamage;
u16 maximum = *maximumDamage;
u16 random = *randomDamage;
u32 strikeCount = GetMoveStrikeCount(ctx->move);
@ -718,7 +779,7 @@ static inline void CalcDynamicMoveDamage(struct BattleContext *ctx, u16 *medianD
median = 0;
for (i = 0; i < partyCount; i++)
median += CalculateMoveDamage(ctx);
maximum = minimum = median;
maximum = minimum = median = random;
gBattleStruct->beatUpSlot = 0;
}
else if (strikeCount > 1 && effect != EFFECT_TRIPLE_KICK)
@ -726,6 +787,7 @@ static inline void CalcDynamicMoveDamage(struct BattleContext *ctx, u16 *medianD
median *= strikeCount;
minimum *= strikeCount;
maximum *= strikeCount;
random *= strikeCount;
}
else if (IsMultiHitMove(ctx->move))
{
@ -734,12 +796,14 @@ static inline void CalcDynamicMoveDamage(struct BattleContext *ctx, u16 *medianD
median *= GetMoveSpeciesPowerOverride_NumOfHits(ctx->move);
minimum *= GetMoveSpeciesPowerOverride_NumOfHits(ctx->move);
maximum *= GetMoveSpeciesPowerOverride_NumOfHits(ctx->move);
random *= GetMoveSpeciesPowerOverride_NumOfHits(ctx->move);
}
else if (ctx->abilityAtk == ABILITY_SKILL_LINK)
{
median *= 5;
minimum *= 5;
maximum *= 5;
random *= 5;
}
else if (ctx->holdEffectAtk == HOLD_EFFECT_LOADED_DICE)
{
@ -747,12 +811,14 @@ static inline void CalcDynamicMoveDamage(struct BattleContext *ctx, u16 *medianD
median /= 2;
minimum *= 4;
maximum *= 5;
random *= RandomUniform(RNG_AI_DMG_ROLL_RANDOM, 4, 5);
}
else
{
median *= 3;
minimum *= 2;
maximum *= 5;
random *= RandomUniform(RNG_AI_DMG_ROLL_RANDOM, 2, 5);
}
}
else if (ctx->abilityAtk == ABILITY_PARENTAL_BOND
@ -762,6 +828,7 @@ static inline void CalcDynamicMoveDamage(struct BattleContext *ctx, u16 *medianD
median += median / (B_PARENTAL_BOND_DMG >= GEN_7 ? 4 : 2);
minimum += minimum / (B_PARENTAL_BOND_DMG >= GEN_7 ? 4 : 2);
maximum += maximum / (B_PARENTAL_BOND_DMG >= GEN_7 ? 4 : 2);
random += random / (B_PARENTAL_BOND_DMG >= GEN_7 ? 4 : 2);
}
if (median == 0)
@ -770,10 +837,13 @@ static inline void CalcDynamicMoveDamage(struct BattleContext *ctx, u16 *medianD
minimum = 1;
if (maximum == 0)
maximum = 1;
if (random == 0)
random = 1;
*medianDamage = median;
*minimumDamage = minimum;
*maximumDamage = maximum;
*randomDamage = random;
}
static inline bool32 ShouldCalcCritDamage(struct BattleContext *ctx)
@ -889,6 +959,7 @@ struct SimulatedDamage AI_CalcDamage(enum Move move, enum BattlerId battlerAtk,
ctx.abilityDef = AI_GetMoldBreakerSanitizedAbility(battlerAtk, ctx.abilityAtk, aiData->abilities[battlerDef], ctx.holdEffectDef, move);
ctx.isCrit = ShouldCalcCritDamage(&ctx);
ctx.typeEffectivenessModifier = CalcTypeEffectivenessMultiplier(&ctx);
AI_SetBattlerTurnOrder(ctx.aiTurnOrder);
u32 movePower = GetMovePower(move);
@ -901,7 +972,7 @@ struct SimulatedDamage AI_CalcDamage(enum Move move, enum BattlerId battlerAtk,
s32 fixedDamage = DoFixedDamageMoveCalc(&ctx);
if (fixedDamage != INT32_MAX)
{
simDamage.minimum = simDamage.median = simDamage.maximum = fixedDamage;
simDamage.minimum = simDamage.median = simDamage.maximum = simDamage.random = fixedDamage;
}
else if (moveEffect == EFFECT_TRIPLE_KICK)
{
@ -914,11 +985,14 @@ struct SimulatedDamage AI_CalcDamage(enum Move move, enum BattlerId battlerAtk,
damageByRollType = GetDamageByRollType(oneTripleKickHit, DMG_ROLL_LOWEST);
simDamage.minimum += AI_ApplyModifiersAfterDmgRoll(&ctx, damageByRollType);
damageByRollType = GetDamageByRollType(oneTripleKickHit, DMG_ROLL_DEFAULT);
damageByRollType = GetDamageByRollType(oneTripleKickHit, DMG_ROLL_MEDIAN);
simDamage.median += AI_ApplyModifiersAfterDmgRoll(&ctx, damageByRollType);
damageByRollType = GetDamageByRollType(oneTripleKickHit, DMG_ROLL_HIGHEST);
simDamage.maximum += AI_ApplyModifiersAfterDmgRoll(&ctx, damageByRollType);
damageByRollType = GetDamageByRollType(oneTripleKickHit, DMG_ROLL_RANDOM);
simDamage.random += AI_ApplyModifiersAfterDmgRoll(&ctx, damageByRollType);
}
}
else
@ -928,15 +1002,18 @@ struct SimulatedDamage AI_CalcDamage(enum Move move, enum BattlerId battlerAtk,
simDamage.minimum = GetDamageByRollType(damage, DMG_ROLL_LOWEST);
simDamage.minimum = AI_ApplyModifiersAfterDmgRoll(&ctx, simDamage.minimum);
simDamage.median = GetDamageByRollType(damage, DMG_ROLL_DEFAULT);
simDamage.median = GetDamageByRollType(damage, DMG_ROLL_MEDIAN);
simDamage.median = AI_ApplyModifiersAfterDmgRoll(&ctx, simDamage.median);
simDamage.maximum = GetDamageByRollType(damage, DMG_ROLL_HIGHEST);
simDamage.maximum = AI_ApplyModifiersAfterDmgRoll(&ctx, simDamage.maximum);
simDamage.random = GetDamageByRollType(damage, DMG_ROLL_RANDOM);
simDamage.random = AI_ApplyModifiersAfterDmgRoll(&ctx, simDamage.random);
}
if (GetActiveGimmick(battlerAtk) != GIMMICK_Z_MOVE)
CalcDynamicMoveDamage(&ctx, &simDamage.median, &simDamage.minimum, &simDamage.maximum);
CalcDynamicMoveDamage(&ctx, &simDamage.median, &simDamage.minimum, &simDamage.maximum, &simDamage.random);
AI_RestoreBattlerTypes(battlerAtk, types);
}
@ -945,6 +1022,7 @@ struct SimulatedDamage AI_CalcDamage(enum Move move, enum BattlerId battlerAtk,
simDamage.minimum = 0;
simDamage.median = 0;
simDamage.maximum = 0;
simDamage.random = 0;
}
// convert multiper to AI_EFFECTIVENESS_xX
@ -1490,14 +1568,14 @@ bool32 CanTargetFaintAi(enum BattlerId battlerDef, enum BattlerId battlerAtk)
return FALSE;
}
u32 NoOfHitsForTargetToFaintBattler(enum BattlerId battlerDef, enum BattlerId battlerAtk, enum AiConsiderEndure considerEndure)
u32 NoOfHitsForTargetToFaintBattler(enum BattlerId battlerDef, enum BattlerId battlerAtk, enum DamageCalcContext calcContext, enum AiConsiderEndure considerEndure)
{
u32 currNumberOfHits;
u32 leastNumberOfHits = UNKNOWN_NO_OF_HITS;
for (u32 moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
{
currNumberOfHits = GetNoOfHitsToKOBattler(battlerDef, battlerAtk, moveIndex, AI_DEFENDING, considerEndure);
currNumberOfHits = GetNoOfHitsToKOBattler(battlerDef, battlerAtk, moveIndex, calcContext, considerEndure);
if (currNumberOfHits != 0)
{
if (currNumberOfHits < leastNumberOfHits)
@ -1544,7 +1622,7 @@ void GetBestDmgMovesFromBattler(enum BattlerId battlerAtk, enum BattlerId battle
{
for (u32 moveIndex = 0; moveIndex < MAX_MON_MOVES; moveIndex++)
{
if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, moveIndex, AI_ATTACKING))
if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, moveIndex, calcContext))
bestMoves[countBestMoves++] = moves[moveIndex];
}
}
@ -1590,7 +1668,7 @@ bool32 IsBestDmgMove(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum
enum Move bestMoves[MAX_MON_MOVES] = {MOVE_NONE};
u32 index = GetMoveIndex(battlerAtk, move);
if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, index, AI_ATTACKING))
if (CanIndexMoveFaintTarget(battlerAtk, battlerDef, index, calcContext))
return TRUE;
GetBestDmgMovesFromBattler(battlerAtk, battlerDef, calcContext, bestMoves);
@ -1873,6 +1951,7 @@ u32 AI_GetSwitchinWeather(enum BattlerId battler)
case ABILITY_DRIZZLE:
return B_WEATHER_RAIN_NORMAL;
case ABILITY_DROUGHT:
case ABILITY_ORICHALCUM_PULSE:
return B_WEATHER_SUN_NORMAL;
case ABILITY_SAND_STREAM:
return B_WEATHER_SANDSTORM;
@ -2460,7 +2539,7 @@ bool32 CanIndexMoveFaintTarget(enum BattlerId battlerAtk, enum BattlerId battler
enum Move *moves = gBattleMons[battlerAtk].moves;
if (IsDoubleBattle() && battlerDef == BATTLE_PARTNER(battlerAtk))
dmg = gAiLogicData->simulatedDmg[battlerAtk][battlerDef][moveIndex].maximum; // Attacking partner, be careful
dmg = AI_GetDamage(battlerAtk, battlerDef, moveIndex, AI_ATTACKING_PARTNER, gAiLogicData); // Attacking partner, be careful
else
dmg = AI_GetDamage(battlerAtk, battlerDef, moveIndex, calcContext, gAiLogicData);
@ -3945,7 +4024,7 @@ static inline bool32 RecoveryEnablesWinning1v1(enum BattlerId battlerAtk, enum B
{
if (!CanTargetFaintAi(battlerDef, battlerAtk)
&& GetBestDmgFromBattler(battlerDef, battlerAtk, AI_DEFENDING) < healAmount
&& NoOfHitsForTargetToFaintBattler(battlerDef, battlerAtk, CONSIDER_ENDURE) < NoOfHitsForTargetToFaintBattlerWithMod(battlerDef, battlerAtk, healAmount))
&& NoOfHitsForTargetToFaintBattler(battlerDef, battlerAtk, AI_DEFENDING, CONSIDER_ENDURE) < NoOfHitsForTargetToFaintBattlerWithMod(battlerDef, battlerAtk, healAmount))
return TRUE; // target can't faint attacker and is dealing less damage than we're healing
else if (!CanTargetFaintAi(battlerDef, battlerAtk) && gAiLogicData->hpPercents[battlerAtk] < ENABLE_RECOVERY_THRESHOLD && RandomPercentage(RNG_AI_SHOULD_RECOVER, SHOULD_RECOVER_CHANCE))
return TRUE; // target can't faint attacker at all, generally safe
@ -4530,28 +4609,6 @@ void FreeRestoreAiLogicData(struct AiLogicData *savedAiLogicData)
Free(savedAiLogicData);
}
// Set potential field effect from ability for switch in
void SetBattlerFieldStatusForSwitchin(enum BattlerId battler)
{
switch (gAiLogicData->abilities[battler])
{
case ABILITY_VESSEL_OF_RUIN:
gBattleMons[battler].volatiles.vesselOfRuin = TRUE;
break;
case ABILITY_SWORD_OF_RUIN:
gBattleMons[battler].volatiles.swordOfRuin = TRUE;
break;
case ABILITY_TABLETS_OF_RUIN:
gBattleMons[battler].volatiles.tabletsOfRuin = TRUE;
break;
case ABILITY_BEADS_OF_RUIN:
gBattleMons[battler].volatiles.beadsOfRuin = TRUE;
break;
default:
break;
}
}
// party logic
s32 CountUsablePartyMons(enum BattlerId battlerId)
{
@ -4827,7 +4884,7 @@ static u32 GetStagesOfStatChange(enum StatChange statChange)
static enum AIScore IncreaseStatUpScoreInternal(enum BattlerId battlerAtk, enum BattlerId battlerDef, enum StatChange statChange, bool32 considerContrary)
{
enum AIScore tempScore = NO_INCREASE;
u32 noOfHitsToFaint = NoOfHitsForTargetToFaintBattler(battlerDef, battlerAtk, DONT_CONSIDER_ENDURE);
u32 noOfHitsToFaint = NoOfHitsForTargetToFaintBattler(battlerDef, battlerAtk, AI_SHOULD_SETUP_DEFENDING, DONT_CONSIDER_ENDURE);
enum Move predictedMoveSpeedCheck = GetIncomingMoveSpeedCheck(battlerAtk, battlerDef, gAiLogicData);
bool32 aiIsFaster = AI_IsFaster(battlerAtk, battlerDef, MOVE_NONE, predictedMoveSpeedCheck, DONT_CONSIDER_PRIORITY); // Don't care about the priority of our setup move, care about outspeeding otherwise
bool32 shouldSetUp = ((noOfHitsToFaint >= 2 && aiIsFaster) || (noOfHitsToFaint >= 3 && !aiIsFaster) || noOfHitsToFaint == UNKNOWN_NO_OF_HITS);
@ -5875,8 +5932,9 @@ bool32 DoesIntimidateRaiseStats(enum Ability ability)
case ABILITY_CONTRARY:
case ABILITY_DEFIANT:
case ABILITY_GUARD_DOG:
case ABILITY_RATTLED:
return TRUE;
case ABILITY_RATTLED:
return GetConfig(B_UPDATED_INTIMIDATE) >= GEN_8;
default:
return FALSE;
}
@ -6427,3 +6485,23 @@ bool32 IsPartyMonPlannedToBeSwitchedInByPartner(u32 partyIndex, enum BattlerId b
return TRUE;
return FALSE;
}
static void AI_SetBattlerTurnOrder(u8 *aiTurnOrder)
{
for (u32 battler = 0; battler < gBattlersCount; battler++)
aiTurnOrder[battler] = battler;
for (u32 i = 0; i < gBattlersCount; i++)
{
for (u32 j = 0; j < gBattlersCount; j++)
{
if (AI_WhoStrikesFirst(aiTurnOrder[i], aiTurnOrder[j], MOVE_NONE, MOVE_NONE, DONT_CONSIDER_PRIORITY) == AI_IS_FASTER)
{
u32 temp = aiTurnOrder[i];
aiTurnOrder[i] = aiTurnOrder[j];
aiTurnOrder[j] = temp;
}
}
}
}

View File

@ -2002,8 +2002,6 @@ static void HandleChooseActionAfterDma3(enum BattlerId battler)
gBattle_BG0_Y = DISPLAY_HEIGHT;
if (gBattleStruct->aiDelayTimer != 0)
{
gBattleStruct->aiDelayFrames = gMain.vblankCounter1 - gBattleStruct->aiDelayTimer;
gBattleStruct->aiDelayTimer = 0;
if (DEBUG_AI_DELAY_TIMER)
{
static const u8 sFramesText[] = _(" frames thinking\n");
@ -2016,6 +2014,8 @@ static void HandleChooseActionAfterDma3(enum BattlerId battler)
StringAppend(gDisplayedStringBattle, sCyclesText);
BattlePutTextOnWindow(gDisplayedStringBattle, B_WIN_ACTION_PROMPT);
}
gBattleStruct->aiDelayTimer = 0;
gBattleStruct->aiDelayFrames = 0;
}
gBattlerControllerFuncs[battler] = HandleInputChooseAction;
}

View File

@ -3930,7 +3930,7 @@ void BattleTurnPassed(void)
{
if (gSideTimers[i].retaliateTimer > 0)
gSideTimers[i].retaliateTimer--;
}
}
gFieldStatuses &= ~STATUS_FIELD_ION_DELUGE;

View File

@ -50,7 +50,7 @@
#include "pokenav.h"
#include "menu_specialized.h"
#include "data.h"
#include "generational_changes.h"
#include "config_changes.h"
#include "move.h"
#include "constants/abilities.h"
#include "constants/battle_anim.h"
@ -8737,11 +8737,23 @@ static void Cmd_trysetencore(void)
if (gBattleMons[gBattlerTarget].volatiles.encoredMove != GetBattlerChosenMove(gBattlerTarget))
gBattleStruct->moveTarget[gBattlerTarget] = SetRandomTarget(gBattlerTarget);
// Encore always lasts 3 turns, but we need to account for a scenario where Encore changes the move during the same turn.
if (HasBattlerActedThisTurn(gBattlerTarget))
gBattleMons[gBattlerTarget].volatiles.encoreTimer = B_ENCORE_TIMER;
u8 turns;
if (GetConfig(B_ENCORE_TURNS) >= GEN_5)
{
turns = B_ENCORE_TIMER; // 4 turns
if (!HasBattlerActedThisTurn(gBattlerTarget))
turns--; // If the target hasn't yet moved this turn, Encore lasts for only three turns.
}
else if (GetConfig(B_ENCORE_TURNS) >= GEN_4)
{
turns = RandomUniform(RNG_ENCORE_TURNS, 3, 7);
}
else
gBattleMons[gBattlerTarget].volatiles.encoreTimer = B_ENCORE_TIMER - 1;
{
turns = RandomUniform(RNG_ENCORE_TURNS, 2, 6);
}
gBattleMons[gBattlerTarget].volatiles.encoreTimer = turns;
gBattlescriptCurrInstr = cmd->nextInstr;
}
}
@ -9908,6 +9920,7 @@ static void HandleRoomMove(u32 statusFlag, u16 *timer, u8 stringId)
if (gFieldStatuses & statusFlag)
{
gFieldStatuses &= ~statusFlag;
*timer = 0;
gBattleCommunication[MULTISTRING_CHOOSER] = stringId + 1;
}
else
@ -10467,7 +10480,8 @@ static void FinalizeCapture(void)
{
u32 ballId = ItemIdToBallId(gLastThrownBall);
enum NationalDexOrder natDexNo = SpeciesToNationalPokedexNum(gBattleMons[gBattlerTarget].species);
if (GetConfig(B_CRITICAL_CAPTURE_IF_OWNED) >= GEN_9 && GetSetPokedexFlag(natDexNo, FLAG_GET_CAUGHT))
if ((GetConfig(B_CRITICAL_CAPTURE_IF_OWNED) >= GEN_9 && GetSetPokedexFlag(natDexNo, FLAG_GET_CAUGHT))
|| IsCriticalCapture())
{
gBattleSpritesDataPtr->animationData->isCriticalCapture = TRUE;
gBattleSpritesDataPtr->animationData->criticalCaptureSuccess = TRUE;
@ -10850,6 +10864,9 @@ static void Cmd_handleballthrow(void)
if (gBattleResults.catchAttempts[ballId] < 255)
gBattleResults.catchAttempts[ballId]++;
gBattleSpritesDataPtr->animationData->isCriticalCapture = FALSE;
gBattleSpritesDataPtr->animationData->criticalCaptureSuccess = FALSE;
//Master Ball check occurs before critical capture check
if (odds == CAPTURE_GUARANTEED)
{
@ -10860,9 +10877,6 @@ static void Cmd_handleballthrow(void)
u8 shakes;
u8 maxShakes;
gBattleSpritesDataPtr->animationData->isCriticalCapture = FALSE;
gBattleSpritesDataPtr->animationData->criticalCaptureSuccess = FALSE;
if (CriticalCapture(odds))
{
maxShakes = BALL_1_SHAKE; // critical capture doesn't guarantee capture
@ -10887,8 +10901,6 @@ static void Cmd_handleballthrow(void)
if (shakes == maxShakes) // mon caught, copy of the code above
{
if (IsCriticalCapture())
gBattleSpritesDataPtr->animationData->criticalCaptureSuccess = TRUE;
FinalizeCapture();
return;
}

View File

@ -5,7 +5,7 @@
#include "battle_scripts.h"
#include "battle_switch_in.h"
#include "battle_controllers.h"
#include "generational_changes.h"
#include "config_changes.h"
#include "constants/battle.h"
#include "constants/moves.h"

View File

@ -11,7 +11,7 @@
#include "battle_z_move.h"
#include "battle_gimmick.h"
#include "battle_hold_effects.h"
#include "generational_changes.h"
#include "config_changes.h"
#include "party_menu.h"
#include "pokemon.h"
#include "international_string_util.h"
@ -1115,6 +1115,38 @@ bool32 IsLastMonToMove(enum BattlerId battler)
return TRUE;
}
static u32 GetAiTurnOrder(u8 *aiTurnOrder, enum BattlerId battler)
{
for (u32 i = 0; i < gBattlersCount; i++)
{
if (aiTurnOrder[i] == battler)
return i;
}
return 0;
}
static bool32 Ai_AttackerMovesAfterTarget(struct BattleContext *ctx)
{
return GetAiTurnOrder(ctx->aiTurnOrder, ctx->battlerAtk) > GetAiTurnOrder(ctx->aiTurnOrder, ctx->battlerDef);
}
static bool32 Ai_AttackerMovesLast(struct BattleContext *ctx)
{
u32 numAliveBattlers = 0;
u32 battlerTurnOrder = GetAiTurnOrder(ctx->aiTurnOrder, ctx->battlerAtk);
for (enum BattlerId battler = B_BATTLER_0; battler < gBattlersCount; battler++)
{
if (IsBattlerAlive(battler))
numAliveBattlers++;
}
if (battlerTurnOrder >= numAliveBattlers - 1)
return TRUE;
return FALSE;
}
bool32 ShouldDefiantCompetitiveActivate(enum BattlerId battler, enum Ability ability)
{
enum BattleSide side = GetBattlerSide(battler);
@ -3207,28 +3239,7 @@ u32 AbilityBattleEffects(enum AbilityEffect caseID, enum BattlerId battler, enum
case ABILITY_DOWNLOAD:
if (shouldAbilityTrigger)
{
enum Stat statId;
enum BattlerId opposingBattler;
u32 opposingDef = 0, opposingSpDef = 0;
opposingBattler = BATTLE_OPPOSITE(battler);
for (i = 0; i < 2; opposingBattler ^= BIT_FLANK, i++)
{
if (IsBattlerAlive(opposingBattler))
{
opposingDef += gBattleMons[opposingBattler].defense
* gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_DEF]][0]
/ gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_DEF]][1];
opposingSpDef += gBattleMons[opposingBattler].spDefense
* gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_SPDEF]][0]
/ gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_SPDEF]][1];
}
}
if (opposingDef < opposingSpDef)
statId = STAT_ATK;
else
statId = STAT_SPATK;
enum Stat statId = GetDownloadStat(battler);
if (CompareStat(battler, statId, MAX_STAT_STAGE, CMP_LESS_THAN, gLastUsedAbility))
{
@ -6299,14 +6310,28 @@ static inline u32 CalcMoveBasePower(struct BattleContext *ctx)
}
break;
case EFFECT_PAYBACK:
if (HasBattlerActedThisTurn(battlerDef)
if (ctx->aiCalc)
{
if (Ai_AttackerMovesAfterTarget(ctx))
basePower *= 2;
}
else if (HasBattlerActedThisTurn(battlerDef)
&& (B_PAYBACK_SWITCH_BOOST < GEN_5 || gBattleStruct->battlerState[battlerDef].isFirstTurn != 2))
{
basePower *= 2;
}
break;
case EFFECT_BOLT_BEAK:
if (!HasBattlerActedThisTurn(battlerDef)
|| gBattleStruct->battlerState[battlerDef].isFirstTurn == 2)
if (ctx->aiCalc)
{
if (!Ai_AttackerMovesAfterTarget(ctx))
basePower *= 2;
}
else if (!HasBattlerActedThisTurn(battlerDef)
|| gBattleStruct->battlerState[battlerDef].isFirstTurn == 2)
{
basePower *= 2;
}
break;
case EFFECT_FUSION_COMBO:
if (move == gLastUsedMove)
@ -6413,9 +6438,12 @@ static inline u32 CalcMoveBasePowerAfterModifiers(struct BattleContext *ctx)
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
break;
case EFFECT_RETALIATE:
if (gSideTimers[atkSide].retaliateTimer == 1)
{
u32 retaliateTimer = gSideTimers[atkSide].retaliateTimer;
if (retaliateTimer == 1 || (gAiLogicData->switchInCalc && retaliateTimer == 2))
modifier = uq4_12_multiply(modifier, UQ_4_12(2.0));
break;
}
case EFFECT_SOLAR_BEAM:
if ((GetConfig(B_SANDSTORM_SOLAR_BEAM) >= GEN_3 && IsBattlerWeatherAffected(ctx->holdEffectAtk, ctx->weather, B_WEATHER_LOW_LIGHT))
|| IsBattlerWeatherAffected(ctx->holdEffectAtk, ctx->weather, (B_WEATHER_RAIN | B_WEATHER_ICY_ANY | B_WEATHER_FOG))) // Excludes Sandstorm
@ -6502,8 +6530,18 @@ static inline u32 CalcMoveBasePowerAfterModifiers(struct BattleContext *ctx)
modifier = uq4_12_multiply(modifier, UQ_4_12(0.75));
break;
case ABILITY_ANALYTIC:
if (IsLastMonToMove(battlerAtk) && moveEffect != EFFECT_FUTURE_SIGHT)
modifier = uq4_12_multiply(modifier, UQ_4_12(1.3));
if (moveEffect == EFFECT_FUTURE_SIGHT)
break;
if (ctx->aiCalc)
{
if (Ai_AttackerMovesLast(ctx))
modifier = uq4_12_multiply(modifier, UQ_4_12(1.3));
}
else if (IsLastMonToMove(battlerAtk))
{
modifier = uq4_12_multiply(modifier, UQ_4_12(1.3));
}
break;
case ABILITY_TOUGH_CLAWS:
if (IsMoveMakingContact(battlerAtk, battlerDef, ctx->abilityAtk, ctx->holdEffectAtk, ctx->move))
@ -10925,3 +10963,28 @@ bool32 IsNaturalEnemy(enum Species speciesAttacker, enum Species speciesTarget)
}
return FALSE;
}
enum Stat GetDownloadStat(enum BattlerId battler)
{
enum BattlerId opposingBattler;
u32 opposingDef = 0, opposingSpDef = 0;
opposingBattler = BATTLE_OPPOSITE(battler);
for (u32 i = 0; i < 2; opposingBattler ^= BIT_FLANK, i++)
{
if (IsBattlerAlive(opposingBattler))
{
opposingDef += gBattleMons[opposingBattler].defense
* gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_DEF]][0]
/ gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_DEF]][1];
opposingSpDef += gBattleMons[opposingBattler].spDefense
* gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_SPDEF]][0]
/ gStatStageRatios[gBattleMons[opposingBattler].statStages[STAT_SPDEF]][1];
}
}
if (opposingDef < opposingSpDef)
return STAT_ATK;
else
return STAT_SPATK;
}

View File

@ -2405,7 +2405,12 @@ static u8 TryForMutation(u8 berryTreeId, u8 berry)
{
x2 = gObjectEvents[j].currentCoords.x;
y2 = gObjectEvents[j].currentCoords.y;
if (Random() % 100 < (OW_BERRY_MUTATION_CHANCE * (mulch == ITEM_TO_MULCH(ITEM_SURPRISE_MULCH) || mulch == ITEM_TO_MULCH(ITEM_AMAZE_MULCH))) && (
u32 rate = OW_BERRY_MUTATION_CHANCE;
if (mulch == ITEM_TO_MULCH(ITEM_SURPRISE_MULCH) || mulch == ITEM_TO_MULCH(ITEM_AMAZE_MULCH))
rate *= 2;
if (Random() % 100 < rate && (
(x1 == x2 && y1 == y2 - 1) ||
(x1 == x2 && y1 == y2 + 1) ||
(x1 == x2 - 1 && y1 == y2) ||

View File

@ -1,7 +1,7 @@
#include "global.h"
#include "generational_changes.h"
#include "config_changes.h"
#include "malloc.h"
#include "constants/generational_changes.h"
#include "constants/config_changes.h"
#include "config/pokerus.h"
#include "config/ai.h"

View File

@ -44,15 +44,3 @@ EVs: 252 Atk / 252 SpA / 6 SpD
- Protect
- Solar Beam
- Dragon Claw
=== PARTNER_DUMMY ===
Name:
Class: Pkmn Trainer 1
Pic: Brendan
Gender: Male
Music: Male
Back Pic: Brendan
Party Size: 0
Wynaut

View File

@ -30,7 +30,7 @@ EVs: 252 Atk / 252 Def / 6 SpA
=== DEBUG_TRAINER_AI ===
Name: Debugger
AI: Basic Trainer
AI: Smart Trainer
Class: Rival
Battle Type: Singles
Pic: Steven

View File

@ -1756,6 +1756,8 @@ const u16 gItemIconPalette_DarkTMHM[] = INCBIN_U16("graphics/items/icon_palettes
const u16 gItemIconPalette_SteelTMHM[] = INCBIN_U16("graphics/items/icon_palettes/steel_tm_hm.gbapal");
const u16 gItemIconPalette_MysteryTMHM[] = INCBIN_U16("graphics/items/icon_palettes/mystery_tm_hm.gbapal");
const u16 gItemIconPalette_FairyTMHM[] = INCBIN_U16("graphics/items/icon_palettes/fairy_tm_hm.gbapal");
// Charms

View File

@ -196,7 +196,7 @@
"respawn_x": 8,
"respawn_y": 5,
"respawn_map": "MAP_PALLET_TOWN_PLAYERS_HOUSE_1F",
"respawn_npc": "LOCALID_MOM"
"respawn_npc": "LOCALID_PLAYERS_HOUSE_1F_MOM"
},
{
"id": "HEAL_LOCATION_VIRIDIAN_CITY",

View File

@ -6141,8 +6141,16 @@ const struct MoveInfo gMovesInfo[MOVES_COUNT_ALL] =
{
.name = COMPOUND_STRING("Encore"),
.description = COMPOUND_STRING(
#if B_ENCORE_TURNS >= GEN_5
"Makes the foe repeat its\n"
"last move over 3 turns."),
#elif B_ENCORE_TURNS >= GEN_4
"Makes the foe repeat its\n"
"last move over 3-7 turns."),
#else
"Makes the foe repeat its\n"
"last move over 2-6 turns."),
#endif
.effect = EFFECT_ENCORE,
.power = 0,
.type = TYPE_NORMAL,

View File

@ -53,6 +53,8 @@ const struct TypeInfo gTypesInfo[NUMBER_OF_MON_TYPES] =
.name = _("None"),
.generic = _("a move"),
.palette = 15, // Uses TYPE_MYSTERY's icon
.zMove = MOVE_BREAKNECK_BLITZ,
.maxMove = MOVE_MAX_STRIKE,
.teraTypeRGBValue = RGB_WHITE,
.damageCategory = DAMAGE_CATEGORY_PHYSICAL,
.paletteTMHM = gItemIconPalette_NormalTMHM,
@ -261,8 +263,11 @@ const struct TypeInfo gTypesInfo[NUMBER_OF_MON_TYPES] =
.name = _("???"),
.generic = _("a ??? move"),
.palette = 15,
.zMove = MOVE_BREAKNECK_BLITZ,
.maxMove = MOVE_MAX_STRIKE,
.teraTypeRGBValue = RGB_WHITE,
.damageCategory = DAMAGE_CATEGORY_SPECIAL,
.damageCategory = DAMAGE_CATEGORY_PHYSICAL,
.paletteTMHM = gItemIconPalette_MysteryTMHM,
.useSecondTypeIconPalette = FALSE,
.isSpecialCaseType = TRUE,
.isHiddenPowerType = FALSE,
@ -473,6 +478,7 @@ const struct TypeInfo gTypesInfo[NUMBER_OF_MON_TYPES] =
.zMove = MOVE_BREAKNECK_BLITZ,
.maxMove = MOVE_MAX_STRIKE,
.teraTypeRGBValue = RGB(10, 18, 27),
.damageCategory = DAMAGE_CATEGORY_SPECIAL,
.paletteTMHM = gItemIconPalette_NormalTMHM, // failsafe
.useSecondTypeIconPalette = FALSE,
.isSpecialCaseType = TRUE,

View File

@ -1454,9 +1454,21 @@ static void Task_RushInjuredPokemonToCenter(u8 taskId)
{
DestroyTask(taskId);
if (gTasks[taskId].tIsPlayerHouse)
{
if (IS_FRLG)
StringCopy(gStringVar1, COMPOUND_STRING("PROF. OAK"));
else
StringCopy(gStringVar1, COMPOUND_STRING("PROF. BIRCH"));
ScriptContext_SetupScript(EventScript_AfterWhiteOutMomHeal);
}
else if (IS_FRLG)
{
ScriptContext_SetupScript(EventScript_AfterWhiteOutHeal_Frlg);
}
else
{
ScriptContext_SetupScript(EventScript_AfterWhiteOutHeal);
}
}
break;
}

View File

@ -102,4 +102,5 @@ void SetWhiteoutRespawnWarpAndHealerNPC(struct WarpData *warp)
warp->x = sWhiteoutRespawnHealCenterMapIdxs[healLocationId - 1][2];
warp->y = sWhiteoutRespawnHealCenterMapIdxs[healLocationId - 1][3];
gSpecialVar_LastTalked = healNpcLocalId;
gSpecialVar_0x800B = healNpcLocalId;
}

View File

@ -220,7 +220,7 @@ void ResetPaletteFadeControl(void)
static u8 UpdateTimeOfDayPaletteFade(void)
{
u32 timePalettes;
u16 copyPalettes;
u32 copyPalettes;
u16 *src;
u16 *dst;

View File

@ -286,6 +286,9 @@ static const u32 sPokedexPlusHGSS_ScreenSearchNational_Tilemap[] = INCBIN_U32("g
#define MON_PAGE_X 48
#define MON_PAGE_Y 56
// Width of pixels one line of text in the species box can be
#define STATS_PAGE_SPECIES_MAX_WIDTH 55
static EWRAM_DATA struct PokedexView *sPokedexView = NULL;
static EWRAM_DATA u16 sLastSelectedPokemon = 0;
static EWRAM_DATA u8 sPokeBallRotation = 0;
@ -597,6 +600,10 @@ u32 GetSpeciesNameFontId(u32 nameWidth);
u32 GetSpeciesNameWidthInChars(const u8 *speciesName);
bool32 IsSpeciesAlcremie(enum Species targetSpecies);
bool32 IsItemSweet(enum Item item);
static void TryLoadDarkModeArrowPalette(void);
//Cry screen
static void FillCryMeterWindowTilemapWithBg(void);
//Stat bars by DizzyEgg
#define TAG_STAT_BAR 4097
@ -1140,6 +1147,7 @@ static const struct CompressedSpriteSheet sInterfaceSpriteSheet[] =
static const struct SpritePalette sInterfaceSpritePalette[] =
{
{sPokedexPlusHGSS_Default_Pal, TAG_DEX_INTERFACE},
{sPokedexPlusHGSS_Default_dark_Pal, TAG_DEX_INTERFACE},
{0}
};
@ -2377,7 +2385,7 @@ static bool8 LoadPokedexListPage(u8 page)
FreeAllSpritePalettes();
gReservedSpritePaletteCount = 8;
LoadCompressedSpriteSheet(&sInterfaceSpriteSheet[HGSS_DECAPPED]);
LoadSpritePalettes(sInterfaceSpritePalette);
LoadSpritePalette(&sInterfaceSpritePalette[HGSS_DARK_MODE]);
LoadSpritePalettes(sStatBarSpritePal);
CreateInterfaceSprites(page);
gMain.state++;
@ -4278,6 +4286,15 @@ static void PrintInfoScreenTextSmall(const u8* str, u8 fontId, u8 left, u8 top)
AddTextPrinterParameterized4(0, fontId, left, top, 0, 0, color, 0, str);
}
static void PrintStatsScreenTextSmallNarrower(u8 windowId, const u8* str, u8 left, u8 top)
{
u8 color[3];
color[0] = TEXT_COLOR_TRANSPARENT;
color[1] = TEXT_DYNAMIC_COLOR_6;
color[2] = TEXT_COLOR_LIGHT_GRAY;
AddTextPrinterParameterized4(windowId, FONT_SMALL_NARROWER, left, top, 0, 0, color, 0, str);
}
static void UNUSED PrintInfoScreenTextSmallWhite(const u8* str, u8 left, u8 top)
{
u8 color[3];
@ -5283,7 +5300,11 @@ static void PrintStatsScreen_NameGender(u8 taskId, u32 num, u32 value)
u8 gender_x, gender_y;
//Name
PrintStatsScreenTextSmall(WIN_STATS_NAME_GENDER, GetSpeciesName(species), base_x, base_y);
const u8 *name = GetSpeciesName(species);
if (GetStringWidth(FONT_SMALL, name, 0) <= STATS_PAGE_SPECIES_MAX_WIDTH)
PrintStatsScreenTextSmall(WIN_STATS_NAME_GENDER, name, base_x, base_y);
else
PrintStatsScreenTextSmallNarrower(WIN_STATS_NAME_GENDER, name, base_x, base_y);
//Number
if (value == 0)
@ -5999,6 +6020,7 @@ static void Task_LoadEvolutionScreen(u8 taskId)
gTasks[taskId].data[3] = 0;
PrintEvolutionTargetSpeciesAndMethod(taskId, NationalPokedexNumToSpeciesHGSS(sPokedexListItem->dexNum), 0, &depth, alreadyPrintedIcons, &iconDepth, 0);
LoadSpritePalette(&gSpritePalette_Arrow);
TryLoadDarkModeArrowPalette();
GetSeenFlagTargetSpecies();
if (sPokedexView->sEvoScreenData.numAllEvolutions > 0 && sPokedexView->sEvoScreenData.numSeen > 0)
{
@ -6460,10 +6482,10 @@ static void PrintEvolutionTargetSpeciesAndMethod(u8 taskId, enum Species species
bool32 isAlcremie = IsSpeciesAlcremie(targetSpecies);
enum Species speciesNameWidthInChars = GetSpeciesNameWidthInChars(GetSpeciesName(targetSpecies));
enum Species speciesNameCharWidth = GetFontAttribute(GetSpeciesNameFontId(speciesNameWidthInChars), FONTATTR_MAX_LETTER_WIDTH);
u32 speciesNameWidthInChars = GetSpeciesNameWidthInChars(GetSpeciesName(targetSpecies));
u32 speciesNameCharWidth = GetFontAttribute(GetSpeciesNameFontId(speciesNameWidthInChars), FONTATTR_MAX_LETTER_WIDTH);
enum Species speciesNameWidth = (speciesNameWidthInChars * speciesNameCharWidth);
u32 speciesNameWidth = (speciesNameWidthInChars * speciesNameCharWidth);
u32 base_x_offset = speciesNameWidth + base_x + depth_offset; // for evo method info
u32 maxScreenWidth = 230 - base_x_offset;
@ -6915,6 +6937,7 @@ static void Task_LoadFormsScreen(u8 taskId)
gTasks[taskId].data[3] = 0;
PrintForms(taskId, NationalPokedexNumToSpeciesHGSS(sPokedexListItem->dexNum));
LoadSpritePalette(&gSpritePalette_Arrow);
TryLoadDarkModeArrowPalette();
gMain.state++;
break;
case 5:
@ -7235,6 +7258,7 @@ static void Task_LoadCryScreen(u8 taskId)
cryMeter.yPos = 3;
if (LoadCryMeter(&cryMeter, 3))
gMain.state++;
FillCryMeterWindowTilemapWithBg();
CopyWindowToVram(WIN_VU_METER, COPYWIN_GFX);
CopyWindowToVram(WIN_INFO, COPYWIN_FULL);
CopyBgTilemapBufferToVram(0);
@ -7451,11 +7475,21 @@ static void LoadPlayArrowPalette(bool8 cryPlaying)
{
u16 color;
if (cryPlaying)
color = RGB(18, 28, 0);
if (!HGSS_DARK_MODE)
{
if (cryPlaying)
color = RGB(29, 9, 4);
else
color = RGB(18, 2, 4);
}
else
color = RGB(15, 21, 0);
LoadPalette(&color, BG_PLTT_ID(5) + 13, PLTT_SIZEOF(1));
{
if (cryPlaying)
color = RGB(9, 9, 9);
else
color = RGB(3, 3, 3);
}
LoadPalette(&color, BG_PLTT_ID(0) + 11, PLTT_SIZEOF(1));
}
static void Task_HandleSizeScreenInput(u8 taskId)
@ -7869,6 +7903,13 @@ static u8 LoadSearchMenu(void)
return CreateTask(Task_LoadSearchMenu, 0);
}
static void PrintSearchTextToFit(const u8 *str, u32 x, u32 y, u32 width)
{
static const u8 color[3] = { TEXT_COLOR_TRANSPARENT, TEXT_DYNAMIC_COLOR_6, TEXT_COLOR_DARK_GRAY };
u32 fontId = GetFontIdToFit(str, FONT_NORMAL, 0, width);
AddTextPrinterParameterized4(0, fontId, x, y, 0, 0, color, TEXT_SKIP_DRAW, str);
}
static void PrintSearchText(const u8 *str, u32 x, u32 y)
{
u8 color[3];
@ -7940,7 +7981,7 @@ static void Task_LoadSearchMenu(u8 taskId)
break;
case 1:
LoadCompressedSpriteSheet(&sInterfaceSpriteSheet[HGSS_DECAPPED]);
LoadSpritePalettes(sInterfaceSpritePalette);
LoadSpritePalette(&sInterfaceSpritePalette[HGSS_DARK_MODE]);
LoadSpritePalettes(sStatBarSpritePal);
CreateSearchParameterScrollArrows(taskId);
for (i = 0; i < NUM_TASK_DATA; i++)
@ -8504,10 +8545,10 @@ static void PrintSelectedSearchParameters(u8 taskId)
PrintSearchText(sDexSearchColorOptions[searchParamId].title, 0x2D, 0x21);
searchParamId = gTasks[taskId].tCursorPos_TypeLeft + gTasks[taskId].tScrollOffset_TypeLeft;
PrintSearchText(sDexSearchTypeOptions[searchParamId].title, 0x2D, 0x31);
PrintSearchTextToFit(sDexSearchTypeOptions[searchParamId].title, 0x2D, 0x31, 38);
searchParamId = gTasks[taskId].tCursorPos_TypeRight + gTasks[taskId].tScrollOffset_TypeRight;
PrintSearchText(sDexSearchTypeOptions[searchParamId].title, 0x5D, 0x31);
PrintSearchTextToFit(sDexSearchTypeOptions[searchParamId].title, 0x5D, 0x31, 38);
searchParamId = gTasks[taskId].tCursorPos_Order + gTasks[taskId].tScrollOffset_Order;
PrintSearchText(sDexOrderOptions[searchParamId].title, 0x2D, 0x41);
@ -8741,3 +8782,33 @@ static void ClearSearchParameterBoxText(void)
{
ClearSearchMenuRect(144, 8, 96, 96);
}
static void TryLoadDarkModeArrowPalette(void)
{
if (!HGSS_DARK_MODE)
return;
u32 index = IndexOfSpritePaletteTag(gSpritePalette_Arrow.tag);
u32 colorArrow = RGB2GBA(72, 72, 72);
u32 colorOutline = RGB2GBA(24, 24, 24);
LoadPalette(&colorArrow, OBJ_PLTT_ID(index) + 1, sizeof(colorArrow));
LoadPalette(&colorOutline, OBJ_PLTT_ID(index) + 2, sizeof(colorOutline));
}
static void FillCryMeterWindowTilemapWithBg(void)
{
// This fills the window behind the 'VU' text on the cry meter.
// It is filled with blank tiles, showing as black.
struct Window windowLocal = gWindows[WIN_VU_METER];
FillBgTilemapBufferRect(
3,
0,
windowLocal.window.tilemapLeft,
windowLocal.window.tilemapTop,
windowLocal.window.width,
windowLocal.window.height,
windowLocal.window.paletteNum);
}

View File

@ -39,8 +39,7 @@ static const struct UnknownStruct sBigMonSizeTable[] =
{ 1700, 1, -26 },
};
// - 4 for unused gift ribbon bits in MON_DATA_UNUSED_RIBBONS
static const u8 sGiftRibbonsMonDataIds[GIFT_RIBBONS_COUNT - 4] =
static const u8 sGiftRibbonsMonDataIds[NUM_GIFT_RIBBONS] =
{
MON_DATA_MARINE_RIBBON, MON_DATA_LAND_RIBBON, MON_DATA_SKY_RIBBON,
MON_DATA_COUNTRY_RIBBON, MON_DATA_NATIONAL_RIBBON, MON_DATA_EARTH_RIBBON,
@ -259,7 +258,7 @@ void GiveGiftRibbonToParty(u8 index, u8 ribbonId)
u8 array[ARRAY_COUNT(sGiftRibbonsMonDataIds)];
memcpy(array, sGiftRibbonsMonDataIds, sizeof(sGiftRibbonsMonDataIds));
if (index < GIFT_RIBBONS_COUNT && ribbonId <= MAX_GIFT_RIBBON)
if (index < NUM_GIFT_RIBBONS && ribbonId <= MAX_GIFT_RIBBON)
{
gSaveBlock1Ptr->giftRibbons[index] = ribbonId;
for (i = 0; i < PARTY_SIZE; i++)

View File

@ -519,7 +519,7 @@ static void PrintInstructionsOnWindow(struct PokemonSpriteVisualizer *data)
{
u8 fontId = FONT_SMALL;
u8 x = 2;
enum Species species = data->modifyArrows.currValue;
enum Species species = IsSpeciesEnabled(data->modifyArrows.currValue) ? SanitizeSpeciesId(data->modifyArrows.currValue) : SPECIES_NONE;
u8 textBottom[] = _("BACK:\nFRONT:\nBG:$");
u8 textBottomForms[] = _("BACK:\nFRONT:\nBG:\nFORMS:$");
@ -575,7 +575,8 @@ static void SetStructPtr(u8 taskId, void *ptr)
static void PrintDigitChars(struct PokemonSpriteVisualizer *data)
{
s32 i;
enum Species species = data->modifyArrows.currValue;
enum Species species = IsSpeciesEnabled(data->modifyArrows.currValue) ? data->modifyArrows.currValue : SPECIES_NONE;
u8 text[MODIFY_DIGITS_MAX + POKEMON_NAME_LENGTH + 8];
for (i = 0; i < data->modifyArrows.maxDigits; i++)
@ -793,7 +794,7 @@ static void BattleLoadOpponentMonSpriteGfxCustom(enum Species species, bool8 isF
static void SetConstSpriteValues(struct PokemonSpriteVisualizer *data)
{
enum Species species = SanitizeSpeciesId(data->currentmonId);
enum Species species = IsSpeciesEnabled(data->currentmonId) ? SanitizeSpeciesId(data->currentmonId) : SPECIES_NONE;
data->constSpriteValues.frontPicCoords = gSpeciesInfo[species].frontPicYOffset;
data->constSpriteValues.frontElevation = gSpeciesInfo[species].enemyMonElevation;
data->constSpriteValues.backPicCoords = gSpeciesInfo[species].backPicYOffset;
@ -810,7 +811,7 @@ static void ResetShadowSettings(struct PokemonSpriteVisualizer *data)
{
if (B_ENEMY_MON_SHADOW_STYLE <= GEN_3 || P_GBA_STYLE_SPECIES_GFX == TRUE)
return;
enum Species species = SanitizeSpeciesId(data->currentmonId);
enum Species species = IsSpeciesEnabled(data->currentmonId) ? SanitizeSpeciesId(data->currentmonId) : SPECIES_NONE;
data->shadowSettings.definedX = gSpeciesInfo[species].enemyShadowXOffset;
data->shadowSettings.definedY = gSpeciesInfo[species].enemyShadowYOffset;
data->shadowSettings.definedSize = gSpeciesInfo[species].enemyShadowSize;
@ -910,7 +911,7 @@ static void SpriteCB_Follower(struct Sprite *sprite)
static void LoadAndCreateEnemyShadowSpriteCustom(struct PokemonSpriteVisualizer *data)
{
bool8 invisible = FALSE;
enum Species species = SanitizeSpeciesId(data->currentmonId);
enum Species species = IsSpeciesEnabled(data->currentmonId) ? SanitizeSpeciesId(data->currentmonId) : SPECIES_NONE;
if (B_ENEMY_MON_SHADOW_STYLE >= GEN_4 && P_GBA_STYLE_SPECIES_GFX == FALSE)
{
@ -1040,7 +1041,7 @@ static void DrawFollowerSprite(struct PokemonSpriteVisualizer *data)
if (!OW_POKEMON_OBJECT_EVENTS)
return;
enum Species species = SanitizeSpeciesId(data->currentmonId);
enum Species species = IsSpeciesEnabled(data->currentmonId) ? SanitizeSpeciesId(data->currentmonId) : SPECIES_NONE;
u16 graphicsId = species + OBJ_EVENT_MON;
if (data->isShiny)
graphicsId += OBJ_EVENT_MON_SHINY;
@ -1268,7 +1269,7 @@ void CB2_Pokemon_Sprite_Visualizer(void)
SetStructPtr(taskId, data);
data->currentmonId = SPECIES_BULBASAUR;
species = SanitizeSpeciesId(data->currentmonId);
species = IsSpeciesEnabled(data->currentmonId) ? SanitizeSpeciesId(data->currentmonId) : SPECIES_NONE;
//Print instructions
PrintInstructionsOnWindow(data);
@ -1385,7 +1386,7 @@ static void ResetBGs_PokemonSpriteVisualizer(u16 a)
static void ApplyOffsetSpriteValues(struct PokemonSpriteVisualizer *data)
{
enum Species species = SanitizeSpeciesId(data->currentmonId);
enum Species species = IsSpeciesEnabled(data->currentmonId) ? SanitizeSpeciesId(data->currentmonId) : SPECIES_NONE;
//Back
gSprites[data->backspriteId].y = VISUALIZER_MON_BACK_Y + gSpeciesInfo[species].backPicYOffset + data->offsetsSpriteValues.offset_back_picCoords;
//Front
@ -1398,7 +1399,7 @@ static void ApplyOffsetSpriteValues(struct PokemonSpriteVisualizer *data)
static void UpdateSubmenuOneOptionValue(u8 taskId, bool8 increment)
{
struct PokemonSpriteVisualizer *data = GetStructPtr(taskId);
enum Species species = SanitizeSpeciesId(data->currentmonId);
enum Species species = IsSpeciesEnabled(data->currentmonId) ? SanitizeSpeciesId(data->currentmonId) : SPECIES_NONE;
u8 option = data->submenuYpos[1];
switch (option)
@ -1483,7 +1484,7 @@ static void UpdateSubmenuOneOptionValue(u8 taskId, bool8 increment)
static void UpdateSubmenuTwoOptionValue(u8 taskId, bool8 increment)
{
struct PokemonSpriteVisualizer *data = GetStructPtr(taskId);
enum Species species = SanitizeSpeciesId(data->currentmonId);
enum Species species = IsSpeciesEnabled(data->currentmonId) ? SanitizeSpeciesId(data->currentmonId) : SPECIES_NONE;
u8 option = data->submenuYpos[2];
s8 offset;
u8 y;
@ -1703,7 +1704,7 @@ static void OpenSubmenu(u32 submenu, u8 taskId)
static void HandleInput_PokemonSpriteVisualizer(u8 taskId)
{
struct PokemonSpriteVisualizer *data = GetStructPtr(taskId);
enum Species species = SanitizeSpeciesId(data->currentmonId);
enum Species species = IsSpeciesEnabled(data->currentmonId) ? SanitizeSpeciesId(data->currentmonId) : SPECIES_NONE;
struct Sprite *Frontsprite = &gSprites[data->frontspriteId];
struct Sprite *Backsprite = &gSprites[data->backspriteId];
@ -1987,7 +1988,7 @@ static void HandleInput_PokemonSpriteVisualizer(u8 taskId)
static void ReloadPokemonSprites(struct PokemonSpriteVisualizer *data)
{
const u16 *palette;
enum Species species = SanitizeSpeciesId(data->currentmonId);
enum Species species = IsSpeciesEnabled(data->currentmonId) ? SanitizeSpeciesId(data->currentmonId) : SPECIES_NONE;
s16 offset_y;
u8 front_x = sBattlerCoords[0][1].x;
u8 front_y;

View File

@ -1,6 +1,6 @@
#include "global.h"
#include "event_data.h"
#include "generational_changes.h"
#include "config_changes.h"
#include "pokemon.h"
#include "pokerus.h"
#include "random.h"

View File

@ -202,7 +202,8 @@ static EWRAM_DATA struct {
u8 partnerCursorPosition;
u16 linkData[20];
u8 timer;
u8 giftRibbons[GIFT_RIBBONS_COUNT];
u8 giftRibbons[NUM_GIFT_RIBBONS];
u8 padding[4];
u8 filler_B4[0x81C];
struct {
bool8 active;

View File

@ -197,14 +197,14 @@ AI_SINGLE_BATTLE_TEST("AI prefers Earthquake over Drill Run if both require the
}
}
AI_SINGLE_BATTLE_TEST("AI prefers a weaker move over a one with a downside effect if both require the same number of hits to ko")
AI_SINGLE_BATTLE_TEST("AI prefers a weaker move over one with a downside effect if both require the same number of hits to ko")
{
enum Move move1 = MOVE_NONE, move2 = MOVE_NONE, move3 = MOVE_NONE, move4 = MOVE_NONE;
enum Move expectedMove;
u16 hp, turns;
// Both moves require the same number of turns but Flamethrower will be chosen over Overheat (powerful effect)
PARAMETRIZE { move1 = MOVE_OVERHEAT; move2 = MOVE_FLAMETHROWER; hp = 300; expectedMove = MOVE_FLAMETHROWER; turns = 2; }
PARAMETRIZE { move1 = MOVE_OVERHEAT; move2 = MOVE_FLAMETHROWER; hp = 320; expectedMove = MOVE_FLAMETHROWER; turns = 2; }
// Overheat kill in least amount of turns
PARAMETRIZE { move1 = MOVE_OVERHEAT; move2 = MOVE_FLAMETHROWER; hp = 250; expectedMove = MOVE_OVERHEAT; turns = 1; }
@ -1007,7 +1007,7 @@ AI_SINGLE_BATTLE_TEST("AI has a chance to prioritize last chance priority damage
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_CAMERUPT) { Speed(2); Moves(MOVE_FLAMETHROWER, MOVE_CELEBRATE); }
OPPONENT(SPECIES_FLOATZEL) { Level(90); Speed(1); HP(1); Moves(MOVE_WAVE_CRASH, MOVE_AQUA_JET); }
OPPONENT(SPECIES_FLOATZEL) { Level(85); Speed(1); HP(1); Moves(MOVE_WAVE_CRASH, MOVE_AQUA_JET); }
} WHEN {
TURN { MOVE(player, MOVE_CELEBRATE); EXPECT_MOVE(opponent, MOVE_AQUA_JET); }
}
@ -1296,7 +1296,7 @@ AI_SINGLE_BATTLE_TEST("AI's comparison of damaging moves correctly reads moveset
PLAYER(SPECIES_RAPIDASH_GALAR){ Level(64); HP(1); Nature(NATURE_TIMID); Moves(MOVE_TACKLE);}
OPPONENT(SPECIES_HAXORUS){ Level(64); Nature(NATURE_JOLLY); Ability(ABILITY_MOLD_BREAKER); Moves(move, MOVE_EARTHQUAKE, MOVE_POISON_JAB); }
} WHEN {
TURN {
TURN {
MOVE(player, MOVE_TACKLE);
if (move == MOVE_TACKLE)
SCORE_EQ_VAL(opponent, MOVE_TACKLE, 104);
@ -1307,3 +1307,64 @@ AI_SINGLE_BATTLE_TEST("AI's comparison of damaging moves correctly reads moveset
}
}
}
AI_SINGLE_BATTLE_TEST("Bolt Beak damage will be correctly seen by AI (singles)")
{
u32 playerSpeed, aiSpeed;
PARAMETRIZE { playerSpeed = 20; aiSpeed = 10; }
PARAMETRIZE { playerSpeed = 10; aiSpeed = 20; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_STARMIE) { Speed(playerSpeed); Moves(MOVE_PROTECT, MOVE_CELEBRATE); }
OPPONENT(SPECIES_ZAPDOS) { Speed(aiSpeed); Moves(MOVE_BOLT_BEAK, MOVE_THUNDER); }
} WHEN {
if (playerSpeed > aiSpeed) {
TURN { MOVE(player, MOVE_PROTECT); EXPECT_MOVE(opponent, MOVE_THUNDER); }
TURN { EXPECT_MOVE(opponent, MOVE_THUNDER); }
} else {
TURN { MOVE(player, MOVE_PROTECT); EXPECT_MOVE(opponent, MOVE_BOLT_BEAK); }
TURN { EXPECT_MOVE(opponent, MOVE_BOLT_BEAK); }
}
}
}
AI_DOUBLE_BATTLE_TEST("Bolt Beak damage will be correctly seen by AI (doubles)")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET) { Speed(1); Moves(MOVE_PROTECT, MOVE_CELEBRATE); }
PLAYER(SPECIES_WOBBUFFET) { Speed(3); Moves(MOVE_PROTECT, MOVE_CELEBRATE); }
OPPONENT(SPECIES_ZAPDOS) { Speed(2); Moves(MOVE_BOLT_BEAK, MOVE_THUNDER); }
OPPONENT(SPECIES_ZAPDOS) { Speed(4); Moves(MOVE_BOLT_BEAK, MOVE_THUNDER); }
TIE_BREAK_TARGET(TARGET_TIE_HI, 0);
} WHEN {
TURN {
MOVE(playerLeft, MOVE_PROTECT);
MOVE(playerRight, MOVE_PROTECT);
SCORE_EQ_VAL(opponentLeft, MOVE_BOLT_BEAK, AI_SCORE_DEFAULT + BEST_DAMAGE_MOVE, target:playerLeft);
SCORE_EQ_VAL(opponentLeft, MOVE_BOLT_BEAK, AI_SCORE_DEFAULT, target:playerRight);
SCORE_EQ_VAL(opponentLeft, MOVE_THUNDER, AI_SCORE_DEFAULT, target:playerLeft);
SCORE_EQ_VAL(opponentLeft, MOVE_THUNDER, AI_SCORE_DEFAULT + BEST_DAMAGE_MOVE, target:playerRight);
SCORE_EQ_VAL(opponentRight, MOVE_BOLT_BEAK, AI_SCORE_DEFAULT + BEST_DAMAGE_MOVE, target:playerLeft);
SCORE_EQ_VAL(opponentRight, MOVE_BOLT_BEAK, AI_SCORE_DEFAULT + BEST_DAMAGE_MOVE, target:playerRight);
SCORE_EQ_VAL(opponentRight, MOVE_THUNDER, AI_SCORE_DEFAULT, target:playerLeft);
SCORE_EQ_VAL(opponentRight, MOVE_THUNDER, AI_SCORE_DEFAULT, target:playerRight);
EXPECT_MOVE(opponentLeft, MOVE_THUNDER, target:playerRight);
EXPECT_MOVE(opponentRight, MOVE_BOLT_BEAK, target:playerRight);
}
TURN {
SCORE_EQ_VAL(opponentLeft, MOVE_BOLT_BEAK, AI_SCORE_DEFAULT + BEST_DAMAGE_MOVE, target:playerLeft);
SCORE_EQ_VAL(opponentLeft, MOVE_BOLT_BEAK, AI_SCORE_DEFAULT, target:playerRight);
SCORE_EQ_VAL(opponentLeft, MOVE_THUNDER, AI_SCORE_DEFAULT, target:playerLeft);
SCORE_EQ_VAL(opponentLeft, MOVE_THUNDER, AI_SCORE_DEFAULT + BEST_DAMAGE_MOVE, target:playerRight);
SCORE_EQ_VAL(opponentRight, MOVE_BOLT_BEAK, AI_SCORE_DEFAULT + BEST_DAMAGE_MOVE, target:playerLeft);
SCORE_EQ_VAL(opponentRight, MOVE_BOLT_BEAK, AI_SCORE_DEFAULT + BEST_DAMAGE_MOVE, target:playerRight);
SCORE_EQ_VAL(opponentRight, MOVE_THUNDER, AI_SCORE_DEFAULT, target:playerLeft);
SCORE_EQ_VAL(opponentRight, MOVE_THUNDER, AI_SCORE_DEFAULT, target:playerRight);
EXPECT_MOVE(opponentLeft, MOVE_THUNDER, target:playerRight);
EXPECT_MOVE(opponentRight, MOVE_BOLT_BEAK, target:playerRight);
}
}
}

View File

@ -285,3 +285,24 @@ AI_SINGLE_BATTLE_TEST("Fillet Away AI handling")
TURN { MOVE(player, move); EXPECT_MOVE(opponent, move == MOVE_SCALD ? MOVE_FILLET_AWAY : MOVE_AQUA_CUTTER); }
}
}
AI_SINGLE_BATTLE_TEST("Retaliate sees damage correctly on the field")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_TRY_TO_FAINT | AI_FLAG_CHECK_VIABILITY | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_WOBBUFFET) { Level(50); HP(100); Nature(NATURE_QUIRKY); Ability(ABILITY_TELEPATHY); Speed(58); Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_RATTATA){ Level(1); HP(1); Nature(NATURE_QUIRKY); Speed(1); Moves(MOVE_TACKLE);}
OPPONENT(SPECIES_KANGASKHAN) { Level(50); Nature(NATURE_QUIRKY); Ability(ABILITY_INNER_FOCUS); Speed(251); Moves(MOVE_RETALIATE, MOVE_SLASH); }
} WHEN {
TURN {
MOVE(player, MOVE_TACKLE);
EXPECT_MOVE(opponent, MOVE_TACKLE);
EXPECT_SEND_OUT(opponent, 1);
}
TURN {
MOVE(player, MOVE_TACKLE);
SCORE_EQ_VAL(opponent, MOVE_RETALIATE, (AI_SCORE_DEFAULT + BEST_DAMAGE_MOVE + FAST_KILL));
SCORE_EQ_VAL(opponent, MOVE_SLASH, (AI_SCORE_DEFAULT));
}
}
}

View File

@ -708,16 +708,33 @@ AI_DOUBLE_BATTLE_TEST("AI sees corresponding absorbing abilities on partners")
GIVEN {
ASSUME(GetMoveTarget(MOVE_DISCHARGE) == TARGET_FOES_AND_ALLY);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_HP_AWARE);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_ZIGZAGOON);
PLAYER(SPECIES_ZIGZAGOON);
OPPONENT(SPECIES_SLAKING) { Moves(move, MOVE_SCRATCH); }
OPPONENT(SPECIES_SLAKING) { Moves(move, MOVE_CONSTRICT); }
OPPONENT(species) { HP(1); Ability(ability); Moves(MOVE_POUND, MOVE_EMBER, MOVE_ROUND); }
} WHEN {
if (ability != ABILITY_CLOUD_NINE)
TURN { EXPECT_MOVE(opponentLeft, move); }
else
TURN { EXPECT_MOVE(opponentLeft, MOVE_SCRATCH); }
TURN { EXPECT_MOVE(opponentLeft, MOVE_CONSTRICT); }
}
}
AI_DOUBLE_BATTLE_TEST("AI sees random rolls correctly")
{
PASSES_RANDOMLY(3, 15, RNG_AI_DMG_ROLL_RANDOM); // Slaking KOs with 3 rolls
GIVEN {
WITH_CONFIG(AI_ROLL_ATTACKING, AI_ROLL_RANDOM);
ASSUME(GetMoveTarget(MOVE_DISCHARGE) == TARGET_FOES_AND_ALLY);
ASSUME(GetMoveType(MOVE_DISCHARGE) == TYPE_ELECTRIC);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_ZIGZAGOON);
PLAYER(SPECIES_ZIGZAGOON);
OPPONENT(SPECIES_SLAKING) { Moves(MOVE_DISCHARGE, MOVE_SCRATCH); }
OPPONENT(SPECIES_PIKACHU) { HP(1); Ability(ABILITY_LIGHTNING_ROD); Moves(MOVE_SCRATCH); }
} WHEN {
TURN { EXPECT_MOVE(opponentLeft, MOVE_SCRATCH); }
}
}

View File

@ -1274,6 +1274,105 @@ AI_SINGLE_BATTLE_TEST("Switch AI: AI will switch out if it has <= 66% HP remaini
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out to cycle Intimidate when facing a physical attacker")
{
PASSES_RANDOMLY(SHOULD_SWITCH_INTIMIDATE_PERCENTAGE, 100, RNG_AI_SWITCH_INTIMIDATE);
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ARCANINE) { Ability(ABILITY_INTIMIDATE); Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); EXPECT_SWITCH(opponent, 1); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will not cycle Intimidate when target blocks or punishes Attack drops")
{
u32 Species = SPECIES_NONE;
enum Ability ability;
PARAMETRIZE { Species = SPECIES_TENTACRUEL; ability = ABILITY_CLEAR_BODY; }
PARAMETRIZE { Species = SPECIES_BRAVIARY; ability = ABILITY_DEFIANT; }
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
PLAYER(Species) { Ability(ability); Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ARCANINE) { Ability(ABILITY_INTIMIDATE); Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); EXPECT_MOVE(opponent, MOVE_TACKLE); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will not cycle Intimidate when target is a special attacker")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING);
PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_WATER_GUN); }
OPPONENT(SPECIES_ARCANINE) { Ability(ABILITY_INTIMIDATE); Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
} WHEN {
TURN { MOVE(player, MOVE_WATER_GUN); EXPECT_MOVE(opponent, MOVE_TACKLE); }
}
}
AI_DOUBLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out to cycle Intimidate in doubles when at least one target is valid")
{
PASSES_RANDOMLY(SHOULD_SWITCH_INTIMIDATE_PERCENTAGE, 100, RNG_AI_SWITCH_INTIMIDATE);
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_WATER_GUN); }
OPPONENT(SPECIES_ARCANINE) { Ability(ABILITY_INTIMIDATE); Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentRight); EXPECT_SWITCH(opponentLeft, 2); }
}
}
AI_DOUBLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will not cycle Intimidate in doubles when both targets block Attack drops")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_TENTACRUEL) { Ability(ABILITY_CLEAR_BODY); Moves(MOVE_TACKLE); }
PLAYER(SPECIES_KINGLER) { Ability(ABILITY_HYPER_CUTTER); Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ARCANINE) { Ability(ABILITY_INTIMIDATE); Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft); MOVE(playerRight, MOVE_TACKLE, target: opponentRight); EXPECT_MOVE(opponentLeft, MOVE_TACKLE); }
}
}
AI_DOUBLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will not cycle Intimidate in doubles when one target punishes stat drops")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_BRAVIARY) { Ability(ABILITY_DEFIANT); Moves(MOVE_TACKLE); }
PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ARCANINE) { Ability(ABILITY_INTIMIDATE); Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft); MOVE(playerRight, MOVE_TACKLE, target: opponentRight); EXPECT_MOVE(opponentLeft, MOVE_TACKLE); }
}
}
AI_DOUBLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will not cycle Intimidate in doubles when both targets are special attackers")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_WATER_GUN); }
PLAYER(SPECIES_ZIGZAGOON) { Moves(MOVE_WATER_GUN); }
OPPONENT(SPECIES_ARCANINE) { Ability(ABILITY_INTIMIDATE); Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_ZIGZAGOON) { Moves(MOVE_TACKLE); }
} WHEN {
TURN { MOVE(playerLeft, MOVE_WATER_GUN, target: opponentLeft); MOVE(playerRight, MOVE_WATER_GUN, target: opponentRight); EXPECT_MOVE(opponentLeft, MOVE_TACKLE); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_SWITCHING: AI will switch out if it has been Encore'd into a status move")
{
PASSES_RANDOMLY(SHOULD_SWITCH_ENCORE_STATUS_PERCENTAGE, 100, RNG_AI_SWITCH_ENCORE);
@ -2244,3 +2343,73 @@ AI_DOUBLE_BATTLE_TEST("AI can switch out both mons on the same turn in double ba
TURN { EXPECT_SWITCH(opponentLeft, 2); EXPECT_SWITCH(opponentRight, 3); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI sees stat stage changes on switchin when doing switching calcs")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_LUXIO) { Speed(2); Ability(ABILITY_GUTS); Moves(MOVE_SCRATCH); }
OPPONENT(SPECIES_ZIGZAGOON) { Speed(1); HP(1); MaxHP(100); Moves(MOVE_SCRATCH); }
OPPONENT(SPECIES_LUXIO) { Speed(1); Ability(ABILITY_GUTS); Moves(MOVE_SCRATCH); }
OPPONENT(SPECIES_LUXIO) { Speed(1); Ability(ABILITY_INTIMIDATE); Moves(MOVE_SCRATCH); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_SCRATCH); EXPECT_SEND_OUT(opponent, 2); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI sees status changes on switchin when doing switching calcs")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_ZIGZAGOON) { Speed(2); HP(1); MaxHP(100); Moves(MOVE_TOXIC_SPIKES); }
PLAYER(SPECIES_LUXIO) { Speed(2); Ability(ABILITY_GUTS); Moves(MOVE_SCRATCH); }
OPPONENT(SPECIES_ZIGZAGOON) { Speed(1); HP(1); MaxHP(100); Moves(MOVE_SCRATCH); }
OPPONENT(SPECIES_LUXIO) { Speed(1); Ability(ABILITY_INTIMIDATE); Moves(MOVE_SCRATCH); }
OPPONENT(SPECIES_LUXIO) { Speed(1); Ability(ABILITY_GUTS); Moves(MOVE_SCRATCH); }
} WHEN {
TURN { MOVE(player, MOVE_TOXIC_SPIKES); EXPECT_MOVE(opponent, MOVE_SCRATCH); SEND_OUT(player, 1); }
TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_SCRATCH); EXPECT_SEND_OUT(opponent, 2); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI sees volate changes on switchin when doing switching calcs")
{
GIVEN {
ASSUME(GetItemHoldEffect(ITEM_BOOSTER_ENERGY) == HOLD_EFFECT_BOOSTER_ENERGY);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_ROARING_MOON) { Speed(2); Moves(MOVE_SCRATCH); }
OPPONENT(SPECIES_ZIGZAGOON) { Speed(1); HP(1); MaxHP(100); Moves(MOVE_SCRATCH); }
OPPONENT(SPECIES_ROARING_MOON) { Speed(1); Moves(MOVE_SCRATCH); }
OPPONENT(SPECIES_ROARING_MOON) { Speed(1); Moves(MOVE_SCRATCH); Item(ITEM_BOOSTER_ENERGY); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); EXPECT_MOVE(opponent, MOVE_SCRATCH); EXPECT_SEND_OUT(opponent, 2); }
}
}
AI_SINGLE_BATTLE_TEST("AI_FLAG_SMART_MON_CHOICES: AI sees HP changes on switchin when doing switchin calcs")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_ZIGZAGOON) { Speed(2); HP(1); MaxHP(100); Moves(MOVE_SPIKES); }
PLAYER(SPECIES_BLAZIKEN) { Speed(2); HP(130); MaxHP(400); Ability(ABILITY_SPEED_BOOST); Moves(MOVE_EMBER); }
OPPONENT(SPECIES_ZIGZAGOON) { Speed(1); HP(1); MaxHP(100); Moves(MOVE_SCRATCH); }
OPPONENT(SPECIES_BLAZIKEN) { Speed(1); HP(180); MaxHP(400); Ability(ABILITY_SPEED_BOOST); Moves(MOVE_EMBER); }
OPPONENT(SPECIES_BLAZIKEN) { Speed(1); HP(180); MaxHP(400); Ability(ABILITY_BLAZE); Moves(MOVE_EMBER); }
} WHEN {
TURN { MOVE(player, MOVE_SPIKES); EXPECT_MOVE(opponent, MOVE_SCRATCH); SEND_OUT(player, 1); }
TURN { MOVE(player, MOVE_EMBER); EXPECT_MOVE(opponent, MOVE_SCRATCH); EXPECT_SEND_OUT(opponent, 2); }
}
}
AI_SINGLE_BATTLE_TEST("Retaliate sees damage correctly for post ko switch in")
{
GIVEN {
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT | AI_FLAG_SMART_SWITCHING | AI_FLAG_SMART_MON_CHOICES | AI_FLAG_OMNISCIENT);
PLAYER(SPECIES_GABITE) { Level(50); Speed(2);}
OPPONENT(SPECIES_ZIGZAGOON) { Level(1); Speed(3); HP(1); Moves(MOVE_TACKLE); }
OPPONENT(SPECIES_GROUDON) { Level(85); Speed(3); Moves(MOVE_PRECIPICE_BLADES); }
OPPONENT(SPECIES_STOUTLAND) { Level(50); Speed(3); Moves(MOVE_RETALIATE); }
} WHEN {
TURN { MOVE(player, MOVE_TACKLE); EXPECT_SEND_OUT(opponent, 2); }
}
}

View File

@ -6,7 +6,7 @@ ASSUMPTIONS
ASSUME(GetMoveEffect(MOVE_ENCORE) == EFFECT_ENCORE);
}
SINGLE_BATTLE_TEST("Encore forces consecutive move uses for 3 turns: Encore used before move")
SINGLE_BATTLE_TEST("Encore forces consecutive move uses for 3 turns: Encore used before move (Gen5+)")
{
struct BattlePokemon *encoreUser = NULL;
struct BattlePokemon *encoreTarget = NULL;
@ -14,14 +14,14 @@ SINGLE_BATTLE_TEST("Encore forces consecutive move uses for 3 turns: Encore used
PARAMETRIZE { encoreUser = opponent; encoreTarget = player; speedPlayer = 10; speedOpponent = 20; }
PARAMETRIZE { encoreUser = player; encoreTarget = opponent; speedPlayer = 20; speedOpponent = 10; }
GIVEN {
WITH_CONFIG(B_ENCORE_TARGET, GEN_3);
WITH_CONFIG(B_ENCORE_TURNS, GEN_5);
PLAYER(SPECIES_WOBBUFFET) { Speed(speedPlayer); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(speedOpponent); }
} WHEN {
TURN { MOVE(encoreUser, MOVE_CELEBRATE); MOVE(encoreTarget, MOVE_CELEBRATE); }
TURN { MOVE(encoreUser, MOVE_ENCORE); MOVE(encoreTarget, MOVE_CELEBRATE); }
TURN { FORCED_MOVE(encoreTarget); }
TURN { FORCED_MOVE(encoreTarget); }
TURN { MOVE(encoreTarget, MOVE_CELEBRATE); }
TURN { MOVE(encoreTarget, MOVE_CELEBRATE); }
TURN { MOVE(encoreTarget, MOVE_SPLASH); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, encoreUser);
@ -34,7 +34,7 @@ SINGLE_BATTLE_TEST("Encore forces consecutive move uses for 3 turns: Encore used
}
}
SINGLE_BATTLE_TEST("Encore forces consecutive move uses for 3 turns for player: Encore used after move")
SINGLE_BATTLE_TEST("Encore forces consecutive move uses for 4 turns: Encore used after move (Gen5+)")
{
struct BattlePokemon *encoreUser = NULL;
struct BattlePokemon *encoreTarget = NULL;
@ -42,14 +42,14 @@ SINGLE_BATTLE_TEST("Encore forces consecutive move uses for 3 turns for player:
PARAMETRIZE { encoreUser = opponent; encoreTarget = player; speedPlayer = 20; speedOpponent = 10; }
PARAMETRIZE { encoreUser = player; encoreTarget = opponent; speedPlayer = 10; speedOpponent = 20; }
GIVEN {
WITH_CONFIG(B_ENCORE_TARGET, GEN_3);
WITH_CONFIG(B_ENCORE_TURNS, GEN_5);
PLAYER(SPECIES_WOBBUFFET) { Speed(speedPlayer); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(speedOpponent); }
} WHEN {
TURN { MOVE(encoreTarget, MOVE_CELEBRATE); MOVE(encoreUser, MOVE_ENCORE); }
TURN { FORCED_MOVE(encoreTarget); }
TURN { FORCED_MOVE(encoreTarget); }
TURN { FORCED_MOVE(encoreTarget); }
TURN { MOVE(encoreTarget, MOVE_CELEBRATE); }
TURN { MOVE(encoreTarget, MOVE_CELEBRATE); }
TURN { MOVE(encoreTarget, MOVE_CELEBRATE); }
TURN { MOVE(encoreTarget, MOVE_SPLASH); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, encoreTarget);
@ -124,8 +124,127 @@ SINGLE_BATTLE_TEST("(DYNAMAX) Dynamaxed Pokemon can be encored immediately after
}
}
TO_DO_BATTLE_TEST("Encore's effect ends if the encored move runs out of PP");
TO_DO_BATTLE_TEST("Encore lasts for 2-6 turns (Gen 2-3)");
TO_DO_BATTLE_TEST("Encore lasts for 3-7 turns (Gen 4)");
TO_DO_BATTLE_TEST("Encore lasts for 3 turns (Gen 5+)");
TO_DO_BATTLE_TEST("Encore randomly chooses an opponent target");
SINGLE_BATTLE_TEST("Encore's effect ends if the encored move runs out of PP")
{
GIVEN {
WITH_CONFIG(B_ENCORE_TURNS, GEN_5);
PLAYER(SPECIES_WOBBUFFET) { Speed(1); MovesWithPP({MOVE_SCRATCH, 2}, {MOVE_CELEBRATE, 10}); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(2); Moves(MOVE_ENCORE, MOVE_CELEBRATE); }
} WHEN {
TURN { MOVE(player, MOVE_SCRATCH); MOVE(opponent, MOVE_CELEBRATE); }
TURN { MOVE(opponent, MOVE_ENCORE); MOVE(player, MOVE_CELEBRATE); }
TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_ENCORE, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_SCRATCH, player);
MESSAGE("Wobbuffet ended its encore!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player);
}
}
// NOTE: AI test is required to validate RNG range without MOVE/FORCED_MOVE invalids; there may be a better approach.
AI_SINGLE_BATTLE_TEST("Encore lasts for 2-6 turns (Gen 2-3)")
{
u32 count, turns;
PARAMETRIZE { turns = 2; }
PARAMETRIZE { turns = 3; }
PARAMETRIZE { turns = 4; }
PARAMETRIZE { turns = 5; }
PARAMETRIZE { turns = 6; }
PASSES_RANDOMLY(1, 5, RNG_ENCORE_TURNS);
GIVEN {
WITH_CONFIG(B_ENCORE_TURNS, GEN_3);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET) { Speed(1); Moves(MOVE_ENCORE, MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(2); Moves(MOVE_CELEBRATE); }
} WHEN {
TURN { EXPECT_MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_ENCORE); }
for (count = 0; count < turns - 1; ++count)
TURN { MOVE(player, MOVE_CELEBRATE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_ENCORE, player);
for (count = 0; count < turns - 1; ++count)
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent);
MESSAGE("The opposing Wobbuffet ended its encore!");
}
}
AI_SINGLE_BATTLE_TEST("Encore lasts for 3-7 turns (Gen 4)")
{
u32 count, turns;
PARAMETRIZE { turns = 3; }
PARAMETRIZE { turns = 4; }
PARAMETRIZE { turns = 5; }
PARAMETRIZE { turns = 6; }
PARAMETRIZE { turns = 7; }
PASSES_RANDOMLY(1, 5, RNG_ENCORE_TURNS);
GIVEN {
WITH_CONFIG(B_ENCORE_TURNS, GEN_4);
AI_FLAGS(AI_FLAG_CHECK_BAD_MOVE | AI_FLAG_CHECK_VIABILITY | AI_FLAG_TRY_TO_FAINT);
PLAYER(SPECIES_WOBBUFFET) { Speed(1); Moves(MOVE_ENCORE, MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(2); Moves(MOVE_CELEBRATE); }
} WHEN {
TURN { EXPECT_MOVE(opponent, MOVE_CELEBRATE); MOVE(player, MOVE_ENCORE); }
for (count = 0; count < turns - 1; ++count)
TURN { MOVE(player, MOVE_CELEBRATE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_ENCORE, player);
for (count = 0; count < turns - 1; ++count)
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent);
NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent);
MESSAGE("The opposing Wobbuffet ended its encore!");
}
}
DOUBLE_BATTLE_TEST("Encore randomly chooses an opponent target (Gen 2-4)")
{
GIVEN {
WITH_CONFIG(B_ENCORE_TARGET, GEN_4);
PLAYER(SPECIES_WOBBUFFET) { Speed(3); Moves(MOVE_TACKLE, MOVE_CELEBRATE); }
PLAYER(SPECIES_WOBBUFFET) { Speed(1); Moves(MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(4); Moves(MOVE_ENCORE, MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(2); Moves(MOVE_CELEBRATE); }
} WHEN {
TURN {
MOVE(opponentLeft, MOVE_CELEBRATE);
MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft);
}
TURN {
MOVE(opponentLeft, MOVE_ENCORE, target: playerLeft, WITH_RNG(RNG_RANDOM_TARGET, 1));
MOVE(playerLeft, MOVE_CELEBRATE);
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_ENCORE, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft, target: opponentRight);
HP_BAR(opponentRight);
}
}
DOUBLE_BATTLE_TEST("Encore allows choosing an opponent target (Gen 5+)")
{
GIVEN {
WITH_CONFIG(B_ENCORE_TARGET, GEN_5);
PLAYER(SPECIES_WOBBUFFET) { Speed(3); Moves(MOVE_TACKLE, MOVE_CELEBRATE); }
PLAYER(SPECIES_WOBBUFFET) { Speed(1); Moves(MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(4); Moves(MOVE_ENCORE, MOVE_CELEBRATE); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(2); Moves(MOVE_CELEBRATE); }
} WHEN {
TURN {
MOVE(opponentLeft, MOVE_CELEBRATE);
MOVE(playerLeft, MOVE_TACKLE, target: opponentLeft);
}
TURN {
MOVE(opponentLeft, MOVE_ENCORE, target: playerLeft);
MOVE(playerLeft, MOVE_TACKLE, target: opponentRight);
}
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_ENCORE, opponentLeft);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TACKLE, playerLeft, target: opponentRight);
HP_BAR(opponentRight);
}
}

View File

@ -1,4 +1,76 @@
#include "global.h"
#include "test/battle.h"
ASSUMPTIONS
{
ASSUME(GetMoveEffect(MOVE_TRICK_ROOM) == EFFECT_TRICK_ROOM);
}
SINGLE_BATTLE_TEST("Trick Room doesn't print its ending message twice when used again")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(10); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(1); }
} WHEN {
TURN { MOVE(player, MOVE_TRICK_ROOM); }
TURN { MOVE(player, MOVE_CELEBRATE); }
TURN { MOVE(player, MOVE_CELEBRATE); }
TURN { MOVE(player, MOVE_CELEBRATE); }
TURN { MOVE(player, MOVE_TRICK_ROOM); }
TURN { MOVE(player, MOVE_CELEBRATE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK_ROOM, player);
MESSAGE("Wobbuffet twisted the dimensions!");
ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK_ROOM, player);
MESSAGE("The twisted dimensions returned to normal!");
NOT MESSAGE("The twisted dimensions returned to normal!");
}
}
SINGLE_BATTLE_TEST("Trick Room reverses move order for 5 turns including the turn it is used")
{
GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(1); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(10); }
} WHEN {
TURN { MOVE(player, MOVE_TRICK_ROOM); MOVE(opponent, MOVE_CELEBRATE); }
TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); }
TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); }
TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); }
TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); }
TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_CELEBRATE); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK_ROOM, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player);
}
}
SINGLE_BATTLE_TEST("Trick Room does not affect move priority")
{
GIVEN {
ASSUME(GetMovePriority(MOVE_CELEBRATE) == 0);
ASSUME(GetMovePriority(MOVE_QUICK_ATTACK) == 1);
PLAYER(SPECIES_WOBBUFFET) { Speed(1); }
OPPONENT(SPECIES_WOBBUFFET) { Speed(10); }
} WHEN {
TURN { MOVE(player, MOVE_TRICK_ROOM); MOVE(opponent, MOVE_CELEBRATE); }
TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_QUICK_ATTACK); }
} SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_TRICK_ROOM, player);
ANIMATION(ANIM_TYPE_MOVE, MOVE_QUICK_ATTACK, opponent);
ANIMATION(ANIM_TYPE_MOVE, MOVE_CELEBRATE, player);
}
}
TO_DO_BATTLE_TEST("TODO: Write Trick Room (Move Effect) test titles")

View File

@ -14,6 +14,7 @@ Gender: Male
Music: Male
Back Pic: Steven
AI: Basic Trainer
Difficulty: Normal
Metang
Brave Nature
@ -45,19 +46,7 @@ EVs: 252 Atk / 252 SpA / 6 SpD
- Solar Beam
- Dragon Claw
=== 2 ===
Name: Test2
Class: Rival
Pic: Steven
Gender: Male
Music: Male
Back Pic: Steven
Difficulty: Normal
Mewtwo
Level: 50
=== 2 ===
=== PARTNER_STEVEN_TEST ===
Name: Test2
Class: Rival
Pic: Steven
@ -70,7 +59,7 @@ Difficulty: Easy
Metapod
Level: 1
=== 2 ===
=== PARTNER_STEVEN_TEST ===
Name: Test2
Class: Rival
Pic: Steven

View File

@ -176,11 +176,12 @@ TEST("Difficulty changes which party is used for enemy trainer if defined for th
TEST("Difficulty default to Normal if the partner doesn't have a member for the current difficulty")
{
SetCurrentDifficultyLevel(DIFFICULTY_EASY);
SetCurrentDifficultyLevel(DIFFICULTY_TEST);
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
u32 currTrainer = TRAINER_PARTNER(1);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE, BATTLE_TYPE_TRAINER);
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_METANG);
EXPECT(GetMonData(&testParty[0], MON_DATA_LEVEL) == 42);
Free(testParty);
SetCurrentDifficultyLevel(DIFFICULTY_NORMAL);
}
@ -189,7 +190,7 @@ TEST("Difficulty changes which party is used for partner if defined for the diff
{
SetCurrentDifficultyLevel(DIFFICULTY_EASY);
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
u32 currTrainer = TRAINER_PARTNER(2);
u32 currTrainer = TRAINER_PARTNER(1);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE, BATTLE_TYPE_TRAINER);
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_METAPOD);
EXPECT(GetMonData(&testParty[0], MON_DATA_LEVEL) == 1);
@ -201,7 +202,7 @@ TEST("Difficulty changes which party is used for partner if defined for the diff
{
SetCurrentDifficultyLevel(DIFFICULTY_HARD);
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
u32 currTrainer = TRAINER_PARTNER(2);
u32 currTrainer = TRAINER_PARTNER(1);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE, BATTLE_TYPE_TRAINER);
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_ARCEUS);
EXPECT(GetMonData(&testParty[0], MON_DATA_LEVEL) == 99);
@ -213,10 +214,10 @@ TEST("Difficulty changes which party is used for partner if defined for the diff
{
SetCurrentDifficultyLevel(DIFFICULTY_NORMAL);
struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon));
u32 currTrainer = TRAINER_PARTNER(2);
u32 currTrainer = TRAINER_PARTNER(1);
CreateNPCTrainerPartyFromTrainer(testParty, GetTrainerStructFromId(currTrainer), TRUE, BATTLE_TYPE_TRAINER);
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_MEWTWO);
EXPECT(GetMonData(&testParty[0], MON_DATA_LEVEL) == 50);
EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES) == SPECIES_METANG);
EXPECT(GetMonData(&testParty[0], MON_DATA_LEVEL) == 42);
Free(testParty);
}

View File

@ -3,7 +3,7 @@
#include "event_data.h"
#include "pokemon.h"
#include "pokerus.h"
#include "generational_changes.h"
#include "config_changes.h"
#include "random.h"
#include "test/overworld_script.h"
#include "test/test.h"

View File

@ -154,18 +154,15 @@ void TestRunner_CheckMemory(void)
const char *location = MemBlockLocation(block);
if (location)
{
const char *cmpString = "src/generational_changes.c";
for (u32 charIndex = 0; charIndex < 26; charIndex++)
const char cmpString[] = "src/config_changes.c";
if (strncmp(cmpString, location, sizeof(cmpString) - 1) != 0)
{
if (cmpString[charIndex] != location[charIndex])
{
Test_MgbaPrintf("%s: %d bytes not freed", location, block->size);
gTestRunnerState.result = TEST_RESULT_FAIL;
if (gTestRunnerState.expectedFailState == EXPECT_FAIL_OPEN)
gTestRunnerState.expectedFailState = EXPECT_FAIL_SUCCESS;
break;
}
Test_MgbaPrintf("%s: %d bytes not freed", location, block->size);
gTestRunnerState.result = TEST_RESULT_FAIL;
if (gTestRunnerState.expectedFailState == EXPECT_FAIL_OPEN)
gTestRunnerState.expectedFailState = EXPECT_FAIL_SUCCESS;
break;
}
}
else

View File

@ -24,7 +24,6 @@ For a given species, a move is considered teachable to that species if:
from itertools import chain
from textwrap import dedent
import glob
import json
import pathlib
import re
@ -32,9 +31,9 @@ import sys
import typing
CONFIG_ENABLED_PAT = re.compile(r"#define P_LEARNSET_HELPER_TEACHABLE\s+(?P<cfg_val>[^ ]*)")
ALPHABETICAL_ORDER_ENABLED_PAT = re.compile(r"#define HGSS_SORT_TMS_BY_NUM\s+(?P<cfg_val>[^ ]*)")
TM_LITTERACY_PAT = re.compile(r"#define P_TM_LITERACY\s+GEN_(?P<cfg_val>[^ ]*)")
CONFIG_ENABLED_PAT = re.compile(r"^#define P_LEARNSET_HELPER_TEACHABLE\s+(?P<cfg_val>[^ ]*)", flags=re.MULTILINE)
ALPHABETICAL_ORDER_ENABLED_PAT = re.compile(r"^#define HGSS_SORT_TMS_BY_NUM\s+(?P<cfg_val>[^ ]*)", flags=re.MULTILINE)
TM_LITERACY_PAT = re.compile(r"^#define P_TM_LITERACY\s+GEN_(?P<cfg_val>[^ ]*)", flags=re.MULTILINE)
TMHM_MACRO_PAT = re.compile(r"F\((\w+)\)")
SNAKIFY_PAT = re.compile(r"(?!^)([A-Z]+)")
@ -64,7 +63,7 @@ def extract_tm_litteracy_config() -> bool:
config = False
with open("./include/config/pokemon.h", "r") as cfg_pokemon_fp:
cfg_pokemon = cfg_pokemon_fp.read()
cfg_defined = TM_LITTERACY_PAT.search(cfg_pokemon)
cfg_defined = TM_LITERACY_PAT.search(cfg_pokemon)
if cfg_defined:
cfg_val = cfg_defined.group("cfg_val")
if ((cfg_val == "LATEST") or (int(cfg_val) > 6)):
@ -193,14 +192,13 @@ def main():
print(__doc__, file=sys.stderr)
quit(1)
if len(sys.argv) == 2:
SOURCE_DIR = pathlib.Path(sys.argv[1])
elif len(sys.argv) == 3:
if len(sys.argv) == 3:
if sys.argv[1] != "--tutors":
print("Unknown make_teachables mode", file=sys.stderr)
quit(1)
tutor_mode = True
SOURCE_DIR = pathlib.Path(sys.argv[2])
SOURCE_DIR = pathlib.Path(sys.argv[-1])
with open("src/data/pokemon/special_movesets.json", "r") as file:
special_movesets = json.load(file)
@ -219,7 +217,6 @@ def main():
assert SOURCE_TEACHING_TYPES_JSON.is_file(), f"{SOURCE_TEACHING_TYPES_JSON=} is not a file"
repo_tms = list(extract_repo_tms())
order_alphabetically = False
with open("./include/config/pokedex_plus_hgss.h", "r") as cfg_pokemon_fp:
cfg_pokemon = cfg_pokemon_fp.read()

View File

@ -1,16 +1,24 @@
#!/usr/bin/env python3
"""
Usage: python3 make_teaching_types.py OUTPUT_FILE
Build a primary store of "teaching-types" for each enabled species in the repository as an
input for make_teachables.py.
"""
import glob
import json
import pathlib
import re
import sys
import typing
CONFIG_ENABLED_PAT = re.compile(r"#define P_LEARNSET_HELPER_TEACHABLE\s+(?P<cfg_val>[^ ]*)")
CONFIG_ENABLED_PAT = re.compile(r"^#define P_LEARNSET_HELPER_TEACHABLE\s+(?P<cfg_val>[^ ]*)", flags=re.MULTILINE)
TEACHING_TYPE_PAT = re.compile(r"\s*\.teachingType\s*=\s*(?P<teaching_type>[A-Z_]+),")
LEARNSET_PAT = re.compile(r"\s*\.teachableLearnset\s*=\s*s(?P<name>\w+?)TeachableLearnset")
PREPROC_START_PAT = re.compile(r"#if(def)?\s+\w+")
PREPROC_END_PAT = re.compile(r"#endif\s*(//\s*\w+)?")
PREPROC_START_PAT = re.compile(r"^#if(def)?\s+\w+", flags=re.MULTILINE)
PREPROC_END_PAT = re.compile(r"^#endif\s*(//\s*\w+)?", flags=re.MULTILINE)
def enabled() -> bool:
"""
@ -35,7 +43,7 @@ def extract_repo_species_data() -> list:
pokemon_list = []
teaching_type = "DEFAULT_LEARNING"
file_list = sorted(glob.glob("src/data/pokemon/species_info/*_families.h"))
file_list.append(pathlib.Path("./src/data/pokemon/species_info.h"))
file_list.append("./src/data/pokemon/species_info.h")
for families_fname in file_list:
with open(families_fname, "r") as family_fp:
species_lines = family_fp.readlines()
@ -66,7 +74,7 @@ def extract_repo_species_data() -> list:
teaching_type = match.group("teaching_type")
return species_data
def add_whitesspaces(parsed_list) ->list:
def add_whitesspaces(parsed_list) -> list:
for i, item in enumerate(parsed_list):
if i == 0:
continue

View File

@ -1,5 +1,12 @@
#!/usr/bin/env python3
"""
Usage: python3 make_tutors.py OUTPUT_FILE
Build a primary store of move-tutors in the repository and what moves they teach.
"""
from itertools import chain
from textwrap import dedent
import glob
import json
@ -8,7 +15,7 @@ import re
import sys
import typing
CONFIG_ENABLED_PAT = re.compile(r"#define P_LEARNSET_HELPER_TEACHABLE\s+(?P<cfg_val>[^ ]*)")
CONFIG_ENABLED_PAT = re.compile(r"^#define P_LEARNSET_HELPER_TEACHABLE\s+(?P<cfg_val>[^ ]*)", flags=re.MULTILINE)
INCFILE_HAS_TUTOR_PAT = re.compile(r"special ChooseMonForMoveTutor")
INCFILE_HAS_TUTOR_PAT2 = re.compile(r"chooseboxmon SELECT_PC_MON_MOVE_TUTOR")
INCFILE_MOVE_PAT = re.compile(r"setvar VAR_0x8005, (MOVE_[A-Z_]*)")

View File

@ -160,9 +160,9 @@ Directive AsmFile::GetDirective()
return Directive::Unknown;
}
// Checks if we're at label that ends with '::'.
// Returns the name if so and an empty string if not.
std::string AsmFile::GetGlobalLabel()
// Checks if we're at label and if so, returns its symbol and scope.
// Returns 'label::none' if not.
Label AsmFile::GetLabel()
{
long start = m_pos;
long pos = m_pos;
@ -175,14 +175,57 @@ std::string AsmFile::GetGlobalLabel()
pos++;
}
if (m_buffer[pos] == ':' && m_buffer[pos + 1] == ':')
if (m_buffer[pos] == ':')
{
m_pos = pos + 2;
ExpectEmptyRestOfLine();
return std::string(&m_buffer[start], pos - start);
std::string symbol(&m_buffer[start], pos - start);
if (m_buffer[pos + 1] == ':')
{
m_pos = pos + 2;
ExpectEmptyRestOfLine();
return Label(symbol, Label::global);
}
else
{
m_pos = pos + 1;
return Label(symbol, Label::local);
}
}
return std::string();
return Label("", Label::none);
}
std::string AsmFile::PeekSection()
{
long oldPos = m_pos;
std::string section;
SkipWhitespace();
// TODO: Support 'pushsection', 'popsection', '.previous'.
if (CheckForDirective(".bss"))
{
section = ".bss";
}
else if (CheckForDirective(".data"))
{
section = ".data";
}
else if (CheckForDirective(".rodata"))
{
section = ".rodata";
}
else if (CheckForDirective(".text"))
{
section = ".text";
}
else if (CheckForDirective(".section"))
{
SkipWhitespace();
section = ReadIdentifier();
}
m_pos = oldPos;
return section;
}
// Skips tabs and spaces.

View File

@ -35,6 +35,17 @@ enum class Directive
Unknown
};
struct Label
{
std::string symbol;
enum Type { none, global, local } type;
Label() : symbol(""), type(none) {}
Label(const Label&) = default;
Label(std::string symbol_, Type type_) : symbol(symbol_), type(type_) {}
explicit operator bool() { return !symbol.empty() && type != none; }
};
class AsmFile
{
public:
@ -43,7 +54,8 @@ public:
AsmFile(const AsmFile&) = delete;
~AsmFile();
Directive GetDirective();
std::string GetGlobalLabel();
Label GetLabel();
std::string PeekSection();
std::string ReadPath();
int ReadString(unsigned char* s);
int ReadBraille(unsigned char* s);

View File

@ -51,9 +51,11 @@ void PrintAsmBytes(unsigned char *s, int length)
}
}
void PreprocAsmFile(std::string filename, bool isStdin, bool doEnum)
void PreprocAsmFile(std::string filename, bool isStdin, bool doEnum, bool doSize)
{
std::stack<AsmFile> stack;
Label prevLabel;
bool inScriptData = false;
stack.push(AsmFile(filename, isStdin, doEnum));
std::printf("# 1 \"%s\"\n", filename.c_str());
@ -62,6 +64,11 @@ void PreprocAsmFile(std::string filename, bool isStdin, bool doEnum)
{
while (stack.top().IsAtEnd())
{
const char *ps = prevLabel.symbol.c_str();
if (doSize && inScriptData && prevLabel)
std::printf(".ifdef %s ; .size %s, . - %s ; .endif\n", ps, ps, ps);
prevLabel = Label();
stack.pop();
if (stack.empty())
@ -100,15 +107,28 @@ void PreprocAsmFile(std::string filename, bool isStdin, bool doEnum)
}
case Directive::Unknown:
{
std::string globalLabel = stack.top().GetGlobalLabel();
Label label = stack.top().GetLabel();
if (globalLabel.length() != 0)
if (label)
{
const char *s = globalLabel.c_str();
std::printf("%s: ; .global %s\n", s, s);
const char *s = label.symbol.c_str();
const char *ps = prevLabel.symbol.c_str();
if (doSize && inScriptData && prevLabel)
std::printf(".ifdef %s ; .size %s, . - %s ; .endif ; ", ps, ps, ps);
if (label.type == Label::global)
std::printf(".global %s\n%s:\n", s, s);
prevLabel = label;
}
else
{
std::string section = stack.top().PeekSection();
if (section == "script_data")
inScriptData = true;
else if (section != "")
inScriptData = false;
stack.top().OutputLine();
}
@ -147,7 +167,7 @@ const char* GetFileExtension(const char* filename)
static void UsageAndExit(const char *program)
{
std::fprintf(stderr, "Usage: %s [-i] [-e] SRC_FILE CHARMAP_FILE\nwhere -i denotes if input is from stdin\n -e enables enum handling\n", program);
std::fprintf(stderr, "Usage: %s [-i] [-e] [-s] SRC_FILE CHARMAP_FILE\nwhere -i denotes if input is from stdin\n -e enables enum handling\n -s enables '.size' handling\n", program);
std::exit(EXIT_FAILURE);
}
@ -158,9 +178,10 @@ int main(int argc, char **argv)
const char *charmap = NULL;
bool isStdin = false;
bool doEnum = false;
bool doSize = false;
/* preproc [-i] [-e] SRC_FILE CHARMAP_FILE */
while ((opt = getopt(argc, argv, "ie")) != -1)
/* preproc [-i] [-e] [-s] SRC_FILE CHARMAP_FILE */
while ((opt = getopt(argc, argv, "ies")) != -1)
{
switch (opt)
{
@ -170,6 +191,9 @@ int main(int argc, char **argv)
case 'e':
doEnum = true;
break;
case 's':
doSize = true;
break;
default:
UsageAndExit(argv[0]);
break;
@ -196,7 +220,7 @@ int main(int argc, char **argv)
if ((extension[0] == 's') && extension[1] == 0)
{
PreprocAsmFile(source, isStdin, doEnum);
PreprocAsmFile(source, isStdin, doEnum, doSize);
}
else if ((extension[0] == 'c' || extension[0] == 'i') && extension[1] == 0)
{