Merge branch 'RHH/upcoming' into RHH/pr/fix/GassyTerrain

# Conflicts:
#	test/test.h
This commit is contained in:
Eduardo Quezada 2023-03-26 16:06:04 -03:00
commit 32b2c01619
82 changed files with 2105 additions and 412 deletions

View File

@ -8627,6 +8627,7 @@ BattleScript_IntimidateActivates::
BattleScript_IntimidateLoop: BattleScript_IntimidateLoop:
jumpifbyteequal gBattlerTarget, gBattlerAttacker, BattleScript_IntimidateLoopIncrement jumpifbyteequal gBattlerTarget, gBattlerAttacker, BattleScript_IntimidateLoopIncrement
jumpiftargetally BattleScript_IntimidateLoopIncrement jumpiftargetally BattleScript_IntimidateLoopIncrement
jumpifabsent BS_TARGET, BattleScript_IntimidateLoopIncrement
jumpifstatus2 BS_TARGET, STATUS2_SUBSTITUTE, BattleScript_IntimidateLoopIncrement jumpifstatus2 BS_TARGET, STATUS2_SUBSTITUTE, BattleScript_IntimidateLoopIncrement
jumpifholdeffect BS_TARGET, HOLD_EFFECT_CLEAR_AMULET, BattleScript_IntimidatePrevented_Item jumpifholdeffect BS_TARGET, HOLD_EFFECT_CLEAR_AMULET, BattleScript_IntimidatePrevented_Item
jumpifability BS_TARGET, ABILITY_CLEAR_BODY, BattleScript_IntimidatePrevented jumpifability BS_TARGET, ABILITY_CLEAR_BODY, BattleScript_IntimidatePrevented

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 735 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 710 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 724 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 750 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 682 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 B

View File

@ -534,7 +534,6 @@ struct BattleStruct
u8 wildVictorySong; u8 wildVictorySong;
u8 dynamicMoveType; u8 dynamicMoveType;
u8 wrappedBy[MAX_BATTLERS_COUNT]; u8 wrappedBy[MAX_BATTLERS_COUNT];
u16 assistPossibleMoves[PARTY_SIZE * MAX_MON_MOVES]; // Each of mons can know max 4 moves.
u8 focusPunchBattlerId; u8 focusPunchBattlerId;
u8 battlerPreventingSwitchout; u8 battlerPreventingSwitchout;
u8 moneyMultiplier:6; u8 moneyMultiplier:6;

View File

