shithub: pokecrystal

ref: 1d6fa83902fb34113389436cc3fac349b839f7db
dir: /extras/trainers.py/

View raw version
# -*- coding: utf-8 -*-
# url: http://hax.iimarck.us/topic/8/

# for fixing trainer_group_names
import re

trainer_group_pointer_table_address    = 0x39999
trianer_group_pointer_table_address_gs = 0x3993E

# TODO: check if "é", ".", "♂", "♀" are okay in the output
trainer_group_names = {
0x01: {"name": "Leader [Falkner]"},
0x02: {"name": "Leader [Whitney]"},
0x03: {"name": "Leader [Bugsy]"},
0x04: {"name": "Leader [Morty]"},
0x05: {"name": "Leader [Pryce]"},
0x06: {"name": "Leader [Jasmine]"},
0x07: {"name": "Leader [Chuck]"},
0x08: {"name": "Leader [Clair]"},
0x09: {"name": "Rival1"},
0x0A: {"name": "Pokémon Prof."},
0x0B: {"name": "Elite Four [Will]"},
0x0C: {"name": "PKMN Trainer [Cal]"},
0x0D: {"name": "Elite Four [Bruno]"},
0x0E: {"name": "Elite Four [Karen]"},
0x0F: {"name": "Elite Four [Koga]"},
0x10: {"name": "Champion"},
0x11: {"name": "Leader [Brock]"},
0x12: {"name": "Leader [Misty]"},
0x13: {"name": "Leader [Lt.Surge]"},
0x14: {"name": "Scientist"},
0x15: {"name": "Leader [Erika]"},
0x16: {"name": "Youngster"},
0x17: {"name": "Schoolboy"},
0x18: {"name": "Bird Keeper"},
0x19: {"name": "Lass"},
0x1A: {"name": "Leader [Janine]"},
0x1B: {"name": "CooltrainerM"},
0x1C: {"name": "CooltrainerF"},
0x1D: {"name": "Beauty"},
0x1E: {"name": "Pokémaniac"},
0x1F: {"name": "GruntM"},
0x20: {"name": "Gentleman"},
0x21: {"name": "Skier"},
0x22: {"name": "Teacher"},
0x23: {"name": "Leader [Sabrina]"},
0x24: {"name": "Bug Catcher"},
0x25: {"name": "Fisher"},
0x26: {"name": "SwimmerM"},
0x27: {"name": "SwimmerF"},
0x28: {"name": "Sailor"},
0x29: {"name": "Super Nerd"},
0x2A: {"name": "Rival2"},
0x2B: {"name": "Guitarist"},
0x2C: {"name": "Hiker"},
0x2D: {"name": "Biker"},
0x2E: {"name": "Leader [Blaine]"},
0x2F: {"name": "Burglar"},
0x30: {"name": "Firebreather"},
0x31: {"name": "Juggler"},
0x32: {"name": "Blackbelt"},
0x33: {"name": "ExecutiveM"},
0x34: {"name": "Psychic"},
0x35: {"name": "Picnicker"},
0x36: {"name": "Camper"},
0x37: {"name": "ExecutiveF"},
0x38: {"name": "Sage"},
0x39: {"name": "Medium"},
0x3A: {"name": "Boarder"},
0x3B: {"name": "PokéfanM"},
0x3C: {"name": "Kimono Girl"},
0x3D: {"name": "Twins"},
0x3E: {"name": "PokéfanF"},
0x3F: {"name": "PKMN Trainer [Red]"},
0x40: {"name": "Leader [Blue]"},
0x41: {"name": "Officer"},
0x42: {"name": "RocketF"},
0x43: {"name": "Mysticalman [Eusine]"}, # crystal only
}

def remove_parentheticals_from_trainer_group_names():
    """ Clean up the trainer group names.
    """
    i = 0
    for (key, value) in trainer_group_names.items():
        # remove the brackets and inner contents from each name
        newvalue = re.sub(r'\[[^\)]*\]', '', value["name"]).strip()

        # clean up some characters
        newvalue = newvalue.replace("♀", "F")\
                           .replace("♂", "M")\
                           .replace(".", "")\
                           .replace(" ", "_")\
                           .replace("é", "e")

        # and calculate the address of the first byte of this pointer
        trainer_group_names[key] = {"name": newvalue,
                                    "pointer_address": trainer_group_pointer_table_address + (i * 2),
                                   }
        i += 1
    return 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.

    <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]