diff --git a/data/battle_scripts_1.s b/data/battle_scripts_1.s index a32e9a4ec..2754b5d7a 100644 --- a/data/battle_scripts_1.s +++ b/data/battle_scripts_1.s @@ -8627,6 +8627,7 @@ BattleScript_IntimidateActivates:: BattleScript_IntimidateLoop: jumpifbyteequal gBattlerTarget, gBattlerAttacker, BattleScript_IntimidateLoopIncrement jumpiftargetally BattleScript_IntimidateLoopIncrement + jumpifabsent BS_TARGET, BattleScript_IntimidateLoopIncrement jumpifstatus2 BS_TARGET, STATUS2_SUBSTITUTE, BattleScript_IntimidateLoopIncrement jumpifholdeffect BS_TARGET, HOLD_EFFECT_CLEAR_AMULET, BattleScript_IntimidatePrevented_Item jumpifability BS_TARGET, ABILITY_CLEAR_BODY, BattleScript_IntimidatePrevented diff --git a/graphics/items/icon_palettes/ability_shield.pal b/graphics/items/icon_palettes/ability_shield.pal new file mode 100644 index 000000000..d11016b32 --- /dev/null +++ b/graphics/items/icon_palettes/ability_shield.pal @@ -0,0 +1,19 @@ +JASC-PAL +0100 +16 +0 0 0 +255 255 255 +248 200 240 +224 176 232 +200 144 224 +240 224 248 +176 120 216 +77 146 186 +105 179 221 +238 246 246 +222 222 222 +197 197 197 +161 161 161 +48 48 48 +255 255 255 +0 0 0 diff --git a/graphics/items/icon_palettes/adamant_crystal.pal b/graphics/items/icon_palettes/adamant_crystal.pal new file mode 100644 index 000000000..738ff0498 --- /dev/null +++ b/graphics/items/icon_palettes/adamant_crystal.pal @@ -0,0 +1,19 @@ +JASC-PAL +0100 +16 +0 0 0 +255 255 255 +184 208 248 +168 200 248 +81 123 173 +128 168 216 +200 232 248 +224 248 248 +248 248 248 +104 104 104 +48 48 48 +255 255 255 +0 0 0 +0 0 0 +0 0 0 +0 0 0 diff --git a/graphics/items/icon_palettes/auspicious_armor.pal b/graphics/items/icon_palettes/auspicious_armor.pal new file mode 100644 index 000000000..9d0c8e444 --- /dev/null +++ b/graphics/items/icon_palettes/auspicious_armor.pal @@ -0,0 +1,19 @@ +JASC-PAL +0100 +16 +0 0 0 +255 255 255 +160 144 32 +192 176 56 +224 208 88 +128 112 32 +72 56 24 +224 80 80 +176 88 88 +120 72 72 +48 48 48 +255 255 255 +0 0 0 +0 0 0 +0 0 0 +0 0 0 diff --git a/graphics/items/icon_palettes/big_bamboo_shoot.pal b/graphics/items/icon_palettes/big_bamboo_shoot.pal new file mode 100644 index 000000000..28ab4fe69 --- /dev/null +++ b/graphics/items/icon_palettes/big_bamboo_shoot.pal @@ -0,0 +1,19 @@ +JASC-PAL +0100 +16 +0 0 0 +0 0 0 +189 202 71 +121 168 43 +120 167 42 +163 126 74 +116 101 78 +79 66 46 +115 77 43 +180 133 94 +233 176 96 +241 241 193 +183 170 147 +0 0 0 +0 0 0 +0 0 0 diff --git a/graphics/items/icon_palettes/booster_energy.pal b/graphics/items/icon_palettes/booster_energy.pal new file mode 100644 index 000000000..21dc17ba2 --- /dev/null +++ b/graphics/items/icon_palettes/booster_energy.pal @@ -0,0 +1,19 @@ +JASC-PAL +0100 +16 +0 0 0 +111 45 22 +0 0 0 +190 91 13 +255 139 1 +255 255 255 +255 231 10 +251 42 6 +154 157 151 +193 161 19 +234 238 234 +122 111 115 +74 67 68 +90 185 248 +78 86 255 +111 52 255 diff --git a/graphics/items/icon_palettes/covert_cloak.pal b/graphics/items/icon_palettes/covert_cloak.pal new file mode 100644 index 000000000..06491d2ad --- /dev/null +++ b/graphics/items/icon_palettes/covert_cloak.pal @@ -0,0 +1,19 @@ +JASC-PAL +0100 +16 +0 0 0 +0 0 0 +54 52 72 +101 120 143 +103 139 163 +84 105 130 +74 75 101 +90 108 136 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 diff --git a/graphics/items/icon_palettes/gimmighoul_coin.pal b/graphics/items/icon_palettes/gimmighoul_coin.pal new file mode 100644 index 000000000..555a04dbe --- /dev/null +++ b/graphics/items/icon_palettes/gimmighoul_coin.pal @@ -0,0 +1,19 @@ +JASC-PAL +0100 +16 +0 0 0 +180 124 35 +115 66 13 +255 249 234 +224 159 27 +255 226 121 +255 200 59 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 diff --git a/graphics/items/icon_palettes/griseous_core.pal b/graphics/items/icon_palettes/griseous_core.pal new file mode 100644 index 000000000..05980782c --- /dev/null +++ b/graphics/items/icon_palettes/griseous_core.pal @@ -0,0 +1,19 @@ +JASC-PAL +0100 +16 +0 0 0 +255 255 255 +206 181 41 +222 198 57 +247 231 140 +156 132 33 +189 156 41 +123 99 33 +49 49 49 +255 255 255 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 diff --git a/graphics/items/icon_palettes/leaders_crest.pal b/graphics/items/icon_palettes/leaders_crest.pal new file mode 100644 index 000000000..1f60ee220 --- /dev/null +++ b/graphics/items/icon_palettes/leaders_crest.pal @@ -0,0 +1,19 @@ +JASC-PAL +0100 +16 +0 0 0 +0 0 0 +188 143 70 +131 85 43 +186 181 176 +80 74 71 +239 185 78 +138 128 128 +220 217 215 +250 225 159 +246 205 93 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 diff --git a/graphics/items/icon_palettes/loaded_dice.pal b/graphics/items/icon_palettes/loaded_dice.pal new file mode 100644 index 000000000..16624b9e9 --- /dev/null +++ b/graphics/items/icon_palettes/loaded_dice.pal @@ -0,0 +1,19 @@ +JASC-PAL +0100 +16 +0 0 0 +48 52 78 +127 216 17 +0 0 0 +192 240 140 +128 217 18 +90 137 46 +129 218 19 +88 135 48 +107 182 17 +128 217 18 +93 144 44 +89 136 47 +255 255 255 +127 216 19 +48 38 64 diff --git a/graphics/items/icon_palettes/lustrous_globe.pal b/graphics/items/icon_palettes/lustrous_globe.pal new file mode 100644 index 000000000..b62730a16 --- /dev/null +++ b/graphics/items/icon_palettes/lustrous_globe.pal @@ -0,0 +1,19 @@ +JASC-PAL +0100 +16 +0 0 0 +255 255 255 +248 216 232 +198 159 217 +210 171 229 +224 192 240 +210 192 240 +160 192 232 +144 200 232 +160 216 240 +128 216 224 +208 240 240 +248 240 240 +248 248 248 +80 80 80 +48 48 48 diff --git a/graphics/items/icon_palettes/malicious_armor.pal b/graphics/items/icon_palettes/malicious_armor.pal new file mode 100644 index 000000000..1ef3efe89 --- /dev/null +++ b/graphics/items/icon_palettes/malicious_armor.pal @@ -0,0 +1,19 @@ +JASC-PAL +0100 +16 +0 0 0 +255 255 255 +66 66 99 +90 90 132 +115 115 156 +90 115 230 +132 140 181 +125 147 246 +48 48 48 +255 255 255 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 diff --git a/graphics/items/icon_palettes/mirror_herb.pal b/graphics/items/icon_palettes/mirror_herb.pal new file mode 100644 index 000000000..b4ad3f972 --- /dev/null +++ b/graphics/items/icon_palettes/mirror_herb.pal @@ -0,0 +1,19 @@ +JASC-PAL +0100 +16 +0 0 0 +0 0 0 +39 194 168 +124 228 211 +19 101 84 +187 228 77 +238 250 169 +234 244 124 +24 152 108 +126 106 28 +173 171 15 +43 138 128 +127 175 55 +0 0 0 +0 0 0 +0 0 0 diff --git a/graphics/items/icon_palettes/punching_glove.pal b/graphics/items/icon_palettes/punching_glove.pal new file mode 100644 index 000000000..f21cb39ec --- /dev/null +++ b/graphics/items/icon_palettes/punching_glove.pal @@ -0,0 +1,19 @@ +JASC-PAL +0100 +16 +0 0 0 +255 255 255 +255 212 0 +200 56 32 +248 64 48 +232 232 232 +48 48 48 +255 255 255 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 +0 0 0 diff --git a/graphics/items/icon_palettes/scroll_of_darkness.pal b/graphics/items/icon_palettes/scroll_of_darkness.pal new file mode 100644 index 000000000..1b66d85e0 --- /dev/null +++ b/graphics/items/icon_palettes/scroll_of_darkness.pal @@ -0,0 +1,19 @@ +JASC-PAL +0100 +16 +0 0 0 +0 0 0 +130 116 116 +86 86 86 +54 41 49 +104 78 86 +56 56 56 +141 105 41 +223 191 65 +242 241 242 +184 172 179 +186 140 57 +0 0 0 +0 0 0 +0 0 0 +0 0 0 diff --git a/graphics/items/icon_palettes/scroll_of_waters.pal b/graphics/items/icon_palettes/scroll_of_waters.pal new file mode 100644 index 000000000..41a94976f --- /dev/null +++ b/graphics/items/icon_palettes/scroll_of_waters.pal @@ -0,0 +1,19 @@ +JASC-PAL +0100 +16 +0 0 0 +0 0 0 +118 109 109 +143 130 127 +101 77 83 +73 68 68 +122 108 104 +141 105 41 +223 191 65 +242 241 242 +184 172 179 +186 140 57 +102 78 84 +0 0 0 +0 0 0 +0 0 0 diff --git a/graphics/items/icon_palettes/tera_orb.pal b/graphics/items/icon_palettes/tera_orb.pal new file mode 100644 index 000000000..78b3689f9 --- /dev/null +++ b/graphics/items/icon_palettes/tera_orb.pal @@ -0,0 +1,19 @@ +JASC-PAL +0100 +16 +0 0 0 +0 0 0 +81 41 75 +98 61 92 +66 72 65 +51 4 43 +201 185 199 +110 86 106 +84 73 92 +32 41 34 +255 255 255 +71 50 92 +64 71 94 +139 165 201 +183 191 209 +0 0 0 diff --git a/graphics/items/icon_palettes/tiny_bamboo_shoot.pal b/graphics/items/icon_palettes/tiny_bamboo_shoot.pal new file mode 100644 index 000000000..1172befa6 --- /dev/null +++ b/graphics/items/icon_palettes/tiny_bamboo_shoot.pal @@ -0,0 +1,19 @@ +JASC-PAL +0100 +16 +0 0 0 +0 0 0 +111 167 43 +179 192 68 +107 144 66 +84 116 48 +68 93 37 +124 137 52 +168 87 80 +168 116 111 +241 241 193 +168 87 80 +183 170 147 +121 52 57 +0 0 0 +0 0 0 diff --git a/graphics/items/icons/ability_shield.png b/graphics/items/icons/ability_shield.png new file mode 100644 index 000000000..de7354d71 Binary files /dev/null and b/graphics/items/icons/ability_shield.png differ diff --git a/graphics/items/icons/adamant_crystal.png b/graphics/items/icons/adamant_crystal.png new file mode 100644 index 000000000..37175b565 Binary files /dev/null and b/graphics/items/icons/adamant_crystal.png differ diff --git a/graphics/items/icons/auspicious_armor.png b/graphics/items/icons/auspicious_armor.png new file mode 100644 index 000000000..db7bb231b Binary files /dev/null and b/graphics/items/icons/auspicious_armor.png differ diff --git a/graphics/items/icons/big_bamboo_shoot.png b/graphics/items/icons/big_bamboo_shoot.png new file mode 100644 index 000000000..49c342866 Binary files /dev/null and b/graphics/items/icons/big_bamboo_shoot.png differ diff --git a/graphics/items/icons/booster_energy.png b/graphics/items/icons/booster_energy.png new file mode 100644 index 000000000..df583a250 Binary files /dev/null and b/graphics/items/icons/booster_energy.png differ diff --git a/graphics/items/icons/covert_cloak.png b/graphics/items/icons/covert_cloak.png new file mode 100644 index 000000000..a6cb97487 Binary files /dev/null and b/graphics/items/icons/covert_cloak.png differ diff --git a/graphics/items/icons/gimmighoul_coin.png b/graphics/items/icons/gimmighoul_coin.png new file mode 100644 index 000000000..b161cf12a Binary files /dev/null and b/graphics/items/icons/gimmighoul_coin.png differ diff --git a/graphics/items/icons/griseous_core.png b/graphics/items/icons/griseous_core.png new file mode 100644 index 000000000..08748fe7b Binary files /dev/null and b/graphics/items/icons/griseous_core.png differ diff --git a/graphics/items/icons/leaders_crest.png b/graphics/items/icons/leaders_crest.png new file mode 100644 index 000000000..b105fcd29 Binary files /dev/null and b/graphics/items/icons/leaders_crest.png differ diff --git a/graphics/items/icons/loaded_dice.png b/graphics/items/icons/loaded_dice.png new file mode 100644 index 000000000..129545092 Binary files /dev/null and b/graphics/items/icons/loaded_dice.png differ diff --git a/graphics/items/icons/lustrous_globe.png b/graphics/items/icons/lustrous_globe.png new file mode 100644 index 000000000..c0e8e885d Binary files /dev/null and b/graphics/items/icons/lustrous_globe.png differ diff --git a/graphics/items/icons/malicious_armor.png b/graphics/items/icons/malicious_armor.png new file mode 100644 index 000000000..931850013 Binary files /dev/null and b/graphics/items/icons/malicious_armor.png differ diff --git a/graphics/items/icons/mirror_herb.png b/graphics/items/icons/mirror_herb.png new file mode 100644 index 000000000..e792bad82 Binary files /dev/null and b/graphics/items/icons/mirror_herb.png differ diff --git a/graphics/items/icons/punching_glove.png b/graphics/items/icons/punching_glove.png new file mode 100644 index 000000000..f0a1438a4 Binary files /dev/null and b/graphics/items/icons/punching_glove.png differ diff --git a/graphics/items/icons/scroll_of_darkness.png b/graphics/items/icons/scroll_of_darkness.png new file mode 100644 index 000000000..52591273b Binary files /dev/null and b/graphics/items/icons/scroll_of_darkness.png differ diff --git a/graphics/items/icons/scroll_of_waters.png b/graphics/items/icons/scroll_of_waters.png new file mode 100644 index 000000000..c3a1af9b6 Binary files /dev/null and b/graphics/items/icons/scroll_of_waters.png differ diff --git a/graphics/items/icons/tera_orb.png b/graphics/items/icons/tera_orb.png new file mode 100644 index 000000000..03ecb8e82 Binary files /dev/null and b/graphics/items/icons/tera_orb.png differ diff --git a/graphics/items/icons/tiny_bamboo_shoot.png b/graphics/items/icons/tiny_bamboo_shoot.png new file mode 100644 index 000000000..6b24c7e38 Binary files /dev/null and b/graphics/items/icons/tiny_bamboo_shoot.png differ diff --git a/include/battle.h b/include/battle.h index a43fc65cd..491273b80 100644 --- a/include/battle.h +++ b/include/battle.h @@ -534,7 +534,6 @@ struct BattleStruct u8 wildVictorySong; u8 dynamicMoveType; u8 wrappedBy[MAX_BATTLERS_COUNT]; - u16 assistPossibleMoves[PARTY_SIZE * MAX_MON_MOVES]; // Each of mons can know max 4 moves. u8 focusPunchBattlerId; u8 battlerPreventingSwitchout; u8 moneyMultiplier:6; diff --git a/include/battle_main.h b/include/battle_main.h index df59dcae3..8d1aad454 100644 --- a/include/battle_main.h +++ b/include/battle_main.h @@ -1,6 +1,9 @@ #ifndef GUARD_BATTLE_MAIN_H #define GUARD_BATTLE_MAIN_H +#include "pokemon.h" +#include "data.h" + struct TrainerMoney { u8 classId; @@ -66,6 +69,7 @@ bool8 TryRunFromBattle(u8 battlerId); void SpecialStatusesClear(void); void SetTypeBeforeUsingMove(u16 move, u8 battlerAtk); bool32 IsWildMonSmart(void); +u8 CreateNPCTrainerPartyFromTrainer(struct Pokemon *party, const struct Trainer *trainer, bool32 firstTrainer, u32 battleTypeFlags); extern struct MultiPartnerMenuPokemon gMultiPartnerParty[MULTI_PARTY_SIZE]; diff --git a/include/constants/hold_effects.h b/include/constants/hold_effects.h index cd4256632..5fc268cb0 100644 --- a/include/constants/hold_effects.h +++ b/include/constants/hold_effects.h @@ -159,6 +159,7 @@ #define HOLD_EFFECT_PUNCHING_GLOVE 178 #define HOLD_EFFECT_COVERT_CLOAK 179 #define HOLD_EFFECT_LOADED_DICE 180 +#define HOLD_EFFECT_BOOSTER_ENERGY 181 // Not implemented. #define HOLD_EFFECT_CHOICE(holdEffect)((holdEffect == HOLD_EFFECT_CHOICE_BAND || holdEffect == HOLD_EFFECT_CHOICE_SCARF || holdEffect == HOLD_EFFECT_CHOICE_SPECS)) diff --git a/include/constants/items.h b/include/constants/items.h index a5a3c95e2..2d186601b 100644 --- a/include/constants/items.h +++ b/include/constants/items.h @@ -928,13 +928,48 @@ #define ITEM_RUBY 756 #define ITEM_SAPPHIRE 757 +// GEN IX ITEMS #define ITEM_ABILITY_SHIELD 758 #define ITEM_CLEAR_AMULET 759 #define ITEM_PUNCHING_GLOVE 760 #define ITEM_COVERT_CLOAK 761 #define ITEM_LOADED_DICE 762 +#define ITEM_AUSPICIOUS_ARMOR 763 +#define ITEM_BOOSTER_ENERGY 764 +#define ITEM_BIG_BAMBOO_SHOOT 765 +#define ITEM_GIMMIGHOUL_COIN 766 +#define ITEM_LEADERS_CREST 767 +#define ITEM_MALICIOUS_ARMOR 768 +#define ITEM_MIRROR_HERB 769 +#define ITEM_SCROLL_OF_DARKNESS 770 +#define ITEM_SCROLL_OF_WATERS 771 +#define ITEM_TERA_ORB 772 +#define ITEM_TINY_BAMBOO_SHOOT 773 -#define ITEMS_COUNT 763 +#define ITEM_BUG_TERA_SHARD 774 +#define ITEM_DARK_TERA_SHARD 775 +#define ITEM_DRAGON_TERA_SHARD 776 +#define ITEM_ELECTRIC_TERA_SHARD 777 +#define ITEM_FAIRY_TERA_SHARD 778 +#define ITEM_FIGHTING_TERA_SHARD 779 +#define ITEM_FIRE_TERA_SHARD 780 +#define ITEM_FLYING_TERA_SHARD 781 +#define ITEM_GHOST_TERA_SHARD 782 +#define ITEM_GRASS_TERA_SHARD 783 +#define ITEM_GROUND_TERA_SHARD 784 +#define ITEM_ICE_TERA_SHARD 785 +#define ITEM_NORMAL_TERA_SHARD 786 +#define ITEM_POISON_TERA_SHARD 787 +#define ITEM_PSYCHIC_TERA_SHARD 788 +#define ITEM_ROCK_TERA_SHARD 789 +#define ITEM_STEEL_TERA_SHARD 790 +#define ITEM_WATER_TERA_SHARD 791 + +#define ITEM_ADAMANT_CRYSTAL 792 +#define ITEM_GRISEOUS_CORE 793 +#define ITEM_LUSTROUS_GLOBE 794 + +#define ITEMS_COUNT 795 #define ITEM_FIELD_ARROW ITEMS_COUNT // A special item id associated with "Cancel"/"Exit" etc. in a list of items or decorations diff --git a/include/constants/trainers.h b/include/constants/trainers.h index 39ca2a2b8..25660ac07 100644 --- a/include/constants/trainers.h +++ b/include/constants/trainers.h @@ -374,7 +374,12 @@ // All trainer parties specify the IV, level, and species for each Pokémon in the // party. Some trainer parties also specify held items and custom moves for each // Pokémon. -#define F_TRAINER_PARTY_CUSTOM_MOVESET (1 << 0) -#define F_TRAINER_PARTY_HELD_ITEM (1 << 1) +#define F_TRAINER_PARTY_CUSTOM_MOVESET (1 << 0) +#define F_TRAINER_PARTY_HELD_ITEM (1 << 1) +#define F_TRAINER_PARTY_EVERYTHING_CUSTOMIZED (1 << 3) + +// Trainer party defines +#define TRAINER_MON_MALE 1 +#define TRAINER_MON_FEMALE 2 #endif // GUARD_TRAINERS_H diff --git a/include/data.h b/include/data.h index 79f6a3715..6e41ac703 100644 --- a/include/data.h +++ b/include/data.h @@ -31,6 +31,26 @@ struct MonCoords #define MON_COORDS_SIZE(width, height)(DIV_ROUND_UP(width, 8) << 4 | DIV_ROUND_UP(height, 8)) #define GET_MON_COORDS_WIDTH(size)((size >> 4) * 8) #define GET_MON_COORDS_HEIGHT(size)((size & 0xF) * 8) +#define TRAINER_PARTY_IVS(hp, atk, def, speed, spatk, spdef) (hp | (atk << 5) | (def << 10) | (speed << 15) | (spatk << 20) | (spdef << 25)) +#define TRAINER_PARTY_EVS(hp, atk, def, speed, spatk, spdef) ((const u8[6]){hp,atk,def,spatk,spdef,speed}) +#define TRAINER_PARTY_NATURE(nature) (nature+1) + +struct TrainerMonCustomized +{ + const u8 *nickname; + const u8 *ev; + u32 iv; + u16 moves[4]; + u16 species; + u16 heldItem; + u16 ability; + u8 lvl; + u8 ball; + u8 friendship; + u8 nature : 5; + bool8 gender : 2; + bool8 isShiny : 1; +}; struct TrainerMonNoItemDefaultMoves { @@ -68,6 +88,7 @@ struct TrainerMonItemCustomMoves #define NO_ITEM_CUSTOM_MOVES(party) { .NoItemCustomMoves = party }, .partySize = ARRAY_COUNT(party), .partyFlags = F_TRAINER_PARTY_CUSTOM_MOVESET #define ITEM_DEFAULT_MOVES(party) { .ItemDefaultMoves = party }, .partySize = ARRAY_COUNT(party), .partyFlags = F_TRAINER_PARTY_HELD_ITEM #define ITEM_CUSTOM_MOVES(party) { .ItemCustomMoves = party }, .partySize = ARRAY_COUNT(party), .partyFlags = F_TRAINER_PARTY_CUSTOM_MOVESET | F_TRAINER_PARTY_HELD_ITEM +#define EVERYTHING_CUSTOMIZED(party) { .EverythingCustomized = party}, .partySize = ARRAY_COUNT(party), .partyFlags = F_TRAINER_PARTY_EVERYTHING_CUSTOMIZED union TrainerMonPtr { @@ -75,20 +96,21 @@ union TrainerMonPtr const struct TrainerMonNoItemCustomMoves *NoItemCustomMoves; const struct TrainerMonItemDefaultMoves *ItemDefaultMoves; const struct TrainerMonItemCustomMoves *ItemCustomMoves; + const struct TrainerMonCustomized *EverythingCustomized; }; struct Trainer { - /*0x00*/ u8 partyFlags; - /*0x01*/ u8 trainerClass; - /*0x02*/ u8 encounterMusic_gender; // last bit is gender - /*0x03*/ u8 trainerPic; - /*0x04*/ u8 trainerName[TRAINER_NAME_LENGTH + 1]; - /*0x10*/ u16 items[MAX_TRAINER_ITEMS]; - /*0x18*/ bool8 doubleBattle; - /*0x1C*/ u32 aiFlags; - /*0x20*/ u8 partySize; - /*0x24*/ union TrainerMonPtr party; + /*0x00*/ u32 aiFlags; + /*0x04*/ union TrainerMonPtr party; + /*0x08*/ u16 items[MAX_TRAINER_ITEMS]; + /*0x10*/ u8 trainerClass; + /*0x11*/ u8 encounterMusic_gender; // last bit is gender + /*0x12*/ u8 trainerPic; + /*0x13*/ u8 trainerName[TRAINER_NAME_LENGTH + 1]; + /*0x1E*/ bool8 doubleBattle:1; + u8 partyFlags:7; + /*0x1F*/ u8 partySize; }; #define TRAINER_ENCOUNTER_MUSIC(trainer)((gTrainers[trainer].encounterMusic_gender & 0x7F)) diff --git a/include/global.h b/include/global.h index 4b3e90bfa..f7d166bb5 100644 --- a/include/global.h +++ b/include/global.h @@ -147,6 +147,9 @@ #define CAT(a, b) CAT_(a, b) #define CAT_(a, b) a ## b +// Converts a string to a compound literal, essentially making it a pointer to const u8 +#define COMPOUND_STRING(str) (const u8[]) _(str) + // This produces an error at compile-time if expr is zero. // It looks like file.c:line: size of array `id' is negative #define STATIC_ASSERT(expr, id) typedef char id[(expr) ? 1 : -1]; diff --git a/include/graphics.h b/include/graphics.h index 8b1e0c3a2..7cf903034 100644 --- a/include/graphics.h +++ b/include/graphics.h @@ -8834,6 +8834,45 @@ extern const u32 gItemIcon_Gem[]; extern const u32 gItemIconPalette_Ruby[]; extern const u32 gItemIconPalette_Sapphire[]; +// GEN IX ITEMS +extern const u32 gItemIcon_AbilityShield[]; +extern const u32 gItemIconPalette_AbilityShield[]; +extern const u32 gItemIcon_AuspiciousArmor[]; +extern const u32 gItemIconPalette_AuspiciousArmor[]; +extern const u32 gItemIcon_BigBambooShoot[]; +extern const u32 gItemIconPalette_BigBambooShoot[]; +extern const u32 gItemIcon_BoosterEnergy[]; +extern const u32 gItemIconPalette_BoosterEnergy[]; +extern const u32 gItemIcon_CovertCloak[]; +extern const u32 gItemIconPalette_CovertCloak[]; +extern const u32 gItemIcon_GimmighoulCoin[]; +extern const u32 gItemIconPalette_GimmighoulCoin[]; +extern const u32 gItemIcon_LeadersCrest[]; +extern const u32 gItemIconPalette_LeadersCrest[]; +extern const u32 gItemIcon_LoadedDice[]; +extern const u32 gItemIconPalette_LoadedDice[]; +extern const u32 gItemIcon_MaliciousArmor[]; +extern const u32 gItemIconPalette_MaliciousArmor[]; +extern const u32 gItemIcon_MirrorHerb[]; +extern const u32 gItemIconPalette_MirrorHerb[]; +extern const u32 gItemIcon_PunchingGlove[]; +extern const u32 gItemIconPalette_PunchingGlove[]; +extern const u32 gItemIcon_ScrollOfDarkness[]; +extern const u32 gItemIconPalette_ScrollOfDarkness[]; +extern const u32 gItemIcon_ScrollOfWaters[]; +extern const u32 gItemIconPalette_ScrollOfWaters[]; +extern const u32 gItemIcon_TeraOrb[]; +extern const u32 gItemIconPalette_TeraOrb[]; +extern const u32 gItemIcon_TinyBambooShoot[]; +extern const u32 gItemIconPalette_TinyBambooShoot[]; + +extern const u32 gItemIcon_AdamantCrystal[]; +extern const u32 gItemIconPalette_AdamantCrystal[]; +extern const u32 gItemIcon_GriseousCore[]; +extern const u32 gItemIconPalette_GriseousCore[]; +extern const u32 gItemIcon_LustrousGlobe[]; +extern const u32 gItemIconPalette_LustrousGlobe[]; + extern const u32 gItemIcon_ReturnToFieldArrow[]; extern const u32 gItemIconPalette_ReturnToFieldArrow[]; diff --git a/include/random.h b/include/random.h index 6bf61de6c..60c718348 100644 --- a/include/random.h +++ b/include/random.h @@ -20,4 +20,67 @@ u16 Random2(void); void SeedRng(u16 seed); void SeedRng2(u16 seed); +/* Structured random number generator. + * Instead of the caller converting bits from Random() to a meaningful + * value, the caller provides metadata that is used to return the + * meaningful value directly. This allows code to interpret the random + * call, for example, battle tests know what the domain of a random call + * is, and can exhaustively test it. + * + * RandomTag identifies the purpose of the value. + * + * RandomUniform(tag, lo, hi) returns a number from lo to hi inclusive. + * + * RandomPercentage(tag, t) returns FALSE with probability (1-t)/100, + * and TRUE with probability t/100. + * + * RandomWeighted(tag, w0, w1, ... wN) returns a number from 0 to N + * inclusive. The return value is proportional to the weights, e.g. + * RandomWeighted(..., 1, 1) returns 50% 0s and 50% 1s. + * RandomWeighted(..., 2, 1) returns 2/3 0s and 1/3 1s. */ + +enum RandomTag +{ + RNG_NONE, + RNG_ACCURACY, + RNG_CONFUSION, + RNG_CRITICAL_HIT, + RNG_CUTE_CHARM, + RNG_DAMAGE_MODIFIER, + RNG_FLAME_BODY, + RNG_FORCE_RANDOM_SWITCH, + RNG_FROZEN, + RNG_HOLD_EFFECT_FLINCH, + RNG_INFATUATION, + RNG_PARALYSIS, + RNG_POISON_POINT, + RNG_RAMPAGE_TURNS, + RNG_SECONDARY_EFFECT, + RNG_SLEEP_TURNS, + RNG_SPEED_TIE, + RNG_STATIC, + RNG_STENCH, +}; + +#define RandomWeighted(tag, ...) \ + ({ \ + const u8 weights[] = { __VA_ARGS__ }; \ + u32 sum, i; \ + for (i = 0, sum = 0; i < ARRAY_COUNT(weights); i++) \ + sum += weights[i]; \ + RandomWeightedArray(tag, sum, ARRAY_COUNT(weights), weights); \ + }) + +#define RandomPercentage(tag, t) \ + ({ \ + const u8 weights[] = { 100 - t, t }; \ + RandomWeightedArray(tag, 100, ARRAY_COUNT(weights), weights); \ + }) + +u32 RandomUniform(enum RandomTag, u32 lo, u32 hi); +u32 RandomWeightedArray(enum RandomTag, u32 sum, u32 n, const u8 *weights); + +u32 RandomUniformDefault(enum RandomTag, u32 lo, u32 hi); +u32 RandomWeightedArrayDefault(enum RandomTag, u32 sum, u32 n, const u8 *weights); + #endif // GUARD_RANDOM_H diff --git a/src/battle_main.c b/src/battle_main.c index 9ea3b6644..efbf1b819 100644 --- a/src/battle_main.c +++ b/src/battle_main.c @@ -118,6 +118,10 @@ static void HandleEndTurn_FinishBattle(void); static void SpriteCB_UnusedBattleInit(struct Sprite *sprite); static void SpriteCB_UnusedBattleInit_Main(struct Sprite *sprite); static void TrySpecialEvolution(void); +static u32 Crc32B (const u8 *data, u32 size); +static u32 GeneratePartyHash(const struct Trainer *trainer, u32 i); +static void ModifyPersonalityForNature(u32 *personality, u32 newNature); +static u32 GeneratePersonalityForGender(u32 gender, u32 species); EWRAM_DATA u16 gBattle_BG0_X = 0; EWRAM_DATA u16 gBattle_BG0_Y = 0; @@ -1869,72 +1873,129 @@ static void SpriteCB_UnusedBattleInit_Main(struct Sprite *sprite) } } -static u8 CreateNPCTrainerParty(struct Pokemon *party, u16 trainerNum, bool8 firstTrainer) +static u32 Crc32B (const u8 *data, u32 size) +{ + s32 i, j; + u32 byte, crc, mask; + + i = 0; + crc = 0xFFFFFFFF; + for (i = 0; i < size; ++i) + { + byte = data[i]; + crc = crc ^ byte; + for (j = 7; j >= 0; --j) + { + mask = -(crc & 1); + crc = (crc >> 1) ^ (0xEDB88320 & mask); + } + } + return ~crc; +} + +static u32 GeneratePartyHash(const struct Trainer *trainer, u32 i) +{ + const u8 *buffer; + u32 n; + if (trainer->partyFlags == 0) + { + buffer = (const u8 *) &trainer->party.NoItemDefaultMoves[i]; + n = sizeof(*trainer->party.NoItemDefaultMoves); + } + else if (trainer->partyFlags == F_TRAINER_PARTY_CUSTOM_MOVESET) + { + buffer = (const u8 *) &trainer->party.NoItemCustomMoves[i]; + n = sizeof(*trainer->party.NoItemCustomMoves); + } + else if (trainer->partyFlags == F_TRAINER_PARTY_HELD_ITEM) + { + buffer = (const u8 *) &trainer->party.ItemDefaultMoves[i]; + n = sizeof(*trainer->party.ItemDefaultMoves); + } + else if (trainer->partyFlags == (F_TRAINER_PARTY_HELD_ITEM | F_TRAINER_PARTY_CUSTOM_MOVESET)) + { + buffer = (const u8 *) &trainer->party.ItemCustomMoves[i]; + n = sizeof(*trainer->party.ItemCustomMoves); + } + else if (trainer->partyFlags == F_TRAINER_PARTY_EVERYTHING_CUSTOMIZED) + { + buffer = (const u8 *) &trainer->party.EverythingCustomized[i]; + n = sizeof(*trainer->party.EverythingCustomized); + } + return Crc32B(buffer, n); +} + +static void ModifyPersonalityForNature(u32 *personality, u32 newNature) +{ + u32 nature = GetNatureFromPersonality(*personality); + s32 diff = abs(nature - newNature); + s32 sign = (nature > newNature) ? 1 : -1; + if (diff > NUM_NATURES / 2) + { + diff = NUM_NATURES - diff; + sign *= -1; + } + *personality -= (diff * sign); +} + +static u32 GeneratePersonalityForGender(u32 gender, u32 species) +{ + const struct SpeciesInfo *speciesInfo = &gSpeciesInfo[species]; + if (gender == MON_MALE) + return ((255 - speciesInfo->genderRatio) / 2) + speciesInfo->genderRatio; + else + return speciesInfo->genderRatio / 2; +} + +u8 CreateNPCTrainerPartyFromTrainer(struct Pokemon *party, const struct Trainer *trainer, bool32 firstTrainer, u32 battleTypeFlags) { - u32 nameHash = 0; u32 personalityValue; u8 fixedIV; s32 i, j; u8 monsCount; - u16 ball; - - if (trainerNum == TRAINER_SECRET_BASE) - return 0; - - if (gBattleTypeFlags & BATTLE_TYPE_TRAINER && !(gBattleTypeFlags & (BATTLE_TYPE_FRONTIER + s32 ball = -1; + if (battleTypeFlags & BATTLE_TYPE_TRAINER && !(battleTypeFlags & (BATTLE_TYPE_FRONTIER | BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_TRAINER_HILL))) { if (firstTrainer == TRUE) ZeroEnemyPartyMons(); - if (gBattleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS) + if (battleTypeFlags & BATTLE_TYPE_TWO_OPPONENTS) { - if (gTrainers[trainerNum].partySize > PARTY_SIZE / 2) + if (trainer->partySize > PARTY_SIZE / 2) monsCount = PARTY_SIZE / 2; else - monsCount = gTrainers[trainerNum].partySize; + monsCount = trainer->partySize; } else { - monsCount = gTrainers[trainerNum].partySize; + monsCount = trainer->partySize; } for (i = 0; i < monsCount; i++) { - - if (gTrainers[trainerNum].doubleBattle == TRUE) + u32 personalityHash = GeneratePartyHash(trainer, i); + if (trainer->doubleBattle == TRUE) personalityValue = 0x80; - else if (gTrainers[trainerNum].encounterMusic_gender & F_TRAINER_FEMALE) + else if (trainer->encounterMusic_gender & F_TRAINER_FEMALE) personalityValue = 0x78; // Use personality more likely to result in a female Pokémon else personalityValue = 0x88; // Use personality more likely to result in a male Pokémon - for (j = 0; gTrainers[trainerNum].trainerName[j] != EOS; j++) - nameHash += gTrainers[trainerNum].trainerName[j]; - - switch (gTrainers[trainerNum].partyFlags) + personalityValue += personalityHash << 8; + switch (trainer->partyFlags) { case 0: { - const struct TrainerMonNoItemDefaultMoves *partyData = gTrainers[trainerNum].party.NoItemDefaultMoves; - - for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++) - nameHash += gSpeciesNames[partyData[i].species][j]; - - personalityValue += nameHash << 8; + const struct TrainerMonNoItemDefaultMoves *partyData = trainer->party.NoItemDefaultMoves; fixedIV = partyData[i].iv * MAX_PER_STAT_IVS / 255; CreateMon(&party[i], partyData[i].species, partyData[i].lvl, fixedIV, TRUE, personalityValue, OT_ID_RANDOM_NO_SHINY, 0); break; } case F_TRAINER_PARTY_CUSTOM_MOVESET: { - const struct TrainerMonNoItemCustomMoves *partyData = gTrainers[trainerNum].party.NoItemCustomMoves; - - for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++) - nameHash += gSpeciesNames[partyData[i].species][j]; - - personalityValue += nameHash << 8; + const struct TrainerMonNoItemCustomMoves *partyData = trainer->party.NoItemCustomMoves; fixedIV = partyData[i].iv * MAX_PER_STAT_IVS / 255; CreateMon(&party[i], partyData[i].species, partyData[i].lvl, fixedIV, TRUE, personalityValue, OT_ID_RANDOM_NO_SHINY, 0); @@ -1947,12 +2008,7 @@ static u8 CreateNPCTrainerParty(struct Pokemon *party, u16 trainerNum, bool8 fir } case F_TRAINER_PARTY_HELD_ITEM: { - const struct TrainerMonItemDefaultMoves *partyData = gTrainers[trainerNum].party.ItemDefaultMoves; - - for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++) - nameHash += gSpeciesNames[partyData[i].species][j]; - - personalityValue += nameHash << 8; + const struct TrainerMonItemDefaultMoves *partyData = trainer->party.ItemDefaultMoves; fixedIV = partyData[i].iv * MAX_PER_STAT_IVS / 255; CreateMon(&party[i], partyData[i].species, partyData[i].lvl, fixedIV, TRUE, personalityValue, OT_ID_RANDOM_NO_SHINY, 0); @@ -1961,12 +2017,7 @@ static u8 CreateNPCTrainerParty(struct Pokemon *party, u16 trainerNum, bool8 fir } case F_TRAINER_PARTY_CUSTOM_MOVESET | F_TRAINER_PARTY_HELD_ITEM: { - const struct TrainerMonItemCustomMoves *partyData = gTrainers[trainerNum].party.ItemCustomMoves; - - for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++) - nameHash += gSpeciesNames[partyData[i].species][j]; - - personalityValue += nameHash << 8; + const struct TrainerMonItemCustomMoves *partyData = trainer->party.ItemCustomMoves; fixedIV = partyData[i].iv * MAX_PER_STAT_IVS / 255; CreateMon(&party[i], partyData[i].species, partyData[i].lvl, fixedIV, TRUE, personalityValue, OT_ID_RANDOM_NO_SHINY, 0); @@ -1979,18 +2030,88 @@ static u8 CreateNPCTrainerParty(struct Pokemon *party, u16 trainerNum, bool8 fir } break; } + case F_TRAINER_PARTY_EVERYTHING_CUSTOMIZED: + { + const struct TrainerMonCustomized *partyData = trainer->party.EverythingCustomized; + u32 otIdType = OT_ID_RANDOM_NO_SHINY; + u32 fixedOtId = 0; + if (partyData[i].gender == TRAINER_MON_MALE) + personalityValue = (personalityValue & 0xFFFFFF00) | GeneratePersonalityForGender(MON_MALE, partyData[i].species); + else if (partyData[i].gender == TRAINER_MON_FEMALE) + personalityValue = (personalityValue & 0xFFFFFF00) | GeneratePersonalityForGender(MON_FEMALE, partyData[i].species); + if (partyData[i].nature != 0) + ModifyPersonalityForNature(&personalityValue, partyData[i].nature - 1); + if (partyData[i].isShiny) + { + otIdType = OT_ID_PRESET; + fixedOtId = HIHALF(personalityValue) ^ LOHALF(personalityValue); + } + CreateMon(&party[i], partyData[i].species, partyData[i].lvl, 0, TRUE, personalityValue, otIdType, fixedOtId); + SetMonData(&party[i], MON_DATA_HELD_ITEM, &partyData[i].heldItem); + + // TODO: Figure out a default strategy when moves are not set, to generate a good moveset + for (j = 0; j < MAX_MON_MOVES; ++j) + { + SetMonData(&party[i], MON_DATA_MOVE1 + j, &partyData[i].moves[j]); + SetMonData(&party[i], MON_DATA_PP1 + j, &gBattleMoves[partyData[i].moves[j]].pp); + } + SetMonData(&party[i], MON_DATA_IVS, &(partyData[i].iv)); + if (partyData[i].ev != NULL) + { + SetMonData(&party[i], MON_DATA_HP_EV, &(partyData[i].ev[0])); + SetMonData(&party[i], MON_DATA_ATK_EV, &(partyData[i].ev[1])); + SetMonData(&party[i], MON_DATA_DEF_EV, &(partyData[i].ev[2])); + SetMonData(&party[i], MON_DATA_SPATK_EV, &(partyData[i].ev[3])); + SetMonData(&party[i], MON_DATA_SPDEF_EV, &(partyData[i].ev[4])); + SetMonData(&party[i], MON_DATA_SPEED_EV, &(partyData[i].ev[5])); + } + if (partyData[i].ability != ABILITY_NONE) + { + const struct SpeciesInfo *speciesInfo = &gSpeciesInfo[partyData[i].species]; + u32 maxAbilities = ARRAY_COUNT(speciesInfo->abilities); + for (j = 0; j < maxAbilities; ++j) + { + if (speciesInfo->abilities[j] == partyData[i].ability) + break; + } + if (j < maxAbilities) + SetMonData(&party[i], MON_DATA_ABILITY_NUM, &j); + } + SetMonData(&party[i], MON_DATA_FRIENDSHIP, &(partyData[i].friendship)); + if (partyData[i].ball != ITEM_NONE) + { + ball = partyData[i].ball; + SetMonData(&party[i], MON_DATA_POKEBALL, &ball); + } + if (partyData[i].nickname != NULL) + { + SetMonData(&party[i], MON_DATA_NICKNAME, partyData[i].nickname); + } + CalculateMonStats(&party[i]); + } } #if B_TRAINER_CLASS_POKE_BALLS >= GEN_7 - ball = (sTrainerBallTable[gTrainers[trainerNum].trainerClass]) ? sTrainerBallTable[gTrainers[trainerNum].trainerClass] : ITEM_POKE_BALL; - SetMonData(&party[i], MON_DATA_POKEBALL, &ball); + if (ball == -1) + { + ball = (sTrainerBallTable[trainer->trainerClass]) ? sTrainerBallTable[trainer->trainerClass] : ITEM_POKE_BALL; + SetMonData(&party[i], MON_DATA_POKEBALL, &ball); + } #endif } - - gBattleTypeFlags |= gTrainers[trainerNum].doubleBattle; } - return gTrainers[trainerNum].partySize; + return trainer->partySize; +} + +static u8 CreateNPCTrainerParty(struct Pokemon *party, u16 trainerNum, bool8 firstTrainer) +{ + u8 retVal; + if (trainerNum == TRAINER_SECRET_BASE) + return 0; + retVal = CreateNPCTrainerPartyFromTrainer(party, &gTrainers[trainerNum], firstTrainer, gBattleTypeFlags); + + gBattleTypeFlags |= gTrainers[trainerNum].doubleBattle; } void VBlankCB_Battle(void) diff --git a/src/battle_script_commands.c b/src/battle_script_commands.c index 126ca06fd..c50d801b1 100644 --- a/src/battle_script_commands.c +++ b/src/battle_script_commands.c @@ -23,6 +23,7 @@ #include "main.h" #include "palette.h" #include "money.h" +#include "malloc.h" #include "bg.h" #include "string_util.h" #include "pokemon_icon.h" @@ -1917,15 +1918,25 @@ static void Cmd_accuracycheck(void) } else { + u32 accuracy; + GET_MOVE_TYPE(move, type); if (JumpIfMoveAffectedByProtect(move)) return; if (AccuracyCalcHelper(move)) return; - // final calculation - if ((Random() % 100 + 1) > GetTotalAccuracy(gBattlerAttacker, gBattlerTarget, move, GetBattlerAbility(gBattlerAttacker), GetBattlerAbility(gBattlerTarget), - GetBattlerHoldEffect(gBattlerAttacker, TRUE), GetBattlerHoldEffect(gBattlerTarget, TRUE))) + accuracy = GetTotalAccuracy( + gBattlerAttacker, + gBattlerTarget, + move, + GetBattlerAbility(gBattlerAttacker), + GetBattlerAbility(gBattlerTarget), + GetBattlerHoldEffect(gBattlerAttacker, TRUE), + GetBattlerHoldEffect(gBattlerTarget, TRUE) + ); + + if (!RandomPercentage(RNG_ACCURACY, accuracy)) { gMoveResultFlags |= MOVE_RESULT_MISSED; if (GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_BLUNDER_POLICY) @@ -2104,10 +2115,8 @@ static void Cmd_critcalc(void) gIsCriticalHit = FALSE; else if (critChance == -2) gIsCriticalHit = TRUE; - else if (Random() % sCriticalHitChance[critChance] == 0) - gIsCriticalHit = TRUE; else - gIsCriticalHit = FALSE; + gIsCriticalHit = RandomWeighted(RNG_CRITICAL_HIT, sCriticalHitChance[critChance] - 1, 1); // Counter for EVO_CRITICAL_HITS. partySlot = gBattlerPartyIndexes[gBattlerAttacker]; @@ -3153,9 +3162,9 @@ void SetMoveEffect(bool32 primary, u32 certain) if (sStatusFlagsForMoveEffects[gBattleScripting.moveEffect] == STATUS1_SLEEP) #if B_SLEEP_TURNS >= GEN_5 - gBattleMons[gEffectBattler].status1 |= ((Random() % 3) + 2); + gBattleMons[gEffectBattler].status1 |= STATUS1_SLEEP_TURN(1 + RandomUniform(RNG_SLEEP_TURNS, 1, 3)); #else - gBattleMons[gEffectBattler].status1 |= ((Random() % 4) + 3); + gBattleMons[gEffectBattler].status1 |= STATUS1_SLEEP_TURN(1 + RandomUniform(RNG_SLEEP_TURNS, 2, 5)); #endif else gBattleMons[gEffectBattler].status1 |= sStatusFlagsForMoveEffects[gBattleScripting.moveEffect]; @@ -3558,7 +3567,7 @@ void SetMoveEffect(bool32 primary, u32 certain) { gBattleMons[gEffectBattler].status2 |= STATUS2_MULTIPLETURNS; gLockedMoves[gEffectBattler] = gCurrentMove; - gBattleMons[gEffectBattler].status2 |= STATUS2_LOCK_CONFUSE_TURN((Random() & 1) + 2); // thrash for 2-3 turns + gBattleMons[gEffectBattler].status2 |= STATUS2_LOCK_CONFUSE_TURN(RandomUniform(RNG_RAMPAGE_TURNS, 2, 3)); } break; case MOVE_EFFECT_SP_ATK_TWO_DOWN: // Overheat @@ -3779,20 +3788,23 @@ static void Cmd_seteffectwithchance(void) else percentChance = gBattleMoves[gCurrentMove].secondaryEffectChance; - if (gBattleScripting.moveEffect & MOVE_EFFECT_CERTAIN - && !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)) + if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) + && gBattleScripting.moveEffect) { - gBattleScripting.moveEffect &= ~MOVE_EFFECT_CERTAIN; - SetMoveEffect(FALSE, MOVE_EFFECT_CERTAIN); - } - else if (Random() % 100 < percentChance - && gBattleScripting.moveEffect - && !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)) - { - if (percentChance >= 100) + if (gBattleScripting.moveEffect & MOVE_EFFECT_CERTAIN + || percentChance >= 100) + { + gBattleScripting.moveEffect &= ~MOVE_EFFECT_CERTAIN; SetMoveEffect(FALSE, MOVE_EFFECT_CERTAIN); - else + } + else if (RandomPercentage(RNG_SECONDARY_EFFECT, percentChance)) + { SetMoveEffect(FALSE, 0); + } + else + { + gBattlescriptCurrInstr = cmd->nextInstr; + } } else { @@ -6973,9 +6985,9 @@ bool32 ShouldPostponeSwitchInAbilities(u32 battlerId) // Checks for double battle, so abilities like Intimidate wait until all battlers are switched-in before activating. if (IsDoubleBattle()) { - if (aliveOpposing1 && !aliveOpposing2 && !HasNoMonsToSwitch(BATTLE_OPPOSITE(battlerId), PARTY_SIZE, PARTY_SIZE)) + if (aliveOpposing1 && !aliveOpposing2 && !HasNoMonsToSwitch(BATTLE_PARTNER(BATTLE_OPPOSITE(battlerId)), PARTY_SIZE, PARTY_SIZE)) return TRUE; - if (!aliveOpposing1 && aliveOpposing2 && !HasNoMonsToSwitch(BATTLE_PARTNER(BATTLE_OPPOSITE(battlerId)), PARTY_SIZE, PARTY_SIZE)) + if (!aliveOpposing1 && aliveOpposing2 && !HasNoMonsToSwitch(BATTLE_OPPOSITE(battlerId), PARTY_SIZE, PARTY_SIZE)) return TRUE; } @@ -12343,7 +12355,7 @@ static void Cmd_forcerandomswitch(void) *(gBattleStruct->battlerPartyIndexes + gBattlerTarget) = gBattlerPartyIndexes[gBattlerTarget]; gBattlescriptCurrInstr = BattleScript_RoarSuccessSwitch; gBattleStruct->forcedSwitch |= gBitTable[gBattlerTarget]; - *(gBattleStruct->monToSwitchIntoId + gBattlerTarget) = validMons[Random() % validMonsCount]; + *(gBattleStruct->monToSwitchIntoId + gBattlerTarget) = validMons[RandomUniform(RNG_FORCE_RANDOM_SWITCH, 0, validMonsCount - 1)]; if (!IsMultiBattle()) SwitchPartyOrder(gBattlerTarget); @@ -12550,7 +12562,7 @@ static void Cmd_tryKO(void) if (gCurrentMove == MOVE_SHEER_COLD && !IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_ICE)) odds -= 10; #endif - if (Random() % 100 + 1 < odds && gBattleMons[gBattlerAttacker].level >= gBattleMons[gBattlerTarget].level) + if (RandomPercentage(RNG_ACCURACY, odds) && gBattleMons[gBattlerAttacker].level >= gBattleMons[gBattlerTarget].level) lands = TRUE; } @@ -14766,38 +14778,40 @@ static void Cmd_assistattackselect(void) s32 chooseableMovesNo = 0; struct Pokemon *party; s32 monId, moveId; - u16 *validMoves = gBattleStruct->assistPossibleMoves; + u16 *validMoves = Alloc(sizeof(u16) * PARTY_SIZE * MAX_MON_MOVES); - if (GET_BATTLER_SIDE(gBattlerAttacker) != B_SIDE_PLAYER) - party = gEnemyParty; - else - party = gPlayerParty; - - for (monId = 0; monId < PARTY_SIZE; monId++) + if (validMoves != NULL) { - if (monId == gBattlerPartyIndexes[gBattlerAttacker]) - continue; - if (GetMonData(&party[monId], MON_DATA_SPECIES_OR_EGG) == SPECIES_NONE) - continue; - if (GetMonData(&party[monId], MON_DATA_SPECIES_OR_EGG) == SPECIES_EGG) - continue; + if (GET_BATTLER_SIDE(gBattlerAttacker) != B_SIDE_PLAYER) + party = gEnemyParty; + else + party = gPlayerParty; - for (moveId = 0; moveId < MAX_MON_MOVES; moveId++) + for (monId = 0; monId < PARTY_SIZE; monId++) { - s32 i = 0; - u16 move = GetMonData(&party[monId], MON_DATA_MOVE1 + moveId); - - if (sForbiddenMoves[move] & FORBIDDEN_ASSIST) + if (monId == gBattlerPartyIndexes[gBattlerAttacker]) + continue; + if (GetMonData(&party[monId], MON_DATA_SPECIES_OR_EGG) == SPECIES_NONE) + continue; + if (GetMonData(&party[monId], MON_DATA_SPECIES_OR_EGG) == SPECIES_EGG) continue; - validMoves[chooseableMovesNo] = move; - chooseableMovesNo++; + for (moveId = 0; moveId < MAX_MON_MOVES; moveId++) + { + u16 move = GetMonData(&party[monId], MON_DATA_MOVE1 + moveId); + + if (sForbiddenMoves[move] & FORBIDDEN_ASSIST) + continue; + + validMoves[chooseableMovesNo++] = move; + } } } + if (chooseableMovesNo) { gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED; - gCalledMove = validMoves[((Random() & 0xFF) * chooseableMovesNo) >> 8]; + gCalledMove = validMoves[Random() % chooseableMovesNo]; gBattlerTarget = GetMoveTarget(gCalledMove, NO_TARGET_OVERRIDE); gBattlescriptCurrInstr = cmd->nextInstr; } @@ -14805,6 +14819,8 @@ static void Cmd_assistattackselect(void) { gBattlescriptCurrInstr = cmd->failInstr; } + + TRY_FREE_AND_SET_NULL(validMoves); } static void Cmd_trysetmagiccoat(void) diff --git a/src/battle_util.c b/src/battle_util.c index e8adc8a11..5683616e8 100644 --- a/src/battle_util.c +++ b/src/battle_util.c @@ -3486,7 +3486,7 @@ u8 AtkCanceller_UnableToUseMove(void) case CANCELLER_FROZEN: // check being frozen if (gBattleMons[gBattlerAttacker].status1 & STATUS1_FREEZE && !(gBattleMoves[gCurrentMove].flags & FLAG_THAW_USER)) { - if (Random() % 5) + if (!RandomPercentage(RNG_FROZEN, 20)) { gBattlescriptCurrInstr = BattleScript_MoveUsedIsFrozen; gHitMarker |= HITMARKER_NO_ATTACKSTRING; @@ -3604,9 +3604,9 @@ u8 AtkCanceller_UnableToUseMove(void) { // confusion dmg #if B_CONFUSION_SELF_DMG_CHANCE >= GEN_7 - if (Random() % 3 == 0) + if (RandomWeighted(RNG_CONFUSION, 2, 1)) #else - if (Random() % 2 == 0) + if (RandomWeighted(RNG_CONFUSION, 1, 1)) #endif { gBattleCommunication[MULTISTRING_CHOOSER] = TRUE; @@ -3632,7 +3632,7 @@ u8 AtkCanceller_UnableToUseMove(void) gBattleStruct->atkCancellerTracker++; break; case CANCELLER_PARALYSED: // paralysis - if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_PARALYSIS) && (Random() % 4) == 0) + if ((gBattleMons[gBattlerAttacker].status1 & STATUS1_PARALYSIS) && !RandomPercentage(RNG_PARALYSIS, 75)) { gProtectStructs[gBattlerAttacker].prlzImmobility = TRUE; // This is removed in FRLG and Emerald for some reason @@ -3647,7 +3647,7 @@ u8 AtkCanceller_UnableToUseMove(void) if (gBattleMons[gBattlerAttacker].status2 & STATUS2_INFATUATION) { gBattleScripting.battler = CountTrailingZeroBits((gBattleMons[gBattlerAttacker].status2 & STATUS2_INFATUATION) >> 0x10); - if (Random() & 1) + if (!RandomPercentage(RNG_INFATUATION, 50)) { BattleScriptPushCursor(); } @@ -5652,7 +5652,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move && TARGET_TURN_DAMAGED && CanBePoisoned(gBattlerTarget, gBattlerAttacker) && IsMoveMakingContact(move, gBattlerAttacker) - && (Random() % 3) == 0) + && RandomWeighted(RNG_POISON_POINT, 2, 1)) { gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_POISON; PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); @@ -5670,7 +5670,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move && TARGET_TURN_DAMAGED && CanBeParalyzed(gBattlerAttacker) && IsMoveMakingContact(move, gBattlerAttacker) - && (Random() % 3) == 0) + && RandomWeighted(RNG_STATIC, 2, 1)) { gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_PARALYSIS; BattleScriptPushCursor(); @@ -5686,7 +5686,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move && (IsMoveMakingContact(move, gBattlerAttacker)) && TARGET_TURN_DAMAGED && CanBeBurned(gBattlerAttacker) - && (Random() % 3) == 0) + && RandomWeighted(RNG_FLAME_BODY, 2, 1)) { gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_BURN; BattleScriptPushCursor(); @@ -5702,7 +5702,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move && (IsMoveMakingContact(move, gBattlerAttacker)) && TARGET_TURN_DAMAGED && gBattleMons[gBattlerTarget].hp != 0 - && (Random() % 3) == 0 + && RandomWeighted(RNG_CUTE_CHARM, 2, 1) && GetBattlerAbility(gBattlerAttacker) != ABILITY_OBLIVIOUS && !IsAbilityOnSide(gBattlerAttacker, ABILITY_AROMA_VEIL) && GetGenderFromSpeciesAndPersonality(speciesAtk, pidAtk) != GetGenderFromSpeciesAndPersonality(speciesDef, pidDef) @@ -5922,7 +5922,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) && gBattleMons[gBattlerTarget].hp != 0 && !gProtectStructs[gBattlerAttacker].confusionSelfDmg - && (Random() % 10) == 0 + && RandomWeighted(RNG_STENCH, 9, 1) && !IS_MOVE_STATUS(move) && !sMovesNotAffectedByStench[gCurrentMove]) { @@ -7642,7 +7642,7 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn) if (gBattleMoveDamage != 0 // Need to have done damage && !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) && TARGET_TURN_DAMAGED - && (Random() % 100) < atkHoldEffectParam + && RandomPercentage(RNG_HOLD_EFFECT_FLINCH, atkHoldEffectParam) && gBattleMoves[gCurrentMove].flags & FLAG_KINGS_ROCK_AFFECTED && gBattleMons[gBattlerTarget].hp) { @@ -9763,7 +9763,7 @@ static s32 DoMoveDamageCalc(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType, // Add a random factor. if (randomFactor) { - dmg *= 100 - (Random() % 16); + dmg *= 100 - RandomUniform(RNG_DAMAGE_MODIFIER, 0, 15); dmg /= 100; } diff --git a/src/data.c b/src/data.c index 08b2c96e0..1ced9ce81 100644 --- a/src/data.c +++ b/src/data.c @@ -3,6 +3,7 @@ #include "battle.h" #include "data.h" #include "graphics.h" +#include "constants/abilities.h" #include "constants/items.h" #include "constants/moves.h" #include "constants/trainers.h" diff --git a/src/data/graphics/items.h b/src/data/graphics/items.h index 35ce13db2..ce4ecf608 100644 --- a/src/data/graphics/items.h +++ b/src/data/graphics/items.h @@ -1931,17 +1931,61 @@ const u32 gItemIconPalette_Ruby[] = INCBIN_U32("graphics/items/icon_palettes/rub const u32 gItemIconPalette_Sapphire[] = INCBIN_U32("graphics/items/icon_palettes/sapphire.gbapal.lz"); -//const u32 gItemIcon_AbilityShield[] = INCBIN_U32("graphics/items/icons/ability_shield.4bpp.lz"); -//const u32 gItemIconPalette_AbilityShield[] = INCBIN_U32("graphics/items/icon_palettes/ability_shield.gbapal.lz"); +const u32 gItemIcon_AbilityShield[] = INCBIN_U32("graphics/items/icons/ability_shield.4bpp.lz"); +const u32 gItemIconPalette_AbilityShield[] = INCBIN_U32("graphics/items/icon_palettes/ability_shield.gbapal.lz"); //const u32 gItemIcon_ClearAmulet[] = INCBIN_U32("graphics/items/icons/clear_amulet.4bpp.lz"); //const u32 gItemIconPalette_ClearAmulet[] = INCBIN_U32("graphics/items/icon_palettes/clear_amulet.gbapal.lz"); -//const u32 gItemIcon_PunchingGlove[] = INCBIN_U32("graphics/items/icons/punching_glove.4bpp.lz"); -//const u32 gItemIconPalette_PunchingGlove[] = INCBIN_U32("graphics/items/icon_palettes/punching_glove.gbapal.lz"); +const u32 gItemIcon_PunchingGlove[] = INCBIN_U32("graphics/items/icons/punching_glove.4bpp.lz"); +const u32 gItemIconPalette_PunchingGlove[] = INCBIN_U32("graphics/items/icon_palettes/punching_glove.gbapal.lz"); -//const u32 gItemIcon_CovertCloak[] = INCBIN_U32("graphics/items/icons/covert_cloak.4bpp.lz"); -//const u32 gItemIconPalette_CovertCloak[] = INCBIN_U32("graphics/items/icon_palettes/covert_cloak.gbapal.lz"); +const u32 gItemIcon_CovertCloak[] = INCBIN_U32("graphics/items/icons/covert_cloak.4bpp.lz"); +const u32 gItemIconPalette_CovertCloak[] = INCBIN_U32("graphics/items/icon_palettes/covert_cloak.gbapal.lz"); -//const u32 gItemIcon_LoadedDice[] = INCBIN_U32("graphics/items/icons/loaded_dice.4bpp.lz"); -//const u32 gItemIconPalette_LoadedDice[] = INCBIN_U32("graphics/items/icon_palettes/loaded_dice.gbapal.lz"); +const u32 gItemIcon_LoadedDice[] = INCBIN_U32("graphics/items/icons/loaded_dice.4bpp.lz"); +const u32 gItemIconPalette_LoadedDice[] = INCBIN_U32("graphics/items/icon_palettes/loaded_dice.gbapal.lz"); + +const u32 gItemIcon_AuspiciousArmor[] = INCBIN_U32("graphics/items/icons/auspicious_armor.4bpp.lz"); +const u32 gItemIconPalette_AuspiciousArmor[] = INCBIN_U32("graphics/items/icon_palettes/auspicious_armor.gbapal.lz"); + +const u32 gItemIcon_BigBambooShoot[] = INCBIN_U32("graphics/items/icons/big_bamboo_shoot.4bpp.lz"); +const u32 gItemIconPalette_BigBambooShoot[] = INCBIN_U32("graphics/items/icon_palettes/big_bamboo_shoot.gbapal.lz"); + +const u32 gItemIcon_BoosterEnergy[] = INCBIN_U32("graphics/items/icons/booster_energy.4bpp.lz"); +const u32 gItemIconPalette_BoosterEnergy[] = INCBIN_U32("graphics/items/icon_palettes/booster_energy.gbapal.lz"); + +const u32 gItemIcon_GimmighoulCoin[] = INCBIN_U32("graphics/items/icons/gimmighoul_coin.4bpp.lz"); +const u32 gItemIconPalette_GimmighoulCoin[] = INCBIN_U32("graphics/items/icon_palettes/gimmighoul_coin.gbapal.lz"); + +const u32 gItemIcon_LeadersCrest[] = INCBIN_U32("graphics/items/icons/leaders_crest.4bpp.lz"); +const u32 gItemIconPalette_LeadersCrest[] = INCBIN_U32("graphics/items/icon_palettes/leaders_crest.gbapal.lz"); + +const u32 gItemIcon_MaliciousArmor[] = INCBIN_U32("graphics/items/icons/malicious_armor.4bpp.lz"); +const u32 gItemIconPalette_MaliciousArmor[] = INCBIN_U32("graphics/items/icon_palettes/malicious_armor.gbapal.lz"); + +const u32 gItemIcon_MirrorHerb[] = INCBIN_U32("graphics/items/icons/mirror_herb.4bpp.lz"); +const u32 gItemIconPalette_MirrorHerb[] = INCBIN_U32("graphics/items/icon_palettes/mirror_herb.gbapal.lz"); + +const u32 gItemIcon_ScrollOfDarkness[] = INCBIN_U32("graphics/items/icons/scroll_of_darkness.4bpp.lz"); +const u32 gItemIconPalette_ScrollOfDarkness[] = INCBIN_U32("graphics/items/icon_palettes/scroll_of_darkness.gbapal.lz"); + +const u32 gItemIcon_ScrollOfWaters[] = INCBIN_U32("graphics/items/icons/scroll_of_waters.4bpp.lz"); +const u32 gItemIconPalette_ScrollOfWaters[] = INCBIN_U32("graphics/items/icon_palettes/scroll_of_waters.gbapal.lz"); + +const u32 gItemIcon_TeraOrb[] = INCBIN_U32("graphics/items/icons/tera_orb.4bpp.lz"); +const u32 gItemIconPalette_TeraOrb[] = INCBIN_U32("graphics/items/icon_palettes/tera_orb.gbapal.lz"); + +const u32 gItemIcon_TinyBambooShoot[] = INCBIN_U32("graphics/items/icons/tiny_bamboo_shoot.4bpp.lz"); +const u32 gItemIconPalette_TinyBambooShoot[] = INCBIN_U32("graphics/items/icon_palettes/tiny_bamboo_shoot.gbapal.lz"); + +// Tera Shards here + +const u32 gItemIcon_AdamantCrystal[] = INCBIN_U32("graphics/items/icons/adamant_crystal.4bpp.lz"); +const u32 gItemIconPalette_AdamantCrystal[] = INCBIN_U32("graphics/items/icon_palettes/adamant_crystal.gbapal.lz"); + +const u32 gItemIcon_GriseousCore[] = INCBIN_U32("graphics/items/icons/griseous_core.4bpp.lz"); +const u32 gItemIconPalette_GriseousCore[] = INCBIN_U32("graphics/items/icon_palettes/griseous_core.gbapal.lz"); + +const u32 gItemIcon_LustrousGlobe[] = INCBIN_U32("graphics/items/icons/lustrous_globe.4bpp.lz"); +const u32 gItemIconPalette_LustrousGlobe[] = INCBIN_U32("graphics/items/icon_palettes/lustrous_globe.gbapal.lz"); diff --git a/src/data/item_icon_table.h b/src/data/item_icon_table.h index 0107b71ab..9e4b56777 100644 --- a/src/data/item_icon_table.h +++ b/src/data/item_icon_table.h @@ -803,11 +803,43 @@ const u32 *const gItemIconTable[ITEMS_COUNT + 1][2] = [ITEM_TEA] = {gItemIcon_Tea, gItemIconPalette_Tea}, [ITEM_RUBY] = {gItemIcon_Gem, gItemIconPalette_Ruby}, [ITEM_SAPPHIRE] = {gItemIcon_Gem, gItemIconPalette_Sapphire}, - [ITEM_ABILITY_SHIELD] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_AbilityShield, gItemIconPalette_AbilityShield}, + [ITEM_ABILITY_SHIELD] = {gItemIcon_AbilityShield, gItemIconPalette_AbilityShield}, [ITEM_CLEAR_AMULET] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_ClearAmulet, gItemIconPalette_ClearAmulet}, - [ITEM_PUNCHING_GLOVE] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_PunchingGlove, gItemIconPalette_PunchingGlove}, - [ITEM_COVERT_CLOAK] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_CovertCloak, gItemIconPalette_CovertCloak}, - [ITEM_LOADED_DICE] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_LoadedDice, gItemIconPalette_LoadedDice}, + [ITEM_PUNCHING_GLOVE] = {gItemIcon_PunchingGlove, gItemIconPalette_PunchingGlove}, + [ITEM_COVERT_CLOAK] = {gItemIcon_CovertCloak, gItemIconPalette_CovertCloak}, + [ITEM_LOADED_DICE] = {gItemIcon_LoadedDice, gItemIconPalette_LoadedDice}, + [ITEM_AUSPICIOUS_ARMOR] = {gItemIcon_AuspiciousArmor, gItemIconPalette_AuspiciousArmor}, + [ITEM_BOOSTER_ENERGY] = {gItemIcon_BoosterEnergy, gItemIconPalette_BoosterEnergy}, + [ITEM_BIG_BAMBOO_SHOOT] = {gItemIcon_BigBambooShoot, gItemIconPalette_BigBambooShoot}, + [ITEM_GIMMIGHOUL_COIN] = {gItemIcon_GimmighoulCoin, gItemIconPalette_GimmighoulCoin}, + [ITEM_LEADERS_CREST] = {gItemIcon_LeadersCrest, gItemIconPalette_LeadersCrest}, + [ITEM_MALICIOUS_ARMOR] = {gItemIcon_MaliciousArmor, gItemIconPalette_MaliciousArmor}, + [ITEM_MIRROR_HERB] = {gItemIcon_MirrorHerb, gItemIconPalette_MirrorHerb}, + [ITEM_SCROLL_OF_DARKNESS] = {gItemIcon_ScrollOfDarkness, gItemIconPalette_ScrollOfDarkness}, + [ITEM_SCROLL_OF_WATERS] = {gItemIcon_ScrollOfWaters, gItemIconPalette_ScrollOfWaters}, + [ITEM_TERA_ORB] = {gItemIcon_TeraOrb, gItemIconPalette_TeraOrb}, + [ITEM_TINY_BAMBOO_SHOOT] = {gItemIcon_TinyBambooShoot, gItemIconPalette_TinyBambooShoot}, + [ITEM_BUG_TERA_SHARD] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_BugTeraShard, gItemIconPalette_BugTeraShard}, + [ITEM_DARK_TERA_SHARD] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_DarkTeraShard, gItemIconPalette_DarkTeraShard}, + [ITEM_DRAGON_TERA_SHARD] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_DragonTeraShard, gItemIconPalette_DragonTeraShard}, + [ITEM_ELECTRIC_TERA_SHARD] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_ElectricTeraShard, gItemIconPalette_ElectricTeraShard}, + [ITEM_FAIRY_TERA_SHARD] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_FairyTeraShard, gItemIconPalette_FairyTeraShard}, + [ITEM_FIGHTING_TERA_SHARD] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_FightingTeraShard, gItemIconPalette_FightingTeraShard}, + [ITEM_FIRE_TERA_SHARD] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_FireTeraShard, gItemIconPalette_FireTeraShard}, + [ITEM_FLYING_TERA_SHARD] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_FlyingTeraShard, gItemIconPalette_FlyingTeraShard}, + [ITEM_GHOST_TERA_SHARD] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_GhostTeraShard, gItemIconPalette_GhostTeraShard}, + [ITEM_GRASS_TERA_SHARD] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_GrassTeraShard, gItemIconPalette_GrassTeraShard}, + [ITEM_GROUND_TERA_SHARD] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_GroundTeraShard, gItemIconPalette_GroundTeraShard}, + [ITEM_ICE_TERA_SHARD] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_IceTeraShard, gItemIconPalette_IceTeraShard}, + [ITEM_NORMAL_TERA_SHARD] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_NormalTeraShard, gItemIconPalette_NormalTeraShard}, + [ITEM_POISON_TERA_SHARD] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_PoisonTeraShard, gItemIconPalette_PoisonTeraShard}, + [ITEM_PSYCHIC_TERA_SHARD] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_PsychicTeraShard, gItemIconPalette_PsychicTeraShard}, + [ITEM_ROCK_TERA_SHARD] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_RockTeraShard, gItemIconPalette_RockTeraShard}, + [ITEM_STEEL_TERA_SHARD] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_SteelTeraShard, gItemIconPalette_SteelTeraShard}, + [ITEM_WATER_TERA_SHARD] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_WaterTeraShard, gItemIconPalette_WaterTeraShard}, + [ITEM_ADAMANT_CRYSTAL] = {gItemIcon_AdamantCrystal, gItemIconPalette_AdamantCrystal}, + [ITEM_GRISEOUS_CORE] = {gItemIcon_GriseousCore, gItemIconPalette_GriseousCore}, + [ITEM_LUSTROUS_GLOBE] = {gItemIcon_LustrousGlobe, gItemIconPalette_LustrousGlobe}, // Return to field arrow [ITEMS_COUNT] = {gItemIcon_ReturnToFieldArrow, gItemIconPalette_ReturnToFieldArrow}, }; diff --git a/src/data/items.h b/src/data/items.h index 63446913e..82e41901c 100644 --- a/src/data/items.h +++ b/src/data/items.h @@ -9907,7 +9907,6 @@ const struct Item gItems[] = [ITEM_LOADED_DICE] = { - //YellwApricorn .name = _("Loaded Dice"), .itemId = ITEM_LOADED_DICE, .price = 20000, @@ -9918,4 +9917,370 @@ const struct Item gItems[] = .fieldUseFunc = ItemUseOutOfBattle_CannotUse, .flingPower = 30, }, + + [ITEM_AUSPICIOUS_ARMOR] = + { + .name = _("AuspciousArmr"), + .itemId = ITEM_AUSPICIOUS_ARMOR, + .price = 3000, + .description = sAuspiciousArmorDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_PARTY_MENU, + .fieldUseFunc = ItemUseOutOfBattle_EvolutionStone, + .flingPower = 30, + }, + + [ITEM_BOOSTER_ENERGY] = + { + .name = _("BoosterEnergy"), + .itemId = ITEM_BOOSTER_ENERGY, + .price = 0, + .holdEffect = HOLD_EFFECT_BOOSTER_ENERGY, + .description = sBoosterEnergyDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + .flingPower = 30, + }, + + [ITEM_BIG_BAMBOO_SHOOT] = + { + .name = _("BigBmbooShoot"), + .itemId = ITEM_BIG_BAMBOO_SHOOT, + .price = 3000, + .description = sBigBambooShootDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + .flingPower = 30, + }, + + [ITEM_GIMMIGHOUL_COIN] = + { + .name = _("GimighoulCoin"), + .itemId = ITEM_GIMMIGHOUL_COIN, + .price = 400, + .description = sGimmighoulCoinDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + }, + + [ITEM_LEADERS_CREST] = + { + .name = _("Leader'sCrest"), + .itemId = ITEM_LEADERS_CREST, + .price = 3000, + .description = sLeadersCrestDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + }, + + [ITEM_MALICIOUS_ARMOR] = + { + .name = _("MaliciousArmr"), + .itemId = ITEM_MALICIOUS_ARMOR, + .price = 3000, + .description = sMaliciousArmorDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_PARTY_MENU, + .fieldUseFunc = ItemUseOutOfBattle_EvolutionStone, + .flingPower = 30, + }, + + [ITEM_MIRROR_HERB] = + { + .name = _("Mirror Herb"), + .itemId = ITEM_MIRROR_HERB, + .price = 30000, + .holdEffect = HOLD_EFFECT_MIRROR_HERB, + .description = sMirrorHerbDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + .flingPower = 30, + }, + + [ITEM_SCROLL_OF_DARKNESS] = + { + .name = _("ScrllOfDrknss"), + .itemId = ITEM_SCROLL_OF_DARKNESS, + .price = 0, + .description = sScrollOfDarknessDesc, + .importance = 1, + .pocket = POCKET_KEY_ITEMS, + .type = ITEM_USE_PARTY_MENU, + .fieldUseFunc = ItemUseOutOfBattle_EvolutionStone, + }, + + [ITEM_SCROLL_OF_WATERS] = + { + .name = _("ScrollOfWatrs"), + .itemId = ITEM_SCROLL_OF_WATERS, + .price = 0, + .description = sScrollOfWatersDesc, + .importance = 1, + .pocket = POCKET_KEY_ITEMS, + .type = ITEM_USE_PARTY_MENU, + .fieldUseFunc = ItemUseOutOfBattle_EvolutionStone, + }, + + [ITEM_TERA_ORB] = + { + .name = _("Tera Orb"), + .itemId = ITEM_TERA_ORB, + .price = 0, + .description = sTeraOrbDesc, + .importance = 1, + .pocket = POCKET_KEY_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + }, + + [ITEM_TINY_BAMBOO_SHOOT] = + { + .name = _("TinyBmbooShot"), + .itemId = ITEM_TINY_BAMBOO_SHOOT, + .price = 750, + .description = sTinyBambooShootDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + .flingPower = 30, + }, + + [ITEM_BUG_TERA_SHARD] = + { + .name = _("Bug TeraShard"), + .itemId = ITEM_BUG_TERA_SHARD, + .price = 0, + .description = sTeraShardDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + }, + + [ITEM_DARK_TERA_SHARD] = + { + .name = _("DarkTeraShard"), + .itemId = ITEM_DARK_TERA_SHARD, + .price = 0, + .description = sTeraShardDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + }, + + [ITEM_DRAGON_TERA_SHARD] = + { + .name = _("DragnTeraShrd"), + .itemId = ITEM_DRAGON_TERA_SHARD, + .price = 0, + .description = sTeraShardDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + }, + + [ITEM_ELECTRIC_TERA_SHARD] = + { + .name = _("EltrcTeraShrd"), + .itemId = ITEM_ELECTRIC_TERA_SHARD, + .price = 0, + .description = sTeraShardDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + }, + + [ITEM_FAIRY_TERA_SHARD] = + { + .name = _("FairyTeraShrd"), + .itemId = ITEM_FAIRY_TERA_SHARD, + .price = 0, + .description = sTeraShardDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + }, + + [ITEM_FIGHTING_TERA_SHARD] = + { + .name = _("FghtngTerShrd"), + .itemId = ITEM_FIGHTING_TERA_SHARD, + .price = 0, + .description = sTeraShardDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + }, + + [ITEM_FIRE_TERA_SHARD] = + { + .name = _("FireTeraShard"), + .itemId = ITEM_FIRE_TERA_SHARD, + .price = 0, + .description = sTeraShardDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + }, + + [ITEM_FLYING_TERA_SHARD] = + { + .name = _("FlyngTeraShrd"), + .itemId = ITEM_FLYING_TERA_SHARD, + .price = 0, + .description = sTeraShardDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + }, + + [ITEM_GHOST_TERA_SHARD] = + { + .name = _("GhostTeraShrd"), + .itemId = ITEM_GHOST_TERA_SHARD, + .price = 0, + .description = sTeraShardDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + }, + + [ITEM_GRASS_TERA_SHARD] = + { + .name = _("GrassTeraShrd"), + .itemId = ITEM_GRASS_TERA_SHARD, + .price = 0, + .description = sTeraShardDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + }, + + [ITEM_GROUND_TERA_SHARD] = + { + .name = _("GrondTeraShrd"), + .itemId = ITEM_GROUND_TERA_SHARD, + .price = 0, + .description = sTeraShardDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + }, + + [ITEM_ICE_TERA_SHARD] = + { + .name = _("Ice TeraShard"), + .itemId = ITEM_ICE_TERA_SHARD, + .price = 0, + .description = sTeraShardDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + }, + + [ITEM_NORMAL_TERA_SHARD] = + { + .name = _("NormlTeraShrd"), + .itemId = ITEM_NORMAL_TERA_SHARD, + .price = 0, + .description = sTeraShardDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + }, + + [ITEM_POISON_TERA_SHARD] = + { + .name = _("PoisnTeraShrd"), + .itemId = ITEM_POISON_TERA_SHARD, + .price = 0, + .description = sTeraShardDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + }, + + [ITEM_PSYCHIC_TERA_SHARD] = + { + .name = _("PschcTeraShrd"), + .itemId = ITEM_PSYCHIC_TERA_SHARD, + .price = 0, + .description = sTeraShardDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + }, + + [ITEM_ROCK_TERA_SHARD] = + { + .name = _("RockTeraShard"), + .itemId = ITEM_ROCK_TERA_SHARD, + .price = 0, + .description = sTeraShardDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + }, + + [ITEM_STEEL_TERA_SHARD] = + { + .name = _("SteelTeraShrd"), + .itemId = ITEM_STEEL_TERA_SHARD, + .price = 0, + .description = sTeraShardDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + }, + + [ITEM_WATER_TERA_SHARD] = + { + .name = _("WaterTeraShrd"), + .itemId = ITEM_WATER_TERA_SHARD, + .price = 0, + .description = sTeraShardDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + }, + + [ITEM_ADAMANT_CRYSTAL] = + { + .name = _("AdamantCrystl"), + .itemId = ITEM_ADAMANT_CRYSTAL, + .price = 0, + .description = sAdamantCrystalDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + .flingPower = 60, + }, + + [ITEM_GRISEOUS_CORE] = + { + .name = _("Griseous Core"), + .itemId = ITEM_GRISEOUS_CORE, + .price = 0, + .description = sGriseousCoreDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + .flingPower = 60, + }, + + [ITEM_LUSTROUS_GLOBE] = + { + .name = _("LustrousGlobe"), + .itemId = ITEM_LUSTROUS_GLOBE, + .price = 0, + .description = sLustrousGlobeDesc, + .pocket = POCKET_ITEMS, + .type = ITEM_USE_BAG_MENU, + .fieldUseFunc = ItemUseOutOfBattle_CannotUse, + .flingPower = 60, + }, }; diff --git a/src/data/pokemon/evolution.h b/src/data/pokemon/evolution.h index 4f09867a6..b76e83a73 100644 --- a/src/data/pokemon/evolution.h +++ b/src/data/pokemon/evolution.h @@ -542,7 +542,9 @@ const struct Evolution gEvolutionTable[NUM_SPECIES][EVOS_PER_MON] = [SPECIES_DREEPY] = {{EVO_LEVEL, 50, SPECIES_DRAKLOAK}}, [SPECIES_DRAKLOAK] = {{EVO_LEVEL, 60, SPECIES_DRAGAPULT}}, [SPECIES_KUBFU] = {{EVO_DARK_SCROLL, 0, SPECIES_URSHIFU}, - {EVO_WATER_SCROLL, 0, SPECIES_URSHIFU_RAPID_STRIKE_STYLE}}, + {EVO_ITEM, ITEM_SCROLL_OF_DARKNESS, SPECIES_URSHIFU}, + {EVO_WATER_SCROLL, 0, SPECIES_URSHIFU_RAPID_STRIKE_STYLE}, + {EVO_ITEM, ITEM_SCROLL_OF_WATERS, SPECIES_URSHIFU_RAPID_STRIKE_STYLE}}, #endif [SPECIES_RATTATA_ALOLAN] = {{EVO_LEVEL_NIGHT, 20, SPECIES_RATICATE_ALOLAN}}, [SPECIES_SANDSHREW_ALOLAN] = {{EVO_ITEM, ITEM_ICE_STONE, SPECIES_SANDSLASH_ALOLAN}}, diff --git a/src/data/pokemon/form_change_table_pointers.h b/src/data/pokemon/form_change_table_pointers.h index 90be7112b..62afeb2e0 100644 --- a/src/data/pokemon/form_change_table_pointers.h +++ b/src/data/pokemon/form_change_table_pointers.h @@ -1,6 +1,10 @@ const struct FormChange *const gFormChangeTablePointers[NUM_SPECIES] = { #if P_GEN_4_POKEMON == TRUE + [SPECIES_DIALGA] = sDialgaFormChangeTable, + [SPECIES_DIALGA_ORIGIN] = sDialgaFormChangeTable, + [SPECIES_PALKIA] = sPalkiaFormChangeTable, + [SPECIES_PALKIA_ORIGIN] = sPalkiaFormChangeTable, [SPECIES_GIRATINA] = sGiratinaFormChangeTable, [SPECIES_GIRATINA_ORIGIN] = sGiratinaFormChangeTable, [SPECIES_SHAYMIN] = sShayminFormChangeTable, diff --git a/src/data/pokemon/form_change_tables.h b/src/data/pokemon/form_change_tables.h index 94da0add3..723bda4b2 100644 --- a/src/data/pokemon/form_change_tables.h +++ b/src/data/pokemon/form_change_tables.h @@ -44,9 +44,21 @@ FORM_BATTLE_END: #define NIGHT 2 #if P_GEN_4_POKEMON == TRUE +static const struct FormChange sDialgaFormChangeTable[] = { + {FORM_ITEM_HOLD, SPECIES_DIALGA, ITEM_NONE}, + {FORM_ITEM_HOLD, SPECIES_DIALGA_ORIGIN, ITEM_ADAMANT_CRYSTAL}, + {FORM_CHANGE_END}, +}; + +static const struct FormChange sPalkiaFormChangeTable[] = { + {FORM_ITEM_HOLD, SPECIES_PALKIA, ITEM_NONE}, + {FORM_ITEM_HOLD, SPECIES_PALKIA_ORIGIN, ITEM_LUSTROUS_GLOBE}, + {FORM_CHANGE_END}, +}; + static const struct FormChange sGiratinaFormChangeTable[] = { {FORM_ITEM_HOLD, SPECIES_GIRATINA, ITEM_NONE}, - {FORM_ITEM_HOLD, SPECIES_GIRATINA_ORIGIN, ITEM_GRISEOUS_ORB}, + {FORM_ITEM_HOLD, SPECIES_GIRATINA_ORIGIN, ITEM_GRISEOUS_CORE}, {FORM_CHANGE_END}, }; diff --git a/src/data/pokemon/item_effects.h b/src/data/pokemon/item_effects.h index 8323801d5..607f57814 100644 --- a/src/data/pokemon/item_effects.h +++ b/src/data/pokemon/item_effects.h @@ -450,125 +450,129 @@ const u8 gItemEffect_TamatoBerry[10] = { const u8 *const gItemEffectTable[ITEMS_COUNT] = { // Medicine - [ITEM_POTION] = gItemEffect_Potion, - [ITEM_SUPER_POTION] = gItemEffect_SuperPotion, - [ITEM_HYPER_POTION] = gItemEffect_HyperPotion, - [ITEM_MAX_POTION] = gItemEffect_MaxPotion, - [ITEM_FULL_RESTORE] = gItemEffect_FullRestore, - [ITEM_REVIVE] = gItemEffect_Revive, - [ITEM_MAX_REVIVE] = gItemEffect_MaxRevive, - [ITEM_FRESH_WATER] = gItemEffect_FreshWater, - [ITEM_SODA_POP] = gItemEffect_SodaPop, - [ITEM_LEMONADE] = gItemEffect_Lemonade, - [ITEM_MOOMOO_MILK] = gItemEffect_MoomooMilk, - [ITEM_ENERGY_POWDER] = gItemEffect_EnergyPowder, - [ITEM_ENERGY_ROOT] = gItemEffect_EnergyRoot, - [ITEM_HEAL_POWDER] = gItemEffect_HealPowder, - [ITEM_REVIVAL_HERB] = gItemEffect_RevivalHerb, - [ITEM_ANTIDOTE] = gItemEffect_Antidote, - [ITEM_PARALYZE_HEAL] = gItemEffect_ParalyzeHeal, - [ITEM_BURN_HEAL] = gItemEffect_BurnHeal, - [ITEM_ICE_HEAL] = gItemEffect_IceHeal, - [ITEM_AWAKENING] = gItemEffect_Awakening, - [ITEM_FULL_HEAL] = gItemEffect_FullHeal, - [ITEM_ETHER] = gItemEffect_Ether, - [ITEM_MAX_ETHER] = gItemEffect_MaxEther, - [ITEM_ELIXIR] = gItemEffect_Elixir, - [ITEM_MAX_ELIXIR] = gItemEffect_MaxElixir, - [ITEM_BERRY_JUICE] = gItemEffect_BerryJuice, - [ITEM_SACRED_ASH] = gItemEffect_SacredAsh, - [ITEM_SWEET_HEART] = gItemEffect_Potion, - [ITEM_MAX_HONEY] = gItemEffect_MaxRevive, + [ITEM_POTION] = gItemEffect_Potion, + [ITEM_SUPER_POTION] = gItemEffect_SuperPotion, + [ITEM_HYPER_POTION] = gItemEffect_HyperPotion, + [ITEM_MAX_POTION] = gItemEffect_MaxPotion, + [ITEM_FULL_RESTORE] = gItemEffect_FullRestore, + [ITEM_REVIVE] = gItemEffect_Revive, + [ITEM_MAX_REVIVE] = gItemEffect_MaxRevive, + [ITEM_FRESH_WATER] = gItemEffect_FreshWater, + [ITEM_SODA_POP] = gItemEffect_SodaPop, + [ITEM_LEMONADE] = gItemEffect_Lemonade, + [ITEM_MOOMOO_MILK] = gItemEffect_MoomooMilk, + [ITEM_ENERGY_POWDER] = gItemEffect_EnergyPowder, + [ITEM_ENERGY_ROOT] = gItemEffect_EnergyRoot, + [ITEM_HEAL_POWDER] = gItemEffect_HealPowder, + [ITEM_REVIVAL_HERB] = gItemEffect_RevivalHerb, + [ITEM_ANTIDOTE] = gItemEffect_Antidote, + [ITEM_PARALYZE_HEAL] = gItemEffect_ParalyzeHeal, + [ITEM_BURN_HEAL] = gItemEffect_BurnHeal, + [ITEM_ICE_HEAL] = gItemEffect_IceHeal, + [ITEM_AWAKENING] = gItemEffect_Awakening, + [ITEM_FULL_HEAL] = gItemEffect_FullHeal, + [ITEM_ETHER] = gItemEffect_Ether, + [ITEM_MAX_ETHER] = gItemEffect_MaxEther, + [ITEM_ELIXIR] = gItemEffect_Elixir, + [ITEM_MAX_ELIXIR] = gItemEffect_MaxElixir, + [ITEM_BERRY_JUICE] = gItemEffect_BerryJuice, + [ITEM_SACRED_ASH] = gItemEffect_SacredAsh, + [ITEM_SWEET_HEART] = gItemEffect_Potion, + [ITEM_MAX_HONEY] = gItemEffect_MaxRevive, // Regional Specialties - [ITEM_PEWTER_CRUNCHIES] = gItemEffect_FullHeal, - [ITEM_RAGE_CANDY_BAR] = gItemEffect_FullHeal, - [ITEM_LAVA_COOKIE] = gItemEffect_FullHeal, - [ITEM_OLD_GATEAU] = gItemEffect_FullHeal, - [ITEM_CASTELIACONE] = gItemEffect_FullHeal, - [ITEM_LUMIOSE_GALETTE] = gItemEffect_FullHeal, - [ITEM_SHALOUR_SABLE] = gItemEffect_FullHeal, - [ITEM_BIG_MALASADA] = gItemEffect_FullHeal, + [ITEM_PEWTER_CRUNCHIES] = gItemEffect_FullHeal, + [ITEM_RAGE_CANDY_BAR] = gItemEffect_FullHeal, + [ITEM_LAVA_COOKIE] = gItemEffect_FullHeal, + [ITEM_OLD_GATEAU] = gItemEffect_FullHeal, + [ITEM_CASTELIACONE] = gItemEffect_FullHeal, + [ITEM_LUMIOSE_GALETTE] = gItemEffect_FullHeal, + [ITEM_SHALOUR_SABLE] = gItemEffect_FullHeal, + [ITEM_BIG_MALASADA] = gItemEffect_FullHeal, // Vitamins - [ITEM_HP_UP] = gItemEffect_HPUp, - [ITEM_PROTEIN] = gItemEffect_Protein, - [ITEM_IRON] = gItemEffect_Iron, - [ITEM_CALCIUM] = gItemEffect_Calcium, - [ITEM_ZINC] = gItemEffect_Zinc, - [ITEM_CARBOS] = gItemEffect_Carbos, - [ITEM_PP_UP] = gItemEffect_PPUp, - [ITEM_PP_MAX] = gItemEffect_PPMax, + [ITEM_HP_UP] = gItemEffect_HPUp, + [ITEM_PROTEIN] = gItemEffect_Protein, + [ITEM_IRON] = gItemEffect_Iron, + [ITEM_CALCIUM] = gItemEffect_Calcium, + [ITEM_ZINC] = gItemEffect_Zinc, + [ITEM_CARBOS] = gItemEffect_Carbos, + [ITEM_PP_UP] = gItemEffect_PPUp, + [ITEM_PP_MAX] = gItemEffect_PPMax, // EV Feathers - [ITEM_HEALTH_FEATHER] = gItemEffect_HpFeather, - [ITEM_MUSCLE_FEATHER] = gItemEffect_AtkFeather, - [ITEM_RESIST_FEATHER] = gItemEffect_DefFeather, - [ITEM_GENIUS_FEATHER] = gItemEffect_SpatkFeather, - [ITEM_CLEVER_FEATHER] = gItemEffect_SpdefFeather, - [ITEM_SWIFT_FEATHER] = gItemEffect_SpeedFeather, + [ITEM_HEALTH_FEATHER] = gItemEffect_HpFeather, + [ITEM_MUSCLE_FEATHER] = gItemEffect_AtkFeather, + [ITEM_RESIST_FEATHER] = gItemEffect_DefFeather, + [ITEM_GENIUS_FEATHER] = gItemEffect_SpatkFeather, + [ITEM_CLEVER_FEATHER] = gItemEffect_SpdefFeather, + [ITEM_SWIFT_FEATHER] = gItemEffect_SpeedFeather, // Candy - [ITEM_RARE_CANDY] = gItemEffect_RareCandy, - [ITEM_EXP_CANDY_XS] = gItemEffect_RareCandy, - [ITEM_EXP_CANDY_S] = gItemEffect_RareCandy, - [ITEM_EXP_CANDY_M] = gItemEffect_RareCandy, - [ITEM_EXP_CANDY_L] = gItemEffect_RareCandy, - [ITEM_EXP_CANDY_XL] = gItemEffect_RareCandy, - //[ITEM_DYNAMAX_CANDY] = gItemEffect_DynamaxCandy, // Todo + [ITEM_RARE_CANDY] = gItemEffect_RareCandy, + [ITEM_EXP_CANDY_XS] = gItemEffect_RareCandy, + [ITEM_EXP_CANDY_S] = gItemEffect_RareCandy, + [ITEM_EXP_CANDY_M] = gItemEffect_RareCandy, + [ITEM_EXP_CANDY_L] = gItemEffect_RareCandy, + [ITEM_EXP_CANDY_XL] = gItemEffect_RareCandy, + //[ITEM_DYNAMAX_CANDY] = gItemEffect_DynamaxCandy, // Todo // Medicinal Flutes - [ITEM_BLUE_FLUTE] = gItemEffect_BlueFlute, - [ITEM_YELLOW_FLUTE] = gItemEffect_YellowFlute, - [ITEM_RED_FLUTE] = gItemEffect_RedFlute, + [ITEM_BLUE_FLUTE] = gItemEffect_BlueFlute, + [ITEM_YELLOW_FLUTE] = gItemEffect_YellowFlute, + [ITEM_RED_FLUTE] = gItemEffect_RedFlute, // X Items - [ITEM_X_ATTACK] = gItemEffect_XAttack, - [ITEM_X_DEFENSE] = gItemEffect_XDefense, - [ITEM_X_SPEED] = gItemEffect_XSpeed, - [ITEM_X_ACCURACY] = gItemEffect_XAccuracy, - [ITEM_X_SP_ATK] = gItemEffect_XSpecialAttack, - [ITEM_X_SP_DEF] = gItemEffect_XSpecialDefense, + [ITEM_X_ATTACK] = gItemEffect_XAttack, + [ITEM_X_DEFENSE] = gItemEffect_XDefense, + [ITEM_X_SPEED] = gItemEffect_XSpeed, + [ITEM_X_ACCURACY] = gItemEffect_XAccuracy, + [ITEM_X_SP_ATK] = gItemEffect_XSpecialAttack, + [ITEM_X_SP_DEF] = gItemEffect_XSpecialDefense, - [ITEM_DIRE_HIT] = gItemEffect_DireHit, - [ITEM_GUARD_SPEC] = gItemEffect_GuardSpec, + [ITEM_DIRE_HIT] = gItemEffect_DireHit, + [ITEM_GUARD_SPEC] = gItemEffect_GuardSpec, - //[ITEM_MAX_MUSHROOMS] = gItemEffect_MaxMushrooms, // Todo + //[ITEM_MAX_MUSHROOMS] = gItemEffect_MaxMushrooms, // Todo // Evolution Items - [ITEM_FIRE_STONE] = gItemEffect_EvoItem, - [ITEM_WATER_STONE] = gItemEffect_EvoItem, - [ITEM_THUNDER_STONE] = gItemEffect_EvoItem, - [ITEM_LEAF_STONE] = gItemEffect_EvoItem, - [ITEM_ICE_STONE] = gItemEffect_EvoItem, - [ITEM_SUN_STONE] = gItemEffect_EvoItem, - [ITEM_MOON_STONE] = gItemEffect_EvoItem, - [ITEM_SHINY_STONE] = gItemEffect_EvoItem, - [ITEM_DUSK_STONE] = gItemEffect_EvoItem, - [ITEM_DAWN_STONE] = gItemEffect_EvoItem, - [ITEM_SWEET_APPLE] = gItemEffect_EvoItem, - [ITEM_TART_APPLE] = gItemEffect_EvoItem, - [ITEM_CRACKED_POT] = gItemEffect_EvoItem, - [ITEM_CHIPPED_POT] = gItemEffect_EvoItem, - [ITEM_GALARICA_CUFF] = gItemEffect_EvoItem, - [ITEM_GALARICA_WREATH] = gItemEffect_EvoItem, + [ITEM_FIRE_STONE] = gItemEffect_EvoItem, + [ITEM_WATER_STONE] = gItemEffect_EvoItem, + [ITEM_THUNDER_STONE] = gItemEffect_EvoItem, + [ITEM_LEAF_STONE] = gItemEffect_EvoItem, + [ITEM_ICE_STONE] = gItemEffect_EvoItem, + [ITEM_SUN_STONE] = gItemEffect_EvoItem, + [ITEM_MOON_STONE] = gItemEffect_EvoItem, + [ITEM_SHINY_STONE] = gItemEffect_EvoItem, + [ITEM_DUSK_STONE] = gItemEffect_EvoItem, + [ITEM_DAWN_STONE] = gItemEffect_EvoItem, + [ITEM_SWEET_APPLE] = gItemEffect_EvoItem, + [ITEM_TART_APPLE] = gItemEffect_EvoItem, + [ITEM_CRACKED_POT] = gItemEffect_EvoItem, + [ITEM_CHIPPED_POT] = gItemEffect_EvoItem, + [ITEM_GALARICA_CUFF] = gItemEffect_EvoItem, + [ITEM_GALARICA_WREATH] = gItemEffect_EvoItem, + [ITEM_AUSPICIOUS_ARMOR] = gItemEffect_EvoItem, + [ITEM_MALICIOUS_ARMOR] = gItemEffect_EvoItem, + [ITEM_SCROLL_OF_DARKNESS] = gItemEffect_EvoItem, + [ITEM_SCROLL_OF_WATERS] = gItemEffect_EvoItem, // Berries - [ITEM_CHERI_BERRY] = gItemEffect_CheriBerry, - [ITEM_CHESTO_BERRY] = gItemEffect_ChestoBerry, - [ITEM_PECHA_BERRY] = gItemEffect_PechaBerry, - [ITEM_RAWST_BERRY] = gItemEffect_RawstBerry, - [ITEM_ASPEAR_BERRY] = gItemEffect_AspearBerry, - [ITEM_LEPPA_BERRY] = gItemEffect_LeppaBerry, - [ITEM_ORAN_BERRY] = gItemEffect_OranBerry, - [ITEM_PERSIM_BERRY] = gItemEffect_PersimBerry, - [ITEM_LUM_BERRY] = gItemEffect_FullHeal, - [ITEM_SITRUS_BERRY] = gItemEffect_SitrusBerry, - [ITEM_POMEG_BERRY] = gItemEffect_PomegBerry, - [ITEM_KELPSY_BERRY] = gItemEffect_KelpsyBerry, - [ITEM_QUALOT_BERRY] = gItemEffect_QualotBerry, - [ITEM_HONDEW_BERRY] = gItemEffect_HondewBerry, - [ITEM_GREPA_BERRY] = gItemEffect_GrepaBerry, - [ITEM_TAMATO_BERRY] = gItemEffect_TamatoBerry, - [LAST_BERRY_INDEX] = NULL, + [ITEM_CHERI_BERRY] = gItemEffect_CheriBerry, + [ITEM_CHESTO_BERRY] = gItemEffect_ChestoBerry, + [ITEM_PECHA_BERRY] = gItemEffect_PechaBerry, + [ITEM_RAWST_BERRY] = gItemEffect_RawstBerry, + [ITEM_ASPEAR_BERRY] = gItemEffect_AspearBerry, + [ITEM_LEPPA_BERRY] = gItemEffect_LeppaBerry, + [ITEM_ORAN_BERRY] = gItemEffect_OranBerry, + [ITEM_PERSIM_BERRY] = gItemEffect_PersimBerry, + [ITEM_LUM_BERRY] = gItemEffect_FullHeal, + [ITEM_SITRUS_BERRY] = gItemEffect_SitrusBerry, + [ITEM_POMEG_BERRY] = gItemEffect_PomegBerry, + [ITEM_KELPSY_BERRY] = gItemEffect_KelpsyBerry, + [ITEM_QUALOT_BERRY] = gItemEffect_QualotBerry, + [ITEM_HONDEW_BERRY] = gItemEffect_HondewBerry, + [ITEM_GREPA_BERRY] = gItemEffect_GrepaBerry, + [ITEM_TAMATO_BERRY] = gItemEffect_TamatoBerry, + [LAST_BERRY_INDEX] = NULL, }; diff --git a/src/data/text/item_descriptions.h b/src/data/text/item_descriptions.h index e815d8ae1..da15f001c 100644 --- a/src/data/text/item_descriptions.h +++ b/src/data/text/item_descriptions.h @@ -3817,3 +3817,78 @@ static const u8 sLoadedDiceDesc[] = _( "Rolls high numbers.\n" "Multihit strikes\n" "hit more times."); + +static const u8 sAuspiciousArmorDesc[] = _( + "Armor inhabited by\n" + "auspicious wishes.\n" + "Causes evolution."); + +static const u8 sBoosterEnergyDesc[] = _( + "Encapsuled energy\n" + "ups Pokémon with\n" + "certain Abilities."); + +static const u8 sBigBambooShootDesc[] = _( + "A large and rare\n" + "bamboo shoot. Best\n" + "sold to gourmands."); + +static const u8 sGimmighoulCoinDesc[] = _( + "Gimmighoul hoard\n" + "and treasure these\n" + "curious coins."); + +static const u8 sLeadersCrestDesc[] = _( + "A shard of an old\n" + "blade of some sort.\n" + "Held by Bisharp."); + +static const u8 sMaliciousArmorDesc[] = _( + "Armor inhabited by\n" + "malicious will.\n" + "Causes evolution."); + +static const u8 sMirrorHerbDesc[] = _( + "Mirrors an enemy's\n" + "stat increases\n" + "but only once."); + +static const u8 sScrollOfDarknessDesc[] = _( + "A peculiar scroll\n" + "with secrets of\n" + "the dark path."); + +static const u8 sScrollOfWatersDesc[] = _( + "A peculiar scroll\n" + "with secrets of\n" + "the water path."); + +static const u8 sTeraOrbDesc[] = _( + "Energy charges can\n" + "be used to cause\n" + "Terastallization."); + +static const u8 sTinyBambooShootDesc[] = _( + "A small and rare\n" + "bamboo shoot. Best\n" + "sold to gourmands."); + +static const u8 sTeraShardDesc[] = _( + "These shards may\n" + "form when a Tera\n" + "Pokémon faints."); + +static const u8 sAdamantCrystalDesc[] = _( + "A large, glowing gem\n" + "that lets Dialga\n" + "change form."); + +static const u8 sGriseousCoreDesc[] = _( + "A large, glowing gem\n" + "that lets Giratina\n" + "change form."); + +static const u8 sLustrousGlobeDesc[] = _( + "A large, glowing gem\n" + "that lets Palkia\n" + "change form."); diff --git a/src/party_menu.c b/src/party_menu.c index 6964fa0c5..9185657a7 100755 --- a/src/party_menu.c +++ b/src/party_menu.c @@ -5516,7 +5516,8 @@ void ItemUseCB_EvolutionStone(u8 taskId, TaskFunc task) } else { - RemoveBagItem(gSpecialVar_ItemId, 1); + if (ItemId_GetPocket(gSpecialVar_ItemId) != POCKET_KEY_ITEMS) + RemoveBagItem(gSpecialVar_ItemId, 1); FreePartyPointers(); } } diff --git a/src/random.c b/src/random.c index de923fba6..e59b56387 100644 --- a/src/random.c +++ b/src/random.c @@ -31,3 +31,27 @@ u16 Random2(void) gRng2Value = ISO_RANDOMIZE1(gRng2Value); return gRng2Value >> 16; } + +__attribute__((weak, alias("RandomUniformDefault"))) +u32 RandomUniform(enum RandomTag tag, u32 lo, u32 hi); + +__attribute__((weak, alias("RandomWeightedArrayDefault"))) +u32 RandomWeightedArray(enum RandomTag tag, u32 sum, u32 n, const u8 *weights); + +u32 RandomUniformDefault(enum RandomTag tag, u32 lo, u32 hi) +{ + return lo + (((hi - lo) * Random()) >> 16); +} + +u32 RandomWeightedArrayDefault(enum RandomTag tag, u32 sum, u32 n, const u8 *weights) +{ + s32 i, targetSum; + targetSum = (sum * Random()) >> 16; + for (i = 0; i < n - 1; i++) + { + targetSum -= weights[i]; + if (targetSum < 0) + return i; + } + return n - 1; +} diff --git a/test/ability_compound_eyes.c b/test/ability_compound_eyes.c index 97ab84dd2..d90604d5c 100644 --- a/test/ability_compound_eyes.c +++ b/test/ability_compound_eyes.c @@ -3,7 +3,7 @@ SINGLE_BATTLE_TEST("Compound Eyes raises accuracy") { - PASSES_RANDOMLY(91, 100); + PASSES_RANDOMLY(91, 100, RNG_ACCURACY); GIVEN { ASSUME(gBattleMoves[MOVE_THUNDER].accuracy == 70); PLAYER(SPECIES_BUTTERFREE) { Ability(ABILITY_COMPOUND_EYES); }; @@ -21,12 +21,11 @@ SINGLE_BATTLE_TEST("Compound Eyes raises accuracy") // than we expect. SINGLE_BATTLE_TEST("Compound Eyes does not affect OHKO moves") { - KNOWN_FAILING; - PASSES_RANDOMLY(30, 100); + PASSES_RANDOMLY(30, 100, RNG_ACCURACY); GIVEN { ASSUME(gBattleMoves[MOVE_FISSURE].accuracy == 30); ASSUME(gBattleMoves[MOVE_FISSURE].effect == EFFECT_OHKO); - PLAYER(SPECIES_BUTTERFREE) { Ability(ABILITY_TINTED_LENS); }; + PLAYER(SPECIES_BUTTERFREE) { Ability(ABILITY_COMPOUND_EYES); }; OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(player, MOVE_FISSURE); } diff --git a/test/ability_cute_charm.c b/test/ability_cute_charm.c index 5e089efdf..ccf5c490b 100644 --- a/test/ability_cute_charm.c +++ b/test/ability_cute_charm.c @@ -1,9 +1,6 @@ #include "global.h" #include "test_battle.h" -// TODO: Currently PASSES_RANDOMLY is incapable of testing Cute Charm -// because it only activates 33% of the time, but we only want to -// measure the 50% of the time that the infatuation prevents our move. SINGLE_BATTLE_TEST("Cute Charm inflicts infatuation on contact") { u32 move; diff --git a/test/ability_intimidate.c b/test/ability_intimidate.c index 0318435e0..a891f9af8 100644 --- a/test/ability_intimidate.c +++ b/test/ability_intimidate.c @@ -122,3 +122,40 @@ SINGLE_BATTLE_TEST("Intimidate and Eject Button force the opponent to Attack") } } } + +DOUBLE_BATTLE_TEST("Intimidate activates on an empty slot") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + PLAYER(SPECIES_CROAGUNK); + PLAYER(SPECIES_WYNAUT); + PLAYER(SPECIES_HITMONTOP) { Ability(ABILITY_INTIMIDATE); }; + OPPONENT(SPECIES_RALTS); + OPPONENT(SPECIES_AZURILL); + } WHEN { + TURN { + SWITCH(playerLeft, 2); + MOVE(playerRight, MOVE_GUNK_SHOT, target: opponentLeft); + MOVE(opponentRight, MOVE_SPLASH); + } + TURN { + SWITCH(playerLeft, 3); + MOVE(playerRight, MOVE_SPLASH); + } + + + } SCENE { + MESSAGE("Wobbuffet, that's enough! Come back!"); + MESSAGE("Go! Wynaut!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_GUNK_SHOT, playerRight); + ANIMATION(ANIM_TYPE_MOVE, MOVE_SPLASH, opponentRight); + MESSAGE("Wynaut, that's enough! Come back!"); + MESSAGE("Go! Hitmontop!"); + ABILITY_POPUP(playerLeft, ABILITY_INTIMIDATE); + NONE_OF { + MESSAGE("Hitmontop's Intimidate cuts Foe Ralts's attack!"); + } + ANIMATION(ANIM_TYPE_GENERAL, B_ANIM_STATS_CHANGE, opponentRight); + MESSAGE("Hitmontop's Intimidate cuts Foe Azurill's attack!"); + } +} diff --git a/test/ability_sand_veil.c b/test/ability_sand_veil.c index 5d2325140..5514f27c0 100644 --- a/test/ability_sand_veil.c +++ b/test/ability_sand_veil.c @@ -16,7 +16,7 @@ SINGLE_BATTLE_TEST("Sand Veil prevents damage from sandstorm") SINGLE_BATTLE_TEST("Sand Veil reduces accuracy during sandstorm") { - PASSES_RANDOMLY(4,5); + PASSES_RANDOMLY(4, 5, RNG_ACCURACY); GIVEN { ASSUME(gBattleMoves[MOVE_POUND].accuracy == 100); PLAYER(SPECIES_SANDSHREW) { Ability(ABILITY_SAND_VEIL); }; diff --git a/test/ability_stench.c b/test/ability_stench.c index 18d909768..fb76ebc07 100644 --- a/test/ability_stench.c +++ b/test/ability_stench.c @@ -3,7 +3,7 @@ SINGLE_BATTLE_TEST("Stench has a 10% chance to flinch") { - PASSES_RANDOMLY(1,10); + PASSES_RANDOMLY(1, 10, RNG_STENCH); GIVEN { ASSUME(gBattleMoves[MOVE_TACKLE].power > 0); PLAYER(SPECIES_GRIMER) { Ability(ABILITY_STENCH); }; @@ -17,7 +17,8 @@ SINGLE_BATTLE_TEST("Stench has a 10% chance to flinch") SINGLE_BATTLE_TEST("Stench does not stack with King's Rock") { - PASSES_RANDOMLY(1,10); + KNOWN_FAILING; + PASSES_RANDOMLY(1, 10, RNG_STENCH); GIVEN { ASSUME(gItems[ITEM_KINGS_ROCK].holdEffect == HOLD_EFFECT_FLINCH); ASSUME(gBattleMoves[MOVE_TACKLE].power > 0); diff --git a/test/hold_effect_red_card.c b/test/hold_effect_red_card.c index 882007cfd..0a8bb3460 100644 --- a/test/hold_effect_red_card.c +++ b/test/hold_effect_red_card.c @@ -8,7 +8,7 @@ ASSUMPTIONS SINGLE_BATTLE_TEST("Red Card switches the attacker with a random non-fainted replacement") { - PASSES_RANDOMLY(1, 2); + PASSES_RANDOMLY(1, 2, RNG_FORCE_RANDOM_SWITCH); GIVEN { PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } OPPONENT(SPECIES_WOBBUFFET); @@ -27,7 +27,7 @@ SINGLE_BATTLE_TEST("Red Card switches the attacker with a random non-fainted rep DOUBLE_BATTLE_TEST("Red Card switches the target with a random non-battler, non-fainted replacement") { - PASSES_RANDOMLY(1, 2); + PASSES_RANDOMLY(1, 2, RNG_FORCE_RANDOM_SWITCH); GIVEN { PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } PLAYER(SPECIES_WYNAUT); diff --git a/test/move.c b/test/move.c index d7e759f27..9ad336330 100644 --- a/test/move.c +++ b/test/move.c @@ -10,7 +10,7 @@ SINGLE_BATTLE_TEST("Accuracy controls the proportion of misses") PARAMETRIZE { move = MOVE_RAZOR_LEAF; } PARAMETRIZE { move = MOVE_SCRATCH; } ASSUME(0 < gBattleMoves[move].accuracy && gBattleMoves[move].accuracy <= 100); - PASSES_RANDOMLY(gBattleMoves[move].accuracy, 100); + PASSES_RANDOMLY(gBattleMoves[move].accuracy, 100, RNG_ACCURACY); GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); @@ -27,10 +27,9 @@ SINGLE_BATTLE_TEST("Secondary Effect Chance controls the proportion of secondary PARAMETRIZE { move = MOVE_THUNDER_SHOCK; } PARAMETRIZE { move = MOVE_DISCHARGE; } PARAMETRIZE { move = MOVE_NUZZLE; } - ASSUME(gBattleMoves[move].accuracy == 100); ASSUME(gBattleMoves[move].effect == EFFECT_PARALYZE_HIT); ASSUME(0 < gBattleMoves[move].secondaryEffectChance && gBattleMoves[move].secondaryEffectChance <= 100); - PASSES_RANDOMLY(gBattleMoves[move].secondaryEffectChance, 100); + PASSES_RANDOMLY(gBattleMoves[move].secondaryEffectChance, 100, RNG_SECONDARY_EFFECT); GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); @@ -70,6 +69,7 @@ SINGLE_BATTLE_TEST("Turn order is determined by speed if priority ties") SINGLE_BATTLE_TEST("Turn order is determined randomly if priority and speed tie") { + KNOWN_FAILING; // The algorithm is significantly biased. PASSES_RANDOMLY(1, 2); GIVEN { PLAYER(SPECIES_WOBBUFFET) { Speed(1); } @@ -85,15 +85,29 @@ SINGLE_BATTLE_TEST("Turn order is determined randomly if priority and speed tie" SINGLE_BATTLE_TEST("Critical hits occur at a 1/24 rate") { ASSUME(B_CRIT_CHANCE >= GEN_7); - ASSUME(gBattleMoves[MOVE_SCRATCH].accuracy == 100); - PASSES_RANDOMLY(100 / 24, 100); + PASSES_RANDOMLY(1, 24, RNG_CRITICAL_HIT); GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { TURN { MOVE(player, MOVE_SCRATCH); } } SCENE { - MESSAGE("It's a critical hit!"); + MESSAGE("A critical hit!"); + } +} + +SINGLE_BATTLE_TEST("Slash's critical hits occur at a 1/8 rate") +{ + ASSUME(B_CRIT_CHANCE >= GEN_7); + ASSUME(gBattleMoves[MOVE_SLASH].flags & FLAG_HIGH_CRIT); + PASSES_RANDOMLY(1, 8, RNG_CRITICAL_HIT); + GIVEN { + PLAYER(SPECIES_WOBBUFFET); + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_SLASH); } + } SCENE { + MESSAGE("A critical hit!"); } } diff --git a/test/move_effect_accuracy_down.c b/test/move_effect_accuracy_down.c index 2a90d8ea2..a6a79d8db 100644 --- a/test/move_effect_accuracy_down.c +++ b/test/move_effect_accuracy_down.c @@ -9,7 +9,7 @@ ASSUMPTIONS SINGLE_BATTLE_TEST("Sand Attack lowers Accuracy") { ASSUME(gBattleMoves[MOVE_SCRATCH].accuracy == 100); - PASSES_RANDOMLY(gBattleMoves[MOVE_SCRATCH].accuracy * 3 / 4, 100); + PASSES_RANDOMLY(gBattleMoves[MOVE_SCRATCH].accuracy * 3 / 4, 100, RNG_ACCURACY); GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); diff --git a/test/move_effect_assist.c b/test/move_effect_assist.c new file mode 100644 index 000000000..e95d84654 --- /dev/null +++ b/test/move_effect_assist.c @@ -0,0 +1,22 @@ +#include "global.h" +#include "test_battle.h" + +ASSUMPTIONS +{ + ASSUME(gBattleMoves[MOVE_ASSIST].effect == EFFECT_ASSIST); +} + +SINGLE_BATTLE_TEST("Assist fails if there are no valid moves to choose from") +{ + GIVEN { + PLAYER(SPECIES_WOBBUFFET) {Moves(MOVE_ASSIST, MOVE_CELEBRATE, MOVE_METRONOME, MOVE_ME_FIRST); } + PLAYER(SPECIES_WOBBUFFET) {Moves(MOVE_ASSIST, MOVE_ENDURE, MOVE_DRAGON_TAIL, MOVE_SPOTLIGHT); } + OPPONENT(SPECIES_WOBBUFFET); + } WHEN { + TURN { MOVE(player, MOVE_ASSIST); } + } SCENE { + MESSAGE("Wobbuffet used Assist!"); + NOT ANIMATION(ANIM_TYPE_MOVE, MOVE_ASSIST, player); + MESSAGE("But it failed!"); + } +} diff --git a/test/move_effect_defog.c b/test/move_effect_defog.c index d877899bb..e9e55d57f 100644 --- a/test/move_effect_defog.c +++ b/test/move_effect_defog.c @@ -327,7 +327,6 @@ DOUBLE_BATTLE_TEST("Defog lowers evasiveness by 1 and removes Aurora Veil from p DOUBLE_BATTLE_TEST("Defog lowers evasiveness by 1 and removes everything it can") { - bool32 defogTurn = FALSE; GIVEN { ASSUME(gBattleMoves[MOVE_HAIL].effect == EFFECT_HAIL); ASSUME(gSpeciesInfo[SPECIES_GLALIE].types[0] == TYPE_ICE); @@ -343,33 +342,29 @@ DOUBLE_BATTLE_TEST("Defog lowers evasiveness by 1 and removes everything it can" TURN { MOVE(playerLeft, MOVE_STICKY_WEB); MOVE(playerRight, MOVE_SPIKES); MOVE(opponentLeft, MOVE_STICKY_WEB); MOVE(opponentRight, MOVE_SPIKES); } TURN { SWITCH(playerLeft, 2); SWITCH(playerRight, 3); SWITCH(opponentLeft, 2); SWITCH(opponentRight, 3);} TURN { MOVE(playerLeft, MOVE_TOXIC_SPIKES); MOVE(playerRight, MOVE_STEALTH_ROCK); MOVE(opponentLeft, MOVE_TOXIC_SPIKES); MOVE(opponentRight, MOVE_STEALTH_ROCK); } - TURN { MOVE(playerLeft, MOVE_HAIL); MOVE(playerRight, MOVE_AURORA_VEIL); MOVE(opponentLeft, MOVE_AURORA_VEIL); MOVE(opponentRight, MOVE_STEALTH_ROCK); } - TURN { MOVE(playerLeft, MOVE_REFLECT); MOVE(playerRight, MOVE_LIGHT_SCREEN); MOVE(opponentLeft, MOVE_REFLECT); MOVE(opponentRight, MOVE_LIGHT_SCREEN); } - TURN { MOVE(playerLeft, MOVE_MIST); MOVE(playerRight, MOVE_SAFEGUARD); MOVE(opponentLeft, MOVE_MIST); MOVE(opponentRight, MOVE_SAFEGUARD); } - TURN { defogTurn = TRUE ; MOVE(opponentRight, MOVE_DEFOG, target:playerLeft);} + TURN { MOVE(playerLeft, MOVE_HAIL); MOVE(playerRight, MOVE_AURORA_VEIL); MOVE(opponentLeft, MOVE_AURORA_VEIL); MOVE(opponentRight, MOVE_LIGHT_SCREEN); } + TURN { MOVE(playerLeft, MOVE_REFLECT); MOVE(playerRight, MOVE_LIGHT_SCREEN); MOVE(opponentLeft, MOVE_REFLECT); MOVE(opponentRight, MOVE_SAFEGUARD); } + TURN { MOVE(playerLeft, MOVE_MIST); MOVE(playerRight, MOVE_SAFEGUARD); MOVE(opponentLeft, MOVE_MIST); MOVE(opponentRight, MOVE_DEFOG, target: playerLeft); } } SCENE { - if (defogTurn == TRUE) - { - MESSAGE("Foe Glalie used Defog!"); - MESSAGE("Glalie is protected by MIST!"); - ANIMATION(ANIM_TYPE_MOVE, MOVE_DEFOG, opponentRight); - // Player side - MESSAGE("Ally's Reflect wore off!"); - MESSAGE("Ally's Light Screen wore off!"); - MESSAGE("Ally's Mist wore off!"); - MESSAGE("Ally's Aurora Veil wore off!"); - MESSAGE("Ally's Safeguard wore off!"); + MESSAGE("Foe Glalie used Defog!"); + MESSAGE("Glalie is protected by MIST!"); + ANIMATION(ANIM_TYPE_MOVE, MOVE_DEFOG, opponentRight); + // Player side + MESSAGE("Ally's Reflect wore off!"); + MESSAGE("Ally's Light Screen wore off!"); + MESSAGE("Ally's Mist wore off!"); + MESSAGE("Ally's Aurora Veil wore off!"); + MESSAGE("Ally's Safeguard wore off!"); - MESSAGE("The spikes disappeared from the ground around your team!"); - MESSAGE("The pointed stones disappeared from around your team!"); - MESSAGE("The poison spikes disappeared from the ground around your team!"); - MESSAGE("The sticky web has disappeared from the ground around your team!"); + MESSAGE("The spikes disappeared from the ground around your team!"); + MESSAGE("The pointed stones disappeared from around your team!"); + MESSAGE("The poison spikes disappeared from the ground around your team!"); + MESSAGE("The sticky web has disappeared from the ground around your team!"); - // Opponent side - MESSAGE("The spikes disappeared from the ground around the opposing team!"); - MESSAGE("The pointed stones disappeared from around the opposing team!"); - MESSAGE("The poison spikes disappeared from the ground around the opposing team!"); - MESSAGE("The sticky web has disappeared from the ground around the opposing team!"); - } + // Opponent side + MESSAGE("The spikes disappeared from the ground around the opposing team!"); + MESSAGE("The pointed stones disappeared from around the opposing team!"); + MESSAGE("The poison spikes disappeared from the ground around the opposing team!"); + MESSAGE("The sticky web has disappeared from the ground around the opposing team!"); } } diff --git a/test/move_effect_evasion_up.c b/test/move_effect_evasion_up.c index d14d15334..4a4e99db7 100644 --- a/test/move_effect_evasion_up.c +++ b/test/move_effect_evasion_up.c @@ -9,7 +9,7 @@ ASSUMPTIONS SINGLE_BATTLE_TEST("Double Team raises Evasion") { ASSUME(gBattleMoves[MOVE_SCRATCH].accuracy == 100); - PASSES_RANDOMLY(gBattleMoves[MOVE_SCRATCH].accuracy * 3 / 4, 100); + PASSES_RANDOMLY(gBattleMoves[MOVE_SCRATCH].accuracy * 3 / 4, 100, RNG_ACCURACY); GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); diff --git a/test/move_effect_hit_switch_target.c b/test/move_effect_hit_switch_target.c index 9c50a4e4c..5af3062a4 100644 --- a/test/move_effect_hit_switch_target.c +++ b/test/move_effect_hit_switch_target.c @@ -9,8 +9,7 @@ ASSUMPTIONS SINGLE_BATTLE_TEST("Dragon Tail switches the target with a random non-fainted replacement") { - KNOWN_FAILING; // Only 18/50. Waiting for an improved PASSES_RANDOMLY. - PASSES_RANDOMLY(90 * 1, 100 * 2); + PASSES_RANDOMLY(1, 2, RNG_FORCE_RANDOM_SWITCH); GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); @@ -27,7 +26,7 @@ SINGLE_BATTLE_TEST("Dragon Tail switches the target with a random non-fainted re DOUBLE_BATTLE_TEST("Dragon Tail switches the target with a random non-battler, non-fainted replacement") { - PASSES_RANDOMLY(90 * 1, 100 * 2); + PASSES_RANDOMLY(1, 2, RNG_FORCE_RANDOM_SWITCH); GIVEN { PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WYNAUT); diff --git a/test/move_effect_rampage.c b/test/move_effect_rampage.c index a3afebf6b..aa4a002fb 100644 --- a/test/move_effect_rampage.c +++ b/test/move_effect_rampage.c @@ -8,7 +8,7 @@ ASSUMPTIONS SINGLE_BATTLE_TEST("Thrash lasts for 2 or 3 turns") { - PASSES_RANDOMLY(1, 2); + PASSES_RANDOMLY(1, 2, RNG_RAMPAGE_TURNS); GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); @@ -26,7 +26,6 @@ SINGLE_BATTLE_TEST("Thrash lasts for 2 or 3 turns") SINGLE_BATTLE_TEST("Thrash confuses the user after it finishes") { GIVEN { - RNGSeed(0x00000000); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -45,7 +44,6 @@ SINGLE_BATTLE_TEST("Thrash does not confuse the user if it is canceled on turn 1 { GIVEN { ASSUME(B_RAMPAGE_CANCELLING >= GEN_5); - RNGSeed(0x00000000); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -61,7 +59,6 @@ SINGLE_BATTLE_TEST("Thrash does not confuse the user if it is canceled on turn 2 { GIVEN { ASSUME(B_RAMPAGE_CANCELLING >= GEN_5); - RNGSeed(0x00000000); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { @@ -78,7 +75,6 @@ SINGLE_BATTLE_TEST("Thrash confuses the user if it is canceled on turn 3 of 3") KNOWN_FAILING; GIVEN { ASSUME(B_RAMPAGE_CANCELLING >= GEN_5); - RNGSeed(0x00000000); PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { diff --git a/test/move_effect_roar.c b/test/move_effect_roar.c index 2d4eadda8..99256b298 100644 --- a/test/move_effect_roar.c +++ b/test/move_effect_roar.c @@ -8,7 +8,7 @@ ASSUMPTIONS SINGLE_BATTLE_TEST("Roar switches the target with a random non-fainted replacement") { - PASSES_RANDOMLY(1, 2); + PASSES_RANDOMLY(1, 2, RNG_FORCE_RANDOM_SWITCH); GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); @@ -25,7 +25,7 @@ SINGLE_BATTLE_TEST("Roar switches the target with a random non-fainted replaceme DOUBLE_BATTLE_TEST("Roar switches the target with a random non-battler, non-fainted replacement") { - PASSES_RANDOMLY(1, 2); + PASSES_RANDOMLY(1, 2, RNG_FORCE_RANDOM_SWITCH); GIVEN { PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WYNAUT); diff --git a/test/move_effect_sleep.c b/test/move_effect_sleep.c index c80faf4bd..c34e1248e 100644 --- a/test/move_effect_sleep.c +++ b/test/move_effect_sleep.c @@ -6,16 +6,33 @@ ASSUMPTIONS ASSUME(gBattleMoves[MOVE_HYPNOSIS].effect == EFFECT_SLEEP); } -SINGLE_BATTLE_TEST("Hypnosis inflicts sleep") +SINGLE_BATTLE_TEST("Hypnosis inflicts 1-3 turns of sleep") { + u32 turns, count; + ASSUME(B_SLEEP_TURNS >= GEN_5); + PARAMETRIZE { turns = 1; } + PARAMETRIZE { turns = 2; } + PARAMETRIZE { turns = 3; } + PASSES_RANDOMLY(1, 3, RNG_SLEEP_TURNS); GIVEN { PLAYER(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_HYPNOSIS); } + TURN { MOVE(player, MOVE_HYPNOSIS); MOVE(opponent, MOVE_CELEBRATE); } + for (count = 0; count < turns; ++count) + TURN {} } SCENE { ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPNOSIS, player); ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + MESSAGE("Foe Wobbuffet fell asleep!"); STATUS_ICON(opponent, sleep: TRUE); + for (count = 0; count < turns; ++count) + { + if (count < turns - 1) + MESSAGE("Foe Wobbuffet is fast asleep."); + ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); + } + MESSAGE("Foe Wobbuffet woke up!"); + STATUS_ICON(opponent, none: TRUE); } } diff --git a/test/random.c b/test/random.c new file mode 100644 index 000000000..abaf4a4f8 --- /dev/null +++ b/test/random.c @@ -0,0 +1,81 @@ +#include "global.h" +#include "test.h" +#include "random.h" + +TEST("RandomUniform generates lo..hi") +{ + u32 lo, hi, i; + PARAMETRIZE { lo = 0; hi = 1; } + PARAMETRIZE { lo = 0; hi = 2; } + PARAMETRIZE { lo = 0; hi = 3; } + PARAMETRIZE { lo = 2; hi = 4; } + for (i = 0; i < 1024; i++) + { + u32 r = RandomUniformDefault(RNG_NONE, lo, hi); + EXPECT(lo <= r && r <= hi); + } +} + +TEST("RandomWeighted generates 0..n-1") +{ + u32 n, sum, i; + static const u8 ws[8] = { 1, 1, 1, 1, 1, 1, 1, 1 }; + PARAMETRIZE { n = 1; } + PARAMETRIZE { n = 2; } + PARAMETRIZE { n = 3; } + PARAMETRIZE { n = 4; } + ASSUME(n <= ARRAY_COUNT(ws)); + for (i = 0, sum = 0; i < n; i++) + sum += ws[i]; + for (i = 0; i < 1024; i++) + { + u32 r = RandomWeightedArrayDefault(RNG_NONE, sum, n, ws); + EXPECT(0 <= r && r < n); + } +} + +TEST("RandomUniform generates uniform distribution") +{ + u32 i, error; + u16 distribution[4]; + + memset(distribution, 0, sizeof(distribution)); + for (i = 0; i < 4096; i++) + { + u32 r = RandomUniformDefault(RNG_NONE, 0, ARRAY_COUNT(distribution)); + EXPECT(0 <= r && r < ARRAY_COUNT(distribution)); + distribution[r]++; + } + + error = 0; + for (i = 0; i < ARRAY_COUNT(distribution); i++) + error += abs(UQ_4_12(0.25) - distribution[i]); + + EXPECT_LT(error, UQ_4_12(0.025)); +} + +TEST("RandomWeighted generates distribution in proportion to the weights") +{ + u32 i, sum, error; + static const u8 ws[4] = { 1, 2, 2, 3 }; + u16 distribution[ARRAY_COUNT(ws)]; + + for (i = 0, sum = 0; i < ARRAY_COUNT(ws); i++) + sum += ws[i]; + + memset(distribution, 0, sizeof(distribution)); + for (i = 0; i < 4096; i++) + { + u32 r = RandomWeightedArrayDefault(RNG_NONE, sum, ARRAY_COUNT(ws), ws); + EXPECT(0 <= r && r < ARRAY_COUNT(ws)); + distribution[r]++; + } + + error = 0; + error += abs(UQ_4_12(0.125) - distribution[0]); + error += abs(UQ_4_12(0.250) - distribution[1]); + error += abs(UQ_4_12(0.250) - distribution[2]); + error += abs(UQ_4_12(0.375) - distribution[3]); + + EXPECT_LT(error, UQ_4_12(0.025)); +} diff --git a/test/status1.c b/test/status1.c index 63a9cd041..f4d3c7d94 100644 --- a/test/status1.c +++ b/test/status1.c @@ -72,7 +72,7 @@ SINGLE_BATTLE_TEST("Burn reduces attack by 50%", s16 damage) SINGLE_BATTLE_TEST("Freeze has a 20% chance of being thawed") { - PASSES_RANDOMLY(20, 100); + PASSES_RANDOMLY(20, 100, RNG_FROZEN); GIVEN { PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); } OPPONENT(SPECIES_WOBBUFFET); @@ -90,9 +90,8 @@ SINGLE_BATTLE_TEST("Freeze is thawed by opponent's Fire-type attacks") PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); } OPPONENT(SPECIES_WOBBUFFET); } WHEN { - TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_EMBER); } + TURN { MOVE(opponent, MOVE_EMBER); MOVE(player, MOVE_CELEBRATE); } } SCENE { - MESSAGE("Wobbuffet is frozen solid!"); MESSAGE("Foe Wobbuffet used Ember!"); MESSAGE("Wobbuffet was defrosted!"); STATUS_ICON(player, none: TRUE); @@ -145,7 +144,7 @@ SINGLE_BATTLE_TEST("Paralysis reduces speed by 50%") SINGLE_BATTLE_TEST("Paralysis has a 25% chance of skipping the turn") { - PASSES_RANDOMLY(25, 100); + PASSES_RANDOMLY(25, 100, RNG_PARALYSIS); GIVEN { PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_PARALYSIS); } OPPONENT(SPECIES_WOBBUFFET); diff --git a/test/test.h b/test/test.h index a05e42a04..dfa1f12b1 100644 --- a/test/test.h +++ b/test/test.h @@ -54,6 +54,16 @@ extern const u8 gTestRunnerI; extern const char gTestRunnerArgv[256]; extern const struct TestRunner gAssumptionsRunner; + +struct FunctionTestRunnerState +{ + u8 parameters; + u8 runParameter; +}; + +extern const struct TestRunner gFunctionTestRunner; +extern struct FunctionTestRunnerState *gFunctionTestRunnerState; + extern struct TestRunnerState gTestRunnerState; void CB2_TestRunner(void); @@ -63,6 +73,17 @@ void Test_ExitWithResult(enum TestResult, const char *fmt, ...); s32 MgbaPrintf_(const char *fmt, ...); +#define TEST(_name) \ + static void CAT(Test, __LINE__)(void); \ + __attribute__((section(".tests"))) static const struct Test CAT(sTest, __LINE__) = \ + { \ + .name = _name, \ + .filename = __FILE__, \ + .runner = &gFunctionTestRunner, \ + .data = (void *)CAT(Test, __LINE__), \ + }; \ + static void CAT(Test, __LINE__)(void) + #define ASSUMPTIONS \ static void Assumptions(void); \ __attribute__((section(".tests"))) static const struct Test sAssumptions = \ @@ -139,6 +160,8 @@ s32 MgbaPrintf_(const char *fmt, ...); #define KNOWN_FAILING \ Test_ExpectedResult(TEST_RESULT_FAIL) +#define PARAMETRIZE if (gFunctionTestRunnerState->parameters++ == gFunctionTestRunnerState->runParameter) + #define TO_DO \ Test_ExpectedResult(TEST_RESULT_TODO) diff --git a/test/test_battle.h b/test/test_battle.h index 1e97229bd..0e0464254 100644 --- a/test/test_battle.h +++ b/test/test_battle.h @@ -65,8 +65,9 @@ * single turn. MOVE causes the player to use Stun Spore and adds the * move to the Pokémon's moveset if an explicit Moves was not specified. * Pokémon that are not mentioned in a TURN use Celebrate. - * The test runner attempts to rig the RNG so that the first move used - * in a turn does not miss and activates its secondary effects (if any). + * The test runner rigs the RNG so that unless otherwise specified, + * moves always hit, never critical hit, always activate their secondary + * effects, and always roll the same damage modifier. * * SCENE describes the player-visible output of the battle. In this case * ANIMATION checks that the Stun Spore animation played, MESSAGE checks @@ -228,12 +229,35 @@ * } * } * - * PASSES_RANDOMLY(successes, trials) - * Checks that the test passes approximately successes/trials. Used for - * testing RNG-based attacks, e.g.: + * PASSES_RANDOMLY(successes, trials, [tag]) + * Checks that the test passes successes/trials. If tag is provided, the + * test is run for each value that the tag can produce. For example, to + * check that Paralysis causes the turn to be skipped 25/100 times, we + * can write the following test that passes only if the Pokémon is fully + * paralyzed and specify that we expect it to pass 25/100 times when + * RNG_PARALYSIS varies: + * SINGLE_BATTLE_TEST("Paralysis has a 25% chance of skipping the turn") + * { + * PASSES_RANDOMLY(25, 100, RNG_PARALYSIS); + * GIVEN { + * PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_PARALYSIS); } + * OPPONENT(SPECIES_WOBBUFFET); + * } WHEN { + * TURN { MOVE(player, MOVE_CELEBRATE); } + * } SCENE { + * MESSAGE("Wobbuffet is paralyzed! It can't move!"); + * } + * } + * All BattleRandom calls involving tag will return the same number, so + * this cannot be used to have two moves independently hit or miss, for + * example. + * + * If the tag is not provided, runs the test 50 times and computes an + * approximate pass ratio. * PASSES_RANDOMLY(gBattleMoves[move].accuracy, 100); - * Note that PASSES_RANDOMLY makes the tests run very slowly and should - * be avoided where possible. + * Note that this mode of PASSES_RANDOMLY makes the tests run very + * slowly and should be avoided where possible. If the mechanic you are + * testing is missing its tag, you should add it. * * GIVEN * Contains the initial state of the parties before the battle. @@ -419,6 +443,7 @@ #include "battle_anim.h" #include "data.h" #include "item.h" +#include "random.h" #include "recorded_battle.h" #include "test.h" #include "util.h" @@ -433,6 +458,7 @@ // NOTE: If the stack is too small the test runner will probably crash // or loop. #define BATTLE_TEST_STACK_SIZE 1024 +#define MAX_TURNS 16 #define MAX_QUEUED_EVENTS 25 enum { BATTLE_TEST_SINGLES, BATTLE_TEST_DOUBLES }; @@ -512,6 +538,13 @@ struct QueuedEvent } as; }; +struct BattlerTurn +{ + u8 hit:2; + u8 criticalHit:2; + u8 secondaryEffect:2; +}; + struct BattleTestData { u8 stack[BATTLE_TEST_STACK_SIZE]; @@ -533,14 +566,13 @@ struct BattleTestData u8 turns; u8 actionBattlers; u8 moveBattlers; - bool8 hasRNGActions:1; struct RecordedBattleSave recordedBattle; u8 battleRecordTypes[MAX_BATTLERS_COUNT][BATTLER_RECORD_SIZE]; u8 battleRecordSourceLineOffsets[MAX_BATTLERS_COUNT][BATTLER_RECORD_SIZE]; u16 recordIndexes[MAX_BATTLERS_COUNT]; + struct BattlerTurn battleRecordTurns[MAX_TURNS][MAX_BATTLERS_COUNT]; u8 lastActionTurn; - u8 nextRNGTurn; u8 queuedEventsCount; u8 queueGroupType; @@ -555,11 +587,12 @@ struct BattleTestRunnerState u8 parametersCount; // Valid only in BattleTest_Setup. u8 parameters; u8 runParameter; + u16 rngTag; u8 trials; - u8 expectedPasses; - u8 observedPasses; - u8 skippedTrials; u8 runTrial; + u16 expectedRatio; + u16 observedRatio; + u16 trialRatio; bool8 runRandomly:1; bool8 runGiven:1; bool8 runWhen:1; @@ -648,13 +681,20 @@ extern struct BattleTestRunnerState *gBattleTestRunnerState; /* Parametrize */ +#undef PARAMETRIZE // Override test/test.h's implementation. + #define PARAMETRIZE if (gBattleTestRunnerState->parametersCount++ == i) /* Randomly */ -#define PASSES_RANDOMLY(passes, trials) for (; gBattleTestRunnerState->runRandomly; gBattleTestRunnerState->runRandomly = FALSE) Randomly(__LINE__, passes, trials) +#define PASSES_RANDOMLY(passes, trials, ...) for (; gBattleTestRunnerState->runRandomly; gBattleTestRunnerState->runRandomly = FALSE) Randomly(__LINE__, passes, trials, (struct RandomlyContext) { __VA_ARGS__ }) -void Randomly(u32 sourceLine, u32 passes, u32 trials); +struct RandomlyContext +{ + u16 tag; +}; + +void Randomly(u32 sourceLine, u32 passes, u32 trials, struct RandomlyContext); /* Given */ @@ -728,6 +768,8 @@ struct MoveContext u16 explicitHit:1; u16 criticalHit:1; u16 explicitCriticalHit:1; + u16 secondaryEffect:1; + u16 explicitSecondaryEffect:1; u16 megaEvolve:1; u16 explicitMegaEvolve:1; // TODO: u8 zMove:1; diff --git a/test/test_runner.c b/test/test_runner.c index 1d3f14aad..ef9f2da73 100644 --- a/test/test_runner.c +++ b/test/test_runner.c @@ -4,6 +4,7 @@ #include "gpu_regs.h" #include "main.h" #include "malloc.h" +#include "random.h" #include "test.h" #include "test_runner.h" @@ -12,6 +13,7 @@ void CB2_TestRunner(void); EWRAM_DATA struct TestRunnerState gTestRunnerState; +EWRAM_DATA struct FunctionTestRunnerState *gFunctionTestRunnerState; void TestRunner_Battle(const struct Test *); @@ -236,6 +238,38 @@ void Test_ExpectedResult(enum TestResult result) gTestRunnerState.expectedResult = result; } +static void FunctionTest_SetUp(void *data) +{ + (void)data; + gFunctionTestRunnerState = AllocZeroed(sizeof(*gFunctionTestRunnerState)); + SeedRng(0); +} + +static void FunctionTest_Run(void *data) +{ + void (*function)(void) = data; + do + { + if (gFunctionTestRunnerState->parameters) + MgbaPrintf_(":N%s %d/%d", gTestRunnerState.test->name, gFunctionTestRunnerState->runParameter + 1, gFunctionTestRunnerState->parameters); + gFunctionTestRunnerState->parameters = 0; + function(); + } while (++gFunctionTestRunnerState->runParameter < gFunctionTestRunnerState->parameters); +} + +static void FunctionTest_TearDown(void *data) +{ + (void)data; + FREE_AND_SET_NULL(gFunctionTestRunnerState); +} + +const struct TestRunner gFunctionTestRunner = +{ + .setUp = FunctionTest_SetUp, + .run = FunctionTest_Run, + .tearDown = FunctionTest_TearDown, +}; + static void Assumptions_Run(void *data) { void (*function)(void) = data; @@ -294,11 +328,12 @@ static void Intr_Timer2(void) void Test_ExitWithResult(enum TestResult result, const char *fmt, ...) { + bool32 handled = FALSE; gTestRunnerState.result = result; ReinitCallbacks(); - if (gTestRunnerState.test->runner->handleExitWithResult - && !gTestRunnerState.test->runner->handleExitWithResult(gTestRunnerState.test->data, result) - && gTestRunnerState.result != gTestRunnerState.expectedResult) + if (gTestRunnerState.test->runner->handleExitWithResult) + handled = gTestRunnerState.test->runner->handleExitWithResult(gTestRunnerState.test->data, result); + if (!handled && gTestRunnerState.result != gTestRunnerState.expectedResult) { va_list va; va_start(va, fmt); diff --git a/test/test_runner_battle.c b/test/test_runner_battle.c index ec7ba275c..d869bf2e5 100644 --- a/test/test_runner_battle.c +++ b/test/test_runner_battle.c @@ -15,26 +15,10 @@ #define STATE gBattleTestRunnerState #define DATA gBattleTestRunnerState->data -/* RNG seeds for controlling the first move of the turn. - * Found via brute force. */ +#define RNG_SEED_DEFAULT 0x00000000 -/* Default seed, triggers most things. - * The 1st roll % 100 is <= 29, to make 30%+ accuracycheck pass. - * The 2nd roll is not a critical hit at the regular crit stage. - * The 3rd roll is consumed by damagecalc. - * The 4th roll is consumed by adjustdamage. - * The 5th roll % 100 is <= 9, to make 10%+ seteffectwithchance pass - * and % 3 is == 0, to make Poison Point and other 1/3s pass. */ -#define RNG_SEED_DEFAULT 0x000002BE - -/* Causes the first attack to critical hit if B_CRIT_CHANCE >= GEN_6. - * The 2nd roll % 24 == 0 to be a critical hit at any stage. - * The other rolls match RNG_SEED_DEFAULT. */ -#define RNG_SEED_CRITICAL_HIT 0x0000A9F4 - -/* Causes the first attack to miss if possible. - * The 1st roll % 100 is 99, to make 99%- accuracycheck fail. */ -#define RNG_SEED_MISS 0x00000074 +#undef Q_4_12 +#define Q_4_12(n) (s32)((n) * 4096) EWRAM_DATA struct BattleTestRunnerState *gBattleTestRunnerState = NULL; @@ -129,12 +113,13 @@ static u32 BattleTest_EstimateCost(void *data) if (!STATE) return 0; STATE->runRandomly = TRUE; - DATA.recordedBattle.rngSeed = RNG_SEED_DEFAULT; InvokeTestFunction(test); cost = 1; if (STATE->parametersCount != 0) cost *= STATE->parametersCount; - if (STATE->trials != 0) + if (STATE->trials == 1) + cost *= 3; + else if (STATE->trials > 1) cost *= STATE->trials; FREE_AND_SET_NULL(STATE); return cost; @@ -162,6 +147,28 @@ static void BattleTest_SetUp(void *data) } } +static void PrintTestName(void) +{ + if (STATE->trials && STATE->parameters) + { + if (STATE->trials == 1) + MgbaPrintf_(":N%s %d/%d (%d/?)", gTestRunnerState.test->name, STATE->runParameter + 1, STATE->parameters, STATE->runTrial + 1); + else + MgbaPrintf_(":N%s %d/%d (%d/%d)", gTestRunnerState.test->name, STATE->runParameter + 1, STATE->parameters, STATE->runTrial + 1, STATE->trials); + } + else if (STATE->trials) + { + if (STATE->trials == 1) + MgbaPrintf_(":N%s (%d/?)", gTestRunnerState.test->name, STATE->runTrial + 1); + else + MgbaPrintf_(":N%s (%d/%d)", gTestRunnerState.test->name, STATE->runTrial + 1, STATE->trials); + } + else if (STATE->parameters) + { + MgbaPrintf_(":N%s %d/%d", gTestRunnerState.test->name, STATE->runParameter + 1, STATE->parameters); + } +} + // This does not take into account priority, statuses, or any other // modifiers. static void SetImplicitSpeeds(void) @@ -280,12 +287,82 @@ static void BattleTest_Run(void *data) STATE->checkProgressTrial = 0; STATE->checkProgressTurn = 0; - if (STATE->trials && STATE->parameters) - MgbaPrintf_(":N%s %d/%d (%d/%d)", gTestRunnerState.test->name, STATE->runParameter + 1, STATE->parameters, STATE->runTrial + 1, STATE->trials); - else if (STATE->trials) - MgbaPrintf_(":N%s (%d/%d)", gTestRunnerState.test->name, STATE->runTrial + 1, STATE->trials); - else if (STATE->parameters) - MgbaPrintf_(":N%s %d/%d", gTestRunnerState.test->name, STATE->runParameter + 1, STATE->parameters); + PrintTestName(); +} + +u32 RandomUniform(enum RandomTag tag, u32 lo, u32 hi) +{ + if (tag == STATE->rngTag) + { + u32 n = hi - lo + 1; + if (STATE->trials == 1) + { + STATE->trials = n; + PrintTestName(); + } + else if (STATE->trials != n) + { + Test_ExitWithResult(TEST_RESULT_ERROR, "RandomUniform called with inconsistent trials %d and %d", STATE->trials, n); + } + STATE->trialRatio = Q_4_12(1) / n; + return STATE->runTrial + lo; + } + + return hi; +} + +u32 RandomWeightedArray(enum RandomTag tag, u32 sum, u32 n, const u8 *weights) +{ + const struct BattlerTurn *turn = NULL; + u32 default_ = n-1; + + if (gCurrentTurnActionNumber < gBattlersCount) + { + u32 battlerId = gBattlerByTurnOrder[gCurrentTurnActionNumber]; + turn = &DATA.battleRecordTurns[gBattleResults.battleTurnCounter][battlerId]; + } + + switch (tag) + { + case RNG_ACCURACY: + ASSUME(n == 2); + if (turn && turn->hit) + return turn->hit - 1; + default_ = TRUE; + break; + + case RNG_CRITICAL_HIT: + ASSUME(n == 2); + if (turn && turn->criticalHit) + return turn->criticalHit - 1; + default_ = FALSE; + break; + + case RNG_SECONDARY_EFFECT: + ASSUME(n == 2); + if (turn && turn->secondaryEffect) + return turn->secondaryEffect - 1; + default_ = TRUE; + break; + } + + if (tag == STATE->rngTag) + { + if (STATE->trials == 1) + { + STATE->trials = n; + PrintTestName(); + } + else if (STATE->trials != n) + { + Test_ExitWithResult(TEST_RESULT_ERROR, "RandomWeighted called with inconsistent trials %d and %d", STATE->trials, n); + } + // TODO: Detect inconsistent sum. + STATE->trialRatio = Q_4_12(weights[STATE->runTrial]) / sum; + return STATE->runTrial; + } + + return default_; } static s32 TryAbilityPopUp(s32 i, s32 n, u32 battlerId, u32 ability) @@ -711,42 +788,36 @@ static void CB2_BattleTest_NextTrial(void) SetMainCallback2(CB2_BattleTest_NextParameter); + switch (gTestRunnerState.result) + { + case TEST_RESULT_FAIL: + break; + case TEST_RESULT_PASS: + STATE->observedRatio += STATE->trialRatio; + break; + default: + return; + } + if (STATE->rngTag) + STATE->trialRatio = 0; + if (++STATE->runTrial < STATE->trials) { - switch (gTestRunnerState.result) - { - case TEST_RESULT_FAIL: - break; - case TEST_RESULT_PASS: - STATE->observedPasses++; - break; - case TEST_RESULT_ASSUMPTION_FAIL: - STATE->skippedTrials++; - if (STATE->skippedTrials > STATE->trials / 4) - Test_ExitWithResult(TEST_RESULT_INVALID, "25%% of the trials were SKIPed"); - break; - default: - return; - } - if (STATE->parameters) - MgbaPrintf_(":N%s %d/%d (%d/%d)", gTestRunnerState.test->name, STATE->runParameter + 1, STATE->parameters, STATE->runTrial + 1, STATE->trials); - else - MgbaPrintf_(":N%s (%d/%d)", gTestRunnerState.test->name, STATE->runTrial + 1, STATE->trials); - gTestRunnerState.result = TEST_RESULT_PASS; + PrintTestName(); + gTestRunnerState.result = TEST_RESULT_PASS; DATA.recordedBattle.rngSeed = ISO_RANDOMIZE1(STATE->runTrial); DATA.queuedEvent = 0; DATA.lastActionTurn = 0; - DATA.nextRNGTurn = 0; SetVariablesForRecordedBattle(&DATA.recordedBattle); SetMainCallback2(CB2_InitBattle); } else { - // This is a tolerance of +/- 4%. - if (abs(STATE->observedPasses - STATE->expectedPasses) <= 2) + // This is a tolerance of +/- ~2%. + if (abs(STATE->observedRatio - STATE->expectedRatio) <= Q_4_12(0.02)) gTestRunnerState.result = TEST_RESULT_PASS; else - Test_ExitWithResult(TEST_RESULT_FAIL, "Expected %d/%d passes, observed %d/%d", STATE->expectedPasses, STATE->trials, STATE->observedPasses, STATE->trials); + Test_ExitWithResult(TEST_RESULT_FAIL, "Expected %q passes/successes, observed %q", STATE->expectedRatio, STATE->observedRatio); } } @@ -773,7 +844,8 @@ static bool32 BattleTest_CheckProgress(void *data) static bool32 BattleTest_HandleExitWithResult(void *data, enum TestResult result) { - if (result != TEST_RESULT_INVALID + if (result != TEST_RESULT_ASSUMPTION_FAIL + && result != TEST_RESULT_INVALID && result != TEST_RESULT_ERROR && result != TEST_RESULT_TIMEOUT && STATE->runTrial < STATE->trials) @@ -787,16 +859,25 @@ static bool32 BattleTest_HandleExitWithResult(void *data, enum TestResult result } } -void Randomly(u32 sourceLine, u32 passes, u32 trials) +void Randomly(u32 sourceLine, u32 passes, u32 trials, struct RandomlyContext ctx) { - INVALID_IF(DATA.recordedBattle.rngSeed != RNG_SEED_DEFAULT, "RNG seed already set"); - // This is a precision of 2%. - STATE->trials = 50; - STATE->expectedPasses = STATE->trials * passes / trials; - STATE->observedPasses = 0; - STATE->skippedTrials = 0; + INVALID_IF(passes > trials, "%d passes specified, but only %d trials", passes, trials); + STATE->rngTag = ctx.tag; STATE->runTrial = 0; - DATA.recordedBattle.rngSeed = 0; + STATE->expectedRatio = Q_4_12(passes) / trials; + STATE->observedRatio = 0; + if (STATE->rngTag) + { + STATE->trials = 1; + STATE->trialRatio = Q_4_12(1); + } + else + { + INVALID_IF(DATA.recordedBattle.rngSeed != RNG_SEED_DEFAULT, "RNG seed already set"); + STATE->trials = 50; + STATE->trialRatio = Q_4_12(1) / STATE->trials; + DATA.recordedBattle.rngSeed = 0; + } } void RNGSeed_(u32 sourceLine, u32 seed) @@ -1025,6 +1106,31 @@ void Status1_(u32 sourceLine, u32 status1) SetMonData(DATA.currentMon, MON_DATA_STATUS, &status1); } +static const char *const sBattlerIdentifiersSingles[] = +{ + "player", + "opponent", +}; + +static const char *const sBattlerIdentifiersDoubles[] = +{ + "playerLeft", + "opponentLeft", + "playerRight", + "opponentRight", +}; + +static const char *BattlerIdentifier(s32 battlerId) +{ + const struct BattleTest *test = gTestRunnerState.test->data; + switch (test->type) + { + case BATTLE_TEST_SINGLES: return sBattlerIdentifiersSingles[battlerId]; + case BATTLE_TEST_DOUBLES: return sBattlerIdentifiersDoubles[battlerId]; + } + return ""; +} + static void PushBattlerAction(u32 sourceLine, s32 battlerId, u32 actionType, u32 byte) { u32 recordIndex = DATA.recordIndexes[battlerId]++; @@ -1037,16 +1143,6 @@ static void PushBattlerAction(u32 sourceLine, s32 battlerId, u32 actionType, u32 void BattleTest_CheckBattleRecordActionType(u32 battlerId, u32 recordIndex, u32 actionType) { - // TODO: Support explicit seeds for each turn? - if (DATA.nextRNGTurn == gBattleResults.battleTurnCounter - && (DATA.recordedBattle.rngSeed == RNG_SEED_DEFAULT - || DATA.recordedBattle.rngSeed == RNG_SEED_CRITICAL_HIT - || DATA.recordedBattle.rngSeed == RNG_SEED_MISS)) - { - gRngValue = DATA.recordedBattle.rngSeed; - DATA.nextRNGTurn++; - } - // An illegal move choice will cause the battle to request a new // move slot and target. This detects the move slot. if (actionType == RECORDED_MOVE_SLOT @@ -1122,10 +1218,11 @@ void BattleTest_CheckBattleRecordActionType(u32 battlerId, u32 recordIndex, u32 void OpenTurn(u32 sourceLine) { INVALID_IF(DATA.turnState != TURN_CLOSED, "Nested TURN"); + if (DATA.turns == MAX_TURNS) + Test_ExitWithResult(TEST_RESULT_ERROR, "%s:%d: TURN exceeds MAX_TURNS", gTestRunnerState.test->filename, sourceLine); DATA.turnState = TURN_OPEN; DATA.actionBattlers = 0x00; DATA.moveBattlers = 0x00; - DATA.hasRNGActions = FALSE; } static void SetSlowerThan(s32 battlerId) @@ -1195,7 +1292,6 @@ void Move(u32 sourceLine, struct BattlePokemon *battler, struct MoveContext ctx) else if (moveId == MOVE_NONE) { INVALID_IF(DATA.explicitMoves[battlerId & BIT_SIDE] & (1 << DATA.currentMonIndexes[battlerId]), "Missing explicit %S", gMoveNames[ctx.move]); - INVALID_IF(i == MAX_MON_MOVES, "Too many different moves"); SetMonData(mon, MON_DATA_MOVE1 + i, &ctx.move); SetMonData(DATA.currentMon, MON_DATA_PP1 + i, &gBattleMoves[ctx.move].pp); moveSlot = i; @@ -1203,6 +1299,7 @@ void Move(u32 sourceLine, struct BattlePokemon *battler, struct MoveContext ctx) break; } } + INVALID_IF(i == MAX_MON_MOVES, "Too many different moves for %s", BattlerIdentifier(battlerId)); } else if (ctx.explicitMoveSlot) { @@ -1227,6 +1324,7 @@ void Move(u32 sourceLine, struct BattlePokemon *battler, struct MoveContext ctx) const struct BattleMove *move = &gBattleMoves[moveId]; if (move->target == MOVE_TARGET_RANDOM || move->target == MOVE_TARGET_BOTH + || move->target == MOVE_TARGET_DEPENDS || move->target == MOVE_TARGET_FOES_AND_ALLY || move->target == MOVE_TARGET_OPPONENTS_FIELD || move->target == MOVE_TARGET_ALL_BATTLERS) @@ -1253,21 +1351,12 @@ void Move(u32 sourceLine, struct BattlePokemon *battler, struct MoveContext ctx) } } - if (ctx.explicitHit && !ctx.hit) - { - if (DATA.hasRNGActions != 0) - Test_ExitWithResult(TEST_RESULT_ERROR, "%s:%d: hit only supported on the first move", gTestRunnerState.test->filename, sourceLine); - INVALID_IF(DATA.recordedBattle.rngSeed != RNG_SEED_DEFAULT, "RNG seed already set"); - DATA.recordedBattle.rngSeed = RNG_SEED_MISS; - } - - if (ctx.explicitCriticalHit && ctx.criticalHit) - { - if (DATA.hasRNGActions != 0) - Test_ExitWithResult(TEST_RESULT_ERROR, "%s:%d: criticalHit only supported on the first move", gTestRunnerState.test->filename, sourceLine); - INVALID_IF(DATA.recordedBattle.rngSeed != RNG_SEED_DEFAULT, "RNG seed already set"); - DATA.recordedBattle.rngSeed = RNG_SEED_CRITICAL_HIT; - } + if (ctx.explicitHit) + DATA.battleRecordTurns[DATA.turns][battlerId].hit = 1 + ctx.hit; + if (ctx.explicitCriticalHit) + DATA.battleRecordTurns[DATA.turns][battlerId].criticalHit = 1 + ctx.criticalHit; + if (ctx.explicitSecondaryEffect) + DATA.battleRecordTurns[DATA.turns][battlerId].secondaryEffect = 1 + ctx.secondaryEffect; if (!(DATA.actionBattlers & (1 << battlerId))) { @@ -1288,14 +1377,6 @@ void Move(u32 sourceLine, struct BattlePokemon *battler, struct MoveContext ctx) DATA.actionBattlers |= 1 << battlerId; DATA.moveBattlers |= 1 << battlerId; } - - // WARNING: Approximation. The move could still cause the RNG to - // advance. - if (gBattleMoves[moveId].accuracy != 0 - || gBattleMoves[moveId].split != SPLIT_STATUS) - { - DATA.hasRNGActions = TRUE; - } } void ForcedMove(u32 sourceLine, struct BattlePokemon *battler) diff --git a/test/trainer_control.c b/test/trainer_control.c new file mode 100644 index 000000000..307942207 --- /dev/null +++ b/test/trainer_control.c @@ -0,0 +1,140 @@ +#include "global.h" +#include "test.h" +#include "battle.h" +#include "battle_main.h" +#include "data.h" +#include "malloc.h" +#include "string_util.h" +#include "constants/item.h" +#include "constants/abilities.h" +#include "constants/trainers.h" +#include "constants/battle.h" + + +static const struct TrainerMonCustomized sTestParty1[] = +{ + { + .species = SPECIES_WOBBUFFET, + .ball = ITEM_MASTER_BALL, + .ability = ABILITY_TELEPATHY, + .friendship = 42, + .gender = TRAINER_MON_FEMALE, + .heldItem = ITEM_ASSAULT_VEST, + .isShiny = TRUE, + .iv = TRAINER_PARTY_IVS(25,26,27,28,29,30), + .ev = TRAINER_PARTY_EVS(252, 0, 0, 252, 4, 0), + .lvl = 67, + .moves = {MOVE_AIR_SLASH, MOVE_BARRIER, MOVE_SOLAR_BEAM, MOVE_EXPLOSION}, + .nature = TRAINER_PARTY_NATURE(NATURE_HASTY), + .nickname = COMPOUND_STRING("Bubbles") + }, + { + .species = SPECIES_WOBBUFFET, + .ability = ABILITY_SHADOW_TAG, + .lvl = 5, + }, +}; + +static const struct TrainerMonNoItemDefaultMoves sTestParty2[] = +{ + { + .species = SPECIES_WOBBUFFET, + .lvl = 5, + }, + { + .species = SPECIES_WOBBUFFET, + .lvl = 6, + } +}; + +static const struct Trainer sTestTrainer1 = +{ + .trainerName = _("Test1"), + .party = EVERYTHING_CUSTOMIZED(sTestParty1), +}; + +static const struct Trainer sTestTrainer2 = +{ + .trainerName = _("Test2"), + .party = NO_ITEM_DEFAULT_MOVES(sTestParty2), +}; + +TEST("CreateNPCTrainerPartyForTrainer generates customized Pokémon") +{ + struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon)); + u8 nickBuffer[20]; + CreateNPCTrainerPartyFromTrainer(testParty, &sTestTrainer1, TRUE, BATTLE_TYPE_TRAINER); + EXPECT(IsMonShiny(&testParty[0])); + EXPECT(!IsMonShiny(&testParty[1])); + + EXPECT(GetMonData(&testParty[0], MON_DATA_POKEBALL, 0) == ITEM_MASTER_BALL); + EXPECT(GetMonData(&testParty[1], MON_DATA_POKEBALL, 0) == ITEM_POKE_BALL); + + EXPECT(GetMonData(&testParty[0], MON_DATA_SPECIES, 0) == SPECIES_WOBBUFFET); + EXPECT(GetMonData(&testParty[1], MON_DATA_SPECIES, 0) == SPECIES_WOBBUFFET); + + EXPECT(GetMonAbility(&testParty[0]) == ABILITY_TELEPATHY); + EXPECT(GetMonAbility(&testParty[1]) == ABILITY_SHADOW_TAG); + + EXPECT(GetMonData(&testParty[0], MON_DATA_FRIENDSHIP, 0) == 42); + EXPECT(GetMonData(&testParty[1], MON_DATA_FRIENDSHIP, 0) == 0); + + EXPECT(GetMonData(&testParty[0], MON_DATA_HELD_ITEM, 0) == ITEM_ASSAULT_VEST); + EXPECT(GetMonData(&testParty[1], MON_DATA_HELD_ITEM, 0) == ITEM_NONE); + + EXPECT(GetMonData(&testParty[0], MON_DATA_HP_IV, 0) == 25); + EXPECT(GetMonData(&testParty[0], MON_DATA_ATK_IV, 0) == 26); + EXPECT(GetMonData(&testParty[0], MON_DATA_DEF_IV, 0) == 27); + EXPECT(GetMonData(&testParty[0], MON_DATA_SPEED_IV, 0) == 28); + EXPECT(GetMonData(&testParty[0], MON_DATA_SPATK_IV, 0) == 29); + EXPECT(GetMonData(&testParty[0], MON_DATA_SPDEF_IV, 0) == 30); + + EXPECT(GetMonData(&testParty[1], MON_DATA_HP_IV, 0) == 0); + EXPECT(GetMonData(&testParty[1], MON_DATA_ATK_IV, 0) == 0); + EXPECT(GetMonData(&testParty[1], MON_DATA_DEF_IV, 0) == 0); + EXPECT(GetMonData(&testParty[1], MON_DATA_SPEED_IV, 0) == 0); + EXPECT(GetMonData(&testParty[1], MON_DATA_SPATK_IV, 0) == 0); + EXPECT(GetMonData(&testParty[1], MON_DATA_SPDEF_IV, 0) == 0); + + EXPECT(GetMonData(&testParty[0], MON_DATA_HP_EV, 0) == 252); + EXPECT(GetMonData(&testParty[0], MON_DATA_ATK_EV, 0) == 0); + EXPECT(GetMonData(&testParty[0], MON_DATA_DEF_EV, 0) == 0); + EXPECT(GetMonData(&testParty[0], MON_DATA_SPEED_EV, 0) == 252); + EXPECT(GetMonData(&testParty[0], MON_DATA_SPATK_EV, 0) == 4); + EXPECT(GetMonData(&testParty[0], MON_DATA_SPDEF_EV, 0) == 0); + + EXPECT(GetMonData(&testParty[1], MON_DATA_HP_EV, 0) == 0); + EXPECT(GetMonData(&testParty[1], MON_DATA_ATK_EV, 0) == 0); + EXPECT(GetMonData(&testParty[1], MON_DATA_DEF_EV, 0) == 0); + EXPECT(GetMonData(&testParty[1], MON_DATA_SPEED_EV, 0) == 0); + EXPECT(GetMonData(&testParty[1], MON_DATA_SPATK_EV, 0) == 0); + EXPECT(GetMonData(&testParty[1], MON_DATA_SPDEF_EV, 0) == 0); + + EXPECT(GetMonData(&testParty[0], MON_DATA_LEVEL, 0) == 67); + EXPECT(GetMonData(&testParty[1], MON_DATA_LEVEL, 0) == 5); + + EXPECT(GetMonData(&testParty[0], MON_DATA_MOVE1, 0) == MOVE_AIR_SLASH); + EXPECT(GetMonData(&testParty[0], MON_DATA_MOVE2, 0) == MOVE_BARRIER); + EXPECT(GetMonData(&testParty[0], MON_DATA_MOVE3, 0) == MOVE_SOLAR_BEAM); + EXPECT(GetMonData(&testParty[0], MON_DATA_MOVE4, 0) == MOVE_EXPLOSION); + + GetMonData(&testParty[0], MON_DATA_NICKNAME, nickBuffer); + EXPECT(StringCompare(nickBuffer, COMPOUND_STRING("Bubbles")) == 0); + + GetMonData(&testParty[1], MON_DATA_NICKNAME, nickBuffer); + EXPECT(StringCompare(nickBuffer, COMPOUND_STRING("Wobbuffet")) == 0); + + EXPECT(GetGenderFromSpeciesAndPersonality(GetMonData(&testParty[0], MON_DATA_SPECIES, 0), testParty[0].box.personality) == MON_FEMALE); + + EXPECT(GetNature(&testParty[0]) == NATURE_HASTY); + + Free(testParty); +} + +TEST("CreateNPCTrainerPartyForTrainer generates different personalities for different mons") +{ + struct Pokemon *testParty = Alloc(6 * sizeof(struct Pokemon)); + CreateNPCTrainerPartyFromTrainer(testParty, &sTestTrainer2, TRUE, BATTLE_TYPE_TRAINER); + EXPECT(testParty[0].box.personality != testParty[1].box.personality); + Free(testParty); +}