@ -1,6 +1,9 @@
#ifndef GUARD_BATTLE_MAIN_H #ifndef GUARD_BATTLE_MAIN_H
#define GUARD_BATTLE_MAIN_H #define GUARD_BATTLE_MAIN_H
#include "pokemon.h"
#include "data.h"
struct TrainerMoney struct TrainerMoney
{ {
u8 classId; u8 classId;
@ -66,6 +69,7 @@ bool8 TryRunFromBattle(u8 battlerId);
void SpecialStatusesClear(void); void SpecialStatusesClear(void);
void SetTypeBeforeUsingMove(u16 move, u8 battlerAtk); void SetTypeBeforeUsingMove(u16 move, u8 battlerAtk);
bool32 IsWildMonSmart(void); bool32 IsWildMonSmart(void);
u8 CreateNPCTrainerPartyFromTrainer(struct Pokemon *party, const struct Trainer *trainer, bool32 firstTrainer, u32 battleTypeFlags);
extern struct MultiPartnerMenuPokemon gMultiPartnerParty[MULTI_PARTY_SIZE]; extern struct MultiPartnerMenuPokemon gMultiPartnerParty[MULTI_PARTY_SIZE];

View File

@ -159,6 +159,7 @@
#define HOLD_EFFECT_PUNCHING_GLOVE 178 #define HOLD_EFFECT_PUNCHING_GLOVE 178
#define HOLD_EFFECT_COVERT_CLOAK 179 #define HOLD_EFFECT_COVERT_CLOAK 179
#define HOLD_EFFECT_LOADED_DICE 180 #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)) #define HOLD_EFFECT_CHOICE(holdEffect)((holdEffect == HOLD_EFFECT_CHOICE_BAND || holdEffect == HOLD_EFFECT_CHOICE_SCARF || holdEffect == HOLD_EFFECT_CHOICE_SPECS))

View File

@ -928,13 +928,48 @@
#define ITEM_RUBY 756 #define ITEM_RUBY 756
#define ITEM_SAPPHIRE 757 #define ITEM_SAPPHIRE 757
// GEN IX ITEMS
#define ITEM_ABILITY_SHIELD 758 #define ITEM_ABILITY_SHIELD 758
#define ITEM_CLEAR_AMULET 759 #define ITEM_CLEAR_AMULET 759
#define ITEM_PUNCHING_GLOVE 760 #define ITEM_PUNCHING_GLOVE 760
#define ITEM_COVERT_CLOAK 761 #define ITEM_COVERT_CLOAK 761
#define ITEM_LOADED_DICE 762 #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 #define ITEM_FIELD_ARROW ITEMS_COUNT
// A special item id associated with "Cancel"/"Exit" etc. in a list of items or decorations // A special item id associated with "Cancel"/"Exit" etc. in a list of items or decorations

View File

@ -374,7 +374,12 @@
// All trainer parties specify the IV, level, and species for each Pokémon in the // 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 // party. Some trainer parties also specify held items and custom moves for each
// Pokémon. // Pokémon.
#define F_TRAINER_PARTY_CUSTOM_MOVESET (1 << 0) #define F_TRAINER_PARTY_CUSTOM_MOVESET (1 << 0)
#define F_TRAINER_PARTY_HELD_ITEM (1 << 1) #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 #endif // GUARD_TRAINERS_H

View File

@ -31,6 +31,26 @@ struct MonCoords
#define MON_COORDS_SIZE(width, height)(DIV_ROUND_UP(width, 8) << 4 | DIV_ROUND_UP(height, 8)) #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_WIDTH(size)((size >> 4) * 8)
#define GET_MON_COORDS_HEIGHT(size)((size & 0xF) * 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 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 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_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 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 union TrainerMonPtr
{ {
@ -75,20 +96,21 @@ union TrainerMonPtr
const struct TrainerMonNoItemCustomMoves *NoItemCustomMoves; const struct TrainerMonNoItemCustomMoves *NoItemCustomMoves;
const struct TrainerMonItemDefaultMoves *ItemDefaultMoves; const struct TrainerMonItemDefaultMoves *ItemDefaultMoves;
const struct TrainerMonItemCustomMoves *ItemCustomMoves; const struct TrainerMonItemCustomMoves *ItemCustomMoves;
const struct TrainerMonCustomized *EverythingCustomized;
}; };
struct Trainer struct Trainer
{ {
/*0x00*/ u8 partyFlags; /*0x00*/ u32 aiFlags;
/*0x01*/ u8 trainerClass; /*0x04*/ union TrainerMonPtr party;
/*0x02*/ u8 encounterMusic_gender; // last bit is gender /*0x08*/ u16 items[MAX_TRAINER_ITEMS];
/*0x03*/ u8 trainerPic; /*0x10*/ u8 trainerClass;
/*0x04*/ u8 trainerName[TRAINER_NAME_LENGTH + 1]; /*0x11*/ u8 encounterMusic_gender; // last bit is gender
/*0x10*/ u16 items[MAX_TRAINER_ITEMS]; /*0x12*/ u8 trainerPic;
/*0x18*/ bool8 doubleBattle; /*0x13*/ u8 trainerName[TRAINER_NAME_LENGTH + 1];
/*0x1C*/ u32 aiFlags; /*0x1E*/ bool8 doubleBattle:1;
/*0x20*/ u8 partySize; u8 partyFlags:7;
/*0x24*/ union TrainerMonPtr party; /*0x1F*/ u8 partySize;
}; };
#define TRAINER_ENCOUNTER_MUSIC(trainer)((gTrainers[trainer].encounterMusic_gender & 0x7F)) #define TRAINER_ENCOUNTER_MUSIC(trainer)((gTrainers[trainer].encounterMusic_gender & 0x7F))

View File

@ -147,6 +147,9 @@
#define CAT(a, b) CAT_(a, b) #define CAT(a, b) CAT_(a, b)
#define CAT_(a, b) 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. // This produces an error at compile-time if expr is zero.
// It looks like file.c:line: size of array `id' is negative // It looks like file.c:line: size of array `id' is negative
#define STATIC_ASSERT(expr, id) typedef char id[(expr) ? 1 : -1]; #define STATIC_ASSERT(expr, id) typedef char id[(expr) ? 1 : -1];

View File

@ -8834,6 +8834,45 @@ extern const u32 gItemIcon_Gem[];
extern const u32 gItemIconPalette_Ruby[]; extern const u32 gItemIconPalette_Ruby[];
extern const u32 gItemIconPalette_Sapphire[]; 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 gItemIcon_ReturnToFieldArrow[];
extern const u32 gItemIconPalette_ReturnToFieldArrow[]; extern const u32 gItemIconPalette_ReturnToFieldArrow[];

View File

@ -20,4 +20,67 @@ u16 Random2(void);
void SeedRng(u16 seed); void SeedRng(u16 seed);
void SeedRng2(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 #endif // GUARD_RANDOM_H

View File

@ -118,6 +118,10 @@ static void HandleEndTurn_FinishBattle(void);
static void SpriteCB_UnusedBattleInit(struct Sprite *sprite); static void SpriteCB_UnusedBattleInit(struct Sprite *sprite);
static void SpriteCB_UnusedBattleInit_Main(struct Sprite *sprite); static void SpriteCB_UnusedBattleInit_Main(struct Sprite *sprite);
static void TrySpecialEvolution(void); 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_X = 0;
EWRAM_DATA u16 gBattle_BG0_Y = 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; u32 personalityValue;
u8 fixedIV; u8 fixedIV;
s32 i, j; s32 i, j;
u8 monsCount; u8 monsCount;
u16 ball; s32 ball = -1;
if (battleTypeFlags & BATTLE_TYPE_TRAINER && !(battleTypeFlags & (BATTLE_TYPE_FRONTIER
if (trainerNum == TRAINER_SECRET_BASE)
return 0;
if (gBattleTypeFlags & BATTLE_TYPE_TRAINER && !(gBattleTypeFlags & (BATTLE_TYPE_FRONTIER
| BATTLE_TYPE_EREADER_TRAINER | BATTLE_TYPE_EREADER_TRAINER
| BATTLE_TYPE_TRAINER_HILL))) | BATTLE_TYPE_TRAINER_HILL)))
{ {
if (firstTrainer == TRUE) if (firstTrainer == TRUE)
ZeroEnemyPartyMons(); 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; monsCount = PARTY_SIZE / 2;
else else
monsCount = gTrainers[trainerNum].partySize; monsCount = trainer->partySize;
} }
else else
{ {
monsCount = gTrainers[trainerNum].partySize; monsCount = trainer->partySize;
} }
for (i = 0; i < monsCount; i++) for (i = 0; i < monsCount; i++)
{ {
u32 personalityHash = GeneratePartyHash(trainer, i);
if (gTrainers[trainerNum].doubleBattle == TRUE) if (trainer->doubleBattle == TRUE)
personalityValue = 0x80; 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 personalityValue = 0x78; // Use personality more likely to result in a female Pokémon
else else
personalityValue = 0x88; // Use personality more likely to result in a male Pokémon personalityValue = 0x88; // Use personality more likely to result in a male Pokémon
for (j = 0; gTrainers[trainerNum].trainerName[j] != EOS; j++) personalityValue += personalityHash << 8;
nameHash += gTrainers[trainerNum].trainerName[j]; switch (trainer->partyFlags)
switch (gTrainers[trainerNum].partyFlags)
{ {
case 0: case 0:
{ {
const struct TrainerMonNoItemDefaultMoves *partyData = gTrainers[trainerNum].party.NoItemDefaultMoves; const struct TrainerMonNoItemDefaultMoves *partyData = trainer->party.NoItemDefaultMoves;
for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++)
nameHash += gSpeciesNames[partyData[i].species][j];
personalityValue += nameHash << 8;
fixedIV = partyData[i].iv * MAX_PER_STAT_IVS / 255; 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); CreateMon(&party[i], partyData[i].species, partyData[i].lvl, fixedIV, TRUE, personalityValue, OT_ID_RANDOM_NO_SHINY, 0);
break; break;
} }
case F_TRAINER_PARTY_CUSTOM_MOVESET: case F_TRAINER_PARTY_CUSTOM_MOVESET:
{ {
const struct TrainerMonNoItemCustomMoves *partyData = gTrainers[trainerNum].party.NoItemCustomMoves; const struct TrainerMonNoItemCustomMoves *partyData = trainer->party.NoItemCustomMoves;
for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++)
nameHash += gSpeciesNames[partyData[i].species][j];
personalityValue += nameHash << 8;
fixedIV = partyData[i].iv * MAX_PER_STAT_IVS / 255; 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); 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: case F_TRAINER_PARTY_HELD_ITEM:
{ {
const struct TrainerMonItemDefaultMoves *partyData = gTrainers[trainerNum].party.ItemDefaultMoves; const struct TrainerMonItemDefaultMoves *partyData = trainer->party.ItemDefaultMoves;
for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++)
nameHash += gSpeciesNames[partyData[i].species][j];
personalityValue += nameHash << 8;
fixedIV = partyData[i].iv * MAX_PER_STAT_IVS / 255; 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); 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: case F_TRAINER_PARTY_CUSTOM_MOVESET | F_TRAINER_PARTY_HELD_ITEM:
{ {
const struct TrainerMonItemCustomMoves *partyData = gTrainers[trainerNum].party.ItemCustomMoves; const struct TrainerMonItemCustomMoves *partyData = trainer->party.ItemCustomMoves;
for (j = 0; gSpeciesNames[partyData[i].species][j] != EOS; j++)
nameHash += gSpeciesNames[partyData[i].species][j];
personalityValue += nameHash << 8;
fixedIV = partyData[i].iv * MAX_PER_STAT_IVS / 255; 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); 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; 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 #if B_TRAINER_CLASS_POKE_BALLS >= GEN_7
ball = (sTrainerBallTable[gTrainers[trainerNum].trainerClass]) ? sTrainerBallTable[gTrainers[trainerNum].trainerClass] : ITEM_POKE_BALL; if (ball == -1)
SetMonData(&party[i], MON_DATA_POKEBALL, &ball); {
ball = (sTrainerBallTable[trainer->trainerClass]) ? sTrainerBallTable[trainer->trainerClass] : ITEM_POKE_BALL;
SetMonData(&party[i], MON_DATA_POKEBALL, &ball);
}
#endif #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) void VBlankCB_Battle(void)

View File

@ -23,6 +23,7 @@
#include "main.h" #include "main.h"
#include "palette.h" #include "palette.h"
#include "money.h" #include "money.h"
#include "malloc.h"
#include "bg.h" #include "bg.h"
#include "string_util.h" #include "string_util.h"
#include "pokemon_icon.h" #include "pokemon_icon.h"
@ -1917,15 +1918,25 @@ static void Cmd_accuracycheck(void)
} }
else else
{ {
u32 accuracy;
GET_MOVE_TYPE(move, type); GET_MOVE_TYPE(move, type);
if (JumpIfMoveAffectedByProtect(move)) if (JumpIfMoveAffectedByProtect(move))
return; return;
if (AccuracyCalcHelper(move)) if (AccuracyCalcHelper(move))
return; return;
// final calculation accuracy = GetTotalAccuracy(
if ((Random() % 100 + 1) > GetTotalAccuracy(gBattlerAttacker, gBattlerTarget, move, GetBattlerAbility(gBattlerAttacker), GetBattlerAbility(gBattlerTarget), gBattlerAttacker,
GetBattlerHoldEffect(gBattlerAttacker, TRUE), GetBattlerHoldEffect(gBattlerTarget, TRUE))) gBattlerTarget,
move,
GetBattlerAbility(gBattlerAttacker),
GetBattlerAbility(gBattlerTarget),
GetBattlerHoldEffect(gBattlerAttacker, TRUE),
GetBattlerHoldEffect(gBattlerTarget, TRUE)
);
if (!RandomPercentage(RNG_ACCURACY, accuracy))
{ {
gMoveResultFlags |= MOVE_RESULT_MISSED; gMoveResultFlags |= MOVE_RESULT_MISSED;
if (GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_BLUNDER_POLICY) if (GetBattlerHoldEffect(gBattlerAttacker, TRUE) == HOLD_EFFECT_BLUNDER_POLICY)
@ -2104,10 +2115,8 @@ static void Cmd_critcalc(void)
gIsCriticalHit = FALSE; gIsCriticalHit = FALSE;
else if (critChance == -2) else if (critChance == -2)
gIsCriticalHit = TRUE; gIsCriticalHit = TRUE;
else if (Random() % sCriticalHitChance[critChance] == 0)
gIsCriticalHit = TRUE;
else else
gIsCriticalHit = FALSE; gIsCriticalHit = RandomWeighted(RNG_CRITICAL_HIT, sCriticalHitChance[critChance] - 1, 1);
// Counter for EVO_CRITICAL_HITS. // Counter for EVO_CRITICAL_HITS.
partySlot = gBattlerPartyIndexes[gBattlerAttacker]; partySlot = gBattlerPartyIndexes[gBattlerAttacker];
@ -3153,9 +3162,9 @@ void SetMoveEffect(bool32 primary, u32 certain)
if (sStatusFlagsForMoveEffects[gBattleScripting.moveEffect] == STATUS1_SLEEP) if (sStatusFlagsForMoveEffects[gBattleScripting.moveEffect] == STATUS1_SLEEP)
#if B_SLEEP_TURNS >= GEN_5 #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 #else
gBattleMons[gEffectBattler].status1 |= ((Random() % 4) + 3); gBattleMons[gEffectBattler].status1 |= STATUS1_SLEEP_TURN(1 + RandomUniform(RNG_SLEEP_TURNS, 2, 5));
#endif #endif
else else
gBattleMons[gEffectBattler].status1 |= sStatusFlagsForMoveEffects[gBattleScripting.moveEffect]; gBattleMons[gEffectBattler].status1 |= sStatusFlagsForMoveEffects[gBattleScripting.moveEffect];
@ -3558,7 +3567,7 @@ void SetMoveEffect(bool32 primary, u32 certain)
{ {
gBattleMons[gEffectBattler].status2 |= STATUS2_MULTIPLETURNS; gBattleMons[gEffectBattler].status2 |= STATUS2_MULTIPLETURNS;
gLockedMoves[gEffectBattler] = gCurrentMove; 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; break;
case MOVE_EFFECT_SP_ATK_TWO_DOWN: // Overheat case MOVE_EFFECT_SP_ATK_TWO_DOWN: // Overheat
@ -3779,20 +3788,23 @@ static void Cmd_seteffectwithchance(void)
else else
percentChance = gBattleMoves[gCurrentMove].secondaryEffectChance; percentChance = gBattleMoves[gCurrentMove].secondaryEffectChance;
if (gBattleScripting.moveEffect & MOVE_EFFECT_CERTAIN if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)
&& !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)) && gBattleScripting.moveEffect)
{ {
gBattleScripting.moveEffect &= ~MOVE_EFFECT_CERTAIN; if (gBattleScripting.moveEffect & MOVE_EFFECT_CERTAIN
SetMoveEffect(FALSE, MOVE_EFFECT_CERTAIN); || percentChance >= 100)
} {
else if (Random() % 100 < percentChance gBattleScripting.moveEffect &= ~MOVE_EFFECT_CERTAIN;
&& gBattleScripting.moveEffect
&& !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT))
{
if (percentChance >= 100)
SetMoveEffect(FALSE, MOVE_EFFECT_CERTAIN); SetMoveEffect(FALSE, MOVE_EFFECT_CERTAIN);
else }
else if (RandomPercentage(RNG_SECONDARY_EFFECT, percentChance))
{
SetMoveEffect(FALSE, 0); SetMoveEffect(FALSE, 0);
}
else
{
gBattlescriptCurrInstr = cmd->nextInstr;
}
} }
else 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. // Checks for double battle, so abilities like Intimidate wait until all battlers are switched-in before activating.
if (IsDoubleBattle()) 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; 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; return TRUE;
} }
@ -12343,7 +12355,7 @@ static void Cmd_forcerandomswitch(void)
*(gBattleStruct->battlerPartyIndexes + gBattlerTarget) = gBattlerPartyIndexes[gBattlerTarget]; *(gBattleStruct->battlerPartyIndexes + gBattlerTarget) = gBattlerPartyIndexes[gBattlerTarget];
gBattlescriptCurrInstr = BattleScript_RoarSuccessSwitch; gBattlescriptCurrInstr = BattleScript_RoarSuccessSwitch;
gBattleStruct->forcedSwitch |= gBitTable[gBattlerTarget]; gBattleStruct->forcedSwitch |= gBitTable[gBattlerTarget];
*(gBattleStruct->monToSwitchIntoId + gBattlerTarget) = validMons[Random() % validMonsCount]; *(gBattleStruct->monToSwitchIntoId + gBattlerTarget) = validMons[RandomUniform(RNG_FORCE_RANDOM_SWITCH, 0, validMonsCount - 1)];
if (!IsMultiBattle()) if (!IsMultiBattle())
SwitchPartyOrder(gBattlerTarget); SwitchPartyOrder(gBattlerTarget);
@ -12550,7 +12562,7 @@ static void Cmd_tryKO(void)
if (gCurrentMove == MOVE_SHEER_COLD && !IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_ICE)) if (gCurrentMove == MOVE_SHEER_COLD && !IS_BATTLER_OF_TYPE(gBattlerAttacker, TYPE_ICE))
odds -= 10; odds -= 10;
#endif #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; lands = TRUE;
} }
@ -14766,38 +14778,40 @@ static void Cmd_assistattackselect(void)
s32 chooseableMovesNo = 0; s32 chooseableMovesNo = 0;
struct Pokemon *party; struct Pokemon *party;
s32 monId, moveId; 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) if (validMoves != NULL)
party = gEnemyParty;
else
party = gPlayerParty;
for (monId = 0; monId < PARTY_SIZE; monId++)
{ {
if (monId == gBattlerPartyIndexes[gBattlerAttacker]) if (GET_BATTLER_SIDE(gBattlerAttacker) != B_SIDE_PLAYER)
continue; party = gEnemyParty;
if (GetMonData(&party[monId], MON_DATA_SPECIES_OR_EGG) == SPECIES_NONE) else
continue; party = gPlayerParty;
if (GetMonData(&party[monId], MON_DATA_SPECIES_OR_EGG) == SPECIES_EGG)
continue;
for (moveId = 0; moveId < MAX_MON_MOVES; moveId++) for (monId = 0; monId < PARTY_SIZE; monId++)
{ {
s32 i = 0; if (monId == gBattlerPartyIndexes[gBattlerAttacker])
u16 move = GetMonData(&party[monId], MON_DATA_MOVE1 + moveId); continue;
if (GetMonData(&party[monId], MON_DATA_SPECIES_OR_EGG) == SPECIES_NONE)
if (sForbiddenMoves[move] & FORBIDDEN_ASSIST) continue;
if (GetMonData(&party[monId], MON_DATA_SPECIES_OR_EGG) == SPECIES_EGG)
continue; continue;
validMoves[chooseableMovesNo] = move; for (moveId = 0; moveId < MAX_MON_MOVES; moveId++)
chooseableMovesNo++; {
u16 move = GetMonData(&party[monId], MON_DATA_MOVE1 + moveId);
if (sForbiddenMoves[move] & FORBIDDEN_ASSIST)
continue;
validMoves[chooseableMovesNo++] = move;
}
} }
} }
if (chooseableMovesNo) if (chooseableMovesNo)
{ {
gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED; gHitMarker &= ~HITMARKER_ATTACKSTRING_PRINTED;
gCalledMove = validMoves[((Random() & 0xFF) * chooseableMovesNo) >> 8]; gCalledMove = validMoves[Random() % chooseableMovesNo];
gBattlerTarget = GetMoveTarget(gCalledMove, NO_TARGET_OVERRIDE); gBattlerTarget = GetMoveTarget(gCalledMove, NO_TARGET_OVERRIDE);
gBattlescriptCurrInstr = cmd->nextInstr; gBattlescriptCurrInstr = cmd->nextInstr;
} }
@ -14805,6 +14819,8 @@ static void Cmd_assistattackselect(void)
{ {
gBattlescriptCurrInstr = cmd->failInstr; gBattlescriptCurrInstr = cmd->failInstr;
} }
TRY_FREE_AND_SET_NULL(validMoves);
} }
static void Cmd_trysetmagiccoat(void) static void Cmd_trysetmagiccoat(void)

