mirror of
https://github.com/rh-hideout/pokeemerald-expansion
synced 2025-10-05 23:42:46 +02:00
Adds conversion script for trainers.h (#7663)
This commit is contained in:
401
migration_scripts/1.13/convert_trainers.py
Normal file
401
migration_scripts/1.13/convert_trainers.py
Normal file
@@ -0,0 +1,401 @@
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
|
||||
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_(\w+)')
|
||||
|
||||
is_trainer_skip = re.compile(r'(const struct Trainer gTrainers\[\] = \{)|(^ \{$)|(\.partySize =)|(\.party = NULL)|(\.mugshotEnabled = TRUE)|(\};)')
|
||||
|
||||
trainer_normal_definition = re.compile(r' \[DIFFICULTY_NORMAL\]\[(TRAINER_\w+)\] =')
|
||||
trainer_easy_definition = re.compile(r' \[DIFFICULTY_EASY\]\[(TRAINER_\w+)\] =')
|
||||
trainer_hard_definition = re.compile(r' \[DIFFICULTY_HARD\]\[(TRAINER_\w+)\] =')
|
||||
end_pokemon_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+)')
|
||||
encounter_music_definition = re.compile(r'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'\.battleType = (\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_mugshot_definition = re.compile(r'\.mugshotColor = MUGSHOT_COLOR_(\w+)')
|
||||
trainer_starting_status_definition = re.compile(r'\.startingStatus = STARTING_STATUS_(\w+)')
|
||||
|
||||
# NOTE: These are just for aesthetics, the Pokemon would still compile
|
||||
# without them.
|
||||
species_replacements = {
|
||||
"CHIEN_PAO": "Chien-Pao",
|
||||
"CHI_YU": "Chi-Yu",
|
||||
"HAKAMO_O": "Hakamo-o",
|
||||
"HO_OH": "Ho-Oh",
|
||||
"JANGMO_O": "Jangmo-o",
|
||||
"KOMMO_O": "Kommo-o",
|
||||
"PORYGON_Z": "Porygon-Z",
|
||||
"ROTOM_": "Rotom-",
|
||||
"TING_LU": "Ting-Lu",
|
||||
"TYPE_NULL": "Type: Null",
|
||||
"WO_CHIEN": "Wo-Chien",
|
||||
|
||||
"_ALOLAN": "-Alola",
|
||||
"_AQUA_BREED": "-Aqua",
|
||||
"_BATTLE_BOND": "-Bond",
|
||||
"_BLAZE_BREED": "-Blaze",
|
||||
"_CAP": "",
|
||||
"_CLOAK": "",
|
||||
"_COMBAT_BREED": "-Combat",
|
||||
"_CROWED_SHIELD": "-Crowned",
|
||||
"_CROWED_SWORD": "-Crowned",
|
||||
"_DRIVE": "",
|
||||
"_EAST_SEA": "-East",
|
||||
"_FAMILY_OF_FOUR": "-Four",
|
||||
"_FEMALE": "-F",
|
||||
"_FLOWER": "",
|
||||
"_GALARIAN": "-Galar",
|
||||
"_GIGANTAMAX": "-Gmax",
|
||||
"_HISUIAN": "-Hisui",
|
||||
"_ICE_RIDER": "-Ice",
|
||||
"_NOICE_FACE": "-Noice",
|
||||
"_ORIGIN": "-Origin",
|
||||
"_ORIGINAL_COLOR": "-Original",
|
||||
"_PALDEAN": "-Paldea",
|
||||
"_PLUMAGE": "",
|
||||
"_POKE_BALL": "-Pokeball",
|
||||
"_SHADOW_RIDER": "-Shadow",
|
||||
"_STRIKE_STYLE": "-Style",
|
||||
"_TOTEM": "-Totem",
|
||||
"_ZEN_MODE": "-Zen",
|
||||
}
|
||||
|
||||
class_fixups = {
|
||||
"Rs": "RS",
|
||||
}
|
||||
|
||||
pic_fixups = {
|
||||
"Rs": "RS",
|
||||
}
|
||||
|
||||
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.attributes['IVs'] = "0 HP / 0 Atk / 0 Def / 0 SpA / 0 SpD / 0 Spe"
|
||||
self.moves = []
|
||||
|
||||
|
||||
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.mugshot = None
|
||||
self.starting_status = None
|
||||
self.party = None
|
||||
self.difficulty = None
|
||||
|
||||
|
||||
def write_tutorial(output):
|
||||
output.write('/*\n')
|
||||
output.write('Trainers and their parties defined with Competetive Syntax.\n')
|
||||
output.write('Compatible with Pokemon Showdown exports.\n')
|
||||
output.write('https://github.com/smogon/pokemon-showdown/blob/master/sim/TEAMS.md\n')
|
||||
output.write('\n')
|
||||
output.write('A trainer specification starts with "=== TRAINER_XXXX ==="\n')
|
||||
output.write('and includes everything until the next line that starts with "==="\n')
|
||||
output.write('or the file ends.\n')
|
||||
output.write('\n')
|
||||
output.write('A blank line is required between the trainer and their Pokemon\n')
|
||||
output.write('and between their Pokemon.\n')
|
||||
output.write('TRAINER_XXXX is how the trainer is referred to within code.\n')
|
||||
output.write('Fields with description and/or example of usage\n')
|
||||
output.write('Required fields for trainers:\n')
|
||||
output.write(' - Name\n')
|
||||
output.write(' - Pic\n')
|
||||
output.write('Optional (but still recommended) fields for trainers:\n')
|
||||
output.write(' - Class (if not specified, PkMn Trainer will be used)\n')
|
||||
output.write(' - Gender (Male/Female, affects random gender weights of party if not specified)\n')
|
||||
output.write(' - Music\n')
|
||||
output.write(' - Items (Some Item / Another Item / Third Item)\n')
|
||||
output.write(' (Can also be specified with ITEM_SOME_ITEM)\n')
|
||||
output.write(' - Battle Type (Singles / Doubles, defaults to Singles)\n')
|
||||
output.write(' - AI (Ai Flag / Another Flag / Third Flag / ...\n')
|
||||
output.write(' see "constants/battle_ai.h" for all flags)\n')
|
||||
output.write(' - Mugshot (enable Mugshots during battle transition\n')
|
||||
output.write(' set to one of Purple, Green, Pink, Blue or Yellow)\n')
|
||||
output.write(' - Starting Status (see include/constants/battle.h for values)\n')
|
||||
output.write('\n')
|
||||
output.write('Pokemon are then specified using the Showdown Export format.\n')
|
||||
output.write("If a field is not specified, it will use it's default value.\n")
|
||||
output.write("\n")
|
||||
output.write('Required fields for Pokemon:\n')
|
||||
output.write(' - Species (Either as SPECIES_ABRA or Abra)\n')
|
||||
output.write(' This line also specifies Gender, Nickname and Held item.\n')
|
||||
output.write(' Alfred (Abra) (M) @ Eviolite\n')
|
||||
output.write(' Roberta (SPECIES_ABRA) (F) @ ITEM_CHOICE_SPECS\n')
|
||||
output.write(' Both lines are valid. Gender (M) or (F) must use a capital letter.\n')
|
||||
output.write(' Nickname length is limited to 10 characters using standard letters.\n')
|
||||
output.write(" With narrow font it's increased to 12. Longer strings will be silently shortened.\n")
|
||||
output.write('\n')
|
||||
output.write('Optional fields for Pokemon:\n')
|
||||
output.write(' - Level (Number between 1 and 100, defaults to 100)\n')
|
||||
output.write(' - Ability (Ability Name or ABILITY_ABILITY_NAME)\n')
|
||||
output.write(' - IVs (0 HP / 1 Atk / 2 Def / 3 SpA / 4 SpD / 5 Spe, defaults to all 31)\n')
|
||||
output.write(' (Order does not matter)\n')
|
||||
output.write(' - EVs (252 HP / 128 Spe / 48 Def, defaults to all 0, is not capped at 512 total)\n')
|
||||
output.write(' (Order does not matter)\n')
|
||||
output.write(' - Ball (Poke Ball or ITEM_POKE_BALL, defaults to Poke Ball)\n')
|
||||
output.write(' - Happiness (Number between 1 and 255)\n')
|
||||
output.write(' - Nature (Rash or NATURE_RASH, defaults to Hardy)\n')
|
||||
output.write(' - Shiny (Yes/No, defaults to No)\n')
|
||||
output.write(' - Dynamax Level (Number between 0 and 10, default 10, also sets "shouldDynamax" to True)\n')
|
||||
output.write(' - Gigantamax (Yes/No, sets to Gigantamax factor)\n')
|
||||
output.write(' (doesn\'t do anything to Pokemon without a Gigantamax form, also sets "shouldDynamax" to True)\n')
|
||||
output.write(' - Tera Type (Set to a Type, either Fire or TYPE_FIRE, also sets "shouldTerastal" to True)\n')
|
||||
output.write('Moves are defined with a - (dash) followed by a single space, then the move name.\n')
|
||||
output.write('Either "- Tackle" or "- MOVE_TACKLE" works. One move per line.\n')
|
||||
output.write('Moves have to be the last lines of a Pokemon.\n')
|
||||
output.write('If no moves are specified, the Pokemon will use the last 4 moves it learns\n')
|
||||
output.write('through levelup at its level.\n')
|
||||
output.write("\n")
|
||||
output.write('Default IVs and Level can be changed in the "main" function of tools/trainerproc/main.c\n')
|
||||
output.write("\n")
|
||||
output.write('This file is processed with a custom preprocessor.\n')
|
||||
output.write('*/\n')
|
||||
output.write("\n")
|
||||
output.write('/*\n')
|
||||
output.write('Comments can be added as C comment blocks\n')
|
||||
output.write('// cannot be used as comments\n')
|
||||
output.write('*/\n')
|
||||
output.write("\n")
|
||||
output.write('/*Comments can also be on a single line*/\n')
|
||||
output.write("\n")
|
||||
output.write("\n")
|
||||
|
||||
|
||||
|
||||
def write_to_file(trainer, output):
|
||||
output.write(f'=== {trainer.id} ===\n')
|
||||
output.write(f'Name: {trainer.name}\n')
|
||||
output.write(f'Class: {trainer.class_}\n')
|
||||
output.write(f'Pic: {trainer.pic}\n')
|
||||
output.write(f'Gender: {trainer.gender}\n')
|
||||
output.write(f'Music: {trainer.encounter_music}\n')
|
||||
if len(trainer.items) > 0:
|
||||
output.write(f'Items: {trainer.items}\n')
|
||||
output.write(f'Battle Type: {trainer.double_battle}\n')
|
||||
if trainer.ai_flags is not None:
|
||||
output.write(f'AI: {trainer.ai_flags}\n')
|
||||
if trainer.mugshot:
|
||||
output.write(f'Mugshot: {trainer.mugshot}\n')
|
||||
if trainer.difficulty is not None:
|
||||
output.write(f'Difficulty: {trainer.difficulty}\n')
|
||||
|
||||
output.write(f'\n')
|
||||
|
||||
for pokemon in trainer.party:
|
||||
if pokemon.species is None:
|
||||
continue
|
||||
if pokemon.item is not None:
|
||||
output.write(f'{pokemon.species} @ {pokemon.item}\n')
|
||||
else:
|
||||
output.write(f'{pokemon.species}\n')
|
||||
# for key in pokemon.attributes:
|
||||
for key in pokemon_attribute_order:
|
||||
if key in pokemon.attributes:
|
||||
output.write(f'{key}: {pokemon.attributes[key]}\n')
|
||||
if pokemon.nature:
|
||||
output.write(f'Nature: {pokemon.nature}\n')
|
||||
for move in pokemon.moves:
|
||||
output.write(f'- {move}\n')
|
||||
output.write(f'\n')
|
||||
|
||||
|
||||
def parse_trainers(content, output):
|
||||
newlines = 0
|
||||
trainer = None
|
||||
pokemon = None
|
||||
party = []
|
||||
moves = []
|
||||
|
||||
write_tutorial(output)
|
||||
|
||||
for line_no, line in enumerate(content, 1):
|
||||
try:
|
||||
line = line[:-1]
|
||||
|
||||
# Trainer defition
|
||||
if m := trainer_normal_definition.search(line):
|
||||
if trainer is not None:
|
||||
trainer.party = party
|
||||
write_to_file(trainer, output)
|
||||
trainer = None
|
||||
party = []
|
||||
[id_] = m.groups()
|
||||
trainer = Trainer(id_)
|
||||
trainer.difficulty = None
|
||||
trainer.gender = 'Male'
|
||||
elif m := trainer_easy_definition.search(line):
|
||||
if trainer is not None:
|
||||
trainer.party = party
|
||||
write_to_file(trainer, output)
|
||||
trainer = None
|
||||
party = []
|
||||
[id_] = m.groups()
|
||||
trainer = Trainer(id_)
|
||||
trainer.difficulty = "Easy"
|
||||
trainer.gender = 'Male'
|
||||
elif m := trainer_hard_definition.search(line):
|
||||
if trainer is not None:
|
||||
trainer.party = party
|
||||
write_to_file(trainer, output)
|
||||
trainer = None
|
||||
party = []
|
||||
[id_] = m.groups()
|
||||
trainer = Trainer(id_)
|
||||
trainer.difficulty = "Hard"
|
||||
trainer.gender = 'Male'
|
||||
elif m := trainer_class_definition.search(line):
|
||||
[class_] = m.groups()
|
||||
class_ = class_.replace("_", " ").title()
|
||||
for match, replacement in class_fixups.items():
|
||||
class_ = class_.replace(match, replacement)
|
||||
trainer.class_ = class_
|
||||
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 := encounter_music_definition.search(line):
|
||||
[music] = m.groups()
|
||||
trainer.encounter_music = music.replace("_", " ").title()
|
||||
elif "F_TRAINER_FEMALE" in line:
|
||||
trainer.gender = 'Female'
|
||||
elif m := trainer_pic_definition.search(line):
|
||||
[pic] = m.groups()
|
||||
pic = pic.replace("_", " ").title()
|
||||
for match, replacement in pic_fixups.items():
|
||||
pic = pic.replace(match, replacement)
|
||||
trainer.pic = pic
|
||||
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 == 'TRAINER_BATTLE_TYPE_DOUBLES':
|
||||
trainer.double_battle = "Doubles"
|
||||
elif double_battle == 'TRAINER_BATTLE_TYPE_SINGLES':
|
||||
trainer.double_battle = "Singles"
|
||||
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_mugshot_definition.search(line):
|
||||
[color] = m.groups()
|
||||
trainer.mugshot = color.title()
|
||||
elif m := trainer_starting_status_definition.search(line):
|
||||
[starting_status] = m.groups()
|
||||
trainer.starting_status = starting_status.replace("_", " ").title()
|
||||
elif m := trainer_party_definition.search(line):
|
||||
pokemon = Pokemon()
|
||||
|
||||
# Party mons
|
||||
elif end_pokemon_definition.search(line):
|
||||
party.append(pokemon)
|
||||
pokemon = Pokemon()
|
||||
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'
|
||||
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'
|
||||
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())
|
||||
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 := move_definition.search(line):
|
||||
[move] = m.groups()
|
||||
pokemon.moves.append(move.replace("_", " ").title())
|
||||
elif m := nature_definition.search(line):
|
||||
[nature] = m.groups()
|
||||
pokemon.nature = nature.replace("_", " ").title()
|
||||
|
||||
except Exception as e:
|
||||
print(f"{line_no}: {e}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
[argv0, trainers_in_path, out_path] = sys.argv
|
||||
except:
|
||||
print(f"usage: python3 {sys.argv[0]} <trainers.h> <out>")
|
||||
print("trainers.h path: src/data/trainers.h")
|
||||
print("trainers.party output path: src/data/trainers.party")
|
||||
else:
|
||||
with open(trainers_in_path, "r") as source, open(out_path, 'w') as output:
|
||||
parse_trainers(source, output)
|
Reference in New Issue
Block a user