forked from DizzyEggg/pokeemerald
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
trainerproc: Competitive-formatted trainers and teams
$ python3 1.8/convert_parties.py src/data/trainers.h src/data/trainer_parties.h src/data/npc_trainers.party Is available to convert Trainer Control-formatted trainers/parties into Competitive-formatted ones. Multiple '#include's can be placed in the trainer section of src/data.c to support spreading the trainers across multiple .party files. trainerproc does not interpret the values, leaving that job to the C compiler, so we use '#line' to associate those errors with the lines in the .party file(s). Because the columns don't make sense we use -fno-show-column and -fno-diagostics-show-caret. We might want to move gTrainers into its own file so that the rest of src/data.c isn't affected by those flags.
- Loading branch information
Showing
13 changed files
with
18,215 additions
and
22,102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,3 +40,5 @@ prefabs.json | |
*.sym | ||
*.js | ||
src/data/map_group_count.h | ||
src/data/npc_trainers.h | ||
test/battle/trainer_control.h |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,272 @@ | ||
# If you have extra members in 'TrainerMon': | ||
# 1. Add a regular expression which matches that member (e.g. 'shadow_definition'). | ||
# 2. Match that regular expression in 'convert' and write into 'attributes' with the key that 'trainerproc' should parse. | ||
# 3. Add the key used in 'attributes' to 'pokemon_attribute_order'. | ||
# 4. Update 'trainerproc.c' to parse the new key. | ||
|
||
import re | ||
import sys | ||
|
||
is_blank = re.compile(r'^[ \t]*$') | ||
|
||
begin_party_definition = re.compile(r'struct TrainerMon (\w+)\[\] =') | ||
end_party_definition = re.compile(r'^};') | ||
begin_pokemon_definition = re.compile(r'^ {$') | ||
end_pokemon_definition = re.compile(r'^ },?$') | ||
level_definition = re.compile(r'\.lvl = (\d+)') | ||
species_definition = re.compile(r'\.species = SPECIES_(\w+)') | ||
gender_definition = re.compile(r'\.gender = TRAINER_MON_(\w+)') | ||
nickname_definition = re.compile(r'\.nickname = COMPOUND_STRING\("([^"]+)"\)') | ||
item_definition = re.compile(r'\.heldItem = ITEM_(\w+)') | ||
ball_definition = re.compile(r'\.ball = ITEM_(\w+)') | ||
ability_definition = re.compile(r'\.ability = ABILITY_(\w+)') | ||
friendship_definition = re.compile(r'\.friendship = (\d+)') | ||
shiny_definition = re.compile(r'\.isShiny = (\w+)') | ||
ivs_definition = re.compile(r'\.iv = TRAINER_PARTY_IVS\(([0-9 ]+),([0-9 ]+),([0-9 ]+),([0-9 ]+),([0-9 ]+),([0-9 ]+)\)') | ||
evs_definition = re.compile(r'\.ev = TRAINER_PARTY_EVS\(([0-9 ]+),([0-9 ]+),([0-9 ]+),([0-9 ]+),([0-9 ]+),([0-9 ]+)\)') | ||
moves_definition = re.compile(r'\.moves = \{([^}]+)\}') | ||
move_definition = re.compile(r'MOVE_(\w+)') | ||
nature_definition = re.compile(r'\.nature = TRAINER_PARTY_NATURE\(NATURE_(\w+)\)') | ||
|
||
species_replacements = { | ||
"HO_OH": "Ho-Oh", | ||
"PORYGON_Z": "Porygon-Z", | ||
"TYPE_NULL": "Type: Null", | ||
"JANGMO_O": "Jangmo-o", | ||
"HAKAMO_O": "Hakamo-o", | ||
"KOMMO_O": "Kommo-o", | ||
"WO_CHIEN": "Wo-Chien", | ||
"CHIEN_PAO": "Chien-Pao", | ||
"TING_LU": "Ting-Lu", | ||
"CHI_YU": "Chi-Yu", | ||
"_ALOLAN": "-Alola", | ||
"_GALARIAN": "-Galar", | ||
"_HISUIAN": "-Hisui", | ||
} | ||
|
||
pokemon_attribute_order = ['Level', 'Ability', 'IVs', 'EVs', 'Happiness', 'Shiny', 'Ball'] | ||
|
||
class Pokemon: | ||
def __init__(self): | ||
self.nickname = None | ||
self.species = None | ||
self.gender = None | ||
self.item = None | ||
self.nature = None | ||
self.attributes = {} | ||
self.moves = [] | ||
|
||
def convert_parties(in_path, in_h): | ||
party_identifier = None | ||
party = None | ||
pokemon = None | ||
parties = {} | ||
|
||
for line_no, line in enumerate(in_h, 1): | ||
try: | ||
line = line[:-1] | ||
if is_blank.search(line): | ||
pass | ||
elif m := begin_party_definition.search(line): | ||
if party: | ||
raise Exception(f"unexpected start of party") | ||
[identifier] = m.groups() | ||
party_identifier = identifier | ||
party = [] | ||
elif end_party_definition.search(line): | ||
if not party: | ||
raise Exception(f"unexpected end of party") | ||
parties[party_identifier] = party | ||
party = None | ||
elif begin_pokemon_definition.search(line): | ||
if pokemon: | ||
raise Exception(f"unexpected start of Pokemon") | ||
pokemon = Pokemon() | ||
elif end_pokemon_definition.search(line): | ||
if not pokemon: | ||
raise Exception(f"unexpected end of Pokemon") | ||
else: | ||
party.append(pokemon) | ||
pokemon = None | ||
elif m := level_definition.search(line): | ||
[level] = m.groups() | ||
pokemon.attributes['Level'] = level | ||
elif m := species_definition.search(line): | ||
[species_] = m.groups() | ||
for match, replacement in species_replacements.items(): | ||
species_ = species_.replace(match, replacement) | ||
pokemon.species = species_.replace("_", " ").title() | ||
elif m := gender_definition.search(line): | ||
[gender_] = m.groups() | ||
if gender_ == 'MALE': | ||
pokemon.gender = 'M' | ||
elif gender_ == 'FEMALE': | ||
pokemon.gender = 'F' | ||
else: | ||
raise Exception(f"unknown gender: '{gender_}'") | ||
elif m := nickname_definition.search(line): | ||
[nickname] = m.groups() | ||
pokemon.nickname = nickname | ||
elif m := item_definition.search(line): | ||
[item_] = m.groups() | ||
pokemon.item = item_.replace("_", " ").title() | ||
elif m := ball_definition.search(line): | ||
[ball] = m.groups() | ||
pokemon.attributes['Ball'] = ball.replace("_", " ").title() | ||
elif m := ability_definition.search(line): | ||
[ability] = m.groups() | ||
pokemon.attributes['Ability'] = ability.replace("_", " ").title() | ||
elif m := friendship_definition.search(line): | ||
[friendship] = m.groups() | ||
pokemon.attributes['Happiness'] = friendship | ||
elif m := shiny_definition.search(line): | ||
[shiny] = m.groups() | ||
if shiny == 'TRUE': | ||
pokemon.attributes['Shiny'] = 'Yes' | ||
elif shiny == 'FALSE': | ||
pokemon.attributes['Shiny'] = 'No' | ||
else: | ||
raise Exception(f"unknown isShiny: '{shiny}'") | ||
elif m := ivs_definition.search(line): | ||
[hp, attack, defense, speed, special_attack, special_defense] = [stat.strip() for stat in m.groups()] | ||
stats = {"HP": hp, "Atk": attack, "Def": defense, "SpA": special_attack, "SpD": special_defense, "Spe": speed} | ||
pokemon.attributes['IVs'] = ' / '.join(f"{value} {key}" for key, value in stats.items() if value != '0') | ||
elif m := evs_definition.search(line): | ||
[hp, attack, defense, speed, special_attack, special_defense] = [stat.strip() for stat in m.groups()] | ||
stats = {"HP": hp, "Atk": attack, "Def": defense, "SpA": special_attack, "SpD": special_defense, "Spe": speed} | ||
pokemon.attributes['EVs'] = ' / '.join(f"{value} {key}" for key, value in stats.items() if value != '0') | ||
elif m := moves_definition.search(line): | ||
[moves_] = m.groups() | ||
pokemon.moves = [move.replace("_", " ").title() for move in move_definition.findall(moves_) if move != "NONE"] | ||
elif m := nature_definition.search(line): | ||
[nature_] = m.groups() | ||
pokemon.nature = nature_.replace("_", " ").title() | ||
else: | ||
raise Exception(f"could not parse '{line}'") | ||
except Exception as e: | ||
print(f"{in_path}:{line_no}: {e}") | ||
return parties | ||
|
||
is_trainer_skip = re.compile(r'(const struct Trainer gTrainers\[\] = \{)|(^ \{$)|(\.partySize =)|(\.party = NULL)|(\};)') | ||
|
||
begin_trainer_definition = re.compile(r' \[(TRAINER_\w+)\] =') | ||
end_trainer_definition = re.compile(r' }') | ||
trainer_class_definition = re.compile(r'\.trainerClass = TRAINER_CLASS_(\w+)') | ||
encounter_music_gender_definition = re.compile(r'\.encounterMusic_gender = (F_TRAINER_FEMALE \| )?TRAINER_ENCOUNTER_MUSIC_(\w+)') | ||
trainer_pic_definition = re.compile(r'\.trainerPic = TRAINER_PIC_(\w+)') | ||
trainer_name_definition = re.compile(r'\.trainerName = _\("([^"]*)"\)') | ||
trainer_items_definition = re.compile(r'\.items = \{([^}]*)\}') | ||
trainer_item_definition = re.compile(r'ITEM_(\w+)') | ||
trainer_double_battle_definition = re.compile(r'\.doubleBattle = (\w+)') | ||
trainer_ai_flags_definition = re.compile(r'\.aiFlags = (.*)') | ||
trainer_ai_flag_definition = re.compile(r'AI_FLAG_(\w+)') | ||
trainer_party_definition = re.compile(r'\.party = TRAINER_PARTY\((\w+)\)') | ||
|
||
class Trainer: | ||
def __init__(self, id_): | ||
self.id = id_ | ||
self.class_ = None | ||
self.encounter_music = None | ||
self.gender = None | ||
self.pic = None | ||
self.name = None | ||
self.items = [] | ||
self.double_battle = None | ||
self.ai_flags = None | ||
self.party = None | ||
|
||
def convert_trainers(in_path, in_h, parties, out_party): | ||
newlines = 0 | ||
trainer = None | ||
for line_no, line in enumerate(in_h, 1): | ||
try: | ||
line = line[:-1] | ||
if is_blank.search(line) or is_trainer_skip.search(line): | ||
pass | ||
elif m := begin_trainer_definition.search(line): | ||
if trainer: | ||
raise Exception(f"unexpected start of trainer") | ||
[id_] = m.groups() | ||
trainer = Trainer(id_) | ||
elif m := trainer_class_definition.search(line): | ||
[class_] = m.groups() | ||
trainer.class_ = class_.replace("_", " ").title() | ||
elif m := encounter_music_gender_definition.search(line): | ||
[is_female, music] = m.groups() | ||
trainer.gender = 'Female' if is_female else 'Male' | ||
trainer.encounter_music = music.replace("_", " ").title() | ||
elif m := trainer_pic_definition.search(line): | ||
[pic] = m.groups() | ||
trainer.pic = pic.replace("_", " ").title() | ||
elif m := trainer_name_definition.search(line): | ||
[name] = m.groups() | ||
trainer.name = name | ||
elif m := trainer_items_definition.search(line): | ||
[items] = m.groups() | ||
trainer.items = " / ".join(item.replace("_", " ").title() for item in trainer_item_definition.findall(items) if item != "NONE") | ||
elif m := trainer_double_battle_definition.search(line): | ||
[double_battle] = m.groups() | ||
if double_battle == 'TRUE': | ||
trainer.double_battle = "Yes" | ||
elif double_battle == 'FALSE': | ||
trainer.double_battle = "No" | ||
else: | ||
raise Exception(f"unknown doubleBattle: '{double_battle}'") | ||
elif m := trainer_ai_flags_definition.search(line): | ||
[ai_flags] = m.groups() | ||
trainer.ai_flags = " / ".join(ai_flag.replace("_", " ").title() for ai_flag in trainer_ai_flag_definition.findall(ai_flags)) | ||
elif m := trainer_party_definition.search(line): | ||
[party] = m.groups() | ||
trainer.party = parties[party] | ||
elif end_trainer_definition.search(line): | ||
if not trainer: | ||
raise Exception(f"unexpected end of trainer") | ||
while newlines > 0: | ||
out_party.write(f"\n") | ||
newlines -= 1 | ||
newlines = 1 | ||
out_party.write(f"=== {trainer.id} ===\n") | ||
out_party.write(f"Name: {trainer.name}\n") | ||
out_party.write(f"Class: {trainer.class_}\n") | ||
out_party.write(f"Pic: {trainer.pic}\n") | ||
out_party.write(f"Gender: {trainer.gender}\n") | ||
out_party.write(f"Music: {trainer.encounter_music}\n") | ||
if trainer.items: | ||
out_party.write(f"Items: {trainer.items}\n") | ||
out_party.write(f"Double Battle: {trainer.double_battle}\n") | ||
if trainer.ai_flags: | ||
out_party.write(f"AI: {trainer.ai_flags}\n") | ||
if trainer.party: | ||
for i, pokemon in enumerate(trainer.party): | ||
out_party.write(f"\n") | ||
if pokemon.nickname: | ||
out_party.write(f"{pokemon.nickname} ({pokemon.species})") | ||
else: | ||
out_party.write(f"{pokemon.species}") | ||
if pokemon.gender: | ||
out_party.write(f" ({pokemon.gender})") | ||
if pokemon.item and pokemon.item != 'None': | ||
out_party.write(f" @ {pokemon.item}") | ||
out_party.write(f"\n") | ||
if pokemon.nature: | ||
out_party.write(f"{pokemon.nature} Nature\n") | ||
for key in pokemon_attribute_order: | ||
if key in pokemon.attributes: | ||
out_party.write(f"{key}: {pokemon.attributes[key]}\n") | ||
for move in pokemon.moves: | ||
out_party.write(f"- {move}\n") | ||
trainer = None | ||
else: | ||
raise Exception(f"could not parse '{line}'") | ||
except Exception as e: | ||
print(f"{in_path}:{line_no}: {e}") | ||
|
||
if __name__ == '__main__': | ||
try: | ||
[argv0, trainers_in_path, parties_in_path, out_path] = sys.argv | ||
except: | ||
print(f"usage: python3 {sys.argv[0]} <trainers.h> <trainer_parties.h> <out>") | ||
else: | ||
with open(trainers_in_path, "r") as trainers_in_h, open(parties_in_path, "r") as parties_in_h, open(out_path, "w") as out_party: | ||
parties = convert_parties(parties_in_path, parties_in_h) | ||
trainers = convert_trainers(trainers_in_path, trainers_in_h, parties, out_party) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.