ref: fb7c3a3ea5c758e1f4ddd2d1d859115cbb419248
parent: 1d6fa83902fb34113389436cc3fac349b839f7db
author: Bryan Bishop <[email protected]>
date: Sat May 19 11:43:26 EDT 2012
move trainer parsing classes into crystal.py
--- a/extras/crystal.py
+++ b/extras/crystal.py
@@ -3434,6 +3434,306 @@
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.
+
+ <Trainer Name> <0x50> <Data type> <Pokémon Data>+ <0xFF>
+
+ Data type <0x00>: Pokémon Data is <Level> <Species>. Used by most trainers.
+ Data type <0x01>: Pokémon Data is <Level> <Pokémon> <Move1> <Move2> <Move3> <Move4>. Used often for Gym Leaders.
+ Data type <0x02>: Pokémon Data is <Level> <Pokémon> <Held Item>. Used mainly by Pokéfans.
+ Data type <0x03>: Pokémon Data is <Level> <Pokémon> <Held Item> <Move1> <Move2> <Move3> <Move4>. 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:
+ """
+ <Trainer Name> <0x50> <Data type> <Pokémon Data>+ <0xFF>
+
+ Data type <0x00>: Pokémon Data is <Level> <Species>. Used by most trainers.
+ Data type <0x01>: Pokémon Data is <Level> <Pokémon> <Move1> <Move2> <Move3> <Move4>. Used often for Gym Leaders.
+ Data type <0x02>: Pokémon Data is <Level> <Pokémon> <Held Item>. Used mainly by Pokéfans.
+ Data type <0x03>: Pokémon Data is <Level> <Pokémon> <Held Item> <Move1> <Move2> <Move3> <Move4>. 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 <Level> <Species>. 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 <Level> <Pokémon> <Move1> <Move2> <Move3> <Move4>. 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 <Level> <Pokémon> <Held Item>. 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 <Level> <Pokémon> <Held Item> <Move1> <Move2> <Move3> <Move4>.
+ 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 @@
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
--- 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 @@
# 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.
-
- <Trainer Name> <0x50> <Data type> <Pokémon Data>+ <0xFF>
-
- Data type <0x00>: Pokémon Data is <Level> <Species>. Used by most trainers.
- Data type <0x01>: Pokémon Data is <Level> <Pokémon> <Move1> <Move2> <Move3> <Move4>. Used often for Gym Leaders.
- Data type <0x02>: Pokémon Data is <Level> <Pokémon> <Held Item>. Used mainly by Pokéfans.
- Data type <0x03>: Pokémon Data is <Level> <Pokémon> <Held Item> <Move1> <Move2> <Move3> <Move4>. 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:
- """
- <Trainer Name> <0x50> <Data type> <Pokémon Data>+ <0xFF>
-
- Data type <0x00>: Pokémon Data is <Level> <Species>. Used by most trainers.
- Data type <0x01>: Pokémon Data is <Level> <Pokémon> <Move1> <Move2> <Move3> <Move4>. Used often for Gym Leaders.
- Data type <0x02>: Pokémon Data is <Level> <Pokémon> <Held Item>. Used mainly by Pokéfans.
- Data type <0x03>: Pokémon Data is <Level> <Pokémon> <Held Item> <Move1> <Move2> <Move3> <Move4>. 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 <Level> <Species>. 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 <Level> <Pokémon> <Move1> <Move2> <Move3> <Move4>. 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 <Level> <Pokémon> <Held Item>. 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 <Level> <Pokémon> <Held Item> <Move1> <Move2> <Move3> <Move4>.
- 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]