From fb7c3a3ea5c758e1f4ddd2d1d859115cbb419248 Mon Sep 17 00:00:00 2001 From: Bryan Bishop Date: Sat, 19 May 2012 15:43:26 -0500 Subject: [PATCH] move trainer parsing classes into crystal.py --- extras/crystal.py | 302 ++++++++++++++++++++++++++++++++++++++++++++- extras/trainers.py | 267 +-------------------------------------- 2 files changed, 302 insertions(+), 267 deletions(-) diff --git a/extras/crystal.py b/extras/crystal.py index 0608c699b..b29c91886 100644 --- a/extras/crystal.py +++ b/extras/crystal.py @@ -3434,6 +3434,306 @@ class TrainerFragmentParam(PointerLabelParam): from trainers import * +# for fixing trainer_group_names +import re + +trainer_group_pointer_table_address = 0x39999 +trainer_group_pointer_table_address_gs = 0x3993E + +class TrainerGroupTable: + """ A list of pointers. + """ + + def __init__(self): + assert 0x43 in trainer_group_maximums.keys(), "TrainerGroupTable should onyl be created after all the trainers have been found" + self.address = trainer_group_pointer_table_address + self.bank = calculate_bank(trainer_group_pointer_table_address) + self.label = Label(name="TrainerGroupPointerTable", address=self.address, object=self) + self.size = None + self.last_address = None + self.dependencies = None + self.headers = [] + self.parse() + + # TODO: add this to script_parse_table + #script_parse_table[address : self.last_address] = self + + def get_dependencies(self, recompute=False, global_dependencies=set()): + global_dependencies.update(self.headers) + if recompute == True and self.dependencies != None and self.dependencies != []: + return self.dependencies + dependencies = [self.headers] + for header in self.headers: + dependencies += header.get_dependencies(recompute=recompute, global_dependencies=global_dependencies) + return dependencies + + def parse(self): + size = 0 + for (key, kvalue) in trainer_group_names.items(): + # calculate the location of this trainer group header from its pointer + pointer_bytes_location = kvalue["pointer_address"] + parsed_address = calculate_pointer_from_bytes_at(pointer_bytes_location, bank=self.bank) + trainer_group_names[key]["parsed_address"] = parsed_address + + # parse the trainer group header at this location + name = kvalue["name"] + trainer_group_header = TrainerGroupHeader(address=parsed_address, group_id=key, group_name=name) + trainer_group_names[key]["header"] = trainer_group_header + self.headers.append(trainer_group_header) + + # keep track of the size of this pointer table + size += 2 + self.size = size + self.last_address = self.address + self.size + + def to_asm(self): + output = "".join([str("dw "+get_label_for(header.address)+"\n") for header in self.headers]) + return output + +class TrainerGroupHeader: + """ + A trainer group header is a repeating list of individual trainer headers. + + <0x50> + <0xFF> + + Data type <0x00>: Pokémon Data is . Used by most trainers. + Data type <0x01>: Pokémon Data is . Used often for Gym Leaders. + Data type <0x02>: Pokémon Data is . Used mainly by Pokéfans. + Data type <0x03>: Pokémon Data is . Used by a few Cooltrainers. + """ + + def __init__(self, address=None, group_id=None, group_name=None): + assert address!=None, "TrainerGroupHeader requires an address" + assert group_id!=None, "TrainerGroupHeader requires a group_id" + assert group_name!=None, "TrainerGroupHeader requires a group_name" + + self.address = address + self.group_id = group_id + self.group_name = group_name + self.dependencies = None + self.individual_trainer_headers = [] + self.label = Label(name=group_name+"TrainerGroupHeader", address=self.address, object=self) + self.parse() + + # TODO: add this to script_parse_table + #script_parse_table[address : self.last_address] = self + + def get_dependencies(self, recompute=False, global_dependencies=set()): + """ TrainerGroupHeader has no dependencies. + """ + # TODO: possibly include self.individual_trainer_headers + if recompute or self.dependencies == None: + self.dependencies = [] + return self.dependencies + + def parse(self): + """ + how do i know when there's no more data for this header? + do a global analysis of the rom and figure out the max ids + this wont work for rom hacks of course + see find_trainer_ids_from_scripts + """ + size = 0 + current_address = self.address + + # create an IndividualTrainerHeader for each id in range(min id, max id + 1) + min_id = min(trainer_group_maximums[self.group_id]) + max_id = max(trainer_group_maximums[self.group_id]) + + for trainer_id in range(min_id, max_id+1): + trainer_header = TrainerHeader(address=current_address, trainer_group_id=self.group_id, trainer_id=trainer_id, parent=self) + current_address += trainer_header.size + size += trainer_header.size + + self.last_address = current_address + self.size = size + + def to_asm(self): + raise NotImplementedError + output += "" + +class TrainerHeader: + """ + <0x50> + <0xFF> + + Data type <0x00>: Pokémon Data is . Used by most trainers. + Data type <0x01>: Pokémon Data is . Used often for Gym Leaders. + Data type <0x02>: Pokémon Data is . Used mainly by Pokéfans. + Data type <0x03>: Pokémon Data is . Used by a few Cooltrainers. + """ + + def __init__(self, address=None, trainer_group_id=None, trainer_id=None, parent=None): + self.parent = parent + self.address = address + self.trainer_group_id = trainer_group_id + self.trainer_id = trainer_id + self.dependencies = [] + self.size = None + self.last_address = None + self.parse() + self.label = Label(name=self.make_name(), address=self.address, object=self) + # this shouldn't be added to script_parse_table because + # TrainerGroupHeader covers its address range + + def make_name(self): + """ Must occur after parse() is called. + Constructs a name based on self.parent.group_name and self.name. + """ + return self.parent.group_name + "_" + self.name + + def get_dependencies(self, recompute=False, global_dependencies=set()): + if recompute or self.dependencies == None: + self.dependencies = [] + return self.dependencies + + def parse(self): + address = self.address + + # figure out how many bytes until 0x50 "@" + jump = how_many_until(chr(0x50), address) + + # parse the "@" into the name + self.name = parse_text_at(address, jump+1) + + # where is the next byte? + current_address = address + jump + 1 + + # figure out the pokemon data type + self.data_type = ord(rom[current_address]) + + current_address += 1 + + # figure out which partymon parser to use for this trainer header + party_mon_parser = None + for monparser in trainer_party_mon_parsers: + if monparser.id == self.data_type: + party_mon_parser = monparser + break + + if party_mon_parser == None: + raise Exception, "no trainer party mon parser found to parse data type " + hex(self.data_type) + + self.party_mons = party_mon_parser(address=current_address, group_id=self.trainer_group_id, trainer_id=self.trainer_id, parent=self) + + # let's have everything in trainer_party_mon_parsers handle the last $FF + self.size = self.party_mons.size + 1 + len(self.name) + self.last_address = self.party_mons.last_address + + def to_asm(self): + output = "db \""+self.name+"\"\n" + output += "; data type\n" + output += "db $%.2x\n"%(self.data_byte) + output += self.party_mons.to_asm() + return output + +# TODO: MoveParam should map to an actual attack +MoveParam = SingleByteParam + +class TrainerPartyMonParser: + """ Just a generic trainer party mon parser. + Don't use this directly. Only use the child classes. + """ + id = None + dependencies = None + params = [] + param_types = None + + # could go either way on this one.. TrainerGroupHeader.parse would need to be changed + # so as to not increase current_address by one after reading "data_type" + override_byte_check = True + + def __init__(self, address=None, group_id=None, trainer_id=None, parent=None): + self.address = address + self.group_id = group_id + self.trainer_id = trainer_id + self.parent = parent + self.args = {} + self.params = {} + self.parse() + + # pick up the $FF at the end + self.size += 1 + self.last_address += 1 + + # this is exactly Command.parse + def parse(self): + #id, size (inclusive), param_types + #param_type = {"name": each[1], "class": each[0]} + if not self.override_byte_check: + current_address = self.address+1 + else: + current_address = self.address + byte = ord(rom[self.address]) + if not self.override_byte_check and (not byte == self.id): + raise Exception, "byte ("+hex(byte)+") != self.id ("+hex(self.id)+")" + i = 0 + for (key, param_type) in self.param_types.items(): + name = param_type["name"] + klass = param_type["class"] + #make an instance of this class, like SingleByteParam() + #or ItemLabelByte.. by making an instance, obj.parse() is called + obj = klass(address=current_address, name=name, parent=self, **dict([(k,v) for (k, v) in self.args.items() if k not in ["parent"]])) + #save this for later + self.params[i] = obj + #increment our counters + current_address += obj.size + i += 1 + self.last_address = current_address + return True + + def to_asm(self): + output = "; " + ", ".join([param_type["name"] for param_type in self.param_types]) + "\n" + output += "db " + ", ".join([param.to_asm() for (name, param) in self.params.items()]) + output += "\n" + output += "db $FF ; end trainer party mons" + return output + +class TrainerPartyMonParser0(TrainerPartyMonParser): + """ Data type <0x00>: Pokémon Data is . Used by most trainers. """ + id = 0 + size = 2 + 1 + param_types = { + 0: {"name": "level", "class": DecimalParam}, + 1: {"name": "species", "class": PokemonParam}, + } +class TrainerPartyMonParser1(TrainerPartyMonParser): + """ Data type <0x01>: Pokémon Data is . Used often for Gym Leaders.""" + id = 1 + size = 6 + 1 + param_types = { + 0: {"name": "level", "class": DecimalParam}, + 1: {"name": "species", "class": PokemonParam}, + 2: {"name": "move1", "class": MoveParam}, + 3: {"name": "move2", "class": MoveParam}, + 4: {"name": "move3", "class": MoveParam}, + 5: {"name": "move4", "class": MoveParam}, + } +class TrainerPartyMonParser2(TrainerPartyMonParser): + """ Data type <0x02>: Pokémon Data is . Used mainly by Pokéfans. """ + id = 2 + size = 3 + 1 + param_types = { + 0: {"name": "level", "class": DecimalParam}, + 1: {"name": "species", "class": PokemonParam}, + 2: {"name": "item", "class": ItemLabelByte}, + } +class TrainerPartyMonParser3(TrainerPartyMonParser): + """ Data type <0x03>: Pokémon Data is . + Used by a few Cooltrainers. """ + id = 3 + size = 7 + 1 + param_types = { + 0: {"name": "level", "class": DecimalParam}, + 1: {"name": "species", "class": PokemonParam}, + 2: {"name": "item", "class": ItemLabelByte}, + 3: {"name": "move1", "class": MoveParam}, + 4: {"name": "move2", "class": MoveParam}, + 5: {"name": "move3", "class": MoveParam}, + 6: {"name": "move4", "class": MoveParam}, + } + +trainer_party_mon_parsers = [TrainerPartyMonParser0, TrainerPartyMonParser1, TrainerPartyMonParser2, TrainerPartyMonParser3] + def find_trainer_ids_from_scripts(): """ Looks through all scripts to find trainer group numbers and trainer numbers. @@ -7623,7 +7923,7 @@ def run_main(): find_trainer_ids_from_scripts() # and parse the main TrainerGroupTable once we know the max number of trainers - #gtable = TrainerGroupTable() + trainer_group_header = TrainerGroupTable() #just a helpful alias main=run_main diff --git a/extras/trainers.py b/extras/trainers.py index 22572138c..9fd203180 100644 --- a/extras/trainers.py +++ b/extras/trainers.py @@ -5,7 +5,7 @@ import re trainer_group_pointer_table_address = 0x39999 -trianer_group_pointer_table_address_gs = 0x3993E +trainer_group_pointer_table_address_gs = 0x3993E # TODO: check if "é", ".", "♂", "♀" are okay in the output trainer_group_names = { @@ -102,268 +102,3 @@ def remove_parentheticals_from_trainer_group_names(): # remove [Blue] from each trainer group name remove_parentheticals_from_trainer_group_names() - -class TrainerGroupTable: - """ A list of pointers. - """ - - def __init__(self): - assert 0x43 in trainer_group_maximums.keys(), "TrainerGroupTable should onyl be created after all the trainers have been found" - self.address = trainer_group_pointer_table_address - self.bank = calculate_bank(trainer_group_pointer_table_address) - self.label = Label(name="TrainerGroupPointerTable", address=self.address, object=self) - self.size = None - self.last_address = None - self.dependencies = None - self.headers = [] - self.parse() - - # TODO: add this to script_parse_table - #script_parse_table[address : self.last_address] = self - - def get_dependencies(self, recompute=False, global_dependencies=set()): - global_dependencies.update(self.headers) - if recompute == True and self.dependencies != None and self.dependencies != []: - return self.dependencies - dependencies = [self.headers] - for header in self.headers: - dependencies += header.get_dependencies(recompute=recompute, global_dependencies=global_dependencies) - return dependencies - - def parse(self): - size = 0 - for (key, kvalue) in trainer_group_names.items(): - # calculate the location of this trainer group header from its pointer - pointer_bytes_location = kvalue["pointer_address"] - parsed_address = calculate_pointer_from_bytes_at(pointer_bytes_location, bank=self.bank) - trainer_group_names[key]["parsed_address"] = parsed_address - - # parse the trainer group header at this location - name = kvalue["name"] - trainer_group_header = TrainerGroupHeader(address=parsed_address, group_id=key, group_name=name) - trainer_group_names[key]["header"] = trainer_group_header - self.headers.append(trainer_group_header) - - # keep track of the size of this pointer table - size += 2 - self.size = size - self.last_address = self.address + self.size - - def to_asm(self): - output = "".join([str("dw "+get_label_for(header.address)+"\n") for header in self.headers]) - return output - -class TrainerGroupHeader: - """ - A trainer group header is a repeating list of individual trainer headers. - - <0x50> + <0xFF> - - Data type <0x00>: Pokémon Data is . Used by most trainers. - Data type <0x01>: Pokémon Data is . Used often for Gym Leaders. - Data type <0x02>: Pokémon Data is . Used mainly by Pokéfans. - Data type <0x03>: Pokémon Data is . Used by a few Cooltrainers. - """ - - def __init__(self, address=None, group_id=None, group_name=None): - assert address!=None, "TrainerGroupHeader requires an address" - assert group_id!=None, "TrainerGroupHeader requires a group_id" - assert group_name!=None, "TrainerGroupHeader requires a group_name" - - self.address = address - self.group_id = group_id - self.group_name = group_name - self.dependencies = None - self.individual_trainer_headers = [] - self.label = Label(name=group_name+"TrainerGroupHeader", address=self.address, object=self) - self.parse() - - # TODO: add this to script_parse_table - #script_parse_table[address : self.last_address] = self - - def get_dependencies(self, recompute=False, global_dependencies=set()): - """ TrainerGroupHeader has no dependencies. - """ - # TODO: possibly include self.individual_trainer_headers - if recompute or self.dependencies == None: - self.dependencies = [] - return self.dependencies - - def parse(self): - """ - how do i know when there's no more data for this header? - do a global analysis of the rom and figure out the max ids - this wont work for rom hacks of course - see find_trainer_ids_from_scripts - """ - size = 0 - current_address = self.address - - # create an IndividualTrainerHeader for each id in range(min id, max id + 1) - min_id = min(trainer_group_maximums[self.group_id]) - max_id = max(trainer_group_maximums[self.group_id]) - - for trainer_id in range(min_id, max_id+1): - trainer_header = TrainerHeader(address=current_address, trainer_group_id=self.group_id, trainer_id=trainer_id, parent=self) - current_address += trainer_header.size - size += trainer_header.size - - self.last_address = current_address - self.size = size - - def to_asm(self): - raise NotImplementedError - output += " - -class TrainerHeader: - """ - <0x50> + <0xFF> - - Data type <0x00>: Pokémon Data is . Used by most trainers. - Data type <0x01>: Pokémon Data is . Used often for Gym Leaders. - Data type <0x02>: Pokémon Data is . Used mainly by Pokéfans. - Data type <0x03>: Pokémon Data is . Used by a few Cooltrainers. - """ - - def __init__(self, address=None, trainer_group_id=None, trainer_id=None, parent=None): - self.parent = parent - self.address = address - self.trainer_group_id = trainer_group_id - self.trainer_id = trainer_id - self.dependencies = [] - self.size = None - self.last_address = None - self.parse() - self.label = Label(name=self.make_name(), address=self.address, object=self) - # this shouldn't be added to script_parse_table because - # TrainerGroupHeader covers its address range - - def make_name(self): - """ Must occur after parse() is called. - Constructs a name based on self.parent.group_name and self.name. - """ - return self.parent.group_name + "_" + self.name - - def get_dependencies(self, recompute=False, global_dependencies=set()): - if recompute or self.dependencies == None: - self.dependencies = [] - return self.dependencies - - def parse(self): - address = self.address - - # figure out how many bytes until 0x50 "@" - jump = how_many_until(chr(0x50), address) - - # parse the "@" into the name - self.name = parse_text_at(address, jump+1) - - # where is the next byte? - current_address = address + jump + 1 - - # figure out the pokemon data type - self.data_type = ord(rom[current_address]) - - current_address += 1 - - # figure out which partymon parser to use for this trainer header - party_mon_parser = None - for monparser in trainer_party_mon_parsers: - if monparser.id == self.data_type: - party_mon_parser = monparser - break - - if party_mon_parser == None: - raise Exception, "no trainer party mon parser found to parse data type " + hex(self.data_type) - - self.party_mons = party_mon_parser(address=current_address, group_id=self.trainer_group_id, trainer_id=self.trainer_id, parent=self) - - # let's have everything in trainer_party_mon_parsers handle the last $FF - self.size = self.party_mons.size + 1 + len(self.name) - self.last_address = self.party_mons.last_address - - def to_asm(self): - output = "db \""+self.name+"\"\n" - output += "; data type\n" - output += "db $%.2x\n"%(self.data_byte) - output += self.party_mons.to_asm() - return output - -class TrainerPartyMonParser: - """ Just a generic trainer party mon parser. - Don't use this directly. Only use the child classes. - """ - id = None - dependencies = None - params = [] - param_types = None - - # could go either way on this one.. TrainerGroupHeader.parse would need to be changed - # so as to not increase current_address by one after reading "data_type" - override_byte_check = True - - def __init__(self, address=None, group_id=None, trainer_id=None, parent=None): - self.address = address - self.group_id = group_id - self.trainer_id = trainer_id - self.parent = parent - self.parse() - - # pick up the $FF at the end - self.size += 1 - self.last_address += 1 - - parse = Command.parse - - def to_asm(self): - output = "; " + ", ".join([param_type["name"] for param_type in self.param_types]) + "\n" - output += "db " + ", ".join([param.to_asm() for (name, param) in self.params.items()]) - output += "\n" - output += "db $FF ; end trainer party mons" - return output - -class TrainerPartyMonParser0(TrainerPartyMonParser): - """ Data type <0x00>: Pokémon Data is . Used by most trainers. """ - id = 0 - size = 2 + 1 - param_types = { - 0: {"name": "level", "class": DecimalParam}, - 1: {"name": "species", "class": PokemonParam}, - } -class TrainerPartyMonParser1(TrainerPartyMonParser): - """ Data type <0x01>: Pokémon Data is . Used often for Gym Leaders.""" - id = 1 - size = 6 + 1 - param_types = { - 0: {"name": "level", "class": DecimalParam}, - 1: {"name": "species", "class": PokemonParam}, - 2: {"name": "move1", "class": MoveParam}, - 3: {"name": "move2", "class": MoveParam}, - 4: {"name": "move3", "class": MoveParam}, - 5: {"name": "move4", "class": MoveParam}, - } -class TrainerPartyMonParser2(TrainerPartyMonParser): - """ Data type <0x02>: Pokémon Data is . Used mainly by Pokéfans. """ - id = 2 - size = 3 + 1 - param_types = { - 0: {"name": "level", "class": DecimalParam}, - 1: {"name": "species", "class": PokemonParam}, - 2: {"name": "item", "class": ItemLabelByte}, - } -class TrainerPartyMonParser3(TrainerPartyMonParser): - """ Data type <0x03>: Pokémon Data is . - Used by a few Cooltrainers. """ - id = 3 - size = 7 + 1 - param_types = { - 0: {"name": "level", "class": DecimalParam}, - 1: {"name": "species", "class": PokemonParam}, - 2: {"name": "item", "class": ItemLabelByte}, - 3: {"name": "move1", "class": MoveParam}, - 4: {"name": "move2", "class": MoveParam}, - 5: {"name": "move3", "class": MoveParam}, - 6: {"name": "move4", "class": MoveParam}, - } - -trainer_party_mon_parsers = [TrainerPartyMonParser0, TrainerPartyMonParser1, TrainerPartyMonParser2, TrainerPartyMonParser3]