View File

@ -3486,7 +3486,7 @@ u8 AtkCanceller_UnableToUseMove(void)
case CANCELLER_FROZEN: // check being frozen case CANCELLER_FROZEN: // check being frozen
if (gBattleMons[gBattlerAttacker].status1 & STATUS1_FREEZE && !(gBattleMoves[gCurrentMove].flags & FLAG_THAW_USER)) if (gBattleMons[gBattlerAttacker].status1 & STATUS1_FREEZE && !(gBattleMoves[gCurrentMove].flags & FLAG_THAW_USER))
{ {
if (Random() % 5) if (!RandomPercentage(RNG_FROZEN, 20))
{ {
gBattlescriptCurrInstr = BattleScript_MoveUsedIsFrozen; gBattlescriptCurrInstr = BattleScript_MoveUsedIsFrozen;
gHitMarker |= HITMARKER_NO_ATTACKSTRING; gHitMarker |= HITMARKER_NO_ATTACKSTRING;
@ -3604,9 +3604,9 @@ u8 AtkCanceller_UnableToUseMove(void)
{ {
// confusion dmg // confusion dmg
#if B_CONFUSION_SELF_DMG_CHANCE >= GEN_7 #if B_CONFUSION_SELF_DMG_CHANCE >= GEN_7
if (Random() % 3 == 0) if (RandomWeighted(RNG_CONFUSION, 2, 1))
#else #else
if (Random() % 2 == 0) if (RandomWeighted(RNG_CONFUSION, 1, 1))
#endif #endif
{ {
gBattleCommunication[MULTISTRING_CHOOSER] = TRUE; gBattleCommunication[MULTISTRING_CHOOSER] = TRUE;
@ -3632,7 +3632,7 @@ u8 AtkCanceller_UnableToUseMove(void)
gBattleStruct->atkCancellerTracker++; gBattleStruct->atkCancellerTracker++;
break; break;
case CANCELLER_PARALYSED: // paralysis 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; gProtectStructs[gBattlerAttacker].prlzImmobility = TRUE;
// This is removed in FRLG and Emerald for some reason // This is removed in FRLG and Emerald for some reason
@ -3647,7 +3647,7 @@ u8 AtkCanceller_UnableToUseMove(void)
if (gBattleMons[gBattlerAttacker].status2 & STATUS2_INFATUATION) if (gBattleMons[gBattlerAttacker].status2 & STATUS2_INFATUATION)
{ {
gBattleScripting.battler = CountTrailingZeroBits((gBattleMons[gBattlerAttacker].status2 & STATUS2_INFATUATION) >> 0x10); gBattleScripting.battler = CountTrailingZeroBits((gBattleMons[gBattlerAttacker].status2 & STATUS2_INFATUATION) >> 0x10);
if (Random() & 1) if (!RandomPercentage(RNG_INFATUATION, 50))
{ {
BattleScriptPushCursor(); BattleScriptPushCursor();
} }
@ -5652,7 +5652,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move
&& TARGET_TURN_DAMAGED && TARGET_TURN_DAMAGED
&& CanBePoisoned(gBattlerTarget, gBattlerAttacker) && CanBePoisoned(gBattlerTarget, gBattlerAttacker)
&& IsMoveMakingContact(move, gBattlerAttacker) && IsMoveMakingContact(move, gBattlerAttacker)
&& (Random() % 3) == 0) && RandomWeighted(RNG_POISON_POINT, 2, 1))
{ {
gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_POISON; gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_POISON;
PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility); PREPARE_ABILITY_BUFFER(gBattleTextBuff1, gLastUsedAbility);
@ -5670,7 +5670,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move
&& TARGET_TURN_DAMAGED && TARGET_TURN_DAMAGED
&& CanBeParalyzed(gBattlerAttacker) && CanBeParalyzed(gBattlerAttacker)
&& IsMoveMakingContact(move, gBattlerAttacker) && IsMoveMakingContact(move, gBattlerAttacker)
&& (Random() % 3) == 0) && RandomWeighted(RNG_STATIC, 2, 1))
{ {
gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_PARALYSIS; gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_PARALYSIS;
BattleScriptPushCursor(); BattleScriptPushCursor();
@ -5686,7 +5686,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move
&& (IsMoveMakingContact(move, gBattlerAttacker)) && (IsMoveMakingContact(move, gBattlerAttacker))
&& TARGET_TURN_DAMAGED && TARGET_TURN_DAMAGED
&& CanBeBurned(gBattlerAttacker) && CanBeBurned(gBattlerAttacker)
&& (Random() % 3) == 0) && RandomWeighted(RNG_FLAME_BODY, 2, 1))
{ {
gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_BURN; gBattleScripting.moveEffect = MOVE_EFFECT_AFFECTS_USER | MOVE_EFFECT_BURN;
BattleScriptPushCursor(); BattleScriptPushCursor();
@ -5702,7 +5702,7 @@ u8 AbilityBattleEffects(u8 caseID, u8 battler, u16 ability, u8 special, u16 move
&& (IsMoveMakingContact(move, gBattlerAttacker)) && (IsMoveMakingContact(move, gBattlerAttacker))
&& TARGET_TURN_DAMAGED && TARGET_TURN_DAMAGED
&& gBattleMons[gBattlerTarget].hp != 0 && gBattleMons[gBattlerTarget].hp != 0
&& (Random() % 3) == 0 && RandomWeighted(RNG_CUTE_CHARM, 2, 1)
&& GetBattlerAbility(gBattlerAttacker) != ABILITY_OBLIVIOUS && GetBattlerAbility(gBattlerAttacker) != ABILITY_OBLIVIOUS
&& !IsAbilityOnSide(gBattlerAttacker, ABILITY_AROMA_VEIL) && !IsAbilityOnSide(gBattlerAttacker, ABILITY_AROMA_VEIL)
&& GetGenderFromSpeciesAndPersonality(speciesAtk, pidAtk) != GetGenderFromSpeciesAndPersonality(speciesDef, pidDef) && 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) if (!(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)
&& gBattleMons[gBattlerTarget].hp != 0 && gBattleMons[gBattlerTarget].hp != 0
&& !gProtectStructs[gBattlerAttacker].confusionSelfDmg && !gProtectStructs[gBattlerAttacker].confusionSelfDmg
&& (Random() % 10) == 0 && RandomWeighted(RNG_STENCH, 9, 1)
&& !IS_MOVE_STATUS(move) && !IS_MOVE_STATUS(move)
&& !sMovesNotAffectedByStench[gCurrentMove]) && !sMovesNotAffectedByStench[gCurrentMove])
{ {
@ -7642,7 +7642,7 @@ u8 ItemBattleEffects(u8 caseID, u8 battlerId, bool8 moveTurn)
if (gBattleMoveDamage != 0 // Need to have done damage if (gBattleMoveDamage != 0 // Need to have done damage
&& !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT) && !(gMoveResultFlags & MOVE_RESULT_NO_EFFECT)
&& TARGET_TURN_DAMAGED && TARGET_TURN_DAMAGED
&& (Random() % 100) < atkHoldEffectParam && RandomPercentage(RNG_HOLD_EFFECT_FLINCH, atkHoldEffectParam)
&& gBattleMoves[gCurrentMove].flags & FLAG_KINGS_ROCK_AFFECTED && gBattleMoves[gCurrentMove].flags & FLAG_KINGS_ROCK_AFFECTED
&& gBattleMons[gBattlerTarget].hp) && gBattleMons[gBattlerTarget].hp)
{ {
@ -9763,7 +9763,7 @@ static s32 DoMoveDamageCalc(u16 move, u8 battlerAtk, u8 battlerDef, u8 moveType,
// Add a random factor. // Add a random factor.
if (randomFactor) if (randomFactor)
{ {
dmg *= 100 - (Random() % 16); dmg *= 100 - RandomUniform(RNG_DAMAGE_MODIFIER, 0, 15);
dmg /= 100; dmg /= 100;
} }

View File

@ -3,6 +3,7 @@
#include "battle.h" #include "battle.h"
#include "data.h" #include "data.h"
#include "graphics.h" #include "graphics.h"
#include "constants/abilities.h"
#include "constants/items.h" #include "constants/items.h"
#include "constants/moves.h" #include "constants/moves.h"
#include "constants/trainers.h" #include "constants/trainers.h"

View File

@ -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 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 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 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 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 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 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 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 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 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 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 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");

View File

@ -803,11 +803,43 @@ const u32 *const gItemIconTable[ITEMS_COUNT + 1][2] =
[ITEM_TEA] = {gItemIcon_Tea, gItemIconPalette_Tea}, [ITEM_TEA] = {gItemIcon_Tea, gItemIconPalette_Tea},
[ITEM_RUBY] = {gItemIcon_Gem, gItemIconPalette_Ruby}, [ITEM_RUBY] = {gItemIcon_Gem, gItemIconPalette_Ruby},
[ITEM_SAPPHIRE] = {gItemIcon_Gem, gItemIconPalette_Sapphire}, [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_CLEAR_AMULET] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_ClearAmulet, gItemIconPalette_ClearAmulet},
[ITEM_PUNCHING_GLOVE] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_PunchingGlove, gItemIconPalette_PunchingGlove}, [ITEM_PUNCHING_GLOVE] = {gItemIcon_PunchingGlove, gItemIconPalette_PunchingGlove},
[ITEM_COVERT_CLOAK] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_CovertCloak, gItemIconPalette_CovertCloak}, [ITEM_COVERT_CLOAK] = {gItemIcon_CovertCloak, gItemIconPalette_CovertCloak},
[ITEM_LOADED_DICE] = {gItemIcon_QuestionMark, gItemIconPalette_QuestionMark}, // {gItemIcon_LoadedDice, gItemIconPalette_LoadedDice}, [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 // Return to field arrow
[ITEMS_COUNT] = {gItemIcon_ReturnToFieldArrow, gItemIconPalette_ReturnToFieldArrow}, [ITEMS_COUNT] = {gItemIcon_ReturnToFieldArrow, gItemIconPalette_ReturnToFieldArrow},
}; };

View File

@ -9907,7 +9907,6 @@ const struct Item gItems[] =
[ITEM_LOADED_DICE] = [ITEM_LOADED_DICE] =
{ {
//YellwApricorn
.name = _("Loaded Dice"), .name = _("Loaded Dice"),
.itemId = ITEM_LOADED_DICE, .itemId = ITEM_LOADED_DICE,
.price = 20000, .price = 20000,
@ -9918,4 +9917,370 @@ const struct Item gItems[] =
.fieldUseFunc = ItemUseOutOfBattle_CannotUse, .fieldUseFunc = ItemUseOutOfBattle_CannotUse,
.flingPower = 30, .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,
},
}; };

View File

@ -542,7 +542,9 @@ const struct Evolution gEvolutionTable[NUM_SPECIES][EVOS_PER_MON] =
[SPECIES_DREEPY] = {{EVO_LEVEL, 50, SPECIES_DRAKLOAK}}, [SPECIES_DREEPY] = {{EVO_LEVEL, 50, SPECIES_DRAKLOAK}},
[SPECIES_DRAKLOAK] = {{EVO_LEVEL, 60, SPECIES_DRAGAPULT}}, [SPECIES_DRAKLOAK] = {{EVO_LEVEL, 60, SPECIES_DRAGAPULT}},
[SPECIES_KUBFU] = {{EVO_DARK_SCROLL, 0, SPECIES_URSHIFU}, [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 #endif
[SPECIES_RATTATA_ALOLAN] = {{EVO_LEVEL_NIGHT, 20, SPECIES_RATICATE_ALOLAN}}, [SPECIES_RATTATA_ALOLAN] = {{EVO_LEVEL_NIGHT, 20, SPECIES_RATICATE_ALOLAN}},
[SPECIES_SANDSHREW_ALOLAN] = {{EVO_ITEM, ITEM_ICE_STONE, SPECIES_SANDSLASH_ALOLAN}}, [SPECIES_SANDSHREW_ALOLAN] = {{EVO_ITEM, ITEM_ICE_STONE, SPECIES_SANDSLASH_ALOLAN}},

View File

@ -1,6 +1,10 @@
const struct FormChange *const gFormChangeTablePointers[NUM_SPECIES] = const struct FormChange *const gFormChangeTablePointers[NUM_SPECIES] =
{ {
#if P_GEN_4_POKEMON == TRUE #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] = sGiratinaFormChangeTable,
[SPECIES_GIRATINA_ORIGIN] = sGiratinaFormChangeTable, [SPECIES_GIRATINA_ORIGIN] = sGiratinaFormChangeTable,
[SPECIES_SHAYMIN] = sShayminFormChangeTable, [SPECIES_SHAYMIN] = sShayminFormChangeTable,

View File

@ -44,9 +44,21 @@ FORM_BATTLE_END:
#define NIGHT 2 #define NIGHT 2
#if P_GEN_4_POKEMON == TRUE #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[] = { static const struct FormChange sGiratinaFormChangeTable[] = {
{FORM_ITEM_HOLD, SPECIES_GIRATINA, ITEM_NONE}, {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}, {FORM_CHANGE_END},
}; };

View File

@ -450,125 +450,129 @@ const u8 gItemEffect_TamatoBerry[10] = {
const u8 *const gItemEffectTable[ITEMS_COUNT] = const u8 *const gItemEffectTable[ITEMS_COUNT] =
{ {
// Medicine // Medicine
[ITEM_POTION] = gItemEffect_Potion, [ITEM_POTION] = gItemEffect_Potion,
[ITEM_SUPER_POTION] = gItemEffect_SuperPotion, [ITEM_SUPER_POTION] = gItemEffect_SuperPotion,
[ITEM_HYPER_POTION] = gItemEffect_HyperPotion, [ITEM_HYPER_POTION] = gItemEffect_HyperPotion,
[ITEM_MAX_POTION] = gItemEffect_MaxPotion, [ITEM_MAX_POTION] = gItemEffect_MaxPotion,
[ITEM_FULL_RESTORE] = gItemEffect_FullRestore, [ITEM_FULL_RESTORE] = gItemEffect_FullRestore,
[ITEM_REVIVE] = gItemEffect_Revive, [ITEM_REVIVE] = gItemEffect_Revive,
[ITEM_MAX_REVIVE] = gItemEffect_MaxRevive, [ITEM_MAX_REVIVE] = gItemEffect_MaxRevive,
[ITEM_FRESH_WATER] = gItemEffect_FreshWater, [ITEM_FRESH_WATER] = gItemEffect_FreshWater,
[ITEM_SODA_POP] = gItemEffect_SodaPop, [ITEM_SODA_POP] = gItemEffect_SodaPop,
[ITEM_LEMONADE] = gItemEffect_Lemonade, [ITEM_LEMONADE] = gItemEffect_Lemonade,
[ITEM_MOOMOO_MILK] = gItemEffect_MoomooMilk, [ITEM_MOOMOO_MILK] = gItemEffect_MoomooMilk,
[ITEM_ENERGY_POWDER] = gItemEffect_EnergyPowder, [ITEM_ENERGY_POWDER] = gItemEffect_EnergyPowder,
[ITEM_ENERGY_ROOT] = gItemEffect_EnergyRoot, [ITEM_ENERGY_ROOT] = gItemEffect_EnergyRoot,
[ITEM_HEAL_POWDER] = gItemEffect_HealPowder, [ITEM_HEAL_POWDER] = gItemEffect_HealPowder,
[ITEM_REVIVAL_HERB] = gItemEffect_RevivalHerb, [ITEM_REVIVAL_HERB] = gItemEffect_RevivalHerb,
[ITEM_ANTIDOTE] = gItemEffect_Antidote, [ITEM_ANTIDOTE] = gItemEffect_Antidote,
[ITEM_PARALYZE_HEAL] = gItemEffect_ParalyzeHeal, [ITEM_PARALYZE_HEAL] = gItemEffect_ParalyzeHeal,
[ITEM_BURN_HEAL] = gItemEffect_BurnHeal, [ITEM_BURN_HEAL] = gItemEffect_BurnHeal,
[ITEM_ICE_HEAL] = gItemEffect_IceHeal, [ITEM_ICE_HEAL] = gItemEffect_IceHeal,
[ITEM_AWAKENING] = gItemEffect_Awakening, [ITEM_AWAKENING] = gItemEffect_Awakening,
[ITEM_FULL_HEAL] = gItemEffect_FullHeal, [ITEM_FULL_HEAL] = gItemEffect_FullHeal,
[ITEM_ETHER] = gItemEffect_Ether, [ITEM_ETHER] = gItemEffect_Ether,
[ITEM_MAX_ETHER] = gItemEffect_MaxEther, [ITEM_MAX_ETHER] = gItemEffect_MaxEther,
[ITEM_ELIXIR] = gItemEffect_Elixir, [ITEM_ELIXIR] = gItemEffect_Elixir,
[ITEM_MAX_ELIXIR] = gItemEffect_MaxElixir, [ITEM_MAX_ELIXIR] = gItemEffect_MaxElixir,
[ITEM_BERRY_JUICE] = gItemEffect_BerryJuice, [ITEM_BERRY_JUICE] = gItemEffect_BerryJuice,
[ITEM_SACRED_ASH] = gItemEffect_SacredAsh, [ITEM_SACRED_ASH] = gItemEffect_SacredAsh,
[ITEM_SWEET_HEART] = gItemEffect_Potion, [ITEM_SWEET_HEART] = gItemEffect_Potion,
[ITEM_MAX_HONEY] = gItemEffect_MaxRevive, [ITEM_MAX_HONEY] = gItemEffect_MaxRevive,
// Regional Specialties // Regional Specialties
[ITEM_PEWTER_CRUNCHIES] = gItemEffect_FullHeal, [ITEM_PEWTER_CRUNCHIES] = gItemEffect_FullHeal,
[ITEM_RAGE_CANDY_BAR] = gItemEffect_FullHeal, [ITEM_RAGE_CANDY_BAR] = gItemEffect_FullHeal,
[ITEM_LAVA_COOKIE] = gItemEffect_FullHeal, [ITEM_LAVA_COOKIE] = gItemEffect_FullHeal,
[ITEM_OLD_GATEAU] = gItemEffect_FullHeal, [ITEM_OLD_GATEAU] = gItemEffect_FullHeal,
[ITEM_CASTELIACONE] = gItemEffect_FullHeal, [ITEM_CASTELIACONE] = gItemEffect_FullHeal,
[ITEM_LUMIOSE_GALETTE] = gItemEffect_FullHeal, [ITEM_LUMIOSE_GALETTE] = gItemEffect_FullHeal,
[ITEM_SHALOUR_SABLE] = gItemEffect_FullHeal, [ITEM_SHALOUR_SABLE] = gItemEffect_FullHeal,
[ITEM_BIG_MALASADA] = gItemEffect_FullHeal, [ITEM_BIG_MALASADA] = gItemEffect_FullHeal,
// Vitamins // Vitamins
[ITEM_HP_UP] = gItemEffect_HPUp, [ITEM_HP_UP] = gItemEffect_HPUp,
[ITEM_PROTEIN] = gItemEffect_Protein, [ITEM_PROTEIN] = gItemEffect_Protein,
[ITEM_IRON] = gItemEffect_Iron, [ITEM_IRON] = gItemEffect_Iron,
[ITEM_CALCIUM] = gItemEffect_Calcium, [ITEM_CALCIUM] = gItemEffect_Calcium,
[ITEM_ZINC] = gItemEffect_Zinc, [ITEM_ZINC] = gItemEffect_Zinc,
[ITEM_CARBOS] = gItemEffect_Carbos, [ITEM_CARBOS] = gItemEffect_Carbos,
[ITEM_PP_UP] = gItemEffect_PPUp, [ITEM_PP_UP] = gItemEffect_PPUp,
[ITEM_PP_MAX] = gItemEffect_PPMax, [ITEM_PP_MAX] = gItemEffect_PPMax,
// EV Feathers // EV Feathers
[ITEM_HEALTH_FEATHER] = gItemEffect_HpFeather, [ITEM_HEALTH_FEATHER] = gItemEffect_HpFeather,
[ITEM_MUSCLE_FEATHER] = gItemEffect_AtkFeather, [ITEM_MUSCLE_FEATHER] = gItemEffect_AtkFeather,
[ITEM_RESIST_FEATHER] = gItemEffect_DefFeather, [ITEM_RESIST_FEATHER] = gItemEffect_DefFeather,
[ITEM_GENIUS_FEATHER] = gItemEffect_SpatkFeather, [ITEM_GENIUS_FEATHER] = gItemEffect_SpatkFeather,
[ITEM_CLEVER_FEATHER] = gItemEffect_SpdefFeather, [ITEM_CLEVER_FEATHER] = gItemEffect_SpdefFeather,
[ITEM_SWIFT_FEATHER] = gItemEffect_SpeedFeather, [ITEM_SWIFT_FEATHER] = gItemEffect_SpeedFeather,
// Candy // Candy
[ITEM_RARE_CANDY] = gItemEffect_RareCandy, [ITEM_RARE_CANDY] = gItemEffect_RareCandy,
[ITEM_EXP_CANDY_XS] = gItemEffect_RareCandy, [ITEM_EXP_CANDY_XS] = gItemEffect_RareCandy,
[ITEM_EXP_CANDY_S] = gItemEffect_RareCandy, [ITEM_EXP_CANDY_S] = gItemEffect_RareCandy,
[ITEM_EXP_CANDY_M] = gItemEffect_RareCandy, [ITEM_EXP_CANDY_M] = gItemEffect_RareCandy,
[ITEM_EXP_CANDY_L] = gItemEffect_RareCandy, [ITEM_EXP_CANDY_L] = gItemEffect_RareCandy,
[ITEM_EXP_CANDY_XL] = gItemEffect_RareCandy, [ITEM_EXP_CANDY_XL] = gItemEffect_RareCandy,
//[ITEM_DYNAMAX_CANDY] = gItemEffect_DynamaxCandy, // Todo //[ITEM_DYNAMAX_CANDY] = gItemEffect_DynamaxCandy, // Todo
// Medicinal Flutes // Medicinal Flutes
[ITEM_BLUE_FLUTE] = gItemEffect_BlueFlute, [ITEM_BLUE_FLUTE] = gItemEffect_BlueFlute,
[ITEM_YELLOW_FLUTE] = gItemEffect_YellowFlute, [ITEM_YELLOW_FLUTE] = gItemEffect_YellowFlute,
[ITEM_RED_FLUTE] = gItemEffect_RedFlute, [ITEM_RED_FLUTE] = gItemEffect_RedFlute,
// X Items // X Items
[ITEM_X_ATTACK] = gItemEffect_XAttack, [ITEM_X_ATTACK] = gItemEffect_XAttack,
[ITEM_X_DEFENSE] = gItemEffect_XDefense, [ITEM_X_DEFENSE] = gItemEffect_XDefense,
[ITEM_X_SPEED] = gItemEffect_XSpeed, [ITEM_X_SPEED] = gItemEffect_XSpeed,
[ITEM_X_ACCURACY] = gItemEffect_XAccuracy, [ITEM_X_ACCURACY] = gItemEffect_XAccuracy,
[ITEM_X_SP_ATK] = gItemEffect_XSpecialAttack, [ITEM_X_SP_ATK] = gItemEffect_XSpecialAttack,
[ITEM_X_SP_DEF] = gItemEffect_XSpecialDefense, [ITEM_X_SP_DEF] = gItemEffect_XSpecialDefense,
[ITEM_DIRE_HIT] = gItemEffect_DireHit, [ITEM_DIRE_HIT] = gItemEffect_DireHit,
[ITEM_GUARD_SPEC] = gItemEffect_GuardSpec, [ITEM_GUARD_SPEC] = gItemEffect_GuardSpec,
//[ITEM_MAX_MUSHROOMS] = gItemEffect_MaxMushrooms, // Todo //[ITEM_MAX_MUSHROOMS] = gItemEffect_MaxMushrooms, // Todo
// Evolution Items // Evolution Items
[ITEM_FIRE_STONE] = gItemEffect_EvoItem, [ITEM_FIRE_STONE] = gItemEffect_EvoItem,
[ITEM_WATER_STONE] = gItemEffect_EvoItem, [ITEM_WATER_STONE] = gItemEffect_EvoItem,
[ITEM_THUNDER_STONE] = gItemEffect_EvoItem, [ITEM_THUNDER_STONE] = gItemEffect_EvoItem,
[ITEM_LEAF_STONE] = gItemEffect_EvoItem, [ITEM_LEAF_STONE] = gItemEffect_EvoItem,
[ITEM_ICE_STONE] = gItemEffect_EvoItem, [ITEM_ICE_STONE] = gItemEffect_EvoItem,
[ITEM_SUN_STONE] = gItemEffect_EvoItem, [ITEM_SUN_STONE] = gItemEffect_EvoItem,
[ITEM_MOON_STONE] = gItemEffect_EvoItem, [ITEM_MOON_STONE] = gItemEffect_EvoItem,
[ITEM_SHINY_STONE] = gItemEffect_EvoItem, [ITEM_SHINY_STONE] = gItemEffect_EvoItem,
[ITEM_DUSK_STONE] = gItemEffect_EvoItem, [ITEM_DUSK_STONE] = gItemEffect_EvoItem,
[ITEM_DAWN_STONE] = gItemEffect_EvoItem, [ITEM_DAWN_STONE] = gItemEffect_EvoItem,
[ITEM_SWEET_APPLE] = gItemEffect_EvoItem, [ITEM_SWEET_APPLE] = gItemEffect_EvoItem,
[ITEM_TART_APPLE] = gItemEffect_EvoItem, [ITEM_TART_APPLE] = gItemEffect_EvoItem,
[ITEM_CRACKED_POT] = gItemEffect_EvoItem, [ITEM_CRACKED_POT] = gItemEffect_EvoItem,
[ITEM_CHIPPED_POT] = gItemEffect_EvoItem, [ITEM_CHIPPED_POT] = gItemEffect_EvoItem,
[ITEM_GALARICA_CUFF] = gItemEffect_EvoItem, [ITEM_GALARICA_CUFF] = gItemEffect_EvoItem,
[ITEM_GALARICA_WREATH] = 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 // Berries
[ITEM_CHERI_BERRY] = gItemEffect_CheriBerry, [ITEM_CHERI_BERRY] = gItemEffect_CheriBerry,
[ITEM_CHESTO_BERRY] = gItemEffect_ChestoBerry, [ITEM_CHESTO_BERRY] = gItemEffect_ChestoBerry,
[ITEM_PECHA_BERRY] = gItemEffect_PechaBerry, [ITEM_PECHA_BERRY] = gItemEffect_PechaBerry,
[ITEM_RAWST_BERRY] = gItemEffect_RawstBerry, [ITEM_RAWST_BERRY] = gItemEffect_RawstBerry,
[ITEM_ASPEAR_BERRY] = gItemEffect_AspearBerry, [ITEM_ASPEAR_BERRY] = gItemEffect_AspearBerry,
[ITEM_LEPPA_BERRY] = gItemEffect_LeppaBerry, [ITEM_LEPPA_BERRY] = gItemEffect_LeppaBerry,
[ITEM_ORAN_BERRY] = gItemEffect_OranBerry, [ITEM_ORAN_BERRY] = gItemEffect_OranBerry,
[ITEM_PERSIM_BERRY] = gItemEffect_PersimBerry, [ITEM_PERSIM_BERRY] = gItemEffect_PersimBerry,
[ITEM_LUM_BERRY] = gItemEffect_FullHeal, [ITEM_LUM_BERRY] = gItemEffect_FullHeal,
[ITEM_SITRUS_BERRY] = gItemEffect_SitrusBerry, [ITEM_SITRUS_BERRY] = gItemEffect_SitrusBerry,
[ITEM_POMEG_BERRY] = gItemEffect_PomegBerry, [ITEM_POMEG_BERRY] = gItemEffect_PomegBerry,
[ITEM_KELPSY_BERRY] = gItemEffect_KelpsyBerry, [ITEM_KELPSY_BERRY] = gItemEffect_KelpsyBerry,
[ITEM_QUALOT_BERRY] = gItemEffect_QualotBerry, [ITEM_QUALOT_BERRY] = gItemEffect_QualotBerry,
[ITEM_HONDEW_BERRY] = gItemEffect_HondewBerry, [ITEM_HONDEW_BERRY] = gItemEffect_HondewBerry,
[ITEM_GREPA_BERRY] = gItemEffect_GrepaBerry, [ITEM_GREPA_BERRY] = gItemEffect_GrepaBerry,
[ITEM_TAMATO_BERRY] = gItemEffect_TamatoBerry, [ITEM_TAMATO_BERRY] = gItemEffect_TamatoBerry,
[LAST_BERRY_INDEX] = NULL, [LAST_BERRY_INDEX] = NULL,
}; };

View File

@ -3817,3 +3817,78 @@ static const u8 sLoadedDiceDesc[] = _(
"Rolls high numbers.\n" "Rolls high numbers.\n"
"Multihit strikes\n" "Multihit strikes\n"
"hit more times."); "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.");

View File

@ -5516,7 +5516,8 @@ void ItemUseCB_EvolutionStone(u8 taskId, TaskFunc task)
} }
else else
{ {
RemoveBagItem(gSpecialVar_ItemId, 1); if (ItemId_GetPocket(gSpecialVar_ItemId) != POCKET_KEY_ITEMS)
RemoveBagItem(gSpecialVar_ItemId, 1);
FreePartyPointers(); FreePartyPointers();
} }
} }

View File

@ -31,3 +31,27 @@ u16 Random2(void)
gRng2Value = ISO_RANDOMIZE1(gRng2Value); gRng2Value = ISO_RANDOMIZE1(gRng2Value);
return gRng2Value >> 16; 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;
}

View File

@ -3,7 +3,7 @@
SINGLE_BATTLE_TEST("Compound Eyes raises accuracy") SINGLE_BATTLE_TEST("Compound Eyes raises accuracy")
{ {
PASSES_RANDOMLY(91, 100); PASSES_RANDOMLY(91, 100, RNG_ACCURACY);
GIVEN { GIVEN {
ASSUME(gBattleMoves[MOVE_THUNDER].accuracy == 70); ASSUME(gBattleMoves[MOVE_THUNDER].accuracy == 70);
PLAYER(SPECIES_BUTTERFREE) { Ability(ABILITY_COMPOUND_EYES); }; PLAYER(SPECIES_BUTTERFREE) { Ability(ABILITY_COMPOUND_EYES); };
@ -21,12 +21,11 @@ SINGLE_BATTLE_TEST("Compound Eyes raises accuracy")
// than we expect. // than we expect.
SINGLE_BATTLE_TEST("Compound Eyes does not affect OHKO moves") SINGLE_BATTLE_TEST("Compound Eyes does not affect OHKO moves")
{ {
KNOWN_FAILING; PASSES_RANDOMLY(30, 100, RNG_ACCURACY);
PASSES_RANDOMLY(30, 100);
GIVEN { GIVEN {
ASSUME(gBattleMoves[MOVE_FISSURE].accuracy == 30); ASSUME(gBattleMoves[MOVE_FISSURE].accuracy == 30);
ASSUME(gBattleMoves[MOVE_FISSURE].effect == EFFECT_OHKO); ASSUME(gBattleMoves[MOVE_FISSURE].effect == EFFECT_OHKO);
PLAYER(SPECIES_BUTTERFREE) { Ability(ABILITY_TINTED_LENS); }; PLAYER(SPECIES_BUTTERFREE) { Ability(ABILITY_COMPOUND_EYES); };
OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET);
} WHEN { } WHEN {
TURN { MOVE(player, MOVE_FISSURE); } TURN { MOVE(player, MOVE_FISSURE); }

View File

@ -1,9 +1,6 @@
#include "global.h" #include "global.h"
#include "test_battle.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") SINGLE_BATTLE_TEST("Cute Charm inflicts infatuation on contact")
{ {
u32 move; u32 move;

View File

@ -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!");
}
}

View File

@ -16,7 +16,7 @@ SINGLE_BATTLE_TEST("Sand Veil prevents damage from sandstorm")
SINGLE_BATTLE_TEST("Sand Veil reduces accuracy during sandstorm") SINGLE_BATTLE_TEST("Sand Veil reduces accuracy during sandstorm")
{ {
PASSES_RANDOMLY(4,5); PASSES_RANDOMLY(4, 5, RNG_ACCURACY);
GIVEN { GIVEN {
ASSUME(gBattleMoves[MOVE_POUND].accuracy == 100); ASSUME(gBattleMoves[MOVE_POUND].accuracy == 100);
PLAYER(SPECIES_SANDSHREW) { Ability(ABILITY_SAND_VEIL); }; PLAYER(SPECIES_SANDSHREW) { Ability(ABILITY_SAND_VEIL); };

View File

@ -3,7 +3,7 @@
SINGLE_BATTLE_TEST("Stench has a 10% chance to flinch") SINGLE_BATTLE_TEST("Stench has a 10% chance to flinch")
{ {
PASSES_RANDOMLY(1,10); PASSES_RANDOMLY(1, 10, RNG_STENCH);
GIVEN { GIVEN {
ASSUME(gBattleMoves[MOVE_TACKLE].power > 0); ASSUME(gBattleMoves[MOVE_TACKLE].power > 0);
PLAYER(SPECIES_GRIMER) { Ability(ABILITY_STENCH); }; 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") SINGLE_BATTLE_TEST("Stench does not stack with King's Rock")
{ {
PASSES_RANDOMLY(1,10); KNOWN_FAILING;
PASSES_RANDOMLY(1, 10, RNG_STENCH);
GIVEN { GIVEN {
ASSUME(gItems[ITEM_KINGS_ROCK].holdEffect == HOLD_EFFECT_FLINCH); ASSUME(gItems[ITEM_KINGS_ROCK].holdEffect == HOLD_EFFECT_FLINCH);
ASSUME(gBattleMoves[MOVE_TACKLE].power > 0); ASSUME(gBattleMoves[MOVE_TACKLE].power > 0);

View File

@ -8,7 +8,7 @@ ASSUMPTIONS
SINGLE_BATTLE_TEST("Red Card switches the attacker with a random non-fainted replacement") 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 { GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); }
OPPONENT(SPECIES_WOBBUFFET); 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") 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 { GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); } PLAYER(SPECIES_WOBBUFFET) { Item(ITEM_RED_CARD); }
PLAYER(SPECIES_WYNAUT); PLAYER(SPECIES_WYNAUT);

View File

@ -10,7 +10,7 @@ SINGLE_BATTLE_TEST("Accuracy controls the proportion of misses")
PARAMETRIZE { move = MOVE_RAZOR_LEAF; } PARAMETRIZE { move = MOVE_RAZOR_LEAF; }
PARAMETRIZE { move = MOVE_SCRATCH; } PARAMETRIZE { move = MOVE_SCRATCH; }
ASSUME(0 < gBattleMoves[move].accuracy && gBattleMoves[move].accuracy <= 100); ASSUME(0 < gBattleMoves[move].accuracy && gBattleMoves[move].accuracy <= 100);
PASSES_RANDOMLY(gBattleMoves[move].accuracy, 100); PASSES_RANDOMLY(gBattleMoves[move].accuracy, 100, RNG_ACCURACY);
GIVEN { GIVEN {
PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET);
OPPONENT(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_THUNDER_SHOCK; }
PARAMETRIZE { move = MOVE_DISCHARGE; } PARAMETRIZE { move = MOVE_DISCHARGE; }
PARAMETRIZE { move = MOVE_NUZZLE; } PARAMETRIZE { move = MOVE_NUZZLE; }
ASSUME(gBattleMoves[move].accuracy == 100);
ASSUME(gBattleMoves[move].effect == EFFECT_PARALYZE_HIT); ASSUME(gBattleMoves[move].effect == EFFECT_PARALYZE_HIT);
ASSUME(0 < gBattleMoves[move].secondaryEffectChance && gBattleMoves[move].secondaryEffectChance <= 100); 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 { GIVEN {
PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET);
OPPONENT(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") SINGLE_BATTLE_TEST("Turn order is determined randomly if priority and speed tie")
{ {
KNOWN_FAILING; // The algorithm is significantly biased.
PASSES_RANDOMLY(1, 2); PASSES_RANDOMLY(1, 2);
GIVEN { GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Speed(1); } 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") SINGLE_BATTLE_TEST("Critical hits occur at a 1/24 rate")
{ {
ASSUME(B_CRIT_CHANCE >= GEN_7); ASSUME(B_CRIT_CHANCE >= GEN_7);
ASSUME(gBattleMoves[MOVE_SCRATCH].accuracy == 100); PASSES_RANDOMLY(1, 24, RNG_CRITICAL_HIT);
PASSES_RANDOMLY(100 / 24, 100);
GIVEN { GIVEN {
PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET);
} WHEN { } WHEN {
TURN { MOVE(player, MOVE_SCRATCH); } TURN { MOVE(player, MOVE_SCRATCH); }
} SCENE { } 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!");
} }
} }

View File

@ -9,7 +9,7 @@ ASSUMPTIONS
SINGLE_BATTLE_TEST("Sand Attack lowers Accuracy") SINGLE_BATTLE_TEST("Sand Attack lowers Accuracy")
{ {
ASSUME(gBattleMoves[MOVE_SCRATCH].accuracy == 100); 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 { GIVEN {
PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET);

22
test/move_effect_assist.c Normal file
View File

@ -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!");
}
}

View File

@ -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") DOUBLE_BATTLE_TEST("Defog lowers evasiveness by 1 and removes everything it can")
{ {
bool32 defogTurn = FALSE;
GIVEN { GIVEN {
ASSUME(gBattleMoves[MOVE_HAIL].effect == EFFECT_HAIL); ASSUME(gBattleMoves[MOVE_HAIL].effect == EFFECT_HAIL);
ASSUME(gSpeciesInfo[SPECIES_GLALIE].types[0] == TYPE_ICE); 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 { 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 { 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_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_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_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_SAFEGUARD); } TURN { MOVE(playerLeft, MOVE_MIST); MOVE(playerRight, MOVE_SAFEGUARD); MOVE(opponentLeft, MOVE_MIST); MOVE(opponentRight, MOVE_DEFOG, target: playerLeft); }
TURN { defogTurn = TRUE ; MOVE(opponentRight, MOVE_DEFOG, target:playerLeft);}
} SCENE { } SCENE {
if (defogTurn == TRUE) MESSAGE("Foe Glalie used Defog!");
{ MESSAGE("Glalie is protected by MIST!");
MESSAGE("Foe Glalie used Defog!"); ANIMATION(ANIM_TYPE_MOVE, MOVE_DEFOG, opponentRight);
MESSAGE("Glalie is protected by MIST!"); // Player side
ANIMATION(ANIM_TYPE_MOVE, MOVE_DEFOG, opponentRight); MESSAGE("Ally's Reflect wore off!");
// Player side MESSAGE("Ally's Light Screen wore off!");
MESSAGE("Ally's Reflect wore off!"); MESSAGE("Ally's Mist wore off!");
MESSAGE("Ally's Light Screen wore off!"); MESSAGE("Ally's Aurora Veil wore off!");
MESSAGE("Ally's Mist wore off!"); MESSAGE("Ally's Safeguard 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 spikes disappeared from the ground around your team!");
MESSAGE("The pointed stones disappeared from 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 poison spikes disappeared from the ground around your team!");
MESSAGE("The sticky web has disappeared from the ground around your team!"); MESSAGE("The sticky web has disappeared from the ground around your team!");
// Opponent side // Opponent side
MESSAGE("The spikes disappeared from the ground around the opposing team!"); MESSAGE("The spikes disappeared from the ground around the opposing team!");
MESSAGE("The pointed stones disappeared from 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 poison spikes disappeared from the ground around the opposing team!");
MESSAGE("The sticky web has disappeared from the ground around the opposing team!"); MESSAGE("The sticky web has disappeared from the ground around the opposing team!");
}
} }
} }

View File

@ -9,7 +9,7 @@ ASSUMPTIONS
SINGLE_BATTLE_TEST("Double Team raises Evasion") SINGLE_BATTLE_TEST("Double Team raises Evasion")
{ {
ASSUME(gBattleMoves[MOVE_SCRATCH].accuracy == 100); 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 { GIVEN {
PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET);

View File

@ -9,8 +9,7 @@ ASSUMPTIONS
SINGLE_BATTLE_TEST("Dragon Tail switches the target with a random non-fainted replacement") 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(1, 2, RNG_FORCE_RANDOM_SWITCH);
PASSES_RANDOMLY(90 * 1, 100 * 2);
GIVEN { GIVEN {
PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET);
OPPONENT(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") 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 { GIVEN {
PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT); PLAYER(SPECIES_WYNAUT);

View File

@ -8,7 +8,7 @@ ASSUMPTIONS
SINGLE_BATTLE_TEST("Thrash lasts for 2 or 3 turns") SINGLE_BATTLE_TEST("Thrash lasts for 2 or 3 turns")
{ {
PASSES_RANDOMLY(1, 2); PASSES_RANDOMLY(1, 2, RNG_RAMPAGE_TURNS);
GIVEN { GIVEN {
PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET);
OPPONENT(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") SINGLE_BATTLE_TEST("Thrash confuses the user after it finishes")
{ {
GIVEN { GIVEN {
RNGSeed(0x00000000);
PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET);
} WHEN { } WHEN {
@ -45,7 +44,6 @@ SINGLE_BATTLE_TEST("Thrash does not confuse the user if it is canceled on turn 1
{ {
GIVEN { GIVEN {
ASSUME(B_RAMPAGE_CANCELLING >= GEN_5); ASSUME(B_RAMPAGE_CANCELLING >= GEN_5);
RNGSeed(0x00000000);
PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET);
} WHEN { } WHEN {
@ -61,7 +59,6 @@ SINGLE_BATTLE_TEST("Thrash does not confuse the user if it is canceled on turn 2
{ {
GIVEN { GIVEN {
ASSUME(B_RAMPAGE_CANCELLING >= GEN_5); ASSUME(B_RAMPAGE_CANCELLING >= GEN_5);
RNGSeed(0x00000000);
PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET);
} WHEN { } WHEN {
@ -78,7 +75,6 @@ SINGLE_BATTLE_TEST("Thrash confuses the user if it is canceled on turn 3 of 3")
KNOWN_FAILING; KNOWN_FAILING;
GIVEN { GIVEN {
ASSUME(B_RAMPAGE_CANCELLING >= GEN_5); ASSUME(B_RAMPAGE_CANCELLING >= GEN_5);
RNGSeed(0x00000000);
PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET);
} WHEN { } WHEN {

View File

@ -8,7 +8,7 @@ ASSUMPTIONS
SINGLE_BATTLE_TEST("Roar switches the target with a random non-fainted replacement") 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 { GIVEN {
PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET);
OPPONENT(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") 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 { GIVEN {
PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET);
PLAYER(SPECIES_WYNAUT); PLAYER(SPECIES_WYNAUT);

View File

@ -6,16 +6,33 @@ ASSUMPTIONS
ASSUME(gBattleMoves[MOVE_HYPNOSIS].effect == EFFECT_SLEEP); 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 { GIVEN {
PLAYER(SPECIES_WOBBUFFET); PLAYER(SPECIES_WOBBUFFET);
OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET);
} WHEN { } WHEN {
TURN { MOVE(player, MOVE_HYPNOSIS); } TURN { MOVE(player, MOVE_HYPNOSIS); MOVE(opponent, MOVE_CELEBRATE); }
for (count = 0; count < turns; ++count)
TURN {}
} SCENE { } SCENE {
ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPNOSIS, player); ANIMATION(ANIM_TYPE_MOVE, MOVE_HYPNOSIS, player);
ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent); ANIMATION(ANIM_TYPE_STATUS, B_ANIM_STATUS_SLP, opponent);
MESSAGE("Foe Wobbuffet fell asleep!");
STATUS_ICON(opponent, sleep: TRUE); 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);
} }
} }

81
test/random.c Normal file
View File

@ -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));
}

View File

@ -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") SINGLE_BATTLE_TEST("Freeze has a 20% chance of being thawed")
{ {
PASSES_RANDOMLY(20, 100); PASSES_RANDOMLY(20, 100, RNG_FROZEN);
GIVEN { GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); } PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); }
OPPONENT(SPECIES_WOBBUFFET); 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); } PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_FREEZE); }
OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET);
} WHEN { } WHEN {
TURN { MOVE(player, MOVE_CELEBRATE); MOVE(opponent, MOVE_EMBER); } TURN { MOVE(opponent, MOVE_EMBER); MOVE(player, MOVE_CELEBRATE); }
} SCENE { } SCENE {
MESSAGE("Wobbuffet is frozen solid!");
MESSAGE("Foe Wobbuffet used Ember!"); MESSAGE("Foe Wobbuffet used Ember!");
MESSAGE("Wobbuffet was defrosted!"); MESSAGE("Wobbuffet was defrosted!");
STATUS_ICON(player, none: TRUE); 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") SINGLE_BATTLE_TEST("Paralysis has a 25% chance of skipping the turn")
{ {
PASSES_RANDOMLY(25, 100); PASSES_RANDOMLY(25, 100, RNG_PARALYSIS);
GIVEN { GIVEN {
PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_PARALYSIS); } PLAYER(SPECIES_WOBBUFFET) { Status1(STATUS1_PARALYSIS); }
OPPONENT(SPECIES_WOBBUFFET); OPPONENT(SPECIES_WOBBUFFET);

View File

@ -54,6 +54,16 @@ extern const u8 gTestRunnerI;
extern const char gTestRunnerArgv[256]; extern const char gTestRunnerArgv[256];
extern const struct TestRunner gAssumptionsRunner; extern const struct TestRunner gAssumptionsRunner;
struct FunctionTestRunnerState
{
u8 parameters;
u8 runParameter;
};
extern const struct TestRunner gFunctionTestRunner;
extern struct FunctionTestRunnerState *gFunctionTestRunnerState;
extern struct TestRunnerState gTestRunnerState; extern struct TestRunnerState gTestRunnerState;
void CB2_TestRunner(void); void CB2_TestRunner(void);
@ -63,6 +73,17 @@ void Test_ExitWithResult(enum TestResult, const char *fmt, ...);
s32 MgbaPrintf_(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 \ #define ASSUMPTIONS \
static void Assumptions(void); \ static void Assumptions(void); \
__attribute__((section(".tests"))) static const struct Test sAssumptions = \ __attribute__((section(".tests"))) static const struct Test sAssumptions = \
@ -139,6 +160,8 @@ s32 MgbaPrintf_(const char *fmt, ...);
#define KNOWN_FAILING \ #define KNOWN_FAILING \
Test_ExpectedResult(TEST_RESULT_FAIL) Test_ExpectedResult(TEST_RESULT_FAIL)
#define PARAMETRIZE if (gFunctionTestRunnerState->parameters++ == gFunctionTestRunnerState->runParameter)
#define TO_DO \ #define TO_DO \
Test_ExpectedResult(TEST_RESULT_TODO) Test_ExpectedResult(TEST_RESULT_TODO)

View File

@ -65,8 +65,9 @@
* single turn. MOVE causes the player to use Stun Spore and adds the * 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. * 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. * 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 * The test runner rigs the RNG so that unless otherwise specified,
* in a turn does not miss and activates its secondary effects (if any). * 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 * SCENE describes the player-visible output of the battle. In this case
* ANIMATION checks that the Stun Spore animation played, MESSAGE checks * ANIMATION checks that the Stun Spore animation played, MESSAGE checks
@ -228,12 +229,35 @@
* } * }
* } * }
* *
* PASSES_RANDOMLY(successes, trials) * PASSES_RANDOMLY(successes, trials, [tag])
* Checks that the test passes approximately successes/trials. Used for * Checks that the test passes successes/trials. If tag is provided, the
* testing RNG-based attacks, e.g.: * 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); * PASSES_RANDOMLY(gBattleMoves[move].accuracy, 100);
* Note that PASSES_RANDOMLY makes the tests run very slowly and should * Note that this mode of PASSES_RANDOMLY makes the tests run very
* be avoided where possible. * slowly and should be avoided where possible. If the mechanic you are
* testing is missing its tag, you should add it.
* *
* GIVEN * GIVEN
* Contains the initial state of the parties before the battle. * Contains the initial state of the parties before the battle.
@ -419,6 +443,7 @@
#include "battle_anim.h" #include "battle_anim.h"
#include "data.h" #include "data.h"
#include "item.h" #include "item.h"
#include "random.h"
#include "recorded_battle.h" #include "recorded_battle.h"
#include "test.h" #include "test.h"
#include "util.h" #include "util.h"
@ -433,6 +458,7 @@
// NOTE: If the stack is too small the test runner will probably crash // NOTE: If the stack is too small the test runner will probably crash
// or loop. // or loop.
#define BATTLE_TEST_STACK_SIZE 1024 #define BATTLE_TEST_STACK_SIZE 1024
#define MAX_TURNS 16
#define MAX_QUEUED_EVENTS 25 #define MAX_QUEUED_EVENTS 25
enum { BATTLE_TEST_SINGLES, BATTLE_TEST_DOUBLES }; enum { BATTLE_TEST_SINGLES, BATTLE_TEST_DOUBLES };
@ -512,6 +538,13 @@ struct QueuedEvent
} as; } as;
}; };
struct BattlerTurn
{
u8 hit:2;
u8 criticalHit:2;
u8 secondaryEffect:2;
};
struct BattleTestData struct BattleTestData
{ {
u8 stack[BATTLE_TEST_STACK_SIZE]; u8 stack[BATTLE_TEST_STACK_SIZE];
@ -533,14 +566,13 @@ struct BattleTestData
u8 turns; u8 turns;
u8 actionBattlers; u8 actionBattlers;
u8 moveBattlers; u8 moveBattlers;
bool8 hasRNGActions:1;
struct RecordedBattleSave recordedBattle; struct RecordedBattleSave recordedBattle;
u8 battleRecordTypes[MAX_BATTLERS_COUNT][BATTLER_RECORD_SIZE]; u8 battleRecordTypes[MAX_BATTLERS_COUNT][BATTLER_RECORD_SIZE];
u8 battleRecordSourceLineOffsets[MAX_BATTLERS_COUNT][BATTLER_RECORD_SIZE]; u8 battleRecordSourceLineOffsets[MAX_BATTLERS_COUNT][BATTLER_RECORD_SIZE];
u16 recordIndexes[MAX_BATTLERS_COUNT]; u16 recordIndexes[MAX_BATTLERS_COUNT];
struct BattlerTurn battleRecordTurns[MAX_TURNS][MAX_BATTLERS_COUNT];
u8 lastActionTurn; u8 lastActionTurn;
u8 nextRNGTurn;
u8 queuedEventsCount; u8 queuedEventsCount;
u8 queueGroupType; u8 queueGroupType;
@ -555,11 +587,12 @@ struct BattleTestRunnerState
u8 parametersCount; // Valid only in BattleTest_Setup. u8 parametersCount; // Valid only in BattleTest_Setup.
u8 parameters; u8 parameters;
u8 runParameter; u8 runParameter;
u16 rngTag;
u8 trials; u8 trials;
u8 expectedPasses;
u8 observedPasses;
u8 skippedTrials;
u8 runTrial; u8 runTrial;
u16 expectedRatio;
u16 observedRatio;
u16 trialRatio;
bool8 runRandomly:1; bool8 runRandomly:1;
bool8 runGiven:1; bool8 runGiven:1;
bool8 runWhen:1; bool8 runWhen:1;
@ -648,13 +681,20 @@ extern struct BattleTestRunnerState *gBattleTestRunnerState;
/* Parametrize */ /* Parametrize */
#undef PARAMETRIZE // Override test/test.h's implementation.
#define PARAMETRIZE if (gBattleTestRunnerState->parametersCount++ == i) #define PARAMETRIZE if (gBattleTestRunnerState->parametersCount++ == i)
/* Randomly */ /* 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 */ /* Given */
@ -728,6 +768,8 @@ struct MoveContext
u16 explicitHit:1; u16 explicitHit:1;
u16 criticalHit:1; u16 criticalHit:1;
u16 explicitCriticalHit:1; u16 explicitCriticalHit:1;
u16 secondaryEffect:1;
u16 explicitSecondaryEffect:1;
u16 megaEvolve:1; u16 megaEvolve:1;
u16 explicitMegaEvolve:1; u16 explicitMegaEvolve:1;
// TODO: u8 zMove:1; // TODO: u8 zMove:1;

View File

@ -4,6 +4,7 @@
#include "gpu_regs.h" #include "gpu_regs.h"
#include "main.h" #include "main.h"
#include "malloc.h" #include "malloc.h"
#include "random.h"
#include "test.h" #include "test.h"
#include "test_runner.h" #include "test_runner.h"
@ -12,6 +13,7 @@
void CB2_TestRunner(void); void CB2_TestRunner(void);
EWRAM_DATA struct TestRunnerState gTestRunnerState; EWRAM_DATA struct TestRunnerState gTestRunnerState;
EWRAM_DATA struct FunctionTestRunnerState *gFunctionTestRunnerState;
void TestRunner_Battle(const struct Test *); void TestRunner_Battle(const struct Test *);
@ -236,6 +238,38 @@ void Test_ExpectedResult(enum TestResult result)
gTestRunnerState.expectedResult = 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) static void Assumptions_Run(void *data)
{ {
void (*function)(void) = data; void (*function)(void) = data;
@ -294,11 +328,12 @@ static void Intr_Timer2(void)
void Test_ExitWithResult(enum TestResult result, const char *fmt, ...) void Test_ExitWithResult(enum TestResult result, const char *fmt, ...)
{ {
bool32 handled = FALSE;
gTestRunnerState.result = result; gTestRunnerState.result = result;
ReinitCallbacks(); ReinitCallbacks();
if (gTestRunnerState.test->runner->handleExitWithResult if (gTestRunnerState.test->runner->handleExitWithResult)
&& !gTestRunnerState.test->runner->handleExitWithResult(gTestRunnerState.test->data, result) handled = gTestRunnerState.test->runner->handleExitWithResult(gTestRunnerState.test->data, result);
&& gTestRunnerState.result != gTestRunnerState.expectedResult) if (!handled && gTestRunnerState.result != gTestRunnerState.expectedResult)
{ {
va_list va; va_list va;
va_start(va, fmt); va_start(va, fmt);

View File

@ -15,26 +15,10 @@
#define STATE gBattleTestRunnerState #define STATE gBattleTestRunnerState
#define DATA gBattleTestRunnerState->data #define DATA gBattleTestRunnerState->data
/* RNG seeds for controlling the first move of the turn. #define RNG_SEED_DEFAULT 0x00000000
* Found via brute force. */
/* Default seed, triggers most things. #undef Q_4_12
* The 1st roll % 100 is <= 29, to make 30%+ accuracycheck pass. #define Q_4_12(n) (s32)((n) * 4096)
* 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
EWRAM_DATA struct BattleTestRunnerState *gBattleTestRunnerState = NULL; EWRAM_DATA struct BattleTestRunnerState *gBattleTestRunnerState = NULL;
@ -129,12 +113,13 @@ static u32 BattleTest_EstimateCost(void *data)
if (!STATE) if (!STATE)
return 0; return 0;
STATE->runRandomly = TRUE; STATE->runRandomly = TRUE;
DATA.recordedBattle.rngSeed = RNG_SEED_DEFAULT;
InvokeTestFunction(test); InvokeTestFunction(test);
cost = 1; cost = 1;
if (STATE->parametersCount != 0) if (STATE->parametersCount != 0)
cost *= STATE->parametersCount; cost *= STATE->parametersCount;
if (STATE->trials != 0) if (STATE->trials == 1)
cost *= 3;
else if (STATE->trials > 1)
cost *= STATE->trials; cost *= STATE->trials;
FREE_AND_SET_NULL(STATE); FREE_AND_SET_NULL(STATE);
return cost; 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 // This does not take into account priority, statuses, or any other
// modifiers. // modifiers.
static void SetImplicitSpeeds(void) static void SetImplicitSpeeds(void)
@ -280,12 +287,82 @@ static void BattleTest_Run(void *data)
STATE->checkProgressTrial = 0; STATE->checkProgressTrial = 0;
STATE->checkProgressTurn = 0; STATE->checkProgressTurn = 0;
if (STATE->trials && STATE->parameters) PrintTestName();
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); u32 RandomUniform(enum RandomTag tag, u32 lo, u32 hi)
else if (STATE->parameters) {
MgbaPrintf_(":N%s %d/%d", gTestRunnerState.test->name, STATE->runParameter + 1, STATE->parameters); 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) 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); 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) if (++STATE->runTrial < STATE->trials)
{ {
switch (gTestRunnerState.result) PrintTestName();
{ gTestRunnerState.result = TEST_RESULT_PASS;
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;
DATA.recordedBattle.rngSeed = ISO_RANDOMIZE1(STATE->runTrial); DATA.recordedBattle.rngSeed = ISO_RANDOMIZE1(STATE->runTrial);
DATA.queuedEvent = 0; DATA.queuedEvent = 0;
DATA.lastActionTurn = 0; DATA.lastActionTurn = 0;
DATA.nextRNGTurn = 0;
SetVariablesForRecordedBattle(&DATA.recordedBattle); SetVariablesForRecordedBattle(&DATA.recordedBattle);
SetMainCallback2(CB2_InitBattle); SetMainCallback2(CB2_InitBattle);
} }
else else
{ {
// This is a tolerance of +/- 4%. // This is a tolerance of +/- ~2%.
if (abs(STATE->observedPasses - STATE->expectedPasses) <= 2) if (abs(STATE->observedRatio - STATE->expectedRatio) <= Q_4_12(0.02))
gTestRunnerState.result = TEST_RESULT_PASS; gTestRunnerState.result = TEST_RESULT_PASS;
else 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) 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_ERROR
&& result != TEST_RESULT_TIMEOUT && result != TEST_RESULT_TIMEOUT
&& STATE->runTrial < STATE->trials) && 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"); INVALID_IF(passes > trials, "%d passes specified, but only %d trials", passes, trials);
// This is a precision of 2%. STATE->rngTag = ctx.tag;
STATE->trials = 50;
STATE->expectedPasses = STATE->trials * passes / trials;
STATE->observedPasses = 0;
STATE->skippedTrials = 0;
STATE->runTrial = 0; 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) void RNGSeed_(u32 sourceLine, u32 seed)
@ -1025,6 +1106,31 @@ void Status1_(u32 sourceLine, u32 status1)
SetMonData(DATA.currentMon, MON_DATA_STATUS, &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 "<unknown>";
}
static void PushBattlerAction(u32 sourceLine, s32 battlerId, u32 actionType, u32 byte) static void PushBattlerAction(u32 sourceLine, s32 battlerId, u32 actionType, u32 byte)
{ {
u32 recordIndex = DATA.recordIndexes[battlerId]++; 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) 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 // An illegal move choice will cause the battle to request a new
// move slot and target. This detects the move slot. // move slot and target. This detects the move slot.
if (actionType == RECORDED_MOVE_SLOT if (actionType == RECORDED_MOVE_SLOT
@ -1122,10 +1218,11 @@ void BattleTest_CheckBattleRecordActionType(u32 battlerId, u32 recordIndex, u32
void OpenTurn(u32 sourceLine) void OpenTurn(u32 sourceLine)
{ {
INVALID_IF(DATA.turnState != TURN_CLOSED, "Nested TURN"); 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.turnState = TURN_OPEN;
DATA.actionBattlers = 0x00; DATA.actionBattlers = 0x00;
DATA.moveBattlers = 0x00; DATA.moveBattlers = 0x00;
DATA.hasRNGActions = FALSE;
} }
static void SetSlowerThan(s32 battlerId) static void SetSlowerThan(s32 battlerId)
@ -1195,7 +1292,6 @@ void Move(u32 sourceLine, struct BattlePokemon *battler, struct MoveContext ctx)
else if (moveId == MOVE_NONE) else if (moveId == MOVE_NONE)
{ {
INVALID_IF(DATA.explicitMoves[battlerId & BIT_SIDE] & (1 << DATA.currentMonIndexes[battlerId]), "Missing explicit %S", gMoveNames[ctx.move]); 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(mon, MON_DATA_MOVE1 + i, &ctx.move);
SetMonData(DATA.currentMon, MON_DATA_PP1 + i, &gBattleMoves[ctx.move].pp); SetMonData(DATA.currentMon, MON_DATA_PP1 + i, &gBattleMoves[ctx.move].pp);
moveSlot = i; moveSlot = i;
@ -1203,6 +1299,7 @@ void Move(u32 sourceLine, struct BattlePokemon *battler, struct MoveContext ctx)
break; break;
} }
} }
INVALID_IF(i == MAX_MON_MOVES, "Too many different moves for %s", BattlerIdentifier(battlerId));
} }
else if (ctx.explicitMoveSlot) else if (ctx.explicitMoveSlot)
{ {
@ -1227,6 +1324,7 @@ void Move(u32 sourceLine, struct BattlePokemon *battler, struct MoveContext ctx)
const struct BattleMove *move = &gBattleMoves[moveId]; const struct BattleMove *move = &gBattleMoves[moveId];
if (move->target == MOVE_TARGET_RANDOM if (move->target == MOVE_TARGET_RANDOM
|| move->target == MOVE_TARGET_BOTH || move->target == MOVE_TARGET_BOTH
|| move->target == MOVE_TARGET_DEPENDS
|| move->target == MOVE_TARGET_FOES_AND_ALLY || move->target == MOVE_TARGET_FOES_AND_ALLY
|| move->target == MOVE_TARGET_OPPONENTS_FIELD || move->target == MOVE_TARGET_OPPONENTS_FIELD
|| move->target == MOVE_TARGET_ALL_BATTLERS) || 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 (ctx.explicitHit)
{ DATA.battleRecordTurns[DATA.turns][battlerId].hit = 1 + ctx.hit;
if (DATA.hasRNGActions != 0) if (ctx.explicitCriticalHit)
Test_ExitWithResult(TEST_RESULT_ERROR, "%s:%d: hit only supported on the first move", gTestRunnerState.test->filename, sourceLine); DATA.battleRecordTurns[DATA.turns][battlerId].criticalHit = 1 + ctx.criticalHit;
INVALID_IF(DATA.recordedBattle.rngSeed != RNG_SEED_DEFAULT, "RNG seed already set"); if (ctx.explicitSecondaryEffect)
DATA.recordedBattle.rngSeed = RNG_SEED_MISS; DATA.battleRecordTurns[DATA.turns][battlerId].secondaryEffect = 1 + ctx.secondaryEffect;
}
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 (!(DATA.actionBattlers & (1 << battlerId))) if (!(DATA.actionBattlers & (1 << battlerId)))
{ {
@ -1288,14 +1377,6 @@ void Move(u32 sourceLine, struct BattlePokemon *battler, struct MoveContext ctx)
DATA.actionBattlers |= 1 << battlerId; DATA.actionBattlers |= 1 << battlerId;
DATA.moveBattlers |= 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) void ForcedMove(u32 sourceLine, struct BattlePokemon *battler)

140
test/trainer_control.c Normal file
View File

@ -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);
}