From 4eceef18a7b9a0b87efa28a6a5445f6680851af2 Mon Sep 17 00:00:00 2001 From: Petr Viktorin Date: Sun, 10 Jun 2012 23:01:15 +0200 Subject: [PATCH] Update struct - Update for Generation 5. This means SaveFilePokemon becomes an abstract base, with SaveFilePokemonGen4 and SaveFilePokemonGen5 subclasses. The pokemon_struct is changed to a structure factory. Diff best viewed with the ignore whitespace setting. - Allow creating a SaveFilePokemon with a zeroed-out blob (to be filled in later). Several fields are zeroed out in gen 5 so this helps the above. --- pokedex/struct/__init__.py | 219 +++++++++----- pokedex/struct/_pokemon_struct.py | 466 +++++++++++++++--------------- 2 files changed, 381 insertions(+), 304 deletions(-) diff --git a/pokedex/struct/__init__.py b/pokedex/struct/__init__.py index b6a3e73..d119e82 100644 --- a/pokedex/struct/__init__.py +++ b/pokedex/struct/__init__.py @@ -13,7 +13,7 @@ import struct from pokedex.db import tables from pokedex.formulae import calculated_hp, calculated_stat from pokedex.compatibility import namedtuple, permutations -from pokedex.struct._pokemon_struct import pokemon_struct +from pokedex.struct._pokemon_struct import make_pokemon_struct def pokemon_prng(seed): u"""Creates a generator that simulates the main Pokémon PRNG.""" @@ -22,43 +22,54 @@ def pokemon_prng(seed): seed &= 0xFFFFFFFF yield seed >> 16 - class SaveFilePokemon(object): - u"""Represents an individual Pokémon, from the game's point of view. + u"""Base class for an individual Pokémon, from the game's point of view. Handles translating between the on-disk encrypted form, the in-RAM blob (also used by pokesav), and something vaguely intelligible. """ - Stat = namedtuple('Stat', ['stat', 'base', 'gene', 'exp', 'calc']) - def __init__(self, blob, encrypted=False): + def __init__(self, blob=None, encrypted=False, session=None): u"""Wraps a Pokémon save struct in a friendly object. If `encrypted` is True, the blob will be decrypted as though it were an on-disk save. Otherwise, the blob is taken to be already decrypted and is left alone. - `session` is an optional database session. + `session` is an optional database session. Either give it or fill it + later with `use_database_session` """ - if encrypted: - # Decrypt it. - # Interpret as one word (pid), followed by a bunch of shorts - struct_def = "I" + "H" * ((len(blob) - 4) / 2) - shuffled = list( struct.unpack(struct_def, blob) ) + try: + self.generation_id + except AttributeError: + raise NotImplementedError( + "Use generation-specific subclass of SaveFilePokemon") - # Apply standard Pokémon decryption, undo the block shuffling, and - # done - self.reciprocal_crypt(shuffled) - words = self.shuffle_chunks(shuffled, reverse=True) - self.blob = struct.pack(struct_def, *words) + if blob: + if encrypted: + # Decrypt it. + # Interpret as one word (pid), followed by a bunch of shorts + struct_def = "I" + "H" * ((len(blob) - 4) / 2) + shuffled = list( struct.unpack(struct_def, blob) ) + # Apply standard Pokémon decryption, undo the block shuffling, and + # done + self.reciprocal_crypt(shuffled) + words = self.shuffle_chunks(shuffled, reverse=True) + self.blob = struct.pack(struct_def, *words) + + else: + # Already decrypted + self.blob = blob + + self.structure = self.pokemon_struct.parse(self.blob) else: - # Already decrypted - self.blob = blob + self.structure = self.pokemon_struct.parse('\0' * (32 * 4 + 8)) - self.structure = pokemon_struct.parse(self.blob) + if session: + self.use_database_session(session) @property def as_struct(self): @@ -98,64 +109,72 @@ class SaveFilePokemon(object): def use_database_session(self, session): """Remembers the given database session, and prefetches a bunch of - database stuff. Gotta call this before you use the database properties - like `species`, etc. + database stuff. Gotta call this (or give it to `__init__`) before + you use the database properties like `species`, etc. """ self._session = session st = self.structure - self._pokemon = session.query(tables.Pokemon).get(st.national_id) - self._pokemon_form = session.query(tables.PokemonForm) \ - .with_parent(self._pokemon) \ - .filter_by(name=st.alternate_form) \ - .one() + + if st.national_id: + self._pokemon = session.query(tables.Pokemon).get(st.national_id) + self._pokemon_form = session.query(tables.PokemonForm) \ + .with_parent(self._pokemon) \ + .filter_by(form_identifier=st.alternate_form) \ + .one() + else: + self._pokemon = self._pokemon_form = None self._ability = self._session.query(tables.Ability).get(st.ability_id) - growth_rate = self._pokemon.evolution_chain.growth_rate - self._experience_rung = session.query(tables.Experience) \ - .filter(tables.Experience.growth_rate == growth_rate) \ - .filter(tables.Experience.experience <= st.exp) \ - .order_by(tables.Experience.level.desc()) \ - [0] - level = self._experience_rung.level - - self._next_experience_rung = None - if level < 100: - self._next_experience_rung = session.query(tables.Experience) \ + if self._pokemon: + growth_rate = self._pokemon.species.growth_rate + self._experience_rung = session.query(tables.Experience) \ .filter(tables.Experience.growth_rate == growth_rate) \ - .filter(tables.Experience.level == level + 1) \ - .one() + .filter(tables.Experience.experience <= st.exp) \ + .order_by(tables.Experience.level.desc()) \ + [0] + level = self._experience_rung.level + + self._next_experience_rung = None + if level < 100: + self._next_experience_rung = session.query(tables.Experience) \ + .filter(tables.Experience.growth_rate == growth_rate) \ + .filter(tables.Experience.level == level + 1) \ + .one() self._held_item = None if st.held_item_id: self._held_item = session.query(tables.ItemGameIndex) \ .filter_by(generation_id = 4, game_index = st.held_item_id).one().item - self._stats = [] - for pokemon_stat in self._pokemon.stats: - structure_name = pokemon_stat.stat.name.lower().replace(' ', '_') - gene = st.ivs['iv_' + structure_name] - exp = st['effort_' + structure_name] + if self._pokemon: + self._stats = [] + for pokemon_stat in self._pokemon.stats: + structure_name = pokemon_stat.stat.name.lower().replace(' ', '_') + gene = st.ivs['iv_' + structure_name] + exp = st['effort_' + structure_name] - if pokemon_stat.stat.name == u'HP': - calc = calculated_hp - else: - calc = calculated_stat + if pokemon_stat.stat.name == u'HP': + calc = calculated_hp + else: + calc = calculated_stat - stat_tup = self.Stat( - stat = pokemon_stat.stat, - base = pokemon_stat.base_stat, - gene = gene, - exp = exp, - calc = calc( - pokemon_stat.base_stat, - level = level, - iv = gene, - effort = exp, - ), - ) + stat_tup = self.Stat( + stat = pokemon_stat.stat, + base = pokemon_stat.base_stat, + gene = gene, + exp = exp, + calc = calc( + pokemon_stat.base_stat, + level = level, + iv = gene, + effort = exp, + ), + ) - self._stats.append(stat_tup) + self._stats.append(stat_tup) + else: + self._stats = [0] * 6 move_ids = ( @@ -171,10 +190,13 @@ class SaveFilePokemon(object): if st.hgss_pokeball >= 17: pokeball_id = st.hgss_pokeball - 17 + 492 - else: + elif st.dppt_pokeball: pokeball_id = st.dppt_pokeball - self._pokeball = session.query(tables.ItemGameIndex) \ - .filter_by(generation_id = 4, game_index = pokeball_id).one().item + else: + pokeball_id = None + if pokeball_id: + self._pokeball = session.query(tables.ItemGameIndex) \ + .filter_by(generation_id = 4, game_index = pokeball_id).one().item egg_loc_id = st.pt_egg_location_id or st.dp_egg_location_id met_loc_id = st.pt_met_location_id or st.dp_met_location_id @@ -182,15 +204,25 @@ class SaveFilePokemon(object): self._egg_location = None if egg_loc_id: self._egg_location = session.query(tables.LocationGameIndex) \ - .filter_by(generation_id = 4, game_index = egg_loc_id).one().location + .filter_by(generation_id = self.generation_id, game_index = egg_loc_id).one().location - self._met_location = session.query(tables.LocationGameIndex) \ - .filter_by(generation_id = 4, game_index = met_loc_id).one().location + if met_loc_id: + self._met_location = session.query(tables.LocationGameIndex) \ + .filter_by(generation_id = self.generation_id, game_index = met_loc_id).one().location + else: + self._met_location = None @property def species(self): - # XXX forme! - return self._pokemon + return self._pokemon_form.species + + @property + def pokemon(self): + return self._pokemon_form.pokemon + + @property + def form(self): + return self._pokemon_form @property def species_form(self): @@ -208,16 +240,6 @@ class SaveFilePokemon(object): def met_location(self): return self._met_location - @property - def shiny_leaves(self): - return ( - self.structure.shining_leaves.leaf1, - self.structure.shining_leaves.leaf2, - self.structure.shining_leaves.leaf3, - self.structure.shining_leaves.leaf4, - self.structure.shining_leaves.leaf5, - ) - @property def level(self): return self._experience_rung.level @@ -319,3 +341,42 @@ class SaveFilePokemon(object): words[i] ^= next(prng) return + + +class SaveFilePokemonGen4(SaveFilePokemon): + generation_id = 4 + pokemon_struct = make_pokemon_struct(generation=generation_id) + + @property + def shiny_leaves(self): + return ( + self.structure.shining_leaves.leaf1, + self.structure.shining_leaves.leaf2, + self.structure.shining_leaves.leaf3, + self.structure.shining_leaves.leaf4, + self.structure.shining_leaves.leaf5, + ) + + +class SaveFilePokemonGen5(SaveFilePokemon): + generation_id = 5 + pokemon_struct = make_pokemon_struct(generation=generation_id) + + def use_database_session(self, session): + super(SaveFilePokemonGen5, self).use_database_session(session) + + st = self.structure + + if st.nature_id: + self._nature = session.query(tables.Nature) \ + .filter_by(game_index = st.nature_id).one() + + @property + def nature(self): + return self._nature + + +save_file_pokemon_classes = { + 4: SaveFilePokemonGen4, + 5: SaveFilePokemonGen5, +} diff --git a/pokedex/struct/_pokemon_struct.py b/pokedex/struct/_pokemon_struct.py index 79ff0e0..cf71c24 100644 --- a/pokedex/struct/_pokemon_struct.py +++ b/pokedex/struct/_pokemon_struct.py @@ -578,238 +578,254 @@ class PokemonFormAdapter(Adapter): return forms.index(obj) << 3 - -# And here we go. # Docs: http://projectpokemon.org/wiki/Pokemon_NDS_Structure -pokemon_struct = Struct('pokemon_struct', - # Header - ULInt32('personality'), # XXX aughgh http://bulbapedia.bulbagarden.net/wiki/Personality - Padding(2), - ULInt16('checksum'), # XXX should be checked or calculated +# http://projectpokemon.org/wiki/Pokemon_Black/White_NDS_Structure +# http://projectpokemon.org/forums/showthread.php?11474-Hex-Values-and-Trashbytes-in-B-W#post93598 - # Block A - ULInt16('national_id'), - ULInt16('held_item_id'), - ULInt16('original_trainer_id'), - ULInt16('original_trainer_secret_id'), - ULInt32('exp'), - ULInt8('happiness'), - ULInt8('ability_id'), # XXX needs to match personality + species - BitStruct('markings', +def make_pokemon_struct(generation): + """Make a pokemon struct class for the given generation + """ + leaves_or_nature = { + 4: BitStruct('shining_leaves', + Padding(2), + Flag('crown'), + Flag('leaf5'), + Flag('leaf4'), + Flag('leaf3'), + Flag('leaf2'), + Flag('leaf1'), + ), + 5: ULInt8('nature_id'), + }[generation] + + padding_or_hidden_ability = { + 4: Padding(1), + 5: Flag('hidden_ability'), + }[generation] + + return Struct('pokemon_struct', + # Header + ULInt32('personality'), # XXX aughgh http://bulbapedia.bulbagarden.net/wiki/Personality Padding(2), - Flag('diamond'), - Flag('star'), - Flag('heart'), - Flag('square'), - Flag('triangle'), - Flag('circle'), - ), - Enum( - ULInt8('original_country'), - jp=1, - us=2, - fr=3, - it=4, - de=5, - es=7, - kr=8, - ), + ULInt16('checksum'), # XXX should be checked or calculated - # XXX sum cannot surpass 510 - ULInt8('effort_hp'), - ULInt8('effort_attack'), - ULInt8('effort_defense'), - ULInt8('effort_speed'), - ULInt8('effort_special_attack'), - ULInt8('effort_special_defense'), + # Block A + ULInt16('national_id'), + ULInt16('held_item_id'), + ULInt16('original_trainer_id'), + ULInt16('original_trainer_secret_id'), + ULInt32('exp'), + ULInt8('happiness'), + ULInt8('ability_id'), # XXX needs to match personality + species + BitStruct('markings', + Padding(2), + Flag('diamond'), + Flag('star'), + Flag('heart'), + Flag('square'), + Flag('triangle'), + Flag('circle'), + ), + Enum(ULInt8('original_country'), + _unset = 0, + jp=1, + us=2, + fr=3, + it=4, + de=5, + es=7, + kr=8, + ), - ULInt8('contest_cool'), - ULInt8('contest_beauty'), - ULInt8('contest_cute'), - ULInt8('contest_smart'), - ULInt8('contest_tough'), - ULInt8('contest_sheen'), + # XXX sum cannot surpass 510 + ULInt8('effort_hp'), + ULInt8('effort_attack'), + ULInt8('effort_defense'), + ULInt8('effort_speed'), + ULInt8('effort_special_attack'), + ULInt8('effort_special_defense'), - LittleEndianBitStruct('sinnoh_ribbons', + ULInt8('contest_cool'), + ULInt8('contest_beauty'), + ULInt8('contest_cute'), + ULInt8('contest_smart'), + ULInt8('contest_tough'), + ULInt8('contest_sheen'), + + LittleEndianBitStruct('sinnoh_ribbons', + Padding(4), + Flag('premier_ribbon'), + Flag('classic_ribbon'), + Flag('carnival_ribbon'), + Flag('festival_ribbon'), + Flag('blue_ribbon'), + Flag('green_ribbon'), + Flag('red_ribbon'), + Flag('legend_ribbon'), + Flag('history_ribbon'), + Flag('record_ribbon'), + Flag('footprint_ribbon'), + Flag('gorgeous_royal_ribbon'), + Flag('royal_ribbon'), + Flag('gorgeous_ribbon'), + Flag('smile_ribbon'), + Flag('snooze_ribbon'), + Flag('relax_ribbon'), + Flag('careless_ribbon'), + Flag('downcast_ribbon'), + Flag('shock_ribbon'), + Flag('alert_ribbon'), + Flag('world_ability_ribbon'), + Flag('pair_ability_ribbon'), + Flag('multi_ability_ribbon'), + Flag('double_ability_ribbon'), + Flag('great_ability_ribbon'), + Flag('ability_ribbon'), + Flag('sinnoh_champ_ribbon'), + ), + + # Block B + ULInt16('move1_id'), + ULInt16('move2_id'), + ULInt16('move3_id'), + ULInt16('move4_id'), + ULInt8('move1_pp'), + ULInt8('move2_pp'), + ULInt8('move3_pp'), + ULInt8('move4_pp'), + ULInt8('move1_pp_ups'), + ULInt8('move2_pp_ups'), + ULInt8('move3_pp_ups'), + ULInt8('move4_pp_ups'), + + LittleEndianBitStruct('ivs', + Flag('is_nicknamed'), + Flag('is_egg'), + BitField('iv_special_defense', 5), + BitField('iv_special_attack', 5), + BitField('iv_speed', 5), + BitField('iv_defense', 5), + BitField('iv_attack', 5), + BitField('iv_hp', 5), + ), + LittleEndianBitStruct('hoenn_ribbons', + Flag('world_ribbon'), + Flag('earth_ribbon'), + Flag('national_ribbon'), + Flag('country_ribbon'), + Flag('sky_ribbon'), + Flag('land_ribbon'), + Flag('marine_ribbon'), + Flag('effort_ribbon'), + Flag('artist_ribbon'), + Flag('victory_ribbon'), + Flag('winning_ribbon'), + Flag('champion_ribbon'), + Flag('tough_ribbon_master'), + Flag('tough_ribbon_hyper'), + Flag('tough_ribbon_super'), + Flag('tough_ribbon'), + Flag('smart_ribbon_master'), + Flag('smart_ribbon_hyper'), + Flag('smart_ribbon_super'), + Flag('smart_ribbon'), + Flag('cute_ribbon_master'), + Flag('cute_ribbon_hyper'), + Flag('cute_ribbon_super'), + Flag('cute_ribbon'), + Flag('beauty_ribbon_master'), + Flag('beauty_ribbon_hyper'), + Flag('beauty_ribbon_super'), + Flag('beauty_ribbon'), + Flag('cool_ribbon_master'), + Flag('cool_ribbon_hyper'), + Flag('cool_ribbon_super'), + Flag('cool_ribbon'), + ), + EmbeddedBitStruct( + PokemonFormAdapter(BitField('alternate_form', 5)), + Enum(BitField('gender', 2), + genderless = 2, + male = 0, + female = 1, + ), + Flag('fateful_encounter'), + ), + leaves_or_nature, + padding_or_hidden_ability, + Padding(1), + ULInt16('pt_egg_location_id'), + ULInt16('pt_met_location_id'), + + # Block C + PokemonStringAdapter(String('nickname', 22)), + Padding(1), + Enum(ULInt8('original_version'), + _unset = 0, + sapphire = 1, + ruby = 2, + emerald = 3, + firered = 4, + leafgreen = 5, + heartgold = 7, + soulsilver = 8, + diamond = 10, + pearl = 11, + platinum = 12, + orre = 15, + ), + LittleEndianBitStruct('sinnoh_contest_ribbons', + Padding(12), + Flag('tough_ribbon_master'), + Flag('tough_ribbon_ultra'), + Flag('tough_ribbon_great'), + Flag('tough_ribbon'), + Flag('smart_ribbon_master'), + Flag('smart_ribbon_ultra'), + Flag('smart_ribbon_great'), + Flag('smart_ribbon'), + Flag('cute_ribbon_master'), + Flag('cute_ribbon_ultra'), + Flag('cute_ribbon_great'), + Flag('cute_ribbon'), + Flag('beauty_ribbon_master'), + Flag('beauty_ribbon_ultra'), + Flag('beauty_ribbon_great'), + Flag('beauty_ribbon'), + Flag('cool_ribbon_master'), + Flag('cool_ribbon_ultra'), + Flag('cool_ribbon_great'), + Flag('cool_ribbon'), + ), Padding(4), - Flag('premier_ribbon'), - Flag('classic_ribbon'), - Flag('carnival_ribbon'), - Flag('festival_ribbon'), - Flag('blue_ribbon'), - Flag('green_ribbon'), - Flag('red_ribbon'), - Flag('legend_ribbon'), - Flag('history_ribbon'), - Flag('record_ribbon'), - Flag('footprint_ribbon'), - Flag('gorgeous_royal_ribbon'), - Flag('royal_ribbon'), - Flag('gorgeous_ribbon'), - Flag('smile_ribbon'), - Flag('snooze_ribbon'), - Flag('relax_ribbon'), - Flag('careless_ribbon'), - Flag('downcast_ribbon'), - Flag('shock_ribbon'), - Flag('alert_ribbon'), - Flag('world_ability_ribbon'), - Flag('pair_ability_ribbon'), - Flag('multi_ability_ribbon'), - Flag('double_ability_ribbon'), - Flag('great_ability_ribbon'), - Flag('ability_ribbon'), - Flag('sinnoh_champ_ribbon'), - ), - # Block B - ULInt16('move1_id'), - ULInt16('move2_id'), - ULInt16('move3_id'), - ULInt16('move4_id'), - ULInt8('move1_pp'), - ULInt8('move2_pp'), - ULInt8('move3_pp'), - ULInt8('move4_pp'), - ULInt8('move1_pp_ups'), - ULInt8('move2_pp_ups'), - ULInt8('move3_pp_ups'), - ULInt8('move4_pp_ups'), - - LittleEndianBitStruct('ivs', - Flag('is_nicknamed'), - Flag('is_egg'), - BitField('iv_special_defense', 5), - BitField('iv_special_attack', 5), - BitField('iv_speed', 5), - BitField('iv_defense', 5), - BitField('iv_attack', 5), - BitField('iv_hp', 5), - ), - LittleEndianBitStruct('hoenn_ribbons', - Flag('world_ribbon'), - Flag('earth_ribbon'), - Flag('national_ribbon'), - Flag('country_ribbon'), - Flag('sky_ribbon'), - Flag('land_ribbon'), - Flag('marine_ribbon'), - Flag('effort_ribbon'), - Flag('artist_ribbon'), - Flag('victory_ribbon'), - Flag('winning_ribbon'), - Flag('champion_ribbon'), - Flag('tough_ribbon_master'), - Flag('tough_ribbon_hyper'), - Flag('tough_ribbon_super'), - Flag('tough_ribbon'), - Flag('smart_ribbon_master'), - Flag('smart_ribbon_hyper'), - Flag('smart_ribbon_super'), - Flag('smart_ribbon'), - Flag('cute_ribbon_master'), - Flag('cute_ribbon_hyper'), - Flag('cute_ribbon_super'), - Flag('cute_ribbon'), - Flag('beauty_ribbon_master'), - Flag('beauty_ribbon_hyper'), - Flag('beauty_ribbon_super'), - Flag('beauty_ribbon'), - Flag('cool_ribbon_master'), - Flag('cool_ribbon_hyper'), - Flag('cool_ribbon_super'), - Flag('cool_ribbon'), - ), - EmbeddedBitStruct( - PokemonFormAdapter(BitField('alternate_form', 5)), - Enum(BitField('gender', 2), - genderless = 2, - male = 0, - female = 1, + # Block D + PokemonStringAdapter(String('original_trainer_name', 16)), + DateAdapter(String('date_egg_received', 3)), + DateAdapter(String('date_met', 3)), + ULInt16('dp_egg_location_id'), + ULInt16('dp_met_location_id'), + ULInt8('pokerus'), # Warning : Values changed in gen 5 + ULInt8('dppt_pokeball'), + EmbeddedBitStruct( + Enum(Flag('original_trainer_gender'), + male = False, + female = True, + ), + BitField('met_at_level', 7), ), - Flag('fateful_encounter'), - ), - BitStruct('shining_leaves', - Padding(2), - Flag('crown'), - Flag('leaf5'), - Flag('leaf4'), - Flag('leaf3'), - Flag('leaf2'), - Flag('leaf1'), - ), - Padding(2), - ULInt16('pt_egg_location_id'), - ULInt16('pt_met_location_id'), - - # Block C - PokemonStringAdapter(String('nickname', 22)), - Padding(1), - Enum(ULInt8('original_version'), - sapphire = 1, - ruby = 2, - emerald = 3, - firered = 4, - leafgreen = 5, - heartgold = 7, - soulsilver = 8, - diamond = 10, - pearl = 11, - platinum = 12, - orre = 15, - ), - LittleEndianBitStruct('sinnoh_contest_ribbons', - Padding(12), - Flag('tough_ribbon_master'), - Flag('tough_ribbon_ultra'), - Flag('tough_ribbon_great'), - Flag('tough_ribbon'), - Flag('smart_ribbon_master'), - Flag('smart_ribbon_ultra'), - Flag('smart_ribbon_great'), - Flag('smart_ribbon'), - Flag('cute_ribbon_master'), - Flag('cute_ribbon_ultra'), - Flag('cute_ribbon_great'), - Flag('cute_ribbon'), - Flag('beauty_ribbon_master'), - Flag('beauty_ribbon_ultra'), - Flag('beauty_ribbon_great'), - Flag('beauty_ribbon'), - Flag('cool_ribbon_master'), - Flag('cool_ribbon_ultra'), - Flag('cool_ribbon_great'), - Flag('cool_ribbon'), - ), - Padding(4), - - # Block D - PokemonStringAdapter(String('original_trainer_name', 16)), - DateAdapter(String('date_egg_received', 3)), - DateAdapter(String('date_met', 3)), - ULInt16('dp_egg_location_id'), - ULInt16('dp_met_location_id'), - ULInt8('pokerus'), - ULInt8('dppt_pokeball'), - EmbeddedBitStruct( - Enum(Flag('original_trainer_gender'), - male = False, - female = True, + Enum(ULInt8('encounter_type'), + special = 0, # egg; pal park; event; honey tree; shaymin + grass = 2, # or darkrai + dialga_palkia = 4, + cave = 5, # or giratina or hall of origin + water = 7, + building = 9, + safari_zone = 10, # includes great marsh + gift = 12, # starter; fossil; ingame trade? + # distortion_world = ???, + hgss_gift = 24, # starter; fossil; bebe's eevee (pt only??) ), - BitField('met_at_level', 7), - ), - Enum(ULInt8('encounter_type'), - special = 0, # egg; pal park; event; honey tree; shaymin - grass = 2, # or darkrai - dialga_palkia = 4, - cave = 5, # or giratina or hall of origin - water = 7, - building = 9, - safari_zone = 10, # includes great marsh - gift = 12, # starter; fossil; ingame trade? - # distortion_world = ???, - hgss_gift = 24, # starter; fossil; bebe's eevee (pt only??) - ), - ULInt8('hgss_pokeball'), - Padding(1), -) + ULInt8('hgss_pokeball'), + Padding(1), + )