Expand trainerproc to support additional battle types (#6770)

Co-authored-by: Alex <93446519+AlexOn1ine@users.noreply.github.com>
This commit is contained in:
Eduardo Quezada 2025-05-17 09:08:44 -04:00 committed by GitHub
parent 4145404d22
commit 406fd7ce81
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 1091 additions and 908 deletions

View File

@ -81,6 +81,12 @@ struct TrainerMon
#define TRAINER_PARTY(partyArray) partyArray, .partySize = ARRAY_COUNT(partyArray)
enum TrainerBattleType
{
TRAINER_BATTLE_TYPE_SINGLES,
TRAINER_BATTLE_TYPE_DOUBLES,
};
struct Trainer
{
/*0x00*/ u64 aiFlags;
@ -90,8 +96,7 @@ struct Trainer
/*0x11*/ u8 encounterMusic_gender; // last bit is gender
/*0x12*/ u8 trainerPic;
/*0x13*/ u8 trainerName[TRAINER_NAME_LENGTH + 1];
/*0x1E*/ bool8 doubleBattle:1;
bool8 padding:1;
/*0x1E*/ u8 battleType:2;
u8 startingStatus:6; // this trainer starts a battle with a given status. see include/constants/battle.h for values
/*0x1F*/ u8 mugshotColor;
/*0x20*/ u8 partySize;
@ -253,12 +258,12 @@ static inline const u8 GetTrainerStartingStatusFromId(u16 trainerId)
return gTrainers[GetCurrentDifficultyLevel()][SanitizeTrainerId(trainerId)].startingStatus;
}
static inline const bool32 IsTrainerDoubleBattle(u16 trainerId)
static inline const enum TrainerBattleType GetTrainerBattleType(u16 trainerId)
{
u32 sanitizedTrainerId = SanitizeTrainerId(trainerId);
enum DifficultyLevel difficulty = GetTrainerDifficultyLevel(sanitizedTrainerId);
return gTrainers[difficulty][sanitizedTrainerId].doubleBattle;
return gTrainers[difficulty][sanitizedTrainerId].battleType;
}
static inline const u8 GetTrainerPartySizeFromId(u16 trainerId)

View File

@ -0,0 +1,12 @@
import re
def trainer_battle_types(data):
data = re.sub(re.escape("Double Battle: No"), "Battle Type: Singles", data)
data = re.sub(re.escape("Double Battle: Yes"), "Battle Type: Doubles", data)
return data
with open('src/data/trainers.party', 'r') as file:
data = file.read()
with open('src/data/trainers.party', 'w') as file:
file.write(trainer_battle_types(data))

View File

@ -533,7 +533,14 @@ static void CB2_InitBattleInternal(void)
| BATTLE_TYPE_TRAINER_HILL
| BATTLE_TYPE_RECORDED)))
{
gBattleTypeFlags |= (IsTrainerDoubleBattle(TRAINER_BATTLE_PARAM.opponentA) ? BATTLE_TYPE_DOUBLE : 0);
switch (GetTrainerBattleType(TRAINER_BATTLE_PARAM.opponentA))
{
case TRAINER_BATTLE_TYPE_SINGLES:
break;
case TRAINER_BATTLE_TYPE_DOUBLES:
gBattleTypeFlags |= BATTLE_TYPE_DOUBLE;
break;
}
}
InitBattleBgsVideo();
@ -1910,7 +1917,7 @@ u8 CreateNPCTrainerPartyFromTrainer(struct Pokemon *party, const struct Trainer
u32 fixedOtId = 0;
u32 abilityNum = 0;
if (trainer->doubleBattle == TRUE)
if (trainer->battleType != TRAINER_BATTLE_TYPE_SINGLES)
personalityValue = 0x80;
else if (trainer->encounterMusic_gender & F_TRAINER_FEMALE)
personalityValue = 0x78; // Use personality more likely to result in a female Pokémon

View File

@ -772,7 +772,7 @@ u8 GetWildBattleTransition(void)
u8 GetTrainerBattleTransition(void)
{
u8 minPartyCount;
u8 minPartyCount = 1;
u8 transitionType;
u8 enemyLevel;
u8 playerLevel;
@ -792,10 +792,15 @@ u8 GetTrainerBattleTransition(void)
|| trainerClass == TRAINER_CLASS_AQUA_ADMIN)
return B_TRANSITION_AQUA;
if (IsTrainerDoubleBattle(trainerId))
minPartyCount = 2; // double battles always at least have 2 Pokémon.
else
switch (GetTrainerBattleType(trainerId))
{
case TRAINER_BATTLE_TYPE_SINGLES:
minPartyCount = 1;
break;
case TRAINER_BATTLE_TYPE_DOUBLES:
minPartyCount = 2; // double battles always at least have 2 Pokémon.
break;
}
transitionType = GetBattleTransitionTypeByMap();
enemyLevel = GetSumOfEnemyPartyLevel(trainerId, minPartyCount);

File diff suppressed because it is too large Load Diff

View File

@ -20,7 +20,7 @@ Optional (but still recommended) fields for trainers:
- Music
- Items (Some Item / Another Item / Third Item)
(Can also be specified with ITEM_SOME_ITEM)
- Double Battle (Yes/No, defaults to No)
- Battle Type (Singles / Doubles, defaults to Singles)
- AI (Ai Flag / Another Flag / Third Flag / ...
see "constants/battle_ai.h" for all flags)
- Mugshot (enable Mugshots during battle transition

View File

@ -12,7 +12,7 @@
#include "constants/trainers.h"
#include "constants/battle.h"
#define NUM_TEST_TRAINERS 9
#define NUM_TEST_TRAINERS 11
static const struct Trainer sTestTrainers[DIFFICULTY_COUNT][NUM_TEST_TRAINERS] =
{
@ -282,3 +282,12 @@ TEST("Trainer Party Pool can choose which functions to use for picking mons")
EXPECT(GetMonData(&testParty[1], MON_DATA_SPECIES) == SPECIES_WOBBUFFET);
Free(testParty);
}
TEST("trainerproc supports both Double Battle: Yes and Battle Type: Doubles")
{
u32 currTrainer;
PARAMETRIZE { currTrainer = 9; }
PARAMETRIZE { currTrainer = 10; }
const struct Trainer trainer = sTestTrainers[GetTrainerDifficultyLevelTest(currTrainer)][currTrainer];
EXPECT(trainer.battleType == TRAINER_BATTLE_TYPE_DOUBLES);
}

View File

@ -21,7 +21,7 @@
#line 6
TRAINER_ENCOUNTER_MUSIC_MALE,
#line 7
.doubleBattle = FALSE,
.battleType = TRAINER_BATTLE_TYPE_SINGLES,
.partySize = 3,
.party = (const struct TrainerMon[])
{
@ -101,7 +101,7 @@
#line 38
TRAINER_ENCOUNTER_MUSIC_MALE,
#line 39
.doubleBattle = FALSE,
.battleType = TRAINER_BATTLE_TYPE_SINGLES,
.partySize = 1,
.party = (const struct TrainerMon[])
{
@ -132,7 +132,7 @@
#line 50
TRAINER_ENCOUNTER_MUSIC_MALE,
#line 51
.doubleBattle = FALSE,
.battleType = TRAINER_BATTLE_TYPE_SINGLES,
.partySize = 1,
.party = (const struct TrainerMon[])
{
@ -163,7 +163,7 @@
#line 62
TRAINER_ENCOUNTER_MUSIC_MALE,
#line 63
.doubleBattle = FALSE,
.battleType = TRAINER_BATTLE_TYPE_SINGLES,
.partySize = 1,
.party = (const struct TrainerMon[])
{
@ -194,7 +194,7 @@
#line 74
TRAINER_ENCOUNTER_MUSIC_MALE,
#line 75
.doubleBattle = FALSE,
.battleType = TRAINER_BATTLE_TYPE_SINGLES,
.partySize = 1,
.party = (const struct TrainerMon[])
{
@ -224,7 +224,7 @@
#line 86
TRAINER_ENCOUNTER_MUSIC_MALE,
#line 87
.doubleBattle = FALSE,
.battleType = TRAINER_BATTLE_TYPE_SINGLES,
#line 88
.partySize = 1,
.poolSize = 4,
@ -289,7 +289,7 @@
#line 103
TRAINER_ENCOUNTER_MUSIC_MALE,
#line 104
.doubleBattle = FALSE,
.battleType = TRAINER_BATTLE_TYPE_SINGLES,
#line 105
.partySize = 3,
.poolSize = 6,
@ -384,7 +384,7 @@
#line 128
TRAINER_ENCOUNTER_MUSIC_MALE,
#line 129
.doubleBattle = TRUE,
.battleType = TRAINER_BATTLE_TYPE_DOUBLES,
#line 131
.poolRuleIndex = POOL_RULESET_WEATHER_DOUBLES,
#line 130
@ -533,7 +533,7 @@
#line 166
TRAINER_ENCOUNTER_MUSIC_MALE,
#line 167
.doubleBattle = FALSE,
.battleType = TRAINER_BATTLE_TYPE_SINGLES,
#line 169
.poolRuleIndex = POOL_RULESET_BASIC,
#line 168
@ -595,7 +595,7 @@
#line 185
TRAINER_ENCOUNTER_MUSIC_MALE,
#line 186
.doubleBattle = FALSE,
.battleType = TRAINER_BATTLE_TYPE_SINGLES,
#line 188
.poolRuleIndex = POOL_RULESET_BASIC,
#line 189
@ -655,7 +655,7 @@
#line 203
TRAINER_ENCOUNTER_MUSIC_MALE,
#line 204
.doubleBattle = FALSE,
.battleType = TRAINER_BATTLE_TYPE_SINGLES,
#line 206
.poolRuleIndex = POOL_RULESET_BASIC,
#line 207
@ -704,3 +704,89 @@
},
},
},
#line 217
[DIFFICULTY_NORMAL][9] =
{
#line 218
.trainerName = _("Test9"),
#line 219
.trainerClass = TRAINER_CLASS_PKMN_TRAINER_1,
#line 220
.trainerPic = TRAINER_PIC_RED,
.encounterMusic_gender =
#line 222
TRAINER_ENCOUNTER_MUSIC_MALE,
#line 223
.battleType = TRAINER_BATTLE_TYPE_DOUBLES,
#line 224
.partySize = 2,
.poolSize = 2,
.party = (const struct TrainerMon[])
{
{
#line 226
.species = SPECIES_WYNAUT,
.gender = TRAINER_MON_RANDOM_GENDER,
#line 227
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
#line 227
.lvl = 100,
.nature = NATURE_HARDY,
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
},
{
#line 228
.species = SPECIES_WOBBUFFET,
.gender = TRAINER_MON_RANDOM_GENDER,
#line 229
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
#line 229
.lvl = 100,
.nature = NATURE_HARDY,
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
},
},
},
#line 230
[DIFFICULTY_NORMAL][10] =
{
#line 231
.trainerName = _("Test10"),
#line 232
.trainerClass = TRAINER_CLASS_PKMN_TRAINER_1,
#line 233
.trainerPic = TRAINER_PIC_RED,
.encounterMusic_gender =
#line 235
TRAINER_ENCOUNTER_MUSIC_MALE,
#line 236
.battleType = TRAINER_BATTLE_TYPE_DOUBLES,
#line 237
.partySize = 2,
.poolSize = 2,
.party = (const struct TrainerMon[])
{
{
#line 239
.species = SPECIES_WYNAUT,
.gender = TRAINER_MON_RANDOM_GENDER,
#line 240
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
#line 240
.lvl = 100,
.nature = NATURE_HARDY,
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
},
{
#line 241
.species = SPECIES_WOBBUFFET,
.gender = TRAINER_MON_RANDOM_GENDER,
#line 242
.iv = TRAINER_PARTY_IVS(31, 31, 31, 31, 31, 31),
#line 242
.lvl = 100,
.nature = NATURE_HARDY,
.dynamaxLevel = MAX_DYNAMAX_LEVEL,
},
},
},

View File

@ -4,7 +4,7 @@ Class: Pkmn Trainer 1
Pic: Red
Gender: Male
Music: Male
Double Battle: No
Battle Type: Singles
Bubbles (Wobbuffet) (F) @ Assault Vest
Hasty Nature
@ -36,7 +36,7 @@ Class: Pkmn Trainer 1
Pic: Red
Gender: Male
Music: Male
Double Battle: No
Battle Type: Singles
Difficulty: Normal
Mewtwo
@ -48,7 +48,7 @@ Class: Pkmn Trainer 1
Pic: Red
Gender: Male
Music: Male
Double Battle: No
Battle Type: Singles
Difficulty: Normal
Mewtwo
@ -60,7 +60,7 @@ Class: Pkmn Trainer 1
Pic: Red
Gender: Male
Music: Male
Double Battle: No
Battle Type: Singles
Difficulty: Easy
Metapod
@ -72,7 +72,7 @@ Class: Pkmn Trainer 1
Pic: Red
Gender: Male
Music: Male
Double Battle: No
Battle Type: Singles
Difficulty: Hard
Arceus
@ -84,7 +84,7 @@ Class: Pkmn Trainer 1
Pic: Red
Gender: Male
Music: Male
Double Battle: No
Battle Type: Singles
Party Size: 1
Wynaut
@ -101,7 +101,7 @@ Class: Pkmn Trainer 1
Pic: Red
Gender: Male
Music: Male
Double Battle: No
Battle Type: Singles
Party Size: 3
Wynaut
@ -126,7 +126,7 @@ Class: Pkmn Trainer 1
Pic: Red
Gender: Male
Music: Male
Double Battle: Yes
Battle Type: Doubles
Party Size: 3
Pool Rules: Weather Doubles
@ -164,7 +164,7 @@ Class: Pkmn Trainer 1
Pic: Red
Gender: Male
Music: Male
Double Battle: No
Battle Type: Singles
Party Size: 2
Pool Rules: Basic
@ -183,7 +183,7 @@ Class: Pkmn Trainer 1
Pic: Red
Gender: Male
Music: Male
Double Battle: No
Battle Type: Singles
Party Size: 2
Pool Rules: Basic
Pool Prune: Test
@ -201,7 +201,7 @@ Class: Pkmn Trainer 1
Pic: Red
Gender: Male
Music: Male
Double Battle: No
Battle Type: Singles
Party Size: 2
Pool Rules: Basic
Pool Pick Functions: Lowest
@ -213,3 +213,29 @@ Wobbuffet
Eevee
Tags: Lead
=== 9 ===
Name: Test9
Class: Pkmn Trainer 1
Pic: Red
Gender: Male
Music: Male
Double Battle: Yes
Party Size: 2
Wynaut
Wobbuffet
=== 10 ===
Name: Test10
Class: Pkmn Trainer 1
Pic: Red
Gender: Male
Music: Male
Battle Type: Doubles
Party Size: 2
Wynaut
Wobbuffet

View File

@ -38,6 +38,12 @@ enum Gender
GENDER_FEMALE,
};
enum BattleType
{
BATTLE_TYPE_SINGLE,
BATTLE_TYPE_DOUBLE,
};
// TODO: Support Hidden Power.
struct Pokemon
{
@ -117,8 +123,8 @@ struct Trainer
struct String name;
int name_line;
bool double_battle;
int double_battle_line;
enum BattleType battle_type;
int battle_type_line;
struct Pokemon pokemon[PARTY_SIZE];
int pokemon_n;
@ -841,12 +847,7 @@ static struct String token_string(const struct Token *t)
static bool token_gender(struct Parser *p, const struct Token *t, enum Gender *g)
{
if (is_empty_token(t))
{
*g = GENDER_ANY;
return true;
}
else if (is_literal_token(t, "M") || is_literal_token(t, "Male"))
if (is_literal_token(t, "M") || is_literal_token(t, "Male"))
{
*g = GENDER_MALE;
return true;
@ -862,6 +863,24 @@ static bool token_gender(struct Parser *p, const struct Token *t, enum Gender *g
}
}
static bool token_battle_type(struct Parser *p, const struct Token *t, enum BattleType *g)
{
if (is_literal_token(t, "Single") || is_literal_token(t, "Singles"))
{
*g = BATTLE_TYPE_SINGLE;
return true;
}
else if (is_literal_token(t, "Double") || is_literal_token(t, "Doubles"))
{
*g = BATTLE_TYPE_DOUBLE;
return true;
}
else
{
return set_parse_error(p, t->location, "invalid battle type");
}
}
static bool token_stats(struct Parser *p, const struct Token *t, struct Stats *stats, bool require_all)
{
struct Source source = {
@ -1195,10 +1214,20 @@ static bool parse_trainer(struct Parser *p, const struct Parsed *parsed, struct
}
else if (is_literal_token(&key, "Double Battle"))
{
if (trainer->double_battle_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'Double Battle'");
trainer->double_battle_line = value.location.line;
if (!token_bool(p, &value, &trainer->double_battle))
if (trainer->battle_type_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'Double Battle' or 'Battle Type'");
trainer->battle_type_line = value.location.line;
bool is_double_battle;
if (!token_bool(p, &value, &is_double_battle))
any_error = !show_parse_error(p);
trainer->battle_type = is_double_battle ? BATTLE_TYPE_DOUBLE : BATTLE_TYPE_SINGLE;
}
else if (is_literal_token(&key, "Battle Type"))
{
if (trainer->battle_type_line)
any_error = !set_show_parse_error(p, key.location, "duplicate 'Double Battle' or 'Battle Type'");
trainer->battle_type_line = value.location.line;
if (!token_battle_type(p, &value, &trainer->battle_type))
any_error = !show_parse_error(p);
}
else if (is_literal_token(&key, "Mugshot"))
@ -1253,7 +1282,7 @@ static bool parse_trainer(struct Parser *p, const struct Parsed *parsed, struct
}
else
{
any_error = !set_show_parse_error(p, key.location, "expected one of 'Name', 'Class', 'Pic', 'Gender', 'Music', 'Items', 'Double Battle', 'Difficulty', 'Party Size', 'Pool Rules', 'Pool Pick Functions', 'Pool Prune' or 'AI'");
any_error = !set_show_parse_error(p, key.location, "expected one of 'Name', 'Class', 'Pic', 'Gender', 'Music', 'Items', 'Battle Type', 'Difficulty', 'Party Size', 'Pool Rules', 'Pool Pick Functions', 'Pool Prune' or 'AI'");
}
}
if (!trainer->pic_line)
@ -1294,7 +1323,9 @@ static bool parse_trainer(struct Parser *p, const struct Parsed *parsed, struct
pokemon->nickname = token_string(&nickname);
pokemon->species = token_string(&species);
if (!token_gender(p, &gender, &pokemon->gender))
if (is_empty_token(&gender))
pokemon->gender = GENDER_ANY;
else if (!token_gender(p, &gender, &pokemon->gender))
any_error = !show_parse_error(p);
pokemon->item = token_string(&item);
pokemon->header_line = species.location.line;
@ -1757,12 +1788,14 @@ static void fprint_trainers(const char *output_path, FILE *f, struct Parsed *par
fprintf(f, " },\n");
}
if (trainer->double_battle_line)
if (trainer->battle_type_line)
{
fprintf(f, "#line %d\n", trainer->double_battle_line);
fprintf(f, " .doubleBattle = ");
fprint_bool(f, trainer->double_battle);
fprintf(f, ",\n");
fprintf(f, "#line %d\n", trainer->battle_type_line);
fprintf(f, " .battleType = ");
if (trainer->battle_type == BATTLE_TYPE_DOUBLE)
fprintf(f, "TRAINER_BATTLE_TYPE_DOUBLES,\n");
else
fprintf(f, "TRAINER_BATTLE_TYPE_SINGLES,\n");
}
if (trainer->ai_flags_n > 0)