diff --git a/pyboy/core/debug.pxi b/pyboy/core/debug.pxi new file mode 100644 index 000000000..6d91d73d9 --- /dev/null +++ b/pyboy/core/debug.pxi @@ -0,0 +1,5 @@ +# +# License: See LICENSE.md file +# GitHub: https://github.com/Baekalfen/PyBoy +# +DEF DEBUG=0 diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/core/battle.py b/pyboy/plugins/game_wrapper_pokemon_gen1/core/battle.py new file mode 100644 index 000000000..25d612976 --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/core/battle.py @@ -0,0 +1,10 @@ +from .memory_object import MemoryObject +from ..data.memory_addrs.battle import BattleAddress, BATTLE_ADDRESS_LOOKUP + +class Battle(MemoryObject): + + _enum = BattleAddress + _lookup = BATTLE_ADDRESS_LOOKUP + + def __init__(self, fields_to_track=None): + super().__init__(fields_to_track) \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/core/game_state.py b/pyboy/plugins/game_wrapper_pokemon_gen1/core/game_state.py new file mode 100644 index 000000000..8e1de7745 --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/core/game_state.py @@ -0,0 +1,23 @@ +from ..data.memory_addrs.game_state import GameStateAddress + +class GameState(): + + def __init__(self, + battle_type + ): + + self._battle_type = battle_type + + def is_in_battle(self): + return self._battle_type != 0 + + @property + def battle_type(self): + return self._battle_type + + @staticmethod + def load_game_state(mem_manager): + + battle_type = mem_manager.read_memory_address(GameStateAddress.BATTLE_TYPE) + + return GameState(battle_type) \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/core/mem_manager.pxd b/pyboy/plugins/game_wrapper_pokemon_gen1/core/mem_manager.pxd new file mode 100644 index 000000000..28a626c1b --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/core/mem_manager.pxd @@ -0,0 +1,10 @@ +# +# License: See LICENSE.md file +# GitHub: https://github.com/Baekalfen/PyBoy +# +from libc.stdint cimport uint8_t +from pyboy.pyboy cimport PyBoy +cimport cython + +cdef class MemoryManager: + cdef PyBoy pyboy \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/core/mem_manager.py b/pyboy/plugins/game_wrapper_pokemon_gen1/core/mem_manager.py new file mode 100644 index 000000000..e99fed0ad --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/core/mem_manager.py @@ -0,0 +1,205 @@ +from enum import Enum +from ..data.memory_addrs.base import MemoryAddress, MemoryAddressType + +STRING_TERMINATOR = 80 +ASCII_DELTA = 63 + +MAX_STRING_LENGTH = 16 + +class MemoryManager(): + + def __init__(self, pyboy): + self.pyboy = pyboy + + @staticmethod + def _byte_to_bitfield(n, reverse : bool): + # Force bit extension to 10 chars; 2 for '0x' and 8 for the bits + bitlist = [1 if digit=='1' else 0 for digit in format(n, '#010b')[2:]] + + if reverse: + bitlist.reverse() + + return bitlist + + @staticmethod + def _bitfield_to_byte(bitlist, reverse : bool): + + if reverse: + bitlist.reverse() + + bit_str = ''.join([str(i) for i in bitlist]) + + return int(bit_str, 2) + + @staticmethod + def get_character_index(character): + if character == ' ': + return 0x7F + if character == '?': + return 0xE6 + if character == '!': + return 0xE7 + if character == 'é': + return 0xBA + + index = ord(character) + if index > 47 and index < 58: + # number + return index + 197 + return index + ASCII_DELTA + + def get_ + + def _read_byte(self, addr): + return self.pyboy.get_memory_value(addr) + + def _write_byte(self, value, addr): + return self.pyboy.set_memory_value(addr, value) + + def read_address_from_memory(self, addr, num_bytes=1): + p_addr = 0 + for i in range(num_bytes): + p_addr += self._read_byte(addr+i) << i*8 + return p_addr + + def read_hex_from_memory(self, addr, num_bytes=1): + bytes = [] + for i in range(num_bytes): + bytes.append(self._read_byte(addr + i)) + # Do not believe there is ever a case where we would + # need to read this in little endian for Pokemon gen 1 + return int.from_bytes(bytes, byteorder='big') + + def write_hex_to_memory(self, value, addr, num_bytes=1): + bytes = value.to_bytes(2, byteorder='big') + assert len(bytes) == num_bytes*2 + for i, byte in enumerate(bytes): + self._write_byte(addr + i, byte) + + def read_bcd_from_memory(self, addr, num_bytes=1): + byte_str = "" + for i in range(num_bytes): + byte_str += "%x"%self._read_byte(addr+i) + return int(byte_str) + + def write_bcd_to_memory(self, value, addr, num_bytes=1): + val_str = str(value) + assert len(val_str) <= num_bytes*2 + + padded_val = val_str.zfill(num_bytes*2) + for i in range(num_bytes): + sub_val = int(padded_val[i*2:(i*2)+2], 16) + self._write_byte(sub_val, addr+i) + + def read_bitfield_from_memory(self, addr, num_bytes=1, reverse=False): + bits = [] + for i in range(num_bytes): + bits.extend(MemoryManager._byte_to_bitfield(self._read_byte(addr+i), reverse)) + return bits + + def write_bitlist_to_memory(self, value, addr, num_bytes=1, reverse=False): + + # TODO: This check might be too restrictive + assert len(value) % 8 == 0 + # TODO: Maybe don't need this check if we trust users to make value fit + assert len(value)/8 == num_bytes + + for i in range(num_bytes): + bit_list_start = i*8 + sub_bit_list = value[bit_list_start:bit_list_start+8] + byte_val = MemoryManager._bitfield_to_byte(sub_bit_list, reverse) + self._write_byte(byte_val, addr+i) + + def read_text_from_memory(self, address, num_bytes): + """ + Retrieves a string from a given address. + + Args: + address (int): Address from where to retrieve text from. + cap (int): Maximum expected length of string (default: 16). + """ + # Make sure string to write not too large + assert num_bytes <= MAX_STRING_LENGTH + text = '' + for i in range(num_bytes): + value = self._read_byte(address + i) + if value == STRING_TERMINATOR: + break + text += chr(value - ASCII_DELTA) + return text + + def write_text_to_memory(self, text, address, num_bytes=1): + """Sets text at address. + + Will always add a string terminator (80) at the end. + """ + + terminated_text = text + '\0' + # TODO: Check that this isn't an off-by-one issue + assert len(terminated_text) <= num_bytes + + i = 0 + for i, chr in enumerate(text): + self.pyboy.set_memory_value(address + i, MemoryManager.get_character_index(chr)) + + def read_memory_address(self, mem_addr): + if isinstance(mem_addr, Enum): + mem_addr = mem_addr.value + if mem_addr.memory_type == MemoryAddressType.HEX: + mem_func = self.read_hex_from_memory + elif mem_addr.memory_type == MemoryAddressType.ADDRESS: + mem_func = self.read_address_from_memory + elif mem_addr.memory_type == MemoryAddressType.BCD: + mem_func = self.read_bcd_from_memory + elif mem_addr.memory_type == MemoryAddressType.BITFIELD: + mem_func = self.read_bitfield_from_memory + elif mem_addr.memory_type == MemoryAddressType.TEXT: + mem_func = self.read_text_from_memory + else: + raise ValueError(f"{mem_addr.memory_type} is not a valid memory type") + + return mem_func(mem_addr.address, mem_addr.num_bytes) + + def write_memory_address(self, mem_addr): + if isinstance(mem_addr, Enum): + mem_addr = mem_addr.value + if mem_addr.memory_type == MemoryAddressType.HEX: + mem_func = self.write_hex_to_memory + elif mem_addr.memory_type == MemoryAddressType.ADDRESS: + #mem_func = self.write_add + pass + elif mem_addr.memory_type == MemoryAddressType.BCD: + mem_func = self.write_bcd_to_memory + elif mem_addr.memory_type == MemoryAddressType.BITFIELD: + mem_func = self.write_bitlist_to_memory + elif mem_addr.memory_type == MemoryAddressType.TEXT: + mem_func = self.write_text_to_memory + else: + raise ValueError(f"{mem_addr.memory_type} is not a valid memory type") + + return mem_func(mem_addr.address, mem_addr.num_bytes) + + def _load_rombank(self, rombank_selected): + self.pyboy.set_memory_value(0x2000, rombank_selected) + + def read_from_rom(self, address, num_bytes=1): + rombank = int(address / 0x4000) + address_in_rom = address % 0x4000 + + # PyBoy treats the switchable ROM bank as being at address + # 0x4000 to 0x8000 (0x0000 to 0x4000 is always ROM bank 0), + # append 04x4000 + + address_in_rom += 0x4000 + + # Switch in correct ROM bank + self._load_rombank(rombank) + + rom_values = [] + + for i in range(num_bytes): + rom_values.append(self.pyboy.get_memory_value(address_in_rom+i)) + + return rom_values + + \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/core/memory_object.py b/pyboy/plugins/game_wrapper_pokemon_gen1/core/memory_object.py new file mode 100644 index 000000000..0657d2b57 --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/core/memory_object.py @@ -0,0 +1,21 @@ +class MemoryObject(): + + _enum = {} + _lookup = {} + + def __init__(self, fields_to_track): + if fields_to_track is None: + fields_to_track = [e for e in self._enum] + self._fields_to_track = fields_to_track + self._data = {} + + def _load_field_from_memory(self, mem_manager, field_enum): + self.data[field_enum] = mem_manager.read_memory_address(self._lookup[field_enum]) + + def load_from_memory(self, mem_manager): + for field_enum in self._fields_to_track: + self._load_field_from_memory(mem_manager, field_enum) + + def get_value(self, k): + return self._data.get(k) + diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/core/move.py b/pyboy/plugins/game_wrapper_pokemon_gen1/core/move.py new file mode 100644 index 000000000..b8af14bae --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/core/move.py @@ -0,0 +1,19 @@ +from ..data.constants.moves import MoveId + +class Move(): + + NONEXISTENT_MOVE_STR = "---" + + def __init__(self, move_id): + pass + + # We can cheat with just using the enum name as the move name + # since there is no move that has any special characters in it + @classmethod + def get_name_from_id(cls, move_id, camel_case=False): + if move_id == 0: + return cls.NONEXISTENT_MOVE_STR + move_name = MoveId(move_id).name.replace('_', ' ') + if camel_case: + return move_name.title() + return move_name \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/core/player.py b/pyboy/plugins/game_wrapper_pokemon_gen1/core/player.py new file mode 100644 index 000000000..584478db4 --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/core/player.py @@ -0,0 +1,71 @@ +from ..data.memory_addrs.player import PlayerAddress, PLAYER_ADDRESS_LOOKUP +from ..data.constants.pokemon import PokemonId +from ..data.constants.misc import Badge +from .memory_object import MemoryObject + +class Player(MemoryObject): + + _enum = PlayerAddress + _lookup = PLAYER_ADDRESS_LOOKUP + + def __init__(self, fields_to_track): + super().__init__(fields_to_track) + + ''' + Getters and setters + ''' + @property + def name(self): + return self._name + + @name.setter + def name(self, n): + # Name truncated to 8 characters + self._name = n[:8] + + @property + def money(self): + return self._money + + @money.setter + def money(self, m): + # Max money game can store is 999999 + self._money = min(m, 999999) + + @property + def num_pokemon_in_party(self): + return len(self._pokemon_in_party) + + @property + def num_badges(self): + return sum(self._badges) + + ''' + Badge functionality + ''' + def has_badge(self, badge : Badge): + return self._badges[badge.value] == 1 + + def give_badge(self, badge : Badge): + self._badges[badge.value] = 1 + + def remove_badge(self, badge : Badge): + self._badges[badge.value] = 0 + + @staticmethod + def load_player(mem_manager, fields_to_track=None): + player = Player(mem_manager, fields_to_track) + player.load_from_memory(mem_manager) + return player + + # def save_player(self, mem_manager): + + # mem_manager.write_memory_address(self._name, PlayerAddress.NAME) + # mem_manager.write_memory_address(self.num_pokemon_in_party, PlayerAddress.NUM_POKEMON_IN_PARTY) + # # TODO: Figure out the loading and unloading of Pokemon in party, as that should be in tandem with + # # Pokemon class so that data does not get out of sync + # mem_manager.write_memory_address(self._badges, PlayerAddress.BADGES) + # mem_manager.write_memory_address(self._money, PlayerAddress.MONEY) + + + \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/core/pokedex.py b/pyboy/plugins/game_wrapper_pokemon_gen1/core/pokedex.py new file mode 100644 index 000000000..ee512b05b --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/core/pokedex.py @@ -0,0 +1,42 @@ +from pyboy.plugins.game_wrapper_pokemon_gen1.core.pokemon import Pokemon + +# TODO: Technically with the project structure these should be in data/memory_addrs, +# but these are literally the only memory addresses we need so they lives here for now. +# Also probably move if we eventually have a flag to check Japanese vs international ROM + +POKEDEX_CAUGHT_ADDR_INT = (0xD2F7, 19) +POKEDEX_SEEN_ADDR_INT = (0xD30A, 19) + + +class Pokedex: + + def __init__(self, + caught_pokemon, + seen_pokemon): + + self._caught_pokemon = caught_pokemon + self._seen_pokemon = seen_pokemon + + @staticmethod + def get_pokedex_range_bits(mem_manager, mem_addr): + return mem_manager.read_memory_address(mem_addr, reverse=True) + + @staticmethod + def load_pokedex(mem_manager): + caught_pokemon = Pokedex.get_pokedex_range_bits(mem_manager, POKEDEX_CAUGHT_ADDR_INT) + seen_pokemon = Pokedex.get_pokedex_range_bits(mem_manager, POKEDEX_SEEN_ADDR_INT) + + return Pokedex(caught_pokemon, seen_pokemon) + + + def get_number_pokemon_caught(self): + return sum(self._caught_pokemon) + + def get_number_pokemon_seen(self): + return sum(self._seen_pokemon) + + def have_caught_pokemon(self, pokemon_id): + return self._caught_pokemon[Pokemon.get_pokedex_id_from_pokemon_id(pokemon_id)-1] == 1 + + def have_seen_pokemon(self, pokemon_id): + return self._seen_pokemon[Pokemon.get_pokedex_id_from_pokemon_id(pokemon_id)-1] == 1 \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/core/pokemon.py b/pyboy/plugins/game_wrapper_pokemon_gen1/core/pokemon.py new file mode 100644 index 000000000..f299fe227 --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/core/pokemon.py @@ -0,0 +1,152 @@ +from ..data.memory_addrs.pokemon import PokemonBaseAddress, PokemonAddress, POKEMON_ADDRESS_LOOKUP +from ..data.constants.pokemon import PokemonId, _POKEMON_NAMES, _POKEMON_POKEDEX_INDEX +from .move import Move +from .memory_object import MemoryObject + +class Pokemon(MemoryObject): + + _enum = PokemonAddress + _lookup = POKEMON_ADDRESS_LOOKUP + + def __init__(self, fields_to_track): + super().__init__(fields_to_track) + + @property + def name(self): + return Pokemon.get_pokemon_name_from_id(self.get_value(PokemonAddress.ID)) + + @property + def pokedex_num(self): + return Pokemon.get_pokedex_id_from_pokemon_id(self.get_value(PokemonAddress.ID)) + + @property + def current_hp(self): + return self.get_value(PokemonAddress.CURRENT_HP) + + @property + def move_1(self): + return self.get_value(PokemonAddress.MOVE_1) + + @property + def move_2(self): + return self.get_value(PokemonAddress.MOVE_2) + + @property + def move_3(self): + return self.get_value(PokemonAddress.MOVE_3) + + @property + def move_4(self): + return self.get_value(PokemonAddress.MOVE_4) + + @property + def experience(self): + return self.get_value(PokemonAddress.EXPERIENCE) + + @property + def pp_move_1(self): + return self.get_value(PokemonAddress.PP_MOVE_1) + + @property + def pp_move_2(self): + return self.get_value(PokemonAddress.PP_MOVE_2) + + @property + def pp_move_3(self): + return self.get_value(PokemonAddress.PP_MOVE_3) + + @property + def pp_move_4(self): + return self.get_value(PokemonAddress.PP_MOVE_4) + + @property + def hp_ev(self): + return self.get_value(PokemonAddress.HP_EV) + + @property + def attack_ev(self): + return self.get_value(PokemonAddress.ATTACK_EV) + + @property + def defense_ev(self): + return self.get_value(PokemonAddress.DEFENSE_EV) + + @property + def speed_ev(self): + return self.get_value(PokemonAddress.SPEED_EV) + + @property + def special_ev(self): + return self.get_value(PokemonAddress.SPECIAL_EV) + + @property + def max_hp(self): + return self.get_value(PokemonAddress.MAX_HP) + + @property + def attack(self): + return self.get_value(PokemonAddress.ATTACK) + + @property + def defense(self): + return self.get_value(PokemonAddress.DEFENSE) + + @property + def speed(self): + return self.get_value(PokemonAddress.SPEED) + + @property + def special(self): + return self.get_value(PokemonAddress.SPECIAL) + + @property + def curr_hp_percent(self): + return self.current_hp / self.max_hp + + @staticmethod + def get_pokemon_name_from_id(pokemon_id): + return _POKEMON_NAMES.get(PokemonId(pokemon_id)) + + @staticmethod + def get_pokedex_id_from_pokemon_id(pokemon_id): + return _POKEMON_POKEDEX_INDEX.get(PokemonId(pokemon_id)) + + @staticmethod + def load_pokemon_from_party(mem_manager, party_id): + pokemon = PartyPokemon(party_id=party_id) + pokemon.load_from_memory(mem_manager) + return pokemon + + def _generate_move_str(self): + s = f"Moves:\n" + s += f"\t{Move.get_name_from_id(self.move_1)} ({self.pp_move_1}/)\n" + s += f"\t{Move.get_name_from_id(self.move_2)} ({self.pp_move_2}/)\n" + s += f"\t{Move.get_name_from_id(self.move_3)} ({self.pp_move_3}/)\n" + s += f"\t{Move.get_name_from_id(self.move_4)} ({self.pp_move_4}/)\n" + + return s + + def _generate_stats_str(self): + + s = f"Stats:\n" + \ + f"\tHP: {self.max_hp}\n" + \ + f"\tAttack: {self.attack}\n" + \ + f"\tDefense: {self.defense}\n" + \ + f"\tSpeed: {self.speed}\n" + \ + f"\tSpecial: {self.special}\n" + return s + + def pretty_stringify(self): + s = f"{self.name}\n" + self._generate_stats_str() + self._generate_move_str() + return s + +class PartyPokemon(Pokemon): + + def __init__(self, party_id, fields_to_track=None): + super().__init__(fields_to_track) + self._party_id = party_id + + def _load_field_from_memory(self, mem_manager, field_enum): + memory_addr = self._lookup[field_enum] + memory_addr = memory_addr.add_offset([e.value for e in PokemonBaseAddress][self._party_id-1]) + self._data[field_enum] = mem_manager.read_memory_address(memory_addr) \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/data/constants/items.py b/pyboy/plugins/game_wrapper_pokemon_gen1/data/constants/items.py new file mode 100644 index 000000000..3f79ecc7a --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/data/constants/items.py @@ -0,0 +1,155 @@ +from enum import Enum + +class Item(Enum): + MASTER_BALL = 0x01 + ULTRA_BALL = 0x02 + GREAT_BALL = 0x03 + POKE_BALL = 0x04 + TOWN_MAP = 0x05 + BICYCLE = 0x06 + SURFBOARD = 0x07 + SAFARI_BALL = 0x08 + POKEDEX = 0x09 + MOON_STONE = 0x0A + ANTIDOTE = 0x0B + BURN_HEAL = 0x0C + ICE_HEAL = 0x0D + AWAKENING = 0x0E + PARLYZ_HEAL = 0x0F + FULL_RESTORE = 0x10 + MAX_POTION = 0x11 + HYPER_POTION = 0x12 + SUPER_POTION = 0x13 + POTION = 0x14 + BOULDERBADGE = 0x15 + CASCADEBADGE = 0x16 + THUNDERBADGE = 0x17 + RAINBOWBADGE = 0x18 + SOULBADGE = 0x19 + MARSHBADGE = 0x1A + VOLCANOBADGE = 0x1B + EARTHBADGE = 0x1C + ESCAPE_ROPE = 0x1D + REPEL = 0x1E + OLD_AMBER = 0x1F + FIRE_STONE = 0x20 + THUNDER_STONE = 0x21 + WATER_STONE = 0x22 + HP_UP = 0x23 + PROTEIN = 0x24 + IRON = 0x25 + CARBOS = 0x26 + CALCIUM = 0x27 + RARE_CANDY = 0x28 + DOME_FOSSIL = 0x29 + HELIX_FOSSIL = 0x2A + SECRET_KEY = 0x2B + UNUSED_ITEM = 0x2C + BIKE_VOUCHER = 0x2D + X_ACCURACY = 0x2E + LEAF_STONE = 0x2F + CARD_KEY = 0x30 + NUGGET = 0x31 + PP_UP_2 = 0x32 + POKE_DOLL = 0x33 + FULL_HEAL = 0x34 + REVIVE = 0x35 + MAX_REVIVE = 0x36 + GUARD_SPEC = 0x37 + SUPER_REPEL = 0x38 + MAX_REPEL = 0x39 + DIRE_HIT = 0x3A + COIN = 0x3B + FRESH_WATER = 0x3C + SODA_POP = 0x3D + LEMONADE = 0x3E + S_S_TICKET = 0x3F + GOLD_TEETH = 0x40 + X_ATTACK = 0x41 + X_DEFEND = 0x42 + X_SPEED = 0x43 + X_SPECIAL = 0x44 + COIN_CASE = 0x45 + OAKS_PARCEL = 0x46 + ITEMFINDER = 0x47 + SILPH_SCOPE = 0x48 + POKE_FLUTE = 0x49 + LIFT_KEY = 0x4A + EXP_ALL = 0x4B + OLD_ROD = 0x4C + GOOD_ROD = 0x4D + SUPER_ROD = 0x4E + PP_UP = 0x4F + ETHER = 0x50 + MAX_ETHER = 0x51 + ELIXER = 0x52 + MAX_ELIXER = 0x53 + FLOOR_B2F = 0x54 + FLOOR_B1F = 0x55 + FLOOR_1F = 0x56 + FLOOR_2F = 0x57 + FLOOR_3F = 0x58 + FLOOR_4F = 0x59 + FLOOR_5F = 0x5A + FLOOR_6F = 0x5B + FLOOR_7F = 0x5C + FLOOR_8F = 0x5D + FLOOR_9F = 0x5E + FLOOR_10F = 0x5F + FLOOR_11F = 0x60 + FLOOR_B4F = 0x61 + HM_01 = 0xC4 + HM_02 = 0xC5 + HM_03 = 0xC6 + HM_04 = 0xC7 + HM_05 = 0xC8 + TM_01 = 0xC9 + TM_02 = 0xCA + TM_03 = 0xCB + TM_04 = 0xCC + TM_05 = 0xCD + TM_06 = 0xCE + TM_07 = 0xCF + TM_08 = 0xD0 + TM_09 = 0xD1 + TM_10 = 0xD2 + TM_11 = 0xD3 + TM_12 = 0xD4 + TM_13 = 0xD5 + TM_14 = 0xD6 + TM_15 = 0xD7 + TM_16 = 0xD8 + TM_17 = 0xD9 + TM_18 = 0xDA + TM_19 = 0xDB + TM_20 = 0xDC + TM_21 = 0xDD + TM_22 = 0xDE + TM_23 = 0xDF + TM_24 = 0xE0 + TM_25 = 0xE1 + TM_26 = 0xE2 + TM_27 = 0xE3 + TM_28 = 0xE4 + TM_29 = 0xE5 + TM_30 = 0xE6 + TM_31 = 0xE7 + TM_32 = 0xE8 + TM_33 = 0xE9 + TM_34 = 0xEA + TM_35 = 0xEB + TM_36 = 0xEC + TM_37 = 0xED + TM_38 = 0xEE + TM_39 = 0xEF + TM_40 = 0xF0 + TM_41 = 0xF1 + TM_42 = 0xF2 + TM_43 = 0xF3 + TM_44 = 0xF4 + TM_45 = 0xF5 + TM_46 = 0xF6 + TM_47 = 0xF7 + TM_48 = 0xF8 + TM_49 = 0xF9 + TM_50 = 0xFA \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/data/constants/locations.py b/pyboy/plugins/game_wrapper_pokemon_gen1/data/constants/locations.py new file mode 100644 index 000000000..b675eaa8f --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/data/constants/locations.py @@ -0,0 +1,502 @@ +from enum import Enum + +class LocationId(Enum): + PALLET_TOWN = 0x00 + VIRIDIAN_CITY = 0x01 + PEWTER_CITY = 0x02 + CERULEAN_CITY = 0x03 + LAVENDER_TOWN = 0x04 + VERMILION_CITY = 0x05 + CELADON_CITY = 0x06 + FUCHSIA_CITY = 0x07 + CINNABAR_ISLAND = 0x08 + INDIGO_PLATEAU = 0x09 + SAFFRON_CITY = 0x0A + UNUSED_MAP_0B = 0x0B + ROUTE_1 = 0x0C + ROUTE_2 = 0x0D + ROUTE_3 = 0x0E + ROUTE_4 = 0x0F + ROUTE_5 = 0x10 + ROUTE_6 = 0x11 + ROUTE_7 = 0x12 + ROUTE_8 = 0x13 + ROUTE_9 = 0x14 + ROUTE_10 = 0x15 + ROUTE_11 = 0x16 + ROUTE_12 = 0x17 + ROUTE_13 = 0x18 + ROUTE_14 = 0x19 + ROUTE_15 = 0x1A + ROUTE_16 = 0x1B + ROUTE_17 = 0x1C + ROUTE_18 = 0x1D + ROUTE_19 = 0x1E + ROUTE_20 = 0x1F + ROUTE_21 = 0x20 + ROUTE_22 = 0x21 + ROUTE_23 = 0x22 + ROUTE_24 = 0x23 + ROUTE_25 = 0x24 + REDS_HOUSE_1F = 0x25 + REDS_HOUSE_2F = 0x26 + BLUES_HOUSE = 0x27 + OAKS_LAB = 0x28 + VIRIDIAN_POKECENTER = 0x29 + VIRIDIAN_MART = 0x2A + VIRIDIAN_SCHOOL_HOUSE = 0x2B + VIRIDIAN_NICKNAME_HOUSE = 0x2C + VIRIDIAN_GYM = 0x2D + DIGLETTS_CAVE_ROUTE_2 = 0x2E + VIRIDIAN_FOREST_NORTH_GATE = 0x2F + ROUTE_2_TRADE_HOUSE = 0x30 + ROUTE_2_GATE = 0x31 + VIRIDIAN_FOREST_SOUTH_GATE = 0x32 + VIRIDIAN_FOREST = 0x33 + MUSEUM_1F = 0x34 + MUSEUM_2F = 0x35 + PEWTER_GYM = 0x36 + PEWTER_NIDORAN_HOUSE = 0x37 + PEWTER_MART = 0x38 + PEWTER_SPEECH_HOUSE = 0x39 + PEWTER_POKECENTER = 0x3A + MT_MOON_1F = 0x3B + MT_MOON_B1F = 0x3C + MT_MOON_B2F = 0x3D + CERULEAN_TRASHED_HOUSE = 0x3E + CERULEAN_TRADE_HOUSE = 0x3F + CERULEAN_POKECENTER = 0x40 + CERULEAN_GYM = 0x41 + BIKE_SHOP = 0x42 + CERULEAN_MART = 0x43 + MT_MOON_POKECENTER = 0x44 + CERULEAN_TRASHED_HOUSE_COPY = 0x45 + ROUTE_5_GATE = 0x46 + UNDERGROUND_PATH_ROUTE_5 = 0x47 + DAYCARE = 0x48 + ROUTE_6_GATE = 0x49 + UNDERGROUND_PATH_ROUTE_6 = 0x4A + UNDERGROUND_PATH_ROUTE_6_COPY = 0x4B + ROUTE_7_GATE = 0x4C + UNDERGROUND_PATH_ROUTE_7 = 0x4D + UNDERGROUND_PATH_ROUTE_7_COPY = 0x4E + ROUTE_8_GATE = 0x4F + UNDERGROUND_PATH_ROUTE_8 = 0x50 + ROCK_TUNNEL_POKECENTER = 0x51 + ROCK_TUNNEL_1F = 0x52 + POWER_PLANT = 0x53 + ROUTE_11_GATE_1F = 0x54 + DIGLETTS_CAVE_ROUTE_11 = 0x55 + ROUTE_11_GATE_2F = 0x56 + ROUTE_12_GATE_1F = 0x57 + BILLS_HOUSE = 0x58 + VERMILION_POKECENTER = 0x59 + POKEMON_FAN_CLUB = 0x5A + VERMILION_MART = 0x5B + VERMILION_GYM = 0x5C + VERMILION_PIDGEY_HOUSE = 0x5D + VERMILION_DOCK = 0x5E + SS_ANNE_1F = 0x5F + SS_ANNE_2F = 0x60 + SS_ANNE_3F = 0x61 + SS_ANNE_B1F = 0x62 + SS_ANNE_BOW = 0x63 + SS_ANNE_KITCHEN = 0x64 + SS_ANNE_CAPTAINS_ROOM = 0x65 + SS_ANNE_1F_ROOMS = 0x66 + SS_ANNE_2F_ROOMS = 0x67 + SS_ANNE_B1F_ROOMS = 0x68 + UNUSED_MAP_69 = 0x69 + UNUSED_MAP_6A = 0x6A + UNUSED_MAP_6B = 0x6B + VICTORY_ROAD_1F = 0x6C + UNUSED_MAP_6D = 0x6D + UNUSED_MAP_6E = 0x6E + UNUSED_MAP_6F = 0x6F + UNUSED_MAP_70 = 0x70 + LANCES_ROOM = 0x71 + UNUSED_MAP_72 = 0x72 + UNUSED_MAP_73 = 0x73 + UNUSED_MAP_74 = 0x74 + UNUSED_MAP_75 = 0x75 + HALL_OF_FAME = 0x76 + UNDERGROUND_PATH_NORTH_SOUTH = 0x77 + CHAMPIONS_ROOM = 0x78 + UNDERGROUND_PATH_WEST_EAST = 0x79 + CELADON_MART_1F = 0x7A + CELADON_MART_2F = 0x7B + CELADON_MART_3F = 0x7C + CELADON_MART_4F = 0x7D + CELADON_MART_ROOF = 0x7E + CELADON_MART_ELEVATOR = 0x7F + CELADON_MANSION_1F = 0x80 + CELADON_MANSION_2F = 0x81 + CELADON_MANSION_3F = 0x82 + CELADON_MANSION_ROOF = 0x83 + CELADON_MANSION_ROOF_HOUSE = 0x84 + CELADON_POKECENTER = 0x85 + CELADON_GYM = 0x86 + GAME_CORNER = 0x87 + CELADON_MART_5F = 0x88 + GAME_CORNER_PRIZE_ROOM = 0x89 + CELADON_DINER = 0x8A + CELADON_CHIEF_HOUSE = 0x8B + CELADON_HOTEL = 0x8C + LAVENDER_POKECENTER = 0x8D + POKEMON_TOWER_1F = 0x8E + POKEMON_TOWER_2F = 0x8F + POKEMON_TOWER_3F = 0x90 + POKEMON_TOWER_4F = 0x91 + POKEMON_TOWER_5F = 0x92 + POKEMON_TOWER_6F = 0x93 + POKEMON_TOWER_7F = 0x94 + MR_FUJIS_HOUSE = 0x95 + LAVENDER_MART = 0x96 + LAVENDER_CUBONE_HOUSE = 0x97 + FUCHSIA_MART = 0x98 + FUCHSIA_BILLS_GRANDPAS_HOUSE = 0x99 + FUCHSIA_POKECENTER = 0x9A + WARDENS_HOUSE = 0x9B + SAFARI_ZONE_GATE = 0x9C + FUCHSIA_GYM = 0x9D + FUCHSIA_MEETING_ROOM = 0x9E + SEAFOAM_ISLANDS_B1F = 0x9F + SEAFOAM_ISLANDS_B2F = 0xA0 + SEAFOAM_ISLANDS_B3F = 0xA1 + SEAFOAM_ISLANDS_B4F = 0xA2 + VERMILION_OLD_ROD_HOUSE = 0xA3 + FUCHSIA_GOOD_ROD_HOUSE = 0xA4 + POKEMON_MANSION_1F = 0xA5 + CINNABAR_GYM = 0xA6 + CINNABAR_LAB = 0xA7 + CINNABAR_LAB_TRADE_ROOM = 0xA8 + CINNABAR_LAB_METRONOME_ROOM = 0xA9 + CINNABAR_LAB_FOSSIL_ROOM = 0xAA + CINNABAR_POKECENTER = 0xAB + CINNABAR_MART = 0xAC + CINNABAR_MART_COPY = 0xAD + INDIGO_PLATEAU_LOBBY = 0xAE + COPYCATS_HOUSE_1F = 0xAF + COPYCATS_HOUSE_2F = 0xB0 + FIGHTING_DOJO = 0xB1 + SAFFRON_GYM = 0xB2 + SAFFRON_PIDGEY_HOUSE = 0xB3 + SAFFRON_MART = 0xB4 + SILPH_CO_1F = 0xB5 + SAFFRON_POKECENTER = 0xB6 + MR_PSYCHICS_HOUSE = 0xB7 + ROUTE_15_GATE_1F = 0xB8 + ROUTE_15_GATE_2F = 0xB9 + ROUTE_16_GATE_1F = 0xBA + ROUTE_16_GATE_2F = 0xBB + ROUTE_16_FLY_HOUSE = 0xBC + ROUTE_12_SUPER_ROD_HOUSE = 0xBD + ROUTE_18_GATE_1F = 0xBE + ROUTE_18_GATE_2F = 0xBF + SEAFOAM_ISLANDS_1F = 0xC0 + ROUTE_22_GATE = 0xC1 + VICTORY_ROAD_2F = 0xC2 + ROUTE_12_GATE_2F = 0xC3 + VERMILION_TRADE_HOUSE = 0xC4 + DIGLETTS_CAVE = 0xC5 + VICTORY_ROAD_3F = 0xC6 + ROCKET_HIDEOUT_B1F = 0xC7 + ROCKET_HIDEOUT_B2F = 0xC8 + ROCKET_HIDEOUT_B3F = 0xC9 + ROCKET_HIDEOUT_B4F = 0xCA + ROCKET_HIDEOUT_ELEVATOR = 0xCB + UNUSED_MAP_CC = 0xCC + UNUSED_MAP_CD = 0xCD + UNUSED_MAP_CE = 0xCE + SILPH_CO_2F = 0xCF + SILPH_CO_3F = 0xD0 + SILPH_CO_4F = 0xD1 + SILPH_CO_5F = 0xD2 + SILPH_CO_6F = 0xD3 + SILPH_CO_7F = 0xD4 + SILPH_CO_8F = 0xD5 + POKEMON_MANSION_2F = 0xD6 + POKEMON_MANSION_3F = 0xD7 + POKEMON_MANSION_B1F = 0xD8 + SAFARI_ZONE_EAST = 0xD9 + SAFARI_ZONE_NORTH = 0xDA + SAFARI_ZONE_WEST = 0xDB + SAFARI_ZONE_CENTER = 0xDC + SAFARI_ZONE_CENTER_REST_HOUSE = 0xDD + SAFARI_ZONE_SECRET_HOUSE = 0xDE + SAFARI_ZONE_WEST_REST_HOUSE = 0xDF + SAFARI_ZONE_EAST_REST_HOUSE = 0xE0 + SAFARI_ZONE_NORTH_REST_HOUSE = 0xE1 + CERULEAN_CAVE_2F = 0xE2 + CERULEAN_CAVE_B1F = 0xE3 + CERULEAN_CAVE_1F = 0xE4 + NAME_RATERS_HOUSE = 0xE5 + CERULEAN_BADGE_HOUSE = 0xE6 + UNUSED_MAP_E7 = 0xE7 + ROCK_TUNNEL_B1F = 0xE8 + SILPH_CO_9F = 0xE9 + SILPH_CO_10F = 0xEA + SILPH_CO_11F = 0xEB + SILPH_CO_ELEVATOR = 0xEC + UNUSED_MAP_ED = 0xED + UNUSED_MAP_EE = 0xEE + TRADE_CENTER = 0xEF + COLOSSEUM = 0xF0 + UNUSED_MAP_F1 = 0xF1 + UNUSED_MAP_F2 = 0xF2 + UNUSED_MAP_F3 = 0xF3 + UNUSED_MAP_F4 = 0xF4 + LORELEIS_ROOM = 0xF5 + BRUNOS_ROOM = 0xF6 + AGATHAS_ROOM = 0xF7 + +LOCATION_NAMES = { + LocationId.PALLET_TOWN: 'PALLET TOWN', + LocationId.VIRIDIAN_CITY: 'VIRIDIAN CITY', + LocationId.PEWTER_CITY: 'PEWTER CITY', + LocationId.CERULEAN_CITY: 'CERULEAN CITY', + LocationId.LAVENDER_TOWN: 'LAVENDER TOWN', + LocationId.VERMILION_CITY: 'VERMILION CITY', + LocationId.CELADON_CITY: 'CELADON CITY', + LocationId.FUCHSIA_CITY: 'FUCHSIA CITY', + LocationId.CINNABAR_ISLAND: 'CINNABAR ISLAND', + LocationId.INDIGO_PLATEAU: 'INDIGO PLATEAU', + LocationId.SAFFRON_CITY: 'SAFFRON CITY', + LocationId.UNUSED_MAP_0B: 'UNUSED MAP 0B', + LocationId.ROUTE_1: 'ROUTE 1', + LocationId.ROUTE_2: 'ROUTE 2', + LocationId.ROUTE_3: 'ROUTE 3', + LocationId.ROUTE_4: 'ROUTE 4', + LocationId.ROUTE_5: 'ROUTE 5', + LocationId.ROUTE_6: 'ROUTE 6', + LocationId.ROUTE_7: 'ROUTE 7', + LocationId.ROUTE_8: 'ROUTE 8', + LocationId.ROUTE_9: 'ROUTE 9', + LocationId.ROUTE_10: 'ROUTE 10', + LocationId.ROUTE_11: 'ROUTE 11', + LocationId.ROUTE_12: 'ROUTE 12', + LocationId.ROUTE_13: 'ROUTE 13', + LocationId.ROUTE_14: 'ROUTE 14', + LocationId.ROUTE_15: 'ROUTE 15', + LocationId.ROUTE_16: 'ROUTE 16', + LocationId.ROUTE_17: 'ROUTE 17', + LocationId.ROUTE_18: 'ROUTE 18', + LocationId.ROUTE_19: 'ROUTE 19', + LocationId.ROUTE_20: 'ROUTE 20', + LocationId.ROUTE_21: 'ROUTE 21', + LocationId.ROUTE_22: 'ROUTE 22', + LocationId.ROUTE_23: 'ROUTE 23', + LocationId.ROUTE_24: 'ROUTE 24', + LocationId.ROUTE_25: 'ROUTE 25', + LocationId.REDS_HOUSE_1F: 'RED\'S HOUSE 1F', + LocationId.REDS_HOUSE_2F: 'RED\'S HOUSE 2F', + LocationId.BLUES_HOUSE: 'BLUE\'S HOUSE', + LocationId.OAKS_LAB: 'OAK\'S LAB', + LocationId.VIRIDIAN_POKECENTER: 'VIRIDIAN POKECENTER', + LocationId.VIRIDIAN_MART: 'VIRIDIAN MART', + LocationId.VIRIDIAN_SCHOOL_HOUSE: 'VIRIDIAN SCHOOL HOUSE', + LocationId.VIRIDIAN_NICKNAME_HOUSE: 'VIRIDIAN NICKNAME HOUSE', + LocationId.VIRIDIAN_GYM: 'VIRIDIAN GYM', + LocationId.DIGLETTS_CAVE_ROUTE_2: 'DIGLETTS CAVE ROUTE 2', + LocationId.VIRIDIAN_FOREST_NORTH_GATE: 'VIRIDIAN FOREST NORTH GATE', + LocationId.ROUTE_2_TRADE_HOUSE: 'ROUTE 2 TRADE HOUSE', + LocationId.ROUTE_2_GATE: 'ROUTE 2 GATE', + LocationId.VIRIDIAN_FOREST_SOUTH_GATE: 'VIRIDIAN FOREST SOUTH GATE', + LocationId.VIRIDIAN_FOREST: 'VIRIDIAN FOREST', + LocationId.MUSEUM_1F: 'MUSEUM 1F', + LocationId.MUSEUM_2F: 'MUSEUM 2F', + LocationId.PEWTER_GYM: 'PEWTER GYM', + LocationId.PEWTER_NIDORAN_HOUSE: 'PEWTER NIDORAN HOUSE', + LocationId.PEWTER_MART: 'PEWTER MART', + LocationId.PEWTER_SPEECH_HOUSE: 'PEWTER SPEECH HOUSE', + LocationId.PEWTER_POKECENTER: 'PEWTER POKECENTER', + LocationId.MT_MOON_1F: 'MT MOON 1F', + LocationId.MT_MOON_B1F: 'MT MOON B1F', + LocationId.MT_MOON_B2F: 'MT MOON B2F', + LocationId.CERULEAN_TRASHED_HOUSE: 'CERULEAN TRASHED HOUSE', + LocationId.CERULEAN_TRADE_HOUSE: 'CERULEAN TRADE HOUSE', + LocationId.CERULEAN_POKECENTER: 'CERULEAN POKECENTER', + LocationId.CERULEAN_GYM: 'CERULEAN GYM', + LocationId.BIKE_SHOP: 'BIKE SHOP', + LocationId.CERULEAN_MART: 'CERULEAN MART', + LocationId.MT_MOON_POKECENTER: 'MT MOON POKECENTER', + LocationId.CERULEAN_TRASHED_HOUSE_COPY: 'CERULEAN TRASHED HOUSE COPY', + LocationId.ROUTE_5_GATE: 'ROUTE 5 GATE', + LocationId.UNDERGROUND_PATH_ROUTE_5: 'UNDERGROUND PATH ROUTE 5', + LocationId.DAYCARE: 'DAYCARE', + LocationId.ROUTE_6_GATE: 'ROUTE 6 GATE', + LocationId.UNDERGROUND_PATH_ROUTE_6: 'UNDERGROUND PATH ROUTE 6', + LocationId.UNDERGROUND_PATH_ROUTE_6_COPY: 'UNDERGROUND PATH ROUTE 6 COPY', + LocationId.ROUTE_7_GATE: 'ROUTE 7 GATE', + LocationId.UNDERGROUND_PATH_ROUTE_7: 'UNDERGROUND PATH ROUTE 7', + LocationId.UNDERGROUND_PATH_ROUTE_7_COPY: 'UNDERGROUND PATH ROUTE 7 COPY', + LocationId.ROUTE_8_GATE: 'ROUTE 8 GATE', + LocationId.UNDERGROUND_PATH_ROUTE_8: 'UNDERGROUND PATH ROUTE 8', + LocationId.ROCK_TUNNEL_POKECENTER: 'ROCK TUNNEL POKECENTER', + LocationId.ROCK_TUNNEL_1F: 'ROCK TUNNEL 1F', + LocationId.POWER_PLANT: 'POWER PLANT', + LocationId.ROUTE_11_GATE_1F: 'ROUTE 11 GATE 1F', + LocationId.DIGLETTS_CAVE_ROUTE_11: 'DIGLETT\'S CAVE ROUTE 11', + LocationId.ROUTE_11_GATE_2F: 'ROUTE 11 GATE 2F', + LocationId.ROUTE_12_GATE_1F: 'ROUTE 12 GATE 1F', + LocationId.BILLS_HOUSE: 'BILLS HOUSE', + LocationId.VERMILION_POKECENTER: 'VERMILION POKECENTER', + LocationId.POKEMON_FAN_CLUB: 'POKEMON FAN CLUB', + LocationId.VERMILION_MART: 'VERMILION MART', + LocationId.VERMILION_GYM: 'VERMILION GYM', + LocationId.VERMILION_PIDGEY_HOUSE: 'VERMILION PIDGEY HOUSE', + LocationId.VERMILION_DOCK: 'VERMILION DOCK', + LocationId.SS_ANNE_1F: 'SS ANNE 1F', + LocationId.SS_ANNE_2F: 'SS ANNE 2F', + LocationId.SS_ANNE_3F: 'SS ANNE 3F', + LocationId.SS_ANNE_B1F: 'SS ANNE B1F', + LocationId.SS_ANNE_BOW: 'SS ANNE BOW', + LocationId.SS_ANNE_KITCHEN: 'SS ANNE KITCHEN', + LocationId.SS_ANNE_CAPTAINS_ROOM: 'SS ANNE CAPTAIN\'S ROOM', + LocationId.SS_ANNE_1F_ROOMS: 'SS ANNE 1F ROOMS', + LocationId.SS_ANNE_2F_ROOMS: 'SS ANNE 2F ROOMS', + LocationId.SS_ANNE_B1F_ROOMS: 'SS ANNE B1F ROOMS', + LocationId.UNUSED_MAP_69: 'UNUSED MAP 69', + LocationId.UNUSED_MAP_6A: 'UNUSED MAP 6A', + LocationId.UNUSED_MAP_6B: 'UNUSED MAP 6B', + LocationId.VICTORY_ROAD_1F: 'VICTORY ROAD 1F', + LocationId.UNUSED_MAP_6D: 'UNUSED MAP 6D', + LocationId.UNUSED_MAP_6E: 'UNUSED MAP 6E', + LocationId.UNUSED_MAP_6F: 'UNUSED MAP 6F', + LocationId.UNUSED_MAP_70: 'UNUSED MAP 70', + LocationId.LANCES_ROOM: 'LANCES ROOM', + LocationId.UNUSED_MAP_72: 'UNUSED MAP 72', + LocationId.UNUSED_MAP_73: 'UNUSED MAP 73', + LocationId.UNUSED_MAP_74: 'UNUSED MAP 74', + LocationId.UNUSED_MAP_75: 'UNUSED MAP 75', + LocationId.HALL_OF_FAME: 'HALL OF FAME', + LocationId.UNDERGROUND_PATH_NORTH_SOUTH: 'UNDERGROUND PATH NORTH SOUTH', + LocationId.CHAMPIONS_ROOM: 'CHAMPION\'S ROOM', + LocationId.UNDERGROUND_PATH_WEST_EAST: 'UNDERGROUND PATH WEST EAST', + LocationId.CELADON_MART_1F: 'CELADON MART 1F', + LocationId.CELADON_MART_2F: 'CELADON MART 2F', + LocationId.CELADON_MART_3F: 'CELADON MART 3F', + LocationId.CELADON_MART_4F: 'CELADON MART 4F', + LocationId.CELADON_MART_ROOF: 'CELADON MART ROOF', + LocationId.CELADON_MART_ELEVATOR: 'CELADON MART ELEVATOR', + LocationId.CELADON_MANSION_1F: 'CELADON MANSION 1F', + LocationId.CELADON_MANSION_2F: 'CELADON MANSION 2F', + LocationId.CELADON_MANSION_3F: 'CELADON MANSION 3F', + LocationId.CELADON_MANSION_ROOF: 'CELADON MANSION ROOF', + LocationId.CELADON_MANSION_ROOF_HOUSE: 'CELADON MANSION ROOF HOUSE', + LocationId.CELADON_POKECENTER: 'CELADON POKECENTER', + LocationId.CELADON_GYM: 'CELADON GYM', + LocationId.GAME_CORNER: 'GAME CORNER', + LocationId.CELADON_MART_5F: 'CELADON MART 5F', + LocationId.GAME_CORNER_PRIZE_ROOM: 'GAME CORNER PRIZE ROOM', + LocationId.CELADON_DINER: 'CELADON DINER', + LocationId.CELADON_CHIEF_HOUSE: 'CELADON CHIEF HOUSE', + LocationId.CELADON_HOTEL: 'CELADON HOTEL', + LocationId.LAVENDER_POKECENTER: 'LAVENDER POKECENTER', + LocationId.POKEMON_TOWER_1F: 'POKEMON TOWER 1F', + LocationId.POKEMON_TOWER_2F: 'POKEMON TOWER 2F', + LocationId.POKEMON_TOWER_3F: 'POKEMON TOWER 3F', + LocationId.POKEMON_TOWER_4F: 'POKEMON TOWER 4F', + LocationId.POKEMON_TOWER_5F: 'POKEMON TOWER 5F', + LocationId.POKEMON_TOWER_6F: 'POKEMON TOWER 6F', + LocationId.POKEMON_TOWER_7F: 'POKEMON TOWER 7F', + LocationId.MR_FUJIS_HOUSE: 'MR FUJIS HOUSE', + LocationId.LAVENDER_MART: 'LAVENDER MART', + LocationId.LAVENDER_CUBONE_HOUSE: 'LAVENDER CUBONE HOUSE', + LocationId.FUCHSIA_MART: 'FUCHSIA MART', + LocationId.FUCHSIA_BILLS_GRANDPAS_HOUSE: 'FUCHSIA BILL\'S GRANDPAS HOUSE', + LocationId.FUCHSIA_POKECENTER: 'FUCHSIA POKECENTER', + LocationId.WARDENS_HOUSE: 'WARDENS HOUSE', + LocationId.SAFARI_ZONE_GATE: 'SAFARI ZONE GATE', + LocationId.FUCHSIA_GYM: 'FUCHSIA GYM', + LocationId.FUCHSIA_MEETING_ROOM: 'FUCHSIA MEETING ROOM', + LocationId.SEAFOAM_ISLANDS_B1F: 'SEAFOAM ISLANDS B1F', + LocationId.SEAFOAM_ISLANDS_B2F: 'SEAFOAM ISLANDS B2F', + LocationId.SEAFOAM_ISLANDS_B3F: 'SEAFOAM ISLANDS B3F', + LocationId.SEAFOAM_ISLANDS_B4F: 'SEAFOAM ISLANDS B4F', + LocationId.VERMILION_OLD_ROD_HOUSE: 'VERMILION OLD ROD HOUSE', + LocationId.FUCHSIA_GOOD_ROD_HOUSE: 'FUCHSIA GOOD ROD HOUSE', + LocationId.POKEMON_MANSION_1F: 'POKEMON MANSION 1F', + LocationId.CINNABAR_GYM: 'CINNABAR GYM', + LocationId.CINNABAR_LAB: 'CINNABAR LAB', + LocationId.CINNABAR_LAB_TRADE_ROOM: 'CINNABAR LAB TRADE ROOM', + LocationId.CINNABAR_LAB_METRONOME_ROOM: 'CINNABAR LAB METRONOME ROOM', + LocationId.CINNABAR_LAB_FOSSIL_ROOM: 'CINNABAR LAB FOSSIL ROOM', + LocationId.CINNABAR_POKECENTER: 'CINNABAR POKECENTER', + LocationId.CINNABAR_MART: 'CINNABAR MART', + LocationId.CINNABAR_MART_COPY: 'CINNABAR MART COPY', + LocationId.INDIGO_PLATEAU_LOBBY: 'INDIGO PLATEAU LOBBY', + LocationId.COPYCATS_HOUSE_1F: 'COPYCATS HOUSE 1F', + LocationId.COPYCATS_HOUSE_2F: 'COPYCATS HOUSE 2F', + LocationId.FIGHTING_DOJO: 'FIGHTING DOJO', + LocationId.SAFFRON_GYM: 'SAFFRON GYM', + LocationId.SAFFRON_PIDGEY_HOUSE: 'SAFFRON PIDGEY HOUSE', + LocationId.SAFFRON_MART: 'SAFFRON MART', + LocationId.SILPH_CO_1F: 'SILPH CO 1F', + LocationId.SAFFRON_POKECENTER: 'SAFFRON POKECENTER', + LocationId.MR_PSYCHICS_HOUSE: 'MR PSYCHICS HOUSE', + LocationId.ROUTE_15_GATE_1F: 'ROUTE 15 GATE 1F', + LocationId.ROUTE_15_GATE_2F: 'ROUTE 15 GATE 2F', + LocationId.ROUTE_16_GATE_1F: 'ROUTE 16 GATE 1F', + LocationId.ROUTE_16_GATE_2F: 'ROUTE 16 GATE 2F', + LocationId.ROUTE_16_FLY_HOUSE: 'ROUTE 16 FLY HOUSE', + LocationId.ROUTE_12_SUPER_ROD_HOUSE: 'ROUTE 12 SUPER ROD HOUSE', + LocationId.ROUTE_18_GATE_1F: 'ROUTE 18 GATE 1F', + LocationId.ROUTE_18_GATE_2F: 'ROUTE 18 GATE 2F', + LocationId.SEAFOAM_ISLANDS_1F: 'SEAFOAM ISLANDS 1F', + LocationId.ROUTE_22_GATE: 'ROUTE 22 GATE', + LocationId.VICTORY_ROAD_2F: 'VICTORY ROAD 2F', + LocationId.ROUTE_12_GATE_2F: 'ROUTE 12 GATE 2F', + LocationId.VERMILION_TRADE_HOUSE: 'VERMILION TRADE HOUSE', + LocationId.DIGLETTS_CAVE: 'DIGLETTS CAVE', + LocationId.VICTORY_ROAD_3F: 'VICTORY ROAD 3F', + LocationId.ROCKET_HIDEOUT_B1F: 'ROCKET HIDEOUT B1F', + LocationId.ROCKET_HIDEOUT_B2F: 'ROCKET HIDEOUT B2F', + LocationId.ROCKET_HIDEOUT_B3F: 'ROCKET HIDEOUT B3F', + LocationId.ROCKET_HIDEOUT_B4F: 'ROCKET HIDEOUT B4F', + LocationId.ROCKET_HIDEOUT_ELEVATOR: 'ROCKET HIDEOUT ELEVATOR', + LocationId.UNUSED_MAP_CC: 'UNUSED MAP CC', + LocationId.UNUSED_MAP_CD: 'UNUSED MAP CD', + LocationId.UNUSED_MAP_CE: 'UNUSED MAP CE', + LocationId.SILPH_CO_2F: 'SILPH CO 2F', + LocationId.SILPH_CO_3F: 'SILPH CO 3F', + LocationId.SILPH_CO_4F: 'SILPH CO 4F', + LocationId.SILPH_CO_5F: 'SILPH CO 5F', + LocationId.SILPH_CO_6F: 'SILPH CO 6F', + LocationId.SILPH_CO_7F: 'SILPH CO 7F', + LocationId.SILPH_CO_8F: 'SILPH CO 8F', + LocationId.POKEMON_MANSION_2F: 'POKEMON MANSION 2F', + LocationId.POKEMON_MANSION_3F: 'POKEMON MANSION 3F', + LocationId.POKEMON_MANSION_B1F: 'POKEMON MANSION B1F', + LocationId.SAFARI_ZONE_EAST: 'SAFARI ZONE EAST', + LocationId.SAFARI_ZONE_NORTH: 'SAFARI ZONE NORTH', + LocationId.SAFARI_ZONE_WEST: 'SAFARI ZONE WEST', + LocationId.SAFARI_ZONE_CENTER: 'SAFARI ZONE CENTER', + LocationId.SAFARI_ZONE_CENTER_REST_HOUSE: 'SAFARI ZONE CENTER REST HOUSE', + LocationId.SAFARI_ZONE_SECRET_HOUSE: 'SAFARI ZONE SECRET HOUSE', + LocationId.SAFARI_ZONE_WEST_REST_HOUSE: 'SAFARI ZONE WEST REST HOUSE', + LocationId.SAFARI_ZONE_EAST_REST_HOUSE: 'SAFARI ZONE EAST REST HOUSE', + LocationId.SAFARI_ZONE_NORTH_REST_HOUSE: 'SAFARI ZONE NORTH REST HOUSE', + LocationId.CERULEAN_CAVE_2F: 'CERULEAN CAVE 2F', + LocationId.CERULEAN_CAVE_B1F: 'CERULEAN CAVE B1F', + LocationId.CERULEAN_CAVE_1F: 'CERULEAN CAVE 1F', + LocationId.NAME_RATERS_HOUSE: 'NAME RATER\'S HOUSE', + LocationId.CERULEAN_BADGE_HOUSE: 'CERULEAN BADGE HOUSE', + LocationId.UNUSED_MAP_E7: 'UNUSED MAP E7', + LocationId.ROCK_TUNNEL_B1F: 'ROCK TUNNEL B1F', + LocationId.SILPH_CO_9F: 'SILPH CO 9F', + LocationId.SILPH_CO_10F: 'SILPH CO 10F', + LocationId.SILPH_CO_11F: 'SILPH CO 11F', + LocationId.SILPH_CO_ELEVATOR: 'SILPH CO ELEVATOR', + LocationId.UNUSED_MAP_ED: 'UNUSED MAP ED', + LocationId.UNUSED_MAP_EE: 'UNUSED MAP EE', + LocationId.TRADE_CENTER: 'TRADE CENTER', + LocationId.COLOSSEUM: 'COLOSSEUM', + LocationId.UNUSED_MAP_F1: 'UNUSED MAP F1', + LocationId.UNUSED_MAP_F2: 'UNUSED MAP F2', + LocationId.UNUSED_MAP_F3: 'UNUSED MAP F3', + LocationId.UNUSED_MAP_F4: 'UNUSED MAP F4', + LocationId.LORELEIS_ROOM: 'LORELEI\'S ROOM', + LocationId.BRUNOS_ROOM: 'BRUNO\'S ROOM', + LocationId.AGATHAS_ROOM: 'AGATHA\'S ROOM', +} \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/data/constants/misc.py b/pyboy/plugins/game_wrapper_pokemon_gen1/data/constants/misc.py new file mode 100644 index 000000000..906daebe3 --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/data/constants/misc.py @@ -0,0 +1,11 @@ +from enum import Enum + +class Badge(Enum): + BOULDER_BADGE = 0 + CASCADE_BADGE = 1 + THUNDER_BADGE = 2 + RAINBOW_BADGE = 3 + SOUL_BADGE = 4 + MARSH_BADGE = 5 + VOLCANO_BADGE = 6 + EARTH_BADGE = 7 \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/data/constants/moves.py b/pyboy/plugins/game_wrapper_pokemon_gen1/data/constants/moves.py new file mode 100644 index 000000000..4bf21950b --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/data/constants/moves.py @@ -0,0 +1,167 @@ +from enum import Enum + +class MoveId(Enum): + POUND = 0x01 + KARATE_CHOP = 0x02 + DOUBLESLAP = 0x03 + COMET_PUNCH = 0x04 + MEGA_PUNCH = 0x05 + PAY_DAY = 0x06 + FIRE_PUNCH = 0x07 + ICE_PUNCH = 0x08 + THUNDERPUNCH = 0x09 + SCRATCH = 0x0a + VICEGRIP = 0x0b + GUILLOTINE = 0x0c + RAZOR_WIND = 0x0d + SWORDS_DANCE = 0x0e + CUT = 0x0f + GUST = 0x10 + WING_ATTACK = 0x11 + WHIRLWIND = 0x12 + FLY = 0x13 + BIND = 0x14 + SLAM = 0x15 + VINE_WHIP = 0x16 + STOMP = 0x17 + DOUBLE_KICK = 0x18 + MEGA_KICK = 0x19 + JUMP_KICK = 0x1a + ROLLING_KICK = 0x1b + SAND_ATTACK = 0x1c + HEADBUTT = 0x1d + HORN_ATTACK = 0x1e + FURY_ATTACK = 0x1f + HORN_DRILL = 0x20 + TACKLE = 0x21 + BODY_SLAM = 0x22 + WRAP = 0x23 + TAKE_DOWN = 0x24 + THRASH = 0x25 + DOUBLE_EDGE = 0x26 + TAIL_WHIP = 0x27 + POISON_STING = 0x28 + TWINEEDLE = 0x29 + PIN_MISSILE = 0x2a + LEER = 0x2b + BITE = 0x2c + GROWL = 0x2d + ROAR = 0x2e + SING = 0x2f + SUPERSONIC = 0x30 + SONICBOOM = 0x31 + DISABLE = 0x32 + ACID = 0x33 + EMBER = 0x34 + FLAMETHROWER = 0x35 + MIST = 0x36 + WATER_GUN = 0x37 + HYDRO_PUMP = 0x38 + SURF = 0x39 + ICE_BEAM = 0x3a + BLIZZARD = 0x3b + PSYBEAM = 0x3c + BUBBLEBEAM = 0x3d + AURORA_BEAM = 0x3e + HYPER_BEAM = 0x3f + PECK = 0x40 + DRILL_PECK = 0x41 + SUBMISSION = 0x42 + LOW_KICK = 0x43 + COUNTER = 0x44 + SEISMIC_TOSS = 0x45 + STRENGTH = 0x46 + ABSORB = 0x47 + MEGA_DRAIN = 0x48 + LEECH_SEED = 0x49 + GROWTH = 0x4a + RAZOR_LEAF = 0x4b + SOLARBEAM = 0x4c + POISONPOWDER = 0x4d + STUN_SPORE = 0x4e + SLEEP_POWDER = 0x4f + PETAL_DANCE = 0x50 + STRING_SHOT = 0x51 + DRAGON_RAGE = 0x52 + FIRE_SPIN = 0x53 + THUNDERSHOCK = 0x54 + THUNDERBOLT = 0x55 + THUNDER_WAVE = 0x56 + THUNDER = 0x57 + ROCK_THROW = 0x58 + EARTHQUAKE = 0x59 + FISSURE = 0x5a + DIG = 0x5b + TOXIC = 0x5c + CONFUSION = 0x5d + PSYCHIC_M = 0x5e + HYPNOSIS = 0x5f + MEDITATE = 0x60 + AGILITY = 0x61 + QUICK_ATTACK = 0x62 + RAGE = 0x63 + TELEPORT = 0x64 + NIGHT_SHADE = 0x65 + MIMIC = 0x66 + SCREECH = 0x67 + DOUBLE_TEAM = 0x68 + RECOVER = 0x69 + HARDEN = 0x6a + MINIMIZE = 0x6b + SMOKESCREEN = 0x6c + CONFUSE_RAY = 0x6d + WITHDRAW = 0x6e + DEFENSE_CURL = 0x6f + BARRIER = 0x70 + LIGHT_SCREEN = 0x71 + HAZE = 0x72 + REFLECT = 0x73 + FOCUS_ENERGY = 0x74 + BIDE = 0x75 + METRONOME = 0x76 + MIRROR_MOVE = 0x77 + SELFDESTRUCT = 0x78 + EGG_BOMB = 0x79 + LICK = 0x7a + SMOG = 0x7b + SLUDGE = 0x7c + BONE_CLUB = 0x7d + FIRE_BLAST = 0x7e + WATERFALL = 0x7f + CLAMP = 0x80 + SWIFT = 0x81 + SKULL_BASH = 0x82 + SPIKE_CANNON = 0x83 + CONSTRICT = 0x84 + AMNESIA = 0x85 + KINESIS = 0x86 + SOFTBOILED = 0x87 + HI_JUMP_KICK = 0x88 + GLARE = 0x89 + DREAM_EATER = 0x8a + POISON_GAS = 0x8b + BARRAGE = 0x8c + LEECH_LIFE = 0x8d + LOVELY_KISS = 0x8e + SKY_ATTACK = 0x8f + TRANSFORM = 0x90 + BUBBLE = 0x91 + DIZZY_PUNCH = 0x92 + SPORE = 0x93 + FLASH = 0x94 + PSYWAVE = 0x95 + SPLASH = 0x96 + ACID_ARMOR = 0x97 + CRABHAMMER = 0x98 + EXPLOSION = 0x99 + FURY_SWIPES = 0x9a + BONEMERANG = 0x9b + REST = 0x9c + ROCK_SLIDE = 0x9d + HYPER_FANG = 0x9e + SHARPEN = 0x9f + CONVERSION = 0xa0 + TRI_ATTACK = 0xa1 + SUPER_FANG = 0xa2 + SLASH = 0xa3 + SUBSTITUTE = 0xa4 \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/data/constants/pokemon.py b/pyboy/plugins/game_wrapper_pokemon_gen1/data/constants/pokemon.py new file mode 100644 index 000000000..52fa079d0 --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/data/constants/pokemon.py @@ -0,0 +1,501 @@ +from enum import Enum + +class PokemonId(Enum): + RHYDON = 0x01 + KANGASKHAN = 0x02 + NIDORAN_M = 0x03 + CLEFAIRY = 0x04 + SPEAROW = 0x05 + VOLTORB = 0x06 + NIDOKING = 0x07 + SLOWBRO = 0x08 + IVYSAUR = 0x09 + EXEGGUTOR = 0x0A + LICKITUNG = 0x0B + EXEGGCUTE = 0x0C + GRIMER = 0x0D + GENGAR = 0x0E + NIDORAN_F = 0x0F + NIDOQUEEN = 0x10 + CUBONE = 0x11 + RHYHORN = 0x12 + LAPRAS = 0x13 + ARCANINE = 0x14 + MEW = 0x15 + GYARADOS = 0x16 + SHELLDER = 0x17 + TENTACOOL = 0x18 + GASTLY = 0x19 + SCYTHER = 0x1A + STARYU = 0x1B + BLASTOISE = 0x1C + PINSIR = 0x1D + TANGELA = 0x1E + MISSINGNO_1F = 0x1F + MISSINGNO_20 = 0x20 + GROWLITHE = 0x21 + ONIX = 0x22 + FEAROW = 0x23 + PIDGEY = 0x24 + SLOWPOKE = 0x25 + KADABRA = 0x26 + GRAVELER = 0x27 + CHANSEY = 0x28 + MACHOKE = 0x29 + MR_MIME = 0x2A + HITMONLEE = 0x2B + HITMONCHAN = 0x2C + ARBOK = 0x2D + PARASECT = 0x2E + PSYDUCK = 0x2F + DROWZEE = 0x30 + GOLEM = 0x31 + MISSINGNO_32 = 0x32 + MAGMAR = 0x33 + MISSINGNO_34 = 0x34 + ELECTABUZZ = 0x35 + MAGNETON = 0x36 + KOFFING = 0x37 + MISSINGNO_38 = 0x38 + MANKEY = 0x39 + SEEL = 0x3A + DIGLETT = 0x3B + TAUROS = 0x3C + MISSINGNO_3D = 0x3D + MISSINGNO_3E = 0x3E + MISSINGNO_3F = 0x3F + FARFETCHD = 0x40 + VENONAT = 0x41 + DRAGONITE = 0x42 + MISSINGNO_43 = 0x43 + MISSINGNO_44 = 0x44 + MISSINGNO_45 = 0x45 + DODUO = 0x46 + POLIWAG = 0x47 + JYNX = 0x48 + MOLTRES = 0x49 + ARTICUNO = 0x4A + ZAPDOS = 0x4B + DITTO = 0x4C + MEOWTH = 0x4D + KRABBY = 0x4E + MISSINGNO_4F = 0x4F + MISSINGNO_50 = 0x50 + MISSINGNO_51 = 0x51 + VULPIX = 0x52 + NINETALES = 0x53 + PIKACHU = 0x54 + RAICHU = 0x55 + MISSINGNO_56 = 0x56 + MISSINGNO_57 = 0x57 + DRATINI = 0x58 + DRAGONAIR = 0x59 + KABUTO = 0x5A + KABUTOPS = 0x5B + HORSEA = 0x5C + SEADRA = 0x5D + MISSINGNO_5E = 0x5E + MISSINGNO_5F = 0x5F + SANDSHREW = 0x60 + SANDSLASH = 0x61 + OMANYTE = 0x62 + OMASTAR = 0x63 + JIGGLYPUFF = 0x64 + WIGGLYTUFF = 0x65 + EEVEE = 0x66 + FLAREON = 0x67 + JOLTEON = 0x68 + VAPOREON = 0x69 + MACHOP = 0x6A + ZUBAT = 0x6B + EKANS = 0x6C + PARAS = 0x6D + POLIWHIRL = 0x6E + POLIWRATH = 0x6F + WEEDLE = 0x70 + KAKUNA = 0x71 + BEEDRILL = 0x72 + MISSINGNO_73 = 0x73 + DODRIO = 0x74 + PRIMEAPE = 0x75 + DUGTRIO = 0x76 + VENOMOTH = 0x77 + DEWGONG = 0x78 + MISSINGNO_79 = 0x79 + MISSINGNO_7A = 0x7A + CATERPIE = 0x7B + METAPOD = 0x7C + BUTTERFREE = 0x7D + MACHAMP = 0x7E + MISSINGNO_7F = 0x7F + GOLDUCK = 0x80 + HYPNO = 0x81 + GOLBAT = 0x82 + MEWTWO = 0x83 + SNORLAX = 0x84 + MAGIKARP = 0x85 + MISSINGNO_86 = 0x86 + MISSINGNO_87 = 0x87 + MUK = 0x88 + MISSINGNO_89 = 0x89 + KINGLER = 0x8A + CLOYSTER = 0x8B + MISSINGNO_8C = 0x8C + ELECTRODE = 0x8D + CLEFABLE = 0x8E + WEEZING = 0x8F + PERSIAN = 0x90 + MAROWAK = 0x91 + MISSINGNO_92 = 0x92 + HAUNTER = 0x93 + ABRA = 0x94 + ALAKAZAM = 0x95 + PIDGEOTTO = 0x96 + PIDGEOT = 0x97 + STARMIE = 0x98 + BULBASAUR = 0x99 + VENUSAUR = 0x9A + TENTACRUEL = 0x9B + MISSINGNO_9C = 0x9C + GOLDEEN = 0x9D + SEAKING = 0x9E + MISSINGNO_9F = 0x9F + MISSINGNO_A0 = 0xA0 + MISSINGNO_A1 = 0xA1 + MISSINGNO_A2 = 0xA2 + PONYTA = 0xA3 + RAPIDASH = 0xA4 + RATTATA = 0xA5 + RATICATE = 0xA6 + NIDORINO = 0xA7 + NIDORINA = 0xA8 + GEODUDE = 0xA9 + PORYGON = 0xAA + AERODACTYL = 0xAB + MISSINGNO_AC = 0xAC + MAGNEMITE = 0xAD + MISSINGNO_AE = 0xAE + MISSINGNO_AF = 0xAF + CHARMANDER = 0xB0 + SQUIRTLE = 0xB1 + CHARMELEON = 0xB2 + WARTORTLE = 0xB3 + CHARIZARD = 0xB4 + MISSINGNO_B5 = 0xB5 + FOSSIL_KABUTOPS = 0xB6 + FOSSIL_AERODACTYL = 0xB7 + MON_GHOST = 0xB8 + ODDISH = 0xB9 + GLOOM = 0xBA + VILEPLUME = 0xBB + BELLSPROUT = 0xBC + WEEPINBELL = 0xBD + VICTREEBEL = 0xBE + +_POKEMON_POKEDEX_INDEX = { + PokemonId.BULBASAUR: 1, + PokemonId.IVYSAUR: 2, + PokemonId.VENUSAUR: 3, + PokemonId.CHARMANDER: 4, + PokemonId.CHARMELEON: 5, + PokemonId.CHARIZARD: 6, + PokemonId.SQUIRTLE: 7, + PokemonId.WARTORTLE: 8, + PokemonId.BLASTOISE: 9, + PokemonId.CATERPIE: 10, + PokemonId.METAPOD: 11, + PokemonId.BUTTERFREE: 12, + PokemonId.WEEDLE: 13, + PokemonId.KAKUNA: 14, + PokemonId.BEEDRILL: 15, + PokemonId.PIDGEY: 16, + PokemonId.PIDGEOTTO: 17, + PokemonId.PIDGEOT: 18, + PokemonId.RATTATA: 19, + PokemonId.RATICATE: 20, + PokemonId.SPEAROW: 21, + PokemonId.FEAROW: 22, + PokemonId.EKANS: 23, + PokemonId.ARBOK: 24, + PokemonId.PIKACHU: 25, + PokemonId.RAICHU: 26, + PokemonId.SANDSHREW: 27, + PokemonId.SANDSLASH: 28, + PokemonId.NIDORAN_F: 29, + PokemonId.NIDORINA: 30, + PokemonId.NIDOQUEEN: 31, + PokemonId.NIDORAN_M: 32, + PokemonId.NIDORINO: 33, + PokemonId.NIDOKING: 34, + PokemonId.CLEFAIRY: 35, + PokemonId.CLEFABLE: 36, + PokemonId.VULPIX: 37, + PokemonId.NINETALES: 38, + PokemonId.JIGGLYPUFF: 39, + PokemonId.WIGGLYTUFF: 40, + PokemonId.ZUBAT: 41, + PokemonId.GOLBAT: 42, + PokemonId.ODDISH: 43, + PokemonId.GLOOM: 44, + PokemonId.VILEPLUME: 45, + PokemonId.PARAS: 46, + PokemonId.PARASECT: 47, + PokemonId.VENONAT: 48, + PokemonId.VENOMOTH: 49, + PokemonId.DIGLETT: 50, + PokemonId.DUGTRIO: 51, + PokemonId.MEOWTH: 52, + PokemonId.PERSIAN: 53, + PokemonId.PSYDUCK: 54, + PokemonId.GOLDUCK: 55, + PokemonId.MANKEY: 56, + PokemonId.PRIMEAPE: 57, + PokemonId.GROWLITHE: 58, + PokemonId.ARCANINE: 59, + PokemonId.POLIWAG: 60, + PokemonId.POLIWHIRL: 61, + PokemonId.POLIWRATH: 62, + PokemonId.ABRA: 63, + PokemonId.KADABRA: 64, + PokemonId.ALAKAZAM: 65, + PokemonId.MACHOP: 66, + PokemonId.MACHOKE: 67, + PokemonId.MACHAMP: 68, + PokemonId.BELLSPROUT: 69, + PokemonId.WEEPINBELL: 70, + PokemonId.VICTREEBEL: 71, + PokemonId.TENTACOOL: 72, + PokemonId.TENTACRUEL: 73, + PokemonId.GEODUDE: 74, + PokemonId.GRAVELER: 75, + PokemonId.GOLEM: 76, + PokemonId.PONYTA: 77, + PokemonId.RAPIDASH: 78, + PokemonId.SLOWPOKE: 79, + PokemonId.SLOWBRO: 80, + PokemonId.MAGNEMITE: 81, + PokemonId.MAGNETON: 82, + PokemonId.FARFETCHD: 83, + PokemonId.DODUO: 84, + PokemonId.DODRIO: 85, + PokemonId.SEEL: 86, + PokemonId.DEWGONG: 87, + PokemonId.GRIMER: 88, + PokemonId.MUK: 89, + PokemonId.SHELLDER: 90, + PokemonId.CLOYSTER: 91, + PokemonId.GASTLY: 92, + PokemonId.HAUNTER: 93, + PokemonId.GENGAR: 94, + PokemonId.ONIX: 95, + PokemonId.DROWZEE: 96, + PokemonId.HYPNO: 97, + PokemonId.KRABBY: 98, + PokemonId.KINGLER: 99, + PokemonId.VOLTORB: 100, + PokemonId.ELECTRODE: 101, + PokemonId.EXEGGCUTE: 102, + PokemonId.EXEGGUTOR: 103, + PokemonId.CUBONE: 104, + PokemonId.MAROWAK: 105, + PokemonId.HITMONLEE: 106, + PokemonId.HITMONCHAN: 107, + PokemonId.LICKITUNG: 108, + PokemonId.KOFFING: 109, + PokemonId.WEEZING: 110, + PokemonId.RHYHORN: 111, + PokemonId.RHYDON: 112, + PokemonId.CHANSEY: 113, + PokemonId.TANGELA: 114, + PokemonId.KANGASKHAN: 115, + PokemonId.HORSEA: 116, + PokemonId.SEADRA: 117, + PokemonId.GOLDEEN: 118, + PokemonId.SEAKING: 119, + PokemonId.STARYU: 120, + PokemonId.STARMIE: 121, + PokemonId.MR_MIME: 122, + PokemonId.SCYTHER: 123, + PokemonId.JYNX: 124, + PokemonId.ELECTABUZZ: 125, + PokemonId.MAGMAR: 126, + PokemonId.PINSIR: 127, + PokemonId.TAUROS: 128, + PokemonId.MAGIKARP: 129, + PokemonId.GYARADOS: 130, + PokemonId.LAPRAS: 131, + PokemonId.DITTO: 132, + PokemonId.EEVEE: 133, + PokemonId.VAPOREON: 134, + PokemonId.JOLTEON: 135, + PokemonId.FLAREON: 136, + PokemonId.PORYGON: 137, + PokemonId.OMANYTE: 138, + PokemonId.OMASTAR: 139, + PokemonId.KABUTO: 140, + PokemonId.KABUTOPS: 141, + PokemonId.AERODACTYL: 142, + PokemonId.SNORLAX: 143, + PokemonId.ARTICUNO: 144, + PokemonId.ZAPDOS: 145, + PokemonId.MOLTRES: 146, + PokemonId.DRATINI: 147, + PokemonId.DRAGONAIR: 148, + PokemonId.DRAGONITE: 149, + PokemonId.MEWTWO: 150, + PokemonId.MEW: 151 +} + +_POKEMON_NAMES = { + PokemonId.BULBASAUR: "Bulbasaur", + PokemonId.IVYSAUR: "Ivysaur", + PokemonId.VENUSAUR: "Venusaur", + PokemonId.CHARMANDER: "Charmander", + PokemonId.CHARMELEON: "Charmeleon", + PokemonId.CHARIZARD: "Charizard", + PokemonId.SQUIRTLE: "Squirtle", + PokemonId.WARTORTLE: "Wartortle", + PokemonId.BLASTOISE: "Blastoise", + PokemonId.CATERPIE: "Caterpie", + PokemonId.METAPOD: "Metapod", + PokemonId.BUTTERFREE: "Butterfree", + PokemonId.CATERPIE: "Weedle", + PokemonId.KAKUNA: "Kakuna", + PokemonId.BEEDRILL: "Beedrill", + PokemonId.PIDGEY: "Pidgey", + PokemonId.PIDGEOTTO: "Pidgeotto", + PokemonId.PIDGEOT: "Pidgeot", + PokemonId.RATTATA: "Rattata", + PokemonId.RATICATE: "Raticate", + PokemonId.SPEAROW: "Spearow", + PokemonId.FEAROW: "Fearow", + PokemonId.EKANS: "Ekans", + PokemonId.ARBOK: "Arbok", + PokemonId.PIKACHU: "Pikachu", + PokemonId.RAICHU: "Raichu", + PokemonId.SANDSHREW: "Sandshrew", + PokemonId.SANDSLASH: "Sandslash", + PokemonId.NIDORAN_F: "Nidoran♀", + PokemonId.NIDORINA: "Nidorina", + PokemonId.NIDOQUEEN: "Nidoqueen", + PokemonId.NIDORAN_M: "Nidoran♂", + PokemonId.NIDORINO: "Nidorino", + PokemonId.NIDOKING: "Nidoking", + PokemonId.CLEFAIRY: "Clefairy", + PokemonId.CLEFABLE: "Clefable", + PokemonId.VULPIX: "Vulpix", + PokemonId.NINETALES: "Ninetales", + PokemonId.JIGGLYPUFF: "Jigglypuff", + PokemonId.WIGGLYTUFF: "Wigglytuff", + PokemonId.ZUBAT: "Zubat", + PokemonId.GOLBAT: "Golbat", + PokemonId.ODDISH: "Oddish", + PokemonId.GLOOM: "Gloom", + PokemonId.VILEPLUME: "Vileplume", + PokemonId.PARAS: "Paras", + PokemonId.PARASECT: "Parasect", + PokemonId.VENONAT: "Venonat", + PokemonId.VENOMOTH: "Venomoth", + PokemonId.DIGLETT: "Diglett", + PokemonId.DUGTRIO: "Dugtrio", + PokemonId.MEOWTH: "Meowth", + PokemonId.PERSIAN: "Persian", + PokemonId.PSYDUCK: "Psyduck", + PokemonId.GOLDUCK: "Golduck", + PokemonId.MANKEY: "Mankey", + PokemonId.PRIMEAPE: "Primeape", + PokemonId.GROWLITHE: "Growlithe", + PokemonId.ARCANINE: "Arcanine", + PokemonId.POLIWAG: "Poliwag", + PokemonId.POLIWHIRL: "Poliwhirl", + PokemonId.POLIWRATH: "Poliwrath", + PokemonId.ABRA: "Abra", + PokemonId.KADABRA: "Kadabra", + PokemonId.ALAKAZAM: "Alakazam", + PokemonId.MACHOP: "Machop", + PokemonId.MACHOKE: "Machoke", + PokemonId.MACHAMP: "Machamp", + PokemonId.BELLSPROUT: "Bellsprout", + PokemonId.WEEPINBELL: "Weepinbell", + PokemonId.VICTREEBEL: "Victreebel", + PokemonId.TENTACOOL: "Tentacool", + PokemonId.TENTACRUEL: "Tentacruel", + PokemonId.GEODUDE: "Geodude", + PokemonId.GRAVELER: "Graveler", + PokemonId.GOLEM: "Golem", + PokemonId.PONYTA: "Ponyta", + PokemonId.RAPIDASH: "Rapidash", + PokemonId.SLOWPOKE: "Slowpoke", + PokemonId.SLOWBRO: "Slowbro", + PokemonId.MAGNEMITE: "Magnemite", + PokemonId.MAGNETON: "Magneton", + PokemonId.FARFETCHD: "Farfetch’d", + PokemonId.DODUO: "Doduo", + PokemonId.DODRIO: "Dodrio", + PokemonId.SEEL: "Seel", + PokemonId.DEWGONG: "Dewgong", + PokemonId.GRIMER: "Grimer", + PokemonId.MUK: "Muk", + PokemonId.SHELLDER: "Shellder", + PokemonId.CLOYSTER: "Cloyster", + PokemonId.GASTLY: "Gastly", + PokemonId.HAUNTER: "Haunter", + PokemonId.GENGAR: "Gengar", + PokemonId.ONIX: "Onix", + PokemonId.DROWZEE: "Drowzee", + PokemonId.HYPNO: "Hypno", + PokemonId.KRABBY: "Krabby", + PokemonId.KINGLER: "Kingler", + PokemonId.VOLTORB: "Voltorb", + PokemonId.ELECTRODE: "Electrode", + PokemonId.EXEGGCUTE: "Exeggcute", + PokemonId.EXEGGUTOR: "Exeggutor", + PokemonId.CUBONE: "Cubone", + PokemonId.MAROWAK: "Marowak", + PokemonId.HITMONLEE: "Hitmonlee", + PokemonId.HITMONCHAN: "Hitmonchan", + PokemonId.LICKITUNG: "Lickitung", + PokemonId.KOFFING: "Koffing", + PokemonId.WEEZING: "Weezing", + PokemonId.RHYHORN: "Rhyhorn", + PokemonId.RHYDON: "Rhydon", + PokemonId.CHANSEY: "Chansey", + PokemonId.TANGELA: "Tangela", + PokemonId.KANGASKHAN: "Kangaskhan", + PokemonId.HORSEA: "Horsea", + PokemonId.SEADRA: "Seadra", + PokemonId.GOLDEEN: "Goldeen", + PokemonId.SEAKING: "Seaking", + PokemonId.STARYU: "Staryu", + PokemonId.STARMIE: "Starmie", + PokemonId.MR_MIME: "Mr. Mime", + PokemonId.SCYTHER: "Scyther", + PokemonId.JYNX: "Jynx", + PokemonId.ELECTABUZZ: "Electabuzz", + PokemonId.MAGMAR: "Magmar", + PokemonId.PINSIR: "Pinsir", + PokemonId.TAUROS: "Tauros", + PokemonId.MAGIKARP: "Magikarp", + PokemonId.GYARADOS: "Gyarados", + PokemonId.LAPRAS: "Lapras", + PokemonId.DITTO: "Ditto", + PokemonId.EEVEE: "Eevee", + PokemonId.VAPOREON: "Vaporeon", + PokemonId.JOLTEON: "Jolteon", + PokemonId.FLAREON: "Flareon", + PokemonId.PORYGON: "Porygon", + PokemonId.OMANYTE: "Omanyte", + PokemonId.OMASTAR: "Omastar", + PokemonId.KABUTO: "Kabuto", + PokemonId.KABUTOPS: "Kabutops", + PokemonId.AERODACTYL: "Aerodactyl", + PokemonId.SNORLAX: "Snorlax", + PokemonId.ARTICUNO: "Articuno", + PokemonId.ZAPDOS: "Zapdos", + PokemonId.MOLTRES: "Moltres", + PokemonId.DRATINI: "Dratini", + PokemonId.DRAGONAIR: "Dragonair", + PokemonId.DRAGONITE: "Dragonite", + PokemonId.MEWTWO: "Mewtwo", + PokemonId.MEW: "Mew", +} \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/data/constants/sprites.py b/pyboy/plugins/game_wrapper_pokemon_gen1/data/constants/sprites.py new file mode 100644 index 000000000..590648de3 --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/data/constants/sprites.py @@ -0,0 +1,75 @@ +from enum import Enum + +class Sprite(Enum): + RED = 0x01 + BLUE = 0x02 + OAK = 0x03 + BUG_CATCHER = 0x04 + SLOWBRO = 0x05 + LASS = 0x06 + BLACK_HAIR_BOY_1 = 0x07 + LITTLE_GIRL = 0x08 + BIRD = 0x09 + FAT_BALD_GUY = 0x0a + GAMBLER = 0x0b + BLACK_HAIR_BOY_2 = 0x0c + GIRL = 0x0d + HIKER = 0x0e + FOULARD_WOMAN = 0x0f + GENTLEMAN = 0x10 + DAISY = 0x11 + BIKER = 0x12 + SAILOR = 0x13 + COOK = 0x14 + BIKE_SHOP_GUY = 0x15 + MR_FUJI = 0x16 + GIOVANNI = 0x17 + ROCKET = 0x18 + MEDIUM = 0x19 + WAITER = 0x1a + ERIKA = 0x1b + MOM_GEISHA = 0x1c + BRUNETTE_GIRL = 0x1d + LANCE = 0x1e + OAK_SCIENTIST_AIDE = 0x1f + OAK_AIDE = 0x20 + ROCKER = 0x21 + SWIMMER = 0x22 + WHITE_PLAYER = 0x23 + GYM_HELPER = 0x24 + OLD_PERSON = 0x25 + MART_GUY = 0x26 + FISHER = 0x27 + OLD_MEDIUM_WOMAN = 0x28 + NURSE = 0x29 + CABLE_CLUB_WOMAN = 0x2a + MR_MASTERBALL = 0x2b + LAPRAS_GIVER = 0x2c + WARDEN = 0x2d + SS_CAPTAIN = 0x2e + FISHER2 = 0x2f + BLACKBELT = 0x30 + GUARD = 0x31 + COP_GUARD = 0x32 + MOM = 0x33 + BALDING_GUY = 0x34 + YOUNG_BOY = 0x35 + GAMEBOY_KID = 0x36 + GAMEBOY_KID_COPY = 0x37 + CLEFAIRY = 0x38 + AGATHA = 0x39 + BRUNO = 0x3a + LORELEI = 0x3b + SEEL = 0x3c + BALL = 0x3d + OMANYTE = 0x3e + BOULDER = 0x3f + PAPER_SHEET = 0x40 + BOOK_MAP_DEX = 0x41 + CLIPBOARD = 0x42 + SNORLAX = 0x43 + OLD_AMBER_COPY = 0x44 + OLD_AMBER = 0x45 + LYING_OLD_MAN_UNUSED_1 = 0x46 + LYING_OLD_MAN_UNUSED_2 = 0x47 + LYING_OLD_MAN = 0x48 \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/data/constants/status.py b/pyboy/plugins/game_wrapper_pokemon_gen1/data/constants/status.py new file mode 100644 index 000000000..7055afe90 --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/data/constants/status.py @@ -0,0 +1,8 @@ +from enum import Enum + +class Status(Enum): + ASLEEP = 4 + POISONED = 8 + BURNED = 16 + FROZEN = 32 + PARALYZED = 64 \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/data/memory_addrs/base.py b/pyboy/plugins/game_wrapper_pokemon_gen1/data/memory_addrs/base.py new file mode 100644 index 000000000..cf6a0a0fb --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/data/memory_addrs/base.py @@ -0,0 +1,58 @@ +from enum import Enum + +class MemoryAddressType(Enum): + HEX = 0 + ADDRESS = 1 + BCD = 2 + BITFIELD = 3 + TEXT = 4 + +class MemoryAddress(): + + def __init__(self, address, num_bytes, memory_type): + self._address = address + self._num_bytes = num_bytes + self._memory_type = memory_type + + @property + def address(self): + return self._address + + @property + def num_bytes(self): + return self._num_bytes + + @property + def memory_type(self): + return self._memory_type + + def __str__(self): + return f"Memory address: {self._address}: Length: {self._num_bytes}, memory_addr_type: {self._memory_type}" + + def add_offset(self, offset): + return MemoryAddress(self.address+offset, self.num_bytes, self.memory_type) + +class HexMemoryAddress(MemoryAddress): + + def __init__(self, address, num_bytes): + super().__init__(address, num_bytes, MemoryAddressType.HEX) + +class AddressMemoryAddress(MemoryAddress): + + def __init__(self, address, num_bytes): + super().__init__(address, num_bytes, MemoryAddressType.ADDRESS) + +class BCDMemoryAddress(MemoryAddress): + + def __init__(self, address, num_bytes): + super().__init__(address, num_bytes, MemoryAddressType.BCD) + +class BitfieldMemoryAddress(MemoryAddress): + + def __init__(self, address, num_bytes): + super().__init__(address, num_bytes, MemoryAddressType.BITFIELD) + +class TextMemoryAddress(MemoryAddress): + + def __init__(self, address, num_bytes): + super().__init__(address, num_bytes, MemoryAddressType.TEXT) \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/data/memory_addrs/battle.py b/pyboy/plugins/game_wrapper_pokemon_gen1/data/memory_addrs/battle.py new file mode 100644 index 000000000..650b1e6e5 --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/data/memory_addrs/battle.py @@ -0,0 +1,97 @@ +from enum import Enum, auto +from .base import MemoryAddress, MemoryAddressType + +class BattleAddress(Enum): + ENEMY_MOVE_ID = auto() + ENEMY_MOVE_EFFECT = auto() + ENEMY_MOVE_POWER = auto() + ENEMY_MOVE_TYPE = auto() + ENEMY_MOVE_ACCURACY = auto() + ENEMY_MOVE_MAX_PP = auto() + PLAYER_MOVE_ID = auto() + PLAYER_MOVE_EFFECT = auto() + PLAYER_MOVE_POWER = auto() + PLAYER_MOVE_TYPE = auto() + PLAYER_MOVE_ACCURACY = auto() + PLAYER_MOVE_MAX_PP = auto() + ENEMY_POKEMON_ID = auto() + PLAYER_POKEMON_ID = auto() + ENEMY_NAME = auto() + # This seems to be a repat? + # ENEMY_POKEMON_ID = 16 + ENEMY_HP = auto() + ENEMY_LEVEL = auto() + ENEMY_STATUS = auto() + ENEMY_TYPE_1 = auto() + ENEMY_TYPE_2 = auto() + # ENEMY_CATCH_RATE_UNUSED only checked by move Transform + ENEMY_CATCH_RATE_UNUSED = auto() + ENEMY_MOVE_1 = auto() + ENEMY_MOVE_2 = auto() + ENEMY_MOVE_3 = auto() + ENEMY_MOVE_4 = auto() + ENEMY_ATT_DEF_DVS = auto() + ENEMY_SPEED_SPEC_DVS = auto() + # Another repeat? + # ENEMY_LEVEL = (0xCFEC) + ENEMY_MAX_HP = auto() + ENEMY_ATTACK = auto() + ENEMY_DEFENSE = auto() + ENEMY_SPEED = auto() + ENEMY_SPECIAL = auto() + ENEMY_MOVE_PP_1 = auto() + ENEMY_MOVE_PP_2 = auto() + ENEMY_MOVE_PP_3 = auto() + EMENY_MOVE_PP_4 = auto() + ENEMY_BASE_STATS = auto() + ENEMY_CATCH_RATE = auto() + ENEMY_BASE_EXP = auto() + TYPE_OF_BATTLE = auto() + +BATTLE_ADDRESS_LOOKUP = { + BattleAddress.ENEMY_MOVE_ID: MemoryAddress(0xCFCC, 1, MemoryAddressType.HEX), + BattleAddress.ENEMY_MOVE_EFFECT: MemoryAddress(0xCFCD, 1, MemoryAddressType.HEX), + BattleAddress.ENEMY_MOVE_POWER: MemoryAddress(0xCFCE, 1, MemoryAddressType.HEX), + BattleAddress.ENEMY_MOVE_TYPE: MemoryAddress(0xCFCF, 1, MemoryAddressType.HEX), + BattleAddress.ENEMY_MOVE_ACCURACY: MemoryAddress(0xCFD0, 1, MemoryAddressType.HEX), + BattleAddress.ENEMY_MOVE_MAX_PP: MemoryAddress(0xCFD1, 1, MemoryAddressType.HEX), + BattleAddress.PLAYER_MOVE_ID: MemoryAddress(0xCFD2, 1, MemoryAddressType.HEX), + BattleAddress.PLAYER_MOVE_EFFECT: MemoryAddress(0xCFD3, 1, MemoryAddressType.HEX), + BattleAddress.PLAYER_MOVE_POWER: MemoryAddress(0xCFD4, 1, MemoryAddressType.HEX), + BattleAddress.PLAYER_MOVE_TYPE: MemoryAddress(0xCFD5, 1, MemoryAddressType.HEX), + BattleAddress.PLAYER_MOVE_ACCURACY: MemoryAddress(0xCFD6, 1, MemoryAddressType.HEX), + BattleAddress.PLAYER_MOVE_MAX_PP: MemoryAddress(0xCFD7, 1, MemoryAddressType.HEX), + BattleAddress.ENEMY_POKEMON_ID: MemoryAddress(0xCFD8, 1, MemoryAddressType.HEX), + BattleAddress.PLAYER_POKEMON_ID: MemoryAddress(0xCFD9, 1, MemoryAddressType.HEX), + BattleAddress.ENEMY_NAME: MemoryAddress(0xCFDA, 10, MemoryAddressType.TEXT), + # This seems to be a repat? + # ENEMY_POKEMON_ID = (0xCFE5, 1, MemoryAddressType.HEX), + BattleAddress.ENEMY_HP: MemoryAddress(0xCFE6, 2, MemoryAddressType.HEX), + BattleAddress.ENEMY_LEVEL: MemoryAddress(0xCFE8, 1, MemoryAddressType.HEX), + BattleAddress.ENEMY_STATUS: MemoryAddress(0xCFE9, 1, MemoryAddressType.HEX), + BattleAddress.ENEMY_TYPE_1: MemoryAddress(0xCFEA, 1, MemoryAddressType.HEX), + BattleAddress.ENEMY_TYPE_2: MemoryAddress(0xCFEB, 1, MemoryAddressType.HEX), + # ENEMY_CATCH_RATE_UNUSED only checked by move Transform + BattleAddress.ENEMY_CATCH_RATE_UNUSED: MemoryAddress(0xCFEC, 1, MemoryAddressType.HEX), + BattleAddress.ENEMY_MOVE_1: MemoryAddress(0xCFED, 1, MemoryAddressType.HEX), + BattleAddress.ENEMY_MOVE_2: MemoryAddress(0xCFEE, 1, MemoryAddressType.HEX), + BattleAddress.ENEMY_MOVE_3: MemoryAddress(0xCFEF, 1, MemoryAddressType.HEX), + BattleAddress.ENEMY_MOVE_4: MemoryAddress(0xCFF0, 1, MemoryAddressType.HEX), + BattleAddress.ENEMY_ATT_DEF_DVS: MemoryAddress(0xCFF1, 1, MemoryAddressType.HEX), + BattleAddress.ENEMY_SPEED_SPEC_DVS: MemoryAddress(0xCFF2, 1, MemoryAddressType.HEX), + # Another repeat? + # ENEMY_LEVEL = MemoryAddress(0xCFEC, 1) + BattleAddress.ENEMY_MAX_HP: MemoryAddress(0xCFF4, 2, MemoryAddressType.HEX), + BattleAddress.ENEMY_ATTACK: MemoryAddress(0xCFF6, 2, MemoryAddressType.HEX), + BattleAddress.ENEMY_DEFENSE: MemoryAddress(0xCFF8, 2, MemoryAddressType.HEX), + BattleAddress.ENEMY_SPEED: MemoryAddress(0xCFFA, 2, MemoryAddressType.HEX), + BattleAddress.ENEMY_SPECIAL: MemoryAddress(0xCFFC, 2, MemoryAddressType.HEX), + BattleAddress.ENEMY_MOVE_PP_1: MemoryAddress(0xCFFF, 1, MemoryAddressType.HEX), + BattleAddress.ENEMY_MOVE_PP_2: MemoryAddress(0xCFFF, 1, MemoryAddressType.HEX), + BattleAddress.ENEMY_MOVE_PP_3: MemoryAddress(0xD000, 1, MemoryAddressType.HEX), + BattleAddress.EMENY_MOVE_PP_4: MemoryAddress(0xD001, 1, MemoryAddressType.HEX), + BattleAddress.ENEMY_BASE_STATS: MemoryAddress(0xD002, 5, MemoryAddressType.HEX), + BattleAddress.ENEMY_CATCH_RATE: MemoryAddress(0xD007, 1, MemoryAddressType.HEX), + BattleAddress.ENEMY_BASE_EXP: MemoryAddress(0xD008, 1, MemoryAddressType.HEX), + BattleAddress.TYPE_OF_BATTLE: MemoryAddress(0xD057, 1, MemoryAddressType.HEX), +} \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/data/memory_addrs/game_state.py b/pyboy/plugins/game_wrapper_pokemon_gen1/data/memory_addrs/game_state.py new file mode 100644 index 000000000..d549023c7 --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/data/memory_addrs/game_state.py @@ -0,0 +1,5 @@ +from enum import Enum +from .base import MemoryAddress, MemoryAddressType + +class GameStateAddress(Enum): + BATTLE_TYPE = (0xD057, 1, MemoryAddressType.HEX) \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/data/memory_addrs/misc.py b/pyboy/plugins/game_wrapper_pokemon_gen1/data/memory_addrs/misc.py new file mode 100644 index 000000000..a71a58c7f --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/data/memory_addrs/misc.py @@ -0,0 +1 @@ +MONEY_ADDR = (0xD347, 3) \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/data/memory_addrs/player.py b/pyboy/plugins/game_wrapper_pokemon_gen1/data/memory_addrs/player.py new file mode 100644 index 000000000..86b490e09 --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/data/memory_addrs/player.py @@ -0,0 +1,16 @@ +from enum import Enum, auto +from .base import HexMemoryAddress, BCDMemoryAddress, TextMemoryAddress + +class PlayerAddress(Enum): + NAME = auto() + NUM_POKEMON_IN_PARTY = auto() + BADGES = auto() + MONEY = auto() + +PLAYER_ADDRESS_LOOKUP = { + PlayerAddress.NAME: TextMemoryAddress(0xD158, 10), + PlayerAddress.NUM_POKEMON_IN_PARTY: HexMemoryAddress(0xD163, 1), + PlayerAddress.BADGES: HexMemoryAddress(0xD356, 1), + PlayerAddress.MONEY: BCDMemoryAddress(0xD347, 3) + +} \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/data/memory_addrs/pokemon.py b/pyboy/plugins/game_wrapper_pokemon_gen1/data/memory_addrs/pokemon.py new file mode 100644 index 000000000..cc4bb1609 --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/data/memory_addrs/pokemon.py @@ -0,0 +1,78 @@ +# Pokemon locations in memory +from enum import Enum, auto +from .base import HexMemoryAddress + +class PokemonBaseAddress(Enum): + POKEMON_1 = 0xD16B + POKEMON_2 = 0xD197 + POKEMON_3 = 0xD1C3 + POKEMON_4 = 0xD1EF + POKEMON_5 = 0xD21B + POKEMON_6 = 0xD247 + +# Offsets for Pokemon data access +# Structure is (offset, num bytes) +class PokemonAddress(Enum): + ID = auto() + CURRENT_HP = auto() + BOX_LEVEL = auto() + STATUS = auto() + TYPE_1 = auto() + TYPE_2 = auto() + CATCH_RATE = auto() + MOVE_1 = auto() + MOVE_2 = auto() + MOVE_3 = auto() + MOVE_4 = auto() + TRAINER_ID = auto() + EXPERIENCE = auto() + HP_EV = auto() + ATTACK_EV = auto() + DEFENSE_EV = auto() + SPEED_EV = auto() + SPECIAL_EV = auto() + ATTACK_DEFENSE_IV = auto() + SPEED_SPECIAL_IV = auto() + PP_MOVE_1 = auto() + PP_MOVE_2 = auto() + PP_MOVE_3 = auto() + PP_MOVE_4 = auto() + LEVEL = auto() + MAX_HP = auto() + ATTACK = auto() + DEFENSE = auto() + SPEED = auto() + SPECIAL = auto() + +POKEMON_ADDRESS_LOOKUP = { + PokemonAddress.ID: HexMemoryAddress(0x0, 1), + PokemonAddress.CURRENT_HP: HexMemoryAddress(0x1, 2), + PokemonAddress.BOX_LEVEL: HexMemoryAddress(0x3, 1), + PokemonAddress.STATUS: HexMemoryAddress(0x4, 1), + PokemonAddress.TYPE_1: HexMemoryAddress(0x5, 1), + PokemonAddress.TYPE_2: HexMemoryAddress(0x6, 1), + PokemonAddress.CATCH_RATE: HexMemoryAddress(0x7, 1), + PokemonAddress.MOVE_1: HexMemoryAddress(0x8, 1), + PokemonAddress.MOVE_2: HexMemoryAddress(0x9, 1), + PokemonAddress.MOVE_3: HexMemoryAddress(0xA, 1), + PokemonAddress.MOVE_4: HexMemoryAddress(0xB, 1), + PokemonAddress.TRAINER_ID: HexMemoryAddress(0xC, 2), + PokemonAddress.EXPERIENCE: HexMemoryAddress(0xE, 3), + PokemonAddress.HP_EV: HexMemoryAddress(0x11, 2), + PokemonAddress.ATTACK_EV: HexMemoryAddress(0x13, 2), + PokemonAddress.DEFENSE_EV: HexMemoryAddress(0x15, 2), + PokemonAddress.SPEED_EV: HexMemoryAddress(0x17, 2), + PokemonAddress.SPECIAL_EV: HexMemoryAddress(0x19, 2), + PokemonAddress.ATTACK_DEFENSE_IV: HexMemoryAddress(0x1B, 1), + PokemonAddress.SPEED_SPECIAL_IV: HexMemoryAddress(0x1C, 1), + PokemonAddress.PP_MOVE_1: HexMemoryAddress(0x1D, 1), + PokemonAddress.PP_MOVE_2: HexMemoryAddress(0x1E, 1), + PokemonAddress.PP_MOVE_3: HexMemoryAddress(0x1F, 1), + PokemonAddress.PP_MOVE_4: HexMemoryAddress(0x20, 1), + PokemonAddress.LEVEL: HexMemoryAddress(0x21, 1), + PokemonAddress.MAX_HP: HexMemoryAddress(0x22, 2), + PokemonAddress.ATTACK: HexMemoryAddress(0x24, 2), + PokemonAddress.DEFENSE: HexMemoryAddress(0x26, 2), + PokemonAddress.SPEED: HexMemoryAddress(0x28, 2), + PokemonAddress.SPECIAL: HexMemoryAddress(0x2A, 2), +} \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1/data/memory_addrs/sprites.py b/pyboy/plugins/game_wrapper_pokemon_gen1/data/memory_addrs/sprites.py new file mode 100644 index 000000000..d9e9ab0f0 --- /dev/null +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/data/memory_addrs/sprites.py @@ -0,0 +1,38 @@ +''' +Memory addresses and descriptions taken from +https://datacrystal.romhacking.net/wiki/Pok%C3%A9mon_Red/Blue:RAM_map +''' + +from enum import Enum + +class SpriteBaseAddress(Enum): + PLAYER_SPRITE = 0xC100 # Player is ALWAYS sprite 0 + SPRITE_1 = 0xC110 + SPIRITE_2 = 0xC120 + SPRITE_3 = 0xC130 + SPRITE_4 = 0xC140 + SPRITE_5 = 0xC150 + SPRITE_6 = 0xC160 + SPRITE_7 = 0xC170 + SPRITE_8 = 0xC180 + SPRITE_9 = 0xC190 + SPRITE_10 = 0xC1A0 + SPRITE_11 = 0xC1B0 + SPRITE_12 = 0xC1C0 + SPRITE_13 = 0xC1D0 + SPRITE_14 = 0xC1E0 + SPRITE_15 = 0xC1F0 + +class SpriteMemoryOffset(Enum): + PICTURE_ID = 0x0 + MOVEMENT_STATUS = 0x1 # (0: uninitialized, 1: ready, 2: delayed, 3: moving) + # TODO: Figure out exactly what this data looks like + IMAGE_INDEX = 0x2 # (changed on update, $ff if off screen, includes facing direction, progress in walking animation and a sprite-specific offset) + Y_SCREEN_POS_D = 0x3 # Y screen position delta (-1,0 or 1; added to c1x4 on each walking animation update) + Y_SCREEN_POS = 0x4 # Y screen position (in pixels, always 4 pixels above grid which makes sprites appear to be in the center of a tile) + X_SCREEN_POS_D = 0x5 # X screen position delta (-1,0 or 1; added to c1x6 on each walking animation update) + X_SCREEN_POS = 0x6 # X screen position (in pixels, snaps to grid if not currently walking) + INTRA_ANIMATION_FRAME_COUNTER = 0x7 # intra-animation-frame counter (counting upwards to 4 until c1x8 is incremented) + ANIMATION_FRAME_COUNTER = 0x8 # animation frame counter (increased every 4 updates, hold four states (totalling to 16 walking frames) + FACING_DIRECTION = 0x9 # facing direction (0: down, 4: up, 8: left, $c: right) + diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1.pxd b/pyboy/plugins/game_wrapper_pokemon_gen1/game_wrapper_pokemon_gen1.pxd similarity index 66% rename from pyboy/plugins/game_wrapper_pokemon_gen1.pxd rename to pyboy/plugins/game_wrapper_pokemon_gen1/game_wrapper_pokemon_gen1.pxd index 71568aca4..1557a8255 100644 --- a/pyboy/plugins/game_wrapper_pokemon_gen1.pxd +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/game_wrapper_pokemon_gen1.pxd @@ -2,14 +2,10 @@ # License: See LICENSE.md file # GitHub: https://github.com/Baekalfen/PyBoy # -cimport cython from libc.stdint cimport uint8_t - -from pyboy.logging.logging cimport Logger from pyboy.plugins.base_plugin cimport PyBoyGameWrapper - - -cdef Logger logger +from pyboy.plugins.game_wrapper_pokemon_gen1.core.mem_manager cimport MemoryManager +cimport cython cdef class GameWrapperPokemonGen1(PyBoyGameWrapper): - pass + cdef MemoryManager mem_manager \ No newline at end of file diff --git a/pyboy/plugins/game_wrapper_pokemon_gen1.py b/pyboy/plugins/game_wrapper_pokemon_gen1/game_wrapper_pokemon_gen1.py similarity index 54% rename from pyboy/plugins/game_wrapper_pokemon_gen1.py rename to pyboy/plugins/game_wrapper_pokemon_gen1/game_wrapper_pokemon_gen1.py index 96de505f6..e5e07c322 100644 --- a/pyboy/plugins/game_wrapper_pokemon_gen1.py +++ b/pyboy/plugins/game_wrapper_pokemon_gen1/game_wrapper_pokemon_gen1.py @@ -2,34 +2,40 @@ # License: See LICENSE.md file # GitHub: https://github.com/Baekalfen/PyBoy # + __pdoc__ = { "GameWrapperPokemonGen1.cartridge_title": False, "GameWrapperPokemonGen1.post_tick": False, } import numpy as np - -import pyboy -from pyboy import utils from pyboy.utils import WindowEvent +from pyboy.logger import logger +from ..base_plugin import PyBoyGameWrapper +from .data.memory_addrs.misc import MONEY_ADDR +from .core.pokedex import Pokedex +from .core.pokemon import Pokemon +from .core.player import Player +from .core.mem_manager import MemoryManager +from .core.game_state import GameState -from .base_plugin import PyBoyGameWrapper +PKMN_SIZE = 0x2C +BYTE_ORDER = 'big' -logger = pyboy.logging.get_logger(__name__) class GameWrapperPokemonGen1(PyBoyGameWrapper): """ - This class wraps Pokemon Red/Blue, and provides basic access for AIs. + This class wraps Pokemon Red/Blue. If you call `print` on an instance of this object, it will show an overview of everything this object provides. """ cartridge_title = None - + def __init__(self, *args, **kwargs): self.shape = (20, 18) super().__init__(*args, game_area_section=(0, 0) + self.shape, game_area_wrap_around=True, **kwargs) - self.sprite_offset = 0x1000 + self.mem_manager = MemoryManager(self.pyboy) def enabled(self): return self.pyboy_argv.get("game_wrapper") and ((self.pyboy.cartridge_title() == "POKEMON RED") or @@ -53,7 +59,7 @@ def _get_screen_background_tilemap(self): def _get_screen_walkable_matrix(self): walkable_tiles_indexes = [] - collision_ptr = self.pyboy.get_memory_value(0xD530) + (self.pyboy.get_memory_value(0xD531) << 8) + collision_ptr = self._read_multibyte_value(0xD530, num_bytes=2) tileset_type = self.pyboy.get_memory_value(0xFFD7) if tileset_type > 0: grass_tile_index = self.pyboy.get_memory_value(0xD535) @@ -101,3 +107,61 @@ def __repr__(self): ) ) # yapf: enable + + def get_pokemon_from_party(self, party_index): + return Pokemon.load_pokemon_from_party(self.mem_manager, party_index) + + def get_all_pokemon_from_party(self): + num_pokemon = self.mem_manager.read_hex_from_memory(0xD163, 1) + pokemon_team = [self.get_pokemon_from_party(i+1) for i in range(num_pokemon)] + return pokemon_team + + def get_pokedex(self): + return Pokedex.load_pokedex(self.mem_manager) + + def get_player(self): + return Player.load_player(self.mem_manager) + + def get_player_location(self): + player_sprite_y = self.mem_manager.read_hex_from_memory(0xD361, 1) + player_sprite_x = self.mem_manager.read_hex_from_memory(0xD362, 1) + player_sprite_map_id = self.mem_manager.read_hex_from_memory(0xD35E, 1) + + return (player_sprite_x, player_sprite_y) + + def get_game_state(self): + return GameState.load_game_state(self.mem_manager) + + def get_screen(self): + return self.pyboy.botsupport_manager().screen() + + def get_screen_array(self): + return self.get_screen().screen_ndarray() + + ''' + WARNING + + The following functions are NOT meant to be used consistently. They exist only as + a way to call the heper memory access functions for testing memory fields. They + WILL BE REMOVED. Ideally, some form of the memory access functions will appear in + PyBoy proper, but alternatively and data accesses made with these functions should + be turned into a named function in the game wrapper. If you find yourself using these + functions consistenly, please open up an issue at https://github.com/SnarkAttack/PyBoy + and indicate the memory addresses you are accessing and their use so that a named function + can be created. If you need those memory values, other people might too. + ''' + + def read_memory(self, address, num_bytes, read_type): + if read_type == 'hex': + return self.mem_manager.read_hex_from_memory(address, num_bytes) + elif read_type == 'address': + return self.mem_manager.read_address_from_memory(address, num_bytes) + elif read_type == 'bcd': + return self.mem_manager.read_bcd_from_memory(address, num_bytes) + elif read_type == 'text': + return self.mem_manager.read_text_from_memory(address, num_bytes) + else: + raise ValueError(f"{read_type} is not a valid read type") + + def read_rom_memory(self, address, num_bytes): + return self.mem_manager.read_from_rom(address, num_bytes) \ No newline at end of file diff --git a/pyboy/plugins/manager.pxd b/pyboy/plugins/manager.pxd index a457d47ca..709a49aef 100644 --- a/pyboy/plugins/manager.pxd +++ b/pyboy/plugins/manager.pxd @@ -22,6 +22,7 @@ from pyboy.plugins.screenshot_recorder cimport ScreenshotRecorder from pyboy.plugins.window_dummy cimport WindowDummy from pyboy.plugins.window_headless cimport WindowHeadless from pyboy.plugins.window_open_gl cimport WindowOpenGL +from pyboy.plugins.game_wrapper_pokemon_gen1.game_wrapper_pokemon_gen1 cimport GameWrapperPokemonGen1 # imports from pyboy.plugins.window_sdl2 cimport WindowSDL2 diff --git a/pyboy/plugins/manager.py b/pyboy/plugins/manager.py index ce37b8983..527be0050 100644 --- a/pyboy/plugins/manager.py +++ b/pyboy/plugins/manager.py @@ -18,7 +18,7 @@ from pyboy.plugins.game_wrapper_super_mario_land import GameWrapperSuperMarioLand # isort:skip from pyboy.plugins.game_wrapper_tetris import GameWrapperTetris # isort:skip from pyboy.plugins.game_wrapper_kirby_dream_land import GameWrapperKirbyDreamLand # isort:skip -from pyboy.plugins.game_wrapper_pokemon_gen1 import GameWrapperPokemonGen1 # isort:skip +from pyboy.plugins.game_wrapper_pokemon_gen1.game_wrapper_pokemon_gen1 import GameWrapperPokemonGen1 # isort:skip # imports end from pyboy.plugins.base_plugin import PyBoyGameWrapper diff --git a/pyboy/pyboy.py b/pyboy/pyboy.py index 7735e459a..c61ce1657 100644 --- a/pyboy/pyboy.py +++ b/pyboy/pyboy.py @@ -14,7 +14,7 @@ from pyboy.openai_gym import PyBoyGymEnv from pyboy.openai_gym import enabled as gym_enabled from pyboy.plugins.manager import PluginManager -from pyboy.utils import IntIOWrapper, WindowEvent +from pyboy.utils import IntIOWrapper, WindowEvent, byte_to_bitfield from . import botsupport from .core.mb import Motherboard @@ -323,6 +323,104 @@ def get_memory_value(self, addr): An integer with the value of the memory address """ return self.mb.getitem(addr) + + def read_address_from_memory(self, addr): + """ + Reads two consecutive bytes of the Game Boy's current memory state and returns the values as an address. Meant + to be used on locations that are known to store pointers to other necessary addresses. + + Returns + ------- + int: + An integer with the value of two consecutive bytes in memory + """ + return self.get_memory_value(addr) + self.get_memory_value(addr+1) << 8 + + def read_hex_from_memory(self, addr, num_bytes, byteorder): + """ + Reads consecutive bytes and combines them into a single value. + + Parameters + ---------- + addr : int + Memory address to begin reading from + num_bytes : int + Number of consecutive bytes to read + byte_order: str + Order in which to combine bytes. Options are 'big' and 'little' + + Returns + ------- + int: + An integer value representing the value stored in combined consecutive bytes + """ + bytes = [] + for i in range(num_bytes): + bytes.append(self.get_memory_value(addr + i)) + return int.from_bytes(bytes, byteorder=byteorder) + + def read_bcd_from_memory(self, addr, num_bytes): + """ + Reads consecutive bytes, each as a binary-coded decimal, and returns their combined value. + + Parameters + ---------- + addr : int + Memory address to begin reading from + num_bytes : int + Number of consecutive bytes to read + byte_order: str + Order in which to combine bytes. Options are 'big' and 'little' + + Returns + ------- + int: + An integer value representing the stored binary-coded decimal + """ + byte_str = "" + for i in range(num_bytes): + byte_str += "%x"%self.get_memory_value(addr+i) + return int(byte_str) + + def read_bitfield_from_memory(self, addr, num_bytes): + """ + Reads consecutive bytes, converting each into an array of 0s and 1s representing + bit values and appending them. + + Parameters + ---------- + addr : int + Memory address to begin reading from + num_bytes : int + Number of consecutive bytes to read + + Returns + ------- + [int]: + An array of integers (0 and 1 exclusively), representing whether or not + individual bits are toggled. + """ + bits = [] + for i in range(num_bytes): + bits.extend(byte_to_bitfield(self.get_memory_value(addr+i))) + return bits + + def read_text_from_memory(self, address, num_bytes): + """ + Retrieves a string from a given address. + + Args: + address (int): Address from where to retrieve text from. + cap (int): Maximum expected length of string (default: 16). + """ + text = '' + for i in range(num_bytes): + value = self.get_memory_value(address + i) + if value == 80: + break + text += chr(value - 64) + return text + def set_memory_value(self, addr, value): """ diff --git a/pyboy/utils.py b/pyboy/utils.py index b2348ac4c..52240f39e 100644 --- a/pyboy/utils.py +++ b/pyboy/utils.py @@ -139,6 +139,11 @@ def flatten_list(l): flat_list.append(item) return flat_list +def byte_to_bitfield(n): + # Force bit extension to 10 chars; 2 for '0x' and 8 for the bits + bitlist = [1 if digit=='1' else 0 for digit in format(n, '#010b')[2:]] + + return bitlist ############################################################## # Window Events