diff --git a/.gitignore b/.gitignore
index 8a7246210f..925a4bd0c7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,6 +13,9 @@
*.z64
*.n64
*.nes
+*.gb
+*.gbc
+*.gba
*.wixobj
*.lck
*.db3
diff --git a/BaseClasses.py b/BaseClasses.py
index d32749f5f1..d91be28c77 100644
--- a/BaseClasses.py
+++ b/BaseClasses.py
@@ -689,14 +689,14 @@ class CollectionState():
def sweep_for_events(self, key_only: bool = False, locations: Optional[Iterable[Location]] = None) -> None:
if locations is None:
locations = self.world.get_filled_locations()
- new_locations = True
+ reachable_events = True
# since the loop has a good chance to run more than once, only filter the events once
locations = {location for location in locations if location.event and
not key_only or getattr(location.item, "locked_dungeon_item", False)}
- while new_locations:
+ while reachable_events:
reachable_events = {location for location in locations if location.can_reach(self)}
- new_locations = reachable_events - self.events
- for event in new_locations:
+ locations -= reachable_events
+ for event in reachable_events:
self.events.add(event)
assert isinstance(event.item, Item), "tried to collect Event with no Item"
self.collect(event.item, True, event)
diff --git a/FactorioClient.py b/FactorioClient.py
index 6797578a3a..1efca05d3c 100644
--- a/FactorioClient.py
+++ b/FactorioClient.py
@@ -211,6 +211,8 @@ async def game_watcher(ctx: FactorioContext):
def stream_factorio_output(pipe, queue, process):
+ pipe.reconfigure(errors="replace")
+
def queuer():
while process.poll() is None:
text = pipe.readline().strip()
diff --git a/Fill.py b/Fill.py
index 4b095eb108..41a096769f 100644
--- a/Fill.py
+++ b/Fill.py
@@ -233,9 +233,12 @@ def accessibility_corrections(world: MultiWorld, state: CollectionState, locatio
def inaccessible_location_rules(world: MultiWorld, state: CollectionState, locations):
maximum_exploration_state = sweep_from_pool(state, [])
unreachable_locations = [location for location in locations if not location.can_reach(maximum_exploration_state)]
- for location in unreachable_locations:
- add_item_rule(location, lambda item: not ((item.classification & 0b0011) and
- world.accessibility[item.player] != 'minimal'))
+ if unreachable_locations:
+ def forbid_important_item_rule(item: Item):
+ return not ((item.classification & 0b0011) and world.accessibility[item.player] != 'minimal')
+
+ for location in unreachable_locations:
+ add_item_rule(location, forbid_important_item_rule)
def distribute_items_restrictive(world: MultiWorld) -> None:
diff --git a/Launcher.py b/Launcher.py
index 9f9aaa4fb2..c24e0c819d 100644
--- a/Launcher.py
+++ b/Launcher.py
@@ -145,6 +145,8 @@ components: Iterable[Component] = (
Component('OoT Adjuster', 'OoTAdjuster'),
# FF1
Component('FF1 Client', 'FF1Client'),
+ # Pokémon
+ Component('Pokemon Client', 'PokemonClient', file_identifier=SuffixIdentifier('.apred', '.apblue')),
# ChecksFinder
Component('ChecksFinder Client', 'ChecksFinderClient'),
# Starcraft 2
diff --git a/Main.py b/Main.py
index bbd0c805df..b26dcf7986 100644
--- a/Main.py
+++ b/Main.py
@@ -82,7 +82,7 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
longest_name = max(len(text) for text in AutoWorld.AutoWorldRegister.world_types)
numlength = 8
for name, cls in AutoWorld.AutoWorldRegister.world_types.items():
- if not cls.hidden:
+ if not cls.hidden and len(cls.item_names) > 0:
logger.info(f" {name:{longest_name}}: {len(cls.item_names):3} "
f"Items (IDs: {min(cls.item_id_to_name):{numlength}} - "
f"{max(cls.item_id_to_name):{numlength}}) | "
diff --git a/Options.py b/Options.py
index 567ac8dbc6..c243c8feb4 100644
--- a/Options.py
+++ b/Options.py
@@ -427,7 +427,6 @@ class TextChoice(Choice):
assert isinstance(value, str) or isinstance(value, int), \
f"{value} is not a valid option for {self.__class__.__name__}"
self.value = value
- super(TextChoice, self).__init__()
@property
def current_key(self) -> str:
@@ -467,6 +466,124 @@ class TextChoice(Choice):
raise TypeError(f"Can't compare {self.__class__.__name__} with {other.__class__.__name__}")
+class BossMeta(AssembleOptions):
+ def __new__(mcs, name, bases, attrs):
+ if name != "PlandoBosses":
+ assert "bosses" in attrs, f"Please define valid bosses for {name}"
+ attrs["bosses"] = frozenset((boss.lower() for boss in attrs["bosses"]))
+ assert "locations" in attrs, f"Please define valid locations for {name}"
+ attrs["locations"] = frozenset((location.lower() for location in attrs["locations"]))
+ cls = super().__new__(mcs, name, bases, attrs)
+ assert not cls.duplicate_bosses or "singularity" in cls.options, f"Please define option_singularity for {name}"
+ return cls
+
+
+class PlandoBosses(TextChoice, metaclass=BossMeta):
+ """Generic boss shuffle option that supports plando. Format expected is
+ 'location1-boss1;location2-boss2;shuffle_mode'.
+ If shuffle_mode is not provided in the string, this will be the default shuffle mode. Must override can_place_boss,
+ which passes a plando boss and location. Check if the placement is valid for your game here."""
+ bosses: typing.ClassVar[typing.Union[typing.Set[str], typing.FrozenSet[str]]]
+ locations: typing.ClassVar[typing.Union[typing.Set[str], typing.FrozenSet[str]]]
+
+ duplicate_bosses: bool = False
+
+ @classmethod
+ def from_text(cls, text: str):
+ # set all of our text to lower case for name checking
+ text = text.lower()
+ if text == "random":
+ return cls(random.choice(list(cls.options.values())))
+ for option_name, value in cls.options.items():
+ if option_name == text:
+ return cls(value)
+ options = text.split(";")
+
+ # since plando exists in the option verify the plando values given are valid
+ cls.validate_plando_bosses(options)
+ return cls.get_shuffle_mode(options)
+
+ @classmethod
+ def get_shuffle_mode(cls, option_list: typing.List[str]):
+ # find out what mode of boss shuffle we should use for placing bosses after plando
+ # and add as a string to look nice in the spoiler
+ if "random" in option_list:
+ shuffle = random.choice(list(cls.options))
+ option_list.remove("random")
+ options = ";".join(option_list) + f";{shuffle}"
+ boss_class = cls(options)
+ else:
+ for option in option_list:
+ if option in cls.options:
+ options = ";".join(option_list)
+ break
+ else:
+ if cls.duplicate_bosses and len(option_list) == 1:
+ if cls.valid_boss_name(option_list[0]):
+ # this doesn't exist in this class but it's a forced option for classes where this is called
+ options = option_list[0] + ";singularity"
+ else:
+ options = option_list[0] + f";{cls.name_lookup[cls.default]}"
+ else:
+ options = ";".join(option_list) + f";{cls.name_lookup[cls.default]}"
+ boss_class = cls(options)
+ return boss_class
+
+ @classmethod
+ def validate_plando_bosses(cls, options: typing.List[str]) -> None:
+ used_locations = []
+ used_bosses = []
+ for option in options:
+ # check if a shuffle mode was provided in the incorrect location
+ if option == "random" or option in cls.options:
+ if option != options[-1]:
+ raise ValueError(f"{option} option must be at the end of the boss_shuffle options!")
+ elif "-" in option:
+ location, boss = option.split("-")
+ if location in used_locations:
+ raise ValueError(f"Duplicate Boss Location {location} not allowed.")
+ if not cls.duplicate_bosses and boss in used_bosses:
+ raise ValueError(f"Duplicate Boss {boss} not allowed.")
+ used_locations.append(location)
+ used_bosses.append(boss)
+ if not cls.valid_boss_name(boss):
+ raise ValueError(f"{boss.title()} is not a valid boss name.")
+ if not cls.valid_location_name(location):
+ raise ValueError(f"{location.title()} is not a valid boss location name.")
+ if not cls.can_place_boss(boss, location):
+ raise ValueError(f"{location.title()} is not a valid location for {boss.title()} to be placed.")
+ else:
+ if cls.duplicate_bosses:
+ if not cls.valid_boss_name(option):
+ raise ValueError(f"{option} is not a valid boss name.")
+ else:
+ raise ValueError(f"{option.title()} is not formatted correctly.")
+
+ @classmethod
+ def can_place_boss(cls, boss: str, location: str) -> bool:
+ raise NotImplementedError
+
+ @classmethod
+ def valid_boss_name(cls, value: str) -> bool:
+ return value in cls.bosses
+
+ @classmethod
+ def valid_location_name(cls, value: str) -> bool:
+ return value in cls.locations
+
+ def verify(self, world, player_name: str, plando_options) -> None:
+ if isinstance(self.value, int):
+ return
+ from Generate import PlandoSettings
+ if not(PlandoSettings.bosses & plando_options):
+ import logging
+ # plando is disabled but plando options were given so pull the option and change it to an int
+ option = self.value.split(";")[-1]
+ self.value = self.options[option]
+ logging.warning(f"The plando bosses module is turned off, so {self.name_lookup[self.value].title()} "
+ f"boss shuffle will be used for player {player_name}.")
+
+
class Range(NumericOption):
range_start = 0
range_end = 1
diff --git a/PokemonClient.py b/PokemonClient.py
new file mode 100644
index 0000000000..2328243de5
--- /dev/null
+++ b/PokemonClient.py
@@ -0,0 +1,319 @@
+import asyncio
+import json
+import time
+import os
+import bsdiff4
+import subprocess
+import zipfile
+import hashlib
+from asyncio import StreamReader, StreamWriter
+from typing import List
+
+
+import Utils
+from CommonClient import CommonContext, server_loop, gui_enabled, ClientCommandProcessor, logger, \
+ get_base_parser
+
+from worlds.pokemon_rb.locations import location_data
+
+location_map = {"Rod": {}, "EventFlag": {}, "Missable": {}, "Hidden": {}, "list": {}}
+location_bytes_bits = {}
+for location in location_data:
+ if location.ram_address is not None:
+ if type(location.ram_address) == list:
+ location_map[type(location.ram_address).__name__][(location.ram_address[0].flag, location.ram_address[1].flag)] = location.address
+ location_bytes_bits[location.address] = [{'byte': location.ram_address[0].byte, 'bit': location.ram_address[0].bit},
+ {'byte': location.ram_address[1].byte, 'bit': location.ram_address[1].bit}]
+ else:
+ location_map[type(location.ram_address).__name__][location.ram_address.flag] = location.address
+ location_bytes_bits[location.address] = {'byte': location.ram_address.byte, 'bit': location.ram_address.bit}
+
+SYSTEM_MESSAGE_ID = 0
+
+CONNECTION_TIMING_OUT_STATUS = "Connection timing out. Please restart your emulator, then restart pkmn_rb.lua"
+CONNECTION_REFUSED_STATUS = "Connection Refused. Please start your emulator and make sure pkmn_rb.lua is running"
+CONNECTION_RESET_STATUS = "Connection was reset. Please restart your emulator, then restart pkmn_rb.lua"
+CONNECTION_TENTATIVE_STATUS = "Initial Connection Made"
+CONNECTION_CONNECTED_STATUS = "Connected"
+CONNECTION_INITIAL_STATUS = "Connection has not been initiated"
+
+DISPLAY_MSGS = True
+
+
+class GBCommandProcessor(ClientCommandProcessor):
+ def __init__(self, ctx: CommonContext):
+ super().__init__(ctx)
+
+ def _cmd_gb(self):
+ """Check Gameboy Connection State"""
+ if isinstance(self.ctx, GBContext):
+ logger.info(f"Gameboy Status: {self.ctx.gb_status}")
+
+
+class GBContext(CommonContext):
+ command_processor = GBCommandProcessor
+ game = 'Pokemon Red and Blue'
+ items_handling = 0b101
+
+ def __init__(self, server_address, password):
+ super().__init__(server_address, password)
+ self.gb_streams: (StreamReader, StreamWriter) = None
+ self.gb_sync_task = None
+ self.messages = {}
+ self.locations_array = None
+ self.gb_status = CONNECTION_INITIAL_STATUS
+ self.awaiting_rom = False
+ self.display_msgs = True
+
+ async def server_auth(self, password_requested: bool = False):
+ if password_requested and not self.password:
+ await super(GBContext, self).server_auth(password_requested)
+ if not self.auth:
+ self.awaiting_rom = True
+ logger.info('Awaiting connection to Bizhawk to get Player information')
+ return
+
+ await self.send_connect()
+
+ def _set_message(self, msg: str, msg_id: int):
+ if DISPLAY_MSGS:
+ self.messages[(time.time(), msg_id)] = msg
+
+ def on_package(self, cmd: str, args: dict):
+ if cmd == 'Connected':
+ self.locations_array = None
+ elif cmd == "RoomInfo":
+ self.seed_name = args['seed_name']
+ elif cmd == 'Print':
+ msg = args['text']
+ if ': !' not in msg:
+ self._set_message(msg, SYSTEM_MESSAGE_ID)
+ elif cmd == "ReceivedItems":
+ msg = f"Received {', '.join([self.item_names[item.item] for item in args['items']])}"
+ self._set_message(msg, SYSTEM_MESSAGE_ID)
+
+ def run_gui(self):
+ from kvui import GameManager
+
+ class GBManager(GameManager):
+ logging_pairs = [
+ ("Client", "Archipelago")
+ ]
+ base_title = "Archipelago Pokémon Client"
+
+ self.ui = GBManager(self)
+ self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
+
+
+def get_payload(ctx: GBContext):
+ current_time = time.time()
+ return json.dumps(
+ {
+ "items": [item.item for item in ctx.items_received],
+ "messages": {f'{key[0]}:{key[1]}': value for key, value in ctx.messages.items()
+ if key[0] > current_time - 10}
+ }
+ )
+
+
+async def parse_locations(data: List, ctx: GBContext):
+ locations = []
+ flags = {"EventFlag": data[:0x140], "Missable": data[0x140:0x140 + 0x20],
+ "Hidden": data[0x140 + 0x20: 0x140 + 0x20 + 0x0E], "Rod": data[0x140 + 0x20 + 0x0E:]}
+
+ # Check for clear problems
+ if len(flags['Rod']) > 1:
+ return
+ if flags["EventFlag"][1] + flags["EventFlag"][8] + flags["EventFlag"][9] + flags["EventFlag"][12] \
+ + flags["EventFlag"][61] + flags["EventFlag"][62] + flags["EventFlag"][63] + flags["EventFlag"][64] \
+ + flags["EventFlag"][65] + flags["EventFlag"][66] + flags["EventFlag"][67] + flags["EventFlag"][68] \
+ + flags["EventFlag"][69] + flags["EventFlag"][70] != 0:
+ return
+
+ for flag_type, loc_map in location_map.items():
+ for flag, loc_id in loc_map.items():
+ if flag_type == "list":
+ if (flags["EventFlag"][location_bytes_bits[loc_id][0]['byte']] & 1 << location_bytes_bits[loc_id][0]['bit']
+ and flags["Missable"][location_bytes_bits[loc_id][1]['byte']] & 1 << location_bytes_bits[loc_id][1]['bit']):
+ locations.append(loc_id)
+ elif flags[flag_type][location_bytes_bits[loc_id]['byte']] & 1 << location_bytes_bits[loc_id]['bit']:
+ locations.append(loc_id)
+ if flags["EventFlag"][280] & 1 and not ctx.finished_game:
+ await ctx.send_msgs([
+ {"cmd": "StatusUpdate",
+ "status": 30}
+ ])
+ ctx.finished_game = True
+ if locations == ctx.locations_array:
+ return
+ ctx.locations_array = locations
+ if locations is not None:
+ await ctx.send_msgs([{"cmd": "LocationChecks", "locations": locations}])
+
+
+async def gb_sync_task(ctx: GBContext):
+ logger.info("Starting GB connector. Use /gb for status information")
+ while not ctx.exit_event.is_set():
+ error_status = None
+ if ctx.gb_streams:
+ (reader, writer) = ctx.gb_streams
+ msg = get_payload(ctx).encode()
+ writer.write(msg)
+ writer.write(b'\n')
+ try:
+ await asyncio.wait_for(writer.drain(), timeout=1.5)
+ try:
+ # Data will return a dict with up to two fields:
+ # 1. A keepalive response of the Players Name (always)
+ # 2. An array representing the memory values of the locations area (if in game)
+ data = await asyncio.wait_for(reader.readline(), timeout=5)
+ data_decoded = json.loads(data.decode())
+ #print(data_decoded)
+
+ if ctx.seed_name and ctx.seed_name != bytes(data_decoded['seedName']).decode():
+ msg = "The server is running a different multiworld than your client is. (invalid seed_name)"
+ logger.info(msg, extra={'compact_gui': True})
+ ctx.gui_error('Error', msg)
+ error_status = CONNECTION_RESET_STATUS
+ ctx.seed_name = bytes(data_decoded['seedName']).decode()
+ if not ctx.auth:
+ ctx.auth = ''.join([chr(i) for i in data_decoded['playerName'] if i != 0])
+ if ctx.auth == '':
+ logger.info("Invalid ROM detected. No player name built into the ROM.")
+ if ctx.awaiting_rom:
+ await ctx.server_auth(False)
+ if 'locations' in data_decoded and ctx.game and ctx.gb_status == CONNECTION_CONNECTED_STATUS \
+ and not error_status and ctx.auth:
+ # Not just a keep alive ping, parse
+ asyncio.create_task(parse_locations(data_decoded['locations'], ctx))
+ except asyncio.TimeoutError:
+ logger.debug("Read Timed Out, Reconnecting")
+ error_status = CONNECTION_TIMING_OUT_STATUS
+ writer.close()
+ ctx.gb_streams = None
+ except ConnectionResetError as e:
+ logger.debug("Read failed due to Connection Lost, Reconnecting")
+ error_status = CONNECTION_RESET_STATUS
+ writer.close()
+ ctx.gb_streams = None
+ except TimeoutError:
+ logger.debug("Connection Timed Out, Reconnecting")
+ error_status = CONNECTION_TIMING_OUT_STATUS
+ writer.close()
+ ctx.gb_streams = None
+ except ConnectionResetError:
+ logger.debug("Connection Lost, Reconnecting")
+ error_status = CONNECTION_RESET_STATUS
+ writer.close()
+ ctx.gb_streams = None
+ if ctx.gb_status == CONNECTION_TENTATIVE_STATUS:
+ if not error_status:
+ logger.info("Successfully Connected to Gameboy")
+ ctx.gb_status = CONNECTION_CONNECTED_STATUS
+ else:
+ ctx.gb_status = f"Was tentatively connected but error occured: {error_status}"
+ elif error_status:
+ ctx.gb_status = error_status
+ logger.info("Lost connection to Gameboy and attempting to reconnect. Use /gb for status updates")
+ else:
+ try:
+ logger.debug("Attempting to connect to Gameboy")
+ ctx.gb_streams = await asyncio.wait_for(asyncio.open_connection("localhost", 17242), timeout=10)
+ ctx.gb_status = CONNECTION_TENTATIVE_STATUS
+ except TimeoutError:
+ logger.debug("Connection Timed Out, Trying Again")
+ ctx.gb_status = CONNECTION_TIMING_OUT_STATUS
+ continue
+ except ConnectionRefusedError:
+ logger.debug("Connection Refused, Trying Again")
+ ctx.gb_status = CONNECTION_REFUSED_STATUS
+ continue
+
+
+async def run_game(romfile):
+ auto_start = Utils.get_options()["pokemon_rb_options"].get("rom_start", True)
+ if auto_start is True:
+ import webbrowser
+ webbrowser.open(romfile)
+ elif os.path.isfile(auto_start):
+ subprocess.Popen([auto_start, romfile],
+ stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+
+
+async def patch_and_run_game(game_version, patch_file, ctx):
+ base_name = os.path.splitext(patch_file)[0]
+ comp_path = base_name + '.gb'
+ with open(Utils.local_path(Utils.get_options()["pokemon_rb_options"][f"{game_version}_rom_file"]), "rb") as stream:
+ base_rom = bytes(stream.read())
+ try:
+ with open(Utils.local_path('lib', 'worlds', 'pokemon_rb', f'basepatch_{game_version}.bsdiff4'), 'rb') as stream:
+ base_patch = bytes(stream.read())
+ except FileNotFoundError:
+ with open(Utils.local_path('worlds', 'pokemon_rb', f'basepatch_{game_version}.bsdiff4'), 'rb') as stream:
+ base_patch = bytes(stream.read())
+ base_patched_rom_data = bsdiff4.patch(base_rom, base_patch)
+ basemd5 = hashlib.md5()
+ basemd5.update(base_patched_rom_data)
+
+ with zipfile.ZipFile(patch_file, 'r') as patch_archive:
+ with patch_archive.open('delta.bsdiff4', 'r') as stream:
+ patch = stream.read()
+ patched_rom_data = bsdiff4.patch(base_patched_rom_data, patch)
+
+ written_hash = patched_rom_data[0xFFCC:0xFFDC]
+ if written_hash == basemd5.digest():
+ with open(comp_path, "wb") as patched_rom_file:
+ patched_rom_file.write(patched_rom_data)
+
+ asyncio.create_task(run_game(comp_path))
+ else:
+ msg = "Patch supplied was not generated with the same base patch version as this client. Patching failed."
+ logger.warning(msg)
+ ctx.gui_error('Error', msg)
+
+
+if __name__ == '__main__':
+
+ Utils.init_logging("PokemonClient")
+
+ options = Utils.get_options()
+
+ async def main():
+ parser = get_base_parser()
+ parser.add_argument('patch_file', default="", type=str, nargs="?",
+ help='Path to an APRED or APBLUE patch file')
+ args = parser.parse_args()
+
+ ctx = GBContext(args.connect, args.password)
+ ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")
+ if gui_enabled:
+ ctx.run_gui()
+ ctx.run_cli()
+ ctx.gb_sync_task = asyncio.create_task(gb_sync_task(ctx), name="GB Sync")
+
+ if args.patch_file:
+ ext = args.patch_file.split(".")[len(args.patch_file.split(".")) - 1].lower()
+ if ext == "apred":
+ logger.info("APRED file supplied, beginning patching process...")
+ asyncio.create_task(patch_and_run_game("red", args.patch_file, ctx))
+ elif ext == "apblue":
+ logger.info("APBLUE file supplied, beginning patching process...")
+ asyncio.create_task(patch_and_run_game("blue", args.patch_file, ctx))
+ else:
+ logger.warning(f"Unknown patch file extension {ext}")
+
+ await ctx.exit_event.wait()
+ ctx.server_address = None
+
+ await ctx.shutdown()
+
+ if ctx.gb_sync_task:
+ await ctx.gb_sync_task
+
+
+ import colorama
+
+ colorama.init()
+
+ asyncio.run(main())
+ colorama.deinit()
diff --git a/README.md b/README.md
index a82282037b..51e46d1c6e 100644
--- a/README.md
+++ b/README.md
@@ -29,6 +29,9 @@ Currently, the following games are supported:
* Donkey Kong Country 3
* Dark Souls 3
* Super Mario World
+* Pokémon Red and Blue
+* Hylics 2
+* Overcooked! 2
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled
diff --git a/Starcraft2Client.py b/Starcraft2Client.py
index 775cd253f5..239d33b049 100644
--- a/Starcraft2Client.py
+++ b/Starcraft2Client.py
@@ -114,12 +114,40 @@ class StarcraftClientProcessor(ClientCommandProcessor):
"""Manually set the SC2 install directory (if the automatic detection fails)."""
if path:
os.environ["SC2PATH"] = path
- check_mod_install()
+ is_mod_installed_correctly()
return True
else:
sc2_logger.warning("When using set_path, you must type the path to your SC2 install directory.")
return False
+ def _cmd_download_data(self, force: bool = False) -> bool:
+ """Download the most recent release of the necessary files for playing SC2 with
+ Archipelago. force should be True or False. force=True will overwrite your files."""
+ if "SC2PATH" not in os.environ:
+ check_game_install_path()
+
+ if os.path.exists(os.environ["SC2PATH"]+"ArchipelagoSC2Version.txt"):
+ with open(os.environ["SC2PATH"]+"ArchipelagoSC2Version.txt", "r") as f:
+ current_ver = f.read()
+ else:
+ current_ver = None
+
+ tempzip, version = download_latest_release_zip('TheCondor07', 'Starcraft2ArchipelagoData', current_version=current_ver, force_download=force)
+
+ if tempzip != '':
+ try:
+ import zipfile
+ zipfile.ZipFile(tempzip).extractall(path=os.environ["SC2PATH"])
+ sc2_logger.info(f"Download complete. Version {version} installed.")
+ with open(os.environ["SC2PATH"]+"ArchipelagoSC2Version.txt", "w") as f:
+ f.write(version)
+ finally:
+ os.remove(tempzip)
+ else:
+ sc2_logger.warning("Download aborted/failed. Read the log for more information.")
+ return False
+ return True
+
class SC2Context(CommonContext):
command_processor = StarcraftClientProcessor
@@ -162,10 +190,13 @@ class SC2Context(CommonContext):
self.build_location_to_mission_mapping()
- # Look for and set SC2PATH.
- # check_game_install_path() returns True if and only if it finds + sets SC2PATH.
- if "SC2PATH" not in os.environ and check_game_install_path():
- check_mod_install()
+ # Looks for the required maps and mods for SC2. Runs check_game_install_path.
+ is_mod_installed_correctly()
+ if os.path.exists(os.environ["SC2PATH"] + "ArchipelagoSC2Version.txt"):
+ with open(os.environ["SC2PATH"] + "ArchipelagoSC2Version.txt", "r") as f:
+ current_ver = f.read()
+ if is_mod_update_available("TheCondor07", "Starcraft2ArchipelagoData", current_ver):
+ sc2_logger.info("NOTICE: Update for required files found. Run /download_data to install.")
def on_print_json(self, args: dict):
# goes to this world
@@ -857,18 +888,53 @@ def check_game_install_path() -> bool:
return False
-def check_mod_install() -> bool:
- # Pull up the SC2PATH if set. If not, encourage the user to manually run /set_path.
- try:
- # Check inside the Mods folder for Archipelago.SC2Mod. If found, tell user. If not, tell user.
- if os.path.isfile(modfile := (os.environ["SC2PATH"] / Path("Mods") / Path("Archipelago.SC2Mod"))):
- sc2_logger.info(f"Archipelago mod found at {modfile}.")
- return True
- else:
- sc2_logger.warning(f"Archipelago mod could not be found at {modfile}. Please install the mod file there.")
- except KeyError:
- sc2_logger.warning(f"SC2PATH isn't set. Please run /set_path with the path to your SC2 install.")
- return False
+def is_mod_installed_correctly() -> bool:
+ """Searches for all required files."""
+ if "SC2PATH" not in os.environ:
+ check_game_install_path()
+
+ mapdir = os.environ['SC2PATH'] / Path('Maps/ArchipelagoCampaign')
+ modfile = os.environ["SC2PATH"] / Path("Mods/Archipelago.SC2Mod")
+ wol_required_maps = [
+ "ap_thanson01.SC2Map", "ap_thanson02.SC2Map", "ap_thanson03a.SC2Map", "ap_thanson03b.SC2Map",
+ "ap_thorner01.SC2Map", "ap_thorner02.SC2Map", "ap_thorner03.SC2Map", "ap_thorner04.SC2Map", "ap_thorner05s.SC2Map",
+ "ap_traynor01.SC2Map", "ap_traynor02.SC2Map", "ap_traynor03.SC2Map",
+ "ap_ttosh01.SC2Map", "ap_ttosh02.SC2Map", "ap_ttosh03a.SC2Map", "ap_ttosh03b.SC2Map",
+ "ap_ttychus01.SC2Map", "ap_ttychus02.SC2Map", "ap_ttychus03.SC2Map", "ap_ttychus04.SC2Map", "ap_ttychus05.SC2Map",
+ "ap_tvalerian01.SC2Map", "ap_tvalerian02a.SC2Map", "ap_tvalerian02b.SC2Map", "ap_tvalerian03.SC2Map",
+ "ap_tzeratul01.SC2Map", "ap_tzeratul02.SC2Map", "ap_tzeratul03.SC2Map", "ap_tzeratul04.SC2Map"
+ ]
+ needs_files = False
+
+ # Check for maps.
+ missing_maps = []
+ for mapfile in wol_required_maps:
+ if not os.path.isfile(mapdir / mapfile):
+ missing_maps.append(mapfile)
+ if len(missing_maps) >= 19:
+ sc2_logger.warning(f"All map files missing from {mapdir}.")
+ needs_files = True
+ elif len(missing_maps) > 0:
+ for map in missing_maps:
+ sc2_logger.debug(f"Missing {map} from {mapdir}.")
+ sc2_logger.warning(f"Missing {len(missing_maps)} map files.")
+ needs_files = True
+ else: # Must be no maps missing
+ sc2_logger.info(f"All maps found in {mapdir}.")
+
+ # Check for mods.
+ if os.path.isfile(modfile):
+ sc2_logger.info(f"Archipelago mod found at {modfile}.")
+ else:
+ sc2_logger.warning(f"Archipelago mod could not be found at {modfile}.")
+ needs_files = True
+
+ # Final verdict.
+ if needs_files:
+ sc2_logger.warning(f"Required files are missing. Run /download_data to acquire them.")
+ return False
+ else:
+ return True
class DllDirectory:
@@ -907,6 +973,64 @@ class DllDirectory:
return False
+def download_latest_release_zip(owner: str, repo: str, current_version: str = None, force_download=False) -> (str, str):
+ """Downloads the latest release of a GitHub repo to the current directory as a .zip file."""
+ import requests
+
+ headers = {"Accept": 'application/vnd.github.v3+json'}
+ url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest"
+
+ r1 = requests.get(url, headers=headers)
+ if r1.status_code == 200:
+ latest_version = r1.json()["tag_name"]
+ sc2_logger.info(f"Latest version: {latest_version}.")
+ else:
+ sc2_logger.warning(f"Status code: {r1.status_code}")
+ sc2_logger.warning(f"Failed to reach GitHub. Could not find download link.")
+ sc2_logger.warning(f"text: {r1.text}")
+ return "", current_version
+
+ if (force_download is False) and (current_version == latest_version):
+ sc2_logger.info("Latest version already installed.")
+ return "", current_version
+
+ sc2_logger.info(f"Attempting to download version {latest_version} of {repo}.")
+ download_url = r1.json()["assets"][0]["browser_download_url"]
+
+ r2 = requests.get(download_url, headers=headers)
+ if r2.status_code == 200:
+ with open(f"{repo}.zip", "wb") as fh:
+ fh.write(r2.content)
+ sc2_logger.info(f"Successfully downloaded {repo}.zip.")
+ return f"{repo}.zip", latest_version
+ else:
+ sc2_logger.warning(f"Status code: {r2.status_code}")
+ sc2_logger.warning("Download failed.")
+ sc2_logger.warning(f"text: {r2.text}")
+ return "", current_version
+
+
+def is_mod_update_available(owner: str, repo: str, current_version: str) -> bool:
+ import requests
+
+ headers = {"Accept": 'application/vnd.github.v3+json'}
+ url = f"https://api.github.com/repos/{owner}/{repo}/releases/latest"
+
+ r1 = requests.get(url, headers=headers)
+ if r1.status_code == 200:
+ latest_version = r1.json()["tag_name"]
+ if current_version != latest_version:
+ return True
+ else:
+ return False
+
+ else:
+ sc2_logger.warning(f"Failed to reach GitHub while checking for updates.")
+ sc2_logger.warning(f"Status code: {r1.status_code}")
+ sc2_logger.warning(f"text: {r1.text}")
+ return False
+
+
if __name__ == '__main__':
colorama.init()
asyncio.run(main())
diff --git a/Utils.py b/Utils.py
index 707415453a..7df5ce5978 100644
--- a/Utils.py
+++ b/Utils.py
@@ -295,6 +295,11 @@ def get_default_options() -> OptionsType:
"sni": "SNI",
"rom_start": True,
},
+ "pokemon_rb_options": {
+ "red_rom_file": "Pokemon Red (UE) [S][!].gb",
+ "blue_rom_file": "Pokemon Blue (UE) [S][!].gb",
+ "rom_start": True
+ }
}
return options
diff --git a/WebHostLib/options.py b/WebHostLib/options.py
index 6807d54689..db1e57fdec 100644
--- a/WebHostLib/options.py
+++ b/WebHostLib/options.py
@@ -15,7 +15,13 @@ handled_in_js = {"start_inventory", "local_items", "non_local_items", "start_hin
def create():
target_folder = local_path("WebHostLib", "static", "generated")
- os.makedirs(os.path.join(target_folder, "configs"), exist_ok=True)
+ yaml_folder = os.path.join(target_folder, "configs")
+ os.makedirs(yaml_folder, exist_ok=True)
+
+ for file in os.listdir(yaml_folder):
+ full_path: str = os.path.join(yaml_folder, file)
+ if os.path.isfile(full_path):
+ os.unlink(full_path)
def dictify_range(option: typing.Union[Options.Range, Options.SpecialRange]):
data = {}
@@ -25,9 +31,12 @@ def create():
data.update({
option.range_start: 0,
option.range_end: 0,
- "random": 0, "random-low": 0, "random-high": 0,
option.default: 50
})
+ for sub_option in {"random", "random-low", "random-high"}:
+ if sub_option != option.default:
+ data[sub_option] = 0
+
notes = {
special: "minimum value without special meaning",
option.range_start: "minimum value",
@@ -43,11 +52,6 @@ def create():
return data, notes
- def default_converter(default_value):
- if isinstance(default_value, (set, frozenset)):
- return list(default_value)
- return default_value
-
def get_html_doc(option_type: type(Options.Option)) -> str:
if not option_type.__doc__:
return "Please document me!"
@@ -73,7 +77,7 @@ def create():
res = Template(file_data).render(
options=all_options,
__version__=__version__, game=game_name, yaml_dump=yaml.dump,
- dictify_range=dictify_range, default_converter=default_converter,
+ dictify_range=dictify_range,
)
del file_data
diff --git a/WebHostLib/static/assets/faq.js b/WebHostLib/static/assets/faq.js
index 35f46e1628..1bf5e5a659 100644
--- a/WebHostLib/static/assets/faq.js
+++ b/WebHostLib/static/assets/faq.js
@@ -26,24 +26,22 @@ window.addEventListener('load', () => {
adjustHeaderWidth();
// Reset the id of all header divs to something nicer
- const headers = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6'));
- const scrollTargetIndex = window.location.href.search(/#[A-z0-9-_]*$/);
- for (let i=0; i < headers.length; i++){
- const headerId = headers[i].innerText.replace(/[ ]/g,'-').toLowerCase()
- headers[i].setAttribute('id', headerId);
- headers[i].addEventListener('click', () =>
- window.location.href = window.location.href.substring(0, scrollTargetIndex) + `#${headerId}`);
+ for (const header of document.querySelectorAll('h1, h2, h3, h4, h5, h6')) {
+ const headerId = header.innerText.replace(/\s+/g, '-').toLowerCase();
+ header.setAttribute('id', headerId);
+ header.addEventListener('click', () => {
+ window.location.hash = `#${headerId}`;
+ header.scrollIntoView();
+ });
}
// Manually scroll the user to the appropriate header if anchor navigation is used
- if (scrollTargetIndex > -1) {
- try{
- const scrollTarget = window.location.href.substring(scrollTargetIndex + 1);
- document.getElementById(scrollTarget).scrollIntoView({ behavior: "smooth" });
- } catch(error) {
- console.error(error);
+ document.fonts.ready.finally(() => {
+ if (window.location.hash) {
+ const scrollTarget = document.getElementById(window.location.hash.substring(1));
+ scrollTarget?.scrollIntoView();
}
- }
+ });
}).catch((error) => {
console.error(error);
tutorialWrapper.innerHTML =
diff --git a/WebHostLib/static/assets/gameInfo.js b/WebHostLib/static/assets/gameInfo.js
index 8a9c8b3f22..b8c56905a5 100644
--- a/WebHostLib/static/assets/gameInfo.js
+++ b/WebHostLib/static/assets/gameInfo.js
@@ -26,24 +26,22 @@ window.addEventListener('load', () => {
adjustHeaderWidth();
// Reset the id of all header divs to something nicer
- const headers = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6'));
- const scrollTargetIndex = window.location.href.search(/#[A-z0-9-_]*$/);
- for (let i=0; i < headers.length; i++){
- const headerId = headers[i].innerText.replace(/[ ]/g,'-').toLowerCase()
- headers[i].setAttribute('id', headerId);
- headers[i].addEventListener('click', () =>
- window.location.href = window.location.href.substring(0, scrollTargetIndex) + `#${headerId}`);
+ for (const header of document.querySelectorAll('h1, h2, h3, h4, h5, h6')) {
+ const headerId = header.innerText.replace(/\s+/g, '-').toLowerCase();
+ header.setAttribute('id', headerId);
+ header.addEventListener('click', () => {
+ window.location.hash = `#${headerId}`;
+ header.scrollIntoView();
+ });
}
// Manually scroll the user to the appropriate header if anchor navigation is used
- if (scrollTargetIndex > -1) {
- try{
- const scrollTarget = window.location.href.substring(scrollTargetIndex + 1);
- document.getElementById(scrollTarget).scrollIntoView({ behavior: "smooth" });
- } catch(error) {
- console.error(error);
+ document.fonts.ready.finally(() => {
+ if (window.location.hash) {
+ const scrollTarget = document.getElementById(window.location.hash.substring(1));
+ scrollTarget?.scrollIntoView();
}
- }
+ });
}).catch((error) => {
console.error(error);
gameInfo.innerHTML =
diff --git a/WebHostLib/static/assets/glossary.js b/WebHostLib/static/assets/glossary.js
index 44012d699f..04a2920086 100644
--- a/WebHostLib/static/assets/glossary.js
+++ b/WebHostLib/static/assets/glossary.js
@@ -26,24 +26,22 @@ window.addEventListener('load', () => {
adjustHeaderWidth();
// Reset the id of all header divs to something nicer
- const headers = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6'));
- const scrollTargetIndex = window.location.href.search(/#[A-z0-9-_]*$/);
- for (let i=0; i < headers.length; i++){
- const headerId = headers[i].innerText.replace(/[ ]/g,'-').toLowerCase()
- headers[i].setAttribute('id', headerId);
- headers[i].addEventListener('click', () =>
- window.location.href = window.location.href.substring(0, scrollTargetIndex) + `#${headerId}`);
+ for (const header of document.querySelectorAll('h1, h2, h3, h4, h5, h6')) {
+ const headerId = header.innerText.replace(/\s+/g, '-').toLowerCase();
+ header.setAttribute('id', headerId);
+ header.addEventListener('click', () => {
+ window.location.hash = `#${headerId}`;
+ header.scrollIntoView();
+ });
}
// Manually scroll the user to the appropriate header if anchor navigation is used
- if (scrollTargetIndex > -1) {
- try{
- const scrollTarget = window.location.href.substring(scrollTargetIndex + 1);
- document.getElementById(scrollTarget).scrollIntoView({ behavior: "smooth" });
- } catch(error) {
- console.error(error);
+ document.fonts.ready.finally(() => {
+ if (window.location.hash) {
+ const scrollTarget = document.getElementById(window.location.hash.substring(1));
+ scrollTarget?.scrollIntoView();
}
- }
+ });
}).catch((error) => {
console.error(error);
tutorialWrapper.innerHTML =
diff --git a/WebHostLib/static/assets/tutorial.js b/WebHostLib/static/assets/tutorial.js
index 23d2f076fc..1db08d85b3 100644
--- a/WebHostLib/static/assets/tutorial.js
+++ b/WebHostLib/static/assets/tutorial.js
@@ -27,25 +27,28 @@ window.addEventListener('load', () => {
tutorialWrapper.innerHTML += (new showdown.Converter()).makeHtml(results);
adjustHeaderWidth();
+ const title = document.querySelector('h1')
+ if (title) {
+ document.title = title.textContent;
+ }
+
// Reset the id of all header divs to something nicer
- const headers = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6'));
- const scrollTargetIndex = window.location.href.search(/#[A-z0-9-_]*$/);
- for (let i=0; i < headers.length; i++){
- const headerId = headers[i].innerText.replace(/[ ]/g,'-').toLowerCase()
- headers[i].setAttribute('id', headerId);
- headers[i].addEventListener('click', () =>
- window.location.href = window.location.href.substring(0, scrollTargetIndex) + `#${headerId}`);
+ for (const header of document.querySelectorAll('h1, h2, h3, h4, h5, h6')) {
+ const headerId = header.innerText.replace(/\s+/g, '-').toLowerCase();
+ header.setAttribute('id', headerId);
+ header.addEventListener('click', () => {
+ window.location.hash = `#${headerId}`;
+ header.scrollIntoView();
+ });
}
// Manually scroll the user to the appropriate header if anchor navigation is used
- if (scrollTargetIndex > -1) {
- try{
- const scrollTarget = window.location.href.substring(scrollTargetIndex + 1);
- document.getElementById(scrollTarget).scrollIntoView({ behavior: "smooth" });
- } catch(error) {
- console.error(error);
+ document.fonts.ready.finally(() => {
+ if (window.location.hash) {
+ const scrollTarget = document.getElementById(window.location.hash.substring(1));
+ scrollTarget?.scrollIntoView();
}
- }
+ });
}).catch((error) => {
console.error(error);
tutorialWrapper.innerHTML =
diff --git a/WebHostLib/static/styles/hostRoom.css b/WebHostLib/static/styles/hostRoom.css
index 94ec24bfa6..827f74c04d 100644
--- a/WebHostLib/static/styles/hostRoom.css
+++ b/WebHostLib/static/styles/hostRoom.css
@@ -55,4 +55,6 @@
border: 1px solid #2a6c2f;
border-radius: 6px;
color: #000000;
+ overflow-y: auto;
+ max-height: 400px;
}
diff --git a/WebHostLib/static/styles/themes/base.css b/WebHostLib/static/styles/themes/base.css
index 0dbdf5f6ea..fca65a51c1 100644
--- a/WebHostLib/static/styles/themes/base.css
+++ b/WebHostLib/static/styles/themes/base.css
@@ -1,5 +1,7 @@
html{
padding-top: 110px;
+ scroll-padding-top: 100px;
+ scroll-behavior: smooth;
}
#base-header{
diff --git a/WebHostLib/templates/check.html b/WebHostLib/templates/check.html
index 64f19b0f9c..04b51340b5 100644
--- a/WebHostLib/templates/check.html
+++ b/WebHostLib/templates/check.html
@@ -1,7 +1,6 @@
{% extends 'pageWrapper.html' %}
{% block head %}
- {{ super() }}
Mystery Check Result
diff --git a/WebHostLib/templates/generate.html b/WebHostLib/templates/generate.html
index aa16a47d35..eff42700a7 100644
--- a/WebHostLib/templates/generate.html
+++ b/WebHostLib/templates/generate.html
@@ -1,7 +1,6 @@
{% extends 'pageWrapper.html' %}
{% block head %}
- {{ super() }}
Generate Game
diff --git a/WebHostLib/templates/hostGame.html b/WebHostLib/templates/hostGame.html
index 55d155c74a..2bcb993af5 100644
--- a/WebHostLib/templates/hostGame.html
+++ b/WebHostLib/templates/hostGame.html
@@ -1,7 +1,6 @@
{% extends 'pageWrapper.html' %}
{% block head %}
- {{ super() }}
Upload Multidata
diff --git a/WebHostLib/templates/options.yaml b/WebHostLib/templates/options.yaml
index 11009106b8..3c21ecfb1d 100644
--- a/WebHostLib/templates/options.yaml
+++ b/WebHostLib/templates/options.yaml
@@ -43,14 +43,18 @@ requires:
{%- if option.range_start is defined and option.range_start is number %}
{{- range_option(option) -}}
{%- elif option.options -%}
- {%- for suboption_option_id, sub_option_name in option.name_lookup.items() %}
+ {%- for suboption_option_id, sub_option_name in option.name_lookup.items() %}
{{ sub_option_name }}: {% if suboption_option_id == option.default %}50{% else %}0{% endif %}
- {%- endfor -%}
- {% if option.default == "random" %}
- random: 50
- {%- endif -%}
+ {%- endfor -%}
+ {% if option.name_lookup[option.default] not in option.options %}
+ {{ option.default }}: 50
+ {%- endif -%}
+ {%- elif option.default is string %}
+ {{ option.default }}: 50
+ {%- elif option.default is iterable and option.default is not mapping %}
+ {{ option.default | list }}
{%- else %}
- {{ yaml_dump(default_converter(option.default)) | indent(4, first=False) }}
- {%- endif -%}
+ {{ yaml_dump(option.default) | indent(4, first=false) }}
+ {%- endif -%}
{%- endfor %}
{% if not options %}{}{% endif %}
diff --git a/WebHostLib/templates/startPlaying.html b/WebHostLib/templates/startPlaying.html
index 157d6de243..436af3df07 100644
--- a/WebHostLib/templates/startPlaying.html
+++ b/WebHostLib/templates/startPlaying.html
@@ -1,7 +1,6 @@
{% extends 'pageWrapper.html' %}
{% block head %}
- {{ super() }}
Start Playing
{% endblock %}
diff --git a/data/lua/PKMN_RB/core.dll b/data/lua/PKMN_RB/core.dll
new file mode 100644
index 0000000000..3e9569571a
Binary files /dev/null and b/data/lua/PKMN_RB/core.dll differ
diff --git a/data/lua/PKMN_RB/json.lua b/data/lua/PKMN_RB/json.lua
new file mode 100644
index 0000000000..a1f6e4ede2
--- /dev/null
+++ b/data/lua/PKMN_RB/json.lua
@@ -0,0 +1,389 @@
+--
+-- json.lua
+--
+-- Copyright (c) 2015 rxi
+--
+-- This library is free software; you can redistribute it and/or modify it
+-- under the terms of the MIT license. See LICENSE for details.
+--
+
+local json = { _version = "0.1.0" }
+
+-------------------------------------------------------------------------------
+-- Encode
+-------------------------------------------------------------------------------
+
+local encode
+
+function error(err)
+ print(err)
+end
+
+local escape_char_map = {
+ [ "\\" ] = "\\\\",
+ [ "\"" ] = "\\\"",
+ [ "\b" ] = "\\b",
+ [ "\f" ] = "\\f",
+ [ "\n" ] = "\\n",
+ [ "\r" ] = "\\r",
+ [ "\t" ] = "\\t",
+}
+
+local escape_char_map_inv = { [ "\\/" ] = "/" }
+for k, v in pairs(escape_char_map) do
+ escape_char_map_inv[v] = k
+end
+
+
+local function escape_char(c)
+ return escape_char_map[c] or string.format("\\u%04x", c:byte())
+end
+
+
+local function encode_nil(val)
+ return "null"
+end
+
+
+local function encode_table(val, stack)
+ local res = {}
+ stack = stack or {}
+
+ -- Circular reference?
+ if stack[val] then error("circular reference") end
+
+ stack[val] = true
+
+ if val[1] ~= nil or next(val) == nil then
+ -- Treat as array -- check keys are valid and it is not sparse
+ local n = 0
+ for k in pairs(val) do
+ if type(k) ~= "number" then
+ error("invalid table: mixed or invalid key types")
+ end
+ n = n + 1
+ end
+ if n ~= #val then
+ print("invalid table: sparse array")
+ print(n)
+ print("VAL:")
+ print(val)
+ print("STACK:")
+ print(stack)
+ end
+ -- Encode
+ for i, v in ipairs(val) do
+ table.insert(res, encode(v, stack))
+ end
+ stack[val] = nil
+ return "[" .. table.concat(res, ",") .. "]"
+
+ else
+ -- Treat as an object
+ for k, v in pairs(val) do
+ if type(k) ~= "string" then
+ error("invalid table: mixed or invalid key types")
+ end
+ table.insert(res, encode(k, stack) .. ":" .. encode(v, stack))
+ end
+ stack[val] = nil
+ return "{" .. table.concat(res, ",") .. "}"
+ end
+end
+
+
+local function encode_string(val)
+ return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"'
+end
+
+
+local function encode_number(val)
+ -- Check for NaN, -inf and inf
+ if val ~= val or val <= -math.huge or val >= math.huge then
+ error("unexpected number value '" .. tostring(val) .. "'")
+ end
+ return string.format("%.14g", val)
+end
+
+
+local type_func_map = {
+ [ "nil" ] = encode_nil,
+ [ "table" ] = encode_table,
+ [ "string" ] = encode_string,
+ [ "number" ] = encode_number,
+ [ "boolean" ] = tostring,
+}
+
+
+encode = function(val, stack)
+ local t = type(val)
+ local f = type_func_map[t]
+ if f then
+ return f(val, stack)
+ end
+ error("unexpected type '" .. t .. "'")
+end
+
+
+function json.encode(val)
+ return ( encode(val) )
+end
+
+
+-------------------------------------------------------------------------------
+-- Decode
+-------------------------------------------------------------------------------
+
+local parse
+
+local function create_set(...)
+ local res = {}
+ for i = 1, select("#", ...) do
+ res[ select(i, ...) ] = true
+ end
+ return res
+end
+
+local space_chars = create_set(" ", "\t", "\r", "\n")
+local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",")
+local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u")
+local literals = create_set("true", "false", "null")
+
+local literal_map = {
+ [ "true" ] = true,
+ [ "false" ] = false,
+ [ "null" ] = nil,
+}
+
+
+local function next_char(str, idx, set, negate)
+ for i = idx, #str do
+ if set[str:sub(i, i)] ~= negate then
+ return i
+ end
+ end
+ return #str + 1
+end
+
+
+local function decode_error(str, idx, msg)
+ --local line_count = 1
+ --local col_count = 1
+ --for i = 1, idx - 1 do
+ -- col_count = col_count + 1
+ -- if str:sub(i, i) == "\n" then
+ -- line_count = line_count + 1
+ -- col_count = 1
+ -- end
+ -- end
+ -- emu.message( string.format("%s at line %d col %d", msg, line_count, col_count) )
+end
+
+
+local function codepoint_to_utf8(n)
+ -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa
+ local f = math.floor
+ if n <= 0x7f then
+ return string.char(n)
+ elseif n <= 0x7ff then
+ return string.char(f(n / 64) + 192, n % 64 + 128)
+ elseif n <= 0xffff then
+ return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128)
+ elseif n <= 0x10ffff then
+ return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128,
+ f(n % 4096 / 64) + 128, n % 64 + 128)
+ end
+ error( string.format("invalid unicode codepoint '%x'", n) )
+end
+
+
+local function parse_unicode_escape(s)
+ local n1 = tonumber( s:sub(3, 6), 16 )
+ local n2 = tonumber( s:sub(9, 12), 16 )
+ -- Surrogate pair?
+ if n2 then
+ return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000)
+ else
+ return codepoint_to_utf8(n1)
+ end
+end
+
+
+local function parse_string(str, i)
+ local has_unicode_escape = false
+ local has_surrogate_escape = false
+ local has_escape = false
+ local last
+ for j = i + 1, #str do
+ local x = str:byte(j)
+
+ if x < 32 then
+ decode_error(str, j, "control character in string")
+ end
+
+ if last == 92 then -- "\\" (escape char)
+ if x == 117 then -- "u" (unicode escape sequence)
+ local hex = str:sub(j + 1, j + 5)
+ if not hex:find("%x%x%x%x") then
+ decode_error(str, j, "invalid unicode escape in string")
+ end
+ if hex:find("^[dD][89aAbB]") then
+ has_surrogate_escape = true
+ else
+ has_unicode_escape = true
+ end
+ else
+ local c = string.char(x)
+ if not escape_chars[c] then
+ decode_error(str, j, "invalid escape char '" .. c .. "' in string")
+ end
+ has_escape = true
+ end
+ last = nil
+
+ elseif x == 34 then -- '"' (end of string)
+ local s = str:sub(i + 1, j - 1)
+ if has_surrogate_escape then
+ s = s:gsub("\\u[dD][89aAbB]..\\u....", parse_unicode_escape)
+ end
+ if has_unicode_escape then
+ s = s:gsub("\\u....", parse_unicode_escape)
+ end
+ if has_escape then
+ s = s:gsub("\\.", escape_char_map_inv)
+ end
+ return s, j + 1
+
+ else
+ last = x
+ end
+ end
+ decode_error(str, i, "expected closing quote for string")
+end
+
+
+local function parse_number(str, i)
+ local x = next_char(str, i, delim_chars)
+ local s = str:sub(i, x - 1)
+ local n = tonumber(s)
+ if not n then
+ decode_error(str, i, "invalid number '" .. s .. "'")
+ end
+ return n, x
+end
+
+
+local function parse_literal(str, i)
+ local x = next_char(str, i, delim_chars)
+ local word = str:sub(i, x - 1)
+ if not literals[word] then
+ decode_error(str, i, "invalid literal '" .. word .. "'")
+ end
+ return literal_map[word], x
+end
+
+
+local function parse_array(str, i)
+ local res = {}
+ local n = 1
+ i = i + 1
+ while 1 do
+ local x
+ i = next_char(str, i, space_chars, true)
+ -- Empty / end of array?
+ if str:sub(i, i) == "]" then
+ i = i + 1
+ break
+ end
+ -- Read token
+ x, i = parse(str, i)
+ res[n] = x
+ n = n + 1
+ -- Next token
+ i = next_char(str, i, space_chars, true)
+ local chr = str:sub(i, i)
+ i = i + 1
+ if chr == "]" then break end
+ if chr ~= "," then decode_error(str, i, "expected ']' or ','") end
+ end
+ return res, i
+end
+
+
+local function parse_object(str, i)
+ local res = {}
+ i = i + 1
+ while 1 do
+ local key, val
+ i = next_char(str, i, space_chars, true)
+ -- Empty / end of object?
+ if str:sub(i, i) == "}" then
+ i = i + 1
+ break
+ end
+ -- Read key
+ if str:sub(i, i) ~= '"' then
+ decode_error(str, i, "expected string for key")
+ end
+ key, i = parse(str, i)
+ -- Read ':' delimiter
+ i = next_char(str, i, space_chars, true)
+ if str:sub(i, i) ~= ":" then
+ decode_error(str, i, "expected ':' after key")
+ end
+ i = next_char(str, i + 1, space_chars, true)
+ -- Read value
+ val, i = parse(str, i)
+ -- Set
+ res[key] = val
+ -- Next token
+ i = next_char(str, i, space_chars, true)
+ local chr = str:sub(i, i)
+ i = i + 1
+ if chr == "}" then break end
+ if chr ~= "," then decode_error(str, i, "expected '}' or ','") end
+ end
+ return res, i
+end
+
+
+local char_func_map = {
+ [ '"' ] = parse_string,
+ [ "0" ] = parse_number,
+ [ "1" ] = parse_number,
+ [ "2" ] = parse_number,
+ [ "3" ] = parse_number,
+ [ "4" ] = parse_number,
+ [ "5" ] = parse_number,
+ [ "6" ] = parse_number,
+ [ "7" ] = parse_number,
+ [ "8" ] = parse_number,
+ [ "9" ] = parse_number,
+ [ "-" ] = parse_number,
+ [ "t" ] = parse_literal,
+ [ "f" ] = parse_literal,
+ [ "n" ] = parse_literal,
+ [ "[" ] = parse_array,
+ [ "{" ] = parse_object,
+}
+
+
+parse = function(str, idx)
+ local chr = str:sub(idx, idx)
+ local f = char_func_map[chr]
+ if f then
+ return f(str, idx)
+ end
+ decode_error(str, idx, "unexpected character '" .. chr .. "'")
+end
+
+
+function json.decode(str)
+ if type(str) ~= "string" then
+ error("expected argument of type string, got " .. type(str))
+ end
+ return ( parse(str, next_char(str, 1, space_chars, true)) )
+end
+
+
+return json
\ No newline at end of file
diff --git a/data/lua/PKMN_RB/pkmn_rb.lua b/data/lua/PKMN_RB/pkmn_rb.lua
new file mode 100644
index 0000000000..7518a5f12b
--- /dev/null
+++ b/data/lua/PKMN_RB/pkmn_rb.lua
@@ -0,0 +1,238 @@
+local socket = require("socket")
+local json = require('json')
+local math = require('math')
+
+local STATE_OK = "Ok"
+local STATE_TENTATIVELY_CONNECTED = "Tentatively Connected"
+local STATE_INITIAL_CONNECTION_MADE = "Initial Connection Made"
+local STATE_UNINITIALIZED = "Uninitialized"
+
+local APIndex = 0x1A6E
+local APItemAddress = 0x00FF
+local EventFlagAddress = 0x1735
+local MissableAddress = 0x161A
+local HiddenItemsAddress = 0x16DE
+local RodAddress = 0x1716
+local InGame = 0x1A71
+
+local ItemsReceived = nil
+local playerName = nil
+local seedName = nil
+
+local prevstate = ""
+local curstate = STATE_UNINITIALIZED
+local gbSocket = nil
+local frame = 0
+
+local u8 = nil
+local wU8 = nil
+local u16
+
+--Sets correct memory access functions based on whether NesHawk or QuickNES is loaded
+local function defineMemoryFunctions()
+ local memDomain = {}
+ local domains = memory.getmemorydomainlist()
+ --if domains[1] == "System Bus" then
+ -- --NesHawk
+ -- isNesHawk = true
+ -- memDomain["systembus"] = function() memory.usememorydomain("System Bus") end
+ -- memDomain["saveram"] = function() memory.usememorydomain("Battery RAM") end
+ -- memDomain["rom"] = function() memory.usememorydomain("PRG ROM") end
+ --elseif domains[1] == "WRAM" then
+ -- --QuickNES
+ -- memDomain["systembus"] = function() memory.usememorydomain("System Bus") end
+ -- memDomain["saveram"] = function() memory.usememorydomain("WRAM") end
+ -- memDomain["rom"] = function() memory.usememorydomain("PRG ROM") end
+ --end
+ memDomain["rom"] = function() memory.usememorydomain("ROM") end
+ memDomain["wram"] = function() memory.usememorydomain("WRAM") end
+ return memDomain
+end
+
+local memDomain = defineMemoryFunctions()
+u8 = memory.read_u8
+wU8 = memory.write_u8
+u16 = memory.read_u16_le
+function uRange(address, bytes)
+ data = memory.readbyterange(address - 1, bytes + 1)
+ data[0] = nil
+ return data
+end
+
+
+function table.empty (self)
+ for _, _ in pairs(self) do
+ return false
+ end
+ return true
+end
+
+function slice (tbl, s, e)
+ local pos, new = 1, {}
+ for i = s + 1, e do
+ new[pos] = tbl[i]
+ pos = pos + 1
+ end
+ return new
+end
+
+function processBlock(block)
+ if block == nil then
+ return
+ end
+ local itemsBlock = block["items"]
+ memDomain.wram()
+ if itemsBlock ~= nil then-- and u8(0x116B) ~= 0x00 then
+ -- print(itemsBlock)
+ ItemsReceived = itemsBlock
+
+ end
+end
+
+function difference(a, b)
+ local aa = {}
+ for k,v in pairs(a) do aa[v]=true end
+ for k,v in pairs(b) do aa[v]=nil end
+ local ret = {}
+ local n = 0
+ for k,v in pairs(a) do
+ if aa[v] then n=n+1 ret[n]=v end
+ end
+ return ret
+end
+
+function generateLocationsChecked()
+ memDomain.wram()
+ events = uRange(EventFlagAddress, 0x140)
+ missables = uRange(MissableAddress, 0x20)
+ hiddenitems = uRange(HiddenItemsAddress, 0x0E)
+ rod = u8(RodAddress)
+
+ data = {}
+
+ table.foreach(events, function(k, v) table.insert(data, v) end)
+ table.foreach(missables, function(k, v) table.insert(data, v) end)
+ table.foreach(hiddenitems, function(k, v) table.insert(data, v) end)
+ table.insert(data, rod)
+
+ return data
+end
+function generateSerialData()
+ memDomain.wram()
+ status = u8(0x1A73)
+ if status == 0 then
+ return nil
+ end
+ return uRange(0x1A76, u8(0x1A74))
+end
+local function arrayEqual(a1, a2)
+ if #a1 ~= #a2 then
+ return false
+ end
+
+ for i, v in ipairs(a1) do
+ if v ~= a2[i] then
+ return false
+ end
+ end
+
+ return true
+end
+
+function receive()
+ l, e = gbSocket:receive()
+ if e == 'closed' then
+ if curstate == STATE_OK then
+ print("Connection closed")
+ end
+ curstate = STATE_UNINITIALIZED
+ return
+ elseif e == 'timeout' then
+ --print("timeout") -- this keeps happening for some reason? just hide it
+ return
+ elseif e ~= nil then
+ print(e)
+ curstate = STATE_UNINITIALIZED
+ return
+ end
+ if l ~= nil then
+ processBlock(json.decode(l))
+ end
+ -- Determine Message to send back
+ memDomain.rom()
+ newPlayerName = uRange(0xFFF0, 0x10)
+ newSeedName = uRange(0xFFDC, 20)
+ if (playerName ~= nil and not arrayEqual(playerName, newPlayerName)) or (seedName ~= nil and not arrayEqual(seedName, newSeedName)) then
+ print("ROM changed, quitting")
+ curstate = STATE_UNINITIALIZED
+ return
+ end
+ playerName = newPlayerName
+ seedName = newSeedName
+ local retTable = {}
+ retTable["playerName"] = playerName
+ retTable["seedName"] = seedName
+ memDomain.wram()
+ if u8(InGame) == 0xAC then
+ retTable["locations"] = generateLocationsChecked()
+ serialData = generateSerialData()
+ if serialData ~= nil then
+ retTable["serial"] = serialData
+ end
+ end
+ msg = json.encode(retTable).."\n"
+ local ret, error = gbSocket:send(msg)
+ if ret == nil then
+ print(error)
+ elseif curstate == STATE_INITIAL_CONNECTION_MADE then
+ curstate = STATE_TENTATIVELY_CONNECTED
+ elseif curstate == STATE_TENTATIVELY_CONNECTED then
+ print("Connected!")
+ curstate = STATE_OK
+ end
+end
+
+function main()
+ if (is23Or24Or25 or is26To28) == false then
+ print("Must use a version of bizhawk 2.3.1 or higher")
+ return
+ end
+ server, error = socket.bind('localhost', 17242)
+
+ while true do
+ if not (curstate == prevstate) then
+ print("Current state: "..curstate)
+ prevstate = curstate
+ end
+ if (curstate == STATE_OK) or (curstate == STATE_INITIAL_CONNECTION_MADE) or (curstate == STATE_TENTATIVELY_CONNECTED) then
+ if (frame % 60 == 0) then
+ receive()
+ if u8(InGame) == 0xAC then
+ ItemIndex = u16(APIndex)
+ if ItemsReceived[ItemIndex + 1] ~= nil then
+ wU8(APItemAddress, ItemsReceived[ItemIndex + 1] - 172000000)
+ end
+ end
+ end
+ elseif (curstate == STATE_UNINITIALIZED) then
+ if (frame % 60 == 0) then
+
+ print("Waiting for client.")
+
+ emu.frameadvance()
+ server:settimeout(2)
+ print("Attempting to connect")
+ local client, timeout = server:accept()
+ if timeout == nil then
+ -- print('Initial Connection Made')
+ curstate = STATE_INITIAL_CONNECTION_MADE
+ gbSocket = client
+ gbSocket:settimeout(0)
+ end
+ end
+ end
+ emu.frameadvance()
+ end
+end
+
+main()
diff --git a/data/lua/PKMN_RB/socket.lua b/data/lua/PKMN_RB/socket.lua
new file mode 100644
index 0000000000..a98e952115
--- /dev/null
+++ b/data/lua/PKMN_RB/socket.lua
@@ -0,0 +1,132 @@
+-----------------------------------------------------------------------------
+-- LuaSocket helper module
+-- Author: Diego Nehab
+-- RCS ID: $Id: socket.lua,v 1.22 2005/11/22 08:33:29 diego Exp $
+-----------------------------------------------------------------------------
+
+-----------------------------------------------------------------------------
+-- Declare module and import dependencies
+-----------------------------------------------------------------------------
+local base = _G
+local string = require("string")
+local math = require("math")
+local socket = require("socket.core")
+module("socket")
+
+-----------------------------------------------------------------------------
+-- Exported auxiliar functions
+-----------------------------------------------------------------------------
+function connect(address, port, laddress, lport)
+ local sock, err = socket.tcp()
+ if not sock then return nil, err end
+ if laddress then
+ local res, err = sock:bind(laddress, lport, -1)
+ if not res then return nil, err end
+ end
+ local res, err = sock:connect(address, port)
+ if not res then return nil, err end
+ return sock
+end
+
+function bind(host, port, backlog)
+ local sock, err = socket.tcp()
+ if not sock then return nil, err end
+ sock:setoption("reuseaddr", true)
+ local res, err = sock:bind(host, port)
+ if not res then return nil, err end
+ res, err = sock:listen(backlog)
+ if not res then return nil, err end
+ return sock
+end
+
+try = newtry()
+
+function choose(table)
+ return function(name, opt1, opt2)
+ if base.type(name) ~= "string" then
+ name, opt1, opt2 = "default", name, opt1
+ end
+ local f = table[name or "nil"]
+ if not f then base.error("unknown key (".. base.tostring(name) ..")", 3)
+ else return f(opt1, opt2) end
+ end
+end
+
+-----------------------------------------------------------------------------
+-- Socket sources and sinks, conforming to LTN12
+-----------------------------------------------------------------------------
+-- create namespaces inside LuaSocket namespace
+sourcet = {}
+sinkt = {}
+
+BLOCKSIZE = 2048
+
+sinkt["close-when-done"] = function(sock)
+ return base.setmetatable({
+ getfd = function() return sock:getfd() end,
+ dirty = function() return sock:dirty() end
+ }, {
+ __call = function(self, chunk, err)
+ if not chunk then
+ sock:close()
+ return 1
+ else return sock:send(chunk) end
+ end
+ })
+end
+
+sinkt["keep-open"] = function(sock)
+ return base.setmetatable({
+ getfd = function() return sock:getfd() end,
+ dirty = function() return sock:dirty() end
+ }, {
+ __call = function(self, chunk, err)
+ if chunk then return sock:send(chunk)
+ else return 1 end
+ end
+ })
+end
+
+sinkt["default"] = sinkt["keep-open"]
+
+sink = choose(sinkt)
+
+sourcet["by-length"] = function(sock, length)
+ return base.setmetatable({
+ getfd = function() return sock:getfd() end,
+ dirty = function() return sock:dirty() end
+ }, {
+ __call = function()
+ if length <= 0 then return nil end
+ local size = math.min(socket.BLOCKSIZE, length)
+ local chunk, err = sock:receive(size)
+ if err then return nil, err end
+ length = length - string.len(chunk)
+ return chunk
+ end
+ })
+end
+
+sourcet["until-closed"] = function(sock)
+ local done
+ return base.setmetatable({
+ getfd = function() return sock:getfd() end,
+ dirty = function() return sock:dirty() end
+ }, {
+ __call = function()
+ if done then return nil end
+ local chunk, err, partial = sock:receive(socket.BLOCKSIZE)
+ if not err then return chunk
+ elseif err == "closed" then
+ sock:close()
+ done = 1
+ return partial
+ else return nil, err end
+ end
+ })
+end
+
+
+sourcet["default"] = sourcet["until-closed"]
+
+source = choose(sourcet)
diff --git a/docs/network protocol.md b/docs/network protocol.md
index 0e7a53f3cf..84587ab237 100644
--- a/docs/network protocol.md
+++ b/docs/network protocol.md
@@ -234,6 +234,8 @@ Sent to clients as a response the a [Get](#Get) package.
| ---- | ---- | ----- |
| keys | dict\[str\, any] | A key-value collection containing all the values for the keys requested in the [Get](#Get) package. |
+If a requested key was not present in the server's data, the associated value will be `null`.
+
Additional arguments added to the [Get](#Get) package that triggered this [Retrieved](#Retrieved) will also be passed along.
### SetReply
diff --git a/docs/running from source.md b/docs/running from source.md
index 24486146f8..2bda62ec1a 100644
--- a/docs/running from source.md
+++ b/docs/running from source.md
@@ -19,6 +19,10 @@ After this, you should be able to run the programs.
* With yaml(s) in the `Players` folder, `Generate.py` will generate the multiworld archive.
* `MultiServer.py`, with the filename of the generated archive as a command line parameter, will host the multiworld locally.
* `--log_network` is a command line parameter useful for debugging.
+ * `WebHost.py` will host the website on your computer.
+ * You can copy `docs/webhost configuration sample.yaml` to `config.yaml`
+ to change WebHost options (like the web hosting port number).
+ * As a side effect, `WebHost.py` creates the template yamls for all the games in `WebHostLib/static/generated`.
## Windows
diff --git a/host.yaml b/host.yaml
index b114135520..f36f014af4 100644
--- a/host.yaml
+++ b/host.yaml
@@ -138,6 +138,14 @@ dkc3_options:
# True for operating system default program
# Alternatively, a path to a program to open the .sfc file with
rom_start: true
+pokemon_rb_options:
+ # File names of the Pokemon Red and Blue roms
+ red_rom_file: "Pokemon Red (UE) [S][!].gb"
+ blue_rom_file: "Pokemon Blue (UE) [S][!].gb"
+ # Set this to false to never autostart a rom (such as after patching)
+ # True for operating system default program
+ # Alternatively, a path to a program to open the .gb file with
+ rom_start: true
smw_options:
# File name of the SMW US rom
rom_file: "Super Mario World (USA).sfc"
diff --git a/inno_setup.iss b/inno_setup.iss
index 9a2a40444e..578add59f6 100644
--- a/inno_setup.iss
+++ b/inno_setup.iss
@@ -59,6 +59,8 @@ Name: "generator/smw"; Description: "Super Mario World ROM Setup"; Types: ful
Name: "generator/soe"; Description: "Secret of Evermore ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 3145728; Flags: disablenouninstallwarning
Name: "generator/lttp"; Description: "A Link to the Past ROM Setup and Enemizer"; Types: full hosting; ExtraDiskSpaceRequired: 5191680
Name: "generator/oot"; Description: "Ocarina of Time ROM Setup"; Types: full hosting; ExtraDiskSpaceRequired: 100663296; Flags: disablenouninstallwarning
+Name: "generator/pkmn_r"; Description: "Pokemon Red ROM Setup"; Types: full hosting
+Name: "generator/pkmn_b"; Description: "Pokemon Blue ROM Setup"; Types: full hosting
Name: "server"; Description: "Server"; Types: full hosting
Name: "client"; Description: "Clients"; Types: full playing
Name: "client/sni"; Description: "SNI Client"; Types: full playing
@@ -70,6 +72,9 @@ Name: "client/factorio"; Description: "Factorio"; Types: full playing
Name: "client/minecraft"; Description: "Minecraft"; Types: full playing; ExtraDiskSpaceRequired: 226894278
Name: "client/oot"; Description: "Ocarina of Time"; Types: full playing
Name: "client/ff1"; Description: "Final Fantasy 1"; Types: full playing
+Name: "client/pkmn"; Description: "Pokemon Client"
+Name: "client/pkmn/red"; Description: "Pokemon Client - Pokemon Red Setup"; Types: full playing; ExtraDiskSpaceRequired: 1048576
+Name: "client/pkmn/blue"; Description: "Pokemon Client - Pokemon Blue Setup"; Types: full playing; ExtraDiskSpaceRequired: 1048576
Name: "client/cf"; Description: "ChecksFinder"; Types: full playing
Name: "client/sc2"; Description: "Starcraft 2"; Types: full playing
Name: "client/text"; Description: "Text, to !command and chat"; Types: full playing
@@ -84,6 +89,8 @@ Source: "{code:GetDKC3ROMPath}"; DestDir: "{app}"; DestName: "Donkey Kong Countr
Source: "{code:GetSMWROMPath}"; DestDir: "{app}"; DestName: "Super Mario World (USA).sfc"; Flags: external; Components: client/sni/smw or generator/smw
Source: "{code:GetSoEROMPath}"; DestDir: "{app}"; DestName: "Secret of Evermore (USA).sfc"; Flags: external; Components: generator/soe
Source: "{code:GetOoTROMPath}"; DestDir: "{app}"; DestName: "The Legend of Zelda - Ocarina of Time.z64"; Flags: external; Components: client/oot or generator/oot
+Source: "{code:GetRedROMPath}"; DestDir: "{app}"; DestName: "Pokemon Red (UE) [S][!].gb"; Flags: external; Components: client/pkmn/red or generator/pkmn_r
+Source: "{code:GetBlueROMPath}"; DestDir: "{app}"; DestName: "Pokemon Blue (UE) [S][!].gb"; Flags: external; Components: client/pkmn/blue or generator/pkmn_b
Source: "{#source_path}\*"; Excludes: "*.sfc, *.log, data\sprites\alttpr, SNI, EnemizerCLI, Archipelago*.exe"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "{#source_path}\SNI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\SNI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: client/sni
Source: "{#source_path}\EnemizerCLI\*"; Excludes: "*.sfc, *.log"; DestDir: "{app}\EnemizerCLI"; Flags: ignoreversion recursesubdirs createallsubdirs; Components: generator/lttp
@@ -98,6 +105,7 @@ Source: "{#source_path}\ArchipelagoMinecraftClient.exe"; DestDir: "{app}"; Flags
Source: "{#source_path}\ArchipelagoOoTClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/oot
Source: "{#source_path}\ArchipelagoOoTAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/oot
Source: "{#source_path}\ArchipelagoFF1Client.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/ff1
+Source: "{#source_path}\ArchipelagoPokemonClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/pkmn
Source: "{#source_path}\ArchipelagoChecksFinderClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/cf
Source: "{#source_path}\ArchipelagoStarcraft2Client.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/sc2
Source: "vc_redist.x64.exe"; DestDir: {tmp}; Flags: deleteafterinstall
@@ -111,6 +119,7 @@ Name: "{group}\{#MyAppName} Factorio Client"; Filename: "{app}\ArchipelagoFactor
Name: "{group}\{#MyAppName} Minecraft Client"; Filename: "{app}\ArchipelagoMinecraftClient.exe"; Components: client/minecraft
Name: "{group}\{#MyAppName} Ocarina of Time Client"; Filename: "{app}\ArchipelagoOoTClient.exe"; Components: client/oot
Name: "{group}\{#MyAppName} Final Fantasy 1 Client"; Filename: "{app}\ArchipelagoFF1Client.exe"; Components: client/ff1
+Name: "{group}\{#MyAppName} Pokemon Client"; Filename: "{app}\ArchipelagoPokemonClient.exe"; Components: client/pkmn
Name: "{group}\{#MyAppName} ChecksFinder Client"; Filename: "{app}\ArchipelagoChecksFinderClient.exe"; Components: client/cf
Name: "{group}\{#MyAppName} Starcraft 2 Client"; Filename: "{app}\ArchipelagoStarcraft2Client.exe"; Components: client/sc2
@@ -121,6 +130,7 @@ Name: "{commondesktop}\{#MyAppName} Factorio Client"; Filename: "{app}\Archipela
Name: "{commondesktop}\{#MyAppName} Minecraft Client"; Filename: "{app}\ArchipelagoMinecraftClient.exe"; Tasks: desktopicon; Components: client/minecraft
Name: "{commondesktop}\{#MyAppName} Ocarina of Time Client"; Filename: "{app}\ArchipelagoOoTClient.exe"; Tasks: desktopicon; Components: client/oot
Name: "{commondesktop}\{#MyAppName} Final Fantasy 1 Client"; Filename: "{app}\ArchipelagoFF1Client.exe"; Tasks: desktopicon; Components: client/ff1
+Name: "{commondesktop}\{#MyAppName} Pokemon Client"; Filename: "{app}\ArchipelagoPokemonClient.exe"; Tasks: desktopicon; Components: client/pkmn
Name: "{commondesktop}\{#MyAppName} ChecksFinder Client"; Filename: "{app}\ArchipelagoChecksFinderClient.exe"; Tasks: desktopicon; Components: client/cf
Name: "{commondesktop}\{#MyAppName} Starcraft 2 Client"; Filename: "{app}\ArchipelagoStarcraft2Client.exe"; Tasks: desktopicon; Components: client/sc2
@@ -179,6 +189,16 @@ Root: HKCR; Subkey: "{#MyAppName}n64zpf"; ValueData: "Archip
Root: HKCR; Subkey: "{#MyAppName}n64zpf\DefaultIcon"; ValueData: "{app}\ArchipelagoOoTClient.exe,0"; ValueType: string; ValueName: ""; Components: client/oot
Root: HKCR; Subkey: "{#MyAppName}n64zpf\shell\open\command"; ValueData: """{app}\ArchipelagoOoTClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/oot
+Root: HKCR; Subkey: ".apred"; ValueData: "{#MyAppName}pkmnrpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/pkmn/red
+Root: HKCR; Subkey: "{#MyAppName}pkmnrpatch"; ValueData: "Archipelago Pokemon Red Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/pkmn/red
+Root: HKCR; Subkey: "{#MyAppName}pkmnrpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoPokemonClient.exe,0"; ValueType: string; ValueName: ""; Components: client/pkmn/red
+Root: HKCR; Subkey: "{#MyAppName}pkmnrpatch\shell\open\command"; ValueData: """{app}\ArchipelagoPokemonClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/pkmn/red
+
+Root: HKCR; Subkey: ".apblue"; ValueData: "{#MyAppName}pkmnbpatch"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: client/pkmn/blue
+Root: HKCR; Subkey: "{#MyAppName}pkmnbpatch"; ValueData: "Archipelago Pokemon Blue Patch"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: client/pkmn/blue
+Root: HKCR; Subkey: "{#MyAppName}pkmnbpatch\DefaultIcon"; ValueData: "{app}\ArchipelagoPokemonClient.exe,0"; ValueType: string; ValueName: ""; Components: client/pkmn/blue
+Root: HKCR; Subkey: "{#MyAppName}pkmnbpatch\shell\open\command"; ValueData: """{app}\ArchipelagoPokemonClient.exe"" ""%1"""; ValueType: string; ValueName: ""; Components: client/pkmn/blue
+
Root: HKCR; Subkey: ".archipelago"; ValueData: "{#MyAppName}multidata"; Flags: uninsdeletevalue; ValueType: string; ValueName: ""; Components: server
Root: HKCR; Subkey: "{#MyAppName}multidata"; ValueData: "Archipelago Server Data"; Flags: uninsdeletekey; ValueType: string; ValueName: ""; Components: server
Root: HKCR; Subkey: "{#MyAppName}multidata\DefaultIcon"; ValueData: "{app}\ArchipelagoServer.exe,0"; ValueType: string; ValueName: ""; Components: server
@@ -234,6 +254,12 @@ var SoERomFilePage: TInputFileWizardPage;
var ootrom: string;
var OoTROMFilePage: TInputFileWizardPage;
+var redrom: string;
+var RedROMFilePage: TInputFileWizardPage;
+
+var bluerom: string;
+var BlueROMFilePage: TInputFileWizardPage;
+
function GetSNESMD5OfFile(const rom: string): string;
var data: AnsiString;
begin
@@ -281,6 +307,21 @@ begin
'.sfc');
end;
+function AddGBRomPage(name: string): TInputFileWizardPage;
+begin
+ Result :=
+ CreateInputFilePage(
+ wpSelectComponents,
+ 'Select ROM File',
+ 'Where is your ' + name + ' located?',
+ 'Select the file, then click Next.');
+
+ Result.Add(
+ 'Location of ROM file:',
+ 'GB ROM files|*.gb;*.gbc|All files|*.*',
+ '.gb');
+end;
+
procedure AddOoTRomPage();
begin
ootrom := FileSearch('The Legend of Zelda - Ocarina of Time.z64', WizardDirValue());
@@ -425,6 +466,38 @@ begin
Result := '';
end;
+function GetRedROMPath(Param: string): string;
+begin
+ if Length(redrom) > 0 then
+ Result := redrom
+ else if Assigned(RedRomFilePage) then
+ begin
+ R := CompareStr(GetMD5OfFile(RedROMFilePage.Values[0]), '3d45c1ee9abd5738df46d2bdda8b57dc')
+ if R <> 0 then
+ MsgBox('Pokemon Red ROM validation failed. Very likely wrong file.', mbInformation, MB_OK);
+
+ Result := RedROMFilePage.Values[0]
+ end
+ else
+ Result := '';
+ end;
+
+function GetBlueROMPath(Param: string): string;
+begin
+ if Length(bluerom) > 0 then
+ Result := bluerom
+ else if Assigned(BlueRomFilePage) then
+ begin
+ R := CompareStr(GetMD5OfFile(BlueROMFilePage.Values[0]), '50927e843568814f7ed45ec4f944bd8b')
+ if R <> 0 then
+ MsgBox('Pokemon Blue ROM validation failed. Very likely wrong file.', mbInformation, MB_OK);
+
+ Result := BlueROMFilePage.Values[0]
+ end
+ else
+ Result := '';
+ end;
+
procedure InitializeWizard();
begin
AddOoTRomPage();
@@ -448,6 +521,14 @@ begin
soerom := CheckRom('Secret of Evermore (USA).sfc', '6e9c94511d04fac6e0a1e582c170be3a');
if Length(soerom) = 0 then
SoEROMFilePage:= AddRomPage('Secret of Evermore (USA).sfc');
+
+ redrom := CheckRom('Pokemon Red (UE) [S][!].gb','3d45c1ee9abd5738df46d2bdda8b57dc');
+ if Length(redrom) = 0 then
+ RedROMFilePage:= AddGBRomPage('Pokemon Red (UE) [S][!].gb');
+
+ bluerom := CheckRom('Pokemon Blue (UE) [S][!].gb','50927e843568814f7ed45ec4f944bd8b');
+ if Length(bluerom) = 0 then
+ BlueROMFilePage:= AddGBRomPage('Pokemon Blue (UE) [S][!].gb');
end;
@@ -466,4 +547,8 @@ begin
Result := not (WizardIsComponentSelected('generator/soe'));
if (assigned(OoTROMFilePage)) and (PageID = OoTROMFilePage.ID) then
Result := not (WizardIsComponentSelected('generator/oot') or WizardIsComponentSelected('client/oot'));
-end;
\ No newline at end of file
+ if (assigned(RedROMFilePage)) and (PageID = RedROMFilePage.ID) then
+ Result := not (WizardIsComponentSelected('generator/pkmn_r') or WizardIsComponentSelected('client/pkmn/red'));
+ if (assigned(BlueROMFilePage)) and (PageID = BlueROMFilePage.ID) then
+ Result := not (WizardIsComponentSelected('generator/pkmn_b') or WizardIsComponentSelected('client/pkmn/blue'));
+end;
diff --git a/setup.py b/setup.py
index 11c993774c..19d042189b 100644
--- a/setup.py
+++ b/setup.py
@@ -289,6 +289,7 @@ tmp="${{exe#*/}}"
if [ ! "${{#tmp}}" -lt "${{#exe}}" ]; then
exe="{default_exe.parent}/$exe"
fi
+export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$APPDIR/{default_exe.parent}/lib"
$APPDIR/$exe "$@"
""")
launcher_filename.chmod(0o755)
diff --git a/test/general/TestImplemented.py b/test/general/TestImplemented.py
index b2e696ab0d..15e099ff09 100644
--- a/test/general/TestImplemented.py
+++ b/test/general/TestImplemented.py
@@ -8,7 +8,7 @@ class TestImplemented(unittest.TestCase):
def testCompletionCondition(self):
"""Ensure a completion condition is set that has requirements."""
for gamename, world_type in AutoWorldRegister.world_types.items():
- if not world_type.hidden and gamename not in {"ArchipIDLE", "Final Fantasy"}:
+ if not world_type.hidden and gamename not in {"ArchipIDLE", "Final Fantasy", "Sudoku"}:
with self.subTest(gamename):
world = setup_default_world(world_type)
self.assertFalse(world.completion_condition[1](world.state))
diff --git a/test/general/TestReachability.py b/test/general/TestReachability.py
index d638b56e8d..13d56d740a 100644
--- a/test/general/TestReachability.py
+++ b/test/general/TestReachability.py
@@ -28,7 +28,7 @@ class TestBase(unittest.TestCase):
def testEmptyStateCanReachSomething(self):
for game_name, world_type in AutoWorldRegister.world_types.items():
# Final Fantasy logic is controlled by finalfantasyrandomizer.com
- if game_name != "Archipelago" and game_name != "Final Fantasy":
+ if game_name not in {"Archipelago", "Final Fantasy", "Sudoku"}:
with self.subTest("Game", game=game_name):
world = setup_default_world(world_type)
state = CollectionState(world)
diff --git a/test/options/TestPlandoBosses.py b/test/options/TestPlandoBosses.py
new file mode 100644
index 0000000000..3c218b69aa
--- /dev/null
+++ b/test/options/TestPlandoBosses.py
@@ -0,0 +1,136 @@
+import unittest
+import Generate
+from Options import PlandoBosses
+
+
+class SingleBosses(PlandoBosses):
+ bosses = {"B1", "B2"}
+ locations = {"L1", "L2"}
+
+ option_vanilla = 0
+ option_shuffle = 1
+
+ @staticmethod
+ def can_place_boss(boss: str, location: str) -> bool:
+ if boss == "b1" and location == "l1":
+ return False
+ return True
+
+
+class MultiBosses(SingleBosses):
+ bosses = SingleBosses.bosses # explicit copy required
+ locations = SingleBosses.locations
+ duplicate_bosses = True
+
+ option_singularity = 2 # required when duplicate_bosses = True
+
+
+class TestPlandoBosses(unittest.TestCase):
+ def testCI(self):
+ """Bosses, locations and modes are supposed to be case-insensitive"""
+ self.assertEqual(MultiBosses.from_any("L1-B2").value, "l1-b2;vanilla")
+ self.assertEqual(MultiBosses.from_any("ShUfFlE").value, SingleBosses.option_shuffle)
+
+ def testRandom(self):
+ """Validate random is random"""
+ import random
+ random.seed(0)
+ value1 = MultiBosses.from_any("random")
+ random.seed(0)
+ value2 = MultiBosses.from_text("random")
+ self.assertEqual(value1, value2)
+ for n in range(0, 100):
+ if MultiBosses.from_text("random") != value1:
+ break
+ else:
+ raise Exception("random is not random")
+
+ def testShuffleMode(self):
+ """Test that simple modes (no Plando) work"""
+ self.assertEqual(MultiBosses.from_any("shuffle"), MultiBosses.option_shuffle)
+ self.assertNotEqual(MultiBosses.from_any("vanilla"), MultiBosses.option_shuffle)
+
+ def testPlacement(self):
+ """Test that valid placements work and invalid placements fail"""
+ with self.assertRaises(ValueError):
+ MultiBosses.from_any("l1-b1")
+ MultiBosses.from_any("l1-b2;l2-b1")
+
+ def testMixed(self):
+ """Test that shuffle is applied for remaining locations"""
+ self.assertIn("shuffle", MultiBosses.from_any("l1-b2;l2-b1;shuffle").value)
+ self.assertIn("vanilla", MultiBosses.from_any("l1-b2;l2-b1").value)
+
+ def testUnknown(self):
+ """Test that unknown values throw exceptions"""
+ # unknown boss
+ with self.assertRaises(ValueError):
+ MultiBosses.from_any("l1-b0")
+ # unknown location
+ with self.assertRaises(ValueError):
+ MultiBosses.from_any("l0-b1")
+ # swapped boss-location
+ with self.assertRaises(ValueError):
+ MultiBosses.from_any("b2-b2")
+ # boss name in place of mode (no singularity)
+ with self.assertRaises(ValueError):
+ SingleBosses.from_any("b1")
+ with self.assertRaises(ValueError):
+ SingleBosses.from_any("l2-b2;b1")
+ # location name in place of mode
+ with self.assertRaises(ValueError):
+ MultiBosses.from_any("l1")
+ with self.assertRaises(ValueError):
+ MultiBosses.from_any("l2-b2;l1")
+ # mode name in place of location
+ with self.assertRaises(ValueError):
+ MultiBosses.from_any("shuffle-b2;vanilla")
+ with self.assertRaises(ValueError):
+ MultiBosses.from_any("shuffle-b2;l2-b2")
+ # mode name in place of boss
+ with self.assertRaises(ValueError):
+ MultiBosses.from_any("l2-shuffle;vanilla")
+ with self.assertRaises(ValueError):
+ MultiBosses.from_any("l1-shuffle;l2-b2")
+
+ def testOrder(self):
+ """Can't use mode in random places"""
+ with self.assertRaises(ValueError):
+ MultiBosses.from_any("shuffle;l2-b2")
+
+ def testDuplicateBoss(self):
+ """Can place the same boss twice"""
+ MultiBosses.from_any("l1-b2;l2-b2")
+ with self.assertRaises(ValueError):
+ SingleBosses.from_any("l1-b2;l2-b2")
+
+ def testDuplicateLocation(self):
+ """Can't use the same location twice"""
+ with self.assertRaises(ValueError):
+ MultiBosses.from_any("l1-b2;l1-b2")
+
+ def testSingularity(self):
+ """Test automatic singularity mode"""
+ self.assertIn(";singularity", MultiBosses.from_any("b2").value)
+
+ def testPlandoSettings(self):
+ """Test that plando settings verification works"""
+ plandoed_string = "l1-b2;l2-b1"
+ mixed_string = "l1-b2;shuffle"
+ regular_string = "shuffle"
+ plandoed = MultiBosses.from_any(plandoed_string)
+ mixed = MultiBosses.from_any(mixed_string)
+ regular = MultiBosses.from_any(regular_string)
+
+ # plando should work with boss plando
+ plandoed.verify(None, "Player", Generate.PlandoSettings.bosses)
+ self.assertTrue(plandoed.value.startswith(plandoed_string))
+ # plando should fall back to default without boss plando
+ plandoed.verify(None, "Player", Generate.PlandoSettings.items)
+ self.assertEqual(plandoed, MultiBosses.option_vanilla)
+ # mixed should fall back to mode
+ mixed.verify(None, "Player", Generate.PlandoSettings.items) # should produce a warning and still work
+ self.assertEqual(mixed, MultiBosses.option_shuffle)
+ # mode stuff should just work
+ regular.verify(None, "Player", Generate.PlandoSettings.items)
+ self.assertEqual(regular, MultiBosses.option_shuffle)
diff --git a/test/options/__init__.py b/test/options/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/overcooked2/TestOvercooked2.py b/test/overcooked2/TestOvercooked2.py
new file mode 100644
index 0000000000..ec9efd1f67
--- /dev/null
+++ b/test/overcooked2/TestOvercooked2.py
@@ -0,0 +1,142 @@
+import unittest
+import json
+
+from random import Random
+
+from worlds.overcooked2.Items import *
+from worlds.overcooked2.Overcooked2Levels import Overcooked2Level, level_id_to_shortname
+from worlds.overcooked2.Logic import level_logic, level_shuffle_factory
+from worlds.overcooked2.Locations import oc2_location_name_to_id
+
+
+class Overcooked2Test(unittest.TestCase):
+ def testItems(self):
+ self.assertEqual(len(item_name_to_id), len(item_id_to_name))
+ self.assertEqual(len(item_name_to_id), len(item_table))
+
+ previous_item = None
+ for item_name in item_table.keys():
+ item: Item = item_table[item_name]
+ self.assertGreaterEqual(item.code, oc2_base_id, "Overcooked Item ID out of range")
+ self.assertLessEqual(item.code, item_table["Calmer Unbread"].code, "Overcooked Item ID out of range")
+
+ if previous_item is not None:
+ self.assertEqual(item.code, previous_item + 1,
+ f"Overcooked Item ID noncontinguous: {item.code-oc2_base_id}")
+ previous_item = item.code
+
+ self.assertEqual(item_table["Ok Emote"].code - item_table["Cooking Emote"].code,
+ 5, "Overcooked Emotes noncontigious")
+
+ for item_name in item_frequencies:
+ self.assertIn(item_name, item_table.keys(), "Unexpected Overcooked Item in item_frequencies")
+
+ for item_name in item_name_to_config_name.keys():
+ self.assertIn(item_name, item_table.keys(), "Unexpected Overcooked Item in config mapping")
+
+ for config_name in item_name_to_config_name.values():
+ self.assertIn(config_name, vanilla_values.keys(), "Unexpected Overcooked Item in default config mapping")
+
+ for config_name in vanilla_values.keys():
+ self.assertIn(config_name, item_name_to_config_name.values(),
+ "Unexpected Overcooked Item in default config mapping")
+
+ events = [
+ ("Kevin-2", {"action": "UNLOCK_LEVEL", "payload": "38"}),
+ ("Curse Emote", {"action": "UNLOCK_EMOTE", "payload": "1"}),
+ ("Larger Tip Jar", {"action": "INC_TIP_COMBO", "payload": ""}),
+ ("Order Lookahead", {"action": "INC_ORDERS_ON_SCREEN", "payload": ""}),
+ ("Control Stick Batteries", {"action": "SET_VALUE", "payload": "DisableControlStick=False"}),
+ ]
+ for (item_name, expected_event) in events:
+ expected_event["message"] = f"{item_name} Acquired!"
+ event = item_to_unlock_event(item_name)
+ self.assertEqual(event, expected_event)
+
+ self.assertFalse(is_progression("Preparing Emote"))
+
+ for item_name in item_table:
+ item_to_unlock_event(item_name)
+
+ def testOvercooked2Levels(self):
+ level_count = 0
+ for _ in Overcooked2Level():
+ level_count += 1
+ self.assertEqual(level_count, 44)
+
+ def testOvercooked2ShuffleFactory(self):
+ previous_runs = set()
+ for seed in range(0, 5):
+ levels = level_shuffle_factory(Random(seed), True, False)
+ self.assertEqual(len(levels), 44)
+ previous_level_id = None
+ for level_id in levels.keys():
+ if previous_level_id is not None:
+ self.assertEqual(previous_level_id+1, level_id)
+ previous_level_id = level_id
+
+ self.assertNotIn(levels[15], previous_runs)
+ previous_runs.add(levels[15])
+
+ levels = level_shuffle_factory(Random(123), False, True)
+ self.assertEqual(len(levels), 44)
+
+ def testLevelNameRepresentation(self):
+ shortnames = [level.as_generic_level.shortname for level in Overcooked2Level()]
+
+ for shortname in shortnames:
+ self.assertIn(shortname, level_logic.keys())
+
+ self.assertEqual(len(level_logic), len(level_id_to_shortname))
+
+ for level_name in level_logic.keys():
+ if level_name != "*":
+ self.assertIn(level_name, level_id_to_shortname.values())
+
+ for level_name in level_id_to_shortname.values():
+ if level_name != "Tutorial":
+ self.assertIn(level_name, level_logic.keys())
+
+ region_names = [level.level_name for level in Overcooked2Level()]
+ for location_name in oc2_location_name_to_id.keys():
+ level_name = location_name.split(" ")[0]
+ self.assertIn(level_name, region_names)
+
+ def testLogic(self):
+ for level_name in level_logic.keys():
+ logic = level_logic[level_name]
+ self.assertEqual(len(logic), 3, "Levels must provide logic for 1, 2, and 3 stars")
+
+ for l in logic:
+ self.assertEqual(len(l), 2)
+ (exclusive, additive) = l
+
+ for req in exclusive:
+ self.assertEqual(type(req), str)
+ self.assertIn(req, item_table.keys())
+
+ if len(additive) != 0:
+ self.assertGreater(len(additive), 1)
+ total_weight = 0.0
+ for req in additive:
+ self.assertEqual(len(req), 2)
+ (item_name, weight) = req
+ self.assertEqual(type(item_name), str)
+ self.assertEqual(type(weight), float)
+ total_weight += weight
+ self.assertIn(item_name, item_table.keys())
+
+ self.assertGreaterEqual(total_weight, 0.99, "Additive requirements must add to 1.0 or greater to have any effect")
+
+ def testItemLocationMapping(self):
+ number_of_items = 0
+ for item_name in item_frequencies:
+ freq = item_frequencies[item_name]
+ self.assertGreaterEqual(freq, 0)
+ number_of_items += freq
+
+ for item_name in item_table:
+ if item_name not in item_frequencies.keys():
+ number_of_items += 1
+
+ self.assertLessEqual(number_of_items, len(oc2_location_name_to_id), "Too many items (before fillers placed)")
diff --git a/test/overcooked2/__init__.py b/test/overcooked2/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/test/soe/TestAccess.py b/test/soe/TestAccess.py
new file mode 100644
index 0000000000..c7da7b8896
--- /dev/null
+++ b/test/soe/TestAccess.py
@@ -0,0 +1,22 @@
+import typing
+from . import SoETestBase
+
+
+class AccessTest(SoETestBase):
+ @staticmethod
+ def _resolveGourds(gourds: typing.Dict[str, typing.Iterable[int]]):
+ return [f"{name} #{number}" for name, numbers in gourds.items() for number in numbers]
+
+ def testBronzeAxe(self):
+ gourds = {
+ "Pyramid bottom": (118, 121, 122, 123, 124, 125),
+ "Pyramid top": (140,)
+ }
+ locations = ["Rimsala"] + self._resolveGourds(gourds)
+ items = [["Bronze Axe"]]
+ self.assertAccessDependency(locations, items)
+
+ def testBronzeSpearPlus(self):
+ locations = ["Megataur"]
+ items = [["Bronze Spear"], ["Lance (Weapon)"], ["Laser Lance"]]
+ self.assertAccessDependency(locations, items)
diff --git a/test/soe/TestGoal.py b/test/soe/TestGoal.py
new file mode 100644
index 0000000000..d127d38998
--- /dev/null
+++ b/test/soe/TestGoal.py
@@ -0,0 +1,53 @@
+from . import SoETestBase
+
+
+class TestFragmentGoal(SoETestBase):
+ options = {
+ "energy_core": "fragments",
+ "available_fragments": 21,
+ "required_fragments": 20,
+ }
+
+ def testFragments(self):
+ self.collect_by_name(["Gladiator Sword", "Diamond Eye", "Wheel", "Gauge"])
+ self.assertBeatable(False) # 0 fragments
+ fragments = self.get_items_by_name("Energy Core Fragment")
+ victory = self.get_item_by_name("Victory")
+ self.collect(fragments[:-2]) # 1 too few
+ self.assertEqual(self.count("Energy Core Fragment"), 19)
+ self.assertBeatable(False)
+ self.collect(fragments[-2:-1]) # exact
+ self.assertEqual(self.count("Energy Core Fragment"), 20)
+ self.assertBeatable(True)
+ self.remove([victory]) # reset
+ self.collect(fragments[-1:]) # 1 extra
+ self.assertEqual(self.count("Energy Core Fragment"), 21)
+ self.assertBeatable(True)
+
+ def testNoWeapon(self):
+ self.collect_by_name(["Diamond Eye", "Wheel", "Gauge", "Energy Core Fragment"])
+ self.assertBeatable(False)
+
+ def testNoRocket(self):
+ self.collect_by_name(["Gladiator Sword", "Diamond Eye", "Wheel", "Energy Core Fragment"])
+ self.assertBeatable(False)
+
+
+class TestShuffleGoal(SoETestBase):
+ options = {
+ "energy_core": "shuffle",
+ }
+
+ def testCore(self):
+ self.collect_by_name(["Gladiator Sword", "Diamond Eye", "Wheel", "Gauge"])
+ self.assertBeatable(False)
+ self.collect_by_name(["Energy Core"])
+ self.assertBeatable(True)
+
+ def testNoWeapon(self):
+ self.collect_by_name(["Diamond Eye", "Wheel", "Gauge", "Energy Core"])
+ self.assertBeatable(False)
+
+ def testNoRocket(self):
+ self.collect_by_name(["Gladiator Sword", "Diamond Eye", "Wheel", "Energy Core"])
+ self.assertBeatable(False)
diff --git a/test/soe/__init__.py b/test/soe/__init__.py
new file mode 100644
index 0000000000..0161a6c32f
--- /dev/null
+++ b/test/soe/__init__.py
@@ -0,0 +1,84 @@
+import typing
+import unittest
+from argparse import Namespace
+from test.general import gen_steps
+from BaseClasses import MultiWorld, Item
+from worlds import AutoWorld
+from worlds.AutoWorld import call_all
+
+
+class SoETestBase(unittest.TestCase):
+ options: typing.Dict[str, typing.Any] = {}
+ world: MultiWorld
+ game = "Secret of Evermore"
+
+ def setUp(self):
+ self.world = MultiWorld(1)
+ self.world.game[1] = self.game
+ self.world.player_name = {1: "Tester"}
+ self.world.set_seed()
+ args = Namespace()
+ for name, option in AutoWorld.AutoWorldRegister.world_types[self.game].option_definitions.items():
+ setattr(args, name, {1: option.from_any(self.options.get(name, option.default))})
+ self.world.set_options(args)
+ self.world.set_default_common_options()
+ for step in gen_steps:
+ call_all(self.world, step)
+
+ def collect_all_but(self, item_names: typing.Union[str, typing.Iterable[str]]):
+ if isinstance(item_names, str):
+ item_names = (item_names,)
+ for item in self.world.get_items():
+ if item.name not in item_names:
+ self.world.state.collect(item)
+
+ def get_item_by_name(self, item_name: str):
+ for item in self.world.get_items():
+ if item.name == item_name:
+ return item
+ raise ValueError("No such item")
+
+ def get_items_by_name(self, item_names: typing.Union[str, typing.Iterable[str]]):
+ if isinstance(item_names, str):
+ item_names = (item_names,)
+ return [item for item in self.world.itempool if item.name in item_names]
+
+ def collect_by_name(self, item_names: typing.Union[str, typing.Iterable[str]]):
+ items = self.get_items_by_name(item_names)
+ self.collect(items)
+ return items
+
+ def collect(self, items: typing.Union[Item, typing.Iterable[Item]]):
+ if isinstance(items, Item):
+ items = (items,)
+ for item in items:
+ self.world.state.collect(item)
+
+ def remove(self, items: typing.Union[Item, typing.Iterable[Item]]):
+ if isinstance(items, Item):
+ items = (items,)
+ for item in items:
+ if item.location and item.location.event and item.location in self.world.state.events:
+ self.world.state.events.remove(item.location)
+ self.world.state.remove(item)
+
+ def can_reach_location(self, location):
+ return self.world.state.can_reach(location, "Location", 1)
+
+ def count(self, item_name):
+ return self.world.state.count(item_name, 1)
+
+ def assertAccessDependency(self, locations, possible_items):
+ all_items = [item_name for item_names in possible_items for item_name in item_names]
+
+ self.collect_all_but(all_items)
+ for location in self.world.get_locations():
+ self.assertEqual(self.world.state.can_reach(location), location.name not in locations)
+ for item_names in possible_items:
+ items = self.collect_by_name(item_names)
+ for location in locations:
+ self.assertTrue(self.can_reach_location(location))
+ self.remove(items)
+
+ def assertBeatable(self, beatable: bool):
+ self.assertEqual(self.world.can_beat_game(self.world.state), beatable)
diff --git a/worlds/Files.py b/worlds/Files.py
index 6f81a0e2ea..ac1acbf322 100644
--- a/worlds/Files.py
+++ b/worlds/Files.py
@@ -99,7 +99,7 @@ class APContainer:
"player_name": self.player_name,
"game": self.game,
# minimum version of patch system expected for patching to be successful
- "compatible_version": 4,
+ "compatible_version": 5,
"version": current_patch_version,
}
diff --git a/worlds/alttp/Bosses.py b/worlds/alttp/Bosses.py
index 1c381b9a1c..870b3c7c2f 100644
--- a/worlds/alttp/Bosses.py
+++ b/worlds/alttp/Bosses.py
@@ -3,7 +3,7 @@ from typing import Optional, Union, List, Tuple, Callable, Dict
from BaseClasses import Boss
from Fill import FillError
-from .Options import Bosses
+from .Options import LTTPBosses as Bosses
def BossFactory(boss: str, player: int) -> Optional[Boss]:
diff --git a/worlds/alttp/Options.py b/worlds/alttp/Options.py
index b13d99f1e7..de6c479bd3 100644
--- a/worlds/alttp/Options.py
+++ b/worlds/alttp/Options.py
@@ -1,7 +1,7 @@
import typing
from BaseClasses import MultiWorld
-from Options import Choice, Range, Option, Toggle, DefaultOnToggle, DeathLink, TextChoice
+from Options import Choice, Range, Option, Toggle, DefaultOnToggle, DeathLink, TextChoice, PlandoBosses
class Logic(Choice):
@@ -138,7 +138,7 @@ class WorldState(Choice):
option_inverted = 2
-class Bosses(TextChoice):
+class LTTPBosses(PlandoBosses):
"""Shuffles bosses around to different locations.
Basic will shuffle all bosses except Ganon and Agahnim anywhere they can be placed.
Full chooses 3 bosses at random to be placed twice instead of Lanmolas, Moldorm, and Helmasaur.
@@ -152,7 +152,9 @@ class Bosses(TextChoice):
option_chaos = 3
option_singularity = 4
- bosses: set = {
+ duplicate_bosses = True
+
+ bosses = {
"Armos Knights",
"Lanmolas",
"Moldorm",
@@ -165,7 +167,7 @@ class Bosses(TextChoice):
"Trinexx",
}
- locations: set = {
+ locations = {
"Ganons Tower Top",
"Tower of Hera",
"Skull Woods",
@@ -181,99 +183,16 @@ class Bosses(TextChoice):
"Ganons Tower Bottom"
}
- def __init__(self, value: typing.Union[str, int]):
- assert isinstance(value, str) or isinstance(value, int), \
- f"{value} is not a valid option for {self.__class__.__name__}"
- self.value = value
-
@classmethod
- def from_text(cls, text: str):
- import random
- # set all of our text to lower case for name checking
- text = text.lower()
- cls.bosses = {boss_name.lower() for boss_name in cls.bosses}
- cls.locations = {boss_location.lower() for boss_location in cls.locations}
- if text == "random":
- return cls(random.choice(list(cls.options.values())))
- for option_name, value in cls.options.items():
- if option_name == text:
- return cls(value)
- options = text.split(";")
-
- # since plando exists in the option verify the plando values given are valid
- cls.validate_plando_bosses(options)
-
- # find out what type of boss shuffle we should use for placing bosses after plando
- # and add as a string to look nice in the spoiler
- if "random" in options:
- shuffle = random.choice(list(cls.options))
- options.remove("random")
- options = ";".join(options) + ";" + shuffle
- boss_class = cls(options)
- else:
- for option in options:
- if option in cls.options:
- boss_class = cls(";".join(options))
- break
- else:
- if len(options) == 1:
- if cls.valid_boss_name(options[0]):
- options = options[0] + ";singularity"
- boss_class = cls(options)
- else:
- options = options[0] + ";none"
- boss_class = cls(options)
- else:
- options = ";".join(options) + ";none"
- boss_class = cls(options)
- return boss_class
-
- @classmethod
- def validate_plando_bosses(cls, options: typing.List[str]) -> None:
- from .Bosses import can_place_boss, format_boss_location
- for option in options:
- if option == "random" or option in cls.options:
- if option != options[-1]:
- raise ValueError(f"{option} option must be at the end of the boss_shuffle options!")
- continue
- if "-" in option:
- location, boss = option.split("-")
- level = ''
- if not cls.valid_boss_name(boss):
- raise ValueError(f"{boss} is not a valid boss name for location {location}.")
- if not cls.valid_location_name(location):
- raise ValueError(f"{location} is not a valid boss location name.")
- if location.split(" ")[-1] in ("top", "middle", "bottom"):
- location = location.split(" ")
- level = location[-1]
- location = " ".join(location[:-1])
- location = location.title().replace("Of", "of")
- if not can_place_boss(boss.title(), location, level):
- raise ValueError(f"{format_boss_location(location, level)} "
- f"is not a valid location for {boss.title()}.")
- else:
- if not cls.valid_boss_name(option):
- raise ValueError(f"{option} is not a valid boss name.")
-
- @classmethod
- def valid_boss_name(cls, value: str) -> bool:
- return value.lower() in cls.bosses
-
- @classmethod
- def valid_location_name(cls, value: str) -> bool:
- return value in cls.locations
-
- def verify(self, world, player_name: str, plando_options) -> None:
- if isinstance(self.value, int):
- return
- from Generate import PlandoSettings
- if not(PlandoSettings.bosses & plando_options):
- import logging
- # plando is disabled but plando options were given so pull the option and change it to an int
- option = self.value.split(";")[-1]
- self.value = self.options[option]
- logging.warning(f"The plando bosses module is turned off, so {self.name_lookup[self.value].title()} "
- f"boss shuffle will be used for player {player_name}.")
+ def can_place_boss(cls, boss: str, location: str) -> bool:
+ from worlds.alttp.Bosses import can_place_boss
+ level = ''
+ words = location.split(" ")
+ if words[-1] in ("top", "middle", "bottom"):
+ level = words[-1]
+ location = " ".join(words[:-1])
+ location = location.title().replace("Of", "of")
+ return can_place_boss(boss.title(), location, level)
class Enemies(Choice):
@@ -497,7 +416,7 @@ alttp_options: typing.Dict[str, type(Option)] = {
"hints": Hints,
"scams": Scams,
"restrict_dungeon_item_on_boss": RestrictBossItem,
- "boss_shuffle": Bosses,
+ "boss_shuffle": LTTPBosses,
"pot_shuffle": PotShuffle,
"enemy_shuffle": EnemyShuffle,
"killable_thieves": KillableThieves,
diff --git a/worlds/bk_sudoku/__init__.py b/worlds/bk_sudoku/__init__.py
new file mode 100644
index 0000000000..7183b0ec18
--- /dev/null
+++ b/worlds/bk_sudoku/__init__.py
@@ -0,0 +1,33 @@
+from BaseClasses import Tutorial
+from ..AutoWorld import World, WebWorld
+from typing import Dict
+
+
+class Bk_SudokuWebWorld(WebWorld):
+ settings_page = "games/Sudoku/info/en"
+ theme = 'partyTime'
+ tutorials = [
+ Tutorial(
+ tutorial_name='Setup Guide',
+ description='A guide to playing BK Sudoku',
+ language='English',
+ file_name='setup_en.md',
+ link='guide/en',
+ authors=['Jarno']
+ )
+ ]
+
+
+class Bk_SudokuWorld(World):
+ """
+ Play a little Sudoku while you're in BK mode to maybe get some useful hints
+ """
+ game = "Sudoku"
+ web = Bk_SudokuWebWorld()
+
+ item_name_to_id: Dict[str, int] = {}
+ location_name_to_id: Dict[str, int] = {}
+
+ @classmethod
+ def stage_assert_generate(cls, world):
+ raise Exception("BK Sudoku cannot be used for generating worlds, the client can instead connect to any other world")
diff --git a/worlds/bk_sudoku/docs/en_Sudoku.md b/worlds/bk_sudoku/docs/en_Sudoku.md
new file mode 100644
index 0000000000..072e43a980
--- /dev/null
+++ b/worlds/bk_sudoku/docs/en_Sudoku.md
@@ -0,0 +1,13 @@
+# Bk Sudoku
+
+## What is this game?
+
+BK Sudoku is not a typical Archipelago game; instead, it is a generic Sudoku client that can connect to any existing multiworld. When connected, you can play Sudoku to unlock random hints for your game. While slow, it will give you something to do when you can't reach the checks in your game.
+
+## What hints are unlocked?
+
+After completing a Sudoku puzzle, the game will unlock 1 random hint for an unchecked location in the slot you are connected to. It is possible to hint the same location repeatedly if that location is still unchecked.
+
+## Where is the settings page?
+
+There is no settings page; this game cannot be used in your .yamls. Instead, the client can connect to any slot in a multiworld.
diff --git a/worlds/bk_sudoku/docs/setup_en.md b/worlds/bk_sudoku/docs/setup_en.md
new file mode 100644
index 0000000000..5e93dce873
--- /dev/null
+++ b/worlds/bk_sudoku/docs/setup_en.md
@@ -0,0 +1,24 @@
+# BK Sudoku Setup Guide
+
+## Required Software
+- [Bk Sudoku](https://github.com/Jarno458/sudoku)
+- [.Net 6](https://docs.microsoft.com/en-us/dotnet/core/install/windows?tabs=net60)
+
+## General Concept
+
+This is a client that can connect to any multiworld slot, and lets you play Sudoku to unlock random hints for that slot's locations.
+
+Due to the fact that the Sudoku client may connect to any slot, it is not necessary to generate a YAML for this game as it does not generate any new slots in the multiworld session.
+
+## Installation Procedures
+
+Go to the latest release on [BK Sudoku Releases](https://github.com/Jarno458/sudoku/releases). Download and extract the `Bk_Sudoku.zip` file.
+
+## Joining a MultiWorld Game
+
+1. Run Bk_Sudoku.exe
+2. Enter the name of the slot you wish to connect to
+3. Enter the server url & port number
+4. Press connect
+5. Choose difficulty
+6. Try to solve the Sudoku
diff --git a/worlds/generic/Rules.py b/worlds/generic/Rules.py
index 9b338e4d70..6f70e1b584 100644
--- a/worlds/generic/Rules.py
+++ b/worlds/generic/Rules.py
@@ -53,17 +53,25 @@ def set_rule(spot: typing.Union["BaseClasses.Location", "BaseClasses.Entrance"],
spot.access_rule = rule
-def add_rule(spot: typing.Union["BaseClasses.Location", "BaseClasses.Entrance"], rule: CollectionRule, combine='and'):
+def add_rule(spot: typing.Union["BaseClasses.Location", "BaseClasses.Entrance"], rule: CollectionRule, combine="and"):
old_rule = spot.access_rule
- if combine == 'or':
- spot.access_rule = lambda state: rule(state) or old_rule(state)
+ # empty rule, replace instead of add
+ if old_rule is spot.__class__.access_rule:
+ spot.access_rule = rule if combine == "and" else old_rule
else:
- spot.access_rule = lambda state: rule(state) and old_rule(state)
+ if combine == "and":
+ spot.access_rule = lambda state: rule(state) and old_rule(state)
+ else:
+ spot.access_rule = lambda state: rule(state) or old_rule(state)
def forbid_item(location: "BaseClasses.Location", item: str, player: int):
old_rule = location.item_rule
- location.item_rule = lambda i: (i.name != item or i.player != player) and old_rule(i)
+ # empty rule
+ if old_rule is location.__class__.item_rule:
+ location.item_rule = lambda i: i.name != item or i.player != player
+ else:
+ location.item_rule = lambda i: (i.name != item or i.player != player) and old_rule(i)
def forbid_items_for_player(location: "BaseClasses.Location", items: typing.Set[str], player: int):
@@ -77,9 +85,16 @@ def forbid_items(location: "BaseClasses.Location", items: typing.Set[str]):
location.item_rule = lambda i: i.name not in items and old_rule(i)
-def add_item_rule(location: "BaseClasses.Location", rule: ItemRule):
+def add_item_rule(location: "BaseClasses.Location", rule: ItemRule, combine: str = "and"):
old_rule = location.item_rule
- location.item_rule = lambda item: rule(item) and old_rule(item)
+ # empty rule, replace instead of add
+ if old_rule is location.__class__.item_rule:
+ location.item_rule = rule if combine == "and" else old_rule
+ else:
+ if combine == "and":
+ location.item_rule = lambda item: rule(item) and old_rule(item)
+ else:
+ location.item_rule = lambda item: rule(item) or old_rule(item)
def item_in_locations(state: "BaseClasses.CollectionState", item: str, player: int,
diff --git a/worlds/generic/docs/plando_en.md b/worlds/generic/docs/plando_en.md
index fa3edd1fa1..c9f70fcb90 100644
--- a/worlds/generic/docs/plando_en.md
+++ b/worlds/generic/docs/plando_en.md
@@ -2,23 +2,24 @@
## What is Plando?
-The purposes of randomizers is to randomize the items in a game to give a new experience. Plando takes this concept and
+The purpose of randomizers is to randomize the items in a game to give a new experience. Plando takes this concept and
changes it up by allowing you to plan out certain aspects of the game by placing certain items in certain locations,
certain bosses in certain rooms, edit text for certain NPCs/signs, or even force certain region connections. Each of
these options are going to be detailed separately as `item plando`, `boss plando`, `text plando`,
-and `connection plando`. Every game in archipelago supports item plando but the other plando options are only supported
-by certain games. Currently, only LTTP supports text and boss plando. Support for connection plando may vary.
+and `connection plando`. Every game in Archipelago supports item plando but the other plando options are only supported
+by certain games. Currently, only A Link to the Past supports text and boss plando. Support for connection plando may
+vary.
### Enabling Plando
-On the website plando will already be enabled. If you will be generating the game locally plando features must be
+On the website, plando will already be enabled. If you will be generating the game locally, plando features must be
enabled (opt-in).
-* To opt-in go to the archipelago installation (default: `C:\ProgramData\Archipelago`), open the host.yaml with a text
+* To opt-in go to the Archipelago installation (default: `C:\ProgramData\Archipelago`), open `host.yaml` with a text
editor and find the `plando_options` key. The available plando modules can be enabled by adding them after this such
as
`plando_options: bosses, items, texts, connections`.
-* You can add the necessary plando modules for your settings to the `requires` section of your yaml. Doing so will throw an error if the options that you need to generate properly are not enabled to ensure you will get the results you desire. Only enter in the plando modules that you are using here but it should look like:
+* You can add the necessary plando modules for your settings to the `requires` section of your YAML. Doing so will throw an error if the options that you need to generate properly are not enabled to ensure you will get the results you desire. Only enter in the plando modules that you are using here but it should look like:
```yaml
requires:
@@ -27,45 +28,45 @@ enabled (opt-in).
```
## Item Plando
-Item plando allows a player to place an item in a specific location or specific locations, place multiple items into a
+Item plando allows a player to place an item in a specific location or specific locations, or place multiple items into a
list of specific locations both in their own game or in another player's game.
-* The options for item plando are `from_pool`, `world`, `percentage`, `force`, `count`, and either item and location, or items
- and locations.
+* The options for item plando are `from_pool`, `world`, `percentage`, `force`, `count`, and either `item` and
+ `location`, or `items` and `locations`.
* `from_pool` determines if the item should be taken *from* the item pool or *added* to it. This can be true or
false and defaults to true if omitted.
* `world` is the target world to place the item in.
* It gets ignored if only one world is generated.
* Can be a number, name, true, false, null, or a list. False is the default.
- * If a number is used it targets that slot or player number in the multiworld.
- * If a name is used it will target the world with that player name.
- * If set to true it will be any player's world besides your own.
- * If set to false it will target your own world.
- * If set to null it will target a random world in the multiworld.
+ * If a number is used, it targets that slot or player number in the multiworld.
+ * If a name is used, it will target the world with that player name.
+ * If set to true, it will be any player's world besides your own.
+ * If set to false, it will target your own world.
+ * If set to null, it will target a random world in the multiworld.
* If a list of names is used, it will target the games with the player names specified.
- * `force` determines whether the generator will fail if the item can't be placed in the location can be true, false,
+ * `force` determines whether the generator will fail if the item can't be placed in the location. Can be true, false,
or silent. Silent is the default.
- * If set to true the item must be placed and the generator will throw an error if it is unable to do so.
- * If set to false the generator will log a warning if the placement can't be done but will still generate.
- * If set to silent and the placement fails it will be ignored entirely.
+ * If set to true, the item must be placed and the generator will throw an error if it is unable to do so.
+ * If set to false, the generator will log a warning if the placement can't be done but will still generate.
+ * If set to silent and the placement fails, it will be ignored entirely.
* `percentage` is the percentage chance for the relevant block to trigger. This can be any value from 0 to 100 and
if omitted will default to 100.
* Single Placement is when you use a plando block to place a single item at a single location.
* `item` is the item you would like to place and `location` is the location to place it.
* Multi Placement uses a plando block to place multiple items in multiple locations until either list is exhausted.
- * `items` defines the items to use and a number letting you place multiple of it. You can use true instead of a number to have it use however many of that item are in your item pool.
+ * `items` defines the items to use, each with a number for the amount. Using `true` instead of a number uses however many of that item are in your item pool.
* `locations` is a list of possible locations those items can be placed in.
* Using the multi placement method, placements are picked randomly.
- * Instead of a number, you can use true
+
* `count` can be used to set the maximum number of items placed from the block. The default is 1 if using `item` and False if using `items`
- * If a number is used it will try to place this number of items.
- * If set to false it will try to place as many items from the block as it can.
- * If `min` and `max` are defined, it will try to place a number of items between these two numbers at random
+ * If a number is used, it will try to place this number of items.
+ * If set to false, it will try to place as many items from the block as it can.
+ * If `min` and `max` are defined, it will try to place a number of items between these two numbers at random.
### Available Items and Locations
-A list of all available items and locations can be found in the [website's datapackage](/datapackage). The items and locations will be in the `"item_name_to_id"` and `"location_name_to_id"` sections of the relevant game. You do not need the quotes but the name must be entered in the same as it appears on that page and is caps-sensitive.
+A list of all available items and locations can be found in the [website's datapackage](/datapackage). The items and locations will be in the `"item_name_to_id"` and `"location_name_to_id"` sections of the relevant game. You do not need the quotes but the name must be entered in the same as it appears on that page and is case-sensitive.
### Examples
@@ -142,43 +143,43 @@ plando_items:
min: 1
max: 4
```
-1. This block has a 50% chance to occur, and if it does will place either the Empire Orb or Radiant Orb on another player's
-Starter Chest 1 and removes the chosen item from the item pool.
+1. This block has a 50% chance to occur, and if it does, it will place either the Empire Orb or Radiant Orb on another
+player's Starter Chest 1 and removes the chosen item from the item pool.
2. This block will always trigger and will place the player's swords, bow, magic meter, strength upgrades, and hookshots
in their own dungeon major item chests.
3. This block will always trigger and will lock boss relics on the bosses.
-4. This block has an 80% chance of occurring and when it does will place all but 1 of the items randomly among the four
-locations chosen here.
+4. This block has an 80% chance of occurring, and when it does, it will place all but 1 of the items randomly among the
+four locations chosen here.
5. This block will always trigger and will attempt to place a random 2 of Levitate, Revealer and Energize into
other players' Master Sword Pedestals or Boss Relic 1 locations.
6. This block will always trigger and will attempt to place a random number, between 1 and 4, of progressive swords
-into any locations within the game slots named BobsSlaytheSpire and BobsRogueLegacy
+into any locations within the game slots named BobsSlaytheSpire and BobsRogueLegacy.
## Boss Plando
-As this is currently only supported by A Link to the Past instead of explaining here please refer to the
+As this is currently only supported by A Link to the Past, instead of finding an explanation here, please refer to the
relevant guide: [A Link to the Past Plando Guide](/tutorial/A%20Link%20to%20the%20Past/plando/en)
## Text Plando
-As this is currently only supported by A Link to the Past instead of explaining here please refer to the
+As this is currently only supported by A Link to the Past, instead of finding an explanation here, please refer to the
relevant guide: [A Link to the Past Plando Guide](/tutorial/A%20Link%20to%20the%20Past/plando/en)
## Connections Plando
This is currently only supported by Minecraft and A Link to the Past. As the way that these games interact with their
-connections is different I will only explain the basics here while more specifics for Link to the Past connection plando
-can be found in its plando guide.
+connections is different, I will only explain the basics here, while more specifics for A Link to the Past connection
+plando can be found in its plando guide.
-* The options for connections are `percentage`, `entrance`, `exit`, and `direction`. Each of these options support
+* The options for connections are `percentage`, `entrance`, `exit`, and `direction`. Each of these options supports
subweights.
* `percentage` is the percentage chance for this connection from 0 to 100 and defaults to 100.
* Every connection has an `entrance` and an `exit`. These can be unlinked like in A Link to the Past insanity entrance
shuffle.
* `direction` can be `both`, `entrance`, or `exit` and determines in which direction this connection will operate.
-[Link to the Past connections](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/alttp/EntranceShuffle.py#L3852)
+[A Link to the Past connections](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/alttp/EntranceShuffle.py#L3852)
[Minecraft connections](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/minecraft/Regions.py#L62)
@@ -186,7 +187,7 @@ can be found in its plando guide.
```yaml
plando_connections:
- # example block 1 - Link to the Past
+ # example block 1 - A Link to the Past
- entrance: Cave Shop (Lake Hylia)
exit: Cave 45
direction: entrance
@@ -206,9 +207,9 @@ plando_connections:
direction: both
```
-1. These connections are decoupled so going into the lake hylia cave shop will take you to the inside of cave 45 and
- when you leave the interior you will exit to the cave 45 ledge. Going into the cave 45 entrance will then take you to
- the lake hylia cave shop. Walking into the entrance for the old man cave and Agahnim's Tower entrance will both take
- you to their locations as normal but leaving old man cave will exit at Agahnim's Tower.
-2. This will force a nether fortress and a village to be the overworld structures for your game. Note that for the
+1. These connections are decoupled, so going into the Lake Hylia Cave Shop will take you to the inside of Cave 45, and
+ when you leave the interior, you will exit to the Cave 45 ledge. Going into the Cave 45 entrance will then take you to
+ the Lake Hylia Cave Shop. Walking into the entrance for the Old Man Cave and Agahnim's Tower entrance will both take
+ you to their locations as normal, but leaving Old Man Cave will exit at Agahnim's Tower.
+2. This will force a Nether fortress and a village to be the Overworld structures for your game. Note that for the
Minecraft connection plando to work structure shuffle must be enabled.
diff --git a/worlds/generic/docs/triggers_en.md b/worlds/generic/docs/triggers_en.md
index 03ce9c65cb..a9ffebb466 100644
--- a/worlds/generic/docs/triggers_en.md
+++ b/worlds/generic/docs/triggers_en.md
@@ -8,36 +8,31 @@ about 5 minutes to read.
Triggers allow you to customize your game settings by allowing you to define one or many options which only occur under
specific conditions. These are essentially "if, then" statements for options in your game. A good example of what you
-can do with triggers is the custom mercenary mode YAML that was created using entirely triggers and plando.
+can do with triggers is the [custom mercenary mode YAML
+](https://github.com/alwaysintreble/Archipelago-yaml-dump/blob/main/Snippets/Mercenary%20Mode%20Snippet.yaml) that was
+created using entirely triggers and plando.
-Mercenary mode
-YAML: [Mercenary Mode YAML on GitHub](https://github.com/alwaysintreble/Archipelago-yaml-dump/blob/main/Snippets/Mercenary%20Mode%20Snippet.yaml)
-
-For more information on plando you can reference the general plando guide or the Link to the Past plando guide.
-
-General plando guide: [Archipelago Plando Guide](/tutorial/Archipelago/plando/en)
-
-Link to the Past plando guide: [LttP Plando Guide](/tutorial/A%20Link%20to%20the%20Past/plando/en)
+For more information on plando, you can reference the [general plando guide](/tutorial/Archipelago/plando/en) or the
+[A Link to the Past plando guide](/tutorial/A%20Link%20to%20the%20Past/plando/en).
## Trigger use
-Triggers may be defined in either the root or in the relevant game sections. Generally, The best place to do this is the
-bottom of the yaml for clear organization.
+Triggers may be defined in either the root or in the relevant game sections. Generally, the best place to do this is the
+bottom of the YAML for clear organization.
-- Triggers comprise the trigger section and then each trigger must have an `option_category`, `option_name`, and
- `option_result` from which it will react to and then an `options` section for the definition of what will happen.
-- `option_category` is the defining section from which the option is defined in.
+Each trigger consists of four parts:
+- `option_category` specifies the section which the triggering option is defined in.
- Example: `A Link to the Past`
- - This is the root category the option is located in. If the option you're triggering off of is in root then you
+ - This is the category the option is located in. If the option you're triggering off of is in root then you
would use `null`, otherwise this is the game for which you want this option trigger to activate.
-- `option_name` is the option setting from which the triggered choice is going to react to.
+- `option_name` specifies the name of the triggering option.
- Example: `shop_item_slots`
- - This can be any option from any category defined in the yaml file in either root or a game section.
-- `option_result` is the result of this option setting from which you would like to react.
+ - This can be any option from any category defined in the YAML file in either root or a game section.
+- `option_result` specifies the value of the option that activates this trigger.
- Example: `15`
- Each trigger must be used for exactly one option result. If you would like the same thing to occur with multiple
- results you would need multiple triggers for this.
-- `options` is where you define what will happen when this is detected. This can be something as simple as ensuring
+ results, you would need multiple triggers for this.
+- `options` is where you define what will happen when the trigger activates. This can be something as simple as ensuring
another option also gets selected or placing an item in a certain location. It is possible to have multiple things
happen in this section.
- Example:
@@ -47,10 +42,10 @@ bottom of the yaml for clear organization.
Rupees (300): 2
```
-This format must be:
+The general format is:
```yaml
- root option:
+ category:
option to change:
desired result
```
@@ -70,8 +65,8 @@ The above examples all together will end up looking like this:
Rupees(300): 2
```
-For this example if the generator happens to roll 15 shuffled in shop item slots for your game you'll be granted 600
-rupees at the beginning. These can also be used to change other options.
+For this example, if the generator happens to roll 15 shuffled in shop item slots for your game, you'll be granted 600
+rupees at the beginning. Triggers can also be used to change other options.
For example:
@@ -85,9 +80,9 @@ For example:
Inverted: true
```
-In this example if your world happens to roll SpecificKeycards then your game will also start in inverted.
+In this example, if your world happens to roll SpecificKeycards, then your game will also start in inverted.
-It is also possible to use imaginary names in options to trigger specific settings. You can use these made up names in
+It is also possible to use imaginary values in options to trigger specific settings. You can use these made-up values in
either your main options or to trigger from another trigger. Currently, this is the only way to trigger on "setting 1
AND setting 2".
@@ -97,33 +92,33 @@ For example:
triggers:
- option_category: Secret of Evermore
option_name: doggomizer
- option_result: pupdunk
- options:
- Secret of Evermore:
- difficulty:
- normal: 50
- pupdunk_hard: 25
- pupdunk_mystery: 25
- exp_modifier:
- 150: 50
- 200: 50
- - option_category: Secret of Evermore
- option_name: difficulty
- option_result: pupdunk_hard
- options:
- Secret of Evermore:
- fix_wings_glitch: false
- difficulty: hard
- - option_category: Secret of Evermore
- option_name: difficulty
- option_result: pupdunk_mystery
- options:
- Secret of Evermore:
- fix_wings_glitch: false
- difficulty: mystery
+ option_result: pupdunk
+ options:
+ Secret of Evermore:
+ difficulty:
+ normal: 50
+ pupdunk_hard: 25
+ pupdunk_mystery: 25
+ exp_modifier:
+ 150: 50
+ 200: 50
+ - option_category: Secret of Evermore
+ option_name: difficulty
+ option_result: pupdunk_hard
+ options:
+ Secret of Evermore:
+ fix_wings_glitch: false
+ difficulty: hard
+ - option_category: Secret of Evermore
+ option_name: difficulty
+ option_result: pupdunk_mystery
+ options:
+ Secret of Evermore:
+ fix_wings_glitch: false
+ difficulty: mystery
```
-In this example (thanks to @Black-Sliver) if the `pupdunk` option is rolled then the difficulty values will be rolled
+In this example (thanks to @Black-Sliver), if the `pupdunk` option is rolled, then the difficulty values will be rolled
again using the new options `normal`, `pupdunk_hard`, and `pupdunk_mystery`, and the exp modifier will be rerolled using
new weights for 150 and 200. This allows for two more triggers that will only be used for the new `pupdunk_hard`
and `pupdunk_mystery` options so that they will only be triggered on "pupdunk AND hard/mystery".
\ No newline at end of file
diff --git a/worlds/hk/__init__.py b/worlds/hk/__init__.py
index 1667ab81f7..9ed0c929bb 100644
--- a/worlds/hk/__init__.py
+++ b/worlds/hk/__init__.py
@@ -637,7 +637,7 @@ class HKItem(Item):
def __init__(self, name, advancement, code, type: str, player: int = None):
if name == "Mimic_Grub":
classification = ItemClassification.trap
- elif type in ("Grub", "DreamWarrior", "Root", "Egg"):
+ elif type in ("Grub", "DreamWarrior", "Root", "Egg", "Dreamer"):
classification = ItemClassification.progression_skip_balancing
elif type == "Charm" and name not in progression_charms:
classification = ItemClassification.progression_skip_balancing
diff --git a/worlds/hylics2/Exits.py b/worlds/hylics2/Exits.py
new file mode 100644
index 0000000000..99ebeba277
--- /dev/null
+++ b/worlds/hylics2/Exits.py
@@ -0,0 +1,94 @@
+from typing import List, Dict
+
+
+region_exit_table: Dict[int, List[str]] = {
+ 0: ["New Game"],
+
+ 1: ["To Waynehouse",
+ "To New Muldul",
+ "To Viewax",
+ "To TV Island",
+ "To Shield Facility",
+ "To Worm Pod",
+ "To Foglast",
+ "To Sage Labyrinth",
+ "To Hylemxylem"],
+
+ 2: ["To World",
+ "To Afterlife",],
+
+ 3: ["To Airship",
+ "To Waynehouse",
+ "To New Muldul",
+ "To Drill Castle",
+ "To Viewax",
+ "To Arcade Island",
+ "To TV Island",
+ "To Juice Ranch",
+ "To Shield Facility",
+ "To Worm Pod",
+ "To Foglast",
+ "To Sage Airship",
+ "To Hylemxylem"],
+
+ 4: ["To World",
+ "To Afterlife",
+ "To New Muldul Vault"],
+
+ 5: ["To New Muldul"],
+
+ 6: ["To World",
+ "To Afterlife"],
+
+ 7: ["To World"],
+
+ 8: ["To World"],
+
+ 9: ["To World",
+ "To Afterlife"],
+
+ 10: ["To World"],
+
+ 11: ["To World",
+ "To Afterlife",
+ "To Worm Pod"],
+
+ 12: ["To Shield Facility",
+ "To Afterlife"],
+
+ 13: ["To World",
+ "To Afterlife"],
+
+ 14: ["To World",
+ "To Sage Labyrinth"],
+
+ 15: ["To Drill Castle",
+ "To Afterlife"],
+
+ 16: ["To World"],
+
+ 17: ["To World",
+ "To Afterlife"]
+}
+
+
+exit_lookup_table: Dict[str, int] = {
+ "New Game": 2,
+ "To Waynehouse": 2,
+ "To Afterlife": 1,
+ "To World": 3,
+ "To New Muldul": 4,
+ "To New Muldul Vault": 5,
+ "To Viewax": 6,
+ "To Airship": 7,
+ "To Arcade Island": 8,
+ "To TV Island": 9,
+ "To Juice Ranch": 10,
+ "To Shield Facility": 11,
+ "To Worm Pod": 12,
+ "To Foglast": 13,
+ "To Drill Castle": 14,
+ "To Sage Labyrinth": 15,
+ "To Sage Airship": 16,
+ "To Hylemxylem": 17
+}
\ No newline at end of file
diff --git a/worlds/hylics2/Items.py b/worlds/hylics2/Items.py
new file mode 100644
index 0000000000..9e36b3c393
--- /dev/null
+++ b/worlds/hylics2/Items.py
@@ -0,0 +1,243 @@
+from BaseClasses import ItemClassification
+from typing import TypedDict, Dict
+
+
+class ItemDict(TypedDict):
+ classification: ItemClassification
+ count: int
+ name: str
+
+
+item_table: Dict[int, ItemDict] = {
+ # Things
+ 200622: {'classification': ItemClassification.filler,
+ 'count': 1,
+ 'name': 'DUBIOUS BERRY'},
+ 200623: {'classification': ItemClassification.filler,
+ 'count': 11,
+ 'name': 'BURRITO'},
+ 200624: {'classification': ItemClassification.filler,
+ 'count': 1,
+ 'name': 'COFFEE'},
+ 200625: {'classification': ItemClassification.filler,
+ 'count': 6,
+ 'name': 'SOUL SPONGE'},
+ 200626: {'classification': ItemClassification.useful,
+ 'count': 6,
+ 'name': 'MUSCLE APPLIQUE'},
+ 200627: {'classification': ItemClassification.filler,
+ 'count': 1,
+ 'name': 'POOLWINE'},
+ 200628: {'classification': ItemClassification.filler,
+ 'count': 3,
+ 'name': 'CUPCAKE'},
+ 200629: {'classification': ItemClassification.filler,
+ 'count': 3,
+ 'name': 'COOKIE'},
+ 200630: {'classification': ItemClassification.progression,
+ 'count': 1,
+ 'name': 'HOUSE KEY'},
+ 200631: {'classification': ItemClassification.filler,
+ 'count': 2,
+ 'name': 'MEAT'},
+ 200632: {'classification': ItemClassification.progression,
+ 'count': 1,
+ 'name': 'PNEUMATOPHORE'},
+ 200633: {'classification': ItemClassification.progression,
+ 'count': 1,
+ 'name': 'CAVE KEY'},
+ 200634: {'classification': ItemClassification.filler,
+ 'count': 6,
+ 'name': 'JUICE'},
+ 200635: {'classification': ItemClassification.progression,
+ 'count': 1,
+ 'name': 'DOCK KEY'},
+ 200636: {'classification': ItemClassification.filler,
+ 'count': 14,
+ 'name': 'BANANA'},
+ 200637: {'classification': ItemClassification.progression,
+ 'count': 3,
+ 'name': 'PAPER CUP'},
+ 200638: {'classification': ItemClassification.progression,
+ 'count': 1,
+ 'name': 'JAIL KEY'},
+ 200639: {'classification': ItemClassification.progression,
+ 'count': 1,
+ 'name': 'PADDLE'},
+ 200640: {'classification': ItemClassification.progression,
+ 'count': 1,
+ 'name': 'WORM ROOM KEY'},
+ 200641: {'classification': ItemClassification.progression,
+ 'count': 1,
+ 'name': 'BRIDGE KEY'},
+ 200642: {'classification': ItemClassification.filler,
+ 'count': 2,
+ 'name': 'STEM CELL'},
+ 200643: {'classification': ItemClassification.progression,
+ 'count': 1,
+ 'name': 'UPPER CHAMBER KEY'},
+ 200644: {'classification': ItemClassification.progression,
+ 'count': 1,
+ 'name': 'VESSEL ROOM KEY'},
+ 200645: {'classification': ItemClassification.filler,
+ 'count': 3,
+ 'name': 'CLOUD GERM'},
+ 200646: {'classification': ItemClassification.progression,
+ 'count': 1,
+ 'name': 'SKULL BOMB'},
+ 200647: {'classification': ItemClassification.progression,
+ 'count': 1,
+ 'name': 'TOWER KEY'},
+ 200648: {'classification': ItemClassification.progression,
+ 'count': 1,
+ 'name': 'DEEP KEY'},
+ 200649: {'classification': ItemClassification.filler,
+ 'count': 1,
+ 'name': 'MULTI-COFFEE'},
+ 200650: {'classification': ItemClassification.filler,
+ 'count': 4,
+ 'name': 'MULTI-JUICE'},
+ 200651: {'classification': ItemClassification.filler,
+ 'count': 1,
+ 'name': 'MULTI STEM CELL'},
+ 200652: {'classification': ItemClassification.filler,
+ 'count': 6,
+ 'name': 'MULTI SOUL SPONGE'},
+ #200653: {'classification': ItemClassification.filler,
+ # 'count': 1,
+ # 'name': 'ANTENNA'},
+ 200654: {'classification': ItemClassification.progression,
+ 'count': 1,
+ 'name': 'UPPER HOUSE KEY'},
+ 200655: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'BOTTOMLESS JUICE'},
+ 200656: {'classification': ItemClassification.progression,
+ 'count': 3,
+ 'name': 'SAGE TOKEN'},
+ 200657: {'classification': ItemClassification.progression,
+ 'count': 1,
+ 'name': 'CLICKER'},
+
+ # Garbs > Gloves
+ 200658: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'CURSED GLOVES'},
+ 200659: {'classification': ItemClassification.useful,
+ 'count': 5,
+ 'name': 'LONG GLOVES'},
+ 200660: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'BRAIN DIGITS'},
+ 200661: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'MATERIEL MITTS'},
+ 200662: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'PLEATHER GAGE'},
+ 200663: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'PEPTIDE BODKINS'},
+ 200664: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'TELESCOPIC SLEEVE'},
+ 200665: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'TENDRIL HAND'},
+ 200666: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'PSYCHIC KNUCKLE'},
+ 200667: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'SINGLE GLOVE'},
+
+ # Garbs > Accessories
+ 200668: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'FADED PONCHO'},
+ 200669: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'JUMPSUIT'},
+ 200670: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'BOOTS'},
+ 200671: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'CONVERTER WORM'},
+ 200672: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'COFFEE CHIP'},
+ 200673: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'RANCHER PONCHO'},
+ 200674: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'ORGAN FORT'},
+ 200675: {'classification': ItemClassification.useful,
+ 'count': 2,
+ 'name': 'LOOPED DOME'},
+ 200676: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'DUCTILE HABIT'},
+ 200677: {'classification': ItemClassification.useful,
+ 'count': 2,
+ 'name': 'TARP'},
+
+ # Bones
+ 200686: {'classification': ItemClassification.filler,
+ 'count': 1,
+ 'name': '100 Bones'},
+ 200687: {'classification': ItemClassification.filler,
+ 'count': 1,
+ 'name': '50 Bones'}
+}
+
+
+gesture_item_table: Dict[int, ItemDict] = {
+ 200678: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'POROMER BLEB'},
+ 200679: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'SOUL CRISPER'},
+ 200680: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'TIME SIGIL'},
+ 200681: {'classification': ItemClassification.progression,
+ 'count': 1,
+ 'name': 'CHARGE UP'},
+ 200682: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'FATE SANDBOX'},
+ 200683: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'TELEDENUDATE'},
+ 200684: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'LINK MOLLUSC'},
+ 200685: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'BOMBO - GENESIS'},
+ 200688: {'classification': ItemClassification.useful,
+ 'count': 1,
+ 'name': 'NEMATODE INTERFACE'},
+}
+
+
+party_item_table: Dict[int, ItemDict] = {
+ 200689: {'classification': ItemClassification.progression,
+ 'count': 1,
+ 'name': 'Pongorma'},
+ 200690: {'classification': ItemClassification.progression,
+ 'count': 1,
+ 'name': 'Dedusmuln'},
+ 200691: {'classification': ItemClassification.progression,
+ 'count': 1,
+ 'name': 'Somsnosa'}
+}
+
+medallion_item_table: Dict[int, ItemDict] = {
+ 200692: {'classification': ItemClassification.filler,
+ 'count': 30,
+ 'name': '10 Bones'}
+}
\ No newline at end of file
diff --git a/worlds/hylics2/Locations.py b/worlds/hylics2/Locations.py
new file mode 100644
index 0000000000..0f8bbb9b99
--- /dev/null
+++ b/worlds/hylics2/Locations.py
@@ -0,0 +1,383 @@
+from typing import Dict, TypedDict
+
+
+class LocationDict(TypedDict, total=False):
+ name: str
+ region: int
+
+
+location_table: Dict[int, LocationDict] = {
+ # Waynehouse
+ 200622: {'name': "Waynehouse: Toilet",
+ 'region': 2},
+ 200623: {'name': "Waynehouse: Basement Pot 1",
+ 'region': 2},
+ 200624: {'name': "Waynehouse: Basement Pot 2",
+ 'region': 2},
+ 200625: {'name': "Waynehouse: Basement Pot 3",
+ 'region': 2},
+ 200626: {'name': "Waynehouse: Sarcophagus",
+ 'region': 2},
+
+ # Afterlife
+ 200628: {'name': "Afterlife: Mangled Wayne",
+ 'region': 1},
+ 200629: {'name': "Afterlife: Jar near Mangled Wayne",
+ 'region': 1},
+ 200630: {'name': "Afterlife: Jar under Pool",
+ 'region': 1},
+
+ # New Muldul
+ 200632: {'name': "New Muldul: Shop Ceiling Pot 1",
+ 'region': 4},
+ 200633: {'name': "New Muldul: Shop Ceiling Pot 2",
+ 'region': 4},
+ 200634: {'name': "New Muldul: Flag Banana",
+ 'region': 4},
+ 200635: {'name': "New Muldul: Pot near Vault",
+ 'region': 4},
+ 200636: {'name': "New Muldul: Underground Pot",
+ 'region': 4},
+ 200637: {'name': "New Muldul: Underground Chest",
+ 'region': 4},
+ 200638: {'name': "New Muldul: Juice Trade",
+ 'region': 4},
+ 200639: {'name': "New Muldul: Basement Suitcase",
+ 'region': 4},
+ 200640: {'name': "New Muldul: Upper House Chest 1",
+ 'region': 4},
+ 200641: {'name': "New Muldul: Upper House Chest 2",
+ 'region': 4},
+
+ # New Muldul Vault
+ 200643: {'name': "New Muldul: Talk to Pongorma",
+ 'region': 4},
+ 200645: {'name': "New Muldul: Rescued Blerol 1",
+ 'region': 4},
+ 200646: {'name': "New Muldul: Rescued Blerol 2",
+ 'region': 4},
+ 200647: {'name': "New Muldul: Vault Left Chest",
+ 'region': 5},
+ 200648: {'name': "New Muldul: Vault Right Chest",
+ 'region': 5},
+ 200649: {'name': "New Muldul: Vault Bomb",
+ 'region': 5},
+
+ # Viewax's Edifice
+ 200650: {'name': "Viewax's Edifice: Fountain Banana",
+ 'region': 6},
+ 200651: {'name': "Viewax's Edifice: Dedusmuln's Suitcase",
+ 'region': 6},
+ 200652: {'name': "Viewax's Edifice: Dedusmuln's Campfire",
+ 'region': 6},
+ 200653: {'name': "Viewax's Edifice: Talk to Dedusmuln",
+ 'region': 6},
+ 200655: {'name': "Viewax's Edifice: Canopic Jar",
+ 'region': 6},
+ 200656: {'name': "Viewax's Edifice: Cave Sarcophagus",
+ 'region': 6},
+ 200657: {'name': "Viewax's Edifice: Shielded Key",
+ 'region': 6},
+ 200658: {'name': "Viewax's Edifice: Tower Pot",
+ 'region': 6},
+ 200659: {'name': "Viewax's Edifice: Tower Jar",
+ 'region': 6},
+ 200660: {'name': "Viewax's Edifice: Tower Chest",
+ 'region': 6},
+ 200661: {'name': "Viewax's Edifice: Sage Fridge",
+ 'region': 6},
+ 200662: {'name': "Viewax's Edifice: Sage Item 1",
+ 'region': 6},
+ 200663: {'name': "Viewax's Edifice: Sage Item 2",
+ 'region': 6},
+ 200664: {'name': "Viewax's Edifice: Viewax Pot",
+ 'region': 6},
+ 200665: {'name': "Viewax's Edifice: Defeat Viewax",
+ 'region': 6},
+
+ # Viewax Arcade Minigame
+ 200667: {'name': "Arcade 1: Key",
+ 'region': 6},
+ 200668: {'name': "Arcade 1: Coin Dash",
+ 'region': 6},
+ 200669: {'name': "Arcade 1: Burrito Alcove 1",
+ 'region': 6},
+ 200670: {'name': "Arcade 1: Burrito Alcove 2",
+ 'region': 6},
+ 200671: {'name': "Arcade 1: Behind Spikes Banana",
+ 'region': 6},
+ 200672: {'name': "Arcade 1: Pyramid Banana",
+ 'region': 6},
+ 200673: {'name': "Arcade 1: Moving Platforms Muscle Applique",
+ 'region': 6},
+ 200674: {'name': "Arcade 1: Bed Banana",
+ 'region': 6},
+
+ # Airship
+ 200675: {'name': "Airship: Talk to Somsnosa",
+ 'region': 7},
+
+ # Arcade Island
+ 200676: {'name': "Arcade Island: Shielded Key",
+ 'region': 8},
+ 200677: {'name': "Arcade 2: Flying Machine Banana",
+ 'region': 8},
+ 200678: {'name': "Arcade 2: Paper Cup Detour",
+ 'region': 8},
+ 200679: {'name': "Arcade 2: Peak Muscle Applique",
+ 'region': 8},
+ 200680: {'name': "Arcade 2: Double Banana 1",
+ 'region': 8},
+ 200681: {'name': "Arcade 2: Double Banana 2",
+ 'region': 8},
+ 200682: {'name': "Arcade 2: Cave Burrito",
+ 'region': 8},
+
+ # Juice Ranch
+ 200684: {'name': "Juice Ranch: Juice 1",
+ 'region': 10},
+ 200685: {'name': "Juice Ranch: Juice 2",
+ 'region': 10},
+ 200686: {'name': "Juice Ranch: Juice 3",
+ 'region': 10},
+ 200687: {'name': "Juice Ranch: Ledge Rancher",
+ 'region': 10},
+ 200688: {'name': "Juice Ranch: Battle with Somsnosa",
+ 'region': 10},
+ 200690: {'name': "Juice Ranch: Fridge",
+ 'region': 10},
+
+ # Worm Pod
+ 200692: {'name': "Worm Pod: Key",
+ 'region': 12},
+
+ # Foglast
+ 200693: {'name': "Foglast: West Sarcophagus",
+ 'region': 13},
+ 200694: {'name': "Foglast: Underground Sarcophagus",
+ 'region': 13},
+ 200695: {'name': "Foglast: Shielded Key",
+ 'region': 13},
+ 200696: {'name': "Foglast: Buy Clicker",
+ 'region': 13},
+ 200698: {'name': "Foglast: Shielded Chest",
+ 'region': 13},
+ 200699: {'name': "Foglast: Cave Fridge",
+ 'region': 13},
+ 200700: {'name': "Foglast: Roof Sarcophagus",
+ 'region': 13},
+ 200701: {'name': "Foglast: Under Lair Sarcophagus 1",
+ 'region': 13},
+ 200702: {'name': "Foglast: Under Lair Sarcophagus 2",
+ 'region': 13},
+ 200703: {'name': "Foglast: Under Lair Sarcophagus 3",
+ 'region': 13},
+ 200704: {'name': "Foglast: Sage Sarcophagus",
+ 'region': 13},
+ 200705: {'name': "Foglast: Sage Item 1",
+ 'region': 13},
+ 200706: {'name': "Foglast: Sage Item 2",
+ 'region': 13},
+
+ # Drill Castle
+ 200707: {'name': "Drill Castle: Ledge Banana",
+ 'region': 14},
+ 200708: {'name': "Drill Castle: Island Banana",
+ 'region': 14},
+ 200709: {'name': "Drill Castle: Island Pot",
+ 'region': 14},
+ 200710: {'name': "Drill Castle: Cave Sarcophagus",
+ 'region': 14},
+ 200711: {'name': "Drill Castle: Roof Banana",
+ 'region': 14},
+
+ # Sage Labyrinth
+ 200713: {'name': "Sage Labyrinth: 1F Chest Near Fountain",
+ 'region': 15},
+ 200714: {'name': "Sage Labyrinth: 1F Hidden Sarcophagus",
+ 'region': 15},
+ 200715: {'name': "Sage Labyrinth: 1F Four Statues Chest 1",
+ 'region': 15},
+ 200716: {'name': "Sage Labyrinth: 1F Four Statues Chest 2",
+ 'region': 15},
+ 200717: {'name': "Sage Labyrinth: B1 Double Chest 1",
+ 'region': 15},
+ 200718: {'name': "Sage Labyrinth: B1 Double Chest 2",
+ 'region': 15},
+ 200719: {'name': "Sage Labyrinth: B1 Single Chest",
+ 'region': 15},
+ 200720: {'name': "Sage Labyrinth: B1 Enemy Chest",
+ 'region': 15},
+ 200721: {'name': "Sage Labyrinth: B1 Hidden Sarcophagus",
+ 'region': 15},
+ 200722: {'name': "Sage Labyrinth: B1 Hole Chest",
+ 'region': 15},
+ 200723: {'name': "Sage Labyrinth: B2 Hidden Sarcophagus 1",
+ 'region': 15},
+ 200724: {'name': "Sage Labyrinth: B2 Hidden Sarcophagus 2",
+ 'region': 15},
+ 200754: {'name': "Sage Labyrinth: 2F Sarcophagus",
+ 'region': 15},
+ 200725: {'name': "Sage Labyrinth: Motor Hunter Sarcophagus",
+ 'region': 15},
+ 200726: {'name': "Sage Labyrinth: Sage Item 1",
+ 'region': 15},
+ 200727: {'name': "Sage Labyrinth: Sage Item 2",
+ 'region': 15},
+ 200728: {'name': "Sage Labyrinth: Sage Left Arm",
+ 'region': 15},
+ 200729: {'name': "Sage Labyrinth: Sage Right Arm",
+ 'region': 15},
+ 200730: {'name': "Sage Labyrinth: Sage Left Leg",
+ 'region': 15},
+ 200731: {'name': "Sage Labyrinth: Sage Right Leg",
+ 'region': 15},
+
+ # Sage Airship
+ 200732: {'name': "Sage Airship: Bottom Level Pot",
+ 'region': 16},
+ 200733: {'name': "Sage Airship: Flesh Pot",
+ 'region': 16},
+ 200734: {'name': "Sage Airship: Top Jar",
+ 'region': 16},
+
+ # Hylemxylem
+ 200736: {'name': "Hylemxylem: Jar",
+ 'region': 17},
+ 200737: {'name': "Hylemxylem: Lower Reservoir Key",
+ 'region': 17},
+ 200738: {'name': "Hylemxylem: Fountain Banana",
+ 'region': 17},
+ 200739: {'name': "Hylemxylem: East Island Banana",
+ 'region': 17},
+ 200740: {'name': "Hylemxylem: East Island Chest",
+ 'region': 17},
+ 200741: {'name': "Hylemxylem: Upper Chamber Banana",
+ 'region': 17},
+ 200742: {'name': "Hylemxylem: Across Upper Reservoir Chest",
+ 'region': 17},
+ 200743: {'name': "Hylemxylem: Drained Lower Reservoir Chest",
+ 'region': 17},
+ 200744: {'name': "Hylemxylem: Drained Lower Reservoir Burrito 1",
+ 'region': 17},
+ 200745: {'name': "Hylemxylem: Drained Lower Reservoir Burrito 2",
+ 'region': 17},
+ 200746: {'name': "Hylemxylem: Lower Reservoir Hole Pot 1",
+ 'region': 17},
+ 200747: {'name': "Hylemxylem: Lower Reservoir Hole Pot 2",
+ 'region': 17},
+ 200748: {'name': "Hylemxylem: Lower Reservoir Hole Pot 3",
+ 'region': 17},
+ 200749: {'name': "Hylemxylem: Lower Reservoir Hole Sarcophagus",
+ 'region': 17},
+ 200750: {'name': "Hylemxylem: Drained Upper Reservoir Burrito 1",
+ 'region': 17},
+ 200751: {'name': "Hylemxylem: Drained Upper Reservoir Burrito 2",
+ 'region': 17},
+ 200752: {'name': "Hylemxylem: Drained Upper Reservoir Burrito 3",
+ 'region': 17},
+ 200753: {'name': "Hylemxylem: Upper Reservoir Hole Key",
+ 'region': 17}
+}
+
+
+tv_location_table: Dict[int, LocationDict] = {
+ 200627: {'name': "Waynehouse: TV",
+ 'region': 2},
+ 200631: {'name': "Afterlife: TV",
+ 'region': 1},
+ 200642: {'name': "New Muldul: TV",
+ 'region': 4},
+ 200666: {'name': "Viewax's Edifice: TV",
+ 'region': 6},
+ 200683: {'name': "TV Island: TV",
+ 'region': 9},
+ 200691: {'name': "Juice Ranch: TV",
+ 'region': 10},
+ 200697: {'name': "Foglast: TV",
+ 'region': 13},
+ 200712: {'name': "Drill Castle: TV",
+ 'region': 14},
+ 200735: {'name': "Sage Airship: TV",
+ 'region': 16}
+}
+
+
+party_location_table: Dict[int, LocationDict] = {
+ 200644: {'name': "New Muldul: Pongorma Joins",
+ 'region': 4},
+ 200654: {'name': "Viewax's Edifice: Dedusmuln Joins",
+ 'region': 6},
+ 200689: {'name': "Juice Ranch: Somsnosa Joins",
+ 'region': 10}
+}
+
+
+medallion_location_table: Dict[int, LocationDict] = {
+ 200755: {'name': "New Muldul: Upper House Medallion",
+ 'region': 4},
+
+ 200756: {'name': "New Muldul: Vault Rear Left Medallion",
+ 'region': 5},
+ 200757: {'name': "New Muldul: Vault Rear Right Medallion",
+ 'region': 5},
+ 200758: {'name': "New Muldul: Vault Center Medallion",
+ 'region': 5},
+ 200759: {'name': "New Muldul: Vault Front Left Medallion",
+ 'region': 5},
+ 200760: {'name': "New Muldul: Vault Front Right Medallion",
+ 'region': 5},
+
+ 200761: {'name': "Viewax's Edifice: Fort Wall Medallion",
+ 'region': 6},
+ 200762: {'name': "Viewax's Edifice: Jar Medallion",
+ 'region': 6},
+ 200763: {'name': "Viewax's Edifice: Sage Chair Medallion",
+ 'region': 6},
+ 200764: {'name': "Arcade 1: Lonely Medallion",
+ 'region': 6},
+ 200765: {'name': "Arcade 1: Alcove Medallion",
+ 'region': 6},
+ 200766: {'name': "Arcade 1: Lava Medallion",
+ 'region': 6},
+
+ 200767: {'name': "Arcade 2: Flying Machine Medallion",
+ 'region': 8},
+ 200768: {'name': "Arcade 2: Guarded Medallion",
+ 'region': 8},
+ 200769: {'name': "Arcade 2: Spinning Medallion",
+ 'region': 8},
+ 200770: {'name': "Arcade 2: Hook Medallion",
+ 'region': 8},
+ 200771: {'name': "Arcade 2: Flag Medallion",
+ 'region': 8},
+
+ 200772: {'name': "Foglast: Under Lair Medallion",
+ 'region': 13},
+ 200773: {'name': "Foglast: Mid-Air Medallion",
+ 'region': 13},
+ 200774: {'name': "Foglast: Top of Tower Medallion",
+ 'region': 13},
+
+ 200775: {'name': "Sage Airship: Walkway Medallion",
+ 'region': 16},
+ 200776: {'name': "Sage Airship: Flesh Medallion",
+ 'region': 16},
+ 200777: {'name': "Sage Airship: Top of Ship Medallion",
+ 'region': 16},
+ 200778: {'name': "Sage Airship: Hidden Medallion 1",
+ 'region': 16},
+ 200779: {'name': "Sage Airship: Hidden Medallion 2",
+ 'region': 16},
+ 200780: {'name': "Sage Airship: Hidden Medallion 3",
+ 'region': 16},
+
+ 200781: {'name': "Hylemxylem: Lower Reservoir Medallion",
+ 'region': 17},
+ 200782: {'name': "Hylemxylem: Lower Reservoir Hole Medallion",
+ 'region': 17},
+ 200783: {'name': "Hylemxylem: Drain Switch Medallion",
+ 'region': 17},
+ 200784: {'name': "Hylemxylem: Warpo Medallion",
+ 'region': 17}
+}
\ No newline at end of file
diff --git a/worlds/hylics2/Options.py b/worlds/hylics2/Options.py
new file mode 100644
index 0000000000..ac57e666a1
--- /dev/null
+++ b/worlds/hylics2/Options.py
@@ -0,0 +1,41 @@
+from Options import Choice, Toggle, DefaultOnToggle, DeathLink
+
+class PartyShuffle(Toggle):
+ """Shuffles party members into the pool.
+ Note that enabling this can potentially increase both the difficulty and length of a run."""
+ display_name = "Shuffle Party Members"
+
+class GestureShuffle(Choice):
+ """Choose where gestures will appear in the item pool."""
+ display_name = "Shuffle Gestures"
+ option_anywhere = 0
+ option_tvs_only = 1
+ option_default_locations = 2
+ default = 0
+
+class MedallionShuffle(Toggle):
+ """Shuffles red medallions into the pool."""
+ display_name = "Shuffle Red Medallions"
+
+class RandomStart(Toggle):
+ """Start the randomizer in 1 of 4 positions.
+ (Waynehouse, Viewax's Edifice, TV Island, Shield Facility)"""
+ display_name = "Randomize Start Location"
+
+class ExtraLogic(DefaultOnToggle):
+ """Include some extra items in logic (CHARGE UP, 1x PAPER CUP) to prevent the game from becoming too difficult."""
+ display_name = "Extra Items in Logic"
+
+class Hylics2DeathLink(DeathLink):
+ """When you die, everyone dies. The reverse is also true.
+ Note that this also includes death by using the PERISH gesture.
+ Can be toggled via in-game console command "/deathlink"."""
+
+hylics2_options = {
+ "party_shuffle": PartyShuffle,
+ "gesture_shuffle" : GestureShuffle,
+ "medallion_shuffle" : MedallionShuffle,
+ "random_start" : RandomStart,
+ "extra_items_in_logic": ExtraLogic,
+ "death_link": Hylics2DeathLink
+}
\ No newline at end of file
diff --git a/worlds/hylics2/Rules.py b/worlds/hylics2/Rules.py
new file mode 100644
index 0000000000..be38e102ea
--- /dev/null
+++ b/worlds/hylics2/Rules.py
@@ -0,0 +1,433 @@
+from worlds.generic.Rules import add_rule
+from ..AutoWorld import LogicMixin
+
+
+class Hylics2Logic(LogicMixin):
+
+ def _hylics2_can_air_dash(self, player):
+ return self.has("PNEUMATOPHORE", player)
+
+ def _hylics2_has_airship(self, player):
+ return self.has("DOCK KEY", player)
+
+ def _hylics2_has_jail_key(self, player):
+ return self.has("JAIL KEY", player)
+
+ def _hylics2_has_paddle(self, player):
+ return self.has("PADDLE", player)
+
+ def _hylics2_has_worm_room_key(self, player):
+ return self.has("WORM ROOM KEY", player)
+
+ def _hylics2_has_bridge_key(self, player):
+ return self.has("BRIDGE KEY", player)
+
+ def _hylics2_has_upper_chamber_key(self, player):
+ return self.has("UPPER CHAMBER KEY", player)
+
+ def _hylics2_has_vessel_room_key(self, player):
+ return self.has("VESSEL ROOM KEY", player)
+
+ def _hylics2_has_house_key(self, player):
+ return self.has("HOUSE KEY", player)
+
+ def _hylics2_has_cave_key(self, player):
+ return self.has("CAVE KEY", player)
+
+ def _hylics2_has_skull_bomb(self, player):
+ return self.has("SKULL BOMB", player)
+
+ def _hylics2_has_tower_key(self, player):
+ return self.has("TOWER KEY", player)
+
+ def _hylics2_has_deep_key(self, player):
+ return self.has("DEEP KEY", player)
+
+ def _hylics2_has_upper_house_key(self, player):
+ return self.has("UPPER HOUSE KEY", player)
+
+ def _hylics2_has_clicker(self, player):
+ return self.has("CLICKER", player)
+
+ def _hylics2_has_tokens(self, player):
+ return self.has("SAGE TOKEN", player, 3)
+
+ def _hylics2_has_charge_up(self, player):
+ return self.has("CHARGE UP", player)
+
+ def _hylics2_has_cup(self, player):
+ return self.has("PAPER CUP", player, 1)
+
+ def _hylics2_has_1_member(self, player):
+ return self.has("Pongorma", player) or self.has("Dedusmuln", player) or self.has("Somsnosa", player)
+
+ def _hylics2_has_2_members(self, player):
+ return (self.has("Pongorma", player) and self.has("Dedusmuln", player)) or\
+ (self.has("Pongorma", player) and self.has("Somsnosa", player)) or\
+ (self.has("Dedusmuln", player) and self.has("Somsnosa", player))
+
+ def _hylics2_has_3_members(self, player):
+ return self.has("Pongorma", player) and self.has("Dedusmuln", player) and self.has("Somsnosa", player)
+
+ def _hylics2_enter_arcade2(self, player):
+ return self._hylics2_can_air_dash(player) and self._hylics2_has_airship(player)
+
+ def _hylics2_enter_wormpod(self, player):
+ return self._hylics2_has_airship(player) and self._hylics2_has_worm_room_key(player) and\
+ self._hylics2_has_paddle(player)
+
+ def _hylics2_enter_sageship(self, player):
+ return self._hylics2_has_skull_bomb(player) and self._hylics2_has_airship(player) and\
+ self._hylics2_has_paddle(player)
+
+ def _hylics2_enter_foglast(self, player):
+ return self._hylics2_enter_wormpod(player)
+
+ def _hylics2_enter_hylemxylem(self, player):
+ return self._hylics2_can_air_dash(player) and self._hylics2_enter_wormpod(player) and\
+ self._hylics2_has_bridge_key(player)
+
+
+def set_rules(hylics2world):
+ world = hylics2world.world
+ player = hylics2world.player
+
+ # Afterlife
+ add_rule(world.get_location("Afterlife: TV", player),
+ lambda state: state._hylics2_has_cave_key(player))
+
+ # New Muldul
+ add_rule(world.get_location("New Muldul: Underground Chest", player),
+ lambda state: state._hylics2_can_air_dash(player))
+ add_rule(world.get_location("New Muldul: TV", player),
+ lambda state: state._hylics2_has_house_key(player))
+ add_rule(world.get_location("New Muldul: Upper House Chest 1", player),
+ lambda state: state._hylics2_has_upper_house_key(player))
+ add_rule(world.get_location("New Muldul: Upper House Chest 2", player),
+ lambda state: state._hylics2_has_upper_house_key(player))
+
+ # New Muldul Vault
+ add_rule(world.get_location("New Muldul: Rescued Blerol 1", player),
+ lambda state: (state._hylics2_can_air_dash(player) or state._hylics2_has_airship(player)) and\
+ ((state._hylics2_has_jail_key(player) and state._hylics2_has_paddle(player)) or\
+ (state._hylics2_has_bridge_key(player) and state._hylics2_has_worm_room_key(player))))
+ add_rule(world.get_location("New Muldul: Rescued Blerol 2", player),
+ lambda state: (state._hylics2_can_air_dash(player) or state._hylics2_has_airship(player)) and\
+ ((state._hylics2_has_jail_key(player) and state._hylics2_has_paddle(player)) or\
+ (state._hylics2_has_bridge_key(player) and state._hylics2_has_worm_room_key(player))))
+ add_rule(world.get_location("New Muldul: Vault Left Chest", player),
+ lambda state: state._hylics2_enter_foglast(player) and state._hylics2_has_bridge_key(player))
+ add_rule(world.get_location("New Muldul: Vault Right Chest", player),
+ lambda state: state._hylics2_enter_foglast(player) and state._hylics2_has_bridge_key(player))
+ add_rule(world.get_location("New Muldul: Vault Bomb", player),
+ lambda state: state._hylics2_enter_foglast(player) and state._hylics2_has_bridge_key(player))
+
+ # Viewax's Edifice
+ add_rule(world.get_location("Viewax's Edifice: Canopic Jar", player),
+ lambda state: state._hylics2_has_paddle(player))
+ add_rule(world.get_location("Viewax's Edifice: Cave Sarcophagus", player),
+ lambda state: state._hylics2_has_paddle(player))
+ add_rule(world.get_location("Viewax's Edifice: Shielded Key", player),
+ lambda state: state._hylics2_has_paddle(player))
+ add_rule(world.get_location("Viewax's Edifice: Shielded Key", player),
+ lambda state: state._hylics2_has_paddle(player))
+ add_rule(world.get_location("Viewax's Edifice: Tower Pot", player),
+ lambda state: state._hylics2_has_paddle(player))
+ add_rule(world.get_location("Viewax's Edifice: Tower Jar", player),
+ lambda state: state._hylics2_has_paddle(player))
+ add_rule(world.get_location("Viewax's Edifice: Tower Chest", player),
+ lambda state: state._hylics2_has_paddle(player) and state._hylics2_has_tower_key(player))
+ add_rule(world.get_location("Viewax's Edifice: Viewax Pot", player),
+ lambda state: state._hylics2_has_paddle(player))
+ add_rule(world.get_location("Viewax's Edifice: Defeat Viewax", player),
+ lambda state: state._hylics2_has_paddle(player))
+ add_rule(world.get_location("Viewax's Edifice: TV", player),
+ lambda state: state._hylics2_has_paddle(player) and state._hylics2_has_jail_key(player))
+ add_rule(world.get_location("Viewax's Edifice: Sage Fridge", player),
+ lambda state: state._hylics2_can_air_dash(player))
+ add_rule(world.get_location("Viewax's Edifice: Sage Item 1", player),
+ lambda state: state._hylics2_can_air_dash(player))
+ add_rule(world.get_location("Viewax's Edifice: Sage Item 2", player),
+ lambda state: state._hylics2_can_air_dash(player))
+
+ # Arcade 1
+ add_rule(world.get_location("Arcade 1: Key", player),
+ lambda state: state._hylics2_has_paddle(player))
+ add_rule(world.get_location("Arcade 1: Coin Dash", player),
+ lambda state: state._hylics2_has_paddle(player))
+ add_rule(world.get_location("Arcade 1: Burrito Alcove 1", player),
+ lambda state: state._hylics2_has_paddle(player))
+ add_rule(world.get_location("Arcade 1: Burrito Alcove 2", player),
+ lambda state: state._hylics2_has_paddle(player))
+ add_rule(world.get_location("Arcade 1: Behind Spikes Banana", player),
+ lambda state: state._hylics2_has_paddle(player))
+ add_rule(world.get_location("Arcade 1: Pyramid Banana", player),
+ lambda state: state._hylics2_has_paddle(player))
+ add_rule(world.get_location("Arcade 1: Moving Platforms Muscle Applique", player),
+ lambda state: state._hylics2_has_paddle(player))
+ add_rule(world.get_location("Arcade 1: Bed Banana", player),
+ lambda state: state._hylics2_has_paddle(player))
+
+ # Airship
+ add_rule(world.get_location("Airship: Talk to Somsnosa", player),
+ lambda state: state._hylics2_has_worm_room_key(player))
+
+ # Foglast
+ add_rule(world.get_location("Foglast: Underground Sarcophagus", player),
+ lambda state: state._hylics2_can_air_dash(player))
+ add_rule(world.get_location("Foglast: Shielded Key", player),
+ lambda state: state._hylics2_can_air_dash(player))
+ add_rule(world.get_location("Foglast: TV", player),
+ lambda state: state._hylics2_can_air_dash(player) and state._hylics2_has_clicker(player))
+ add_rule(world.get_location("Foglast: Buy Clicker", player),
+ lambda state: state._hylics2_can_air_dash(player))
+ add_rule(world.get_location("Foglast: Shielded Chest", player),
+ lambda state: state._hylics2_can_air_dash(player))
+ add_rule(world.get_location("Foglast: Cave Fridge", player),
+ lambda state: state._hylics2_can_air_dash(player))
+ add_rule(world.get_location("Foglast: Roof Sarcophagus", player),
+ lambda state: state._hylics2_can_air_dash(player) and state._hylics2_has_bridge_key(player))
+ add_rule(world.get_location("Foglast: Under Lair Sarcophagus 1", player),
+ lambda state: state._hylics2_can_air_dash(player) and state._hylics2_has_bridge_key(player))
+ add_rule(world.get_location("Foglast: Under Lair Sarcophagus 2", player),
+ lambda state: state._hylics2_can_air_dash(player) and state._hylics2_has_bridge_key(player))
+ add_rule(world.get_location("Foglast: Under Lair Sarcophagus 3", player),
+ lambda state: state._hylics2_can_air_dash(player) and state._hylics2_has_bridge_key(player))
+ add_rule(world.get_location("Foglast: Sage Sarcophagus", player),
+ lambda state: state._hylics2_can_air_dash(player) and state._hylics2_has_bridge_key(player))
+ add_rule(world.get_location("Foglast: Sage Item 1", player),
+ lambda state: state._hylics2_can_air_dash(player) and state._hylics2_has_bridge_key(player))
+ add_rule(world.get_location("Foglast: Sage Item 2", player),
+ lambda state: state._hylics2_can_air_dash(player) and state._hylics2_has_bridge_key(player))
+
+ # Drill Castle
+ add_rule(world.get_location("Drill Castle: Island Banana", player),
+ lambda state: state._hylics2_can_air_dash(player))
+ add_rule(world.get_location("Drill Castle: Island Pot", player),
+ lambda state: state._hylics2_can_air_dash(player))
+ add_rule(world.get_location("Drill Castle: Cave Sarcophagus", player),
+ lambda state: state._hylics2_can_air_dash(player))
+ add_rule(world.get_location("Drill Castle: TV", player),
+ lambda state: state._hylics2_can_air_dash(player))
+
+ # Sage Labyrinth
+ add_rule(world.get_location("Sage Labyrinth: Sage Item 1", player),
+ lambda state: state._hylics2_has_deep_key(player))
+ add_rule(world.get_location("Sage Labyrinth: Sage Item 2", player),
+ lambda state: state._hylics2_has_deep_key(player))
+ add_rule(world.get_location("Sage Labyrinth: Sage Left Arm", player),
+ lambda state: state._hylics2_has_deep_key(player))
+ add_rule(world.get_location("Sage Labyrinth: Sage Right Arm", player),
+ lambda state: state._hylics2_has_deep_key(player))
+ add_rule(world.get_location("Sage Labyrinth: Sage Left Leg", player),
+ lambda state: state._hylics2_has_deep_key(player))
+ add_rule(world.get_location("Sage Labyrinth: Sage Right Leg", player),
+ lambda state: state._hylics2_has_deep_key(player))
+
+ # Sage Airship
+ add_rule(world.get_location("Sage Airship: TV", player),
+ lambda state: state._hylics2_has_tokens(player))
+
+ # Hylemxylem
+ add_rule(world.get_location("Hylemxylem: Upper Chamber Banana", player),
+ lambda state: state._hylics2_has_upper_chamber_key(player))
+ add_rule(world.get_location("Hylemxylem: Across Upper Reservoir Chest", player),
+ lambda state: state._hylics2_has_upper_chamber_key(player))
+ add_rule(world.get_location("Hylemxylem: Drained Lower Reservoir Chest", player),
+ lambda state: state._hylics2_has_upper_chamber_key(player))
+ add_rule(world.get_location("Hylemxylem: Drained Lower Reservoir Burrito 1", player),
+ lambda state: state._hylics2_has_upper_chamber_key(player))
+ add_rule(world.get_location("Hylemxylem: Drained Lower Reservoir Burrito 2", player),
+ lambda state: state._hylics2_has_upper_chamber_key(player))
+ add_rule(world.get_location("Hylemxylem: Lower Reservoir Hole Pot 1", player),
+ lambda state: state._hylics2_has_upper_chamber_key(player))
+ add_rule(world.get_location("Hylemxylem: Lower Reservoir Hole Pot 2", player),
+ lambda state: state._hylics2_has_upper_chamber_key(player))
+ add_rule(world.get_location("Hylemxylem: Lower Reservoir Hole Pot 3", player),
+ lambda state: state._hylics2_has_upper_chamber_key(player))
+ add_rule(world.get_location("Hylemxylem: Lower Reservoir Hole Sarcophagus", player),
+ lambda state: state._hylics2_has_upper_chamber_key(player))
+ add_rule(world.get_location("Hylemxylem: Drained Upper Reservoir Burrito 1", player),
+ lambda state: state._hylics2_has_upper_chamber_key(player))
+ add_rule(world.get_location("Hylemxylem: Drained Upper Reservoir Burrito 2", player),
+ lambda state: state._hylics2_has_upper_chamber_key(player))
+ add_rule(world.get_location("Hylemxylem: Drained Upper Reservoir Burrito 3", player),
+ lambda state: state._hylics2_has_upper_chamber_key(player))
+ add_rule(world.get_location("Hylemxylem: Upper Reservoir Hole Key", player),
+ lambda state: state._hylics2_has_upper_chamber_key(player))
+
+ # extra rules if Extra Items in Logic is enabled
+ if world.extra_items_in_logic[player]:
+ for i in world.get_region("Foglast", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_charge_up(player))
+ for i in world.get_region("Sage Airship", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_charge_up(player) and state._hylics2_has_cup(player) and\
+ state._hylics2_has_worm_room_key(player))
+ for i in world.get_region("Hylemxylem", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_charge_up(player) and state._hylics2_has_cup(player))
+
+ add_rule(world.get_location("Sage Labyrinth: Motor Hunter Sarcophagus", player),
+ lambda state: state._hylics2_has_charge_up(player) and state._hylics2_has_cup(player))
+
+ # extra rules if Shuffle Party Members is enabled
+ if world.party_shuffle[player]:
+ for i in world.get_region("Arcade Island", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_3_members(player))
+ for i in world.get_region("Foglast", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_3_members(player) or\
+ (state._hylics2_has_2_members(player) and state._hylics2_has_jail_key(player)))
+ for i in world.get_region("Sage Airship", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_3_members(player))
+ for i in world.get_region("Hylemxylem", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_3_members(player))
+
+ add_rule(world.get_location("Viewax's Edifice: Defeat Viewax", player),
+ lambda state: state._hylics2_has_2_members(player))
+ add_rule(world.get_location("New Muldul: Rescued Blerol 1", player),
+ lambda state: state._hylics2_has_2_members(player))
+ add_rule(world.get_location("New Muldul: Rescued Blerol 2", player),
+ lambda state: state._hylics2_has_2_members(player))
+ add_rule(world.get_location("New Muldul: Vault Left Chest", player),
+ lambda state: state._hylics2_has_3_members(player))
+ add_rule(world.get_location("New Muldul: Vault Right Chest", player),
+ lambda state: state._hylics2_has_3_members(player))
+ add_rule(world.get_location("New Muldul: Vault Bomb", player),
+ lambda state: state._hylics2_has_2_members(player))
+ add_rule(world.get_location("Juice Ranch: Battle with Somsnosa", player),
+ lambda state: state._hylics2_has_2_members(player))
+ add_rule(world.get_location("Juice Ranch: Somsnosa Joins", player),
+ lambda state: state._hylics2_has_2_members(player))
+ add_rule(world.get_location("Airship: Talk to Somsnosa", player),
+ lambda state: state._hylics2_has_3_members(player))
+ add_rule(world.get_location("Sage Labyrinth: Motor Hunter Sarcophagus", player),
+ lambda state: state._hylics2_has_3_members(player))
+
+ # extra rules if Shuffle Red Medallions is enabled
+ if world.medallion_shuffle[player]:
+ add_rule(world.get_location("New Muldul: Upper House Medallion", player),
+ lambda state: state._hylics2_has_upper_house_key(player))
+ add_rule(world.get_location("New Muldul: Vault Rear Left Medallion", player),
+ lambda state: state._hylics2_enter_foglast(player) and state._hylics2_has_bridge_key(player))
+ add_rule(world.get_location("New Muldul: Vault Rear Right Medallion", player),
+ lambda state: state._hylics2_enter_foglast(player) and state._hylics2_has_bridge_key(player))
+ add_rule(world.get_location("New Muldul: Vault Center Medallion", player),
+ lambda state: state._hylics2_enter_foglast(player) and state._hylics2_has_bridge_key(player))
+ add_rule(world.get_location("New Muldul: Vault Front Left Medallion", player),
+ lambda state: state._hylics2_enter_foglast(player) and state._hylics2_has_bridge_key(player))
+ add_rule(world.get_location("New Muldul: Vault Front Right Medallion", player),
+ lambda state: state._hylics2_enter_foglast(player) and state._hylics2_has_bridge_key(player))
+ add_rule(world.get_location("Viewax's Edifice: Fort Wall Medallion", player),
+ lambda state: state._hylics2_has_paddle(player))
+ add_rule(world.get_location("Viewax's Edifice: Jar Medallion", player),
+ lambda state: state._hylics2_has_paddle(player))
+ add_rule(world.get_location("Viewax's Edifice: Sage Chair Medallion", player),
+ lambda state: state._hylics2_can_air_dash(player))
+ add_rule(world.get_location("Arcade 1: Lonely Medallion", player),
+ lambda state: state._hylics2_has_paddle(player))
+ add_rule(world.get_location("Arcade 1: Alcove Medallion", player),
+ lambda state: state._hylics2_has_paddle(player))
+ add_rule(world.get_location("Foglast: Under Lair Medallion", player),
+ lambda state: state._hylics2_has_bridge_key(player))
+ add_rule(world.get_location("Foglast: Mid-Air Medallion", player),
+ lambda state: state._hylics2_can_air_dash(player))
+ add_rule(world.get_location("Foglast: Top of Tower Medallion", player),
+ lambda state: state._hylics2_has_paddle(player))
+ add_rule(world.get_location("Hylemxylem: Lower Reservoir Hole Medallion", player),
+ lambda state: state._hylics2_has_upper_chamber_key(player))
+
+ # extra rules is Shuffle Red Medallions and Party Shuffle are enabled
+ if world.party_shuffle[player] and world.medallion_shuffle[player]:
+ add_rule(world.get_location("New Muldul: Vault Rear Left Medallion", player),
+ lambda state: state._hylics2_has_jail_key(player))
+ add_rule(world.get_location("New Muldul: Vault Rear Right Medallion", player),
+ lambda state: state._hylics2_has_jail_key(player))
+ add_rule(world.get_location("New Muldul: Vault Center Medallion", player),
+ lambda state: state._hylics2_has_jail_key(player))
+ add_rule(world.get_location("New Muldul: Vault Front Left Medallion", player),
+ lambda state: state._hylics2_has_jail_key(player))
+ add_rule(world.get_location("New Muldul: Vault Front Right Medallion", player),
+ lambda state: state._hylics2_has_jail_key(player))
+
+ # entrances
+ for i in world.get_region("Airship", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+ for i in world.get_region("Arcade Island", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player) and state._hylics2_can_air_dash(player))
+ for i in world.get_region("Worm Pod", player).entrances:
+ add_rule(i, lambda state: state._hylics2_enter_wormpod(player))
+ for i in world.get_region("Foglast", player).entrances:
+ add_rule(i, lambda state: state._hylics2_enter_foglast(player))
+ for i in world.get_region("Sage Labyrinth", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_skull_bomb(player))
+ for i in world.get_region("Sage Airship", player).entrances:
+ add_rule(i, lambda state: state._hylics2_enter_sageship(player))
+ for i in world.get_region("Hylemxylem", player).entrances:
+ add_rule(i, lambda state: state._hylics2_enter_hylemxylem(player))
+
+ # random start logic (default)
+ if ((not world.random_start[player]) or \
+ (world.random_start[player] and hylics2world.start_location == "Waynehouse")):
+ # entrances
+ for i in world.get_region("Viewax", player).entrances:
+ add_rule(i, lambda state: state._hylics2_can_air_dash(player) or state._hylics2_has_airship(player))
+ for i in world.get_region("TV Island", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+ for i in world.get_region("Shield Facility", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+ for i in world.get_region("Juice Ranch", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+
+ # random start logic (Viewax's Edifice)
+ elif (world.random_start[player] and hylics2world.start_location == "Viewax's Edifice"):
+ for i in world.get_region("Waynehouse", player).entrances:
+ add_rule(i, lambda state: state._hylics2_can_air_dash(player) or state._hylics2_has_airship(player))
+ for i in world.get_region("New Muldul", player).entrances:
+ add_rule(i, lambda state: state._hylics2_can_air_dash(player) or state._hylics2_has_airship(player))
+ for i in world.get_region("New Muldul Vault", player).entrances:
+ add_rule(i, lambda state: state._hylics2_can_air_dash(player) or state._hylics2_has_airship(player))
+ for i in world.get_region("Drill Castle", player).entrances:
+ add_rule(i, lambda state: state._hylics2_can_air_dash(player) or state._hylics2_has_airship(player))
+ for i in world.get_region("TV Island", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+ for i in world.get_region("Shield Facility", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+ for i in world.get_region("Juice Ranch", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+ for i in world.get_region("Sage Labyrinth", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+
+ # random start logic (TV Island)
+ elif (world.random_start[player] and hylics2world.start_location == "TV Island"):
+ for i in world.get_region("Waynehouse", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+ for i in world.get_region("New Muldul", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+ for i in world.get_region("New Muldul Vault", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+ for i in world.get_region("Drill Castle", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+ for i in world.get_region("Viewax", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+ for i in world.get_region("Shield Facility", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+ for i in world.get_region("Juice Ranch", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+ for i in world.get_region("Sage Labyrinth", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+
+ # random start logic (Shield Facility)
+ elif (world.random_start[player] and hylics2world.start_location == "Shield Facility"):
+ for i in world.get_region("Waynehouse", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+ for i in world.get_region("New Muldul", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+ for i in world.get_region("New Muldul Vault", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+ for i in world.get_region("Drill Castle", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+ for i in world.get_region("Viewax", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+ for i in world.get_region("TV Island", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
+ for i in world.get_region("Sage Labyrinth", player).entrances:
+ add_rule(i, lambda state: state._hylics2_has_airship(player))
\ No newline at end of file
diff --git a/worlds/hylics2/__init__.py b/worlds/hylics2/__init__.py
new file mode 100644
index 0000000000..b429eb6a44
--- /dev/null
+++ b/worlds/hylics2/__init__.py
@@ -0,0 +1,246 @@
+import random
+from typing import Dict, Any
+from BaseClasses import Region, Entrance, Location, Item, Tutorial, ItemClassification, RegionType
+from worlds.generic.Rules import set_rule
+from ..AutoWorld import World, WebWorld
+from . import Items, Locations, Options, Rules, Exits
+
+
+class Hylics2Web(WebWorld):
+ theme = "ocean"
+ tutorials = [Tutorial(
+ "Multiworld Setup Guide",
+ "A guide to settings up the Hylics 2 randomizer connected to an Archipelago Multiworld",
+ "English",
+ "setup_en.md",
+ "setup/en",
+ ["TRPG"]
+ )]
+
+
+class Hylics2World(World):
+ """
+ Hylics 2 is a surreal and unusual RPG, with a bizarre yet unique visual style. Play as Wayne,
+ travel the world, and gather your allies to defeat the nefarious Gibby in his Hylemxylem!
+ """
+ game: str = "Hylics 2"
+ web = Hylics2Web()
+
+ all_items = {**Items.item_table, **Items.gesture_item_table, **Items.party_item_table,
+ **Items.medallion_item_table}
+ all_locations = {**Locations.location_table, **Locations.tv_location_table, **Locations.party_location_table,
+ **Locations.medallion_location_table}
+
+ item_name_to_id = {data["name"]: item_id for item_id, data in all_items.items()}
+ location_name_to_id = {data["name"]: loc_id for loc_id, data in all_locations.items()}
+ option_definitions = Options.hylics2_options
+
+ topology_present: bool = True
+ remote_items: bool = True
+ remote_start_inventory: bool = True
+
+ data_version: 1
+
+ start_location = "Waynehouse"
+
+
+ def set_rules(self):
+ Rules.set_rules(self)
+
+
+ def create_item(self, name: str) -> "Hylics2Item":
+ item_id: int = self.item_name_to_id[name]
+
+ return Hylics2Item(name, self.all_items[item_id]["classification"], item_id, player=self.player)
+
+
+ def add_item(self, name: str, classification: ItemClassification, code: int) -> "Item":
+ return Hylics2Item(name, classification, code, self.player)
+
+
+ def create_event(self, event: str):
+ return Hylics2Item(event, ItemClassification.progression_skip_balancing, None, self.player)
+
+
+ # set random starting location if option is enabled
+ def generate_early(self):
+ if self.world.random_start[self.player]:
+ i = self.world.random.randint(0, 3)
+ if i == 0:
+ self.start_location = "Waynehouse"
+ elif i == 1:
+ self.start_location = "Viewax's Edifice"
+ elif i == 2:
+ self.start_location = "TV Island"
+ elif i == 3:
+ self.start_location = "Shield Facility"
+
+ def generate_basic(self):
+ # create location for beating the game and place Victory event there
+ loc = Location(self.player, "Defeat Gibby", None, self.world.get_region("Hylemxylem", self.player))
+ loc.place_locked_item(self.create_event("Victory"))
+ set_rule(loc, lambda state: state._hylics2_has_upper_chamber_key(self.player)
+ and state._hylics2_has_vessel_room_key(self.player))
+ self.world.get_region("Hylemxylem", self.player).locations.append(loc)
+ self.world.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
+
+ # create item pool
+ pool = []
+
+ # add regular items
+ for i, data in Items.item_table.items():
+ if data["count"] > 0:
+ for j in range(data["count"]):
+ pool.append(self.add_item(data["name"], data["classification"], i))
+
+ # add party members if option is enabled
+ if self.world.party_shuffle[self.player]:
+ for i, data in Items.party_item_table.items():
+ pool.append(self.add_item(data["name"], data["classification"], i))
+
+ # handle gesture shuffle options
+ if self.world.gesture_shuffle[self.player] == 2: # vanilla locations
+ gestures = Items.gesture_item_table
+ self.world.get_location("Waynehouse: TV", self.player)\
+ .place_locked_item(self.add_item(gestures[200678]["name"], gestures[200678]["classification"], 200678))
+ self.world.get_location("Afterlife: TV", self.player)\
+ .place_locked_item(self.add_item(gestures[200683]["name"], gestures[200683]["classification"], 200683))
+ self.world.get_location("New Muldul: TV", self.player)\
+ .place_locked_item(self.add_item(gestures[200679]["name"], gestures[200679]["classification"], 200679))
+ self.world.get_location("Viewax's Edifice: TV", self.player)\
+ .place_locked_item(self.add_item(gestures[200680]["name"], gestures[200680]["classification"], 200680))
+ self.world.get_location("TV Island: TV", self.player)\
+ .place_locked_item(self.add_item(gestures[200681]["name"], gestures[200681]["classification"], 200681))
+ self.world.get_location("Juice Ranch: TV", self.player)\
+ .place_locked_item(self.add_item(gestures[200682]["name"], gestures[200682]["classification"], 200682))
+ self.world.get_location("Foglast: TV", self.player)\
+ .place_locked_item(self.add_item(gestures[200684]["name"], gestures[200684]["classification"], 200684))
+ self.world.get_location("Drill Castle: TV", self.player)\
+ .place_locked_item(self.add_item(gestures[200688]["name"], gestures[200688]["classification"], 200688))
+ self.world.get_location("Sage Airship: TV", self.player)\
+ .place_locked_item(self.add_item(gestures[200685]["name"], gestures[200685]["classification"], 200685))
+
+ elif self.world.gesture_shuffle[self.player] == 1: # TVs only
+ gestures = list(Items.gesture_item_table.items())
+ tvs = list(Locations.tv_location_table.items())
+
+ # if Extra Items in Logic is enabled place CHARGE UP first and make sure it doesn't get
+ # placed at Sage Airship: TV
+ if self.world.extra_items_in_logic[self.player]:
+ tv = self.world.random.choice(tvs)
+ gest = gestures.index((200681, Items.gesture_item_table[200681]))
+ while tv[1]["name"] == "Sage Airship: TV":
+ tv = self.world.random.choice(tvs)
+ self.world.get_location(tv[1]["name"], self.player)\
+ .place_locked_item(self.add_item(gestures[gest][1]["name"], gestures[gest][1]["classification"],
+ gestures[gest]))
+ gestures.remove(gestures[gest])
+ tvs.remove(tv)
+
+ for i in range(len(gestures)):
+ gest = self.world.random.choice(gestures)
+ tv = self.world.random.choice(tvs)
+ self.world.get_location(tv[1]["name"], self.player)\
+ .place_locked_item(self.add_item(gest[1]["name"], gest[1]["classification"], gest[1]))
+ gestures.remove(gest)
+ tvs.remove(tv)
+
+ else: # add gestures to pool like normal
+ for i, data in Items.gesture_item_table.items():
+ pool.append(self.add_item(data["name"], data["classification"], i))
+
+ # add '10 Bones' items if medallion shuffle is enabled
+ if self.world.medallion_shuffle[self.player]:
+ for i, data in Items.medallion_item_table.items():
+ for j in range(data["count"]):
+ pool.append(self.add_item(data["name"], data["classification"], i))
+
+ # add to world's pool
+ self.world.itempool += pool
+
+
+ def fill_slot_data(self) -> Dict[str, Any]:
+ slot_data: Dict[str, Any] = {
+ "party_shuffle": self.world.party_shuffle[self.player].value,
+ "medallion_shuffle": self.world.medallion_shuffle[self.player].value,
+ "random_start" : self.world.random_start[self.player].value,
+ "start_location" : self.start_location,
+ "death_link": self.world.death_link[self.player].value
+ }
+ return slot_data
+
+
+ def create_regions(self) -> None:
+
+ region_table: Dict[int, Region] = {
+ 0: Region("Menu", RegionType.Generic, "Menu", self.player, self.world),
+ 1: Region("Afterlife", RegionType.Generic, "Afterlife", self.player, self.world),
+ 2: Region("Waynehouse", RegionType.Generic, "Waynehouse", self.player, self.world),
+ 3: Region("World", RegionType.Generic, "World", self.player, self.world),
+ 4: Region("New Muldul", RegionType.Generic, "New Muldul", self.player, self.world),
+ 5: Region("New Muldul Vault", RegionType.Generic, "New Muldul Vault", self.player, self.world),
+ 6: Region("Viewax", RegionType.Generic, "Viewax's Edifice", self.player, self.world),
+ 7: Region("Airship", RegionType.Generic, "Airship", self.player, self.world),
+ 8: Region("Arcade Island", RegionType.Generic, "Arcade Island", self.player, self.world),
+ 9: Region("TV Island", RegionType.Generic, "TV Island", self.player, self.world),
+ 10: Region("Juice Ranch", RegionType.Generic, "Juice Ranch", self.player, self.world),
+ 11: Region("Shield Facility", RegionType.Generic, "Shield Facility", self.player, self.world),
+ 12: Region("Worm Pod", RegionType.Generic, "Worm Pod", self.player, self.world),
+ 13: Region("Foglast", RegionType.Generic, "Foglast", self.player, self.world),
+ 14: Region("Drill Castle", RegionType.Generic, "Drill Castle", self.player, self.world),
+ 15: Region("Sage Labyrinth", RegionType.Generic, "Sage Labyrinth", self.player, self.world),
+ 16: Region("Sage Airship", RegionType.Generic, "Sage Airship", self.player, self.world),
+ 17: Region("Hylemxylem", RegionType.Generic, "Hylemxylem", self.player, self.world)
+ }
+
+ # create regions from table
+ for i, reg in region_table.items():
+ self.world.regions.append(reg)
+ # get all exits per region
+ for j, exits in Exits.region_exit_table.items():
+ if j == i:
+ for k in exits:
+ # create entrance and connect it to parent and destination regions
+ ent = Entrance(self.player, k, reg)
+ reg.exits.append(ent)
+ if k == "New Game" and self.world.random_start[self.player]:
+ if self.start_location == "Waynehouse":
+ ent.connect(region_table[2])
+ elif self.start_location == "Viewax's Edifice":
+ ent.connect(region_table[6])
+ elif self.start_location == "TV Island":
+ ent.connect(region_table[9])
+ elif self.start_location == "Shield Facility":
+ ent.connect(region_table[11])
+ else:
+ for name, num in Exits.exit_lookup_table.items():
+ if k == name:
+ ent.connect(region_table[num])
+
+ # add regular locations
+ for i, data in Locations.location_table.items():
+ region_table[data["region"]].locations\
+ .append(Hylics2Location(self.player, data["name"], i, region_table[data["region"]]))
+ for i, data in Locations.tv_location_table.items():
+ region_table[data["region"]].locations\
+ .append(Hylics2Location(self.player, data["name"], i, region_table[data["region"]]))
+
+ # add party member locations if option is enabled
+ if self.world.party_shuffle[self.player]:
+ for i, data in Locations.party_location_table.items():
+ region_table[data["region"]].locations\
+ .append(Hylics2Location(self.player, data["name"], i, region_table[data["region"]]))
+
+ # add medallion locations if option is enabled
+ if self.world.medallion_shuffle[self.player]:
+ for i, data in Locations.medallion_location_table.items():
+ region_table[data["region"]].locations\
+ .append(Hylics2Location(self.player, data["name"], i, region_table[data["region"]]))
+
+
+class Hylics2Location(Location):
+ game: str = "Hylics 2"
+
+
+class Hylics2Item(Item):
+ game: str = "Hylics 2"
\ No newline at end of file
diff --git a/worlds/hylics2/docs/en_Hylics 2.md b/worlds/hylics2/docs/en_Hylics 2.md
new file mode 100644
index 0000000000..cb201a52bb
--- /dev/null
+++ b/worlds/hylics2/docs/en_Hylics 2.md
@@ -0,0 +1,17 @@
+# Hylics 2
+
+## Where is the settings page?
+
+The [player settings page for this game](../player-settings) contains all the options you need to configure and export a config file.
+
+## What does randomization do to this game?
+
+In Hylics 2, all unique items, equipment and skills are randomized. This includes items in chests, items that are freely standing in the world, items recieved from talking to certain characters, gestures learned from TVs, and so on. Items recieved from completing battles are not randomized, with the exception of the Jail Key recieved from defeating Viewax.
+
+## What Hylics 2 items can appear in other players' worlds?
+
+Consumable items, key items, gloves, accessories, and gestures can appear in other players' worlds.
+
+## What does another world's item look like in Hylics 2?
+
+All items retain their original appearance. You won't know if an item belongs to another player until you collect it.
\ No newline at end of file
diff --git a/worlds/hylics2/docs/setup_en.md b/worlds/hylics2/docs/setup_en.md
new file mode 100644
index 0000000000..1e1ac49790
--- /dev/null
+++ b/worlds/hylics2/docs/setup_en.md
@@ -0,0 +1,35 @@
+# Hylics 2 Randomizer Setup Guide
+
+## Required Software
+
+- Hylics 2 from: [Steam](https://store.steampowered.com/app/1286710/Hylics_2/) or [itch.io](https://mason-lindroth.itch.io/hylics-2)
+- BepInEx from: [GitHub](https://github.com/BepInEx/BepInEx/releases)
+- Archipelago Mod for Hylics 2 from: [GitHub](https://github.com/TRPG0/ArchipelagoHylics2)
+
+## Instructions (Windows)
+
+1. Download and install BepInEx 5 (32-bit, version 5.4.20 or newer) to your Hylics 2 root folder. *Do not use any pre-release versions of BepInEx 6.*
+
+2. Start Hylics 2 once so that BepInEx can create its required configuration files.
+
+3. Download the latest version of ArchipelagoHylics2 from the [Releases](https://github.com/TRPG0/ArchipelagoHylics2/releases) page and extract the contents of the zip file into `BepInEx\plugins`.
+
+4. Start Hylics 2 again. To verify that the mod is working, begin a new game or load a save file.
+
+## Connecting
+
+To connect to an Archipelago server, open the in-game console (default key: `/`) and use the command `/connect [address:port] [name] [password]`. The port and password are both optional - if no port is provided then the default port of 38281 is used.
+**Make sure that you have connected to a server at least once before attempting to check any locations.**
+
+## Other Commands
+
+There are a few additional commands that can be used while playing Hylics 2 randomizer:
+
+- `/disconnect` - Disconnect from an Archipelago server.
+- `/popups` - Enables or disables in-game messages when an item is found or recieved.
+- `/airship` - Resummons the airship at the dock above New Muldul and teleports Wayne to it, in case the player gets stuck. Player must have the DOCK KEY to use this command.
+- `/respawn` - Moves Wayne back to the spawn position of the current area in case you get stuck. `/respawn home` will teleport Wayne back to his original starting position.
+- `/checked [region]` - States how many locations have been checked in a given region. If no region is given, then the player's location will be used.
+- `/deathlink` - Enables or disables DeathLink.
+- `/help [command]` - Lists a command, it's description, and it's required arguments (if any). If no command is given, all commands will be displayed.
+- `![command]` - Entering any command with an `!` at the beginning allows for remotely sending commands to the server.
\ No newline at end of file
diff --git a/worlds/oot/EntranceShuffle.py b/worlds/oot/EntranceShuffle.py
index 08d1e3ff79..bd06a3d81b 100644
--- a/worlds/oot/EntranceShuffle.py
+++ b/worlds/oot/EntranceShuffle.py
@@ -343,6 +343,27 @@ priority_entrance_table = {
}
+# These hint texts have more than one entrance, so they are OK for impa's house and potion shop
+multi_interior_regions = {
+ 'Kokiri Forest',
+ 'Lake Hylia',
+ 'the Market',
+ 'Kakariko Village',
+ 'Lon Lon Ranch',
+}
+
+interior_entrance_bias = {
+ 'Kakariko Village -> Kak Potion Shop Front': 4,
+ 'Kak Backyard -> Kak Potion Shop Back': 4,
+ 'Kakariko Village -> Kak Impas House': 3,
+ 'Kak Impas Ledge -> Kak Impas House Back': 3,
+ 'Goron City -> GC Shop': 2,
+ 'Zoras Domain -> ZD Shop': 2,
+ 'Market Entrance -> Market Guard House': 2,
+ 'ToT Entrance -> Temple of Time': 1,
+}
+
+
class EntranceShuffleError(Exception):
pass
@@ -500,7 +521,7 @@ def shuffle_random_entrances(ootworld):
delete_target_entrance(remaining_target)
for pool_type, entrance_pool in one_way_entrance_pools.items():
- shuffle_entrance_pool(ootworld, entrance_pool, one_way_target_entrance_pools[pool_type], locations_to_ensure_reachable, all_state, none_state, check_all=True, retry_count=5)
+ shuffle_entrance_pool(ootworld, pool_type, entrance_pool, one_way_target_entrance_pools[pool_type], locations_to_ensure_reachable, all_state, none_state, check_all=True, retry_count=5)
replaced_entrances = [entrance.replaces for entrance in entrance_pool]
for remaining_target in chain.from_iterable(one_way_target_entrance_pools.values()):
if remaining_target.replaces in replaced_entrances:
@@ -510,7 +531,7 @@ def shuffle_random_entrances(ootworld):
# Shuffle all entrance pools, in order
for pool_type, entrance_pool in entrance_pools.items():
- shuffle_entrance_pool(ootworld, entrance_pool, target_entrance_pools[pool_type], locations_to_ensure_reachable, all_state, none_state)
+ shuffle_entrance_pool(ootworld, pool_type, entrance_pool, target_entrance_pools[pool_type], locations_to_ensure_reachable, all_state, none_state, check_all=True)
# Multiple checks after shuffling to ensure everything is OK
# Check that all entrances hook up correctly
@@ -596,7 +617,7 @@ def place_one_way_priority_entrance(ootworld, priority_name, allowed_regions, al
raise EntranceShuffleError(f'Unable to place priority one-way entrance for {priority_name} in world {ootworld.player}')
-def shuffle_entrance_pool(ootworld, entrance_pool, target_entrances, locations_to_ensure_reachable, all_state, none_state, check_all=False, retry_count=20):
+def shuffle_entrance_pool(ootworld, pool_type, entrance_pool, target_entrances, locations_to_ensure_reachable, all_state, none_state, check_all=False, retry_count=20):
restrictive_entrances, soft_entrances = split_entrances_by_requirements(ootworld, entrance_pool, target_entrances)
@@ -604,11 +625,11 @@ def shuffle_entrance_pool(ootworld, entrance_pool, target_entrances, locations_t
retry_count -= 1
rollbacks = []
try:
- shuffle_entrances(ootworld, restrictive_entrances, target_entrances, rollbacks, locations_to_ensure_reachable, all_state, none_state)
+ shuffle_entrances(ootworld, pool_type+'Rest', restrictive_entrances, target_entrances, rollbacks, locations_to_ensure_reachable, all_state, none_state)
if check_all:
- shuffle_entrances(ootworld, soft_entrances, target_entrances, rollbacks, locations_to_ensure_reachable, all_state, none_state)
+ shuffle_entrances(ootworld, pool_type+'Soft', soft_entrances, target_entrances, rollbacks, locations_to_ensure_reachable, all_state, none_state)
else:
- shuffle_entrances(ootworld, soft_entrances, target_entrances, rollbacks, set(), all_state, none_state)
+ shuffle_entrances(ootworld, pool_type+'Soft', soft_entrances, target_entrances, rollbacks, set(), all_state, none_state)
validate_world(ootworld, None, locations_to_ensure_reachable, all_state, none_state)
for entrance, target in rollbacks:
@@ -621,12 +642,16 @@ def shuffle_entrance_pool(ootworld, entrance_pool, target_entrances, locations_t
raise EntranceShuffleError(f'Entrance placement attempt count exceeded for world {ootworld.player}')
-def shuffle_entrances(ootworld, entrances, target_entrances, rollbacks, locations_to_ensure_reachable, all_state, none_state):
+def shuffle_entrances(ootworld, pool_type, entrances, target_entrances, rollbacks, locations_to_ensure_reachable, all_state, none_state):
ootworld.world.random.shuffle(entrances)
for entrance in entrances:
if entrance.connected_region != None:
continue
ootworld.world.random.shuffle(target_entrances)
+ # Here we deliberately introduce bias by prioritizing certain interiors, i.e. the ones most likely to cause problems.
+ # success rate over randomization
+ if pool_type in {'InteriorSoft', 'MixedSoft'}:
+ target_entrances.sort(reverse=True, key=lambda entrance: interior_entrance_bias.get(entrance.replaces.name, 0))
for target in target_entrances:
if target.connected_region == None:
continue
@@ -715,25 +740,33 @@ def validate_world(ootworld, entrance_placed, locations_to_ensure_reachable, all
# Check if all locations are reachable if not beatable-only or game is not yet complete
if locations_to_ensure_reachable:
- if world.accessibility[player].current_key != 'minimal' or not world.can_beat_game(all_state):
- for loc in locations_to_ensure_reachable:
- if not all_state.can_reach(loc, 'Location', player):
- raise EntranceShuffleError(f'{loc} is unreachable')
+ for loc in locations_to_ensure_reachable:
+ if not all_state.can_reach(loc, 'Location', player):
+ raise EntranceShuffleError(f'{loc} is unreachable')
if ootworld.shuffle_interior_entrances and (ootworld.misc_hints or ootworld.hints != 'none') and \
(entrance_placed == None or entrance_placed.type in ['Interior', 'SpecialInterior']):
# Ensure Kak Potion Shop entrances are in the same hint area so there is no ambiguity as to which entrance is used for hints
- potion_front_entrance = get_entrance_replacing(world.get_region('Kak Potion Shop Front', player), 'Kakariko Village -> Kak Potion Shop Front', player)
- potion_back_entrance = get_entrance_replacing(world.get_region('Kak Potion Shop Back', player), 'Kak Backyard -> Kak Potion Shop Back', player)
- if potion_front_entrance is not None and potion_back_entrance is not None and not same_hint_area(potion_front_entrance, potion_back_entrance):
+ potion_front = get_entrance_replacing(world.get_region('Kak Potion Shop Front', player), 'Kakariko Village -> Kak Potion Shop Front', player)
+ potion_back = get_entrance_replacing(world.get_region('Kak Potion Shop Back', player), 'Kak Backyard -> Kak Potion Shop Back', player)
+ if potion_front is not None and potion_back is not None and not same_hint_area(potion_front, potion_back):
raise EntranceShuffleError('Kak Potion Shop entrances are not in the same hint area')
+ elif (potion_front and not potion_back) or (not potion_front and potion_back):
+ # Check the hint area and ensure it's one of the ones with more than one entrance
+ potion_placed_entrance = potion_front if potion_front else potion_back
+ if get_hint_area(potion_placed_entrance) not in multi_interior_regions:
+ raise EntranceShuffleError('Kak Potion Shop entrances can never be in the same hint area')
# When cows are shuffled, ensure the same thing for Impa's House, since the cow is reachable from both sides
if ootworld.shuffle_cows:
- impas_front_entrance = get_entrance_replacing(world.get_region('Kak Impas House', player), 'Kakariko Village -> Kak Impas House', player)
- impas_back_entrance = get_entrance_replacing(world.get_region('Kak Impas House Back', player), 'Kak Impas Ledge -> Kak Impas House Back', player)
- if impas_front_entrance is not None and impas_back_entrance is not None and not same_hint_area(impas_front_entrance, impas_back_entrance):
+ impas_front = get_entrance_replacing(world.get_region('Kak Impas House', player), 'Kakariko Village -> Kak Impas House', player)
+ impas_back = get_entrance_replacing(world.get_region('Kak Impas House Back', player), 'Kak Impas Ledge -> Kak Impas House Back', player)
+ if impas_front is not None and impas_back is not None and not same_hint_area(impas_front, impas_back):
raise EntranceShuffleError('Kak Impas House entrances are not in the same hint area')
+ elif (impas_front and not impas_back) or (not impas_front and impas_back):
+ impas_placed_entrance = impas_front if impas_front else impas_back
+ if get_hint_area(impas_placed_entrance) not in multi_interior_regions:
+ raise EntranceShuffleError('Kak Impas House entrances can never be in the same hint area')
# Check basic refills, time passing, return to ToT
if (ootworld.shuffle_special_interior_entrances or ootworld.shuffle_overworld_entrances or ootworld.spawn_positions) and \
@@ -845,3 +878,4 @@ def delete_target_entrance(target):
if target.parent_region != None:
target.parent_region.exits.remove(target)
target.parent_region = None
+ del target
diff --git a/worlds/oot/__init__.py b/worlds/oot/__init__.py
index 2536c3d4c9..c985ea13f0 100644
--- a/worlds/oot/__init__.py
+++ b/worlds/oot/__init__.py
@@ -1,7 +1,7 @@
import logging
import threading
import copy
-from collections import Counter
+from collections import Counter, deque
logger = logging.getLogger("Ocarina of Time")
@@ -412,17 +412,6 @@ class OOTWorld(World):
self.shop_prices[location.name] = int(self.world.random.betavariate(1.5, 2) * 60) * 5
def fill_bosses(self, bossCount=9):
- rewardlist = (
- 'Kokiri Emerald',
- 'Goron Ruby',
- 'Zora Sapphire',
- 'Forest Medallion',
- 'Fire Medallion',
- 'Water Medallion',
- 'Spirit Medallion',
- 'Shadow Medallion',
- 'Light Medallion'
- )
boss_location_names = (
'Queen Gohma',
'King Dodongo',
@@ -434,7 +423,7 @@ class OOTWorld(World):
'Twinrova',
'Links Pocket'
)
- boss_rewards = [self.create_item(reward) for reward in rewardlist]
+ boss_rewards = [item for item in self.itempool if item.type == 'DungeonReward']
boss_locations = [self.world.get_location(loc, self.player) for loc in boss_location_names]
placed_prizes = [loc.item.name for loc in boss_locations if loc.item is not None]
@@ -447,9 +436,8 @@ class OOTWorld(World):
self.world.random.shuffle(prize_locs)
item = prizepool.pop()
loc = prize_locs.pop()
- self.world.push_item(loc, item, collect=False)
- loc.locked = True
- loc.event = True
+ loc.place_locked_item(item)
+ self.world.itempool.remove(item)
def create_item(self, name: str):
if name in item_table:
@@ -496,6 +484,10 @@ class OOTWorld(World):
# Generate itempool
generate_itempool(self)
add_dungeon_items(self)
+ # Add dungeon rewards
+ rewardlist = sorted(list(self.item_name_groups['rewards']))
+ self.itempool += map(self.create_item, rewardlist)
+
junk_pool = get_junk_pool(self)
removed_items = []
# Determine starting items
@@ -621,61 +613,64 @@ class OOTWorld(World):
"Gerudo Training Ground Maze Path Final Chest", "Gerudo Training Ground MQ Ice Arrows Chest",
]
+ def get_names(items):
+ for item in items:
+ yield item.name
+
# Place/set rules for dungeon items
itempools = {
- 'dungeon': [],
- 'overworld': [],
- 'any_dungeon': [],
+ 'dungeon': set(),
+ 'overworld': set(),
+ 'any_dungeon': set(),
}
any_dungeon_locations = []
for dungeon in self.dungeons:
- itempools['dungeon'] = []
+ itempools['dungeon'] = set()
# Put the dungeon items into their appropriate pools.
# Build in reverse order since we need to fill boss key first and pop() returns the last element
if self.shuffle_mapcompass in itempools:
- itempools[self.shuffle_mapcompass].extend(dungeon.dungeon_items)
+ itempools[self.shuffle_mapcompass].update(get_names(dungeon.dungeon_items))
if self.shuffle_smallkeys in itempools:
- itempools[self.shuffle_smallkeys].extend(dungeon.small_keys)
+ itempools[self.shuffle_smallkeys].update(get_names(dungeon.small_keys))
shufflebk = self.shuffle_bosskeys if dungeon.name != 'Ganons Castle' else self.shuffle_ganon_bosskey
if shufflebk in itempools:
- itempools[shufflebk].extend(dungeon.boss_key)
+ itempools[shufflebk].update(get_names(dungeon.boss_key))
# We can't put a dungeon item on the end of a dungeon if a song is supposed to go there. Make sure not to include it.
dungeon_locations = [loc for region in dungeon.regions for loc in region.locations
if loc.item is None and (
self.shuffle_song_items != 'dungeon' or loc.name not in dungeon_song_locations)]
if itempools['dungeon']: # only do this if there's anything to shuffle
- for item in itempools['dungeon']:
+ dungeon_itempool = [item for item in self.world.itempool if item.player == self.player and item.name in itempools['dungeon']]
+ for item in dungeon_itempool:
self.world.itempool.remove(item)
self.world.random.shuffle(dungeon_locations)
fill_restrictive(self.world, self.world.get_all_state(False), dungeon_locations,
- itempools['dungeon'], True, True)
+ dungeon_itempool, True, True)
any_dungeon_locations.extend(dungeon_locations) # adds only the unfilled locations
# Now fill items that can go into any dungeon. Retrieve the Gerudo Fortress keys from the pool if necessary
if self.shuffle_fortresskeys == 'any_dungeon':
- fortresskeys = filter(lambda item: item.player == self.player and item.type == 'HideoutSmallKey',
- self.world.itempool)
- itempools['any_dungeon'].extend(fortresskeys)
+ itempools['any_dungeon'].add('Small Key (Thieves Hideout)')
if itempools['any_dungeon']:
- for item in itempools['any_dungeon']:
+ any_dungeon_itempool = [item for item in self.world.itempool if item.player == self.player and item.name in itempools['any_dungeon']]
+ for item in any_dungeon_itempool:
self.world.itempool.remove(item)
- itempools['any_dungeon'].sort(key=lambda item:
- {'GanonBossKey': 4, 'BossKey': 3, 'SmallKey': 2, 'HideoutSmallKey': 1}.get(item.type, 0))
+ any_dungeon_itempool.sort(key=lambda item:
+ {'GanonBossKey': 4, 'BossKey': 3, 'SmallKey': 2, 'HideoutSmallKey': 1}.get(item.type, 0))
self.world.random.shuffle(any_dungeon_locations)
fill_restrictive(self.world, self.world.get_all_state(False), any_dungeon_locations,
- itempools['any_dungeon'], True, True)
+ any_dungeon_itempool, True, True)
# If anything is overworld-only, fill into local non-dungeon locations
if self.shuffle_fortresskeys == 'overworld':
- fortresskeys = filter(lambda item: item.player == self.player and item.type == 'HideoutSmallKey',
- self.world.itempool)
- itempools['overworld'].extend(fortresskeys)
+ itempools['overworld'].add('Small Key (Thieves Hideout)')
if itempools['overworld']:
- for item in itempools['overworld']:
+ overworld_itempool = [item for item in self.world.itempool if item.player == self.player and item.name in itempools['overworld']]
+ for item in overworld_itempool:
self.world.itempool.remove(item)
- itempools['overworld'].sort(key=lambda item:
- {'GanonBossKey': 4, 'BossKey': 3, 'SmallKey': 2, 'HideoutSmallKey': 1}.get(item.type, 0))
+ overworld_itempool.sort(key=lambda item:
+ {'GanonBossKey': 4, 'BossKey': 3, 'SmallKey': 2, 'HideoutSmallKey': 1}.get(item.type, 0))
non_dungeon_locations = [loc for loc in self.get_locations() if
not loc.item and loc not in any_dungeon_locations and
(loc.type != 'Shop' or loc.name in self.shop_prices) and
@@ -683,7 +678,7 @@ class OOTWorld(World):
(loc.name not in dungeon_song_locations or self.shuffle_song_items != 'dungeon')]
self.world.random.shuffle(non_dungeon_locations)
fill_restrictive(self.world, self.world.get_all_state(False), non_dungeon_locations,
- itempools['overworld'], True, True)
+ overworld_itempool, True, True)
# Place songs
# 5 built-in retries because this section can fail sometimes
@@ -805,6 +800,10 @@ class OOTWorld(World):
or (self.skip_child_zelda and loc.name in ['HC Zeldas Letter', 'Song from Impa'])):
loc.address = None
+ # Handle item-linked dungeon items and songs
+ def stage_pre_fill(cls):
+ pass
+
def generate_output(self, output_directory: str):
if self.hints != 'none':
self.hint_data_available.wait()
@@ -831,18 +830,25 @@ class OOTWorld(World):
# Write entrances to spoiler log
all_entrances = self.get_shuffled_entrances()
- all_entrances.sort(key=lambda x: x.name)
- all_entrances.sort(key=lambda x: x.type)
+ all_entrances.sort(reverse=True, key=lambda x: x.name)
+ all_entrances.sort(reverse=True, key=lambda x: x.type)
if not self.decouple_entrances:
- for loadzone in all_entrances:
- if loadzone.primary:
- entrance = loadzone
+ while all_entrances:
+ loadzone = all_entrances.pop()
+ if loadzone.type != 'Overworld':
+ if loadzone.primary:
+ entrance = loadzone
+ else:
+ entrance = loadzone.reverse
+ if entrance.reverse is not None:
+ self.world.spoiler.set_entrance(entrance, entrance.replaces.reverse, 'both', self.player)
+ else:
+ self.world.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player)
else:
- entrance = loadzone.reverse
- if entrance.reverse is not None:
- self.world.spoiler.set_entrance(entrance, entrance.replaces.reverse, 'both', self.player)
- else:
- self.world.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player)
+ reverse = loadzone.replaces.reverse
+ if reverse in all_entrances:
+ all_entrances.remove(reverse)
+ self.world.spoiler.set_entrance(loadzone, reverse, 'both', self.player)
else:
for entrance in all_entrances:
self.world.spoiler.set_entrance(entrance, entrance.replaces, 'entrance', self.player)
@@ -1027,7 +1033,7 @@ class OOTWorld(World):
all_state = self.world.get_all_state(use_cache=False)
# Remove event progression items
for item, player in all_state.prog_items:
- if (item not in item_table or item_table[item][2] is None) and player == self.player:
+ if player == self.player and (item not in item_table or (item_table[item][2] is None and item_table[item][0] != 'DungeonReward')):
all_state.prog_items[(item, player)] = 0
# Remove all events and checked locations
all_state.locations_checked = {loc for loc in all_state.locations_checked if loc.player != self.player}
diff --git a/worlds/overcooked2/Items.py b/worlds/overcooked2/Items.py
new file mode 100644
index 0000000000..8cbe071140
--- /dev/null
+++ b/worlds/overcooked2/Items.py
@@ -0,0 +1,152 @@
+from BaseClasses import Item
+from typing import NamedTuple, Dict
+
+
+class ItemData(NamedTuple):
+ code: int
+
+
+class Overcooked2Item(Item):
+ game: str = "Overcooked! 2"
+
+
+oc2_base_id = 213700
+
+item_table: Dict[str, ItemData] = {
+ "Wood" : ItemData(oc2_base_id + 1 ),
+ "Coal Bucket" : ItemData(oc2_base_id + 2 ),
+ "Spare Plate" : ItemData(oc2_base_id + 3 ),
+ "Fire Extinguisher" : ItemData(oc2_base_id + 4 ),
+ "Bellows" : ItemData(oc2_base_id + 5 ),
+ "Clean Dishes" : ItemData(oc2_base_id + 6 ),
+ "Larger Tip Jar" : ItemData(oc2_base_id + 7 ),
+ "Progressive Dash" : ItemData(oc2_base_id + 8 ),
+ "Progressive Throw/Catch" : ItemData(oc2_base_id + 9 ),
+ "Coin Purse" : ItemData(oc2_base_id + 10),
+ "Control Stick Batteries" : ItemData(oc2_base_id + 11),
+ "Wok Wheels" : ItemData(oc2_base_id + 12),
+ "Dish Scrubber" : ItemData(oc2_base_id + 13),
+ "Burn Leniency" : ItemData(oc2_base_id + 14),
+ "Sharp Knife" : ItemData(oc2_base_id + 15),
+ "Order Lookahead" : ItemData(oc2_base_id + 16),
+ "Lightweight Backpack" : ItemData(oc2_base_id + 17),
+ "Faster Respawn Time" : ItemData(oc2_base_id + 18),
+ "Faster Condiment/Drink Switch" : ItemData(oc2_base_id + 19),
+ "Guest Patience" : ItemData(oc2_base_id + 20),
+ "Kevin-1" : ItemData(oc2_base_id + 21),
+ "Kevin-2" : ItemData(oc2_base_id + 22),
+ "Kevin-3" : ItemData(oc2_base_id + 23),
+ "Kevin-4" : ItemData(oc2_base_id + 24),
+ "Kevin-5" : ItemData(oc2_base_id + 25),
+ "Kevin-6" : ItemData(oc2_base_id + 26),
+ "Kevin-7" : ItemData(oc2_base_id + 27),
+ "Kevin-8" : ItemData(oc2_base_id + 28),
+ "Cooking Emote" : ItemData(oc2_base_id + 29),
+ "Curse Emote" : ItemData(oc2_base_id + 30),
+ "Serving Emote" : ItemData(oc2_base_id + 31),
+ "Preparing Emote" : ItemData(oc2_base_id + 32),
+ "Washing Up Emote" : ItemData(oc2_base_id + 33),
+ "Ok Emote" : ItemData(oc2_base_id + 34),
+ "Ramp Button" : ItemData(oc2_base_id + 35),
+ "Bonus Star" : ItemData(oc2_base_id + 36),
+ "Calmer Unbread" : ItemData(oc2_base_id + 37),
+}
+
+item_frequencies = {
+ "Progressive Throw/Catch": 2,
+ "Larger Tip Jar": 2,
+ "Order Lookahead": 2,
+ "Progressive Dash": 2,
+ "Bonus Star": 0, # Filler Item
+ # default: 1
+}
+
+item_name_to_config_name = {
+ "Wood" : "DisableWood" ,
+ "Coal Bucket" : "DisableCoal" ,
+ "Spare Plate" : "DisableOnePlate" ,
+ "Fire Extinguisher" : "DisableFireExtinguisher" ,
+ "Bellows" : "DisableBellows" ,
+ "Clean Dishes" : "PlatesStartDirty" ,
+ "Control Stick Batteries" : "DisableControlStick" ,
+ "Wok Wheels" : "DisableWokDrag" ,
+ "Dish Scrubber" : "WashTimeMultiplier" ,
+ "Burn Leniency" : "BurnSpeedMultiplier" ,
+ "Sharp Knife" : "ChoppingTimeScale" ,
+ "Lightweight Backpack" : "BackpackMovementScale" ,
+ "Faster Respawn Time" : "RespawnTime" ,
+ "Faster Condiment/Drink Switch": "CarnivalDispenserRefactoryTime",
+ "Guest Patience" : "CustomOrderLifetime" ,
+ "Ramp Button" : "DisableRampButton" ,
+ "Calmer Unbread" : "AggressiveHorde" ,
+ "Coin Purse" : "DisableEarnHordeMoney" ,
+}
+
+vanilla_values = {
+ "DisableWood": False,
+ "DisableCoal": False,
+ "DisableOnePlate": False,
+ "DisableFireExtinguisher": False,
+ "DisableBellows": False,
+ "PlatesStartDirty": False,
+ "DisableControlStick": False,
+ "DisableWokDrag": False,
+ "DisableRampButton": False,
+ "WashTimeMultiplier": 1.0,
+ "BurnSpeedMultiplier": 1.0,
+ "ChoppingTimeScale": 1.0,
+ "BackpackMovementScale": 1.0,
+ "RespawnTime": 5.0,
+ "CarnivalDispenserRefactoryTime": 0.0,
+ "CustomOrderLifetime": 100.0,
+ "AggressiveHorde": False,
+ "DisableEarnHordeMoney": False,
+}
+
+item_id_to_name: Dict[int, str] = {
+ data.code: item_name for item_name, data in item_table.items() if data.code
+}
+
+item_name_to_id: Dict[str, int] = {
+ item_name: data.code for item_name, data in item_table.items() if data.code
+}
+
+
+def is_progression(item_name: str) -> bool:
+ return not item_name.endswith("Emote")
+
+
+def item_to_unlock_event(item_name: str) -> Dict[str, str]:
+ message = f"{item_name} Acquired!"
+ action = ""
+ payload = ""
+ if item_name.startswith("Kevin"):
+ kevin_num = int(item_name.split("-")[-1])
+ action = "UNLOCK_LEVEL"
+ payload = str(kevin_num + 36)
+ elif "Emote" in item_name:
+ action = "UNLOCK_EMOTE"
+ payload = str(item_table[item_name].code - item_table["Cooking Emote"].code)
+ elif item_name == "Larger Tip Jar":
+ action = "INC_TIP_COMBO"
+ elif item_name == "Order Lookahead":
+ action = "INC_ORDERS_ON_SCREEN"
+ elif item_name == "Bonus Star":
+ action = "INC_STAR_COUNT"
+ payload = "1"
+ elif item_name == "Progressive Dash":
+ action = "INC_DASH"
+ elif item_name == "Progressive Throw/Catch":
+ action = "INC_THROW"
+ else:
+ config_name = item_name_to_config_name[item_name]
+ vanilla_value = vanilla_values[config_name]
+
+ action = "SET_VALUE"
+ payload = f"{config_name}={vanilla_value}"
+
+ return {
+ "message": message,
+ "action": action,
+ "payload": payload,
+ }
diff --git a/worlds/overcooked2/Locations.py b/worlds/overcooked2/Locations.py
new file mode 100644
index 0000000000..1b73b74e94
--- /dev/null
+++ b/worlds/overcooked2/Locations.py
@@ -0,0 +1,15 @@
+from BaseClasses import Location
+from .Overcooked2Levels import Overcooked2Level
+
+
+class Overcooked2Location(Location):
+ game: str = "Overcooked! 2"
+
+
+oc2_location_name_to_id = dict()
+oc2_location_id_to_name = dict()
+for level in Overcooked2Level():
+ if level.level_id == 36:
+ continue # level 6-6 does not have an item location
+ oc2_location_name_to_id[level.location_name_item] = level.level_id
+ oc2_location_id_to_name[level.level_id] = level.location_name_item
diff --git a/worlds/overcooked2/Logic.py b/worlds/overcooked2/Logic.py
new file mode 100644
index 0000000000..6fb1a50a41
--- /dev/null
+++ b/worlds/overcooked2/Logic.py
@@ -0,0 +1,3899 @@
+from BaseClasses import CollectionState
+from .Overcooked2Levels import Overcooked2GenericLevel, Overcooked2Dlc, Overcooked2Level
+from typing import Dict
+from random import Random
+
+
+def has_requirements_for_level_access(state: CollectionState, level_name: str, previous_level_completed_event_name: str,
+ required_star_count: int, player: int) -> bool:
+ # Check if the ramps in the overworld are set correctly
+ if level_name in ramp_logic:
+ if not state.has("Ramp Button", player):
+ return False # need the item to use ramps
+
+ for req in ramp_logic[level_name]:
+ if not state.has(req + " Level Complete", player):
+ return False # This level needs another to be beaten first
+
+ # Kevin Levels Need to have the corresponding items
+ if level_name.startswith("K"):
+ return state.has(level_name, player)
+
+ # Must have enough stars to purchase level
+ star_count = state.item_count("Star", player) + state.item_count("Bonus Star", player)
+ if star_count < required_star_count:
+ return False
+
+ # If this isn't the first level in a world, it needs the previous level to be unlocked first
+ if previous_level_completed_event_name is not None:
+ if not state.has(previous_level_completed_event_name, player):
+ return False
+
+ # If we made it this far we have all requirements
+ return True
+
+
+def has_requirements_for_level_star(
+ state: CollectionState, level: Overcooked2GenericLevel, stars: int, player: int) -> bool:
+ assert 0 <= stars <= 3
+
+ # First ensure that previous stars are obtainable
+ if stars > 1:
+ if not has_requirements_for_level_star(state, level, stars-1, player):
+ return False
+
+ # Second, ensure that global requirements are met
+ if not meets_requirements(state, "*", stars, player):
+ return False
+
+ # Finally, return success only if this level's requirements are met
+ return meets_requirements(state, level.shortname, stars, player)
+
+
+def meets_requirements(state: CollectionState, name: str, stars: int, player: int):
+ # Get requirements for level
+ (exclusive_reqs, additive_reqs) = level_logic[name][stars-1]
+
+ # print(f"{name} ({stars}-Stars): {exclusive_reqs}|{additive_reqs}")
+
+ # Check if we meet exclusive requirements
+ if len(exclusive_reqs) > 0 and not state.has_all(exclusive_reqs, player):
+ return False
+
+ # Check if we meet additive requirements
+ if len(additive_reqs) == 0:
+ return True
+
+ total: float = 0.0
+ for (item_name, weight) in additive_reqs:
+ for _ in range(0, state.item_count(item_name, player)):
+ total += weight
+ if total >= 0.99: # be nice to rounding errors :)
+ return True
+
+ return False
+
+
+def is_item_progression(item_name, level_mapping, include_kevin):
+ if item_name.endswith("Emote"):
+ return False
+
+ if "Kevin" in item_name or item_name in ["Ramp Button"]:
+ return True # always progression
+
+ def item_in_logic(shortname, _item_name):
+ for star in range(0, 3):
+ (exclusive, additive) = level_logic[shortname][star]
+
+ if _item_name in exclusive:
+ return True
+
+ for req in additive:
+ if req[0] == _item_name:
+ if req[1] > 0.3: # this bit smells of a deal with the devil, but it seems to be for the better
+ return True
+ break
+
+ return False
+
+ if item_in_logic("*", item_name):
+ return True
+
+ for level in Overcooked2Level():
+ if not include_kevin and level.level_id > 36:
+ break
+
+ if level_mapping is None:
+ unmapped_level = Overcooked2GenericLevel(level.level_id)
+ else:
+ unmapped_level = level_mapping[level.level_id]
+
+ if item_in_logic(unmapped_level.shortname, item_name):
+ return True
+
+ return False
+
+
+def is_useful(item_name):
+ return item_name in [
+ "Faster Respawn Time",
+ "Fire Extinguisher",
+ "Clean Dishes",
+ "Larger Tip Jar",
+ "Dish Scrubber",
+ "Burn Leniency",
+ "Sharp Knife",
+ "Order Lookahead",
+ "Guest Patience",
+ "Bonus Star",
+ ]
+
+
+def level_shuffle_factory(
+ rng: Random,
+ shuffle_prep_levels: bool,
+ shuffle_horde_levels: bool,
+) -> Dict[int, Overcooked2GenericLevel]: # return
+ # Create a list of all valid levels for selection
+ # (excludes tutorial, throne, kevin and sometimes horde levels)
+ pool = list()
+ for dlc in Overcooked2Dlc:
+ for level_id in range(dlc.start_level_id(), dlc.end_level_id()):
+ if level_id in dlc.excluded_levels():
+ continue
+
+ if not shuffle_horde_levels and level_id in dlc.horde_levels():
+ continue
+
+ if not shuffle_prep_levels and level_id in dlc.prep_levels():
+ continue
+
+ pool.append(
+ Overcooked2GenericLevel(level_id, dlc)
+ )
+
+ # Sort the pool to eliminate risk
+ pool.sort(key=lambda x: int(x.dlc)*1000 + x.level_id)
+
+ result: Dict[int, Overcooked2GenericLevel] = dict()
+ story = Overcooked2Dlc.STORY
+
+ while len(result) == 0 or not meets_minimum_sphere_one_requirements(result):
+ result.clear()
+
+ # Shuffle the pool, using the provided RNG
+ rng.shuffle(pool)
+
+ # Return the first 44 levels and assign those to each level
+ for level_id in range(story.start_level_id(), story.end_level_id()):
+ if level_id not in story.excluded_levels():
+ result[level_id] = pool[level_id-1]
+ else:
+ result[level_id] = Overcooked2GenericLevel(level_id) # This is just 6-6 right now
+
+ return result
+
+
+def meets_minimum_sphere_one_requirements(
+ levels: Dict[int, Overcooked2GenericLevel],
+) -> bool:
+
+ # 1-1, 2-1, and 4-1 are garunteed to be accessible on
+ # the overworld without requiring a ramp or additional stars
+ sphere_one = [1, 7, 19]
+
+ # 1-2, 2-2, 3-1 and 5-1 are almost always the next thing unlocked
+ sphere_twoish = [2, 8, 13, 25]
+
+ # Peek the logic for sphere one and see how many are possible
+ # with no items
+ sphere_one_count = 0
+ for level_id in sphere_one:
+ if (is_completable_no_items(levels[level_id])):
+ sphere_one_count += 1
+
+ sphere_twoish_count = 0
+ for level_id in sphere_twoish:
+ if (is_completable_no_items(levels[level_id])):
+ sphere_twoish_count += 1
+
+ return sphere_one_count >= 2 and \
+ sphere_twoish_count >= 2 and \
+ sphere_one_count + sphere_twoish_count >= 6
+
+
+def is_completable_no_items(level: Overcooked2GenericLevel) -> bool:
+ one_star_logic = level_logic[level.shortname][0]
+ (exclusive, additive) = one_star_logic
+
+ # print(f"\n{level.shortname}: {exclusive} / {additive}")
+
+ return len(exclusive) == 0 and len(additive) == 0
+
+
+# If key missing, doesn't require a ramp to access (or the logic is handled by a preceeding level)
+#
+# If empty, a ramp is required to access, but the ramp button is garunteed accessible
+#
+# If populated, a ramp is required to access and the button requires all levels in the
+# list to be compelted before it can be pressed
+#
+ramp_logic = {
+ "1-5": [],
+ "2-2": [],
+ "3-1": [],
+ "5-2": [],
+ "6-1": [],
+ "6-2": ["5-1"], # 5-1 spawns blue button, blue button gets you to red button
+ "Kevin-1": [],
+ "Kevin-7": ["5-1"], # 5-1 spawns blue button,
+ # press blue button,
+ # climb blue ramp,
+ # jump the gap,
+ # climb wood ramps
+ "Kevin-8": ["5-1", "6-2"], # Same as above, but 6-2 spawns the ramp to K8
+}
+
+horde_logic = { # Additive
+ ("Coin Purse", 0.7),
+ ("Calmer Unbread", 0.35),
+ ("Progressive Dash", 0.2),
+ ("Progressive Throw/Catch", 0.15),
+ ("Sharp Knife", 0.15),
+ ("Dish Scrubber", 0.125),
+ ("Burn Leniency", 0.1),
+ ("Spare Plate", 0.075),
+ ("Clean Dishes", 0.025),
+}
+
+# Level 1 - dict keyed by friendly level names
+# Level 2 - tuple with 3 elements, one for each star requirement
+# Level 3 - tuple with 2 elements, one for exclusive requirements and one for additive requirements
+# Level 4 (exclusive) - set of item name strings of items which MUST be in the inventory to allow logical completion
+# Level 4 (additive) - list of tuples containing item name and item weight where the sum of which are in the player's inventory
+# must be 1.0+ to allow logical completion
+#
+# Each Star's logical requirements imply any previous requirements
+#
+level_logic = {
+ # "Tutorial": [],
+ "*": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+ ("Progressive Throw/Catch", 0.4),
+ ("Progressive Dash", 0.35),
+ ("Sharp Knife", 0.3),
+ ("Dish Scrubber", 0.25),
+ ("Larger Tip Jar", 0.2),
+ ("Spare Plate", 0.2),
+ ("Burn Leniency", 0.15),
+ ("Order Lookahead", 0.15),
+ ("Clean Dishes", 0.1),
+ ("Guest Patience", 0.1),
+ },
+ ),
+ ( # 3-star
+ [ # Exclusive
+ "Progressive Dash",
+ "Spare Plate",
+ "Larger Tip Jar",
+ "Progressive Throw/Catch",
+ ],
+ { # Additive
+ },
+ )
+ ),
+ "Story 1-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 1-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 1-3": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 1-4": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 1-5": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 1-6": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 2-1": (
+ ( # 1-star
+ { # Exclusive
+ "Progressive Throw/Catch",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 2-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 2-3": (
+ ( # 1-star
+ { # Exclusive
+ "Progressive Throw/Catch"
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 2-4": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+ ("Progressive Throw/Catch", 1.0),
+ ("Progressive Dash", 1.0),
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+ "Fire Extinguisher",
+ },
+ [ # Additive
+
+ ]
+ )
+ ),
+ "Story 2-5": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 2-6": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 3-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 3-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+ ("Progressive Throw/Catch", 1.0),
+ ("Progressive Dash", 0.5),
+ ("Sharp Knife", 0.5),
+ ("Larger Tip Jar", 0.25),
+ ("Dish Scrubber", 0.25),
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Progressive Throw/Catch",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 3-3": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 3-4": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Progressive Throw/Catch",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 3-5": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Progressive Throw/Catch",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 3-6": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 4-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 4-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Progressive Throw/Catch",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 4-3": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 4-4": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 4-5": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 4-6": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 5-1": (
+ ( # 1-star
+ { # Exclusive
+ "Control Stick Batteries"
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 5-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Fire Extinguisher",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 5-3": (
+ ( # 1-star
+ { # Exclusive
+ "Control Stick Batteries"
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 5-4": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 5-5": (
+ ( # 1-star
+ { # Exclusive
+ "Control Stick Batteries"
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 5-6": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 6-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+ "Fire Extinguisher",
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 6-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 6-3": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 6-4": (
+ ( # 1-star
+ { # Exclusive
+ "Control Stick Batteries"
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 6-5": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story 6-6": (
+ ( # 1-star
+ { # Exclusive
+ "Progressive Throw/Catch",
+ "Progressive Dash",
+ "Spare Plate",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story K-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story K-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story K-3": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story K-4": (
+ ( # 1-star
+ { # Exclusive
+ "Fire Extinguisher",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story K-5": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story K-6": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story K-7": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Story K-8": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Surf 1-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Surf 1-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Surf 1-3": (
+ ( # 1-star
+ { # Exclusive
+ "Progressive Throw/Catch",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Surf 1-4": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Surf 2-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Bellows",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Surf 2-2": (
+ ( # 1-star
+ { # Exclusive
+ "Control Stick Batteries"
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Bellows",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Surf 2-3": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Surf 2-4": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Surf 3-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Bellows",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Surf 3-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Surf 3-3": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Surf 3-4": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Bellows",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Surf K-1": (
+ ( # 1-star
+ { # Exclusive
+ "Control Stick Batteries"
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Progressive Throw/Catch",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Campfire 1-1": (
+ ( # 1-star
+ { # Exclusive
+ "Wood"
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Campfire 1-2": (
+ ( # 1-star
+ { # Exclusive
+ "Wood"
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Campfire 1-3": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+ "Lightweight Backpack"
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Campfire 1-4": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Campfire 2-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Campfire 2-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+ "Lightweight Backpack"
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Campfire 2-3": (
+ ( # 1-star
+ { # Exclusive
+ "Wood"
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+ "Lightweight Backpack"
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Campfire 2-4": (
+ ( # 1-star
+ { # Exclusive
+ "Wood"
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Campfire 3-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+ "Lightweight Backpack"
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Campfire 3-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Campfire 3-3": (
+ ( # 1-star
+ { # Exclusive
+ "Wood",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Lightweight Backpack",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Campfire 3-4": (
+ ( # 1-star
+ { # Exclusive
+ "Wood",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Campfire K-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Campfire K-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Campfire K-3": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Carnival 1-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Carnival 1-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Carnival 1-3": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Carnival 1-4": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+ "Faster Condiment/Drink Switch"
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Carnival 2-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Carnival 2-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+ "Faster Condiment/Drink Switch"
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Carnival 2-3": (
+ ( # 1-star
+ { # Exclusive
+ "Control Stick Batteries"
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Carnival 2-4": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+ "Faster Condiment/Drink Switch"
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Carnival 3-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Carnival 3-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+ "Faster Condiment/Drink Switch"
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Carnival 3-3": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+ "Faster Condiment/Drink Switch"
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Carnival 3-4": (
+ ( # 1-star
+ { # Exclusive
+ "Control Stick Batteries"
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+ "Faster Condiment/Drink Switch"
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Carnival K-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Carnival K-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Carnival K-3": (
+ ( # 1-star
+ { # Exclusive
+ "Control Stick Batteries"
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Horde 1-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ }
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Horde 1-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ horde_logic
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Coal Bucket",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Horde 1-3": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Coal Bucket",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Horde 2-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Horde 2-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Horde 2-3": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Coal Bucket",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Horde 3-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Horde 3-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Horde 3-3": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+ ("Progressive Throw/Catch", 0.5),
+ ("Progressive Dash", 0.5),
+ ("Coal Bucket", 0.5),
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Progressive Throw/Catch",
+ "Coal Bucket",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Horde K-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Horde K-2": (
+ ( # 1-star
+ { # Exclusive
+ "Control Stick Batteries"
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Horde K-3": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Horde H-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ horde_logic,
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Horde H-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ horde_logic,
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Horde H-3": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ horde_logic,
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Horde H-4": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ horde_logic,
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Horde H-5": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ horde_logic,
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Horde H-6": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ horde_logic,
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Horde H-7": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ horde_logic,
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Horde H-8": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ horde_logic,
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Christmas 1-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Christmas 1-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Christmas 1-3": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Christmas 1-4": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Christmas 1-5": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Chinese 1-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Chinese 1-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Wok Wheels"
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Chinese 1-3": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Chinese 1-4": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Wok Wheels"
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Chinese 1-5": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Chinese 1-6": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Chinese 1-7": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Wok Wheels"
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Winter 1-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Winter H-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ horde_logic,
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Winter 1-3": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Winter H-4": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ horde_logic,
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Winter 1-5": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Spring 1-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Wok Wheels"
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Spring 1-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Spring 1-3": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Wok Wheels"
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Spring 1-4": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Spring 1-5": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "SOBO 1-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+ "Faster Condiment/Drink Switch"
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "SOBO 1-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+ "Faster Condiment/Drink Switch"
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "SOBO 1-3": (
+ ( # 1-star
+ { # Exclusive
+ "Control Stick Batteries"
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Fire Extinguisher",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "SOBO 1-4": (
+ ( # 1-star
+ { # Exclusive
+ "Fire Extinguisher",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+ "Faster Condiment/Drink Switch"
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "SOBO 1-5": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+ "Fire Extinguisher",
+ "Faster Condiment/Drink Switch",
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Moon 1-1": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Moon 1-2": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Moon 1-3": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Moon 1-4": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+ "Moon 1-5": (
+ ( # 1-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 2-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ ),
+ ( # 3-star
+ { # Exclusive
+
+ },
+ { # Additive
+
+ },
+ )
+ ),
+}
diff --git a/worlds/overcooked2/Options.py b/worlds/overcooked2/Options.py
new file mode 100644
index 0000000000..78e0fd6e90
--- /dev/null
+++ b/worlds/overcooked2/Options.py
@@ -0,0 +1,110 @@
+from typing import TypedDict
+from Options import DefaultOnToggle, Range, Choice
+
+
+class OC2OnToggle(DefaultOnToggle):
+ @property
+ def result(self) -> bool:
+ return bool(self.value)
+
+
+class AlwaysServeOldestOrder(OC2OnToggle):
+ """Modifies the game so that serving an expired order doesn't target the ticket with the highest tip. This helps players dig out of a broken tip combo faster."""
+ display_name = "Always Serve Oldest Order"
+
+
+class AlwaysPreserveCookingProgress(OC2OnToggle):
+ """Modifies the game to behave more like AYCE, where adding an item to an in-progress container doesn't reset the entire progress bar."""
+ display_name = "Preserve Cooking/Mixing Progress"
+
+
+class DisplayLeaderboardScores(OC2OnToggle):
+ """Modifies the Overworld map to fetch and display the current world records for each level. Press number keys 1-4 to view leaderboard scores for that number of players."""
+ display_name = "Display Leaderboard Scores"
+
+
+class ShuffleLevelOrder(OC2OnToggle):
+ """Shuffles the order of kitchens on the overworld map. Also draws from DLC maps."""
+ display_name = "Shuffle Level Order"
+
+
+class IncludeHordeLevels(OC2OnToggle):
+ """Includes "Horde Defence" levels in the pool of possible kitchens when Shuffle Level Order is enabled. Also adds two horde-specific items into the item pool."""
+ display_name = "Include Horde Levels"
+
+
+class KevinLevels(OC2OnToggle):
+ """Includes the 8 Kevin level locations on the map as unlockables. Turn off to make games shorter."""
+ display_name = "Kevin Level Checks"
+
+
+class FixBugs(OC2OnToggle):
+ """Fixes Bugs Present in the base game:
+ - Double Serving Exploit
+ - Sink Bug
+ - Control Stick Cancel/Throw Bug
+ - Can't Throw Near Empty Burner Bug"""
+ display_name = "Fix Bugs"
+
+
+class ShorterLevelDuration(OC2OnToggle):
+ """Modifies level duration to be about 1/3rd shorter than in the original game, thus bringing the item discovery pace in line with other popular Archipelago games.
+
+ Points required to earn stars are scaled accordingly. ("Boss Levels" which change scenery mid-game are not affected.)"""
+ display_name = "Shorter Level Duration"
+
+
+class PrepLevels(Choice):
+ """Choose How "Prep Levels" are handled (levels where the timer does not start until the first order is served):
+
+ - Original: Prep Levels may appear
+
+ - Excluded: Prep Levels are excluded from the pool during level shuffling
+
+ - All You Can Eat: Prep Levels may appear, but the timer automatically starts. The star score requirements are also adjusted to use the All You Can Eat World Record (if it exists)"""
+ auto_display_name = True
+ display_name = "Prep Level Behavior"
+ option_original = 0
+ option_excluded = 1
+ option_all_you_can_eat = 2
+ default = 1
+
+
+class StarsToWin(Range):
+ """Number of stars required to unlock 6-6.
+
+ Level purchase requirements between 1-1 and 6-6 will be spread between these two numbers. Using too high of a number may result in more frequent generation failures, especially when horde levels are enabled."""
+ display_name = "Stars to Win"
+ range_start = 0
+ range_end = 100
+ default = 66
+
+
+class StarThresholdScale(Range):
+ """How difficult should the third star for each level be on a scale of 1-100%, where 100% is the current world record score and 45% is the average vanilla 4-star score."""
+ display_name = "Star Difficulty %"
+ range_start = 1
+ range_end = 100
+ default = 45
+
+
+overcooked_options = {
+ # randomization options
+ "shuffle_level_order": ShuffleLevelOrder,
+ "include_horde_levels": IncludeHordeLevels,
+ "prep_levels": PrepLevels,
+ "kevin_levels": KevinLevels,
+
+ # quality of life options
+ "fix_bugs": FixBugs,
+ "shorter_level_duration": ShorterLevelDuration,
+ "always_preserve_cooking_progress": AlwaysPreserveCookingProgress,
+ "always_serve_oldest_order": AlwaysServeOldestOrder,
+ "display_leaderboard_scores": DisplayLeaderboardScores,
+
+ # difficulty settings
+ "stars_to_win": StarsToWin,
+ "star_threshold_scale": StarThresholdScale,
+}
+
+OC2Options = TypedDict("OC2Options", {option.__name__: option for option in overcooked_options.values()})
diff --git a/worlds/overcooked2/Overcooked2Levels.py b/worlds/overcooked2/Overcooked2Levels.py
new file mode 100644
index 0000000000..aac9ea0cbe
--- /dev/null
+++ b/worlds/overcooked2/Overcooked2Levels.py
@@ -0,0 +1,349 @@
+from enum import Enum
+from typing import List
+
+
+class Overcooked2Dlc(Enum):
+ STORY = "Story"
+ SURF_N_TURF = "Surf 'n' Turf"
+ CAMPFIRE_COOK_OFF = "Campfire Cook Off"
+ NIGHT_OF_THE_HANGRY_HORDE = "Night of the Hangry Horde"
+ CARNIVAL_OF_CHAOS = "Carnival of Chaos"
+ SEASONAL = "Seasonal"
+ # CHRISTMAS = "Christmas"
+ # CHINESE_NEW_YEAR = "Chinese New Year"
+ # WINTER_WONDERLAND = "Winter Wonderland"
+ # MOON_HARVEST = "Moon Harvest"
+ # SPRING_FRESTIVAL = "Spring Festival"
+ # SUNS_OUT_BUNS_OUT = "Sun's Out Buns Out"
+
+ def __int__(self) -> int:
+ if self == Overcooked2Dlc.STORY:
+ return 0
+ if self == Overcooked2Dlc.SURF_N_TURF:
+ return 1
+ if self == Overcooked2Dlc.CAMPFIRE_COOK_OFF:
+ return 2
+ if self == Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE:
+ return 3
+ if self == Overcooked2Dlc.CARNIVAL_OF_CHAOS:
+ return 4
+ if self == Overcooked2Dlc.SEASONAL:
+ return 5
+ assert False
+
+ # inclusive
+ def start_level_id(self) -> int:
+ if self == Overcooked2Dlc.STORY:
+ return 1
+ return 0
+
+ # exclusive
+ def end_level_id(self) -> int:
+ id = None
+ if self == Overcooked2Dlc.STORY:
+ id = 6*6 + 8 # world_count*level_count + kevin count
+ if self == Overcooked2Dlc.SURF_N_TURF:
+ id = 3*4 + 1
+ if self == Overcooked2Dlc.CAMPFIRE_COOK_OFF:
+ id = 3*4 + 3
+ if self == Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE:
+ id = 3*3 + 3 + 8
+ if self == Overcooked2Dlc.CARNIVAL_OF_CHAOS:
+ id = 3*4 + 3
+ if self == Overcooked2Dlc.SEASONAL:
+ id = 31
+
+ return self.start_level_id() + id
+
+ # Tutorial + Horde Levels + Endgame
+ def excluded_levels(self) -> List[int]:
+ if self == Overcooked2Dlc.STORY:
+ return [0, 36]
+
+ return []
+
+ def horde_levels(self) -> List[int]:
+ if self == Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE:
+ return [12, 13, 14, 15, 16, 17, 18, 19]
+ if self == Overcooked2Dlc.SEASONAL:
+ return [13, 15]
+
+ return []
+
+ def prep_levels(self) -> List[int]:
+ if self == Overcooked2Dlc.STORY:
+ return [1, 2, 5, 10, 12, 13, 28, 31]
+ if self == Overcooked2Dlc.SURF_N_TURF:
+ return [0, 4]
+ if self == Overcooked2Dlc.CAMPFIRE_COOK_OFF:
+ return [0, 2, 4, 9]
+ if self == Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE:
+ return [0, 1, 4]
+ if self == Overcooked2Dlc.CARNIVAL_OF_CHAOS:
+ return [0, 1, 3, 4, 5]
+ if self == Overcooked2Dlc.SEASONAL:
+ # moon 1-1 is a prep level for 1P only, but we can't make that assumption here
+ return [0, 1, 5, 6, 12, 14, 16, 17, 18, 22, 23, 24, 27, 29]
+
+ return []
+
+
+class Overcooked2GameWorld(Enum):
+ ONE = 1
+ TWO = 2
+ THREE = 3
+ FOUR = 4
+ FIVE = 5
+ SIX = 6
+ KEVIN = 7
+
+ @property
+ def as_str(self) -> str:
+ if self == Overcooked2GameWorld.KEVIN:
+ return "Kevin"
+
+ return str(int(self.value))
+
+ @property
+ def sublevel_count(self) -> int:
+ if self == Overcooked2GameWorld.KEVIN:
+ return 8
+
+ return 6
+
+ @property
+ def base_id(self) -> int:
+ if self == Overcooked2GameWorld.ONE:
+ return 1
+
+ prev = Overcooked2GameWorld(self.value - 1)
+ return prev.base_id + prev.sublevel_count
+
+ @property
+ def name(self) -> str:
+ if self == Overcooked2GameWorld.KEVIN:
+ return "Kevin"
+
+ return "World " + self.as_str
+
+
+class Overcooked2GenericLevel():
+ dlc: Overcooked2Dlc
+ level_id: int
+
+ def __init__(self, level_id: int, dlc: Overcooked2Dlc = Overcooked2Dlc("Story")):
+ self.dlc = dlc
+ self.level_id = level_id
+
+ def __str__(self) -> str:
+ return f"{self.dlc.value}|{self.level_id}"
+
+ def __repr__(self) -> str:
+ return f"{self}"
+
+ @property
+ def shortname(self) -> str:
+ return level_id_to_shortname[(self.dlc, self.level_id)]
+
+ @property
+ def is_horde(self) -> bool:
+ return self.level_id in self.dlc.horde_levels()
+
+
+class Overcooked2Level:
+ """
+ Abstraction for a playable levels in Overcooked 2. By default constructor
+ it can be used as an iterator for all locations in the Story map.
+ """
+ world: Overcooked2GameWorld
+ sublevel: int
+
+ def __init__(self):
+ self.world = Overcooked2GameWorld.ONE
+ self.sublevel = 0
+
+ def __iter__(self):
+ return self
+
+ def __next__(self):
+ self.sublevel += 1
+ if self.sublevel > self.world.sublevel_count:
+ if self.world == Overcooked2GameWorld.KEVIN:
+ raise StopIteration
+ self.world = Overcooked2GameWorld(self.world.value + 1)
+ self.sublevel = 1
+
+ return self
+
+ @property
+ def level_id(self) -> int:
+ return self.world.base_id + (self.sublevel - 1)
+
+ @property
+ def level_name(self) -> str:
+ return self.world.as_str + "-" + str(self.sublevel)
+
+ @property
+ def location_name_item(self) -> str:
+ return self.level_name + " Completed"
+
+ @property
+ def location_name_level_complete(self) -> str:
+ return self.level_name + " Level Completed"
+
+ @property
+ def event_name_level_complete(self) -> str:
+ return self.level_name + " Level Complete"
+
+ def location_name_star_event(self, stars: int) -> str:
+ return "%s (%d-Star)" % (self.level_name, stars)
+
+ @property
+ def as_generic_level(self) -> Overcooked2GenericLevel:
+ return Overcooked2GenericLevel(self.level_id)
+
+
+# Note that there are valid levels beyond what is listed here, but they are all
+# Onion King Dialogs
+level_id_to_shortname = {
+ (Overcooked2Dlc.STORY , 0 ): "Tutorial" ,
+ (Overcooked2Dlc.STORY , 1 ): "Story 1-1" ,
+ (Overcooked2Dlc.STORY , 2 ): "Story 1-2" ,
+ (Overcooked2Dlc.STORY , 3 ): "Story 1-3" ,
+ (Overcooked2Dlc.STORY , 4 ): "Story 1-4" ,
+ (Overcooked2Dlc.STORY , 5 ): "Story 1-5" ,
+ (Overcooked2Dlc.STORY , 6 ): "Story 1-6" ,
+ (Overcooked2Dlc.STORY , 7 ): "Story 2-1" ,
+ (Overcooked2Dlc.STORY , 8 ): "Story 2-2" ,
+ (Overcooked2Dlc.STORY , 9 ): "Story 2-3" ,
+ (Overcooked2Dlc.STORY , 10 ): "Story 2-4" ,
+ (Overcooked2Dlc.STORY , 11 ): "Story 2-5" ,
+ (Overcooked2Dlc.STORY , 12 ): "Story 2-6" ,
+ (Overcooked2Dlc.STORY , 13 ): "Story 3-1" ,
+ (Overcooked2Dlc.STORY , 14 ): "Story 3-2" ,
+ (Overcooked2Dlc.STORY , 15 ): "Story 3-3" ,
+ (Overcooked2Dlc.STORY , 16 ): "Story 3-4" ,
+ (Overcooked2Dlc.STORY , 17 ): "Story 3-5" ,
+ (Overcooked2Dlc.STORY , 18 ): "Story 3-6" ,
+ (Overcooked2Dlc.STORY , 19 ): "Story 4-1" ,
+ (Overcooked2Dlc.STORY , 20 ): "Story 4-2" ,
+ (Overcooked2Dlc.STORY , 21 ): "Story 4-3" ,
+ (Overcooked2Dlc.STORY , 22 ): "Story 4-4" ,
+ (Overcooked2Dlc.STORY , 23 ): "Story 4-5" ,
+ (Overcooked2Dlc.STORY , 24 ): "Story 4-6" ,
+ (Overcooked2Dlc.STORY , 25 ): "Story 5-1" ,
+ (Overcooked2Dlc.STORY , 26 ): "Story 5-2" ,
+ (Overcooked2Dlc.STORY , 27 ): "Story 5-3" ,
+ (Overcooked2Dlc.STORY , 28 ): "Story 5-4" ,
+ (Overcooked2Dlc.STORY , 29 ): "Story 5-5" ,
+ (Overcooked2Dlc.STORY , 30 ): "Story 5-6" ,
+ (Overcooked2Dlc.STORY , 31 ): "Story 6-1" ,
+ (Overcooked2Dlc.STORY , 32 ): "Story 6-2" ,
+ (Overcooked2Dlc.STORY , 33 ): "Story 6-3" ,
+ (Overcooked2Dlc.STORY , 34 ): "Story 6-4" ,
+ (Overcooked2Dlc.STORY , 35 ): "Story 6-5" ,
+ (Overcooked2Dlc.STORY , 36 ): "Story 6-6" ,
+ (Overcooked2Dlc.STORY , 37 ): "Story K-1" ,
+ (Overcooked2Dlc.STORY , 38 ): "Story K-2" ,
+ (Overcooked2Dlc.STORY , 39 ): "Story K-3" ,
+ (Overcooked2Dlc.STORY , 40 ): "Story K-4" ,
+ (Overcooked2Dlc.STORY , 41 ): "Story K-5" ,
+ (Overcooked2Dlc.STORY , 42 ): "Story K-6" ,
+ (Overcooked2Dlc.STORY , 43 ): "Story K-7" ,
+ (Overcooked2Dlc.STORY , 44 ): "Story K-8" ,
+ (Overcooked2Dlc.SURF_N_TURF , 0 ): "Surf 1-1" ,
+ (Overcooked2Dlc.SURF_N_TURF , 1 ): "Surf 1-2" ,
+ (Overcooked2Dlc.SURF_N_TURF , 2 ): "Surf 1-3" ,
+ (Overcooked2Dlc.SURF_N_TURF , 3 ): "Surf 1-4" ,
+ (Overcooked2Dlc.SURF_N_TURF , 4 ): "Surf 2-1" ,
+ (Overcooked2Dlc.SURF_N_TURF , 5 ): "Surf 2-2" ,
+ (Overcooked2Dlc.SURF_N_TURF , 6 ): "Surf 2-3" ,
+ (Overcooked2Dlc.SURF_N_TURF , 7 ): "Surf 2-4" ,
+ (Overcooked2Dlc.SURF_N_TURF , 8 ): "Surf 3-1" ,
+ (Overcooked2Dlc.SURF_N_TURF , 9 ): "Surf 3-2" ,
+ (Overcooked2Dlc.SURF_N_TURF , 10 ): "Surf 3-3" ,
+ (Overcooked2Dlc.SURF_N_TURF , 11 ): "Surf 3-4" ,
+ (Overcooked2Dlc.SURF_N_TURF , 12 ): "Surf K-1" ,
+ (Overcooked2Dlc.CAMPFIRE_COOK_OFF , 0 ): "Campfire 1-1" ,
+ (Overcooked2Dlc.CAMPFIRE_COOK_OFF , 1 ): "Campfire 1-2" ,
+ (Overcooked2Dlc.CAMPFIRE_COOK_OFF , 2 ): "Campfire 1-3" ,
+ (Overcooked2Dlc.CAMPFIRE_COOK_OFF , 3 ): "Campfire 1-4" ,
+ (Overcooked2Dlc.CAMPFIRE_COOK_OFF , 4 ): "Campfire 2-1" ,
+ (Overcooked2Dlc.CAMPFIRE_COOK_OFF , 5 ): "Campfire 2-2" ,
+ (Overcooked2Dlc.CAMPFIRE_COOK_OFF , 6 ): "Campfire 2-3" ,
+ (Overcooked2Dlc.CAMPFIRE_COOK_OFF , 7 ): "Campfire 2-4" ,
+ (Overcooked2Dlc.CAMPFIRE_COOK_OFF , 8 ): "Campfire 3-1" ,
+ (Overcooked2Dlc.CAMPFIRE_COOK_OFF , 9 ): "Campfire 3-2" ,
+ (Overcooked2Dlc.CAMPFIRE_COOK_OFF , 10 ): "Campfire 3-3" ,
+ (Overcooked2Dlc.CAMPFIRE_COOK_OFF , 11 ): "Campfire 3-4" ,
+ (Overcooked2Dlc.CAMPFIRE_COOK_OFF , 12 ): "Campfire K-1" ,
+ (Overcooked2Dlc.CAMPFIRE_COOK_OFF , 13 ): "Campfire K-2" ,
+ (Overcooked2Dlc.CAMPFIRE_COOK_OFF , 14 ): "Campfire K-3" ,
+ (Overcooked2Dlc.CARNIVAL_OF_CHAOS , 0 ): "Carnival 1-1" ,
+ (Overcooked2Dlc.CARNIVAL_OF_CHAOS , 1 ): "Carnival 1-2" ,
+ (Overcooked2Dlc.CARNIVAL_OF_CHAOS , 2 ): "Carnival 1-3" ,
+ (Overcooked2Dlc.CARNIVAL_OF_CHAOS , 3 ): "Carnival 1-4" ,
+ (Overcooked2Dlc.CARNIVAL_OF_CHAOS , 4 ): "Carnival 2-1" ,
+ (Overcooked2Dlc.CARNIVAL_OF_CHAOS , 5 ): "Carnival 2-2" ,
+ (Overcooked2Dlc.CARNIVAL_OF_CHAOS , 6 ): "Carnival 2-3" ,
+ (Overcooked2Dlc.CARNIVAL_OF_CHAOS , 7 ): "Carnival 2-4" ,
+ (Overcooked2Dlc.CARNIVAL_OF_CHAOS , 8 ): "Carnival 3-1" ,
+ (Overcooked2Dlc.CARNIVAL_OF_CHAOS , 9 ): "Carnival 3-2" ,
+ (Overcooked2Dlc.CARNIVAL_OF_CHAOS , 10 ): "Carnival 3-3" ,
+ (Overcooked2Dlc.CARNIVAL_OF_CHAOS , 11 ): "Carnival 3-4" ,
+ (Overcooked2Dlc.CARNIVAL_OF_CHAOS , 12 ): "Carnival K-1" ,
+ (Overcooked2Dlc.CARNIVAL_OF_CHAOS , 13 ): "Carnival K-2" ,
+ (Overcooked2Dlc.CARNIVAL_OF_CHAOS , 14 ): "Carnival K-3" ,
+ (Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE , 0 ): "Horde 1-1" ,
+ (Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE , 1 ): "Horde 1-2" ,
+ (Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE , 2 ): "Horde 1-3" ,
+ (Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE , 3 ): "Horde 2-1" ,
+ (Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE , 4 ): "Horde 2-2" ,
+ (Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE , 5 ): "Horde 2-3" ,
+ (Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE , 6 ): "Horde 3-1" ,
+ (Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE , 7 ): "Horde 3-2" ,
+ (Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE , 8 ): "Horde 3-3" ,
+ (Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE , 9 ): "Horde K-1" ,
+ (Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE , 10 ): "Horde K-2" ,
+ (Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE , 11 ): "Horde K-3" ,
+ (Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE , 12 ): "Horde H-1" ,
+ (Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE , 13 ): "Horde H-2" ,
+ (Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE , 14 ): "Horde H-3" ,
+ (Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE , 15 ): "Horde H-4" ,
+ (Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE , 16 ): "Horde H-5" ,
+ (Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE , 17 ): "Horde H-6" ,
+ (Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE , 18 ): "Horde H-7" ,
+ (Overcooked2Dlc.NIGHT_OF_THE_HANGRY_HORDE , 19 ): "Horde H-8" ,
+ (Overcooked2Dlc.SEASONAL , 0 ): "Christmas 1-1" ,
+ (Overcooked2Dlc.SEASONAL , 1 ): "Christmas 1-2" ,
+ (Overcooked2Dlc.SEASONAL , 2 ): "Christmas 1-3" ,
+ (Overcooked2Dlc.SEASONAL , 3 ): "Christmas 1-4" ,
+ (Overcooked2Dlc.SEASONAL , 4 ): "Christmas 1-5" ,
+ (Overcooked2Dlc.SEASONAL , 5 ): "Chinese 1-1" ,
+ (Overcooked2Dlc.SEASONAL , 6 ): "Chinese 1-2" ,
+ (Overcooked2Dlc.SEASONAL , 7 ): "Chinese 1-3" ,
+ (Overcooked2Dlc.SEASONAL , 8 ): "Chinese 1-4" ,
+ (Overcooked2Dlc.SEASONAL , 9 ): "Chinese 1-5" ,
+ (Overcooked2Dlc.SEASONAL , 10 ): "Chinese 1-6" ,
+ (Overcooked2Dlc.SEASONAL , 11 ): "Chinese 1-7" ,
+ (Overcooked2Dlc.SEASONAL , 12 ): "Winter 1-1" ,
+ (Overcooked2Dlc.SEASONAL , 13 ): "Winter H-2" ,
+ (Overcooked2Dlc.SEASONAL , 14 ): "Winter 1-3" ,
+ (Overcooked2Dlc.SEASONAL , 15 ): "Winter H-4" ,
+ (Overcooked2Dlc.SEASONAL , 16 ): "Winter 1-5" ,
+ (Overcooked2Dlc.SEASONAL , 17 ): "Spring 1-1" ,
+ (Overcooked2Dlc.SEASONAL , 18 ): "Spring 1-2" ,
+ (Overcooked2Dlc.SEASONAL , 19 ): "Spring 1-3" ,
+ (Overcooked2Dlc.SEASONAL , 20 ): "Spring 1-4" ,
+ (Overcooked2Dlc.SEASONAL , 21 ): "Spring 1-5" ,
+ (Overcooked2Dlc.SEASONAL , 22 ): "SOBO 1-1" ,
+ (Overcooked2Dlc.SEASONAL , 23 ): "SOBO 1-2" ,
+ (Overcooked2Dlc.SEASONAL , 24 ): "SOBO 1-3" ,
+ (Overcooked2Dlc.SEASONAL , 25 ): "SOBO 1-4" ,
+ (Overcooked2Dlc.SEASONAL , 26 ): "SOBO 1-5" ,
+ (Overcooked2Dlc.SEASONAL , 27 ): "Moon 1-1" ,
+ (Overcooked2Dlc.SEASONAL , 28 ): "Moon 1-2" ,
+ (Overcooked2Dlc.SEASONAL , 29 ): "Moon 1-3" ,
+ (Overcooked2Dlc.SEASONAL , 30 ): "Moon 1-4" ,
+ (Overcooked2Dlc.SEASONAL , 31 ): "Moon 1-5" ,
+}
diff --git a/worlds/overcooked2/__init__.py b/worlds/overcooked2/__init__.py
new file mode 100644
index 0000000000..c47b755fbf
--- /dev/null
+++ b/worlds/overcooked2/__init__.py
@@ -0,0 +1,510 @@
+from enum import Enum
+from typing import Callable, Dict, Any, List, Optional
+
+from BaseClasses import ItemClassification, CollectionState, Region, Entrance, Location, RegionType, Tutorial
+from worlds.AutoWorld import World, WebWorld
+
+from .Overcooked2Levels import Overcooked2Level, Overcooked2GenericLevel
+from .Locations import Overcooked2Location, oc2_location_name_to_id, oc2_location_id_to_name
+from .Options import overcooked_options, OC2Options, OC2OnToggle
+from .Items import item_table, Overcooked2Item, item_name_to_id, item_id_to_name, item_to_unlock_event, item_frequencies
+from .Logic import has_requirements_for_level_star, has_requirements_for_level_access, level_shuffle_factory, is_item_progression, is_useful
+
+
+class Overcooked2Web(WebWorld):
+ theme = "partyTime"
+
+ bug_report_page = "https://github.com/toasterparty/oc2-modding/issues"
+ setup_en = Tutorial(
+ "Multiworld Setup Tutorial",
+ "A guide to setting up the Overcooked! 2 randomizer on your computer.",
+ "English",
+ "setup_en.md",
+ "setup/en",
+ ["toasterparty"]
+ )
+
+ tutorials = [setup_en]
+
+
+class PrepLevelMode(Enum):
+ original = 0
+ excluded = 1
+ ayce = 2
+
+
+class Overcooked2World(World):
+ """
+ Overcooked! 2 is a franticly paced arcade cooking game where
+ players race against the clock to complete orders for points. Bring
+ peace to the Onion Kingdom once again by recovering lost items and abilities,
+ earning stars to unlock levels, and defeating the unbread horde. Levels are
+ randomized to increase gameplay variety. Play with up to 4 friends.
+ """
+
+ # Autoworld API
+
+ game = "Overcooked! 2"
+ web = Overcooked2Web()
+ required_client_version = (0, 3, 4)
+ option_definitions = overcooked_options
+ topology_present: bool = False
+ remote_items: bool = True
+ remote_start_inventory: bool = False
+ data_version = 2
+
+ item_name_to_id = item_name_to_id
+ item_id_to_name = item_id_to_name
+
+ location_id_to_name = oc2_location_id_to_name
+ location_name_to_id = oc2_location_name_to_id
+
+ options: Dict[str, Any]
+ itempool: List[Overcooked2Item]
+
+
+ # Helper Functions
+
+ def is_level_horde(self, level_id: int) -> bool:
+ return self.options["IncludeHordeLevels"] and \
+ (self.level_mapping is not None) and \
+ level_id in self.level_mapping.keys() and \
+ self.level_mapping[level_id].is_horde
+
+ def create_item(self, item: str, classification: ItemClassification = ItemClassification.progression) -> Overcooked2Item:
+ return Overcooked2Item(item, classification, self.item_name_to_id[item], self.player)
+
+ def create_event(self, event: str, classification: ItemClassification) -> Overcooked2Item:
+ return Overcooked2Item(event, classification, None, self.player)
+
+ def place_event(self, location_name: str, item_name: str,
+ classification: ItemClassification = ItemClassification.progression_skip_balancing):
+ location: Location = self.world.get_location(location_name, self.player)
+ location.place_locked_item(self.create_event(item_name, classification))
+
+ def add_region(self, region_name: str):
+ region = Region(
+ region_name,
+ RegionType.Generic,
+ region_name,
+ self.player,
+ self.world,
+ )
+ self.world.regions.append(region)
+
+ def connect_regions(self, source: str, target: str, rule: Optional[Callable[[CollectionState], bool]] = None):
+ sourceRegion = self.world.get_region(source, self.player)
+ targetRegion = self.world.get_region(target, self.player)
+
+ connection = Entrance(self.player, '', sourceRegion)
+ if rule:
+ connection.access_rule = rule
+
+ sourceRegion.exits.append(connection)
+ connection.connect(targetRegion)
+
+ def add_level_location(
+ self,
+ region_name: str,
+ location_name: str,
+ level_id: int,
+ stars: int,
+ is_event: bool = False,
+ ) -> None:
+
+ if is_event:
+ location_id = None
+ else:
+ location_id = level_id
+
+ region = self.world.get_region(region_name, self.player)
+ location = Overcooked2Location(
+ self.player,
+ location_name,
+ location_id,
+ region,
+ )
+
+ location.event = is_event
+
+ # if level_id is none, then it's the 6-6 edge case
+ if level_id is None:
+ level_id = 36
+ if self.level_mapping is not None and level_id in self.level_mapping:
+ level = self.level_mapping[level_id]
+ else:
+ level = Overcooked2GenericLevel(level_id)
+
+ completion_condition: Callable[[CollectionState], bool] = \
+ lambda state, level=level, stars=stars: \
+ has_requirements_for_level_star(state, level, stars, self.player)
+ location.access_rule = completion_condition
+
+ region.locations.append(
+ location
+ )
+
+ def get_options(self) -> Dict[str, Any]:
+ return OC2Options({option.__name__: getattr(self.world, name)[self.player].result
+ if issubclass(option, OC2OnToggle) else getattr(self.world, name)[self.player].value
+ for name, option in overcooked_options.items()})
+
+ # Helper Data
+
+ level_unlock_counts: Dict[int, int] # level_id, stars to purchase
+ level_mapping: Dict[int, Overcooked2GenericLevel] # level_id, level
+
+ # Autoworld Hooks
+
+ def generate_early(self):
+ self.options = self.get_options()
+
+ # 0.0 to 1.0 where 1.0 is World Record
+ self.star_threshold_scale = self.options["StarThresholdScale"] / 100.0
+
+ # Generate level unlock requirements such that the levels get harder to unlock
+ # the further the game has progressed, and levels progress radially rather than linearly
+ self.level_unlock_counts = level_unlock_requirement_factory(self.options["StarsToWin"])
+
+ # Assign new kitchens to each spot on the overworld using pure random chance and nothing else
+ if self.options["ShuffleLevelOrder"]:
+ self.level_mapping = \
+ level_shuffle_factory(
+ self.world.random,
+ self.options["PrepLevels"] != PrepLevelMode.excluded.value,
+ self.options["IncludeHordeLevels"],
+ )
+ else:
+ self.level_mapping = None
+
+ def create_regions(self) -> None:
+ # Menu -> Overworld
+ self.add_region("Menu")
+ self.add_region("Overworld")
+ self.connect_regions("Menu", "Overworld")
+
+ for level in Overcooked2Level():
+ if not self.options["KevinLevels"] and level.level_id > 36:
+ break
+
+ # Create Region (e.g. "1-1")
+ self.add_region(level.level_name)
+
+ # Add Location to house progression item (1-star)
+ if level.level_id == 36:
+ # 6-6 doesn't have progression, but it does have victory condition which is placed later
+ self.add_level_location(
+ level.level_name,
+ level.location_name_item,
+ None,
+ 1,
+ )
+ else:
+ # Location to house progression item
+ self.add_level_location(
+ level.level_name,
+ level.location_name_item,
+ level.level_id,
+ 1,
+ )
+
+ # Location to house level completed event
+ self.add_level_location(
+ level.level_name,
+ level.location_name_level_complete,
+ level.level_id,
+ 1,
+ is_event=True,
+ )
+
+ # Add Locations to house star aquisition events, except for horde levels
+ if not self.is_level_horde(level.level_id):
+ for n in [1, 2, 3]:
+ self.add_level_location(
+ level.level_name,
+ level.location_name_star_event(n),
+ level.level_id,
+ n,
+ is_event=True,
+ )
+
+ # Overworld -> Level
+ required_star_count: int = self.level_unlock_counts[level.level_id]
+ if level.level_id % 6 != 1 and level.level_id <= 36:
+ previous_level_completed_event_name: str = Overcooked2GenericLevel(
+ level.level_id - 1).shortname.split(" ")[1] + " Level Complete"
+ else:
+ previous_level_completed_event_name = None
+
+ level_access_rule: Callable[[CollectionState], bool] = \
+ lambda state, level_name=level.level_name, previous_level_completed_event_name=previous_level_completed_event_name, required_star_count=required_star_count: \
+ has_requirements_for_level_access(state, level_name, previous_level_completed_event_name, required_star_count, self.player)
+ self.connect_regions("Overworld", level.level_name, level_access_rule)
+
+ # Level --> Overworld
+ self.connect_regions(level.level_name, "Overworld")
+
+ completion_condition: Callable[[CollectionState], bool] = lambda state: \
+ state.has("Victory", self.player)
+ self.world.completion_condition[self.player] = completion_condition
+
+ def create_items(self):
+ self.itempool = []
+
+ # Make Items
+ # useful = list()
+ # filler = list()
+ # progression = list()
+ for item_name in item_table:
+ if not self.options["IncludeHordeLevels"] and item_name in ["Calmer Unbread", "Coin Purse"]:
+ # skip items which are irrelevant to the seed
+ continue
+
+ if not self.options["KevinLevels"] and item_name.startswith("Kevin"):
+ continue
+
+ if is_item_progression(item_name, self.level_mapping, self.options["KevinLevels"]):
+ # print(f"{item_name} is progression")
+ # progression.append(item_name)
+ classification = ItemClassification.progression
+ else:
+ # print(f"{item_name} is filler")
+ if (is_useful(item_name)):
+ # useful.append(item_name)
+ classification = ItemClassification.useful
+ else:
+ # filler.append(item_name)
+ classification = ItemClassification.filler
+
+ if item_name in item_frequencies:
+ freq = item_frequencies[item_name]
+
+ while freq > 0:
+ self.itempool.append(self.create_item(item_name, classification))
+ classification = ItemClassification.useful # only the first progressive item can be progression
+ freq -= 1
+ else:
+ self.itempool.append(self.create_item(item_name, classification))
+
+ # print(f"progression: {progression}")
+ # print(f"useful: {useful}")
+ # print(f"filler: {filler}")
+
+ # Fill any free space with filler
+ pool_count = len(oc2_location_name_to_id)
+ if not self.options["KevinLevels"]:
+ pool_count -= 8
+
+ while len(self.itempool) < pool_count:
+ self.itempool.append(self.create_item("Bonus Star", ItemClassification.useful))
+
+ self.world.itempool += self.itempool
+
+ def set_rules(self):
+ pass
+
+ def generate_basic(self) -> None:
+ # Add Events (Star Acquisition)
+ for level in Overcooked2Level():
+ if not self.options["KevinLevels"] and level.level_id > 36:
+ break
+
+ if level.level_id != 36:
+ self.place_event(level.location_name_level_complete, level.event_name_level_complete)
+
+ if self.is_level_horde(level.level_id):
+ continue # horde levels don't have star rewards
+
+ for n in [1, 2, 3]:
+ self.place_event(level.location_name_star_event(n), "Star")
+
+ # Add Victory Condition
+ self.place_event("6-6 Completed", "Victory")
+
+ # Items get distributed to locations
+
+ def fill_json_data(self) -> Dict[str, Any]:
+ mod_name = f"AP-{self.world.seed_name}-P{self.player}-{self.world.player_name[self.player]}"
+
+ # Serialize Level Order
+ story_level_order = dict()
+
+ if self.options["ShuffleLevelOrder"]:
+ for level_id in self.level_mapping:
+ level: Overcooked2GenericLevel = self.level_mapping[level_id]
+ story_level_order[str(level_id)] = {
+ "DLC": level.dlc.value,
+ "LevelID": level.level_id,
+ }
+
+ custom_level_order = dict()
+ custom_level_order["Story"] = story_level_order
+
+ # Serialize Unlock Requirements
+ level_purchase_requirements = dict()
+ for level_id in self.level_unlock_counts:
+ level_purchase_requirements[str(level_id)] = self.level_unlock_counts[level_id]
+
+ # Override Vanilla Unlock Chain Behavior
+ # (all worlds accessible from the start and progressible in any order)
+ level_unlock_requirements = dict()
+ level_force_reveal = [
+ 1, # 1-1
+ 7, # 2-1
+ 13, # 3-1
+ 19, # 4-1
+ 25, # 5-1
+ 31, # 6-1
+ ]
+ for level_id in range(1, 37):
+ if (level_id not in level_force_reveal):
+ level_unlock_requirements[str(level_id)] = level_id - 1
+
+ # Set Kevin Unlock Requirements
+ if self.options["KevinLevels"]:
+ def kevin_level_to_keyholder_level_id(level_id: int) -> Optional[int]:
+ location = self.world.find_item(f"Kevin-{level_id-36}", self.player)
+ if location.player != self.player:
+ return None # This kevin level will be unlocked by the server at runtime
+ level_id = oc2_location_name_to_id[location.name]
+ return level_id
+
+ for level_id in range(37, 45):
+ keyholder_level_id = kevin_level_to_keyholder_level_id(level_id)
+ if keyholder_level_id is not None:
+ level_unlock_requirements[str(level_id)] = keyholder_level_id
+
+ # Place Items at Level Completion Screens (local only)
+ on_level_completed: Dict[str, list[Dict[str, str]]] = dict()
+ regions = self.world.get_regions(self.player)
+ for region in regions:
+ for location in region.locations:
+ if location.item is None:
+ continue
+ if location.item.code is None:
+ continue # it's an event
+ if location.item.player != self.player:
+ continue # not for us
+ level_id = str(oc2_location_name_to_id[location.name])
+ on_level_completed[level_id] = [item_to_unlock_event(location.item.name)]
+
+ # Put it all together
+ star_threshold_scale = self.options["StarThresholdScale"] / 100
+
+ base_data = {
+ # Changes Inherent to rando
+ "DisableAllMods": False,
+ "UnlockAllChefs": True,
+ "UnlockAllDLC": True,
+ "DisplayFPS": True,
+ "SkipTutorial": True,
+ "SkipAllOnionKing": True,
+ "SkipTutorialPopups": True,
+ "RevealAllLevels": False,
+ "PurchaseAllLevels": False,
+ "CheatsEnabled": False,
+ "ImpossibleTutorial": True,
+ "ForbidDLC": True,
+ "ForceSingleSaveSlot": True,
+ "DisableNGP": True,
+ "LevelForceReveal": level_force_reveal,
+ "SaveFolderName": mod_name,
+ "CustomOrderTimeoutPenalty": 10,
+ "LevelForceHide": [37, 38, 39, 40, 41, 42, 43, 44],
+
+ # Game Modifications
+ "LevelPurchaseRequirements": level_purchase_requirements,
+ "Custom66TimerScale": max(0.4, (1.0 - star_threshold_scale)),
+
+ "CustomLevelOrder": custom_level_order,
+
+ # Items (Starting Inventory)
+ "CustomOrderLifetime": 70.0, # 100 is original
+ "DisableWood": True,
+ "DisableCoal": True,
+ "DisableOnePlate": True,
+ "DisableFireExtinguisher": True,
+ "DisableBellows": True,
+ "PlatesStartDirty": True,
+ "MaxTipCombo": 2,
+ "DisableDash": True,
+ "WeakDash": True,
+ "DisableThrow": True,
+ "DisableCatch": True,
+ "DisableControlStick": True,
+ "DisableWokDrag": True,
+ "DisableRampButton": True,
+ "WashTimeMultiplier": 1.4,
+ "BurnSpeedMultiplier": 1.43,
+ "MaxOrdersOnScreenOffset": -2,
+ "ChoppingTimeScale": 1.4,
+ "BackpackMovementScale": 0.75,
+ "RespawnTime": 10.0,
+ "CarnivalDispenserRefactoryTime": 4.0,
+ "LevelUnlockRequirements": level_unlock_requirements,
+ "LockedEmotes": [0, 1, 2, 3, 4, 5],
+ "StarOffset": 0,
+ "AggressiveHorde": True,
+ "DisableEarnHordeMoney": True,
+
+ # Item Unlocking
+ "OnLevelCompleted": on_level_completed,
+ }
+
+ # Set remaining data in the options dict
+ bugs = ["FixDoubleServing", "FixSinkBug", "FixControlStickThrowBug", "FixEmptyBurnerThrow"]
+ for bug in bugs:
+ self.options[bug] = self.options["FixBugs"]
+ self.options["PreserveCookingProgress"] = self.options["AlwaysPreserveCookingProgress"]
+ self.options["TimerAlwaysStarts"] = self.options["PrepLevels"] == PrepLevelMode.ayce.value
+ self.options["LevelTimerScale"] = 0.666 if self.options["ShorterLevelDuration"] else 1.0
+ self.options["LeaderboardScoreScale"] = {
+ "FourStars": 1.0,
+ "ThreeStars": star_threshold_scale,
+ "TwoStars": star_threshold_scale * 0.75,
+ "OneStar": star_threshold_scale * 0.35,
+ }
+
+ base_data.update(self.options)
+ return base_data
+
+ def fill_slot_data(self) -> Dict[str, Any]:
+ return self.fill_json_data()
+
+
+def level_unlock_requirement_factory(stars_to_win: int) -> Dict[int, int]:
+ level_unlock_counts = dict()
+ level = 1
+ sublevel = 1
+ for n in range(1, 37):
+ progress: float = float(n)/36.0
+ progress *= progress # x^2 curve
+
+ star_count = int(progress*float(stars_to_win))
+ min = (n-1)*3
+ if (star_count > min):
+ star_count = min
+
+ level_id = (level-1)*6 + sublevel
+
+ # print("%d-%d (%d) = %d" % (level, sublevel, level_id, star_count))
+
+ level_unlock_counts[level_id] = star_count
+
+ level += 1
+ if level > 6:
+ level = 1
+ sublevel += 1
+
+ # force sphere 1 to 0 stars to help keep our promises to the item fill algo
+ level_unlock_counts[1] = 0 # 1-1
+ level_unlock_counts[7] = 0 # 2-1
+ level_unlock_counts[19] = 0 # 4-1
+
+ # Force 5-1 into sphere 1 to help things out
+ level_unlock_counts[25] = 1 # 5-1
+
+ for n in range(37, 46):
+ level_unlock_counts[n] = 0
+
+ return level_unlock_counts
diff --git a/worlds/overcooked2/docs/en_Overcooked! 2.md b/worlds/overcooked2/docs/en_Overcooked! 2.md
new file mode 100644
index 0000000000..d6de25f3e9
--- /dev/null
+++ b/worlds/overcooked2/docs/en_Overcooked! 2.md
@@ -0,0 +1,86 @@
+# Overcooked! 2
+
+## Quick Links
+- [Setup Guide](../../../../tutorial/Overcooked!%202/setup/en)
+- [Settings Page](../../../../games/Overcooked!%202/player-settings)
+- [OC2-Modding GitHub](https://github.com/toasterparty/oc2-modding)
+
+## How Does Randomizer Work in the Kitchen?
+
+The *Overcooked! 2* Randomizer completely transforms the game into a metroidvania with items and item locations. Many of the Chefs' inherent abilities have been temporarily removed such that your scoring potential is limited at the start of the game. The more your inventory grows, the easier it will be to earn 2 and 3 Stars on each level.
+
+The game takes place entirely in the "Story" campaign on a fresh save file. The ultimate goal is to reach and complete level 6-6. In order to do this you must regain enough of your abilities to complete all levels in World 6 and obtain enough stars to purchase 6-6*.
+
+Randomizer can be played alone (one player switches between controlling two chefs) or up to 4 local/online friends. Player count can be changed at any time during the Archipelago game.
+
+**Note: 6-6 is excluded from "Shuffle Level Order", so it will always be the standard final boss stage.*
+
+## Items
+
+The first time a level is completed, a random item is given to the chef(s). If playing in a MultiWorld, completing a level may instead give another Archipelago user their item. The item found is displayed as text at the top of the results screen.
+
+Once all items have been obtained, the game will play like the original experience.
+
+The following items were invented for Randomizer:
+
+### Player Abilities
+- Dash/Dash Cooldown
+- Throw/Catch
+- Sharp Knife
+- Dish Scrubber
+- Control Stick Batteries
+- Lightweight Backpack
+- Faster Respawn Time
+- Emote (x6)
+
+### Objects
+- Spare Plate
+- Clean Dishes
+- Wood
+- Coal Bucket
+- Bellows
+- Fire Extinguisher
+
+### Kitchen/Environment
+- Larger Tip Jar
+- Guest Patience
+- Burn Leniency
+- Faster Condiment & Drink Switch
+- Wok Wheels
+- Coin Purse
+- Calmer Unbread
+
+### Overworld
+- Unlock Kevin Level (x8)
+- Ramp Button
+- Bonus Star (Filler Item*)
+
+**Note: Bonus star count varies with settings*
+
+## Other Game Modifications
+
+In addition to shuffling items, the following changes are applied to the game:
+
+### Quality of Life
+- Tutorial is skipped
+- Non-linear level order
+- "Auto-Complete" feature to finish a level early when a target score is obtained
+- Bugfixes for issues present in the base game (including "Sink Bug" and "Double Serving")
+- All chef avatars automatically unlocked
+- Optionally, level time can be reduced to make progression faster paced
+
+### Randomization Options
+
+- *Shuffle Level Order*
+ - Replaces each level on the overworld with a random level
+ - DLC levels can show up on the Story Overworld
+ - Optionally exclude "Horde" Levels
+ - Optionally exclude "Prep" Levels
+
+### Difficulty Adjustments
+- Stars required to unlock levels have been rebalanced
+- Points required to earn stars have been rebalanced
+ - Based off of the current World Record on the game's [Leaderboard](https://overcooked.greeny.dev)
+ - 1-Star/2-Star scores are much closer to the 3-Star Score
+- Significantly reduced the time allotted to beat the final level
+- Reduced penalty for expired order
diff --git a/worlds/overcooked2/docs/setup_en.md b/worlds/overcooked2/docs/setup_en.md
new file mode 100644
index 0000000000..d724f02f7f
--- /dev/null
+++ b/worlds/overcooked2/docs/setup_en.md
@@ -0,0 +1,84 @@
+# Overcooked! 2 Randomizer Setup Guide
+
+## Quick Links
+- [Main Page](../../../../games/Overcooked!%202/info/en)
+- [Settings Page](../../../../games/Overcooked!%202/player-settings)
+- [OC2-Modding GitHub](https://github.com/toasterparty/oc2-modding)
+
+## Required Software
+
+- Windows 10+
+- [Overcooked! 2](https://store.steampowered.com/bundle/13608/Overcooked_2___Gourmet_Edition/) for PC
+ - **Steam: Recommended**
+ - Steam (Beta Branch): Supported
+ - Epic Games: Supported
+ - GOG: Not officially supported - Adventurous users may choose to experiment at their own risk
+ - Windows Store (aka GamePass): Not Supported
+ - Xbox/PS/Switch: Not Supported
+- [OC2-Modding Client](https://github.com/toasterparty/oc2-modding/releases) (instructions below)
+
+## Overview
+
+*OC2-Modding* is a general purpose modding framework which doubles as an Archipelago MultiWorld Client. It works by using Harmony to inject custom code into the game at runtime, so none of the orignal game files need to be modified in any way.
+
+When connecting to an Archipelago session using the in-game login screen, a modfile containing all relevant game modifications is automatically downloaded and applied.
+
+From this point, the game will communicate with the Archipelago service directly to manage sending/receiving items. Notifications of important events will appear through an in-game console at the top of the screen.
+
+## Overcooked! 2 Modding Guide
+
+### Install
+
+1. Download and extract the contents of the latest [OC2-Modding Release](https://github.com/toasterparty/oc2-modding/releases) anywhere on your PC
+
+2. Double-Click **oc2-modding-install.bat** follow the instructions.
+
+Once *OC2-Modding* is installed, you have successfully installed everything you need to play/participate in Archipelago MultiWorld games.
+
+### Disable
+
+To temporarily turn off *OC2-Modding* and return to the original game, open **...\Overcooked! 2\BepInEx\config\OC2Modding.cfg** in a text editor like notepad and edit the following:
+
+`DisableAllMods = true`
+
+To re-enable, simply change the word **true** back to a **false**.
+
+### Uninstall
+
+To completely remove *OC2-Modding*, navigate to your game's installation folder and run **oc2-modding-uninstall.bat**.
+
+## Generate a MultiWorld Game
+
+1. Visit the [Player Settings](../../../../games/Overcooked!%202/player-settings) page and configure the game-specific settings to taste
+
+2. Export your yaml file and use it to generate a new randomized game
+- (For instructions on how to generate an Archipelago game, refer to the [Archipelago Web Guide](../../../../tutorial/Archipelago/using_website/en))
+
+## Joining a MultiWorld Game
+
+1. Launch the game
+
+2. When attempting to enter the main menu from the title screen, the game will freeze and prompt you to sign in:
+
+
+
+3. Sign-in with server address, username and password of the corresponding room you would like to join.
+- Otherwise, if you just want to play the vanilla game without any modifications, you may press "Continue without Archipelago" button.
+
+4. Upon successful connection to the Archipelago service, you will be granted access to the main menu. The game will act as though you are playing for the first time. ***DO NOT FEAR*** — your original save data has not been overwritten; the Overcooked Randomizer just uses a temporary directory for it's save game data.
+
+## Playing Co-Op
+
+- To play local multiplayer (or Parsec/"Steam Play Together"), simply add the additional player to your game session as you would in the base game
+
+- To play online multiplayer, the guest *must* also have the same version of OC2-Modding installed. In order for the game to work, the guest must sign in using the same information the host used to connect to the Archipelago session. Once both host and client are both connected, they may join one another in-game and proceed as normal. It does not matter who hosts the game, and the game's hosts may be changed at any point. You may notice some things are different when playing this way:
+
+ - Guests will still receive Archipelago messages about sent/received items the same as the host
+
+ - When the host loads the campaign, any connected guests are forced to select "Don't Save" when prompted to pick which save slot to use. This is because randomizer uses the Archipelago service as a pseudo "cloud save", so progress will always be synchronized between all participants of that randomized *Overcooked! 2* instance.
+
+## Auto-Complete
+
+Since the goal of randomizer isn't necessarily to achieve new personal high scores, players may find themselves waiting for a level timer to expire once they've met their objective. A new feature called *Auto-Complete* has been added to automatically complete levels once a target star count has been achieved.
+
+To enable *Auto-Complete*, press the **Show** button near the top of your screen to expand the modding controls. Then, repeatedly press the **Auto-Complete** button until it shows the desired setting.
diff --git a/worlds/pokemon_rb/LICENSE b/worlds/pokemon_rb/LICENSE
new file mode 100644
index 0000000000..0dc1b2dcd0
--- /dev/null
+++ b/worlds/pokemon_rb/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Alex "Alchav" Avery
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/worlds/pokemon_rb/__init__.py b/worlds/pokemon_rb/__init__.py
new file mode 100644
index 0000000000..4c37db8450
--- /dev/null
+++ b/worlds/pokemon_rb/__init__.py
@@ -0,0 +1,252 @@
+from typing import TextIO
+import os
+import logging
+
+from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification
+from Fill import fill_restrictive, FillError, sweep_from_pool
+from ..AutoWorld import World, WebWorld
+from ..generic.Rules import add_item_rule
+from .items import item_table, item_groups
+from .locations import location_data, PokemonRBLocation
+from .regions import create_regions
+from .logic import PokemonLogic
+from .options import pokemon_rb_options
+from .rom_addresses import rom_addresses
+from .text import encode_text
+from .rom import generate_output, get_base_rom_bytes, get_base_rom_path, process_pokemon_data, process_wild_pokemon,\
+ process_static_pokemon
+from .rules import set_rules
+
+import worlds.pokemon_rb.poke_data as poke_data
+
+
+class PokemonWebWorld(WebWorld):
+ tutorials = [Tutorial(
+ "Multiworld Setup Guide",
+ "A guide to playing Pokemon Red and Blue with Archipelago.",
+ "English",
+ "setup_en.md",
+ "setup/en",
+ ["Alchav"]
+ )]
+
+
+class PokemonRedBlueWorld(World):
+ """Pokémon Red and Pokémon Blue are the original monster-collecting turn-based RPGs. Explore the Kanto region with
+ your Pokémon, catch more than 150 unique creatures, earn badges from the region's Gym Leaders, and challenge the
+ Elite Four to become the champion!"""
+ # -MuffinJets#4559
+ game = "Pokemon Red and Blue"
+ option_definitions = pokemon_rb_options
+ remote_items = False
+ data_version = 1
+ topology_present = False
+
+ item_name_to_id = {name: data.id for name, data in item_table.items()}
+ location_name_to_id = {location.name: location.address for location in location_data if location.type == "Item"}
+ item_name_groups = item_groups
+
+ web = PokemonWebWorld()
+
+ def __init__(self, world: MultiWorld, player: int):
+ super().__init__(world, player)
+ self.fly_map = None
+ self.fly_map_code = None
+ self.extra_badges = {}
+ self.type_chart = None
+ self.local_poke_data = None
+ self.learnsets = None
+ self.trainer_name = None
+ self.rival_name = None
+
+ @classmethod
+ def stage_assert_generate(cls, world):
+ versions = set()
+ for player in world.player_ids:
+ if world.worlds[player].game == "Pokemon Red and Blue":
+ versions.add(world.game_version[player].current_key)
+ for version in versions:
+ if not os.path.exists(get_base_rom_path(version)):
+ raise FileNotFoundError(get_base_rom_path(version))
+
+ def generate_early(self):
+ def encode_name(name, t):
+ try:
+ if len(encode_text(name)) > 7:
+ raise IndexError(f"{t} name too long for player {self.world.player_name[self.player]}. Must be 7 characters or fewer.")
+ return encode_text(name, length=8, whitespace="@", safety=True)
+ except KeyError as e:
+ raise KeyError(f"Invalid character(s) in {t} name for player {self.world.player_name[self.player]}") from e
+ self.trainer_name = encode_name(self.world.trainer_name[self.player].value, "Player")
+ self.rival_name = encode_name(self.world.rival_name[self.player].value, "Rival")
+
+ if self.world.badges_needed_for_hm_moves[self.player].value >= 2:
+ badges_to_add = ["Marsh Badge", "Volcano Badge", "Earth Badge"]
+ if self.world.badges_needed_for_hm_moves[self.player].value == 3:
+ badges = ["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Marsh Badge",
+ "Soul Badge", "Volcano Badge", "Earth Badge"]
+ self.world.random.shuffle(badges)
+ badges_to_add += [badges.pop(), badges.pop()]
+ hm_moves = ["Cut", "Fly", "Surf", "Strength", "Flash"]
+ self.world.random.shuffle(hm_moves)
+ self.extra_badges = {}
+ for badge in badges_to_add:
+ self.extra_badges[hm_moves.pop()] = badge
+
+ process_pokemon_data(self)
+
+ def create_items(self) -> None:
+ locations = [location for location in location_data if location.type == "Item"]
+ item_pool = []
+ for location in locations:
+ if "Hidden" in location.name and not self.world.randomize_hidden_items[self.player].value:
+ continue
+ if "Rock Tunnel B1F" in location.region and not self.world.extra_key_items[self.player].value:
+ continue
+ if location.name == "Celadon City - Mansion Lady" and not self.world.tea[self.player].value:
+ continue
+ item = self.create_item(location.original_item)
+ if location.event:
+ self.world.get_location(location.name, self.player).place_locked_item(item)
+ elif ("Badge" not in item.name or self.world.badgesanity[self.player].value) and \
+ (item.name != "Oak's Parcel" or self.world.old_man[self.player].value != 1):
+ item_pool.append(item)
+ self.world.random.shuffle(item_pool)
+
+ self.world.itempool += item_pool
+
+
+ def pre_fill(self):
+
+ process_wild_pokemon(self)
+ process_static_pokemon(self)
+
+ if self.world.old_man[self.player].value == 1:
+ item = self.create_item("Oak's Parcel")
+ locations = []
+ for location in self.world.get_locations():
+ if location.player == self.player and location.item is None and location.can_reach(self.world.state) \
+ and location.item_rule(item):
+ locations.append(location)
+ self.world.random.choice(locations).place_locked_item(item)
+
+ if not self.world.badgesanity[self.player].value:
+ self.world.non_local_items[self.player].value -= self.item_name_groups["Badges"]
+ for i in range(5):
+ try:
+ badges = []
+ badgelocs = []
+ for badge in ["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Soul Badge",
+ "Marsh Badge", "Volcano Badge", "Earth Badge"]:
+ badges.append(self.create_item(badge))
+ for loc in ["Pewter Gym - Brock 1", "Cerulean Gym - Misty 1", "Vermilion Gym - Lt. Surge 1",
+ "Celadon Gym - Erika 1", "Fuchsia Gym - Koga 1", "Saffron Gym - Sabrina 1",
+ "Cinnabar Gym - Blaine 1", "Viridian Gym - Giovanni 1"]:
+ badgelocs.append(self.world.get_location(loc, self.player))
+ state = self.world.get_all_state(False)
+ self.world.random.shuffle(badges)
+ self.world.random.shuffle(badgelocs)
+ fill_restrictive(self.world, state, badgelocs.copy(), badges, True, True)
+ except FillError:
+ for location in badgelocs:
+ location.item = None
+ continue
+ break
+ else:
+ raise FillError(f"Failed to place badges for player {self.player}")
+
+ locs = [self.world.get_location("Fossil - Choice A", self.player),
+ self.world.get_location("Fossil - Choice B", self.player)]
+ for loc in locs:
+ add_item_rule(loc, lambda i: i.advancement or i.name in self.item_name_groups["Unique"]
+ or i.name == "Master Ball")
+
+ loc = self.world.get_location("Pallet Town - Player's PC", self.player)
+ if loc.item is None:
+ locs.append(loc)
+
+ for loc in locs:
+ unplaced_items = []
+ if loc.name in self.world.priority_locations[self.player].value:
+ add_item_rule(loc, lambda i: i.advancement)
+ for item in self.world.itempool:
+ if item.player == self.player and loc.item_rule(item):
+ self.world.itempool.remove(item)
+ state = sweep_from_pool(self.world.state, self.world.itempool + unplaced_items)
+ if state.can_reach(loc, "Location", self.player):
+ loc.place_locked_item(item)
+ break
+ else:
+ unplaced_items.append(item)
+ self.world.itempool += unplaced_items
+
+ intervene = False
+ test_state = self.world.get_all_state(False)
+ if not test_state.pokemon_rb_can_surf(self.player) or not test_state.pokemon_rb_can_strength(self.player):
+ intervene = True
+ elif self.world.accessibility[self.player].current_key != "minimal":
+ if not test_state.pokemon_rb_can_cut(self.player) or not test_state.pokemon_rb_can_flash(self.player):
+ intervene = True
+ if intervene:
+ # the way this is handled will be improved significantly in the future when I add options to
+ # let you choose the exact weights for HM compatibility
+ logging.warning(
+ f"HM-compatible Pokémon possibly missing, placing Mew on Route 1 for player {self.player}")
+ loc = self.world.get_location("Route 1 - Wild Pokemon - 1", self.player)
+ loc.item = self.create_item("Mew")
+
+ def create_regions(self):
+ if self.world.free_fly_location[self.player].value:
+ fly_map_code = self.world.random.randint(5, 9)
+ if fly_map_code == 9:
+ fly_map_code = 10
+ if fly_map_code == 5:
+ fly_map_code = 4
+ else:
+ fly_map_code = 0
+ self.fly_map = ["Pallet Town", "Viridian City", "Pewter City", "Cerulean City", "Lavender Town",
+ "Vermilion City", "Celadon City", "Fuchsia City", "Cinnabar Island", "Indigo Plateau",
+ "Saffron City"][fly_map_code]
+ self.fly_map_code = fly_map_code
+ create_regions(self.world, self.player)
+ self.world.completion_condition[self.player] = lambda state, player=self.player: state.has("Become Champion", player=player)
+
+ def set_rules(self):
+ set_rules(self.world, self.player)
+
+ def create_item(self, name: str) -> Item:
+ return PokemonRBItem(name, self.player)
+
+ def generate_output(self, output_directory: str):
+ generate_output(self, output_directory)
+
+ def write_spoiler_header(self, spoiler_handle: TextIO):
+ if self.world.free_fly_location[self.player].value:
+ spoiler_handle.write('Fly unlocks: %s\n' % self.fly_map)
+ if self.extra_badges:
+ for hm_move, badge in self.extra_badges.items():
+ spoiler_handle.write(hm_move + " enabled by: " + (" " * 20)[:20 - len(hm_move)] + badge + "\n")
+
+ def write_spoiler(self, spoiler_handle):
+ if self.world.randomize_type_matchup_types[self.player].value or \
+ self.world.randomize_type_matchup_type_effectiveness[self.player].value:
+ spoiler_handle.write(f"\n\nType matchups ({self.world.player_name[self.player]}):\n\n")
+ for matchup in self.type_chart:
+ spoiler_handle.write(f"{matchup[0]} deals {matchup[2] * 10}% damage to {matchup[1]}\n")
+
+ def get_filler_item_name(self) -> str:
+ return self.world.random.choice([item for item in item_table if item_table[item].classification in
+ [ItemClassification.filler, ItemClassification.trap]])
+
+
+class PokemonRBItem(Item):
+ game = "Pokemon Red and Blue"
+ type = None
+
+ def __init__(self, name, player: int = None):
+ item_data = item_table[name]
+ super(PokemonRBItem, self).__init__(
+ name,
+ item_data.classification,
+ item_data.id, player
+ )
diff --git a/worlds/pokemon_rb/basepatch_blue.bsdiff4 b/worlds/pokemon_rb/basepatch_blue.bsdiff4
new file mode 100644
index 0000000000..c688ebede0
Binary files /dev/null and b/worlds/pokemon_rb/basepatch_blue.bsdiff4 differ
diff --git a/worlds/pokemon_rb/basepatch_red.bsdiff4 b/worlds/pokemon_rb/basepatch_red.bsdiff4
new file mode 100644
index 0000000000..a9005d3744
Binary files /dev/null and b/worlds/pokemon_rb/basepatch_red.bsdiff4 differ
diff --git a/worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md b/worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md
new file mode 100644
index 0000000000..fe2550d4a1
--- /dev/null
+++ b/worlds/pokemon_rb/docs/en_Pokemon Red and Blue.md
@@ -0,0 +1,55 @@
+# Pokémon Red and Blue
+
+## Where is the settings page?
+
+The [player settings page for this game](../player-settings) contains all the options you need to configure and export a
+config file.
+
+## What does randomization do to this game?
+
+Items which the player would normally acquire throughout the game have been moved around. Logic remains, so the game is
+always able to be completed, but because of the item shuffle the player may need to access certain areas before they
+would in the vanilla game.
+
+A great many things besides item placement can be randomized, such as the location of Pokémon, their stats, types, etc., depending on your yaml settings.
+
+Many baseline changes are made to the game, including:
+
+* Bag item space increased to 128 slots (up from 20)
+* PC item storage increased to 64 slots (up from 50)
+* You can hold B to run (or bike extra fast!).
+* You can hold select while talking to a trainer to re-battle them.
+* You can return to route 2 from Diglett's Cave without the use of Cut.
+* Mew can be encountered at the S.S. Anne dock truck. This can be randomized depending on your settings.
+* The S.S. Anne will never depart.
+* Seafoam Islands entrances are swapped. This means you need Strength to travel through from Cinnabar Island to Fuchsia
+City
+* After obtaining one of the fossil item checks in Mt Moon, the remaining item can be received from the Cinnabar Lab
+fossil scientist. This may require reviving a number of fossils, depending on your settings.
+* Obedience depends on the total number of badges you have obtained instead of depending on specific badges.
+* Pokémon that evolve by trading can also evolve by reaching level 35.
+* Evolution stones are reusable.
+* Much of the dialogue throughout the game has been removed or shortened.
+* If the Old Man is blocking your way through Viridian City, you do not have Oak's Parcel in your inventory, and you've
+exhausted your money and Poké Balls, you can get a free Poké Ball from your mom.
+
+## What items and locations get shuffled?
+
+All items that go into your bags given by NPCs or found on the ground, as well as gym badges.
+Optionally, hidden items (those located with the Item Finder) can be shuffled as well.
+
+## Which items can be in another player's world?
+
+Any of the items which can be shuffled may also be placed into another player's world.
+By default, gym badges are shuffled across only the 8 gyms, but you can turn on Badgesanity in your yaml to shuffle them
+into the general item pool.
+
+## What does another world's item look like in Pokémon Red and Blue?
+
+All items for other games will display simply as "AP ITEM," including those for other Pokémon Red and Blue games.
+
+## When the player receives an item, what happens?
+
+A "received item" sound effect will play. Currently, there is no in-game message informing you of what the item is.
+If you are in battle, have menus or text boxes opened, or scripted events are occurring, the items will not be given to
+you until these have ended.
diff --git a/worlds/pokemon_rb/docs/setup_en.md b/worlds/pokemon_rb/docs/setup_en.md
new file mode 100644
index 0000000000..58ed3b0dc6
--- /dev/null
+++ b/worlds/pokemon_rb/docs/setup_en.md
@@ -0,0 +1,84 @@
+# Setup Guide for Pokémon Red and Blue: Archipelago
+
+## Important
+
+As we are using Bizhawk, this guide is only applicable to Windows and Linux systems.
+
+## Required Software
+
+- Bizhawk: [Bizhawk Releases from TASVideos](https://tasvideos.org/BizHawk/ReleaseHistory)
+ - Version 2.3.1 and later are supported. Version 2.7 is recommended for stability.
+ - Detailed installation instructions for Bizhawk can be found at the above link.
+ - Windows users must run the prereq installer first, which can also be found at the above link.
+- The built-in Archipelago client, which can be installed [here](https://github.com/ArchipelagoMW/Archipelago/releases)
+ (select `Pokemon Client` during installation).
+- Pokémon Red and/or Blue ROM files. The Archipelago community cannot provide these.
+
+## Configuring Bizhawk
+
+Once Bizhawk has been installed, open Bizhawk and change the following settings:
+
+- Under Config > Customize > Advanced, make sure the box for AutoSaveRAM is checked, and click the 5s button.
+ This reduces the possibility of losing save data in emulator crashes.
+- Under Config > Customize, check the "Run in background" and "Accept background input" boxes. This will allow you to
+ continue playing in the background, even if another window is selected.
+
+It is strongly recommended to associate GB rom extensions (\*.gb) to the Bizhawk we've just installed.
+To do so, we simply have to search any Gameboy rom we happened to own, right click and select "Open with...", unfold
+the list that appears and select the bottom option "Look for another application", then browse to the Bizhawk folder
+and select EmuHawk.exe.
+
+## Configuring your YAML file
+
+### What is a YAML file and why do I need one?
+
+Your YAML file contains a set of configuration options which provide the generator with information about how it should
+generate your game. Each player of a multiworld will provide their own YAML file. This setup allows each player to enjoy
+an experience customized for their taste, and different players in the same multiworld can all have different options.
+
+### Where do I get a YAML file?
+
+You can generate a yaml or download a template by visiting the [Pokemon Red and Blue Player Settings Page](/games/Pokemon Red and Blue/player-settings)
+
+It is important to note that the `game_version` option determines the ROM file that will be patched.
+Both the player and the person generating (if they are generating locally) will need the corresponding ROM file.
+
+For `trainer_name` and `rival_name` the following regular characters are allowed:
+
+* `‘’“”·… ABCDEFGHIJKLMNOPQRSTUVWXYZ():;[]abcdefghijklmnopqrstuvwxyzé'-?!.♂$×/,♀0123456789`
+
+And the following special characters (these each take up one character):
+* `<'d>`
+* `<'l>`
+* `<'t>`
+* `<'v>`
+* `<'r>`
+* `<'m>`
+* ``
+* ``
+* `` alias for `♂`
+* `` alias for `♀`
+
+## Joining a MultiWorld Game
+
+### Obtain your Pokémon patch file
+
+When you join a multiworld game, you will be asked to provide your YAML file to whoever is hosting. Once that is done,
+the host will provide you with either a link to download your data file, or with a zip file containing everyone's data
+files. Your data file should have a `.apred` or `.apblue` extension.
+
+Double-click on your patch file to start your client and start the ROM patch process. Once the process is finished
+(this can take a while), the client and the emulator will be started automatically (if you associated the extension
+to the emulator as recommended).
+
+### Connect to the Multiserver
+
+Once both the client and the emulator are started, you must connect them. Within the emulator click on the "Tools"
+menu and select "Lua Console". Click the folder button or press Ctrl+O to open a Lua script.
+
+Navigate to your Archipelago install folder and open `data/lua/PKMN_RB/pkmr_rb.lua`.
+
+To connect the client to the multiserver simply put `:` on the textfield on top and press enter (if the
+server uses password, type in the bottom textfield `/connect : [password]`)
+
+Now you are ready to start your adventure in Kanto.
diff --git a/worlds/pokemon_rb/items.py b/worlds/pokemon_rb/items.py
new file mode 100644
index 0000000000..53722c02a4
--- /dev/null
+++ b/worlds/pokemon_rb/items.py
@@ -0,0 +1,176 @@
+from BaseClasses import ItemClassification
+from .poke_data import pokemon_data
+
+class ItemData:
+ def __init__(self, id, classification, groups):
+ self.groups = groups
+ self.classification = classification
+ self.id = None if id is None else id + 172000000
+
+item_table = {
+ "Master Ball": ItemData(1, ItemClassification.useful, ["Consumables", "Poke Balls"]),
+ "Ultra Ball": ItemData(2, ItemClassification.filler, ["Consumables", "Poke Balls"]),
+ "Great Ball": ItemData(3, ItemClassification.filler, ["Consumables", "Poke Balls"]),
+ "Poke Ball": ItemData(4, ItemClassification.filler, ["Consumables", "Poke Balls"]),
+ "Town Map": ItemData(5, ItemClassification.progression_skip_balancing, ["Unique", "Key Items"]),
+ "Bicycle": ItemData(6, ItemClassification.progression, ["Unique", "Key Items"]),
+ # "Flippers": ItemData(7, ItemClassification.progression),
+ #"Safari Ball": ItemData(8, ItemClassification.filler),
+ #"Pokedex": ItemData(9, ItemClassification.filler),
+ "Moon Stone": ItemData(10, ItemClassification.useful, ["Unique", "Evolution Stones"]),
+ "Antidote": ItemData(11, ItemClassification.filler, ["Consumables"]),
+ "Burn Heal": ItemData(12, ItemClassification.filler, ["Consumables"]),
+ "Ice Heal": ItemData(13, ItemClassification.filler, ["Consumables"]),
+ "Awakening": ItemData(14, ItemClassification.filler, ["Consumables"]),
+ "Paralyze Heal": ItemData(15, ItemClassification.filler, ["Consumables"]),
+ "Full Restore": ItemData(16, ItemClassification.filler, ["Consumables"]),
+ "Max Potion": ItemData(17, ItemClassification.filler, ["Consumables"]),
+ "Hyper Potion": ItemData(18, ItemClassification.filler, ["Consumables"]),
+ "Super Potion": ItemData(19, ItemClassification.filler, ["Consumables"]),
+ "Potion": ItemData(20, ItemClassification.filler, ["Consumables"]),
+ "Boulder Badge": ItemData(21, ItemClassification.progression, ["Unique", "Key Items", "Badges"]),
+ "Cascade Badge": ItemData(22, ItemClassification.progression, ["Unique", "Key Items", "Badges"]),
+ "Thunder Badge": ItemData(23, ItemClassification.progression, ["Unique", "Key Items", "Badges"]),
+ "Rainbow Badge": ItemData(24, ItemClassification.progression, ["Unique", "Key Items", "Badges"]),
+ "Soul Badge": ItemData(25, ItemClassification.progression, ["Unique", "Key Items", "Badges"]),
+ "Marsh Badge": ItemData(26, ItemClassification.progression, ["Unique", "Key Items", "Badges"]),
+ "Volcano Badge": ItemData(27, ItemClassification.progression, ["Unique", "Key Items", "Badges"]),
+ "Earth Badge": ItemData(28, ItemClassification.progression, ["Unique", "Key Items", "Badges"]),
+ "Escape Rope": ItemData(29, ItemClassification.filler, ["Consumables"]),
+ "Repel": ItemData(30, ItemClassification.filler, ["Consumables"]),
+ "Old Amber": ItemData(31, ItemClassification.progression_skip_balancing, ["Unique", "Fossils"]),
+ "Fire Stone": ItemData(32, ItemClassification.useful, ["Unique", "Evolution Stones"]),
+ "Thunder Stone": ItemData(33, ItemClassification.useful, ["Unique", "Evolution Stones"]),
+ "Water Stone": ItemData(34, ItemClassification.useful, ["Unique", "Evolution Stones"]),
+ "HP Up": ItemData(35, ItemClassification.filler, ["Consumables", "Vitamins"]),
+ "Protein": ItemData(36, ItemClassification.filler, ["Consumables", "Vitamins"]),
+ "Iron": ItemData(37, ItemClassification.filler, ["Consumables", "Vitamins"]),
+ "Carbos": ItemData(38, ItemClassification.filler, ["Consumables", "Vitamins"]),
+ "Calcium": ItemData(39, ItemClassification.filler, ["Consumables", "Vitamins"]),
+ "Rare Candy": ItemData(40, ItemClassification.useful, ["Consumables"]),
+ "Dome Fossil": ItemData(41, ItemClassification.progression_skip_balancing, ["Unique", "Fossils"]),
+ "Helix Fossil": ItemData(42, ItemClassification.progression_skip_balancing, ["Unique", "Fossils"]),
+ "Secret Key": ItemData(43, ItemClassification.progression, ["Unique", "Key Items"]),
+ "Bike Voucher": ItemData(45, ItemClassification.progression, ["Unique", "Key Items"]),
+ "X Accuracy": ItemData(46, ItemClassification.filler, ["Consumables", "Battle Items"]),
+ "Leaf Stone": ItemData(47, ItemClassification.useful, ["Unique", "Evolution Stones"]),
+ "Card Key": ItemData(48, ItemClassification.progression, ["Unique", "Key Items"]),
+ "Nugget": ItemData(49, ItemClassification.filler, []),
+ #"Laptop": ItemData(50, ItemClassification.useful, ["Unique"]),
+ "Poke Doll": ItemData(51, ItemClassification.filler, ["Consumables"]),
+ "Full Heal": ItemData(52, ItemClassification.filler, ["Consumables"]),
+ "Revive": ItemData(53, ItemClassification.filler, ["Consumables"]),
+ "Max Revive": ItemData(54, ItemClassification.filler, ["Consumables"]),
+ "Guard Spec": ItemData(55, ItemClassification.filler, ["Consumables", "Battle Items"]),
+ "Super Repel": ItemData(56, ItemClassification.filler, ["Consumables"]),
+ "Max Repel": ItemData(57, ItemClassification.filler, ["Consumables"]),
+ "Dire Hit": ItemData(58, ItemClassification.filler, ["Consumables", "Battle Items"]),
+ #"Coin": ItemData(59, ItemClassification.filler),
+ "Fresh Water": ItemData(60, ItemClassification.filler, ["Consumables"]),
+ "Soda Pop": ItemData(61, ItemClassification.filler, ["Consumables"]),
+ "Lemonade": ItemData(62, ItemClassification.filler, ["Consumables"]),
+ "S.S. Ticket": ItemData(63, ItemClassification.progression, ["Unique", "Key Items"]),
+ "Gold Teeth": ItemData(64, ItemClassification.progression, ["Unique", "Key Items"]),
+ "X Attack": ItemData(65, ItemClassification.filler, ["Consumables", "Battle Items"]),
+ "X Defend": ItemData(66, ItemClassification.filler, ["Consumables", "Battle Items"]),
+ "X Speed": ItemData(67, ItemClassification.filler, ["Consumables", "Battle Items"]),
+ "X Special": ItemData(68, ItemClassification.filler, ["Consumables", "Battle Items"]),
+ "Coin Case": ItemData(69, ItemClassification.progression_skip_balancing, ["Unique", "Key Items"]),
+ "Oak's Parcel": ItemData(70, ItemClassification.progression, ["Unique", "Key Items"]),
+ "Item Finder": ItemData(71, ItemClassification.progression, ["Unique", "Key Items"]),
+ "Silph Scope": ItemData(72, ItemClassification.progression, ["Unique", "Key Items"]),
+ "Poke Flute": ItemData(73, ItemClassification.progression, ["Unique", "Key Items"]),
+ "Lift Key": ItemData(74, ItemClassification.progression, ["Unique", "Key Items"]),
+ "Exp. All": ItemData(75, ItemClassification.useful, ["Unique"]),
+ "Old Rod": ItemData(76, ItemClassification.progression_skip_balancing, ["Unique", "Key Items", "Rods"]),
+ "Good Rod": ItemData(77, ItemClassification.progression_skip_balancing, ["Unique", "Key Items", "Rods"]),
+ "Super Rod": ItemData(78, ItemClassification.progression_skip_balancing, ["Unique", "Key Items", "Rods"]),
+ "PP Up": ItemData(79, ItemClassification.filler, ["Consumables"]),
+ "Ether": ItemData(80, ItemClassification.filler, ["Consumables"]),
+ "Max Ether": ItemData(81, ItemClassification.filler, ["Consumables"]),
+ "Elixir": ItemData(82, ItemClassification.filler, ["Consumables"]),
+ "Max Elixir": ItemData(83, ItemClassification.filler, ["Consumables"]),
+ "Tea": ItemData(84, ItemClassification.progression, ["Unique", "Key Items"]),
+ # "Master Sword": ItemData(85, ItemClassification.progression),
+ # "Flute": ItemData(86, ItemClassification.progression),
+ # "Titan's Mitt": ItemData(87, ItemClassification.progression),
+ # "Lamp": ItemData(88, ItemClassification.progression),
+ "Plant Key": ItemData(89, ItemClassification.progression, ["Unique", "Key Items"]),
+ "Mansion Key": ItemData(90, ItemClassification.progression, ["Unique", "Key Items"]),
+ "Hideout Key": ItemData(91, ItemClassification.progression, ["Unique", "Key Items"]),
+ "Safari Pass": ItemData(93, ItemClassification.progression, ["Unique", "Key Items"]),
+ "HM01 Cut": ItemData(196, ItemClassification.progression, ["Unique", "HMs"]),
+ "HM02 Fly": ItemData(197, ItemClassification.progression, ["Unique", "HMs"]),
+ "HM03 Surf": ItemData(198, ItemClassification.progression, ["Unique", "HMs"]),
+ "HM04 Strength": ItemData(199, ItemClassification.progression, ["Unique", "HMs"]),
+ "HM05 Flash": ItemData(200, ItemClassification.progression, ["Unique", "HMs"]),
+ "TM01 Mega Punch": ItemData(201, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM02 Razor Wind": ItemData(202, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM03 Swords Dance": ItemData(203, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM04 Whirlwind": ItemData(204, ItemClassification.filler, ["Unique", "TMs"]),
+ "TM05 Mega Kick": ItemData(205, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM06 Toxic": ItemData(206, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM07 Horn Drill": ItemData(207, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM08 Body Slam": ItemData(208, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM09 Take Down": ItemData(209, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM10 Double Edge": ItemData(210, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM11 Bubble Beam": ItemData(211, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM12 Water Gun": ItemData(212, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM13 Ice Beam": ItemData(213, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM14 Blizzard": ItemData(214, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM15 Hyper Beam": ItemData(215, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM16 Pay Day": ItemData(216, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM17 Submission": ItemData(217, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM18 Counter": ItemData(218, ItemClassification.filler, ["Unique", "TMs"]),
+ "TM19 Seismic Toss": ItemData(219, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM20 Rage": ItemData(220, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM21 Mega Drain": ItemData(221, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM22 Solar Beam": ItemData(222, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM23 Dragon Rage": ItemData(223, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM24 Thunderbolt": ItemData(224, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM25 Thunder": ItemData(225, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM26 Earthquake": ItemData(226, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM27 Fissure": ItemData(227, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM28 Dig": ItemData(228, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM29 Psychic": ItemData(229, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM30 Teleport": ItemData(230, ItemClassification.filler, ["Unique", "TMs"]),
+ "TM31 Mimic": ItemData(231, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM32 Double Team": ItemData(232, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM33 Reflect": ItemData(233, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM34 Bide": ItemData(234, ItemClassification.filler, ["Unique", "TMs"]),
+ "TM35 Metronome": ItemData(235, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM36 Self Destruct": ItemData(236, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM37 Egg Bomb": ItemData(237, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM38 Fire Blast": ItemData(238, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM39 Swift": ItemData(239, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM40 Skull Bash": ItemData(240, ItemClassification.filler, ["Unique", "TMs"]),
+ "TM41 Soft Boiled": ItemData(241, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM42 Dream Eater": ItemData(242, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM43 Sky Attack": ItemData(243, ItemClassification.filler, ["Unique", "TMs"]),
+ "TM44 Rest": ItemData(244, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM45 Thunder Wave": ItemData(245, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM46 Psywave": ItemData(246, ItemClassification.filler, ["Unique", "TMs"]),
+ "TM47 Explosion": ItemData(247, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM48 Rock Slide": ItemData(248, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM49 Tri Attack": ItemData(249, ItemClassification.useful, ["Unique", "TMs"]),
+ "TM50 Substitute": ItemData(250, ItemClassification.useful, ["Unique", "TMs"]),
+
+ "Fuji Saved": ItemData(None, ItemClassification.progression, []),
+ "Silph Co Liberated": ItemData(None, ItemClassification.progression, []),
+ "Become Champion": ItemData(None, ItemClassification.progression, [])
+}
+item_table.update(
+ {pokemon: ItemData(None, ItemClassification.progression, []) for pokemon in pokemon_data.keys()}
+)
+item_table.update(
+ {f"Missable {pokemon}": ItemData(None, ItemClassification.useful, []) for pokemon in pokemon_data.keys()}
+)
+item_table.update(
+ {f"Static {pokemon}": ItemData(None, ItemClassification.progression, []) for pokemon in pokemon_data.keys()}
+)
+
+
+item_groups = {}
+for item, data in item_table.items():
+ for group in data.groups:
+ item_groups[group] = item_groups.get(group, []) + [item]
\ No newline at end of file
diff --git a/worlds/pokemon_rb/locations.py b/worlds/pokemon_rb/locations.py
new file mode 100644
index 0000000000..a619336f57
--- /dev/null
+++ b/worlds/pokemon_rb/locations.py
@@ -0,0 +1,1719 @@
+
+from BaseClasses import Location
+from .rom_addresses import rom_addresses
+loc_id_start = 17200000
+
+class LocationData:
+ def __init__(self, region, name, original_item, rom_address=None, ram_address=None, event=False, type="Item"):
+ self.region = region
+ if "Route" in region:
+ region = " ".join(region.split()[:2])
+ self.name = region + " - " + name
+ self.original_item = original_item
+ self.rom_address = rom_address
+ self.ram_address = ram_address
+ self.event = event
+ self.type = type
+
+class EventFlag:
+ def __init__(self, flag):
+ self.byte = int(flag / 8)
+ self.bit = flag % 8
+ self.flag = flag
+
+
+class Missable:
+ def __init__(self, flag):
+ self.byte = int(flag / 8)
+ self.bit = flag % 8
+ self.flag = flag
+
+
+class Hidden:
+ def __init__(self, flag):
+ self.byte = int(flag / 8)
+ self.bit = flag % 8
+ self.flag = flag
+
+
+class Rod:
+ def __init__(self, flag):
+ self.byte = 0
+ self.bit = flag
+ self.flag = flag
+
+# def get_locations(player=None):
+location_data = [
+
+ LocationData("Vermilion City", "Fishing Guru", "Old Rod", rom_addresses["Rod_Vermilion_City_Fishing_Guru"], Rod(3)),
+ LocationData("Fuchsia City", "Fishing Guru's Brother", "Good Rod", rom_addresses["Rod_Fuchsia_City_Fishing_Brother"], Rod(4)),
+ LocationData("Route 12 South", "Fishing Guru's Brother", "Super Rod", rom_addresses["Rod_Route12_Fishing_Brother"], Rod(5)),
+
+ LocationData("Pallet Town", "Player's PC", "Potion", rom_addresses['PC_Item'], EventFlag(1),),
+ LocationData("Celadon City", "Mansion Lady", "Tea", rom_addresses["Event_Mansion_Lady"], EventFlag(2)),
+ LocationData("Pallet Town", "Rival's Sister", "Town Map", rom_addresses["Event_Rivals_Sister"], EventFlag(24)),
+ LocationData("Pallet Town", "Oak's Post-Route-22-Rival Gift", "Poke Ball", rom_addresses["Event_Oaks_Gift"], EventFlag(36)),
+ LocationData("Route 1", "Free Sample Man", "Potion", rom_addresses["Event_Free_Sample"], EventFlag(960)),
+ LocationData("Viridian City", "Sleepy Guy", "TM42 Dream Eater", rom_addresses["Event_Sleepy_Guy"],
+ EventFlag(41)),
+ LocationData("Viridian City", "Pokemart", "Oak's Parcel", rom_addresses["Event_Pokemart_Quest"],
+ EventFlag(57)),
+ LocationData("Viridian Gym", "Giovanni 2", "TM27 Fissure", rom_addresses["Event_Viridian_Gym"], EventFlag(80)),
+ LocationData("Route 2 East", "Oak's Aide", "HM05 Flash", rom_addresses["Event_Route_2_Oaks_Aide"],
+ EventFlag(984)),
+ LocationData("Pewter City", "Museum", "Old Amber", rom_addresses["Event_Museum"], EventFlag(105)),
+ LocationData("Pewter Gym", "Brock 2", "TM34 Bide", rom_addresses["Event_Pewter_Gym"], EventFlag(118)),
+ LocationData("Cerulean City", "Bicycle Shop", "Bicycle", rom_addresses["Event_Bicycle_Shop"], EventFlag(192)),
+ LocationData("Cerulean Gym", "Misty 2", "TM11 Bubble Beam", rom_addresses["Event_Cerulean_Gym"],
+ EventFlag(190)),
+ LocationData("Route 24", "Nugget Bridge", "Nugget", rom_addresses["Event_Nugget_Bridge"], EventFlag(1344)),
+ LocationData("Route 25", "Bill", "S.S. Ticket", rom_addresses["Event_Bill"], EventFlag(1372)),
+ LocationData("Lavender Town", "Mr. Fuji", "Poke Flute", rom_addresses["Event_Fuji"], EventFlag(296)),
+ LocationData("Route 12 North", "Mourning Girl", "TM39 Swift", rom_addresses["Event_Mourning_Girl"],
+ EventFlag(1152)),
+ LocationData("Vermilion City", "Pokemon Fan Club", "Bike Voucher", rom_addresses["Event_Pokemon_Fan_Club"],
+ EventFlag(337)),
+ LocationData("Vermilion Gym", "Lt. Surge 2", "TM24 Thunderbolt", rom_addresses["Event_Vermillion_Gym"],
+ EventFlag(358)),
+ LocationData("S.S. Anne 2F", "Captain", "HM01 Cut", rom_addresses["Event_SS_Anne_Captain"], EventFlag(1504)),
+ LocationData("Route 11 East", "Oak's Aide", "Item Finder", rom_addresses["Event_Rt11_Oaks_Aide"],
+ EventFlag(1151)),
+ LocationData("Celadon City", "Stranded Man", "TM41 Soft Boiled", rom_addresses["Event_Stranded_Man"],
+ EventFlag(384)),
+ LocationData("Celadon City", "Thirsty Girl Gets Water", "TM13 Ice Beam",
+ rom_addresses["Event_Thirsty_Girl_Water"], EventFlag(396)),
+ LocationData("Celadon City", "Thirsty Girl Gets Soda Pop", "TM48 Rock Slide",
+ rom_addresses["Event_Thirsty_Girl_Soda"], EventFlag(397)),
+ LocationData("Celadon City", "Thirsty Girl Gets Lemonade", "TM49 Tri Attack",
+ rom_addresses["Event_Thirsty_Girl_Lemonade"], EventFlag(398)),
+ LocationData("Celadon City", "Counter Man", "TM18 Counter", rom_addresses["Event_Counter"], EventFlag(399)),
+ LocationData("Celadon City", "Gambling Addict", "Coin Case", rom_addresses["Event_Gambling_Addict"],
+ EventFlag(480)),
+ LocationData("Celadon Gym", "Erika 2", "TM21 Mega Drain", rom_addresses["Event_Celadon_Gym"], EventFlag(424)),
+ LocationData("Silph Co 11F", "Silph Co President", "Master Ball", rom_addresses["Event_Silph_Co_President"],
+ EventFlag(1933)),
+ LocationData("Silph Co 2F", "Woman", "TM36 Self Destruct", rom_addresses["Event_Scared_Woman"],
+ EventFlag(1791)),
+ LocationData("Route 16 North", "House Woman", "HM02 Fly", rom_addresses["Event_Rt16_House_Woman"], EventFlag(1230)),
+ LocationData("Route 15", "Oak's Aide", "Exp. All", rom_addresses["Event_Rt_15_Oaks_Aide"], EventFlag(1200)),
+ LocationData("Fuchsia City", "Safari Zone Warden", "HM04 Strength", rom_addresses["Event_Warden"], EventFlag(568)),
+ LocationData("Fuchsia Gym", "Koga 2", "TM06 Toxic", rom_addresses["Event_Fuschia_Gym"], EventFlag(600)),
+ LocationData("Safari Zone West", "Secret House", "HM03 Surf", rom_addresses["Event_Safari_Zone_Secret_House"], EventFlag(2176)),
+ LocationData("Cinnabar Island", "Lab Scientist", "TM35 Metronome", rom_addresses["Event_Lab_Scientist"], EventFlag(727)),
+ LocationData("Cinnabar Gym", "Blaine 2", "TM38 Fire Blast", rom_addresses["Event_Cinnabar_Gym"],
+ EventFlag(664)),
+ LocationData("Copycat's House", "Copycat", "TM31 Mimic", rom_addresses["Event_Copycat"], EventFlag(832)),
+ LocationData("Saffron City", "Mr. Psychic", "TM29 Psychic", rom_addresses["Event_Mr_Psychic"], EventFlag(944)),
+ LocationData("Saffron Gym", "Sabrina 2", "TM46 Psywave", rom_addresses["Event_Saffron_Gym"], EventFlag(864)),
+ LocationData("Fossil", "Choice A", "Dome Fossil",
+ [rom_addresses["Event_Dome_Fossil"], rom_addresses["Event_Dome_Fossil_B"],
+ rom_addresses["Dome_Fossil_Text"]], EventFlag(0x57E)),
+ LocationData("Fossil", "Choice B", "Helix Fossil",
+ [rom_addresses["Event_Helix_Fossil"], rom_addresses["Event_Helix_Fossil_B"],
+ rom_addresses["Helix_Fossil_Text"]], EventFlag(0x57F)),
+
+ LocationData("Cerulean City", "Rocket Thief", "TM28 Dig", rom_addresses["Event_Rocket_Thief"],
+ Missable(6)),
+ LocationData("Route 2 East", "South Item", "Moon Stone", rom_addresses["Missable_Route_2_Item_1"],
+ Missable(25)),
+ LocationData("Route 2 East", "North Item", "HP Up", rom_addresses["Missable_Route_2_Item_2"], Missable(26)),
+ LocationData("Route 4", "Item", "TM04 Whirlwind", rom_addresses["Missable_Route_4_Item"], Missable(27)),
+ LocationData("Route 9", "Item", "TM30 Teleport", rom_addresses["Missable_Route_9_Item"], Missable(28)),
+ LocationData("Route 12 North", "Island Item", "TM16 Pay Day", rom_addresses["Missable_Route_12_Item_1"], Missable(30)),
+ LocationData("Route 12 South", "Item Behind Cuttable Tree", "Iron", rom_addresses["Missable_Route_12_Item_2"], Missable(31)),
+ LocationData("Route 15", "Item", "TM20 Rage", rom_addresses["Missable_Route_15_Item"], Missable(32)),
+ LocationData("Route 24", "Item", "TM45 Thunder Wave", rom_addresses["Missable_Route_24_Item"], Missable(37)),
+ LocationData("Route 25", "Item", "TM19 Seismic Toss", rom_addresses["Missable_Route_25_Item"], Missable(38)),
+ LocationData("Viridian Gym", "Item", "Revive", rom_addresses["Missable_Viridian_Gym_Item"], Missable(51)),
+ LocationData("Cerulean Cave 1F", "Southwest Item", "Full Restore", rom_addresses["Missable_Cerulean_Cave_1F_Item_1"],
+ Missable(53)),
+ LocationData("Cerulean Cave 1F", "Northeast Item", "Max Elixir", rom_addresses["Missable_Cerulean_Cave_1F_Item_2"],
+ Missable(54)),
+ LocationData("Cerulean Cave 1F", "Northwest Item", "Nugget", rom_addresses["Missable_Cerulean_Cave_1F_Item_3"],
+ Missable(55)),
+ LocationData("Pokemon Tower 3F", "North Item", "Escape Rope", rom_addresses["Missable_Pokemon_Tower_3F_Item"],
+ Missable(57)),
+ LocationData("Pokemon Tower 4F", "East Item", "Elixir", rom_addresses["Missable_Pokemon_Tower_4F_Item_1"],
+ Missable(58)),
+ LocationData("Pokemon Tower 4F", "West Item", "Awakening", rom_addresses["Missable_Pokemon_Tower_4F_Item_2"],
+ Missable(59)),
+ LocationData("Pokemon Tower 4F", "South Item", "HP Up", rom_addresses["Missable_Pokemon_Tower_4F_Item_3"],
+ Missable(60)),
+ LocationData("Pokemon Tower 5F", "Southwest Item", "Nugget", rom_addresses["Missable_Pokemon_Tower_5F_Item"],
+ Missable(61)),
+ LocationData("Pokemon Tower 6F", "West Item", "Rare Candy", rom_addresses["Missable_Pokemon_Tower_6F_Item_1"],
+ Missable(62)),
+ LocationData("Pokemon Tower 6F", "Southeast Item", "X Accuracy", rom_addresses["Missable_Pokemon_Tower_6F_Item_2"],
+ Missable(63)),
+ LocationData("Fuchsia City", "Warden's House Item", "Rare Candy", rom_addresses["Missable_Wardens_House_Item"],
+ Missable(71)),
+ LocationData("Pokemon Mansion 1F", "North Item", "Escape Rope",
+ rom_addresses["Missable_Pokemon_Mansion_1F_Item_1"], Missable(72)),
+ LocationData("Pokemon Mansion 1F", "South Item", "Carbos", rom_addresses["Missable_Pokemon_Mansion_1F_Item_2"],
+ Missable(73)),
+ LocationData("Power Plant", "Southwest Item", "Carbos", rom_addresses["Missable_Power_Plant_Item_1"], Missable(86)),
+ LocationData("Power Plant", "North Item", "HP Up", rom_addresses["Missable_Power_Plant_Item_2"], Missable(87)),
+ LocationData("Power Plant", "Northeast Item", "Rare Candy", rom_addresses["Missable_Power_Plant_Item_3"],
+ Missable(88)),
+ LocationData("Power Plant", "Southeast Item", "TM25 Thunder", rom_addresses["Missable_Power_Plant_Item_4"],
+ Missable(89)),
+ LocationData("Power Plant", "South Item", "TM33 Reflect", rom_addresses["Missable_Power_Plant_Item_5"],
+ Missable(90)),
+ LocationData("Victory Road 2F", "Northeast Item", "TM17 Submission", rom_addresses["Missable_Victory_Road_2F_Item_1"],
+ Missable(92)),
+ LocationData("Victory Road 2F", "East Item", "Full Heal", rom_addresses["Missable_Victory_Road_2F_Item_2"],
+ Missable(93)),
+ LocationData("Victory Road 2F", "West Item", "TM05 Mega Kick", rom_addresses["Missable_Victory_Road_2F_Item_3"],
+ Missable(94)),
+ LocationData("Victory Road 2F", "North Item Near Moltres", "Guard Spec", rom_addresses["Missable_Victory_Road_2F_Item_4"],
+ Missable(95)),
+ LocationData("Viridian Forest", "East Item", "Antidote", rom_addresses["Missable_Viridian_Forest_Item_1"],
+ Missable(100)),
+ LocationData("Viridian Forest", "Northwest Item", "Potion", rom_addresses["Missable_Viridian_Forest_Item_2"],
+ Missable(101)),
+ LocationData("Viridian Forest", "Southwest Item", "Poke Ball",
+ rom_addresses["Missable_Viridian_Forest_Item_3"], Missable(102)),
+ LocationData("Mt Moon 1F", "West Item", "Potion", rom_addresses["Missable_Mt_Moon_1F_Item_1"], Missable(103)),
+ LocationData("Mt Moon 1F", "Northwest Item", "Moon Stone", rom_addresses["Missable_Mt_Moon_1F_Item_2"], Missable(104)),
+ LocationData("Mt Moon 1F", "Southeast Item", "Rare Candy", rom_addresses["Missable_Mt_Moon_1F_Item_3"], Missable(105)),
+ LocationData("Mt Moon 1F", "East Item", "Escape Rope", rom_addresses["Missable_Mt_Moon_1F_Item_4"],
+ Missable(106)),
+ LocationData("Mt Moon 1F", "South Item", "Potion", rom_addresses["Missable_Mt_Moon_1F_Item_5"], Missable(107)),
+ LocationData("Mt Moon 1F", "Southwest Item", "TM12 Water Gun", rom_addresses["Missable_Mt_Moon_1F_Item_6"],
+ Missable(108)),
+ LocationData("Mt Moon B2F", "South Item", "HP Up", rom_addresses["Missable_Mt_Moon_B2F_Item_1"], Missable(111)),
+ LocationData("Mt Moon B2F", "North Item", "TM01 Mega Punch", rom_addresses["Missable_Mt_Moon_B2F_Item_2"],
+ Missable(112)),
+ LocationData("S.S. Anne 1F", "Item", "TM08 Body Slam", rom_addresses["Missable_SS_Anne_1F_Item"],
+ Missable(114)),
+ LocationData("S.S. Anne 2F", "Item 1", "Max Ether", rom_addresses["Missable_SS_Anne_2F_Item_1"],
+ Missable(115)),
+ LocationData("S.S. Anne 2F", "Item 2", "Rare Candy", rom_addresses["Missable_SS_Anne_2F_Item_2"],
+ Missable(116)),
+ LocationData("S.S. Anne B1F", "Item 1", "Ether", rom_addresses["Missable_SS_Anne_B1F_Item_1"], Missable(117)),
+ LocationData("S.S. Anne B1F", "Item 2", "TM44 Rest", rom_addresses["Missable_SS_Anne_B1F_Item_2"],
+ Missable(118)),
+ LocationData("S.S. Anne B1F", "Item 3", "Max Potion", rom_addresses["Missable_SS_Anne_B1F_Item_3"],
+ Missable(119)),
+ LocationData("Victory Road 3F", "Northeast Item", "Max Revive", rom_addresses["Missable_Victory_Road_3F_Item_1"],
+ Missable(120)),
+ LocationData("Victory Road 3F", "Northwest Item", "TM47 Explosion", rom_addresses["Missable_Victory_Road_3F_Item_2"],
+ Missable(121)),
+ LocationData("Rocket Hideout B1F", "West Item", "Escape Rope",
+ rom_addresses["Missable_Rocket_Hideout_B1F_Item_1"], Missable(123)),
+ LocationData("Rocket Hideout B1F", "Southwest Item", "Hyper Potion",
+ rom_addresses["Missable_Rocket_Hideout_B1F_Item_2"], Missable(124)),
+ LocationData("Rocket Hideout B2F", "Northwest Left Item", "Moon Stone", rom_addresses["Missable_Rocket_Hideout_B2F_Item_1"],
+ Missable(125)),
+ LocationData("Rocket Hideout B2F", "Northeast Item", "Nugget", rom_addresses["Missable_Rocket_Hideout_B2F_Item_2"],
+ Missable(126)),
+ LocationData("Rocket Hideout B2F", "Northwest Right Item", "TM07 Horn Drill",
+ rom_addresses["Missable_Rocket_Hideout_B2F_Item_3"], Missable(127)),
+ LocationData("Rocket Hideout B2F", "Southwest Item", "Super Potion",
+ rom_addresses["Missable_Rocket_Hideout_B2F_Item_4"], Missable(128)),
+ LocationData("Rocket Hideout B3F", "East Item", "TM10 Double Edge",
+ rom_addresses["Missable_Rocket_Hideout_B3F_Item_1"], Missable(129)),
+ LocationData("Rocket Hideout B3F", "Center Item", "Rare Candy", rom_addresses["Missable_Rocket_Hideout_B3F_Item_2"],
+ Missable(130)),
+ LocationData("Rocket Hideout B4F", "West Item", "HP Up", rom_addresses["Missable_Rocket_Hideout_B4F_Item_1"],
+ Missable(132)),
+ LocationData("Rocket Hideout B4F", "Northwest Item", "TM02 Razor Wind",
+ rom_addresses["Missable_Rocket_Hideout_B4F_Item_2"], Missable(133)),
+ LocationData("Rocket Hideout B4F", "Southwest Item (Lift Key)", "Iron", rom_addresses["Missable_Rocket_Hideout_B4F_Item_3"],
+ Missable(134)),
+ LocationData("Rocket Hideout B4F", "Giovanni Item (Lift Key)", "Silph Scope",
+ rom_addresses["Missable_Rocket_Hideout_B4F_Item_4"], [EventFlag(0x6A7), Missable(135)]),
+ LocationData("Rocket Hideout B4F", "Rocket Grunt Item", "Lift Key", rom_addresses["Missable_Rocket_Hideout_B4F_Item_5"],
+ [EventFlag(0x6A6), Missable(136)]),
+ LocationData("Silph Co 3F", "Item (Card Key)", "Hyper Potion", rom_addresses["Missable_Silph_Co_3F_Item"], Missable(144)),
+ LocationData("Silph Co 4F", "Left Item (Card Key)", "Full Heal", rom_addresses["Missable_Silph_Co_4F_Item_1"],
+ Missable(148)),
+ LocationData("Silph Co 4F", "Middle Item (Card Key)", "Max Revive", rom_addresses["Missable_Silph_Co_4F_Item_2"],
+ Missable(149)),
+ LocationData("Silph Co 4F", "Right Item (Card Key)", "Escape Rope", rom_addresses["Missable_Silph_Co_4F_Item_3"],
+ Missable(150)),
+ LocationData("Silph Co 5F", "Southwest Item", "TM09 Take Down", rom_addresses["Missable_Silph_Co_5F_Item_1"],
+ Missable(155)),
+ LocationData("Silph Co 5F", "Northwest Item (Card Key)", "Protein", rom_addresses["Missable_Silph_Co_5F_Item_2"], Missable(156)),
+ LocationData("Silph Co 5F", "Southeast Item", "Card Key", rom_addresses["Missable_Silph_Co_5F_Item_3"], Missable(157)),
+ LocationData("Silph Co 6F", "West Item (Card Key)", "HP Up", rom_addresses["Missable_Silph_Co_6F_Item_1"], Missable(161)),
+ LocationData("Silph Co 6F", "Southwest Item (Card Key)", "X Accuracy", rom_addresses["Missable_Silph_Co_6F_Item_2"],
+ Missable(162)),
+ LocationData("Silph Co 7F", "West Item", "Calcium", rom_addresses["Missable_Silph_Co_7F_Item_1"], Missable(168)),
+ LocationData("Silph Co 7F", "East Item (Card Key)", "TM03 Swords Dance", rom_addresses["Missable_Silph_Co_7F_Item_2"],
+ Missable(169)),
+ LocationData("Silph Co 10F", "Left Item", "TM26 Earthquake", rom_addresses["Missable_Silph_Co_10F_Item_1"],
+ Missable(180)),
+ LocationData("Silph Co 10F", "Bottom Item", "Rare Candy", rom_addresses["Missable_Silph_Co_10F_Item_2"],
+ Missable(181)),
+ LocationData("Silph Co 10F", "Right Item", "Carbos", rom_addresses["Missable_Silph_Co_10F_Item_3"], Missable(182)),
+ LocationData("Pokemon Mansion 2F", "Northeast Item", "Calcium", rom_addresses["Missable_Pokemon_Mansion_2F_Item"],
+ Missable(187)),
+ LocationData("Pokemon Mansion 3F", "Southwest Item", "Max Potion", rom_addresses["Missable_Pokemon_Mansion_3F_Item_1"],
+ Missable(188)),
+ LocationData("Pokemon Mansion 3F", "Northeast Item", "Iron", rom_addresses["Missable_Pokemon_Mansion_3F_Item_2"],
+ Missable(189)),
+ LocationData("Pokemon Mansion B1F", "North Item", "Rare Candy",
+ rom_addresses["Missable_Pokemon_Mansion_B1F_Item_1"], Missable(190)),
+ LocationData("Pokemon Mansion B1F", "Southwest Item", "Full Restore",
+ rom_addresses["Missable_Pokemon_Mansion_B1F_Item_2"], Missable(191)),
+ LocationData("Pokemon Mansion B1F", "South Item", "TM14 Blizzard",
+ rom_addresses["Missable_Pokemon_Mansion_B1F_Item_3"], Missable(192)),
+ LocationData("Pokemon Mansion B1F", "Northwest Item", "TM22 Solar Beam",
+ rom_addresses["Missable_Pokemon_Mansion_B1F_Item_4"], Missable(193)),
+ LocationData("Pokemon Mansion B1F", "West Item", "Secret Key",
+ rom_addresses["Missable_Pokemon_Mansion_B1F_Item_5"], Missable(194)),
+ LocationData("Safari Zone East", "Northeast Item", "Full Restore", rom_addresses["Missable_Safari_Zone_East_Item_1"],
+ Missable(195)),
+ LocationData("Safari Zone East", "West Item", "Max Potion", rom_addresses["Missable_Safari_Zone_East_Item_2"],
+ Missable(196)),
+ LocationData("Safari Zone East", "East Item", "Carbos", rom_addresses["Missable_Safari_Zone_East_Item_3"],
+ Missable(197)),
+ LocationData("Safari Zone East", "Center Item", "TM37 Egg Bomb", rom_addresses["Missable_Safari_Zone_East_Item_4"],
+ Missable(198)),
+ LocationData("Safari Zone North", "Northeast Item", "Protein", rom_addresses["Missable_Safari_Zone_North_Item_1"],
+ Missable(199)),
+ LocationData("Safari Zone North", "North Item", "TM40 Skull Bash",
+ rom_addresses["Missable_Safari_Zone_North_Item_2"], Missable(200)),
+ LocationData("Safari Zone West", "Southwest Item", "Max Potion", rom_addresses["Missable_Safari_Zone_West_Item_1"],
+ Missable(201)),
+ LocationData("Safari Zone West", "Northwest Item", "TM32 Double Team",
+ rom_addresses["Missable_Safari_Zone_West_Item_2"], Missable(202)),
+ LocationData("Safari Zone West", "Southeast Item", "Max Revive", rom_addresses["Missable_Safari_Zone_West_Item_3"],
+ Missable(203)),
+ LocationData("Safari Zone West", "Northeast Item", "Gold Teeth", rom_addresses["Missable_Safari_Zone_West_Item_4"],
+ Missable(204)),
+ LocationData("Safari Zone Center", "Island Item", "Nugget", rom_addresses["Missable_Safari_Zone_Center_Item"],
+ Missable(205)),
+ LocationData("Cerulean Cave 2F", "East Item", "PP Up", rom_addresses["Missable_Cerulean_Cave_2F_Item_1"],
+ Missable(206)),
+ LocationData("Cerulean Cave 2F", "Southwest Item", "Ultra Ball", rom_addresses["Missable_Cerulean_Cave_2F_Item_2"],
+ Missable(207)),
+ LocationData("Cerulean Cave 2F", "North Item", "Full Restore", rom_addresses["Missable_Cerulean_Cave_2F_Item_3"],
+ Missable(208)),
+ LocationData("Cerulean Cave B1F", "Center Item", "Ultra Ball", rom_addresses["Missable_Cerulean_Cave_B1F_Item_1"],
+ Missable(210)),
+ LocationData("Cerulean Cave B1F", "North Item", "Max Revive", rom_addresses["Missable_Cerulean_Cave_B1F_Item_2"],
+ Missable(211)),
+ LocationData("Victory Road 1F", "Top Item", "TM43 Sky Attack", rom_addresses["Missable_Victory_Road_1F_Item_1"],
+ Missable(212)),
+ LocationData("Victory Road 1F", "Left Item", "Rare Candy", rom_addresses["Missable_Victory_Road_1F_Item_2"],
+ Missable(213)),
+ LocationData("Rock Tunnel B1F", "Southwest Item", "Hideout Key", rom_addresses["Missable_Rock_Tunnel_B1F_Item_1"],
+ Missable(231)),
+ LocationData("Rock Tunnel B1F", "West Item", "Mansion Key", rom_addresses["Missable_Rock_Tunnel_B1F_Item_2"],
+ Missable(232)),
+ LocationData("Rock Tunnel B1F", "Northwest Item", "Plant Key", rom_addresses["Missable_Rock_Tunnel_B1F_Item_3"],
+ Missable(233)),
+ LocationData("Rock Tunnel B1F", "North Item", "Safari Pass", rom_addresses["Missable_Rock_Tunnel_B1F_Item_4"],
+ Missable(234)),
+
+ LocationData("Pewter Gym", "Brock 1", "Boulder Badge", rom_addresses['Badge_Pewter_Gym'], EventFlag(0x8A0)),
+ LocationData("Cerulean Gym", "Misty 1", "Cascade Badge", rom_addresses['Badge_Cerulean_Gym'], EventFlag(0x8A1)),
+ LocationData("Vermilion Gym", "Lt. Surge 1", "Thunder Badge", rom_addresses['Badge_Vermilion_Gym'], EventFlag(0x8A2)),
+ LocationData("Celadon Gym", "Erika 1", "Rainbow Badge", rom_addresses['Badge_Celadon_Gym'], EventFlag(0x8A3)),
+ LocationData("Fuchsia Gym", "Koga 1", "Soul Badge", rom_addresses['Badge_Fuchsia_Gym'], EventFlag(0x8A4)),
+ LocationData("Saffron Gym", "Sabrina 1", "Marsh Badge", rom_addresses['Badge_Saffron_Gym'], EventFlag(0x8A5)),
+ LocationData("Cinnabar Gym", "Blaine 1", "Volcano Badge", rom_addresses['Badge_Cinnabar_Gym'], EventFlag(0x8A6)),
+ LocationData("Viridian Gym", "Giovanni 1", "Earth Badge", rom_addresses['Badge_Viridian_Gym'], EventFlag(0x8A7)),
+
+ LocationData("Viridian Forest", "Hidden Item Northwest by Trainer", "Potion", rom_addresses['Hidden_Item_Viridian_Forest_1'], Hidden(0)),
+ LocationData("Viridian Forest", "Hidden Item Entrance Tree", "Antidote", rom_addresses['Hidden_Item_Viridian_Forest_2'], Hidden(1)),
+ LocationData("Mt Moon B2F", "Hidden Item Dead End Before Fossils", "Moon Stone", rom_addresses['Hidden_Item_MtMoonB2F_1'], Hidden(2)),
+ LocationData("Route 25", "Hidden Item Fence Outside Bill's House", "Ether", rom_addresses['Hidden_Item_Route_25_1'], Hidden(3)),
+ LocationData("Route 9", "Hidden Item Rock By Grass", "Ether", rom_addresses['Hidden_Item_Route_9'], Hidden(4)),
+ LocationData("S.S. Anne 1F", "Hidden Item Kitchen Trash", "Great Ball", rom_addresses['Hidden_Item_SS_Anne_Kitchen'], Hidden(5)),
+ LocationData("S.S. Anne B1F", "Hidden Item Under Pillow", "Hyper Potion", rom_addresses['Hidden_Item_SS_Anne_B1F'], Hidden(6)),
+ LocationData("Route 10 North", "Hidden Item Behind Rock Tunnel Entrance Tree", "Super Potion", rom_addresses['Hidden_Item_Route_10_1'], Hidden(7)),
+ LocationData("Route 10 South", "Hidden Item Rock", "Max Ether", rom_addresses['Hidden_Item_Route_10_2'], Hidden(8)),
+ LocationData("Rocket Hideout B1F", "Hidden Item Pot Plant", "PP Up", rom_addresses['Hidden_Item_Rocket_Hideout_B1F'], Hidden(9)),
+ LocationData("Rocket Hideout B3F", "Hidden Item Near East Item", "Nugget", rom_addresses['Hidden_Item_Rocket_Hideout_B3F'], Hidden(10)),
+ LocationData("Rocket Hideout B4F", "Hidden Item Behind Giovanni", "Super Potion", rom_addresses['Hidden_Item_Rocket_Hideout_B4F'], Hidden(11)),
+ LocationData("Pokemon Tower 5F", "Hidden Item Near West Staircase", "Elixir", rom_addresses['Hidden_Item_Pokemon_Tower_5F'], Hidden(12)),
+ LocationData("Route 13", "Hidden Item Dead End Boulder", "PP Up", rom_addresses['Hidden_Item_Route_13_1'], Hidden(13)),
+ LocationData("Route 13", "Hidden Item Dead End By Water Corner", "Calcium", rom_addresses['Hidden_Item_Route_13_2'], Hidden(14)),
+ LocationData("Pokemon Mansion B1F", "Hidden Item Secret Key Room Corner", "Rare Candy", rom_addresses['Hidden_Item_Pokemon_Mansion_B1F'], Hidden(15)),
+ LocationData("Safari Zone West", "Hidden Item Secret House Statue", "Revive", rom_addresses['Hidden_Item_Safari_Zone_West'], Hidden(17)),
+ LocationData("Silph Co 5F", "Hidden Item Pot Plant", "Elixir", rom_addresses['Hidden_Item_Silph_Co_5F'], Hidden(18)),
+ LocationData("Silph Co 9F", "Hidden Item Nurse Bed", "Max Potion", rom_addresses['Hidden_Item_Silph_Co_9F'], Hidden(19)),
+ LocationData("Copycat's House", "Hidden Item Desk", "Nugget", rom_addresses['Hidden_Item_Copycats_House'], Hidden(20)),
+ LocationData("Cerulean Cave 1F", "Hidden Item Center Rocks", "Rare Candy", rom_addresses['Hidden_Item_Cerulean_Cave_1F'], Hidden(21)),
+ LocationData("Cerulean Cave B1F", "Hidden Item Northeast Rocks", "Ultra Ball", rom_addresses['Hidden_Item_Cerulean_Cave_B1F'], Hidden(22)),
+ LocationData("Power Plant", "Hidden Item Central Dead End", "Max Elixir", rom_addresses['Hidden_Item_Power_Plant_1'], Hidden(23)),
+ LocationData("Power Plant", "Hidden Item Before Zapdos", "PP Up", rom_addresses['Hidden_Item_Power_Plant_2'], Hidden(24)),
+ LocationData("Seafoam Islands B2F", "Hidden Item Rock", "Nugget", rom_addresses['Hidden_Item_Seafoam_Islands_B2F'], Hidden(25)),
+ LocationData("Seafoam Islands B4F", "Hidden Item Corner Island", "Ultra Ball", rom_addresses['Hidden_Item_Seafoam_Islands_B4F'], Hidden(26)),
+ LocationData("Pokemon Mansion 1F", "Hidden Item Block Near Entrance Carpet", "Moon Stone", rom_addresses['Hidden_Item_Pokemon_Mansion_1F'], Hidden(27)),
+ LocationData("Pokemon Mansion 3F", "Hidden Item Behind Burglar", "Max Revive", rom_addresses['Hidden_Item_Pokemon_Mansion_3F'], Hidden(28)),
+ LocationData("Route 23 North", "Hidden Item Rocks Before Final Guard", "Full Restore", rom_addresses['Hidden_Item_Route_23_1'], Hidden(29)),
+ LocationData("Route 23 North", "Hidden Item East Tree After Water", "Ultra Ball", rom_addresses['Hidden_Item_Route_23_2'], Hidden(30)),
+ LocationData("Route 23 South", "Hidden Item On Island", "Max Ether", rom_addresses['Hidden_Item_Route_23_3'], Hidden(31)),
+ LocationData("Victory Road 2F", "Hidden Item Rock Before Moltres", "Ultra Ball", rom_addresses['Hidden_Item_Victory_Road_2F_1'], Hidden(32)),
+ LocationData("Victory Road 2F", "Hidden Item Rock In Final Room", "Full Restore", rom_addresses['Hidden_Item_Victory_Road_2F_2'], Hidden(33)),
+ #LocationData("Vermilion City", "Hidden Item The Truck", "Max Elixir", rom_addresses['Hidden_Item_Unused_6F'], Hidden(34)),
+ LocationData("Viridian City", "Hidden Item Cuttable Tree", "Potion", rom_addresses['Hidden_Item_Viridian_City'], Hidden(35)),
+ LocationData("Route 11", "Hidden Item Isolated Tree Near Gate", "Potion", rom_addresses['Hidden_Item_Route_11'], Hidden(36)),
+ LocationData("Route 12 West", "Hidden Item Tree Near Gate", "Hyper Potion", rom_addresses['Hidden_Item_Route_12'], Hidden(37)),
+ LocationData("Route 17", "Hidden Item In Grass", "Rare Candy", rom_addresses['Hidden_Item_Route_17_1'], Hidden(38)),
+ LocationData("Route 17", "Hidden Item Near Northernmost Sign", "Full Restore", rom_addresses['Hidden_Item_Route_17_2'], Hidden(39)),
+ LocationData("Route 17", "Hidden Item East Center", "PP Up", rom_addresses['Hidden_Item_Route_17_3'], Hidden(40)),
+ LocationData("Route 17", "Hidden Item West Center", "Max Revive", rom_addresses['Hidden_Item_Route_17_4'], Hidden(41)),
+ LocationData("Route 17", "Hidden Item Before Final Bridge", "Max Elixir", rom_addresses['Hidden_Item_Route_17_5'], Hidden(42)),
+ LocationData("Underground Tunnel North-South", "Hidden Item Near Northern Stairs", "Full Restore", rom_addresses['Hidden_Item_Underground_Path_NS_1'], Hidden(43)),
+ LocationData("Underground Tunnel North-South", "Hidden Item Near Southern Stairs", "X Special", rom_addresses['Hidden_Item_Underground_Path_NS_2'], Hidden(44)),
+ LocationData("Underground Tunnel West-East", "Hidden Item West", "Nugget", rom_addresses['Hidden_Item_Underground_Path_WE_1'], Hidden(45)),
+ LocationData("Underground Tunnel West-East", "Hidden Item East", "Elixir", rom_addresses['Hidden_Item_Underground_Path_WE_2'], Hidden(46)),
+ LocationData("Celadon City", "Hidden Item Dead End Near Cuttable Tree", "PP Up", rom_addresses['Hidden_Item_Celadon_City'], Hidden(47)),
+ LocationData("Route 25", "Hidden Item Northeast Of Grass", "Elixir", rom_addresses['Hidden_Item_Route_25_2'], Hidden(48)),
+ LocationData("Mt Moon B2F", "Hidden Item Lone Rock", "Ether", rom_addresses['Hidden_Item_MtMoonB2F_2'], Hidden(49)),
+ LocationData("Seafoam Islands B3F", "Hidden Item Rock", "Max Elixir", rom_addresses['Hidden_Item_Seafoam_Islands_B3F'], Hidden(50)),
+ LocationData("Vermilion City", "Hidden Item In Water Near Fan Club", "Max Ether", rom_addresses['Hidden_Item_Vermilion_City'], Hidden(51)),
+ LocationData("Cerulean City", "Hidden Item Gym Badge Guy's Backyard", "Rare Candy", rom_addresses['Hidden_Item_Cerulean_City'], Hidden(52)),
+ LocationData("Route 4", "Hidden Item Plateau East Of Mt Moon", "Great Ball", rom_addresses['Hidden_Item_Route_4'], Hidden(53)),
+
+ LocationData("Indigo Plateau", "Become Champion", "Become Champion", event=True),
+ LocationData("Pokemon Tower 7F", "Fuji Saved", "Fuji Saved", event=True),
+ LocationData("Silph Co 11F", "Silph Co Liberated", "Silph Co Liberated", event=True),
+
+ LocationData("Pallet Town", "Super Rod Pokemon - 1", "Tentacool", rom_addresses["Wild_Super_Rod_A"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pallet Town", "Super Rod Pokemon - 2", "Poliwag", rom_addresses["Wild_Super_Rod_A"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 22", "Super Rod Pokemon - 1", "Goldeen", rom_addresses["Wild_Super_Rod_B"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 22", "Super Rod Pokemon - 2", "Poliwag", rom_addresses["Wild_Super_Rod_B"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 24", "Super Rod Pokemon - 1", "Psyduck", rom_addresses["Wild_Super_Rod_C"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 24", "Super Rod Pokemon - 2", "Goldeen", rom_addresses["Wild_Super_Rod_C"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 24", "Super Rod Pokemon - 3", "Krabby", rom_addresses["Wild_Super_Rod_C"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 6", "Super Rod Pokemon - 1", "Krabby", rom_addresses["Wild_Super_Rod_D"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 6", "Super Rod Pokemon - 2", "Shellder", rom_addresses["Wild_Super_Rod_D"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 10 North", "Super Rod Pokemon - 1", "Poliwhirl", rom_addresses["Wild_Super_Rod_E"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 10 North", "Super Rod Pokemon - 2", "Slowpoke", rom_addresses["Wild_Super_Rod_E"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Safari Zone Center", "Super Rod Pokemon - 1", "Dratini", rom_addresses["Wild_Super_Rod_F"] + 1,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone Center", "Super Rod Pokemon - 2", "Krabby", rom_addresses["Wild_Super_Rod_F"] + 3,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone Center", "Super Rod Pokemon - 3", "Psyduck", rom_addresses["Wild_Super_Rod_F"] + 5,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone Center", "Super Rod Pokemon - 4", "Slowpoke", rom_addresses["Wild_Super_Rod_F"] + 7,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 12 North", "Super Rod Pokemon - 1", "Tentacool", rom_addresses["Wild_Super_Rod_G"] + 1,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 12 North", "Super Rod Pokemon - 2", "Krabby", rom_addresses["Wild_Super_Rod_G"] + 3,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 12 North", "Super Rod Pokemon - 3", "Goldeen", rom_addresses["Wild_Super_Rod_G"] + 5,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 12 North", "Super Rod Pokemon - 4", "Magikarp", rom_addresses["Wild_Super_Rod_G"] + 7,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 19", "Super Rod Pokemon - 1", "Staryu", rom_addresses["Wild_Super_Rod_H"] + 1,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 19", "Super Rod Pokemon - 2", "Horsea", rom_addresses["Wild_Super_Rod_H"] + 3,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 19", "Super Rod Pokemon - 3", "Shellder", rom_addresses["Wild_Super_Rod_H"] + 5,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 19", "Super Rod Pokemon - 4", "Goldeen", rom_addresses["Wild_Super_Rod_H"] + 7,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 23 South", "Super Rod Pokemon - 1", "Slowbro", rom_addresses["Wild_Super_Rod_I"] + 1,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 23 South", "Super Rod Pokemon - 2", "Seaking", rom_addresses["Wild_Super_Rod_I"] + 3,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 23 South", "Super Rod Pokemon - 3", "Kingler", rom_addresses["Wild_Super_Rod_I"] + 5,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 23 South", "Super Rod Pokemon - 4", "Seadra", rom_addresses["Wild_Super_Rod_I"] + 7,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Fuchsia City", "Super Rod Pokemon - 1", "Seaking", rom_addresses["Wild_Super_Rod_J"] + 1,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Fuchsia City", "Super Rod Pokemon - 2", "Krabby", rom_addresses["Wild_Super_Rod_J"] + 3,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Fuchsia City", "Super Rod Pokemon - 3", "Goldeen", rom_addresses["Wild_Super_Rod_J"] + 5,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Fuchsia City", "Super Rod Pokemon - 4", "Magikarp", rom_addresses["Wild_Super_Rod_J"] + 7,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 1", "Wild Pokemon - 1", "Pidgey", rom_addresses["Wild_Route1"] + 1, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 1", "Wild Pokemon - 2", "Rattata", rom_addresses["Wild_Route1"] + 3, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 1", "Wild Pokemon - 3", "Rattata", rom_addresses["Wild_Route1"] + 5, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 1", "Wild Pokemon - 4", "Rattata", rom_addresses["Wild_Route1"] + 7, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 1", "Wild Pokemon - 5", "Pidgey", rom_addresses["Wild_Route1"] + 9, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 1", "Wild Pokemon - 6", "Pidgey", rom_addresses["Wild_Route1"] + 11, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 1", "Wild Pokemon - 7", "Pidgey", rom_addresses["Wild_Route1"] + 13, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 1", "Wild Pokemon - 8", "Rattata", rom_addresses["Wild_Route1"] + 15, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 1", "Wild Pokemon - 9", "Pidgey", rom_addresses["Wild_Route1"] + 17, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 1", "Wild Pokemon - 10", "Pidgey", rom_addresses["Wild_Route1"] + 19, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 2", "Wild Pokemon - 1", "Rattata", rom_addresses["Wild_Route2"] + 1, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 2", "Wild Pokemon - 2", "Pidgey", rom_addresses["Wild_Route2"] + 3, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 2", "Wild Pokemon - 3", "Pidgey", rom_addresses["Wild_Route2"] + 5, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 2", "Wild Pokemon - 4", "Rattata", rom_addresses["Wild_Route2"] + 7, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 2", "Wild Pokemon - 5", "Pidgey", rom_addresses["Wild_Route2"] + 9, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 2", "Wild Pokemon - 6", ["Weedle", "Caterpie"], rom_addresses["Wild_Route2"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 2", "Wild Pokemon - 7", "Rattata", rom_addresses["Wild_Route2"] + 13, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 2", "Wild Pokemon - 8", "Rattata", rom_addresses["Wild_Route2"] + 15, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 2", "Wild Pokemon - 9", ["Weedle", "Caterpie"], rom_addresses["Wild_Route2"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 2", "Wild Pokemon - 10", ["Weedle", "Caterpie"], rom_addresses["Wild_Route2"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 22", "Wild Pokemon - 1", "Rattata", rom_addresses["Wild_Route22"] + 1, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 22", "Wild Pokemon - 2", ["Nidoran M", "Nidoran F"], rom_addresses["Wild_Route22"] + 3,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 22", "Wild Pokemon - 3", "Rattata", rom_addresses["Wild_Route22"] + 5, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 22", "Wild Pokemon - 4", ["Nidoran M", "Nidoran F"], rom_addresses["Wild_Route22"] + 7,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 22", "Wild Pokemon - 5", "Rattata", rom_addresses["Wild_Route22"] + 9, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 22", "Wild Pokemon - 6", ["Nidoran M", "Nidoran F"], rom_addresses["Wild_Route22"] + 11,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 22", "Wild Pokemon - 7", "Spearow", rom_addresses["Wild_Route22"] + 13, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 22", "Wild Pokemon - 8", "Spearow", rom_addresses["Wild_Route22"] + 15, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 22", "Wild Pokemon - 9", ["Nidoran F", "Nidoran M"], rom_addresses["Wild_Route22"] + 17,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 22", "Wild Pokemon - 10", ["Nidoran F", "Nidoran M"], rom_addresses["Wild_Route22"] + 19,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Viridian Forest", "Wild Pokemon - 1", ["Weedle", "Caterpie"],
+ rom_addresses["Wild_ViridianForest"] + 1, None, event=True, type="Wild Encounter"),
+ LocationData("Viridian Forest", "Wild Pokemon - 2", ["Kakuna", "Metapod"],
+ rom_addresses["Wild_ViridianForest"] + 3, None, event=True, type="Wild Encounter"),
+ LocationData("Viridian Forest", "Wild Pokemon - 3", ["Weedle", "Caterpie"],
+ rom_addresses["Wild_ViridianForest"] + 5, None, event=True, type="Wild Encounter"),
+ LocationData("Viridian Forest", "Wild Pokemon - 4", ["Weedle", "Caterpie"],
+ rom_addresses["Wild_ViridianForest"] + 7, None, event=True, type="Wild Encounter"),
+ LocationData("Viridian Forest", "Wild Pokemon - 5", ["Kakuna", "Metapod"],
+ rom_addresses["Wild_ViridianForest"] + 9, None, event=True, type="Wild Encounter"),
+ LocationData("Viridian Forest", "Wild Pokemon - 6", ["Kakuna", "Metapod"],
+ rom_addresses["Wild_ViridianForest"] + 11, None, event=True, type="Wild Encounter"),
+ LocationData("Viridian Forest", "Wild Pokemon - 7", ["Metapod", "Kakuna"],
+ rom_addresses["Wild_ViridianForest"] + 13, None, event=True, type="Wild Encounter"),
+ LocationData("Viridian Forest", "Wild Pokemon - 8", ["Caterpie", "Weedle"],
+ rom_addresses["Wild_ViridianForest"] + 15,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Viridian Forest", "Wild Pokemon - 9", "Pikachu", rom_addresses["Wild_ViridianForest"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Viridian Forest", "Wild Pokemon - 10", "Pikachu", rom_addresses["Wild_ViridianForest"] + 19,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 3", "Wild Pokemon - 1", "Pidgey", rom_addresses["Wild_Route3"] + 1, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 3", "Wild Pokemon - 2", "Spearow", rom_addresses["Wild_Route3"] + 3, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 3", "Wild Pokemon - 3", "Pidgey", rom_addresses["Wild_Route3"] + 5, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 3", "Wild Pokemon - 4", "Spearow", rom_addresses["Wild_Route3"] + 7, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 3", "Wild Pokemon - 5", "Spearow", rom_addresses["Wild_Route3"] + 9, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 3", "Wild Pokemon - 6", "Pidgey", rom_addresses["Wild_Route3"] + 11, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 3", "Wild Pokemon - 7", "Spearow", rom_addresses["Wild_Route3"] + 13, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 3", "Wild Pokemon - 8", "Jigglypuff", rom_addresses["Wild_Route3"] + 15, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 3", "Wild Pokemon - 9", "Jigglypuff", rom_addresses["Wild_Route3"] + 17, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 3", "Wild Pokemon - 10", "Jigglypuff", rom_addresses["Wild_Route3"] + 19, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon 1F", "Wild Pokemon - 1", "Zubat", rom_addresses["Wild_MtMoon1F"] + 1, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon 1F", "Wild Pokemon - 2", "Zubat", rom_addresses["Wild_MtMoon1F"] + 3, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon 1F", "Wild Pokemon - 3", "Zubat", rom_addresses["Wild_MtMoon1F"] + 5, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon 1F", "Wild Pokemon - 4", "Geodude", rom_addresses["Wild_MtMoon1F"] + 7, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon 1F", "Wild Pokemon - 5", "Zubat", rom_addresses["Wild_MtMoon1F"] + 9, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon 1F", "Wild Pokemon - 6", "Zubat", rom_addresses["Wild_MtMoon1F"] + 11, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon 1F", "Wild Pokemon - 7", "Geodude", rom_addresses["Wild_MtMoon1F"] + 13, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon 1F", "Wild Pokemon - 8", "Paras", rom_addresses["Wild_MtMoon1F"] + 15, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon 1F", "Wild Pokemon - 9", "Zubat", rom_addresses["Wild_MtMoon1F"] + 17, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon 1F", "Wild Pokemon - 10", "Clefairy", rom_addresses["Wild_MtMoon1F"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Mt Moon B1F", "Wild Pokemon - 1", "Zubat", rom_addresses["Wild_MtMoonB1F"] + 1, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon B1F", "Wild Pokemon - 2", "Zubat", rom_addresses["Wild_MtMoonB1F"] + 3, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon B1F", "Wild Pokemon - 3", "Geodude", rom_addresses["Wild_MtMoonB1F"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Mt Moon B1F", "Wild Pokemon - 4", "Geodude", rom_addresses["Wild_MtMoonB1F"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Mt Moon B1F", "Wild Pokemon - 5", "Zubat", rom_addresses["Wild_MtMoonB1F"] + 9, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon B1F", "Wild Pokemon - 6", "Paras", rom_addresses["Wild_MtMoonB1F"] + 11, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon B1F", "Wild Pokemon - 7", "Zubat", rom_addresses["Wild_MtMoonB1F"] + 13, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon B1F", "Wild Pokemon - 8", "Zubat", rom_addresses["Wild_MtMoonB1F"] + 15, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon B1F", "Wild Pokemon - 9", "Clefairy", rom_addresses["Wild_MtMoonB1F"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Mt Moon B1F", "Wild Pokemon - 10", "Geodude", rom_addresses["Wild_MtMoonB1F"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Mt Moon B2F", "Wild Pokemon - 1", "Zubat", rom_addresses["Wild_MtMoonB2F"] + 1, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon B2F", "Wild Pokemon - 2", "Geodude", rom_addresses["Wild_MtMoonB2F"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Mt Moon B2F", "Wild Pokemon - 3", "Zubat", rom_addresses["Wild_MtMoonB2F"] + 5, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon B2F", "Wild Pokemon - 4", "Geodude", rom_addresses["Wild_MtMoonB2F"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Mt Moon B2F", "Wild Pokemon - 5", "Zubat", rom_addresses["Wild_MtMoonB2F"] + 9, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon B2F", "Wild Pokemon - 6", "Paras", rom_addresses["Wild_MtMoonB2F"] + 11, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon B2F", "Wild Pokemon - 7", "Paras", rom_addresses["Wild_MtMoonB2F"] + 13, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon B2F", "Wild Pokemon - 8", "Clefairy", rom_addresses["Wild_MtMoonB2F"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Mt Moon B2F", "Wild Pokemon - 9", "Zubat", rom_addresses["Wild_MtMoonB2F"] + 17, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Mt Moon B2F", "Wild Pokemon - 10", "Clefairy", rom_addresses["Wild_MtMoonB2F"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 4", "Wild Pokemon - 1", "Rattata", rom_addresses["Wild_Route4"] + 1, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 4", "Wild Pokemon - 2", "Spearow", rom_addresses["Wild_Route4"] + 3, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 4", "Wild Pokemon - 3", "Rattata", rom_addresses["Wild_Route4"] + 5, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 4", "Wild Pokemon - 4", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 4", "Wild Pokemon - 5", "Spearow", rom_addresses["Wild_Route4"] + 9, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 4", "Wild Pokemon - 6", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 4", "Wild Pokemon - 7", "Rattata", rom_addresses["Wild_Route4"] + 13, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 4", "Wild Pokemon - 8", "Spearow", rom_addresses["Wild_Route4"] + 15, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 4", "Wild Pokemon - 9", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 4", "Wild Pokemon - 10", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route4"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 24", "Wild Pokemon - 1", ["Weedle", "Caterpie"], rom_addresses["Wild_Route24"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 24", "Wild Pokemon - 2", ["Kakuna", "Metapod"], rom_addresses["Wild_Route24"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 24", "Wild Pokemon - 3", "Pidgey", rom_addresses["Wild_Route24"] + 5, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 24", "Wild Pokemon - 4", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route24"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 24", "Wild Pokemon - 5", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route24"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 24", "Wild Pokemon - 6", "Abra", rom_addresses["Wild_Route24"] + 11, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 24", "Wild Pokemon - 7", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route24"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 24", "Wild Pokemon - 8", "Pidgey", rom_addresses["Wild_Route24"] + 15, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 24", "Wild Pokemon - 9", "Abra", rom_addresses["Wild_Route24"] + 17, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 24", "Wild Pokemon - 10", "Abra", rom_addresses["Wild_Route24"] + 19, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 25", "Wild Pokemon - 1", ["Weedle", "Caterpie"], rom_addresses["Wild_Route25"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 25", "Wild Pokemon - 2", ["Kakuna", "Metapod"], rom_addresses["Wild_Route25"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 25", "Wild Pokemon - 3", "Pidgey", rom_addresses["Wild_Route25"] + 5, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 25", "Wild Pokemon - 4", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route25"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 25", "Wild Pokemon - 5", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route25"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 25", "Wild Pokemon - 6", "Abra", rom_addresses["Wild_Route25"] + 11, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 25", "Wild Pokemon - 7", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route25"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 25", "Wild Pokemon - 8", "Abra", rom_addresses["Wild_Route25"] + 15, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 25", "Wild Pokemon - 9", ["Metapod", "Kakuna"], rom_addresses["Wild_Route25"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 25", "Wild Pokemon - 10", ["Caterpie", "Weedle"], rom_addresses["Wild_Route25"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 9", "Wild Pokemon - 1", "Rattata", rom_addresses["Wild_Route9"] + 1, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 9", "Wild Pokemon - 2", "Spearow", rom_addresses["Wild_Route9"] + 3, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 9", "Wild Pokemon - 3", "Rattata", rom_addresses["Wild_Route9"] + 5, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 9", "Wild Pokemon - 4", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route9"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 9", "Wild Pokemon - 5", "Spearow", rom_addresses["Wild_Route9"] + 9, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 9", "Wild Pokemon - 6", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route9"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 9", "Wild Pokemon - 7", "Rattata", rom_addresses["Wild_Route9"] + 13, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 9", "Wild Pokemon - 8", "Spearow", rom_addresses["Wild_Route9"] + 15, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 9", "Wild Pokemon - 9", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route9"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 9", "Wild Pokemon - 10", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route9"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 5", "Wild Pokemon - 1", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route5"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 5", "Wild Pokemon - 2", "Pidgey", rom_addresses["Wild_Route5"] + 3, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 5", "Wild Pokemon - 3", "Pidgey", rom_addresses["Wild_Route5"] + 5, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 5", "Wild Pokemon - 4", ["Mankey", "Meowth"], rom_addresses["Wild_Route5"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 5", "Wild Pokemon - 5", ["Mankey", "Meowth"], rom_addresses["Wild_Route5"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 5", "Wild Pokemon - 6", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route5"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 5", "Wild Pokemon - 7", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route5"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 5", "Wild Pokemon - 8", "Pidgey", rom_addresses["Wild_Route5"] + 15, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 5", "Wild Pokemon - 9", ["Mankey", "Meowth"], rom_addresses["Wild_Route5"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 5", "Wild Pokemon - 10", ["Mankey", "Meowth"], rom_addresses["Wild_Route5"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 6", "Wild Pokemon - 1", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route6"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 6", "Wild Pokemon - 2", "Pidgey", rom_addresses["Wild_Route6"] + 3, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 6", "Wild Pokemon - 3", "Pidgey", rom_addresses["Wild_Route6"] + 5, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 6", "Wild Pokemon - 4", ["Mankey", "Meowth"], rom_addresses["Wild_Route6"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 6", "Wild Pokemon - 5", ["Mankey", "Meowth"], rom_addresses["Wild_Route6"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 6", "Wild Pokemon - 6", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route6"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 6", "Wild Pokemon - 7", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route6"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 6", "Wild Pokemon - 8", "Pidgey", rom_addresses["Wild_Route6"] + 15, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 6", "Wild Pokemon - 9", ["Mankey", "Meowth"], rom_addresses["Wild_Route6"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 6", "Wild Pokemon - 10", ["Mankey", "Meowth"], rom_addresses["Wild_Route6"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 11", "Wild Pokemon - 1", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route11"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 11", "Wild Pokemon - 2", "Spearow", rom_addresses["Wild_Route11"] + 3, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 11", "Wild Pokemon - 3", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route11"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 11", "Wild Pokemon - 4", "Drowzee", rom_addresses["Wild_Route11"] + 7, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 11", "Wild Pokemon - 5", "Spearow", rom_addresses["Wild_Route11"] + 9, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 11", "Wild Pokemon - 6", "Drowzee", rom_addresses["Wild_Route11"] + 11, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 11", "Wild Pokemon - 7", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route11"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 11", "Wild Pokemon - 8", "Spearow", rom_addresses["Wild_Route11"] + 15, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 11", "Wild Pokemon - 9", "Drowzee", rom_addresses["Wild_Route11"] + 17, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 11", "Wild Pokemon - 10", "Drowzee", rom_addresses["Wild_Route11"] + 19, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Rock Tunnel 1F", "Wild Pokemon - 1", "Zubat", rom_addresses["Wild_RockTunnel1F"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Rock Tunnel 1F", "Wild Pokemon - 2", "Zubat", rom_addresses["Wild_RockTunnel1F"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Rock Tunnel 1F", "Wild Pokemon - 3", "Geodude", rom_addresses["Wild_RockTunnel1F"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Rock Tunnel 1F", "Wild Pokemon - 4", "Machop", rom_addresses["Wild_RockTunnel1F"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Rock Tunnel 1F", "Wild Pokemon - 5", "Geodude", rom_addresses["Wild_RockTunnel1F"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Rock Tunnel 1F", "Wild Pokemon - 6", "Zubat", rom_addresses["Wild_RockTunnel1F"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Rock Tunnel 1F", "Wild Pokemon - 7", "Zubat", rom_addresses["Wild_RockTunnel1F"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Rock Tunnel 1F", "Wild Pokemon - 8", "Machop", rom_addresses["Wild_RockTunnel1F"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Rock Tunnel 1F", "Wild Pokemon - 9", "Onix", rom_addresses["Wild_RockTunnel1F"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Rock Tunnel 1F", "Wild Pokemon - 10", "Onix", rom_addresses["Wild_RockTunnel1F"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Rock Tunnel B1F", "Wild Pokemon - 1", "Zubat", rom_addresses["Wild_RockTunnelB1F"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Rock Tunnel B1F", "Wild Pokemon - 2", "Zubat", rom_addresses["Wild_RockTunnelB1F"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Rock Tunnel B1F", "Wild Pokemon - 3", "Geodude", rom_addresses["Wild_RockTunnelB1F"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Rock Tunnel B1F", "Wild Pokemon - 4", "Machop", rom_addresses["Wild_RockTunnelB1F"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Rock Tunnel B1F", "Wild Pokemon - 5", "Geodude", rom_addresses["Wild_RockTunnelB1F"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Rock Tunnel B1F", "Wild Pokemon - 6", "Zubat", rom_addresses["Wild_RockTunnelB1F"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Rock Tunnel B1F", "Wild Pokemon - 7", "Machop", rom_addresses["Wild_RockTunnelB1F"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Rock Tunnel B1F", "Wild Pokemon - 8", "Onix", rom_addresses["Wild_RockTunnelB1F"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Rock Tunnel B1F", "Wild Pokemon - 9", "Onix", rom_addresses["Wild_RockTunnelB1F"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Rock Tunnel B1F", "Wild Pokemon - 10", "Geodude", rom_addresses["Wild_RockTunnelB1F"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 10 North", "Wild Pokemon - 1", "Voltorb", rom_addresses["Wild_Route10"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 10 North", "Wild Pokemon - 2", "Spearow", rom_addresses["Wild_Route10"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 10 North", "Wild Pokemon - 3", "Voltorb", rom_addresses["Wild_Route10"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 10 North", "Wild Pokemon - 4", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route10"] + 7,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 10 North", "Wild Pokemon - 5", "Spearow", rom_addresses["Wild_Route10"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 10 North", "Wild Pokemon - 6", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route10"] + 11,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 10 North", "Wild Pokemon - 7", "Voltorb", rom_addresses["Wild_Route10"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 10 North", "Wild Pokemon - 8", "Spearow", rom_addresses["Wild_Route10"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 10 North", "Wild Pokemon - 9", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route10"] + 17,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 10 North", "Wild Pokemon - 10", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route10"] + 19,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 12 Grass", "Wild Pokemon - 1", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route12"] + 1,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 12 Grass", "Wild Pokemon - 2", "Pidgey", rom_addresses["Wild_Route12"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 12 Grass", "Wild Pokemon - 3", "Pidgey", rom_addresses["Wild_Route12"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 12 Grass", "Wild Pokemon - 4", "Venonat", rom_addresses["Wild_Route12"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 12 Grass", "Wild Pokemon - 5", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route12"] + 9,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 12 Grass", "Wild Pokemon - 6", "Venonat", rom_addresses["Wild_Route12"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 12 Grass", "Wild Pokemon - 7", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route12"] + 13,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 12 Grass", "Wild Pokemon - 8", "Pidgey", rom_addresses["Wild_Route12"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 12 Grass", "Wild Pokemon - 9", ["Gloom", "Weepinbell"], rom_addresses["Wild_Route12"] + 17,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 12 Grass", "Wild Pokemon - 10", ["Gloom", "Weepinbell"], rom_addresses["Wild_Route12"] + 19,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 8 Grass", "Wild Pokemon - 1", "Pidgey", rom_addresses["Wild_Route8"] + 1, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 8 Grass", "Wild Pokemon - 2", ["Mankey", "Meowth"], rom_addresses["Wild_Route8"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 8 Grass", "Wild Pokemon - 3", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route8"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 8 Grass", "Wild Pokemon - 4", ["Growlithe", "Vulpix"], rom_addresses["Wild_Route8"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 8 Grass", "Wild Pokemon - 5", "Pidgey", rom_addresses["Wild_Route8"] + 9, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 8 Grass", "Wild Pokemon - 6", ["Mankey", "Meowth"], rom_addresses["Wild_Route8"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 8 Grass", "Wild Pokemon - 7", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route8"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 8 Grass", "Wild Pokemon - 8", ["Growlithe", "Vulpix"], rom_addresses["Wild_Route8"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 8 Grass", "Wild Pokemon - 9", ["Growlithe", "Vulpix"], rom_addresses["Wild_Route8"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 8 Grass", "Wild Pokemon - 10", ["Growlithe", "Vulpix"], rom_addresses["Wild_Route8"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 7", "Wild Pokemon - 1", "Pidgey", rom_addresses["Wild_Route7"] + 1, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 7", "Wild Pokemon - 2", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route7"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 7", "Wild Pokemon - 3", ["Mankey", "Meowth"], rom_addresses["Wild_Route7"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 7", "Wild Pokemon - 4", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route7"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 7", "Wild Pokemon - 5", "Pidgey", rom_addresses["Wild_Route7"] + 9, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 7", "Wild Pokemon - 6", ["Mankey", "Meowth"], rom_addresses["Wild_Route7"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 7", "Wild Pokemon - 7", ["Growlithe", "Vulpix"], rom_addresses["Wild_Route7"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 7", "Wild Pokemon - 8", ["Growlithe", "Vulpix"], rom_addresses["Wild_Route7"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 7", "Wild Pokemon - 9", ["Mankey", "Meowth"], rom_addresses["Wild_Route7"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 7", "Wild Pokemon - 10", ["Mankey", "Meowth"], rom_addresses["Wild_Route7"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 3F", "Wild Pokemon - 1", "Gastly", rom_addresses["Wild_PokemonTower3F"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 3F", "Wild Pokemon - 2", "Gastly", rom_addresses["Wild_PokemonTower3F"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 3F", "Wild Pokemon - 3", "Gastly", rom_addresses["Wild_PokemonTower3F"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 3F", "Wild Pokemon - 4", "Gastly", rom_addresses["Wild_PokemonTower3F"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 3F", "Wild Pokemon - 5", "Gastly", rom_addresses["Wild_PokemonTower3F"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 3F", "Wild Pokemon - 6", "Gastly", rom_addresses["Wild_PokemonTower3F"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 3F", "Wild Pokemon - 7", "Gastly", rom_addresses["Wild_PokemonTower3F"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 3F", "Wild Pokemon - 8", "Cubone", rom_addresses["Wild_PokemonTower3F"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 3F", "Wild Pokemon - 9", "Cubone", rom_addresses["Wild_PokemonTower3F"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 3F", "Wild Pokemon - 10", "Haunter", rom_addresses["Wild_PokemonTower3F"] + 19,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 4F", "Wild Pokemon - 1", "Gastly", rom_addresses["Wild_PokemonTower4F"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 4F", "Wild Pokemon - 2", "Gastly", rom_addresses["Wild_PokemonTower4F"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 4F", "Wild Pokemon - 3", "Gastly", rom_addresses["Wild_PokemonTower4F"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 4F", "Wild Pokemon - 4", "Gastly", rom_addresses["Wild_PokemonTower4F"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 4F", "Wild Pokemon - 5", "Gastly", rom_addresses["Wild_PokemonTower4F"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 4F", "Wild Pokemon - 6", "Gastly", rom_addresses["Wild_PokemonTower4F"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 4F", "Wild Pokemon - 7", "Haunter", rom_addresses["Wild_PokemonTower4F"] + 13,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 4F", "Wild Pokemon - 8", "Cubone", rom_addresses["Wild_PokemonTower4F"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 4F", "Wild Pokemon - 9", "Cubone", rom_addresses["Wild_PokemonTower4F"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 4F", "Wild Pokemon - 10", "Gastly", rom_addresses["Wild_PokemonTower4F"] + 19,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 5F", "Wild Pokemon - 1", "Gastly", rom_addresses["Wild_PokemonTower5F"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 5F", "Wild Pokemon - 2", "Gastly", rom_addresses["Wild_PokemonTower5F"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 5F", "Wild Pokemon - 3", "Gastly", rom_addresses["Wild_PokemonTower5F"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 5F", "Wild Pokemon - 4", "Gastly", rom_addresses["Wild_PokemonTower5F"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 5F", "Wild Pokemon - 5", "Gastly", rom_addresses["Wild_PokemonTower5F"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 5F", "Wild Pokemon - 6", "Gastly", rom_addresses["Wild_PokemonTower5F"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 5F", "Wild Pokemon - 7", "Haunter", rom_addresses["Wild_PokemonTower5F"] + 13,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 5F", "Wild Pokemon - 8", "Cubone", rom_addresses["Wild_PokemonTower5F"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 5F", "Wild Pokemon - 9", "Cubone", rom_addresses["Wild_PokemonTower5F"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 5F", "Wild Pokemon - 10", "Gastly", rom_addresses["Wild_PokemonTower5F"] + 19,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 6F", "Wild Pokemon - 1", "Gastly", rom_addresses["Wild_PokemonTower6F"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 6F", "Wild Pokemon - 2", "Gastly", rom_addresses["Wild_PokemonTower6F"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 6F", "Wild Pokemon - 3", "Gastly", rom_addresses["Wild_PokemonTower6F"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 6F", "Wild Pokemon - 4", "Gastly", rom_addresses["Wild_PokemonTower6F"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 6F", "Wild Pokemon - 5", "Gastly", rom_addresses["Wild_PokemonTower6F"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 6F", "Wild Pokemon - 6", "Gastly", rom_addresses["Wild_PokemonTower6F"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 6F", "Wild Pokemon - 7", "Haunter", rom_addresses["Wild_PokemonTower6F"] + 13,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 6F", "Wild Pokemon - 8", "Cubone", rom_addresses["Wild_PokemonTower6F"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 6F", "Wild Pokemon - 9", "Cubone", rom_addresses["Wild_PokemonTower6F"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 6F", "Wild Pokemon - 10", "Haunter", rom_addresses["Wild_PokemonTower6F"] + 19,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 7F", "Wild Pokemon - 1", "Gastly", rom_addresses["Wild_PokemonTower7F"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 7F", "Wild Pokemon - 2", "Gastly", rom_addresses["Wild_PokemonTower7F"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 7F", "Wild Pokemon - 3", "Gastly", rom_addresses["Wild_PokemonTower7F"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 7F", "Wild Pokemon - 4", "Gastly", rom_addresses["Wild_PokemonTower7F"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 7F", "Wild Pokemon - 5", "Gastly", rom_addresses["Wild_PokemonTower7F"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 7F", "Wild Pokemon - 6", "Haunter", rom_addresses["Wild_PokemonTower7F"] + 11,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 7F", "Wild Pokemon - 7", "Cubone", rom_addresses["Wild_PokemonTower7F"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 7F", "Wild Pokemon - 8", "Cubone", rom_addresses["Wild_PokemonTower7F"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 7F", "Wild Pokemon - 9", "Haunter", rom_addresses["Wild_PokemonTower7F"] + 17,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Tower 7F", "Wild Pokemon - 10", "Haunter", rom_addresses["Wild_PokemonTower7F"] + 19,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 13", "Wild Pokemon - 1", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route13"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 13", "Wild Pokemon - 2", "Pidgey", rom_addresses["Wild_Route13"] + 3, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 13", "Wild Pokemon - 3", "Pidgey", rom_addresses["Wild_Route13"] + 5, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 13", "Wild Pokemon - 4", "Venonat", rom_addresses["Wild_Route13"] + 7, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 13", "Wild Pokemon - 5", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route13"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 13", "Wild Pokemon - 6", "Venonat", rom_addresses["Wild_Route13"] + 11, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 13", "Wild Pokemon - 7", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route13"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 13", "Wild Pokemon - 8", "Ditto", rom_addresses["Wild_Route13"] + 15, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 13", "Wild Pokemon - 9", ["Gloom", "Weepinbell"], rom_addresses["Wild_Route13"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 13", "Wild Pokemon - 10", ["Gloom", "Weepinbell"], rom_addresses["Wild_Route13"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 14", "Wild Pokemon - 1", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route14"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 14", "Wild Pokemon - 2", "Pidgey", rom_addresses["Wild_Route14"] + 3, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 14", "Wild Pokemon - 3", "Ditto", rom_addresses["Wild_Route14"] + 5, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 14", "Wild Pokemon - 4", "Venonat", rom_addresses["Wild_Route14"] + 7, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 14", "Wild Pokemon - 5", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route14"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 14", "Wild Pokemon - 6", "Venonat", rom_addresses["Wild_Route14"] + 11, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 14", "Wild Pokemon - 7", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route14"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 14", "Wild Pokemon - 8", ["Gloom", "Weepinbell"], rom_addresses["Wild_Route14"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 14", "Wild Pokemon - 9", "Pidgeotto", rom_addresses["Wild_Route14"] + 17, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 14", "Wild Pokemon - 10", "Pidgeotto", rom_addresses["Wild_Route14"] + 19, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 15", "Wild Pokemon - 1", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route15"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 15", "Wild Pokemon - 2", "Ditto", rom_addresses["Wild_Route15"] + 3, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 15", "Wild Pokemon - 3", "Pidgey", rom_addresses["Wild_Route15"] + 5, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 15", "Wild Pokemon - 4", "Venonat", rom_addresses["Wild_Route15"] + 7, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 15", "Wild Pokemon - 5", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route15"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 15", "Wild Pokemon - 6", "Venonat", rom_addresses["Wild_Route15"] + 11, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 15", "Wild Pokemon - 7", ["Oddish", "Bellsprout"], rom_addresses["Wild_Route15"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 15", "Wild Pokemon - 8", ["Gloom", "Weepinbell"], rom_addresses["Wild_Route15"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 15", "Wild Pokemon - 9", "Pidgeotto", rom_addresses["Wild_Route15"] + 17, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 15", "Wild Pokemon - 10", "Pidgeotto", rom_addresses["Wild_Route15"] + 19, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 16 North", "Wild Pokemon - 1", "Spearow", rom_addresses["Wild_Route16"] + 1, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 16 North", "Wild Pokemon - 2", "Spearow", rom_addresses["Wild_Route16"] + 3, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 16 North", "Wild Pokemon - 3", "Rattata", rom_addresses["Wild_Route16"] + 5, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 16 North", "Wild Pokemon - 4", "Doduo", rom_addresses["Wild_Route16"] + 7, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 16 North", "Wild Pokemon - 5", "Rattata", rom_addresses["Wild_Route16"] + 9, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 16 North", "Wild Pokemon - 6", "Doduo", rom_addresses["Wild_Route16"] + 11, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 16 North", "Wild Pokemon - 7", "Doduo", rom_addresses["Wild_Route16"] + 13, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 16 North", "Wild Pokemon - 8", "Rattata", rom_addresses["Wild_Route16"] + 15, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 16 North", "Wild Pokemon - 9", "Raticate", rom_addresses["Wild_Route16"] + 17, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 16 North", "Wild Pokemon - 10", "Raticate", rom_addresses["Wild_Route16"] + 19, None,
+ event=True,
+ type="Wild Encounter"),
+ LocationData("Route 17", "Wild Pokemon - 1", "Spearow", rom_addresses["Wild_Route17"] + 1, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 17", "Wild Pokemon - 2", "Spearow", rom_addresses["Wild_Route17"] + 3, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 17", "Wild Pokemon - 3", "Raticate", rom_addresses["Wild_Route17"] + 5, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 17", "Wild Pokemon - 4", "Doduo", rom_addresses["Wild_Route17"] + 7, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 17", "Wild Pokemon - 5", "Raticate", rom_addresses["Wild_Route17"] + 9, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 17", "Wild Pokemon - 6", "Doduo", rom_addresses["Wild_Route17"] + 11, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 17", "Wild Pokemon - 7", "Doduo", rom_addresses["Wild_Route17"] + 13, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 17", "Wild Pokemon - 8", "Raticate", rom_addresses["Wild_Route17"] + 15, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 17", "Wild Pokemon - 9", "Fearow", rom_addresses["Wild_Route17"] + 17, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 17", "Wild Pokemon - 10", "Fearow", rom_addresses["Wild_Route17"] + 19, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 18", "Wild Pokemon - 1", "Spearow", rom_addresses["Wild_Route18"] + 1, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 18", "Wild Pokemon - 2", "Spearow", rom_addresses["Wild_Route18"] + 3, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 18", "Wild Pokemon - 3", "Raticate", rom_addresses["Wild_Route18"] + 5, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 18", "Wild Pokemon - 4", "Doduo", rom_addresses["Wild_Route18"] + 7, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 18", "Wild Pokemon - 5", "Fearow", rom_addresses["Wild_Route18"] + 9, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 18", "Wild Pokemon - 6", "Doduo", rom_addresses["Wild_Route18"] + 11, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 18", "Wild Pokemon - 7", "Doduo", rom_addresses["Wild_Route18"] + 13, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 18", "Wild Pokemon - 8", "Raticate", rom_addresses["Wild_Route18"] + 15, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 18", "Wild Pokemon - 9", "Fearow", rom_addresses["Wild_Route18"] + 17, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 18", "Wild Pokemon - 10", "Fearow", rom_addresses["Wild_Route18"] + 19, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Safari Zone Center", "Wild Pokemon - 1", ["Nidoran M", "Nidoran F"],
+ rom_addresses["Wild_SafariZoneCenter"] + 1, None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone Center", "Wild Pokemon - 2", "Rhyhorn", rom_addresses["Wild_SafariZoneCenter"] + 3,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone Center", "Wild Pokemon - 3", "Venonat", rom_addresses["Wild_SafariZoneCenter"] + 5,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone Center", "Wild Pokemon - 4", "Exeggcute", rom_addresses["Wild_SafariZoneCenter"] + 7,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone Center", "Wild Pokemon - 5", ["Nidorino", "Nidorina"],
+ rom_addresses["Wild_SafariZoneCenter"] + 9, None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone Center", "Wild Pokemon - 6", "Exeggcute", rom_addresses["Wild_SafariZoneCenter"] + 11,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone Center", "Wild Pokemon - 7", ["Nidorina", "Nidorino"],
+ rom_addresses["Wild_SafariZoneCenter"] + 13, None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone Center", "Wild Pokemon - 8", "Parasect", rom_addresses["Wild_SafariZoneCenter"] + 15,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone Center", "Wild Pokemon - 9", ["Scyther", "Pinsir"],
+ rom_addresses["Wild_SafariZoneCenter"] + 17, None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone Center", "Wild Pokemon - 10", "Chansey", rom_addresses["Wild_SafariZoneCenter"] + 19,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone East", "Wild Pokemon - 1", ["Nidoran M", "Nidoran F"],
+ rom_addresses["Wild_SafariZoneEast"] + 1, None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone East", "Wild Pokemon - 2", "Doduo", rom_addresses["Wild_SafariZoneEast"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Safari Zone East", "Wild Pokemon - 3", "Paras", rom_addresses["Wild_SafariZoneEast"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Safari Zone East", "Wild Pokemon - 4", "Exeggcute", rom_addresses["Wild_SafariZoneEast"] + 7,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone East", "Wild Pokemon - 5", ["Nidorino", "Nidorina"],
+ rom_addresses["Wild_SafariZoneEast"] + 9, None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone East", "Wild Pokemon - 6", "Exeggcute", rom_addresses["Wild_SafariZoneEast"] + 11,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone East", "Wild Pokemon - 7", ["Nidoran F", "Nidoran M"],
+ rom_addresses["Wild_SafariZoneEast"] + 13, None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone East", "Wild Pokemon - 8", "Parasect", rom_addresses["Wild_SafariZoneEast"] + 15,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone East", "Wild Pokemon - 9", "Kangaskhan", rom_addresses["Wild_SafariZoneEast"] + 17,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone East", "Wild Pokemon - 10", ["Scyther", "Pinsir"],
+ rom_addresses["Wild_SafariZoneEast"] + 19, None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone North", "Wild Pokemon - 1", ["Nidoran M", "Nidoran F"],
+ rom_addresses["Wild_SafariZoneNorth"] + 1, None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone North", "Wild Pokemon - 2", "Rhyhorn", rom_addresses["Wild_SafariZoneNorth"] + 3,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone North", "Wild Pokemon - 3", "Paras", rom_addresses["Wild_SafariZoneNorth"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Safari Zone North", "Wild Pokemon - 4", "Exeggcute", rom_addresses["Wild_SafariZoneNorth"] + 7,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone North", "Wild Pokemon - 5", ["Nidorino", "Nidorina"],
+ rom_addresses["Wild_SafariZoneNorth"] + 9, None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone North", "Wild Pokemon - 6", "Exeggcute", rom_addresses["Wild_SafariZoneNorth"] + 11,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone North", "Wild Pokemon - 7", ["Nidorina", "Nidorino"],
+ rom_addresses["Wild_SafariZoneNorth"] + 13, None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone North", "Wild Pokemon - 8", "Venomoth", rom_addresses["Wild_SafariZoneNorth"] + 15,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone North", "Wild Pokemon - 9", "Chansey", rom_addresses["Wild_SafariZoneNorth"] + 17,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone North", "Wild Pokemon - 10", "Tauros", rom_addresses["Wild_SafariZoneNorth"] + 19,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone West", "Wild Pokemon - 1", ["Nidoran M", "Nidoran F"],
+ rom_addresses["Wild_SafariZoneWest"] + 1, None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone West", "Wild Pokemon - 2", "Doduo", rom_addresses["Wild_SafariZoneWest"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Safari Zone West", "Wild Pokemon - 3", "Venonat", rom_addresses["Wild_SafariZoneWest"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Safari Zone West", "Wild Pokemon - 4", "Exeggcute", rom_addresses["Wild_SafariZoneWest"] + 7,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone West", "Wild Pokemon - 5", ["Nidorino", "Nidorina"],
+ rom_addresses["Wild_SafariZoneWest"] + 9, None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone West", "Wild Pokemon - 6", "Exeggcute", rom_addresses["Wild_SafariZoneWest"] + 11,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone West", "Wild Pokemon - 7", ["Nidoran F", "Nidoran M"],
+ rom_addresses["Wild_SafariZoneWest"] + 13, None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone West", "Wild Pokemon - 8", "Venomoth", rom_addresses["Wild_SafariZoneWest"] + 15,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Safari Zone West", "Wild Pokemon - 9", "Tauros", rom_addresses["Wild_SafariZoneWest"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Safari Zone West", "Wild Pokemon - 10", "Kangaskhan", rom_addresses["Wild_SafariZoneWest"] + 19,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 20 West", "Surf Pokemon - 1", "Tentacool", rom_addresses["Wild_SeaRoutes"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 20 West", "Surf Pokemon - 2", "Tentacool", rom_addresses["Wild_SeaRoutes"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 20 West", "Surf Pokemon - 3", "Tentacool", rom_addresses["Wild_SeaRoutes"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 20 West", "Surf Pokemon - 4", "Tentacool", rom_addresses["Wild_SeaRoutes"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 20 West", "Surf Pokemon - 5", "Tentacool", rom_addresses["Wild_SeaRoutes"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 20 West", "Surf Pokemon - 6", "Tentacool", rom_addresses["Wild_SeaRoutes"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 20 West", "Surf Pokemon - 7", "Tentacool", rom_addresses["Wild_SeaRoutes"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 20 West", "Surf Pokemon - 8", "Tentacool", rom_addresses["Wild_SeaRoutes"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 20 West", "Surf Pokemon - 9", "Tentacool", rom_addresses["Wild_SeaRoutes"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 20 West", "Surf Pokemon - 10", "Tentacool", rom_addresses["Wild_SeaRoutes"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 21", "Surf Pokemon - 1", "Tentacool", rom_addresses["Wild_Surf_Route21"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 21", "Surf Pokemon - 2", "Tentacool", rom_addresses["Wild_Surf_Route21"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 21", "Surf Pokemon - 3", "Tentacool", rom_addresses["Wild_Surf_Route21"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 21", "Surf Pokemon - 4", "Tentacool", rom_addresses["Wild_Surf_Route21"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 21", "Surf Pokemon - 5", "Tentacool", rom_addresses["Wild_Surf_Route21"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 21", "Surf Pokemon - 6", "Tentacool", rom_addresses["Wild_Surf_Route21"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 21", "Surf Pokemon - 7", "Tentacool", rom_addresses["Wild_Surf_Route21"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 21", "Surf Pokemon - 8", "Tentacool", rom_addresses["Wild_Surf_Route21"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 21", "Surf Pokemon - 9", "Tentacool", rom_addresses["Wild_Surf_Route21"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 21", "Surf Pokemon - 10", "Tentacool", rom_addresses["Wild_Surf_Route21"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands 1F", "Wild Pokemon - 1", "Seel", rom_addresses["Wild_SeafoamIslands1F"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands 1F", "Wild Pokemon - 2", ["Slowpoke", "Psyduck"],
+ rom_addresses["Wild_SeafoamIslands1F"] + 3, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands 1F", "Wild Pokemon - 3", ["Shellder", "Staryu"],
+ rom_addresses["Wild_SeafoamIslands1F"] + 5, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands 1F", "Wild Pokemon - 4", ["Horsea", "Krabby"],
+ rom_addresses["Wild_SeafoamIslands1F"] + 7, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands 1F", "Wild Pokemon - 5", ["Horsea", "Krabby"],
+ rom_addresses["Wild_SeafoamIslands1F"] + 9, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands 1F", "Wild Pokemon - 6", "Zubat", rom_addresses["Wild_SeafoamIslands1F"] + 11,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands 1F", "Wild Pokemon - 7", "Golbat", rom_addresses["Wild_SeafoamIslands1F"] + 13,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands 1F", "Wild Pokemon - 8", ["Psyduck", "Slowpoke"],
+ rom_addresses["Wild_SeafoamIslands1F"] + 15, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands 1F", "Wild Pokemon - 9", ["Shellder", "Staryu"],
+ rom_addresses["Wild_SeafoamIslands1F"] + 17, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands 1F", "Wild Pokemon - 10", ["Golduck", "Slowbro"],
+ rom_addresses["Wild_SeafoamIslands1F"] + 19, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B1F", "Wild Pokemon - 1", ["Staryu", "Shellder"],
+ rom_addresses["Wild_SeafoamIslandsB1F"] + 1, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B1F", "Wild Pokemon - 2", ["Horsea", "Krabby"],
+ rom_addresses["Wild_SeafoamIslandsB1F"] + 3, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B1F", "Wild Pokemon - 3", ["Shellder", "Staryu"],
+ rom_addresses["Wild_SeafoamIslandsB1F"] + 5, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B1F", "Wild Pokemon - 4", ["Horsea", "Krabby"],
+ rom_addresses["Wild_SeafoamIslandsB1F"] + 7, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B1F", "Wild Pokemon - 5", ["Slowpoke", "Psyduck"],
+ rom_addresses["Wild_SeafoamIslandsB1F"] + 9, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B1F", "Wild Pokemon - 6", "Seel", rom_addresses["Wild_SeafoamIslandsB1F"] + 11,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B1F", "Wild Pokemon - 7", ["Slowpoke", "Psyduck"],
+ rom_addresses["Wild_SeafoamIslandsB1F"] + 13, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B1F", "Wild Pokemon - 8", "Seel", rom_addresses["Wild_SeafoamIslandsB1F"] + 15,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B1F", "Wild Pokemon - 9", "Dewgong", rom_addresses["Wild_SeafoamIslandsB1F"] + 17,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B1F", "Wild Pokemon - 10", ["Seadra", "Kingler"],
+ rom_addresses["Wild_SeafoamIslandsB1F"] + 19, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B2F", "Wild Pokemon - 1", "Seel", rom_addresses["Wild_SeafoamIslandsB2F"] + 1,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B2F", "Wild Pokemon - 2", ["Slowpoke", "Psyduck"],
+ rom_addresses["Wild_SeafoamIslandsB2F"] + 3, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B2F", "Wild Pokemon - 3", "Seel", rom_addresses["Wild_SeafoamIslandsB2F"] + 5,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B2F", "Wild Pokemon - 4", ["Slowpoke", "Psyduck"],
+ rom_addresses["Wild_SeafoamIslandsB2F"] + 7, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B2F", "Wild Pokemon - 5", ["Horsea", "Krabby"],
+ rom_addresses["Wild_SeafoamIslandsB2F"] + 9, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B2F", "Wild Pokemon - 6", ["Staryu", "Shellder"],
+ rom_addresses["Wild_SeafoamIslandsB2F"] + 11, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B2F", "Wild Pokemon - 7", ["Horsea", "Krabby"],
+ rom_addresses["Wild_SeafoamIslandsB2F"] + 13, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B2F", "Wild Pokemon - 8", ["Shellder", "Staryu"],
+ rom_addresses["Wild_SeafoamIslandsB2F"] + 15, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B2F", "Wild Pokemon - 9", "Golbat", rom_addresses["Wild_SeafoamIslandsB2F"] + 17,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B2F", "Wild Pokemon - 10", ["Slowbro", "Golduck"],
+ rom_addresses["Wild_SeafoamIslandsB2F"] + 19, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B3F", "Wild Pokemon - 1", ["Slowpoke", "Psyduck"],
+ rom_addresses["Wild_SeafoamIslandsB3F"] + 1, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B3F", "Wild Pokemon - 2", "Seel", rom_addresses["Wild_SeafoamIslandsB3F"] + 3,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B3F", "Wild Pokemon - 3", ["Slowpoke", "Psyduck"],
+ rom_addresses["Wild_SeafoamIslandsB3F"] + 5, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B3F", "Wild Pokemon - 4", "Seel", rom_addresses["Wild_SeafoamIslandsB3F"] + 7,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B3F", "Wild Pokemon - 5", ["Horsea", "Krabby"],
+ rom_addresses["Wild_SeafoamIslandsB3F"] + 9, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B3F", "Wild Pokemon - 6", ["Shellder", "Staryu"],
+ rom_addresses["Wild_SeafoamIslandsB3F"] + 11, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B3F", "Wild Pokemon - 7", ["Horsea", "Krabby"],
+ rom_addresses["Wild_SeafoamIslandsB3F"] + 13, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B3F", "Wild Pokemon - 8", ["Shellder", "Staryu"],
+ rom_addresses["Wild_SeafoamIslandsB3F"] + 15, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B3F", "Wild Pokemon - 9", ["Seadra", "Kingler"],
+ rom_addresses["Wild_SeafoamIslandsB3F"] + 17, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B3F", "Wild Pokemon - 10", "Dewgong",
+ rom_addresses["Wild_SeafoamIslandsB3F"] + 19, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B4F", "Wild Pokemon - 1", ["Horsea", "Krabby"],
+ rom_addresses["Wild_SeafoamIslandsB4F"] + 1, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B4F", "Wild Pokemon - 2", ["Shellder", "Staryu"],
+ rom_addresses["Wild_SeafoamIslandsB4F"] + 3, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B4F", "Wild Pokemon - 3", ["Horsea", "Krabby"],
+ rom_addresses["Wild_SeafoamIslandsB4F"] + 5, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B4F", "Wild Pokemon - 4", "Shellder", rom_addresses["Wild_SeafoamIslandsB4F"] + 7,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B4F", "Wild Pokemon - 5", ["Slowpoke", "Psyduck"],
+ rom_addresses["Wild_SeafoamIslandsB4F"] + 9, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B4F", "Wild Pokemon - 6", "Seel", rom_addresses["Wild_SeafoamIslandsB4F"] + 11,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B4F", "Wild Pokemon - 7", ["Slowpoke", "Psyduck"],
+ rom_addresses["Wild_SeafoamIslandsB4F"] + 13, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B4F", "Wild Pokemon - 8", "Seel", rom_addresses["Wild_SeafoamIslandsB4F"] + 15,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B4F", "Wild Pokemon - 9", ["Slowbro", "Golduck"],
+ rom_addresses["Wild_SeafoamIslandsB4F"] + 17, None, event=True, type="Wild Encounter"),
+ LocationData("Seafoam Islands B4F", "Wild Pokemon - 10", "Golbat", rom_addresses["Wild_SeafoamIslandsB4F"] + 19,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 1F", "Wild Pokemon - 1", ["Koffing", "Grimer"],
+ rom_addresses["Wild_PokemonMansion1F"] + 1, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 1F", "Wild Pokemon - 2", ["Koffing", "Grimer"],
+ rom_addresses["Wild_PokemonMansion1F"] + 3, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 1F", "Wild Pokemon - 3", "Ponyta", rom_addresses["Wild_PokemonMansion1F"] + 5,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 1F", "Wild Pokemon - 4", "Ponyta", rom_addresses["Wild_PokemonMansion1F"] + 7,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 1F", "Wild Pokemon - 5", ["Growlithe", "Vulpix"],
+ rom_addresses["Wild_PokemonMansion1F"] + 9, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 1F", "Wild Pokemon - 6", "Ponyta", rom_addresses["Wild_PokemonMansion1F"] + 11,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 1F", "Wild Pokemon - 7", ["Grimer", "Koffing"],
+ rom_addresses["Wild_PokemonMansion1F"] + 13, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 1F", "Wild Pokemon - 8", "Ponyta", rom_addresses["Wild_PokemonMansion1F"] + 15,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 1F", "Wild Pokemon - 9", ["Weezing", "Muk"],
+ rom_addresses["Wild_PokemonMansion1F"] + 17, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 1F", "Wild Pokemon - 10", ["Muk", "Weezing"],
+ rom_addresses["Wild_PokemonMansion1F"] + 19, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 2F", "Wild Pokemon - 1", ["Growlithe", "Vulpix"],
+ rom_addresses["Wild_PokemonMansion2F"] + 1, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 2F", "Wild Pokemon - 2", ["Koffing", "Grimer"],
+ rom_addresses["Wild_PokemonMansion2F"] + 3, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 2F", "Wild Pokemon - 3", ["Koffing", "Grimer"],
+ rom_addresses["Wild_PokemonMansion2F"] + 5, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 2F", "Wild Pokemon - 4", "Ponyta", rom_addresses["Wild_PokemonMansion2F"] + 7,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 2F", "Wild Pokemon - 5", ["Koffing", "Grimer"],
+ rom_addresses["Wild_PokemonMansion2F"] + 9, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 2F", "Wild Pokemon - 6", "Ponyta", rom_addresses["Wild_PokemonMansion2F"] + 11,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 2F", "Wild Pokemon - 7", ["Grimer", "Koffing"],
+ rom_addresses["Wild_PokemonMansion2F"] + 13, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 2F", "Wild Pokemon - 8", "Ponyta", rom_addresses["Wild_PokemonMansion2F"] + 15,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 2F", "Wild Pokemon - 9", ["Weezing", "Muk"],
+ rom_addresses["Wild_PokemonMansion2F"] + 17, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 2F", "Wild Pokemon - 10", ["Muk", "Weezing"],
+ rom_addresses["Wild_PokemonMansion2F"] + 19, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 3F", "Wild Pokemon - 1", ["Koffing", "Grimer"],
+ rom_addresses["Wild_PokemonMansion3F"] + 1, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 3F", "Wild Pokemon - 2", ["Growlithe", "Vulpix"],
+ rom_addresses["Wild_PokemonMansion3F"] + 3, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 3F", "Wild Pokemon - 3", ["Koffing", "Grimer"],
+ rom_addresses["Wild_PokemonMansion3F"] + 5, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 3F", "Wild Pokemon - 4", "Ponyta", rom_addresses["Wild_PokemonMansion3F"] + 7,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 3F", "Wild Pokemon - 5", ["Ponyta", "Magmar"],
+ rom_addresses["Wild_PokemonMansion3F"] + 9, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 3F", "Wild Pokemon - 6", ["Weezing", "Muk"],
+ rom_addresses["Wild_PokemonMansion3F"] + 11, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 3F", "Wild Pokemon - 7", ["Grimer", "Koffing"],
+ rom_addresses["Wild_PokemonMansion3F"] + 13, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 3F", "Wild Pokemon - 8", ["Weezing", "Muk"],
+ rom_addresses["Wild_PokemonMansion3F"] + 15, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 3F", "Wild Pokemon - 9", "Ponyta", rom_addresses["Wild_PokemonMansion3F"] + 17,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion 3F", "Wild Pokemon - 10", ["Muk", "Weezing"],
+ rom_addresses["Wild_PokemonMansion3F"] + 19, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion B1F", "Wild Pokemon - 1", ["Koffing", "Grimer"],
+ rom_addresses["Wild_PokemonMansionB1F"] + 1, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion B1F", "Wild Pokemon - 2", ["Koffing", "Grimer"],
+ rom_addresses["Wild_PokemonMansionB1F"] + 3, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion B1F", "Wild Pokemon - 3", ["Growlithe", "Vulpix"],
+ rom_addresses["Wild_PokemonMansionB1F"] + 5, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion B1F", "Wild Pokemon - 4", "Ponyta", rom_addresses["Wild_PokemonMansionB1F"] + 7,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion B1F", "Wild Pokemon - 5", ["Koffing", "Grimer"],
+ rom_addresses["Wild_PokemonMansionB1F"] + 9, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion B1F", "Wild Pokemon - 6", ["Weezing", "Muk"],
+ rom_addresses["Wild_PokemonMansionB1F"] + 11, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion B1F", "Wild Pokemon - 7", "Ponyta", rom_addresses["Wild_PokemonMansionB1F"] + 13,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion B1F", "Wild Pokemon - 8", ["Grimer", "Koffing"],
+ rom_addresses["Wild_PokemonMansionB1F"] + 15, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion B1F", "Wild Pokemon - 9", ["Weezing", "Magmar"],
+ rom_addresses["Wild_PokemonMansionB1F"] + 17, None, event=True, type="Wild Encounter"),
+ LocationData("Pokemon Mansion B1F", "Wild Pokemon - 10", ["Muk", "Weezing"],
+ rom_addresses["Wild_PokemonMansionB1F"] + 19, None, event=True, type="Wild Encounter"),
+ LocationData("Route 21", "Wild Pokemon - 1", "Rattata", rom_addresses["Wild_Route21"] + 1, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 21", "Wild Pokemon - 2", "Pidgey", rom_addresses["Wild_Route21"] + 3, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 21", "Wild Pokemon - 3", "Raticate", rom_addresses["Wild_Route21"] + 5, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 21", "Wild Pokemon - 4", "Rattata", rom_addresses["Wild_Route21"] + 7, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 21", "Wild Pokemon - 5", "Pidgey", rom_addresses["Wild_Route21"] + 9, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 21", "Wild Pokemon - 6", "Pidgeotto", rom_addresses["Wild_Route21"] + 11, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 21", "Wild Pokemon - 7", "Pidgeotto", rom_addresses["Wild_Route21"] + 13, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 21", "Wild Pokemon - 8", "Tangela", rom_addresses["Wild_Route21"] + 15, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 21", "Wild Pokemon - 9", "Tangela", rom_addresses["Wild_Route21"] + 17, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 21", "Wild Pokemon - 10", "Tangela", rom_addresses["Wild_Route21"] + 19, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Cerulean Cave 1F", "Wild Pokemon - 1", "Golbat", rom_addresses["Wild_CeruleanCave1F"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave 1F", "Wild Pokemon - 2", "Hypno", rom_addresses["Wild_CeruleanCave1F"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave 1F", "Wild Pokemon - 3", "Magneton", rom_addresses["Wild_CeruleanCave1F"] + 5,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave 1F", "Wild Pokemon - 4", "Dodrio", rom_addresses["Wild_CeruleanCave1F"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave 1F", "Wild Pokemon - 5", "Venomoth", rom_addresses["Wild_CeruleanCave1F"] + 9,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave 1F", "Wild Pokemon - 6", ["Arbok", "Sandslash"],
+ rom_addresses["Wild_CeruleanCave1F"] + 11, None, event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave 1F", "Wild Pokemon - 7", "Kadabra", rom_addresses["Wild_CeruleanCave1F"] + 13,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave 1F", "Wild Pokemon - 8", "Parasect", rom_addresses["Wild_CeruleanCave1F"] + 15,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave 1F", "Wild Pokemon - 9", "Raichu", rom_addresses["Wild_CeruleanCave1F"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave 1F", "Wild Pokemon - 10", "Ditto", rom_addresses["Wild_CeruleanCave1F"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave 2F", "Wild Pokemon - 1", "Dodrio", rom_addresses["Wild_CeruleanCave2F"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave 2F", "Wild Pokemon - 2", "Venomoth", rom_addresses["Wild_CeruleanCave2F"] + 3,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave 2F", "Wild Pokemon - 3", "Kadabra", rom_addresses["Wild_CeruleanCave2F"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave 2F", "Wild Pokemon - 4", "Rhydon", rom_addresses["Wild_CeruleanCave2F"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave 2F", "Wild Pokemon - 5", "Marowak", rom_addresses["Wild_CeruleanCave2F"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave 2F", "Wild Pokemon - 6", "Electrode", rom_addresses["Wild_CeruleanCave2F"] + 11,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave 2F", "Wild Pokemon - 7", "Chansey", rom_addresses["Wild_CeruleanCave2F"] + 13,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave 2F", "Wild Pokemon - 8", "Wigglytuff", rom_addresses["Wild_CeruleanCave2F"] + 15,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave 2F", "Wild Pokemon - 9", "Ditto", rom_addresses["Wild_CeruleanCave2F"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave 2F", "Wild Pokemon - 10", "Ditto", rom_addresses["Wild_CeruleanCave2F"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave B1F", "Wild Pokemon - 1", "Rhydon", rom_addresses["Wild_CeruleanCaveB1F"] + 1,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave B1F", "Wild Pokemon - 2", "Marowak", rom_addresses["Wild_CeruleanCaveB1F"] + 3,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave B1F", "Wild Pokemon - 3", "Electrode", rom_addresses["Wild_CeruleanCaveB1F"] + 5,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave B1F", "Wild Pokemon - 4", "Chansey", rom_addresses["Wild_CeruleanCaveB1F"] + 7,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave B1F", "Wild Pokemon - 5", "Parasect", rom_addresses["Wild_CeruleanCaveB1F"] + 9,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave B1F", "Wild Pokemon - 6", "Raichu", rom_addresses["Wild_CeruleanCaveB1F"] + 11,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave B1F", "Wild Pokemon - 7", ["Arbok", "Sandslash"],
+ rom_addresses["Wild_CeruleanCaveB1F"] + 13,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave B1F", "Wild Pokemon - 8", "Ditto", rom_addresses["Wild_CeruleanCaveB1F"] + 15,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave B1F", "Wild Pokemon - 9", "Ditto", rom_addresses["Wild_CeruleanCaveB1F"] + 17,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Cerulean Cave B1F", "Wild Pokemon - 10", "Ditto", rom_addresses["Wild_CeruleanCaveB1F"] + 19,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Power Plant", "Wild Pokemon - 1", "Voltorb", rom_addresses["Wild_PowerPlant"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Power Plant", "Wild Pokemon - 2", "Magnemite", rom_addresses["Wild_PowerPlant"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Power Plant", "Wild Pokemon - 3", "Pikachu", rom_addresses["Wild_PowerPlant"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Power Plant", "Wild Pokemon - 4", "Pikachu", rom_addresses["Wild_PowerPlant"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Power Plant", "Wild Pokemon - 5", "Magnemite", rom_addresses["Wild_PowerPlant"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Power Plant", "Wild Pokemon - 6", "Voltorb", rom_addresses["Wild_PowerPlant"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Power Plant", "Wild Pokemon - 7", "Magneton", rom_addresses["Wild_PowerPlant"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Power Plant", "Wild Pokemon - 8", "Magneton", rom_addresses["Wild_PowerPlant"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Power Plant", "Wild Pokemon - 9", ["Electabuzz", "Raichu"], rom_addresses["Wild_PowerPlant"] + 17,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Power Plant", "Wild Pokemon - 10", ["Electabuzz", "Raichu"],
+ rom_addresses["Wild_PowerPlant"] + 19, None, event=True, type="Wild Encounter"),
+ LocationData("Route 23 North", "Wild Pokemon - 1", ["Ekans", "Sandshrew"], rom_addresses["Wild_Route23"] + 1,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 23 North", "Wild Pokemon - 2", "Ditto", rom_addresses["Wild_Route23"] + 3, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 23 North", "Wild Pokemon - 3", "Spearow", rom_addresses["Wild_Route23"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 23 North", "Wild Pokemon - 4", "Fearow", rom_addresses["Wild_Route23"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 23 North", "Wild Pokemon - 5", "Ditto", rom_addresses["Wild_Route23"] + 9, None, event=True,
+ type="Wild Encounter"),
+ LocationData("Route 23 North", "Wild Pokemon - 6", "Fearow", rom_addresses["Wild_Route23"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 23 North", "Wild Pokemon - 7", ["Arbok", "Sandslash"], rom_addresses["Wild_Route23"] + 13,
+ None, event=True, type="Wild Encounter"),
+ LocationData("Route 23 North", "Wild Pokemon - 8", "Ditto", rom_addresses["Wild_Route23"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 23 North", "Wild Pokemon - 9", "Fearow", rom_addresses["Wild_Route23"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Route 23 North", "Wild Pokemon - 10", "Fearow", rom_addresses["Wild_Route23"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 2F", "Wild Pokemon - 1", "Machop", rom_addresses["Wild_VictoryRoad2F"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 2F", "Wild Pokemon - 2", "Geodude", rom_addresses["Wild_VictoryRoad2F"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 2F", "Wild Pokemon - 3", "Zubat", rom_addresses["Wild_VictoryRoad2F"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 2F", "Wild Pokemon - 4", "Onix", rom_addresses["Wild_VictoryRoad2F"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 2F", "Wild Pokemon - 5", "Onix", rom_addresses["Wild_VictoryRoad2F"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 2F", "Wild Pokemon - 6", "Onix", rom_addresses["Wild_VictoryRoad2F"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 2F", "Wild Pokemon - 7", "Machoke", rom_addresses["Wild_VictoryRoad2F"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 2F", "Wild Pokemon - 8", "Golbat", rom_addresses["Wild_VictoryRoad2F"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 2F", "Wild Pokemon - 9", "Marowak", rom_addresses["Wild_VictoryRoad2F"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 2F", "Wild Pokemon - 10", "Graveler", rom_addresses["Wild_VictoryRoad2F"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 3F", "Wild Pokemon - 1", "Machop", rom_addresses["Wild_VictoryRoad3F"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 3F", "Wild Pokemon - 2", "Geodude", rom_addresses["Wild_VictoryRoad3F"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 3F", "Wild Pokemon - 3", "Zubat", rom_addresses["Wild_VictoryRoad3F"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 3F", "Wild Pokemon - 4", "Onix", rom_addresses["Wild_VictoryRoad3F"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 3F", "Wild Pokemon - 5", "Venomoth", rom_addresses["Wild_VictoryRoad3F"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 3F", "Wild Pokemon - 6", "Onix", rom_addresses["Wild_VictoryRoad3F"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 3F", "Wild Pokemon - 7", "Graveler", rom_addresses["Wild_VictoryRoad3F"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 3F", "Wild Pokemon - 8", "Golbat", rom_addresses["Wild_VictoryRoad3F"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 3F", "Wild Pokemon - 9", "Machoke", rom_addresses["Wild_VictoryRoad3F"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 3F", "Wild Pokemon - 10", "Machoke", rom_addresses["Wild_VictoryRoad3F"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 1F", "Wild Pokemon - 1", "Machop", rom_addresses["Wild_VictoryRoad1F"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 1F", "Wild Pokemon - 2", "Geodude", rom_addresses["Wild_VictoryRoad1F"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 1F", "Wild Pokemon - 3", "Zubat", rom_addresses["Wild_VictoryRoad1F"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 1F", "Wild Pokemon - 4", "Onix", rom_addresses["Wild_VictoryRoad1F"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 1F", "Wild Pokemon - 5", "Onix", rom_addresses["Wild_VictoryRoad1F"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 1F", "Wild Pokemon - 6", "Onix", rom_addresses["Wild_VictoryRoad1F"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 1F", "Wild Pokemon - 7", "Graveler", rom_addresses["Wild_VictoryRoad1F"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 1F", "Wild Pokemon - 8", "Golbat", rom_addresses["Wild_VictoryRoad1F"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 1F", "Wild Pokemon - 9", "Machoke", rom_addresses["Wild_VictoryRoad1F"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Victory Road 1F", "Wild Pokemon - 10", "Marowak", rom_addresses["Wild_VictoryRoad1F"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Diglett's Cave", "Wild Pokemon - 1", "Diglett", rom_addresses["Wild_DiglettsCave"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Diglett's Cave", "Wild Pokemon - 2", "Diglett", rom_addresses["Wild_DiglettsCave"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Diglett's Cave", "Wild Pokemon - 3", "Diglett", rom_addresses["Wild_DiglettsCave"] + 5, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Diglett's Cave", "Wild Pokemon - 4", "Diglett", rom_addresses["Wild_DiglettsCave"] + 7, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Diglett's Cave", "Wild Pokemon - 5", "Diglett", rom_addresses["Wild_DiglettsCave"] + 9, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Diglett's Cave", "Wild Pokemon - 6", "Diglett", rom_addresses["Wild_DiglettsCave"] + 11, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Diglett's Cave", "Wild Pokemon - 7", "Diglett", rom_addresses["Wild_DiglettsCave"] + 13, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Diglett's Cave", "Wild Pokemon - 8", "Diglett", rom_addresses["Wild_DiglettsCave"] + 15, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Diglett's Cave", "Wild Pokemon - 9", "Dugtrio", rom_addresses["Wild_DiglettsCave"] + 17, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Diglett's Cave", "Wild Pokemon - 10", "Dugtrio", rom_addresses["Wild_DiglettsCave"] + 19, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Anywhere", "Good Rod Pokemon - 1", "Goldeen", rom_addresses["Wild_Good_Rod"] + 1, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Anywhere", "Good Rod Pokemon - 2", "Poliwag", rom_addresses["Wild_Good_Rod"] + 3, None,
+ event=True, type="Wild Encounter"),
+ LocationData("Anywhere", "Old Rod Pokemon", "Magikarp", rom_addresses["Wild_Old_Rod"] + 1, None,
+ event=True, type="Wild Encounter"),
+
+ # "Wild encounters" means a Pokemon you cannot miss or release and lose - re-use it for these
+ LocationData("Celadon Prize Corner", "Pokemon Prize - 1", "Abra", [rom_addresses["Prize_Mon_A"],
+ rom_addresses["Prize_Mon_A2"]], None, event=True,
+ type="Wild Encounter"),
+ LocationData("Celadon Prize Corner", "Pokemon Prize - 2", "Clefairy", [rom_addresses["Prize_Mon_B"],
+ rom_addresses["Prize_Mon_B2"]], None,
+ event=True, type="Wild Encounter"),
+ LocationData("Celadon Prize Corner", "Pokemon Prize - 3", ["Nidorina", "Nidorino"], [rom_addresses["Prize_Mon_C"],
+ rom_addresses["Prize_Mon_C2"]],
+ None,
+ event=True, type="Wild Encounter"),
+ LocationData("Celadon Prize Corner", "Pokemon Prize - 4", ["Dratini", "Pinsir"], [rom_addresses["Prize_Mon_D"],
+ rom_addresses["Prize_Mon_D2"]],
+ None, event=True, type="Wild Encounter"),
+ LocationData("Celadon Prize Corner", "Pokemon Prize - 5", ["Scyther", "Dratini"], [rom_addresses["Prize_Mon_E"],
+ rom_addresses["Prize_Mon_E2"]],
+ None, event=True, type="Wild Encounter"),
+ LocationData("Celadon Prize Corner", "Pokemon Prize - 6", "Porygon", [rom_addresses["Prize_Mon_F"],
+ rom_addresses["Prize_Mon_F2"]], None,
+ event=True, type="Wild Encounter"),
+
+ # counted for pokedex, because they cannot be permanently "missed" but not for HMs since they can be released
+ # or evolved forms may not be able to learn the same HMs
+ LocationData("Celadon City", "Celadon Mansion Pokemon", "Eevee", rom_addresses["Gift_Eevee"], None,
+ event=True, type="Static Pokemon"),
+ LocationData("Silph Co 7F", "Gift Pokemon", "Lapras", rom_addresses["Gift_Lapras"], None,
+ event=True, type="Static Pokemon"),
+ LocationData("Route 3", "Pokemon For Sale", "Magikarp", rom_addresses["Gift_Magikarp"], None,
+ event=True, type="Static Pokemon"),
+ LocationData("Cinnabar Island", "Old Amber Pokemon", "Aerodactyl", rom_addresses["Gift_Aerodactyl"], None,
+ event=True, type="Static Pokemon"),
+ LocationData("Cinnabar Island", "Helix Fossil Pokemon", "Omanyte", rom_addresses["Gift_Omanyte"], None,
+ event=True, type="Static Pokemon"),
+ LocationData("Cinnabar Island", "Dome Fossil Pokemon", "Kabuto", rom_addresses["Gift_Kabuto"], None,
+ event=True, type="Static Pokemon"),
+
+ # not counted for logic currently. Could perhaps make static encounters resettable in the future?
+ LocationData("Power Plant", "Fake Pokeball Battle 1", "Voltorb", rom_addresses["Static_Encounter_Voltorb_A"],
+ None, event=True, type="Missable Pokemon"),
+ LocationData("Power Plant", "Fake Pokeball Battle 2", "Voltorb", rom_addresses["Static_Encounter_Voltorb_B"],
+ None, event=True, type="Missable Pokemon"),
+ LocationData("Power Plant", "Fake Pokeball Battle 3", "Voltorb", rom_addresses["Static_Encounter_Voltorb_C"],
+ None, event=True, type="Missable Pokemon"),
+ LocationData("Power Plant", "Fake Pokeball Battle 4", "Voltorb", rom_addresses["Static_Encounter_Voltorb_D"],
+ None, event=True, type="Missable Pokemon"),
+ LocationData("Power Plant", "Fake Pokeball Battle 5", "Voltorb", rom_addresses["Static_Encounter_Voltorb_E"],
+ None, event=True, type="Missable Pokemon"),
+ LocationData("Power Plant", "Fake Pokeball Battle 6", "Voltorb", rom_addresses["Static_Encounter_Voltorb_F"],
+ None, event=True, type="Missable Pokemon"),
+ LocationData("Power Plant", "Fake Pokeball Battle 7", "Voltorb", rom_addresses["Static_Encounter_Electrode_A"],
+ None, event=True, type="Missable Pokemon"),
+ LocationData("Power Plant", "Fake Pokeball Battle 8", "Voltorb", rom_addresses["Static_Encounter_Electrode_B"],
+ None, event=True, type="Missable Pokemon"),
+
+ LocationData("Pokemon Tower 6F", "Restless Soul", "Marowak", [rom_addresses["Ghost_Battle1"],
+ rom_addresses["Ghost_Battle2"],
+ rom_addresses["Ghost_Battle3"],
+ rom_addresses["Ghost_Battle4"],
+ rom_addresses["Ghost_Battle5"],
+ rom_addresses["Ghost_Battle6"]], None, event=True,
+ type="Missable Pokemon"),
+
+ LocationData("Route 12 West", "Sleeping Pokemon", "Snorlax", rom_addresses["Static_Encounter_Snorlax_A"],
+ None, event=True, type="Missable Pokemon"),
+ LocationData("Route 16", "Sleeping Pokemon", "Snorlax", rom_addresses["Static_Encounter_Snorlax_B"],
+ None, event=True, type="Missable Pokemon"),
+
+ LocationData("Saffron City", "Fighting Dojo Gift 1", "Hitmonlee", rom_addresses["Gift_Hitmonlee"],
+ None, event=True, type="Missable Pokemon"),
+ LocationData("Saffron City", "Fighting Dojo Gift 2", "Hitmonchan", rom_addresses["Gift_Hitmonchan"],
+ None, event=True, type="Missable Pokemon"),
+
+ LocationData("Pallet Town", "Starter 1", "Charmander", [rom_addresses["Starter1_A"],
+ rom_addresses["Starter1_B"], rom_addresses["Starter1_C"],
+ rom_addresses["Starter1_D"],
+ rom_addresses["Starter1_F"], rom_addresses["Starter1_H"]],
+ None, event=True,
+ type="Starter Pokemon"),
+
+ LocationData("Pallet Town", "Starter 2", "Squirtle", [rom_addresses["Starter2_A"],
+ rom_addresses["Starter2_B"], rom_addresses["Starter2_E"],
+ rom_addresses["Starter2_F"],
+ rom_addresses["Starter2_G"], rom_addresses["Starter2_H"],
+ rom_addresses["Starter2_I"],
+ rom_addresses["Starter2_J"], rom_addresses["Starter2_K"],
+ rom_addresses["Starter2_L"],
+ rom_addresses["Starter2_M"], rom_addresses["Starter2_N"],
+ rom_addresses["Starter2_O"]], None,
+ event=True, type="Starter Pokemon"),
+
+ LocationData("Pallet Town", "Starter 3", "Bulbasaur", [rom_addresses["Starter3_A"],
+ rom_addresses["Starter3_B"], rom_addresses["Starter3_C"],
+ rom_addresses["Starter3_D"],
+ rom_addresses["Starter3_E"], rom_addresses["Starter3_G"],
+ rom_addresses["Starter3_I"],
+ rom_addresses["Starter3_J"], rom_addresses["Starter3_K"],
+ rom_addresses["Starter3_L"],
+ rom_addresses["Starter3_M"], rom_addresses["Starter3_N"],
+ rom_addresses["Starter3_O"]], None,
+ event=True, type="Starter Pokemon"),
+
+ LocationData("Power Plant", "Legendary Pokemon", "Zapdos", rom_addresses["Static_Encounter_Zapdos"],
+ None, event=True, type="Legendary Pokemon"),
+ LocationData("Seafoam Islands B4F", "Legendary Pokemon", "Articuno", rom_addresses["Static_Encounter_Articuno"],
+ None, event=True, type="Legendary Pokemon"),
+ LocationData("Victory Road 2F", "Legendary Pokemon", "Moltres", rom_addresses["Static_Encounter_Moltres"],
+ None, event=True, type="Legendary Pokemon"),
+ LocationData("Cerulean Cave B1F", "Legendary Pokemon", "Mewtwo", rom_addresses["Static_Encounter_Mewtwo"],
+ None, event=True, type="Legendary Pokemon"),
+ LocationData("Vermilion City", "Legendary Pokemon", "Mew", rom_addresses["Static_Encounter_Mew"],
+ None, event=True, type="Legendary Pokemon"),
+]
+
+for i, location in enumerate(location_data):
+ if location.event or location.rom_address is None:
+ location.address = None
+ else:
+ location.address = loc_id_start + i
+
+
+
+class PokemonRBLocation(Location):
+ game = "Pokemon Red and Blue"
+
+ def __init__(self, player, name, address, rom_address):
+ super(PokemonRBLocation, self).__init__(
+ player, name,
+ address
+ )
+ self.rom_address = rom_address
\ No newline at end of file
diff --git a/worlds/pokemon_rb/logic.py b/worlds/pokemon_rb/logic.py
new file mode 100644
index 0000000000..3b1a3594bd
--- /dev/null
+++ b/worlds/pokemon_rb/logic.py
@@ -0,0 +1,73 @@
+from ..AutoWorld import LogicMixin
+import worlds.pokemon_rb.poke_data as poke_data
+
+
+class PokemonLogic(LogicMixin):
+ def pokemon_rb_can_surf(self, player):
+ return (((self.has("HM03 Surf", player) and self.can_learn_hm("10000", player))
+ or self.has("Flippers", player)) and (self.has("Soul Badge", player) or
+ self.has(self.world.worlds[player].extra_badges.get("Surf"), player)
+ or self.world.badges_needed_for_hm_moves[player].value == 0))
+
+ def pokemon_rb_can_cut(self, player):
+ return ((self.has("HM01 Cut", player) and self.can_learn_hm("100", player) or self.has("Master Sword", player))
+ and (self.has("Cascade Badge", player) or
+ self.has(self.world.worlds[player].extra_badges.get("Cut"), player) or
+ self.world.badges_needed_for_hm_moves[player].value == 0))
+
+ def pokemon_rb_can_fly(self, player):
+ return (((self.has("HM02 Fly", player) and self.can_learn_hm("1000", player)) or self.has("Flute", player)) and
+ (self.has("Thunder Badge", player) or self.has(self.world.worlds[player].extra_badges.get("Fly"), player)
+ or self.world.badges_needed_for_hm_moves[player].value == 0))
+
+ def pokemon_rb_can_strength(self, player):
+ return ((self.has("HM04 Strength", player) and self.can_learn_hm("100000", player)) or
+ self.has("Titan's Mitt", player)) and (self.has("Rainbow Badge", player) or
+ self.has(self.world.worlds[player].extra_badges.get("Strength"), player)
+ or self.world.badges_needed_for_hm_moves[player].value == 0)
+
+ def pokemon_rb_can_flash(self, player):
+ return (((self.has("HM05 Flash", player) and self.can_learn_hm("1000000", player)) or self.has("Lamp", player))
+ and (self.has("Boulder Badge", player) or self.has(self.world.worlds[player].extra_badges.get("Flash"),
+ player) or self.world.badges_needed_for_hm_moves[player].value == 0))
+
+ def can_learn_hm(self, move, player):
+ for pokemon, data in self.world.worlds[player].local_poke_data.items():
+ if self.has(pokemon, player) and data["tms"][6] & int(move, 2):
+ return True
+ return False
+
+ def pokemon_rb_can_get_hidden_items(self, player):
+ return self.has("Item Finder", player) or not self.world.require_item_finder[player].value
+
+ def pokemon_rb_cerulean_cave(self, count, player):
+ return len([item for item in
+ ["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Soul Badge", "Marsh Badge",
+ "Volcano Badge", "Earth Badge", "Bicycle", "Silph Scope", "Item Finder", "Super Rod", "Good Rod",
+ "Old Rod", "Lift Key", "Card Key", "Town Map", "Coin Case", "S.S. Ticket", "Secret Key",
+ "Mansion Key", "Safari Pass", "Plant Key", "Hideout Key", "HM01 Cut", "HM02 Fly", "HM03 Surf",
+ "HM04 Strength", "HM05 Flash"] if self.has(item, player)]) >= count
+
+ def pokemon_rb_can_pass_guards(self, player):
+ if self.world.tea[player].value:
+ return self.has("Tea", player)
+ else:
+ # this could just be "True", but you never know what weird options I might introduce later ;)
+ return self.can_reach("Celadon City - Counter Man", "Location", player)
+
+ def pokemon_rb_has_badges(self, count, player):
+ return len([item for item in ["Boulder Badge", "Cascade Badge", "Thunder Badge", "Rainbow Badge", "Marsh Badge",
+ "Soul Badge", "Volcano Badge", "Earth Badge"] if self.has(item, player)]) >= count
+
+ def pokemon_rb_has_pokemon(self, count, player):
+ obtained_pokemon = set()
+ for pokemon in poke_data.pokemon_data.keys():
+ if self.has(pokemon, player) or self.has(f"Static {pokemon}", player):
+ obtained_pokemon.add(pokemon)
+
+ return len(obtained_pokemon) >= count
+
+ def pokemon_rb_fossil_checks(self, count, player):
+ return (self.can_reach('Mt Moon 1F - Southwest Item', 'Location', player) and
+ self.can_reach('Cinnabar Island - Lab Scientist', 'Location', player) and len(
+ [item for item in ["Dome Fossil", "Helix Fossil", "Old Amber"] if self.has(item, player)]) >= count)
diff --git a/worlds/pokemon_rb/options.py b/worlds/pokemon_rb/options.py
new file mode 100644
index 0000000000..e7e972f3b8
--- /dev/null
+++ b/worlds/pokemon_rb/options.py
@@ -0,0 +1,480 @@
+
+from Options import Toggle, Choice, Range, SpecialRange, FreeText, TextChoice
+
+
+class GameVersion(Choice):
+ """Select Red or Blue version."""
+ display_name = "Game Version"
+ option_red = 1
+ option_blue = 0
+ default = "random"
+
+
+class TrainerName(FreeText):
+ """Your trainer name. Cannot exceed 7 characters.
+ See the setup guide on archipelago.gg for a list of allowed characters."""
+ display_name = "Trainer Name"
+ default = "ASH"
+
+
+class RivalName(FreeText):
+ """Your rival's name. Cannot exceed 7 characters.
+ See the setup guide on archipelago.gg for a list of allowed characters."""
+ display_name = "Rival's Name"
+ default = "GARY"
+
+
+class Goal(Choice):
+ """If Professor Oak is selected, your victory condition will require challenging and defeating Oak after becoming"""
+ """Champion and defeating or capturing the Pokemon at the end of Cerulean Cave."""
+ display_name = "Goal"
+ option_pokemon_league = 0
+ option_professor_oak = 1
+ default = 0
+
+
+class EliteFourCondition(Range):
+ """Number of badges required to challenge the Elite Four once the Indigo Plateau has been reached.
+ Your rival will reveal the amount needed on the first Route 22 battle (after turning in Oak's Parcel)."""
+ display_name = "Elite Four Condition"
+ range_start = 0
+ range_end = 8
+ default = 8
+
+
+class VictoryRoadCondition(Range):
+ """Number of badges required to reach Victory Road."""
+ display_name = "Victory Road Condition"
+ range_start = 0
+ range_end = 8
+ default = 8
+
+
+class ViridianGymCondition(Range):
+ """Number of badges required to enter Viridian Gym."""
+ display_name = "Viridian Gym Condition"
+ range_start = 0
+ range_end = 7
+ default = 7
+
+
+class CeruleanCaveCondition(Range):
+ """Number of badges, HMs, and key items (not counting items you can lose) required to access Cerulean Cave."""
+ """If extra_key_items is turned on, the number chosen will be increased by 4."""
+ display_name = "Cerulean Cave Condition"
+ range_start = 0
+ range_end = 25
+ default = 20
+
+
+class SecondFossilCheckCondition(Range):
+ """After choosing one of the fossil location items, you can obtain the remaining item from the Cinnabar Lab
+ Scientist after reviving this number of fossils."""
+ display_name = "Second Fossil Check Condition"
+ range_start = 0
+ range_end = 3
+ default = 3
+
+
+class BadgeSanity(Toggle):
+ """Shuffle gym badges into the general item pool. If turned off, badges will be shuffled across the 8 gyms."""
+ display_name = "Badgesanity"
+ default = 0
+
+
+class BadgesNeededForHMMoves(Choice):
+ """Off will remove the requirement for badges to use HM moves. Extra will give the Marsh, Volcano, and Earth
+ Badges a random HM move to enable. Extra Plus will additionally pick two random badges to enable a second HM move.
+ A man in Cerulean City will reveal the moves enabled by each Badge."""
+ display_name = "Badges Needed For HM Moves"
+ default = 1
+ option_on = 1
+ alias_true = 1
+ option_off = 0
+ alias_false = 0
+ option_extra = 2
+ option_extra_plus = 3
+
+
+class OldMan(Choice):
+ """With Open Viridian City, the Old Man will let you through without needing to turn in Oak's Parcel."""
+ """Early Parcel will ensure Oak's Parcel is available at the beginning of your game."""
+ display_name = "Old Man"
+ option_vanilla = 0
+ option_early_parcel = 1
+ option_open_viridian_city = 2
+ default = 1
+
+
+class Tea(Toggle):
+ """Adds a Tea item to the item pool which the Saffron guards require instead of the vending machine drinks.
+ Adds a location check to the Celadon Mansion 1F, where Tea is acquired in FireRed and LeafGreen."""
+ display_name = "Tea"
+ default = 0
+
+
+class ExtraKeyItems(Toggle):
+ """Adds key items that are required to access the Rocket Hideout, Cinnabar Mansion, Safari Zone, and Power Plant.
+ Adds four item pickups to Rock Tunnel B1F."""
+ display_name = "Extra Key Items"
+ default = 0
+
+
+class ExtraStrengthBoulders(Toggle):
+ """Adds Strength Boulders blocking the Route 11 gate, and in Route 13 (can be bypassed with Surf).
+ This potentially increases the usefulness of Strength as well as the Bicycle."""
+ display_name = "Extra Strength Boulders"
+ default = 0
+
+
+class RequireItemFinder(Toggle):
+ """Require Item Finder to pick up hidden items."""
+ display_name = "Require Item Finder"
+ default = 0
+
+
+class RandomizeHiddenItems(Choice):
+ """Randomize hidden items. If you choose exclude, they will be randomized but will be guaranteed junk items."""
+ display_name = "Randomize Hidden Items"
+ option_on = 1
+ option_off = 0
+ alias_true = 1
+ alias_false = 0
+ option_exclude = 2
+ default = 0
+
+
+class FreeFlyLocation(Toggle):
+ """One random fly destination will be unlocked by default."""
+ display_name = "Free Fly Location"
+ default = 1
+
+
+class OaksAidRt2(Range):
+ """Number of Pokemon registered in the Pokedex required to receive the item from Oak's Aide on Route 2"""
+ display_name = "Oak's Aide Route 2"
+ range_start = 0
+ range_end = 80
+ default = 10
+
+
+class OaksAidRt11(Range):
+ """Number of Pokemon registered in the Pokedex required to receive the item from Oak's Aide on Route 11"""
+ display_name = "Oak's Aide Route 11"
+ range_start = 0
+ range_end = 80
+ default = 30
+
+
+class OaksAidRt15(Range):
+ """Number of Pokemon registered in the Pokedex required to receive the item from Oak's Aide on Route 15"""
+ display_name = "Oak's Aide Route 15"
+ range_start = 0
+ range_end = 80
+ default = 50
+
+
+class ExpModifier(SpecialRange):
+ """Modifier for EXP gained. When specifying a number, exp is multiplied by this amount and divided by 16."""
+ display_name = "Exp Modifier"
+ range_start = 0
+ range_end = 255
+ default = 16
+ special_range_names = {
+ "half": default / 2,
+ "normal": default,
+ "double": default * 2,
+ "triple": default * 3,
+ "quadruple": default * 4,
+ "quintuple": default * 5,
+ "sextuple": default * 6,
+ "septuple": default * 7,
+ "octuple": default * 8,
+ }
+
+
+class RandomizeWildPokemon(Choice):
+ """Randomize all wild Pokemon and game corner prize Pokemon. match_types will select a Pokemon with at least one
+ type matching the original type of the original Pokemon. match_base_stats will prefer Pokemon with closer base stat
+ totals. match_types_and_base_stats will match types and will weight towards similar base stats, but there may not be
+ many to choose from."""
+ display_name = "Randomize Wild Pokemon"
+ default = 0
+ option_vanilla = 0
+ option_match_types = 1
+ option_match_base_stats = 2
+ option_match_types_and_base_stats = 3
+ option_completely_random = 4
+
+
+class RandomizeStarterPokemon(Choice):
+ """Randomize the starter Pokemon choices."""
+ display_name = "Randomize Starter Pokemon"
+ default = 0
+ option_vanilla = 0
+ option_match_types = 1
+ option_match_base_stats = 2
+ option_match_types_and_base_stats = 3
+ option_completely_random = 4
+
+
+class RandomizeStaticPokemon(Choice):
+ """Randomize one-time gift and encountered Pokemon. These will always be first evolution stage Pokemon."""
+ display_name = "Randomize Static Pokemon"
+ default = 0
+ option_vanilla = 0
+ option_match_types = 1
+ option_match_base_stats = 2
+ option_match_types_and_base_stats = 3
+ option_completely_random = 4
+
+
+class RandomizeLegendaryPokemon(Choice):
+ """Randomize Legendaries. Mew has been added as an encounter at the Vermilion dock truck.
+ Shuffle will shuffle the legendaries with each other. Static will shuffle them into other static Pokemon locations.
+ 'Any' will allow legendaries to appear anywhere based on wild and static randomization options, and their locations
+ will be randomized according to static Pokemon randomization options."""
+ display_name = "Randomize Legendary Pokemon"
+ default = 0
+ option_vanilla = 0
+ option_shuffle = 1
+ option_static = 2
+ option_any = 3
+
+
+class CatchEmAll(Choice):
+ """Guarantee all first evolution stage Pokemon are available, or all Pokemon of all stages.
+ Currently only has an effect if wild Pokemon are randomized."""
+ display_name = "Catch 'Em All"
+ default = 0
+ option_off = 0
+ alias_false = 0
+ option_first_stage = 1
+ option_all_pokemon = 2
+
+
+class RandomizeTrainerParties(Choice):
+ """Randomize enemy Pokemon encountered in trainer battles."""
+ display_name = "Randomize Trainer Parties"
+ default = 0
+ option_vanilla = 0
+ option_match_types = 1
+ option_match_base_stats = 2
+ option_match_types_and_base_stats = 3
+ option_completely_random = 4
+
+
+class TrainerLegendaries(Toggle):
+ """Allow legendary Pokemon in randomized trainer parties."""
+ display_name = "Trainer Legendaries"
+ default = 0
+
+
+class BlindTrainers(Range):
+ """Chance each frame that you are standing on a tile in a trainer's line of sight that they will fail to initiate a
+ battle. If you move into and out of their line of sight without stopping, this chance will only trigger once."""
+ display_name = "Blind Trainers"
+ range_start = 0
+ range_end = 100
+ default = 0
+
+
+class MinimumStepsBetweenEncounters(Range):
+ """Minimum number of steps between wild Pokemon encounters."""
+ display_name = "Minimum Steps Between Encounters"
+ default = 3
+ range_start = 0
+ range_end = 255
+
+
+class RandomizePokemonStats(Choice):
+ """Randomize base stats for each Pokemon. Shuffle will shuffle the 5 base stat values amongst each other. Randomize
+ will completely randomize each stat, but will still add up to the same base stat total."""
+ display_name = "Randomize Pokemon Stats"
+ default = 0
+ option_vanilla = 0
+ option_shuffle = 1
+ option_randomize = 2
+
+
+class RandomizePokemonCatchRates(Toggle):
+ """Randomize the catch rate for each Pokemon."""
+ display_name = "Randomize Catch Rates"
+ default = 0
+
+
+class MinimumCatchRate(Range):
+ """Minimum catch rate for each Pokemon. If randomize_catch_rates is on, this will be the minimum value that can be
+ chosen. Otherwise, it will raise any Pokemon's catch rate up to this value if its normal catch rate is lower."""
+ display_name = "Minimum Catch Rate"
+ range_start = 1
+ range_end = 255
+ default = 3
+
+
+class RandomizePokemonMovesets(Choice):
+ """Randomize the moves learned by Pokemon. prefer_types will prefer moves that match the type of the Pokemon."""
+ display_name = "Randomize Pokemon Movesets"
+ option_vanilla = 0
+ option_prefer_types = 1
+ option_completely_random = 2
+ default = 0
+
+
+class StartWithFourMoves(Toggle):
+ """If movesets are randomized, this will give all Pokemon 4 starting moves."""
+ display_name = "Start With Four Moves"
+ default = 0
+
+
+class TMCompatibility(Choice):
+ """Randomize which Pokemon can learn each TM. prefer_types: 90% chance if Pokemon's type matches the move,
+ 50% chance if move is Normal type and the Pokemon is not, and 25% chance otherwise. Pokemon will retain the same
+ TM compatibility when they evolve if the evolved form has the same type(s). Mew will always be able to learn
+ every TM."""
+ display_name = "TM Compatibility"
+ default = 0
+ option_vanilla = 0
+ option_prefer_types = 1
+ option_completely_random = 2
+ option_full_compatibility = 3
+
+
+class HMCompatibility(Choice):
+ """Randomize which Pokemon can learn each HM. prefer_types: 100% chance if Pokemon's type matches the move,
+ 75% chance if move is Normal type and the Pokemon is not, and 25% chance otherwise. Pokemon will retain the same
+ HM compatibility when they evolve if the evolved form has the same type(s). Mew will always be able to learn
+ every HM."""
+ display_name = "HM Compatibility"
+ default = 0
+ option_vanilla = 0
+ option_prefer_types = 1
+ option_completely_random = 2
+ option_full_compatibility = 3
+
+
+class RandomizePokemonTypes(Choice):
+ """Randomize the types of each Pokemon. Follow Evolutions will ensure Pokemon's types remain the same when evolving
+ (except possibly gaining a type)."""
+ display_name = "Pokemon Types"
+ option_vanilla = 0
+ option_follow_evolutions = 1
+ option_randomize = 2
+ default = 0
+
+
+class SecondaryTypeChance(SpecialRange):
+ """If randomize_pokemon_types is on, this is the chance each Pokemon will have a secondary type. If follow_evolutions
+ is selected, it is the chance a second type will be added at each evolution stage. vanilla will give secondary types
+ to Pokemon that normally have a secondary type."""
+ display_name = "Secondary Type Chance"
+ range_start = -1
+ range_end = 100
+ default = -1
+ special_range_names = {
+ "vanilla": -1
+ }
+
+
+class RandomizeTypeChartTypes(Choice):
+ """The game's type chart consists of 3 columns: attacking type, defending type, and type effectiveness.
+ Matchups that have regular type effectiveness are not in the chart. Shuffle will shuffle the attacking types
+ across the attacking type column and the defending types across the defending type column (so for example Normal
+ type will still have exactly 2 types that it receives non-regular damage from, and 2 types it deals non-regular
+ damage to). Randomize will randomize each type in both columns to any random type."""
+ display_name = "Randomize Type Chart Types"
+ option_vanilla = 0
+ option_shuffle = 1
+ option_randomize = 2
+ default = 0
+
+
+class RandomizeTypeChartTypeEffectiveness(Choice):
+ """The game's type chart consists of 3 columns: attacking type, defending type, and type effectiveness.
+ Matchups that have regular type effectiveness are not in the chart. Shuffle will shuffle the type effectiveness
+ across the type effectiveness column (so for example there will always be 6 type immunities). Randomize will
+ randomize each entry in the table to no effect, not very effective, or super effective; with no effect occurring
+ at a low chance. Chaos will randomize the values to anywhere between 0% and 200% damage, in 10% increments."""
+ display_name = "Randomize Type Chart Type Effectiveness"
+ option_vanilla = 0
+ option_shuffle = 1
+ option_randomize = 2
+ option_chaos = 3
+ default = 0
+
+
+class SafariZoneNormalBattles(Toggle):
+ """Change the Safari Zone to have standard wild pokemon battles."""
+ display_name = "Safari Zone Normal Battles"
+ default = 0
+
+
+class NormalizeEncounterChances(Toggle):
+ """Each wild encounter table has 10 slots for Pokemon. Normally the chance for each being chosen ranges from
+ 19.9% to 1.2%. Turn this on to normalize them all to 10% each."""
+ display_name = "Normalize Encounter Chances"
+ default = 0
+
+
+class ReusableTMs(Toggle):
+ """Makes TMs reusable, so they will not be consumed upon use."""
+ display_name = "Reusable TMs"
+ default = 0
+
+
+class StartingMoney(Range):
+ """The amount of money you start with."""
+ display_name = "Starting Money"
+ default = 3000
+ range_start = 0
+ range_end = 999999
+
+
+pokemon_rb_options = {
+ "game_version": GameVersion,
+ "trainer_name": TrainerName,
+ "rival_name": RivalName,
+ #"goal": Goal,
+ "elite_four_condition": EliteFourCondition,
+ "victory_road_condition": VictoryRoadCondition,
+ "viridian_gym_condition": ViridianGymCondition,
+ "cerulean_cave_condition": CeruleanCaveCondition,
+ "second_fossil_check_condition": SecondFossilCheckCondition,
+ "badgesanity": BadgeSanity,
+ "old_man": OldMan,
+ "tea": Tea,
+ "extra_key_items": ExtraKeyItems,
+ "extra_strength_boulders": ExtraStrengthBoulders,
+ "require_item_finder": RequireItemFinder,
+ "randomize_hidden_items": RandomizeHiddenItems,
+ "badges_needed_for_hm_moves": BadgesNeededForHMMoves,
+ "free_fly_location": FreeFlyLocation,
+ "oaks_aide_rt_2": OaksAidRt2,
+ "oaks_aide_rt_11": OaksAidRt11,
+ "oaks_aide_rt_15": OaksAidRt15,
+ "blind_trainers": BlindTrainers,
+ "minimum_steps_between_encounters": MinimumStepsBetweenEncounters,
+ "exp_modifier": ExpModifier,
+ "randomize_wild_pokemon": RandomizeWildPokemon,
+ "randomize_starter_pokemon": RandomizeStarterPokemon,
+ "randomize_static_pokemon": RandomizeStaticPokemon,
+ "randomize_legendary_pokemon": RandomizeLegendaryPokemon,
+ "catch_em_all": CatchEmAll,
+ "randomize_pokemon_stats": RandomizePokemonStats,
+ "randomize_pokemon_catch_rates": RandomizePokemonCatchRates,
+ "minimum_catch_rate": MinimumCatchRate,
+ "randomize_trainer_parties": RandomizeTrainerParties,
+ "trainer_legendaries": TrainerLegendaries,
+ "randomize_pokemon_movesets": RandomizePokemonMovesets,
+ "start_with_four_moves": StartWithFourMoves,
+ "tm_compatibility": TMCompatibility,
+ "hm_compatibility": HMCompatibility,
+ "randomize_pokemon_types": RandomizePokemonTypes,
+ "secondary_type_chance": SecondaryTypeChance,
+ "randomize_type_matchup_types": RandomizeTypeChartTypes,
+ "randomize_type_matchup_type_effectiveness": RandomizeTypeChartTypeEffectiveness,
+ "safari_zone_normal_battles": SafariZoneNormalBattles,
+ "normalize_encounter_chances": NormalizeEncounterChances,
+ "reusable_tms": ReusableTMs,
+ "starting_money": StartingMoney,
+}
\ No newline at end of file
diff --git a/worlds/pokemon_rb/poke_data.py b/worlds/pokemon_rb/poke_data.py
new file mode 100644
index 0000000000..75222d570f
--- /dev/null
+++ b/worlds/pokemon_rb/poke_data.py
@@ -0,0 +1,1212 @@
+id_to_mon = {1: 'Rhydon', 2: 'Kangaskhan', 3: 'Nidoran M', 4: 'Clefairy', 5: 'Spearow', 6: 'Voltorb', 7: 'Nidoking',
+ 8: 'Slowbro', 9: 'Ivysaur', 10: 'Exeggutor', 11: 'Lickitung', 12: 'Exeggcute', 13: 'Grimer', 14: 'Gengar',
+ 15: 'Nidoran F', 16: 'Nidoqueen', 17: 'Cubone', 18: 'Rhyhorn', 19: 'Lapras', 20: 'Arcanine', 21: 'Mew',
+ 22: 'Gyarados', 23: 'Shellder', 24: 'Tentacool', 25: 'Gastly', 26: 'Scyther', 27: 'Staryu',
+ 28: 'Blastoise', 29: 'Pinsir', 30: 'Tangela', 33: 'Growlithe', 34: 'Onix', 35: 'Fearow', 36: 'Pidgey',
+ 37: 'Slowpoke', 38: 'Kadabra', 39: 'Graveler', 40: 'Chansey', 41: 'Machoke', 42: 'Mr Mime',
+ 43: 'Hitmonlee', 44: 'Hitmonchan', 45: 'Arbok', 46: 'Parasect', 47: 'Psyduck', 48: 'Drowzee', 49: 'Golem',
+ 51: 'Magmar', 53: 'Electabuzz', 54: 'Magneton', 55: 'Koffing', 57: 'Mankey', 58: 'Seel', 59: 'Diglett',
+ 60: 'Tauros', 64: 'Farfetchd', 65: 'Venonat', 66: 'Dragonite', 70: 'Doduo', 71: 'Poliwag', 72: 'Jynx',
+ 73: 'Moltres', 74: 'Articuno', 75: 'Zapdos', 76: 'Ditto', 77: 'Meowth', 78: 'Krabby', 82: 'Vulpix',
+ 83: 'Ninetales', 84: 'Pikachu', 85: 'Raichu', 88: 'Dratini', 89: 'Dragonair', 90: 'Kabuto', 91: 'Kabutops',
+ 92: 'Horsea', 93: 'Seadra', 96: 'Sandshrew', 97: 'Sandslash', 98: 'Omanyte', 99: 'Omastar',
+ 100: 'Jigglypuff', 101: 'Wigglytuff', 102: 'Eevee', 103: 'Flareon', 104: 'Jolteon', 105: 'Vaporeon',
+ 106: 'Machop', 107: 'Zubat', 108: 'Ekans', 109: 'Paras', 110: 'Poliwhirl', 111: 'Poliwrath', 112: 'Weedle',
+ 113: 'Kakuna', 114: 'Beedrill', 116: 'Dodrio', 117: 'Primeape', 118: 'Dugtrio', 119: 'Venomoth',
+ 120: 'Dewgong', 123: 'Caterpie', 124: 'Metapod', 125: 'Butterfree', 126: 'Machamp', 128: 'Golduck',
+ 129: 'Hypno', 130: 'Golbat', 131: 'Mewtwo', 132: 'Snorlax', 133: 'Magikarp', 136: 'Muk', 138: 'Kingler',
+ 139: 'Cloyster', 141: 'Electrode', 142: 'Clefable', 143: 'Weezing', 144: 'Persian', 145: 'Marowak',
+ 147: 'Haunter', 148: 'Abra', 149: 'Alakazam', 150: 'Pidgeotto', 151: 'Pidgeot', 152: 'Starmie',
+ 153: 'Bulbasaur', 154: 'Venusaur', 155: 'Tentacruel', 157: 'Goldeen', 158: 'Seaking', 163: 'Ponyta',
+ 164: 'Rapidash', 165: 'Rattata', 166: 'Raticate', 167: 'Nidorino', 168: 'Nidorina', 169: 'Geodude',
+ 170: 'Porygon', 171: 'Aerodactyl', 173: 'Magnemite', 176: 'Charmander', 177: 'Squirtle', 178: 'Charmeleon',
+ 179: 'Wartortle', 180: 'Charizard', 185: 'Oddish', 186: 'Gloom', 187: 'Vileplume', 188: 'Bellsprout',
+ 189: 'Weepinbell', 190: 'Victreebel'}
+
+
+pokemon_dex = {
+ 'Bulbasaur': 1, 'Ivysaur': 2, 'Venusaur': 3, 'Charmander': 4, 'Charmeleon': 5, 'Charizard': 6, 'Squirtle': 7,
+ 'Wartortle': 8, 'Blastoise': 9, 'Caterpie': 10, 'Metapod': 11, 'Butterfree': 12, 'Weedle': 13, 'Kakuna': 14,
+ 'Beedrill': 15, 'Pidgey': 16, 'Pidgeotto': 17, 'Pidgeot': 18, 'Rattata': 19, 'Raticate': 20, 'Spearow': 21,
+ 'Fearow': 22, 'Ekans': 23, 'Arbok': 24, 'Pikachu': 25, 'Raichu': 26, 'Sandshrew': 27, 'Sandslash': 28,
+ 'Nidoran F': 29, 'Nidorina': 30, 'Nidoqueen': 31, 'Nidoran M': 32, 'Nidorino': 33, 'Nidoking': 34, 'Clefairy': 35,
+ 'Clefable': 36, 'Vulpix': 37, 'Ninetales': 38, 'Jigglypuff': 39, 'Wigglytuff': 40, 'Zubat': 41, 'Golbat': 42,
+ 'Oddish': 43, 'Gloom': 44, 'Vileplume': 45, 'Paras': 46, 'Parasect': 47, 'Venonat': 48, 'Venomoth': 49,
+ 'Diglett': 50, 'Dugtrio': 51, 'Meowth': 52, 'Persian': 53, 'Psyduck': 54, 'Golduck': 55, 'Mankey': 56,
+ 'Primeape': 57, 'Growlithe': 58, 'Arcanine': 59, 'Poliwag': 60, 'Poliwhirl': 61, 'Poliwrath': 62, 'Abra': 63,
+ 'Kadabra': 64, 'Alakazam': 65, 'Machop': 66, 'Machoke': 67, 'Machamp': 68, 'Bellsprout': 69, 'Weepinbell': 70,
+ 'Victreebel': 71, 'Tentacool': 72, 'Tentacruel': 73, 'Geodude': 74, 'Graveler': 75, 'Golem': 76, 'Ponyta': 77,
+ 'Rapidash': 78, 'Slowpoke': 79, 'Slowbro': 80, 'Magnemite': 81, 'Magneton': 82, 'Farfetchd': 83, 'Doduo': 84,
+ 'Dodrio': 85, 'Seel': 86, 'Dewgong': 87, 'Grimer': 88, 'Muk': 89, 'Shellder': 90, 'Cloyster': 91, 'Gastly': 92,
+ 'Haunter': 93, 'Gengar': 94, 'Onix': 95, 'Drowzee': 96, 'Hypno': 97, 'Krabby': 98, 'Kingler': 99, 'Voltorb': 100,
+ 'Electrode': 101, 'Exeggcute': 102, 'Exeggutor': 103, 'Cubone': 104, 'Marowak': 105, 'Hitmonlee': 106,
+ 'Hitmonchan': 107, 'Lickitung': 108, 'Koffing': 109, 'Weezing': 110, 'Rhyhorn': 111, 'Rhydon': 112, 'Chansey': 113,
+ 'Tangela': 114, 'Kangaskhan': 115, 'Horsea': 116, 'Seadra': 117, 'Goldeen': 118, 'Seaking': 119, 'Staryu': 120,
+ 'Starmie': 121, 'Mr Mime': 122, 'Scyther': 123, 'Jynx': 124, 'Electabuzz': 125, 'Magmar': 126, 'Pinsir': 127,
+ 'Tauros': 128, 'Magikarp': 129, 'Gyarados': 130, 'Lapras': 131, 'Ditto': 132, 'Eevee': 133, 'Vaporeon': 134,
+ 'Jolteon': 135, 'Flareon': 136, 'Porygon': 137, 'Omanyte': 138, 'Omastar': 139, 'Kabuto': 140, 'Kabutops': 141,
+ 'Aerodactyl': 142, 'Snorlax': 143, 'Articuno': 144, 'Zapdos': 145, 'Moltres': 146, 'Dratini': 147, 'Dragonair': 148,
+ 'Dragonite': 149, 'Mewtwo': 150, 'Mew': 151
+}
+
+
+type_ids = {
+ "Normal": 0x0,
+ "Fighting": 0x1,
+ "Flying": 0x2,
+ "Poison": 0x3,
+ "Ground": 0x4,
+ "Rock": 0x5,
+ "Bug": 0x7,
+ "Ghost": 0x8,
+ "Fire": 0x14,
+ "Water": 0x15,
+ "Grass": 0x16,
+ "Electric": 0x17,
+ "Psychic": 0x18,
+ "Ice": 0x19,
+ "Dragon": 0x1A
+}
+type_names = {
+ 0x0: "Normal",
+ 0x1: "Fighting",
+ 0x2: "Flying",
+ 0x3: "Poison",
+ 0x4: "Ground",
+ 0x5: "Rock",
+ 0x7: "Bug",
+ 0x8: "Ghost",
+ 0x14: "Fire",
+ 0x15: "Water",
+ 0x16: "Grass",
+ 0x17: "Electric",
+ 0x18: "Psychic",
+ 0x19: "Ice",
+ 0x1a: "Dragon"
+}
+
+type_chart = [
+ ["Water", "Fire", 20],
+ ["Fire", "Grass", 20],
+ ["Fire", "Ice", 20],
+ ["Grass", "Water", 20],
+ ["Electric", "Water", 20],
+ ["Water", "Rock", 20],
+ ["Ground", "Flying", 0],
+ ["Water", "Water", 5],
+ ["Fire", "Fire", 5],
+ ["Electric", "Electric", 5],
+ ["Ice", "Ice", 5],
+ ["Grass", "Grass", 5],
+ ["Psychic", "Psychic", 5],
+ ["Fire", "Water", 5],
+ ["Grass", "Fire", 5],
+ ["Water", "Grass", 5],
+ ["Electric", "Grass", 5],
+ ["Normal", "Rock", 5],
+ ["Normal", "Ghost", 0],
+ ["Ghost", "Ghost", 20],
+ ["Fire", "Bug", 20],
+ ["Fire", "Rock", 5],
+ ["Water", "Ground", 20],
+ ["Electric", "Ground", 0],
+ ["Electric", "Flying", 20],
+ ["Grass", "Ground", 20],
+ ["Grass", "Bug", 5],
+ ["Grass", "Poison", 5],
+ ["Grass", "Rock", 20],
+ ["Grass", "Flying", 5],
+ ["Ice", "Water", 5],
+ ["Ice", "Grass", 20],
+ ["Ice", "Ground", 20],
+ ["Ice", "Flying", 20],
+ ["Fighting", "Normal", 20],
+ ["Fighting", "Poison", 5],
+ ["Fighting", "Flying", 5],
+ ["Fighting", "Psychic", 5],
+ ["Fighting", "Bug", 5],
+ ["Fighting", "Rock", 20],
+ ["Fighting", "Ice", 20],
+ ["Fighting", "Ghost", 0],
+ ["Poison", "Grass", 20],
+ ["Poison", "Poison", 5],
+ ["Poison", "Ground", 5],
+ ["Poison", "Bug", 20],
+ ["Poison", "Rock", 5],
+ ["Poison", "Ghost", 5],
+ ["Ground", "Fire", 20],
+ ["Ground", "Electric", 20],
+ ["Ground", "Grass", 5],
+ ["Ground", "Bug", 5],
+ ["Ground", "Rock", 20],
+ ["Ground", "Poison", 20],
+ ["Flying", "Electric", 5],
+ ["Flying", "Fighting", 20],
+ ["Flying", "Bug", 20],
+ ["Flying", "Grass", 20],
+ ["Flying", "Rock", 5],
+ ["Psychic", "Fighting", 20],
+ ["Psychic", "Poison", 20],
+ ["Bug", "Fire", 5],
+ ["Bug", "Grass", 20],
+ ["Bug", "Fighting", 5],
+ ["Bug", "Flying", 5],
+ ["Bug", "Psychic", 20],
+ ["Bug", "Ghost", 5],
+ ["Bug", "Poison", 20],
+ ["Rock", "Fire", 20],
+ ["Rock", "Fighting", 5],
+ ["Rock", "Ground", 5],
+ ["Rock", "Flying", 20],
+ ["Rock", "Bug", 20],
+ ["Rock", "Ice", 20],
+ ["Ghost", "Normal", 0],
+ ["Ghost", "Psychic", 0],
+ ["Fire", "Dragon", 5],
+ ["Water", "Dragon", 5],
+ ["Electric", "Dragon", 5],
+ ["Grass", "Dragon", 5],
+ ["Ice", "Dragon", 20],
+ ["Dragon", "Dragon", 20]
+]
+
+pokemon_data = {
+ 'Bulbasaur': {'id': 153, 'dex': 1, 'hp': 45, 'atk': 49, 'def': 49, 'spd': 45, 'spc': 65, 'type1': 'Grass',
+ 'type2': 'Poison', 'catch rate': 45, 'base exp': 64, 'start move 1': 'Tackle',
+ 'start move 2': 'Growl', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xa4\x038\xc0\x03\x08\x06')},
+ 'Ivysaur': {'id': 9, 'dex': 2, 'hp': 60, 'atk': 62, 'def': 63, 'spd': 60, 'spc': 80, 'type1': 'Grass',
+ 'type2': 'Poison', 'catch rate': 45, 'base exp': 141, 'start move 1': 'Tackle', 'start move 2': 'Growl',
+ 'start move 3': 'Leech Seed', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xa4\x038\xc0\x03\x08\x06')},
+ 'Venusaur': {'id': 154, 'dex': 3, 'hp': 80, 'atk': 82, 'def': 83, 'spd': 80, 'spc': 100, 'type1': 'Grass',
+ 'type2': 'Poison', 'catch rate': 45, 'base exp': 208, 'start move 1': 'Tackle',
+ 'start move 2': 'Growl', 'start move 3': 'Leech Seed', 'start move 4': 'Vine Whip', 'growth rate': 3,
+ 'tms': bytearray(b'\xa4C8\xc0\x03\x08\x06')},
+ 'Charmander': {'id': 176, 'dex': 4, 'hp': 39, 'atk': 52, 'def': 43, 'spd': 65, 'spc': 50, 'type1': 'Fire',
+ 'type2': 'Fire', 'catch rate': 45, 'base exp': 65, 'start move 1': 'Scratch',
+ 'start move 2': 'Growl', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xb5\x03O\xc8\xe3\x08&')},
+ 'Charmeleon': {'id': 178, 'dex': 5, 'hp': 58, 'atk': 64, 'def': 58, 'spd': 80, 'spc': 65, 'type1': 'Fire',
+ 'type2': 'Fire', 'catch rate': 45, 'base exp': 142, 'start move 1': 'Scratch',
+ 'start move 2': 'Growl', 'start move 3': 'Ember', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xb5\x03O\xc8\xe3\x08&')},
+ 'Charizard': {'id': 180, 'dex': 6, 'hp': 78, 'atk': 84, 'def': 78, 'spd': 100, 'spc': 85, 'type1': 'Fire',
+ 'type2': 'Flying', 'catch rate': 45, 'base exp': 209, 'start move 1': 'Scratch',
+ 'start move 2': 'Growl', 'start move 3': 'Ember', 'start move 4': 'Leer', 'growth rate': 3,
+ 'tms': bytearray(b'\xb5CO\xce\xe3\x08&')},
+ 'Squirtle': {'id': 177, 'dex': 7, 'hp': 44, 'atk': 48, 'def': 65, 'spd': 43, 'spc': 50, 'type1': 'Water',
+ 'type2': 'Water', 'catch rate': 45, 'base exp': 66, 'start move 1': 'Tackle',
+ 'start move 2': 'Tail Whip', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xb1?\x0f\xc8\x83\x082')},
+ 'Wartortle': {'id': 179, 'dex': 8, 'hp': 59, 'atk': 63, 'def': 80, 'spd': 58, 'spc': 65, 'type1': 'Water',
+ 'type2': 'Water', 'catch rate': 45, 'base exp': 143, 'start move 1': 'Tackle',
+ 'start move 2': 'Tail Whip', 'start move 3': 'Bubble', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xb1?\x0f\xc8\x83\x082')},
+ 'Blastoise': {'id': 28, 'dex': 9, 'hp': 79, 'atk': 83, 'def': 100, 'spd': 78, 'spc': 85, 'type1': 'Water',
+ 'type2': 'Water', 'catch rate': 45, 'base exp': 210, 'start move 1': 'Tackle',
+ 'start move 2': 'Tail Whip', 'start move 3': 'Bubble', 'start move 4': 'Water Gun', 'growth rate': 3,
+ 'tms': bytearray(b'\xb1\x7f\x0f\xce\x83\x082')},
+ 'Caterpie': {'id': 123, 'dex': 10, 'hp': 45, 'atk': 30, 'def': 35, 'spd': 45, 'spc': 20, 'type1': 'Bug',
+ 'type2': 'Bug', 'catch rate': 255, 'base exp': 53, 'start move 1': 'Tackle',
+ 'start move 2': 'String Shot', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\x00\x00\x00\x00\x00\x00\x00')},
+ 'Metapod': {'id': 124, 'dex': 11, 'hp': 50, 'atk': 20, 'def': 55, 'spd': 30, 'spc': 25, 'type1': 'Bug',
+ 'type2': 'Bug', 'catch rate': 120, 'base exp': 72, 'start move 1': 'Harden', 'start move 2': 'No Move',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\x00\x00\x00\x00\x00\x00\x00')},
+ 'Butterfree': {'id': 125, 'dex': 12, 'hp': 60, 'atk': 45, 'def': 50, 'spd': 70, 'spc': 80, 'type1': 'Bug',
+ 'type2': 'Flying', 'catch rate': 45, 'base exp': 160, 'start move 1': 'Confusion',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'*C8\xf0C(\x02')},
+ 'Weedle': {'id': 112, 'dex': 13, 'hp': 40, 'atk': 35, 'def': 30, 'spd': 50, 'spc': 20, 'type1': 'Bug',
+ 'type2': 'Poison', 'catch rate': 255, 'base exp': 52, 'start move 1': 'Poison Sting',
+ 'start move 2': 'String Shot', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\x00\x00\x00\x00\x00\x00\x00')},
+ 'Kakuna': {'id': 113, 'dex': 14, 'hp': 45, 'atk': 25, 'def': 50, 'spd': 35, 'spc': 25, 'type1': 'Bug',
+ 'type2': 'Poison', 'catch rate': 120, 'base exp': 71, 'start move 1': 'Harden',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\x00\x00\x00\x00\x00\x00\x00')},
+ 'Beedrill': {'id': 114, 'dex': 15, 'hp': 65, 'atk': 80, 'def': 40, 'spd': 75, 'spc': 45, 'type1': 'Bug',
+ 'type2': 'Poison', 'catch rate': 45, 'base exp': 159, 'start move 1': 'Fury Attack',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'$C\x18\xc0\xc3\x08\x06')},
+ 'Pidgey': {'id': 36, 'dex': 16, 'hp': 40, 'atk': 45, 'def': 40, 'spd': 56, 'spc': 35, 'type1': 'Normal',
+ 'type2': 'Flying', 'catch rate': 255, 'base exp': 55, 'start move 1': 'Gust', 'start move 2': 'No Move',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'*\x03\x08\xc0C\x0c\n')},
+ 'Pidgeotto': {'id': 150, 'dex': 17, 'hp': 63, 'atk': 60, 'def': 55, 'spd': 71, 'spc': 50, 'type1': 'Normal',
+ 'type2': 'Flying', 'catch rate': 120, 'base exp': 113, 'start move 1': 'Gust',
+ 'start move 2': 'Sand Attack', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'*\x03\x08\xc0C\x0c\n')},
+ 'Pidgeot': {'id': 151, 'dex': 18, 'hp': 83, 'atk': 80, 'def': 75, 'spd': 91, 'spc': 70, 'type1': 'Normal',
+ 'type2': 'Flying', 'catch rate': 45, 'base exp': 172, 'start move 1': 'Gust',
+ 'start move 2': 'Sand Attack', 'start move 3': 'Quick Attack', 'start move 4': 'No Move',
+ 'growth rate': 3, 'tms': bytearray(b'*C\x08\xc0C\x0c\n')},
+ 'Rattata': {'id': 165, 'dex': 19, 'hp': 30, 'atk': 56, 'def': 35, 'spd': 72, 'spc': 25, 'type1': 'Normal',
+ 'type2': 'Normal', 'catch rate': 255, 'base exp': 57, 'start move 1': 'Tackle',
+ 'start move 2': 'Tail Whip', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa0/\x88\xc9\xc2\x08\x02')},
+ 'Raticate': {'id': 166, 'dex': 20, 'hp': 55, 'atk': 81, 'def': 60, 'spd': 97, 'spc': 50, 'type1': 'Normal',
+ 'type2': 'Normal', 'catch rate': 90, 'base exp': 116, 'start move 1': 'Tackle',
+ 'start move 2': 'Tail Whip', 'start move 3': 'Quick Attack', 'start move 4': 'No Move',
+ 'growth rate': 0, 'tms': bytearray(b'\xa0\x7f\x88\xc9\xc2\x08\x02')},
+ 'Spearow': {'id': 5, 'dex': 21, 'hp': 40, 'atk': 60, 'def': 30, 'spd': 70, 'spc': 31, 'type1': 'Normal',
+ 'type2': 'Flying', 'catch rate': 255, 'base exp': 58, 'start move 1': 'Peck', 'start move 2': 'Growl',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'*\x03\x08\xc0B\x0c\n')},
+ 'Fearow': {'id': 35, 'dex': 22, 'hp': 65, 'atk': 90, 'def': 65, 'spd': 100, 'spc': 61, 'type1': 'Normal',
+ 'type2': 'Flying', 'catch rate': 90, 'base exp': 162, 'start move 1': 'Peck', 'start move 2': 'Growl',
+ 'start move 3': 'Leer', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'*C\x08\xc0B\x0c\n')},
+ 'Ekans': {'id': 108, 'dex': 23, 'hp': 35, 'atk': 60, 'def': 44, 'spd': 55, 'spc': 40, 'type1': 'Poison',
+ 'type2': 'Poison', 'catch rate': 255, 'base exp': 62, 'start move 1': 'Wrap', 'start move 2': 'Leer',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa0\x03\x18\xce\x82\x88"')},
+ 'Arbok': {'id': 45, 'dex': 24, 'hp': 60, 'atk': 85, 'def': 69, 'spd': 80, 'spc': 65, 'type1': 'Poison',
+ 'type2': 'Poison', 'catch rate': 90, 'base exp': 147, 'start move 1': 'Wrap', 'start move 2': 'Leer',
+ 'start move 3': 'Poison Sting', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa0C\x18\xce\x82\x88"')},
+ 'Pikachu': {'id': 84, 'dex': 25, 'hp': 35, 'atk': 55, 'def': 30, 'spd': 90, 'spc': 50, 'type1': 'Electric',
+ 'type2': 'Electric', 'catch rate': 190, 'base exp': 82, 'start move 1': 'Thundershock',
+ 'start move 2': 'Growl', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xb1\x83\x8d\xc1\xc3\x18B')},
+ 'Raichu': {'id': 85, 'dex': 26, 'hp': 60, 'atk': 90, 'def': 55, 'spd': 100, 'spc': 90, 'type1': 'Electric',
+ 'type2': 'Electric', 'catch rate': 75, 'base exp': 122, 'start move 1': 'Thundershock',
+ 'start move 2': 'Growl', 'start move 3': 'Thunder Wave', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xb1\xc3\x8d\xc1\xc3\x18B')},
+ 'Sandshrew': {'id': 96, 'dex': 27, 'hp': 50, 'atk': 75, 'def': 85, 'spd': 40, 'spc': 30, 'type1': 'Ground',
+ 'type2': 'Ground', 'catch rate': 255, 'base exp': 93, 'start move 1': 'Scratch',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa4\x03\r\xce\xc2\x88&')},
+ 'Sandslash': {'id': 97, 'dex': 28, 'hp': 75, 'atk': 100, 'def': 110, 'spd': 65, 'spc': 55, 'type1': 'Ground',
+ 'type2': 'Ground', 'catch rate': 90, 'base exp': 163, 'start move 1': 'Scratch',
+ 'start move 2': 'Sand Attack', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa4C\r\xce\xc2\x88&')},
+ 'Nidoran F': {'id': 15, 'dex': 29, 'hp': 55, 'atk': 47, 'def': 52, 'spd': 41, 'spc': 40, 'type1': 'Poison',
+ 'type2': 'Poison', 'catch rate': 235, 'base exp': 59, 'start move 1': 'Growl',
+ 'start move 2': 'Tackle', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xa0#\x88\xc1\x83\x08\x02')},
+ 'Nidorina': {'id': 168, 'dex': 30, 'hp': 70, 'atk': 62, 'def': 67, 'spd': 56, 'spc': 55, 'type1': 'Poison',
+ 'type2': 'Poison', 'catch rate': 120, 'base exp': 117, 'start move 1': 'Growl',
+ 'start move 2': 'Tackle', 'start move 3': 'Scratch', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xe0?\x88\xc1\x83\x08\x02')},
+ 'Nidoqueen': {'id': 16, 'dex': 31, 'hp': 90, 'atk': 82, 'def': 87, 'spd': 76, 'spc': 75, 'type1': 'Poison',
+ 'type2': 'Ground', 'catch rate': 45, 'base exp': 194, 'start move 1': 'Tackle',
+ 'start move 2': 'Scratch', 'start move 3': 'Tail Whip', 'start move 4': 'Body Slam', 'growth rate': 3,
+ 'tms': bytearray(b'\xf1\xff\x8f\xc7\xa3\x882')},
+ 'Nidoran M': {'id': 3, 'dex': 32, 'hp': 46, 'atk': 57, 'def': 40, 'spd': 50, 'spc': 40, 'type1': 'Poison',
+ 'type2': 'Poison', 'catch rate': 235, 'base exp': 60, 'start move 1': 'Leer',
+ 'start move 2': 'Tackle', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xe0#\x88\xc1\x83\x08\x02')},
+ 'Nidorino': {'id': 167, 'dex': 33, 'hp': 61, 'atk': 72, 'def': 57, 'spd': 65, 'spc': 55, 'type1': 'Poison',
+ 'type2': 'Poison', 'catch rate': 120, 'base exp': 118, 'start move 1': 'Leer',
+ 'start move 2': 'Tackle', 'start move 3': 'Horn Attack', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xe0?\x88\xc1\x83\x08\x02')},
+ 'Nidoking': {'id': 7, 'dex': 34, 'hp': 81, 'atk': 92, 'def': 77, 'spd': 85, 'spc': 75, 'type1': 'Poison',
+ 'type2': 'Ground', 'catch rate': 45, 'base exp': 195, 'start move 1': 'Tackle',
+ 'start move 2': 'Horn Attack', 'start move 3': 'Poison Sting', 'start move 4': 'Thrash',
+ 'growth rate': 3, 'tms': bytearray(b'\xf1\xff\x8f\xc7\xa3\x882')},
+ 'Clefairy': {'id': 4, 'dex': 35, 'hp': 70, 'atk': 45, 'def': 48, 'spd': 35, 'spc': 60, 'type1': 'Normal',
+ 'type2': 'Normal', 'catch rate': 150, 'base exp': 68, 'start move 1': 'Pound', 'start move 2': 'Growl',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 4,
+ 'tms': bytearray(b'\xb1?\xaf\xf1\xa78c')},
+ 'Clefable': {'id': 142, 'dex': 36, 'hp': 95, 'atk': 70, 'def': 73, 'spd': 60, 'spc': 85, 'type1': 'Normal',
+ 'type2': 'Normal', 'catch rate': 25, 'base exp': 129, 'start move 1': 'Sing',
+ 'start move 2': 'Doubleslap', 'start move 3': 'Minimize', 'start move 4': 'Metronome',
+ 'growth rate': 4, 'tms': bytearray(b'\xb1\x7f\xaf\xf1\xa78c')},
+ 'Vulpix': {'id': 82, 'dex': 37, 'hp': 38, 'atk': 41, 'def': 40, 'spd': 65, 'spc': 65, 'type1': 'Fire',
+ 'type2': 'Fire', 'catch rate': 190, 'base exp': 63, 'start move 1': 'Ember', 'start move 2': 'Tail Whip',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa0\x03\x08\xc8\xe3\x08\x02')},
+ 'Ninetales': {'id': 83, 'dex': 38, 'hp': 73, 'atk': 76, 'def': 75, 'spd': 100, 'spc': 100, 'type1': 'Fire',
+ 'type2': 'Fire', 'catch rate': 75, 'base exp': 178, 'start move 1': 'Ember',
+ 'start move 2': 'Tail Whip', 'start move 3': 'Quick Attack', 'start move 4': 'Roar', 'growth rate': 0,
+ 'tms': bytearray(b'\xa0C\x08\xc8\xe3\x08\x02')},
+ 'Jigglypuff': {'id': 100, 'dex': 39, 'hp': 115, 'atk': 45, 'def': 20, 'spd': 20, 'spc': 25, 'type1': 'Normal',
+ 'type2': 'Normal', 'catch rate': 170, 'base exp': 76, 'start move 1': 'Sing',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 4,
+ 'tms': bytearray(b'\xb1?\xaf\xf1\xa38c')},
+ 'Wigglytuff': {'id': 101, 'dex': 40, 'hp': 140, 'atk': 70, 'def': 45, 'spd': 45, 'spc': 50, 'type1': 'Normal',
+ 'type2': 'Normal', 'catch rate': 50, 'base exp': 109, 'start move 1': 'Sing',
+ 'start move 2': 'Disable', 'start move 3': 'Defense Curl', 'start move 4': 'Doubleslap',
+ 'growth rate': 4, 'tms': bytearray(b'\xb1\x7f\xaf\xf1\xa38c')},
+ 'Zubat': {'id': 107, 'dex': 41, 'hp': 40, 'atk': 45, 'def': 35, 'spd': 55, 'spc': 40, 'type1': 'Poison',
+ 'type2': 'Flying', 'catch rate': 255, 'base exp': 54, 'start move 1': 'Leech Life',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'*\x03\x18\xc0B\x08\x02')},
+ 'Golbat': {'id': 130, 'dex': 42, 'hp': 75, 'atk': 80, 'def': 70, 'spd': 90, 'spc': 75, 'type1': 'Poison',
+ 'type2': 'Flying', 'catch rate': 90, 'base exp': 171, 'start move 1': 'Leech Life',
+ 'start move 2': 'Screech', 'start move 3': 'Bite', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'*C\x18\xc0B\x08\x02')},
+ 'Oddish': {'id': 185, 'dex': 43, 'hp': 45, 'atk': 50, 'def': 55, 'spd': 30, 'spc': 75, 'type1': 'Grass',
+ 'type2': 'Poison', 'catch rate': 255, 'base exp': 78, 'start move 1': 'Absorb',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'$\x038\xc0\x03\x08\x06')},
+ 'Gloom': {'id': 186, 'dex': 44, 'hp': 60, 'atk': 65, 'def': 70, 'spd': 40, 'spc': 85, 'type1': 'Grass',
+ 'type2': 'Poison', 'catch rate': 120, 'base exp': 132, 'start move 1': 'Absorb',
+ 'start move 2': 'Poisonpowder', 'start move 3': 'Stun Spore', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'$\x038\xc0\x03\x08\x06')},
+ 'Vileplume': {'id': 187, 'dex': 45, 'hp': 75, 'atk': 80, 'def': 85, 'spd': 50, 'spc': 100, 'type1': 'Grass',
+ 'type2': 'Poison', 'catch rate': 45, 'base exp': 184, 'start move 1': 'Stun Spore',
+ 'start move 2': 'Sleep Powder', 'start move 3': 'Acid', 'start move 4': 'Petal Dance',
+ 'growth rate': 3, 'tms': bytearray(b'\xa4C8\xc0\x03\x08\x06')},
+ 'Paras': {'id': 109, 'dex': 46, 'hp': 35, 'atk': 70, 'def': 55, 'spd': 25, 'spc': 55, 'type1': 'Bug',
+ 'type2': 'Grass', 'catch rate': 190, 'base exp': 70, 'start move 1': 'Scratch', 'start move 2': 'No Move',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa4\x038\xc8\x83\x08\x06')},
+ 'Parasect': {'id': 46, 'dex': 47, 'hp': 60, 'atk': 95, 'def': 80, 'spd': 30, 'spc': 80, 'type1': 'Bug',
+ 'type2': 'Grass', 'catch rate': 75, 'base exp': 128, 'start move 1': 'Scratch',
+ 'start move 2': 'Stun Spore', 'start move 3': 'Leech Life', 'start move 4': 'No Move',
+ 'growth rate': 0, 'tms': bytearray(b'\xa4C8\xc8\x83\x08\x06')},
+ 'Venonat': {'id': 65, 'dex': 48, 'hp': 60, 'atk': 55, 'def': 50, 'spd': 45, 'spc': 40, 'type1': 'Bug',
+ 'type2': 'Poison', 'catch rate': 190, 'base exp': 75, 'start move 1': 'Tackle',
+ 'start move 2': 'Disable', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b' \x038\xd0\x03(\x02')},
+ 'Venomoth': {'id': 119, 'dex': 49, 'hp': 70, 'atk': 65, 'def': 60, 'spd': 90, 'spc': 90, 'type1': 'Bug',
+ 'type2': 'Poison', 'catch rate': 75, 'base exp': 138, 'start move 1': 'Tackle',
+ 'start move 2': 'Disable', 'start move 3': 'Poisonpowder', 'start move 4': 'Leech Life',
+ 'growth rate': 0, 'tms': bytearray(b'*C8\xf0C(\x02')},
+ 'Diglett': {'id': 59, 'dex': 50, 'hp': 10, 'atk': 55, 'def': 25, 'spd': 95, 'spc': 45, 'type1': 'Ground',
+ 'type2': 'Ground', 'catch rate': 255, 'base exp': 81, 'start move 1': 'Scratch',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa0\x03\x08\xce\x02\x88\x02')},
+ 'Dugtrio': {'id': 118, 'dex': 51, 'hp': 35, 'atk': 80, 'def': 50, 'spd': 120, 'spc': 70, 'type1': 'Ground',
+ 'type2': 'Ground', 'catch rate': 50, 'base exp': 153, 'start move 1': 'Scratch',
+ 'start move 2': 'Growl', 'start move 3': 'Dig', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa0C\x08\xce\x02\x88\x02')},
+ 'Meowth': {'id': 77, 'dex': 52, 'hp': 40, 'atk': 45, 'def': 35, 'spd': 90, 'spc': 40, 'type1': 'Normal',
+ 'type2': 'Normal', 'catch rate': 255, 'base exp': 69, 'start move 1': 'Scratch', 'start move 2': 'Growl',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa0\x8f\x88\xc1\xc2\x08\x02')},
+ 'Persian': {'id': 144, 'dex': 53, 'hp': 65, 'atk': 70, 'def': 60, 'spd': 115, 'spc': 65, 'type1': 'Normal',
+ 'type2': 'Normal', 'catch rate': 90, 'base exp': 148, 'start move 1': 'Scratch',
+ 'start move 2': 'Growl', 'start move 3': 'Bite', 'start move 4': 'Screech', 'growth rate': 0,
+ 'tms': bytearray(b'\xa0\xcf\x88\xc1\xc2\x08\x02')},
+ 'Psyduck': {'id': 47, 'dex': 54, 'hp': 50, 'atk': 52, 'def': 48, 'spd': 55, 'spc': 50, 'type1': 'Water',
+ 'type2': 'Water', 'catch rate': 190, 'base exp': 80, 'start move 1': 'Scratch',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xb1\xbf\x0f\xc8\xc2\x082')},
+ 'Golduck': {'id': 128, 'dex': 55, 'hp': 80, 'atk': 82, 'def': 78, 'spd': 85, 'spc': 80, 'type1': 'Water',
+ 'type2': 'Water', 'catch rate': 75, 'base exp': 174, 'start move 1': 'Scratch',
+ 'start move 2': 'Tail Whip', 'start move 3': 'Disable', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xb1\xff\x0f\xc8\xc2\x082')},
+ 'Mankey': {'id': 57, 'dex': 56, 'hp': 40, 'atk': 80, 'def': 35, 'spd': 70, 'spc': 35, 'type1': 'Fighting',
+ 'type2': 'Fighting', 'catch rate': 190, 'base exp': 74, 'start move 1': 'Scratch',
+ 'start move 2': 'Leer', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xb1\x83\x8f\xc9\xc6\x88"')},
+ 'Primeape': {'id': 117, 'dex': 57, 'hp': 65, 'atk': 105, 'def': 60, 'spd': 95, 'spc': 60, 'type1': 'Fighting',
+ 'type2': 'Fighting', 'catch rate': 75, 'base exp': 149, 'start move 1': 'Scratch',
+ 'start move 2': 'Leer', 'start move 3': 'Karate Chop', 'start move 4': 'Fury Swipes', 'growth rate': 0,
+ 'tms': bytearray(b'\xb1\xc3\x8f\xc9\xc6\x88"')},
+ 'Growlithe': {'id': 33, 'dex': 58, 'hp': 55, 'atk': 70, 'def': 45, 'spd': 60, 'spc': 50, 'type1': 'Fire',
+ 'type2': 'Fire', 'catch rate': 190, 'base exp': 91, 'start move 1': 'Bite', 'start move 2': 'Roar',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 5,
+ 'tms': bytearray(b'\xa0\x03H\xc8\xe3\x08\x02')},
+ 'Arcanine': {'id': 20, 'dex': 59, 'hp': 90, 'atk': 110, 'def': 80, 'spd': 95, 'spc': 80, 'type1': 'Fire',
+ 'type2': 'Fire', 'catch rate': 75, 'base exp': 213, 'start move 1': 'Roar', 'start move 2': 'Ember',
+ 'start move 3': 'Leer', 'start move 4': 'Take Down', 'growth rate': 5,
+ 'tms': bytearray(b'\xa0CH\xe8\xe3\x08\x02')},
+ 'Poliwag': {'id': 71, 'dex': 60, 'hp': 40, 'atk': 50, 'def': 40, 'spd': 90, 'spc': 40, 'type1': 'Water',
+ 'type2': 'Water', 'catch rate': 255, 'base exp': 77, 'start move 1': 'Bubble',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xa0?\x08\xd0\x82(\x12')},
+ 'Poliwhirl': {'id': 110, 'dex': 61, 'hp': 65, 'atk': 65, 'def': 65, 'spd': 90, 'spc': 50, 'type1': 'Water',
+ 'type2': 'Water', 'catch rate': 120, 'base exp': 131, 'start move 1': 'Bubble',
+ 'start move 2': 'Hypnosis', 'start move 3': 'Water Gun', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xb1?\x0f\xd6\x86(2')},
+ 'Poliwrath': {'id': 111, 'dex': 62, 'hp': 90, 'atk': 85, 'def': 95, 'spd': 70, 'spc': 70, 'type1': 'Water',
+ 'type2': 'Fighting', 'catch rate': 45, 'base exp': 185, 'start move 1': 'Hypnosis',
+ 'start move 2': 'Water Gun', 'start move 3': 'Doubleslap', 'start move 4': 'Body Slam',
+ 'growth rate': 3, 'tms': bytearray(b'\xb1\x7f\x0f\xd6\x86(2')},
+ 'Abra': {'id': 148, 'dex': 63, 'hp': 25, 'atk': 20, 'def': 15, 'spd': 90, 'spc': 105, 'type1': 'Psychic',
+ 'type2': 'Psychic', 'catch rate': 200, 'base exp': 73, 'start move 1': 'Teleport',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xb1\x03\x0f\xf0\x878C')},
+ 'Kadabra': {'id': 38, 'dex': 64, 'hp': 40, 'atk': 35, 'def': 30, 'spd': 105, 'spc': 120, 'type1': 'Psychic',
+ 'type2': 'Psychic', 'catch rate': 100, 'base exp': 145, 'start move 1': 'Teleport',
+ 'start move 2': 'Confusion', 'start move 3': 'Disable', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xb1\x03\x0f\xf8\x878C')},
+ 'Alakazam': {'id': 149, 'dex': 65, 'hp': 55, 'atk': 50, 'def': 45, 'spd': 120, 'spc': 135, 'type1': 'Psychic',
+ 'type2': 'Psychic', 'catch rate': 50, 'base exp': 186, 'start move 1': 'Teleport',
+ 'start move 2': 'Confusion', 'start move 3': 'Disable', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xb1C\x0f\xf8\x878C')},
+ 'Machop': {'id': 106, 'dex': 66, 'hp': 70, 'atk': 80, 'def': 50, 'spd': 35, 'spc': 35, 'type1': 'Fighting',
+ 'type2': 'Fighting', 'catch rate': 180, 'base exp': 88, 'start move 1': 'Karate Chop',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xb1\x03\x0f\xce\xa6\x88"')},
+ 'Machoke': {'id': 41, 'dex': 67, 'hp': 80, 'atk': 100, 'def': 70, 'spd': 45, 'spc': 50, 'type1': 'Fighting',
+ 'type2': 'Fighting', 'catch rate': 90, 'base exp': 146, 'start move 1': 'Karate Chop',
+ 'start move 2': 'Low Kick', 'start move 3': 'Leer', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xb1\x03\x0f\xce\xa6\x88"')},
+ 'Machamp': {'id': 126, 'dex': 68, 'hp': 90, 'atk': 130, 'def': 80, 'spd': 55, 'spc': 65, 'type1': 'Fighting',
+ 'type2': 'Fighting', 'catch rate': 45, 'base exp': 193, 'start move 1': 'Karate Chop',
+ 'start move 2': 'Low Kick', 'start move 3': 'Leer', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xb1C\x0f\xce\xa6\x88"')},
+ 'Bellsprout': {'id': 188, 'dex': 69, 'hp': 50, 'atk': 75, 'def': 35, 'spd': 40, 'spc': 70, 'type1': 'Grass',
+ 'type2': 'Poison', 'catch rate': 255, 'base exp': 84, 'start move 1': 'Vine Whip',
+ 'start move 2': 'Growth', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'$\x038\xc0\x03\x08\x06')},
+ 'Weepinbell': {'id': 189, 'dex': 70, 'hp': 65, 'atk': 90, 'def': 50, 'spd': 55, 'spc': 85, 'type1': 'Grass',
+ 'type2': 'Poison', 'catch rate': 120, 'base exp': 151, 'start move 1': 'Vine Whip',
+ 'start move 2': 'Growth', 'start move 3': 'Wrap', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'$\x038\xc0\x03\x08\x06')},
+ 'Victreebel': {'id': 190, 'dex': 71, 'hp': 80, 'atk': 105, 'def': 65, 'spd': 70, 'spc': 100, 'type1': 'Grass',
+ 'type2': 'Poison', 'catch rate': 45, 'base exp': 191, 'start move 1': 'Sleep Powder',
+ 'start move 2': 'Stun Spore', 'start move 3': 'Acid', 'start move 4': 'Razor Leaf', 'growth rate': 3,
+ 'tms': bytearray(b'\xa4C8\xc0\x03\x08\x06')},
+ 'Tentacool': {'id': 24, 'dex': 72, 'hp': 40, 'atk': 40, 'def': 35, 'spd': 70, 'spc': 100, 'type1': 'Water',
+ 'type2': 'Poison', 'catch rate': 190, 'base exp': 105, 'start move 1': 'Acid',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 5,
+ 'tms': bytearray(b'$?\x18\xc0\x83\x08\x16')},
+ 'Tentacruel': {'id': 155, 'dex': 73, 'hp': 80, 'atk': 70, 'def': 65, 'spd': 100, 'spc': 120, 'type1': 'Water',
+ 'type2': 'Poison', 'catch rate': 60, 'base exp': 205, 'start move 1': 'Acid',
+ 'start move 2': 'Supersonic', 'start move 3': 'Wrap', 'start move 4': 'No Move', 'growth rate': 5,
+ 'tms': bytearray(b'$\x7f\x18\xc0\x83\x08\x16')},
+ 'Geodude': {'id': 169, 'dex': 74, 'hp': 40, 'atk': 80, 'def': 100, 'spd': 20, 'spc': 30, 'type1': 'Rock',
+ 'type2': 'Ground', 'catch rate': 255, 'base exp': 86, 'start move 1': 'Tackle',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xa1\x03\x0f\xce.\xc8"')},
+ 'Graveler': {'id': 39, 'dex': 75, 'hp': 55, 'atk': 95, 'def': 115, 'spd': 35, 'spc': 45, 'type1': 'Rock',
+ 'type2': 'Ground', 'catch rate': 120, 'base exp': 134, 'start move 1': 'Tackle',
+ 'start move 2': 'Defense Curl', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xa1\x03\x0f\xce.\xc8"')},
+ 'Golem': {'id': 49, 'dex': 76, 'hp': 80, 'atk': 110, 'def': 130, 'spd': 45, 'spc': 55, 'type1': 'Rock',
+ 'type2': 'Ground', 'catch rate': 45, 'base exp': 177, 'start move 1': 'Tackle',
+ 'start move 2': 'Defense Curl', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xb1C\x0f\xce.\xc8"')},
+ 'Ponyta': {'id': 163, 'dex': 77, 'hp': 50, 'atk': 85, 'def': 55, 'spd': 90, 'spc': 65, 'type1': 'Fire',
+ 'type2': 'Fire', 'catch rate': 190, 'base exp': 152, 'start move 1': 'Ember', 'start move 2': 'No Move',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xe0\x03\x08\xc0\xe3\x08\x02')},
+ 'Rapidash': {'id': 164, 'dex': 78, 'hp': 65, 'atk': 100, 'def': 70, 'spd': 105, 'spc': 80, 'type1': 'Fire',
+ 'type2': 'Fire', 'catch rate': 60, 'base exp': 192, 'start move 1': 'Ember',
+ 'start move 2': 'Tail Whip', 'start move 3': 'Stomp', 'start move 4': 'Growl', 'growth rate': 0,
+ 'tms': bytearray(b'\xe0C\x08\xc0\xe3\x08\x02')},
+ 'Slowpoke': {'id': 37, 'dex': 79, 'hp': 90, 'atk': 65, 'def': 65, 'spd': 15, 'spc': 40, 'type1': 'Water',
+ 'type2': 'Psychic', 'catch rate': 190, 'base exp': 99, 'start move 1': 'Confusion',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa0\xbf\x08\xfe\xe38s')},
+ 'Slowbro': {'id': 8, 'dex': 80, 'hp': 95, 'atk': 75, 'def': 110, 'spd': 30, 'spc': 80, 'type1': 'Water',
+ 'type2': 'Psychic', 'catch rate': 75, 'base exp': 164, 'start move 1': 'Confusion',
+ 'start move 2': 'Disable', 'start move 3': 'Headbutt', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xb1\xff\x0f\xfe\xe38s')},
+ 'Magnemite': {'id': 173, 'dex': 81, 'hp': 25, 'atk': 35, 'def': 70, 'spd': 45, 'spc': 95, 'type1': 'Electric',
+ 'type2': 'Electric', 'catch rate': 190, 'base exp': 89, 'start move 1': 'Tackle',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b' \x03\x88\xe1C\x18B')},
+ 'Magneton': {'id': 54, 'dex': 82, 'hp': 50, 'atk': 60, 'def': 95, 'spd': 70, 'spc': 120, 'type1': 'Electric',
+ 'type2': 'Electric', 'catch rate': 60, 'base exp': 161, 'start move 1': 'Tackle',
+ 'start move 2': 'Sonicboom', 'start move 3': 'Thundershock', 'start move 4': 'No Move',
+ 'growth rate': 0, 'tms': bytearray(b' C\x88\xe1C\x18B')},
+ 'Farfetchd': {'id': 64, 'dex': 83, 'hp': 52, 'atk': 65, 'def': 55, 'spd': 60, 'spc': 58, 'type1': 'Normal',
+ 'type2': 'Flying', 'catch rate': 45, 'base exp': 94, 'start move 1': 'Peck',
+ 'start move 2': 'Sand Attack', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xae\x03\x08\xc0\xc3\x08\x0e')},
+ 'Doduo': {'id': 70, 'dex': 84, 'hp': 35, 'atk': 85, 'def': 45, 'spd': 75, 'spc': 35, 'type1': 'Normal',
+ 'type2': 'Flying', 'catch rate': 190, 'base exp': 96, 'start move 1': 'Peck', 'start move 2': 'No Move',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa8\x03\x08\xc0\x83\x0c\x0b')},
+ 'Dodrio': {'id': 116, 'dex': 85, 'hp': 60, 'atk': 110, 'def': 70, 'spd': 100, 'spc': 60, 'type1': 'Normal',
+ 'type2': 'Flying', 'catch rate': 45, 'base exp': 158, 'start move 1': 'Peck', 'start move 2': 'Growl',
+ 'start move 3': 'Fury Attack', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa8C\x08\xc0\x83\x0c\x0b')},
+ 'Seel': {'id': 58, 'dex': 86, 'hp': 65, 'atk': 45, 'def': 55, 'spd': 45, 'spc': 70, 'type1': 'Water',
+ 'type2': 'Water', 'catch rate': 190, 'base exp': 100, 'start move 1': 'Headbutt',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xe0\xbf\x08\xc0\x82\x082')},
+ 'Dewgong': {'id': 120, 'dex': 87, 'hp': 90, 'atk': 70, 'def': 80, 'spd': 70, 'spc': 95, 'type1': 'Water',
+ 'type2': 'Ice', 'catch rate': 75, 'base exp': 176, 'start move 1': 'Headbutt', 'start move 2': 'Growl',
+ 'start move 3': 'Aurora Beam', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xe0\xff\x08\xc0\x82\x082')},
+ 'Grimer': {'id': 13, 'dex': 88, 'hp': 80, 'atk': 80, 'def': 50, 'spd': 25, 'spc': 40, 'type1': 'Poison',
+ 'type2': 'Poison', 'catch rate': 190, 'base exp': 90, 'start move 1': 'Pound', 'start move 2': 'Disable',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa0\x00\x98\xc1*H\x02')},
+ 'Muk': {'id': 136, 'dex': 89, 'hp': 105, 'atk': 105, 'def': 75, 'spd': 50, 'spc': 65, 'type1': 'Poison',
+ 'type2': 'Poison', 'catch rate': 75, 'base exp': 157, 'start move 1': 'Pound', 'start move 2': 'Disable',
+ 'start move 3': 'Poison Gas', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa0@\x98\xc1*H\x02')},
+ 'Shellder': {'id': 23, 'dex': 90, 'hp': 30, 'atk': 65, 'def': 100, 'spd': 40, 'spc': 45, 'type1': 'Water',
+ 'type2': 'Water', 'catch rate': 190, 'base exp': 97, 'start move 1': 'Tackle',
+ 'start move 2': 'Withdraw', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 5,
+ 'tms': bytearray(b' ?\x08\xe0KH\x13')},
+ 'Cloyster': {'id': 139, 'dex': 91, 'hp': 50, 'atk': 95, 'def': 180, 'spd': 70, 'spc': 85, 'type1': 'Water',
+ 'type2': 'Ice', 'catch rate': 60, 'base exp': 203, 'start move 1': 'Withdraw',
+ 'start move 2': 'Supersonic', 'start move 3': 'Clamp', 'start move 4': 'Aurora Beam', 'growth rate': 5,
+ 'tms': bytearray(b' \x7f\x08\xe0KH\x13')},
+ 'Gastly': {'id': 25, 'dex': 92, 'hp': 30, 'atk': 35, 'def': 30, 'spd': 80, 'spc': 100, 'type1': 'Ghost',
+ 'type2': 'Poison', 'catch rate': 190, 'base exp': 95, 'start move 1': 'Lick',
+ 'start move 2': 'Confuse Ray', 'start move 3': 'Night Shade', 'start move 4': 'No Move',
+ 'growth rate': 3, 'tms': bytearray(b' \x00\x98\xd1\nj\x02')},
+ 'Haunter': {'id': 147, 'dex': 93, 'hp': 45, 'atk': 50, 'def': 45, 'spd': 95, 'spc': 115, 'type1': 'Ghost',
+ 'type2': 'Poison', 'catch rate': 90, 'base exp': 126, 'start move 1': 'Lick',
+ 'start move 2': 'Confuse Ray', 'start move 3': 'Night Shade', 'start move 4': 'No Move',
+ 'growth rate': 3, 'tms': bytearray(b' \x00\x98\xd1\nj\x02')},
+ 'Gengar': {'id': 14, 'dex': 94, 'hp': 60, 'atk': 65, 'def': 60, 'spd': 110, 'spc': 130, 'type1': 'Ghost',
+ 'type2': 'Poison', 'catch rate': 45, 'base exp': 190, 'start move 1': 'Lick',
+ 'start move 2': 'Confuse Ray', 'start move 3': 'Night Shade', 'start move 4': 'No Move',
+ 'growth rate': 3, 'tms': bytearray(b'\xb1C\x9f\xd1\x8ej"')},
+ 'Onix': {'id': 34, 'dex': 95, 'hp': 35, 'atk': 45, 'def': 160, 'spd': 70, 'spc': 30, 'type1': 'Rock',
+ 'type2': 'Ground', 'catch rate': 45, 'base exp': 108, 'start move 1': 'Tackle', 'start move 2': 'Screech',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa0\x03\x08\xce\x8a\xc8"')},
+ 'Drowzee': {'id': 48, 'dex': 96, 'hp': 60, 'atk': 48, 'def': 45, 'spd': 42, 'spc': 90, 'type1': 'Psychic',
+ 'type2': 'Psychic', 'catch rate': 190, 'base exp': 102, 'start move 1': 'Pound',
+ 'start move 2': 'Hypnosis', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xb1\x03\x0f\xf0\x87:C')},
+ 'Hypno': {'id': 129, 'dex': 97, 'hp': 85, 'atk': 73, 'def': 70, 'spd': 67, 'spc': 115, 'type1': 'Psychic',
+ 'type2': 'Psychic', 'catch rate': 75, 'base exp': 165, 'start move 1': 'Pound',
+ 'start move 2': 'Hypnosis', 'start move 3': 'Disable', 'start move 4': 'Confusion', 'growth rate': 0,
+ 'tms': bytearray(b'\xb1C\x0f\xf0\x87:C')},
+ 'Krabby': {'id': 78, 'dex': 98, 'hp': 30, 'atk': 105, 'def': 90, 'spd': 50, 'spc': 25, 'type1': 'Water',
+ 'type2': 'Water', 'catch rate': 225, 'base exp': 115, 'start move 1': 'Bubble', 'start move 2': 'Leer',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa4?\x08\xc0\x02\x086')},
+ 'Kingler': {'id': 138, 'dex': 99, 'hp': 55, 'atk': 130, 'def': 115, 'spd': 75, 'spc': 50, 'type1': 'Water',
+ 'type2': 'Water', 'catch rate': 60, 'base exp': 206, 'start move 1': 'Bubble', 'start move 2': 'Leer',
+ 'start move 3': 'Vicegrip', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa4\x7f\x08\xc0\x02\x086')},
+ 'Voltorb': {'id': 6, 'dex': 100, 'hp': 40, 'atk': 30, 'def': 50, 'spd': 100, 'spc': 55, 'type1': 'Electric',
+ 'type2': 'Electric', 'catch rate': 190, 'base exp': 103, 'start move 1': 'Tackle',
+ 'start move 2': 'Screech', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b' \x01\x88\xe1KXB')},
+ 'Electrode': {'id': 141, 'dex': 101, 'hp': 60, 'atk': 50, 'def': 70, 'spd': 140, 'spc': 80, 'type1': 'Electric',
+ 'type2': 'Electric', 'catch rate': 60, 'base exp': 150, 'start move 1': 'Tackle',
+ 'start move 2': 'Screech', 'start move 3': 'Sonicboom', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b' A\x88\xe1\xcbXB')},
+ 'Exeggcute': {'id': 12, 'dex': 102, 'hp': 60, 'atk': 40, 'def': 80, 'spd': 40, 'spc': 60, 'type1': 'Grass',
+ 'type2': 'Psychic', 'catch rate': 90, 'base exp': 98, 'start move 1': 'Barrage',
+ 'start move 2': 'Hypnosis', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 5,
+ 'tms': bytearray(b' \x03\x08\xf0\x1bh\x02')},
+ 'Exeggutor': {'id': 10, 'dex': 103, 'hp': 95, 'atk': 95, 'def': 85, 'spd': 55, 'spc': 125, 'type1': 'Grass',
+ 'type2': 'Psychic', 'catch rate': 45, 'base exp': 212, 'start move 1': 'Barrage',
+ 'start move 2': 'Hypnosis', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 5,
+ 'tms': bytearray(b' C8\xf0\x1bh"')},
+ 'Cubone': {'id': 17, 'dex': 104, 'hp': 50, 'atk': 50, 'def': 95, 'spd': 35, 'spc': 40, 'type1': 'Ground',
+ 'type2': 'Ground', 'catch rate': 190, 'base exp': 87, 'start move 1': 'Bone Club',
+ 'start move 2': 'Growl', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xb1?\x0f\xce\xa2\x08"')},
+ 'Marowak': {'id': 145, 'dex': 105, 'hp': 60, 'atk': 80, 'def': 110, 'spd': 45, 'spc': 50, 'type1': 'Ground',
+ 'type2': 'Ground', 'catch rate': 75, 'base exp': 124, 'start move 1': 'Bone Club',
+ 'start move 2': 'Growl', 'start move 3': 'Leer', 'start move 4': 'Focus Energy', 'growth rate': 0,
+ 'tms': bytearray(b'\xb1\x7f\x0f\xce\xa2\x08"')},
+ 'Hitmonlee': {'id': 43, 'dex': 106, 'hp': 50, 'atk': 120, 'def': 53, 'spd': 87, 'spc': 35, 'type1': 'Fighting',
+ 'type2': 'Fighting', 'catch rate': 45, 'base exp': 139, 'start move 1': 'Double Kick',
+ 'start move 2': 'Meditate', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xb1\x03\x0f\xc0\xc6\x08"')},
+ 'Hitmonchan': {'id': 44, 'dex': 107, 'hp': 50, 'atk': 105, 'def': 79, 'spd': 76, 'spc': 35, 'type1': 'Fighting',
+ 'type2': 'Fighting', 'catch rate': 45, 'base exp': 140, 'start move 1': 'Comet Punch',
+ 'start move 2': 'Agility', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xb1\x03\x0f\xc0\xc6\x08"')},
+ 'Lickitung': {'id': 11, 'dex': 108, 'hp': 90, 'atk': 55, 'def': 75, 'spd': 30, 'spc': 60, 'type1': 'Normal',
+ 'type2': 'Normal', 'catch rate': 45, 'base exp': 127, 'start move 1': 'Wrap',
+ 'start move 2': 'Supersonic', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xb5\x7f\x8f\xc7\xa2\x086')},
+ 'Koffing': {'id': 55, 'dex': 109, 'hp': 40, 'atk': 65, 'def': 95, 'spd': 35, 'spc': 60, 'type1': 'Poison',
+ 'type2': 'Poison', 'catch rate': 190, 'base exp': 114, 'start move 1': 'Tackle', 'start move 2': 'Smog',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b' \x00\x88\xc1*H\x02')},
+ 'Weezing': {'id': 143, 'dex': 110, 'hp': 65, 'atk': 90, 'def': 120, 'spd': 60, 'spc': 85, 'type1': 'Poison',
+ 'type2': 'Poison', 'catch rate': 60, 'base exp': 173, 'start move 1': 'Tackle', 'start move 2': 'Smog',
+ 'start move 3': 'Sludge', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b' @\x88\xc1*H\x02')},
+ 'Rhyhorn': {'id': 18, 'dex': 111, 'hp': 80, 'atk': 85, 'def': 95, 'spd': 25, 'spc': 30, 'type1': 'Ground',
+ 'type2': 'Rock', 'catch rate': 120, 'base exp': 135, 'start move 1': 'Horn Attack',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 5,
+ 'tms': bytearray(b'\xe0\x03\x88\xcf\xa2\x88"')},
+ 'Rhydon': {'id': 1, 'dex': 112, 'hp': 105, 'atk': 130, 'def': 120, 'spd': 40, 'spc': 45, 'type1': 'Ground',
+ 'type2': 'Rock', 'catch rate': 60, 'base exp': 204, 'start move 1': 'Horn Attack',
+ 'start move 2': 'Stomp', 'start move 3': 'Tail Whip', 'start move 4': 'Fury Attack', 'growth rate': 5,
+ 'tms': bytearray(b'\xf1\xff\x8f\xcf\xa2\x882')},
+ 'Chansey': {'id': 40, 'dex': 113, 'hp': 250, 'atk': 5, 'def': 5, 'spd': 50, 'spc': 105, 'type1': 'Normal',
+ 'type2': 'Normal', 'catch rate': 30, 'base exp': 255, 'start move 1': 'Pound',
+ 'start move 2': 'Doubleslap', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 4,
+ 'tms': bytearray(b'\xb1\x7f\xaf\xf1\xb79c')},
+ 'Tangela': {'id': 30, 'dex': 114, 'hp': 65, 'atk': 55, 'def': 115, 'spd': 60, 'spc': 100, 'type1': 'Grass',
+ 'type2': 'Grass', 'catch rate': 45, 'base exp': 166, 'start move 1': 'Constrict',
+ 'start move 2': 'Bind', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa4C8\xc0\x82\x08\x06')},
+ 'Kangaskhan': {'id': 2, 'dex': 115, 'hp': 105, 'atk': 95, 'def': 80, 'spd': 90, 'spc': 40, 'type1': 'Normal',
+ 'type2': 'Normal', 'catch rate': 45, 'base exp': 175, 'start move 1': 'Comet Punch',
+ 'start move 2': 'Rage', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xb1\x7f\x8f\xc7\xa2\x882')},
+ 'Horsea': {'id': 92, 'dex': 116, 'hp': 30, 'atk': 40, 'def': 70, 'spd': 60, 'spc': 70, 'type1': 'Water',
+ 'type2': 'Water', 'catch rate': 225, 'base exp': 83, 'start move 1': 'Bubble', 'start move 2': 'No Move',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b' ?\x08\xc0\xc2\x08\x12')},
+ 'Seadra': {'id': 93, 'dex': 117, 'hp': 55, 'atk': 65, 'def': 95, 'spd': 85, 'spc': 95, 'type1': 'Water',
+ 'type2': 'Water', 'catch rate': 75, 'base exp': 155, 'start move 1': 'Bubble',
+ 'start move 2': 'Smokescreen', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b' \x7f\x08\xc0\xc2\x08\x12')},
+ 'Goldeen': {'id': 157, 'dex': 118, 'hp': 45, 'atk': 67, 'def': 60, 'spd': 63, 'spc': 50, 'type1': 'Water',
+ 'type2': 'Water', 'catch rate': 225, 'base exp': 111, 'start move 1': 'Peck',
+ 'start move 2': 'Tail Whip', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'`?\x08\xc0\xc2\x08\x12')},
+ 'Seaking': {'id': 158, 'dex': 119, 'hp': 80, 'atk': 92, 'def': 65, 'spd': 68, 'spc': 80, 'type1': 'Water',
+ 'type2': 'Water', 'catch rate': 60, 'base exp': 170, 'start move 1': 'Peck',
+ 'start move 2': 'Tail Whip', 'start move 3': 'Supersonic', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'`\x7f\x08\xc0\xc2\x08\x12')},
+ 'Staryu': {'id': 27, 'dex': 120, 'hp': 30, 'atk': 45, 'def': 55, 'spd': 85, 'spc': 70, 'type1': 'Water',
+ 'type2': 'Water', 'catch rate': 225, 'base exp': 106, 'start move 1': 'Tackle',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 5,
+ 'tms': bytearray(b' ?\x88\xf1\xc38S')},
+ 'Starmie': {'id': 152, 'dex': 121, 'hp': 60, 'atk': 75, 'def': 85, 'spd': 115, 'spc': 100, 'type1': 'Water',
+ 'type2': 'Psychic', 'catch rate': 60, 'base exp': 207, 'start move 1': 'Tackle',
+ 'start move 2': 'Water Gun', 'start move 3': 'Harden', 'start move 4': 'No Move', 'growth rate': 5,
+ 'tms': bytearray(b' \x7f\x88\xf1\xc38S')},
+ 'Mr Mime': {'id': 42, 'dex': 122, 'hp': 40, 'atk': 45, 'def': 65, 'spd': 90, 'spc': 100, 'type1': 'Psychic',
+ 'type2': 'Psychic', 'catch rate': 45, 'base exp': 136, 'start move 1': 'Confusion',
+ 'start move 2': 'Barrier', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xb1C\xaf\xf1\x878B')},
+ 'Scyther': {'id': 26, 'dex': 123, 'hp': 70, 'atk': 110, 'def': 80, 'spd': 105, 'spc': 55, 'type1': 'Bug',
+ 'type2': 'Flying', 'catch rate': 45, 'base exp': 187, 'start move 1': 'Quick Attack',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'$C\x08\xc0\xc2\x08\x06')},
+ 'Jynx': {'id': 72, 'dex': 124, 'hp': 65, 'atk': 50, 'def': 35, 'spd': 95, 'spc': 95, 'type1': 'Ice',
+ 'type2': 'Psychic', 'catch rate': 45, 'base exp': 137, 'start move 1': 'Pound',
+ 'start move 2': 'Lovely Kiss', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xb1\x7f\x0f\xf0\x87(\x02')},
+ 'Electabuzz': {'id': 53, 'dex': 125, 'hp': 65, 'atk': 83, 'def': 57, 'spd': 105, 'spc': 85, 'type1': 'Electric',
+ 'type2': 'Electric', 'catch rate': 45, 'base exp': 156, 'start move 1': 'Quick Attack',
+ 'start move 2': 'Leer', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xb1C\x8f\xf1\xc78b')},
+ 'Magmar': {'id': 51, 'dex': 126, 'hp': 65, 'atk': 95, 'def': 57, 'spd': 93, 'spc': 85, 'type1': 'Fire',
+ 'type2': 'Fire', 'catch rate': 45, 'base exp': 167, 'start move 1': 'Ember', 'start move 2': 'No Move',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xb1C\x0f\xf0\xa6("')},
+ 'Pinsir': {'id': 29, 'dex': 127, 'hp': 65, 'atk': 125, 'def': 100, 'spd': 85, 'spc': 55, 'type1': 'Bug',
+ 'type2': 'Bug', 'catch rate': 45, 'base exp': 200, 'start move 1': 'Vicegrip', 'start move 2': 'No Move',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 5,
+ 'tms': bytearray(b'\xa4C\r\xc0\x02\x08&')},
+ 'Tauros': {'id': 60, 'dex': 128, 'hp': 75, 'atk': 100, 'def': 95, 'spd': 110, 'spc': 70, 'type1': 'Normal',
+ 'type2': 'Normal', 'catch rate': 45, 'base exp': 211, 'start move 1': 'Tackle',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 5,
+ 'tms': bytearray(b'\xe0s\x88\xc7\xa2\x08"')},
+ 'Magikarp': {'id': 133, 'dex': 129, 'hp': 20, 'atk': 10, 'def': 55, 'spd': 80, 'spc': 20, 'type1': 'Water',
+ 'type2': 'Water', 'catch rate': 255, 'base exp': 20, 'start move 1': 'Splash',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 5,
+ 'tms': bytearray(b'\x00\x00\x00\x00\x00\x00\x00')},
+ 'Gyarados': {'id': 22, 'dex': 130, 'hp': 95, 'atk': 125, 'def': 79, 'spd': 81, 'spc': 100, 'type1': 'Water',
+ 'type2': 'Flying', 'catch rate': 45, 'base exp': 214, 'start move 1': 'Bite',
+ 'start move 2': 'Dragon Rage', 'start move 3': 'Leer', 'start move 4': 'Hydro Pump', 'growth rate': 5,
+ 'tms': bytearray(b'\xa0\x7f\xc8\xc1\xa3\x082')},
+ 'Lapras': {'id': 19, 'dex': 131, 'hp': 130, 'atk': 85, 'def': 80, 'spd': 60, 'spc': 95, 'type1': 'Water',
+ 'type2': 'Ice', 'catch rate': 45, 'base exp': 219, 'start move 1': 'Water Gun', 'start move 2': 'Growl',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 5,
+ 'tms': bytearray(b'\xe0\x7f\xe8\xd1\x83(2')},
+ 'Ditto': {'id': 76, 'dex': 132, 'hp': 48, 'atk': 48, 'def': 48, 'spd': 48, 'spc': 48, 'type1': 'Normal',
+ 'type2': 'Normal', 'catch rate': 35, 'base exp': 61, 'start move 1': 'Transform',
+ 'start move 2': 'No Move', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\x00\x00\x00\x00\x00\x00\x00')},
+ 'Eevee': {'id': 102, 'dex': 133, 'hp': 55, 'atk': 55, 'def': 50, 'spd': 55, 'spc': 65, 'type1': 'Normal',
+ 'type2': 'Normal', 'catch rate': 45, 'base exp': 92, 'start move 1': 'Tackle',
+ 'start move 2': 'Sand Attack', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa0\x03\x08\xc0\xc3\x08\x02')},
+ 'Vaporeon': {'id': 105, 'dex': 134, 'hp': 130, 'atk': 65, 'def': 60, 'spd': 65, 'spc': 110, 'type1': 'Water',
+ 'type2': 'Water', 'catch rate': 45, 'base exp': 196, 'start move 1': 'Tackle',
+ 'start move 2': 'Sand Attack', 'start move 3': 'Quick Attack', 'start move 4': 'Water Gun',
+ 'growth rate': 0, 'tms': bytearray(b'\xa0\x7f\x08\xc0\xc3\x08\x12')},
+ 'Jolteon': {'id': 104, 'dex': 135, 'hp': 65, 'atk': 65, 'def': 60, 'spd': 130, 'spc': 110, 'type1': 'Electric',
+ 'type2': 'Electric', 'catch rate': 45, 'base exp': 197, 'start move 1': 'Tackle',
+ 'start move 2': 'Sand Attack', 'start move 3': 'Quick Attack', 'start move 4': 'Thundershock',
+ 'growth rate': 0, 'tms': bytearray(b'\xa0C\x88\xc1\xc3\x18B')},
+ 'Flareon': {'id': 103, 'dex': 136, 'hp': 65, 'atk': 130, 'def': 60, 'spd': 65, 'spc': 110, 'type1': 'Fire',
+ 'type2': 'Fire', 'catch rate': 45, 'base exp': 198, 'start move 1': 'Tackle',
+ 'start move 2': 'Sand Attack', 'start move 3': 'Quick Attack', 'start move 4': 'Ember',
+ 'growth rate': 0, 'tms': bytearray(b'\xa0C\x08\xc0\xe3\x08\x02')},
+ 'Porygon': {'id': 170, 'dex': 137, 'hp': 65, 'atk': 60, 'def': 70, 'spd': 40, 'spc': 75, 'type1': 'Normal',
+ 'type2': 'Normal', 'catch rate': 45, 'base exp': 130, 'start move 1': 'Tackle',
+ 'start move 2': 'Sharpen', 'start move 3': 'Conversion', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b' s\x88\xf1\xc38C')},
+ 'Omanyte': {'id': 98, 'dex': 138, 'hp': 35, 'atk': 40, 'def': 100, 'spd': 35, 'spc': 90, 'type1': 'Rock',
+ 'type2': 'Water', 'catch rate': 45, 'base exp': 120, 'start move 1': 'Water Gun',
+ 'start move 2': 'Withdraw', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa0?\x08\xc0\x03\x08\x12')},
+ 'Omastar': {'id': 99, 'dex': 139, 'hp': 70, 'atk': 60, 'def': 125, 'spd': 55, 'spc': 115, 'type1': 'Rock',
+ 'type2': 'Water', 'catch rate': 45, 'base exp': 199, 'start move 1': 'Water Gun',
+ 'start move 2': 'Withdraw', 'start move 3': 'Horn Attack', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xe0\x7f\r\xc0\x83\x08\x12')},
+ 'Kabuto': {'id': 90, 'dex': 140, 'hp': 30, 'atk': 80, 'def': 90, 'spd': 55, 'spc': 45, 'type1': 'Rock',
+ 'type2': 'Water', 'catch rate': 45, 'base exp': 119, 'start move 1': 'Scratch', 'start move 2': 'Harden',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xa0?\x08\xc0\x03\x08\x12')},
+ 'Kabutops': {'id': 91, 'dex': 141, 'hp': 60, 'atk': 115, 'def': 105, 'spd': 80, 'spc': 70, 'type1': 'Rock',
+ 'type2': 'Water', 'catch rate': 45, 'base exp': 201, 'start move 1': 'Scratch',
+ 'start move 2': 'Harden', 'start move 3': 'Absorb', 'start move 4': 'No Move', 'growth rate': 0,
+ 'tms': bytearray(b'\xb6\x7f\r\xc0\x83\x08\x12')},
+ 'Aerodactyl': {'id': 171, 'dex': 142, 'hp': 80, 'atk': 105, 'def': 65, 'spd': 130, 'spc': 60, 'type1': 'Rock',
+ 'type2': 'Flying', 'catch rate': 45, 'base exp': 202, 'start move 1': 'Wing Attack',
+ 'start move 2': 'Agility', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 5,
+ 'tms': bytearray(b'*CH\xc0c\x0c\n')},
+ 'Snorlax': {'id': 132, 'dex': 143, 'hp': 160, 'atk': 110, 'def': 65, 'spd': 30, 'spc': 65, 'type1': 'Normal',
+ 'type2': 'Normal', 'catch rate': 25, 'base exp': 154, 'start move 1': 'Headbutt',
+ 'start move 2': 'Amnesia', 'start move 3': 'Rest', 'start move 4': 'No Move', 'growth rate': 5,
+ 'tms': bytearray(b'\xb1\xff\xaf\xd7\xaf\xa82')},
+ 'Articuno': {'id': 74, 'dex': 144, 'hp': 90, 'atk': 85, 'def': 100, 'spd': 85, 'spc': 125, 'type1': 'Ice',
+ 'type2': 'Flying', 'catch rate': 3, 'base exp': 215, 'start move 1': 'Peck',
+ 'start move 2': 'Ice Beam', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 5,
+ 'tms': bytearray(b'*\x7f\x08\xc0C\x0c\n')},
+ 'Zapdos': {'id': 75, 'dex': 145, 'hp': 90, 'atk': 90, 'def': 85, 'spd': 100, 'spc': 125, 'type1': 'Electric',
+ 'type2': 'Flying', 'catch rate': 3, 'base exp': 216, 'start move 1': 'Thundershock',
+ 'start move 2': 'Drill Peck', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 5,
+ 'tms': bytearray(b'*C\x88\xc1C\x1cJ')},
+ 'Moltres': {'id': 73, 'dex': 146, 'hp': 90, 'atk': 100, 'def': 90, 'spd': 90, 'spc': 125, 'type1': 'Fire',
+ 'type2': 'Flying', 'catch rate': 3, 'base exp': 217, 'start move 1': 'Peck',
+ 'start move 2': 'Fire Spin', 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 5,
+ 'tms': bytearray(b'*C\x08\xc0c\x0c\n')},
+ 'Dratini': {'id': 88, 'dex': 147, 'hp': 41, 'atk': 64, 'def': 45, 'spd': 50, 'spc': 50, 'type1': 'Dragon',
+ 'type2': 'Dragon', 'catch rate': 45, 'base exp': 67, 'start move 1': 'Wrap', 'start move 2': 'Leer',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 5,
+ 'tms': bytearray(b'\xa0?\xc8\xc1\xe3\x18\x12')},
+ 'Dragonair': {'id': 89, 'dex': 148, 'hp': 61, 'atk': 84, 'def': 65, 'spd': 70, 'spc': 70, 'type1': 'Dragon',
+ 'type2': 'Dragon', 'catch rate': 45, 'base exp': 144, 'start move 1': 'Wrap', 'start move 2': 'Leer',
+ 'start move 3': 'Thunder Wave', 'start move 4': 'No Move', 'growth rate': 5,
+ 'tms': bytearray(b'\xe0?\xc8\xc1\xe3\x18\x12')},
+ 'Dragonite': {'id': 66, 'dex': 149, 'hp': 91, 'atk': 134, 'def': 95, 'spd': 80, 'spc': 100, 'type1': 'Dragon',
+ 'type2': 'Flying', 'catch rate': 45, 'base exp': 218, 'start move 1': 'Wrap', 'start move 2': 'Leer',
+ 'start move 3': 'Thunder Wave', 'start move 4': 'Agility', 'growth rate': 5,
+ 'tms': bytearray(b'\xe2\x7f\xc8\xc1\xe3\x182')},
+ 'Mewtwo': {'id': 131, 'dex': 150, 'hp': 106, 'atk': 110, 'def': 90, 'spd': 130, 'spc': 154, 'type1': 'Psychic',
+ 'type2': 'Psychic', 'catch rate': 3, 'base exp': 220, 'start move 1': 'Confusion',
+ 'start move 2': 'Disable', 'start move 3': 'Swift', 'start move 4': 'Psychic', 'growth rate': 5,
+ 'tms': bytearray(b'\xb1\xff\xaf\xf1\xaf8c')},
+ 'Mew': {'id': 21, 'dex': 151, 'hp': 100, 'atk': 100, 'def': 100, 'spd': 100, 'spc': 100, 'type1': 'Psychic',
+ 'type2': 'Psychic', 'catch rate': 45, 'base exp': 64, 'start move 1': 'Pound', 'start move 2': 'No Move',
+ 'start move 3': 'No Move', 'start move 4': 'No Move', 'growth rate': 3,
+ 'tms': bytearray(b'\xff\xff\xff\xff\xff\xff\xff')}}
+
+
+
+evolves_from = {
+ "Ivysaur": "Bulbasaur",
+ "Venusaur": "Ivysaur",
+ "Charmeleon": "Charmander",
+ "Charizard": "Charmeleon",
+ "Wartortle": "Squirtle",
+ "Blastoise": "Wartortle",
+ "Metapod": "Caterpie",
+ "Butterfree": "Metapod",
+ "Kakuna": "Weedle",
+ "Beedrill": "Kakuna",
+ "Pidgeotto": "Pidgey",
+ "Pidgeot": "Pidgeotto",
+ "Raticate": "Rattata",
+ "Fearow": "Spearow",
+ "Arbok": "Ekans",
+ "Raichu": "Pikachu",
+ "Sandslash": "Sandshrew",
+ "Nidorina": "Nidoran F",
+ "Nidoqueen": "Nidorina",
+ "Nidorino": "Nidoran M",
+ "Nidoking": "Nidorino",
+ "Clefable": "Clefairy",
+ "Ninetales": "Vulpix",
+ "Wigglytuff": "Jigglypuff",
+ "Golbat": "Zubat",
+ "Gloom": "Oddish",
+ "Vileplume": "Gloom",
+ "Parasect": "Paras",
+ "Venomoth": "Venonat",
+ "Dugtrio": "Diglett",
+ "Persian": "Meowth",
+ "Golduck": "Psyduck",
+ "Primeape": "Mankey",
+ "Arcanine": "Growlithe",
+ "Poliwhirl": "Poliwag",
+ "Poliwrath": "Poliwhirl",
+ "Kadabra": "Abra",
+ "Alakazam": "Kadabra",
+ "Machoke": "Machop",
+ "Machamp": "Machoke",
+ "Weepinbell": "Bellsprout",
+ "Victreebel": "Weepinbell",
+ "Tentacruel": "Tentacool",
+ "Graveler": "Geodude",
+ "Golem": "Graveler",
+ "Rapidash": "Ponyta",
+ "Slowbro": "Slowpoke",
+ "Magneton": "Magnemite",
+ "Dodrio": "Doduo",
+ "Dewgong": "Seel",
+ "Muk": "Grimer",
+ "Cloyster": "Shellder",
+ "Haunter": "Gastly",
+ "Gengar": "Haunter",
+ "Hypno": "Drowzee",
+ "Kingler": "Krabby",
+ "Electrode": "Voltorb",
+ "Exeggutor": "Exeggcute",
+ "Marowak": "Cubone",
+ "Weezing": "Koffing",
+ "Rhydon": "Rhyhorn",
+ "Seadra": "Horsea",
+ "Seaking": "Goldeen",
+ "Starmie": "Staryu",
+ "Gyarados": "Magikarp",
+ "Vaporeon": "Eevee",
+ "Jolteon": "Eevee",
+ "Flareon": "Eevee",
+ "Omastar": "Omanyte",
+ "Kabutops": "Kabuto",
+ "Dragonair": "Dratini",
+ "Dragonite": "Dragonair"
+}
+
+evolves_to = {}
+for from_mon, to_mon in zip(evolves_from.values(), evolves_from.keys()):
+ if from_mon != "Eevee":
+ evolves_to[from_mon] = to_mon
+
+# basic_three_stage_pokemon = []
+# for mon in evolves_to.keys():
+# if evolves_to[mon] in evolves_to.keys():
+# basic_three_stage_pokemon.append(mon)
+# print(basic_three_stage_pokemon)
+
+learnsets = {
+ 'Rhydon': ['Stomp', 'Tail Whip', 'Fury Attack', 'Horn Drill', 'Leer', 'Take Down'],
+ 'Kangaskhan': ['Bite', 'Tail Whip', 'Mega Punch', 'Leer', 'Dizzy Punch'],
+ 'Nidoran M': ['Horn Attack', 'Poison Sting', 'Focus Energy', 'Fury Attack', 'Horn Drill', 'Double Kick'],
+ 'Clefairy': ['Sing', 'Doubleslap', 'Minimize', 'Metronome', 'Defense Curl', 'Light Screen'],
+ 'Spearow': ['Leer', 'Fury Attack', 'Mirror Move', 'Drill Peck', 'Agility'],
+ 'Voltorb': ['Sonicboom', 'Selfdestruct', 'Light Screen', 'Swift', 'Explosion'],
+ 'Nidoking': ['Horn Attack', 'Poison Sting', 'Thrash'],
+ 'Slowbro': ['Disable', 'Headbutt', 'Growl', 'Water Gun', 'Withdraw', 'Amnesia', 'Psychic'],
+ 'Ivysaur': ['Leech Seed', 'Vine Whip', 'Poisonpowder', 'Razor Leaf', 'Growth', 'Sleep Powder', 'Solarbeam'],
+ 'Exeggutor': ['Stomp'], 'Lickitung': ['Stomp', 'Disable', 'Defense Curl', 'Slam', 'Screech'],
+ 'Exeggcute': ['Reflect', 'Leech Seed', 'Stun Spore', 'Poisonpowder', 'Solarbeam', 'Sleep Powder'],
+ 'Grimer': ['Poison Gas', 'Minimize', 'Sludge', 'Harden', 'Screech', 'Acid Armor'],
+ 'Gengar': ['Hypnosis', 'Dream Eater'],
+ 'Nidoran F': ['Scratch', 'Poison Sting', 'Tail Whip', 'Bite', 'Fury Swipes', 'Double Kick'],
+ 'Nidoqueen': ['Scratch', 'Poison Sting', 'Body Slam'],
+ 'Cubone': ['Leer', 'Focus Energy', 'Thrash', 'Bonemerang', 'Rage'],
+ 'Rhyhorn': ['Stomp', 'Tail Whip', 'Fury Attack', 'Horn Drill', 'Leer', 'Take Down'],
+ 'Lapras': ['Sing', 'Mist', 'Body Slam', 'Confuse Ray', 'Ice Beam', 'Hydro Pump'],
+ 'Mew': ['Transform', 'Mega Punch', 'Metronome', 'Psychic'],
+ 'Gyarados': ['Bite', 'Dragon Rage', 'Leer', 'Hydro Pump', 'Hyper Beam'],
+ 'Shellder': ['Supersonic', 'Clamp', 'Aurora Beam', 'Leer', 'Ice Beam'],
+ 'Tentacool': ['Supersonic', 'Wrap', 'Poison Sting', 'Water Gun', 'Constrict', 'Barrier', 'Screech', 'Hydro Pump'],
+ 'Gastly': ['Hypnosis', 'Dream Eater'],
+ 'Scyther': ['Leer', 'Focus Energy', 'Double Team', 'Slash', 'Swords Dance', 'Agility'],
+ 'Staryu': ['Water Gun', 'Harden', 'Recover', 'Swift', 'Minimize', 'Light Screen', 'Hydro Pump'],
+ 'Blastoise': ['Bubble', 'Water Gun', 'Bite', 'Withdraw', 'Skull Bash', 'Hydro Pump'],
+ 'Pinsir': ['Seismic Toss', 'Guillotine', 'Focus Energy', 'Harden', 'Slash', 'Swords Dance'],
+ 'Tangela': ['Absorb', 'Poisonpowder', 'Stun Spore', 'Sleep Powder', 'Slam', 'Growth'],
+ 'Growlithe': ['Ember', 'Leer', 'Take Down', 'Agility', 'Flamethrower'],
+ 'Onix': ['Bind', 'Rock Throw', 'Rage', 'Slam', 'Harden'],
+ 'Fearow': ['Leer', 'Fury Attack', 'Mirror Move', 'Drill Peck', 'Agility'],
+ 'Pidgey': ['Sand Attack', 'Quick Attack', 'Whirlwind', 'Wing Attack', 'Agility', 'Mirror Move'],
+ 'Slowpoke': ['Disable', 'Headbutt', 'Growl', 'Water Gun', 'Amnesia', 'Psychic'],
+ 'Kadabra': ['Confusion', 'Disable', 'Psybeam', 'Recover', 'Psychic', 'Reflect'],
+ 'Graveler': ['Defense Curl', 'Rock Throw', 'Selfdestruct', 'Harden', 'Earthquake', 'Explosion'],
+ 'Chansey': ['Sing', 'Growl', 'Minimize', 'Defense Curl', 'Light Screen', 'Double Edge'],
+ 'Machoke': ['Low Kick', 'Leer', 'Focus Energy', 'Seismic Toss', 'Submission'],
+ 'Mr Mime': ['Confusion', 'Light Screen', 'Doubleslap', 'Meditate', 'Substitute'],
+ 'Hitmonlee': ['Rolling Kick', 'Jump Kick', 'Focus Energy', 'Hi Jump Kick', 'Mega Kick'],
+ 'Hitmonchan': ['Fire Punch', 'Ice Punch', 'Thunderpunch', 'Mega Punch', 'Counter'],
+ 'Arbok': ['Poison Sting', 'Bite', 'Glare', 'Screech', 'Acid'],
+ 'Parasect': ['Stun Spore', 'Leech Life', 'Spore', 'Slash', 'Growth'],
+ 'Psyduck': ['Tail Whip', 'Disable', 'Confusion', 'Fury Swipes', 'Hydro Pump'],
+ 'Drowzee': ['Disable', 'Confusion', 'Headbutt', 'Poison Gas', 'Psychic', 'Meditate'],
+ 'Golem': ['Defense Curl', 'Rock Throw', 'Selfdestruct', 'Harden', 'Earthquake', 'Explosion'],
+ 'Magmar': ['Leer', 'Confuse Ray', 'Fire Punch', 'Smokescreen', 'Smog', 'Flamethrower'],
+ 'Electabuzz': ['Thundershock', 'Screech', 'Thunderpunch', 'Light Screen', 'Thunder'],
+ 'Magneton': ['Sonicboom', 'Thundershock', 'Supersonic', 'Thunder Wave', 'Swift', 'Screech'],
+ 'Koffing': ['Sludge', 'Smokescreen', 'Selfdestruct', 'Haze', 'Explosion'],
+ 'Mankey': ['Karate Chop', 'Fury Swipes', 'Focus Energy', 'Seismic Toss', 'Thrash'],
+ 'Seel': ['Growl', 'Aurora Beam', 'Rest', 'Take Down', 'Ice Beam'],
+ 'Diglett': ['Growl', 'Dig', 'Sand Attack', 'Slash', 'Earthquake'],
+ 'Tauros': ['Stomp', 'Tail Whip', 'Leer', 'Rage', 'Take Down'],
+ 'Farfetchd': ['Leer', 'Fury Attack', 'Swords Dance', 'Agility', 'Slash'],
+ 'Venonat': ['Poisonpowder', 'Leech Life', 'Stun Spore', 'Psybeam', 'Sleep Powder', 'Psychic'],
+ 'Dragonite': ['Thunder Wave', 'Agility', 'Slam', 'Dragon Rage', 'Hyper Beam'],
+ 'Doduo': ['Growl', 'Fury Attack', 'Drill Peck', 'Rage', 'Tri Attack', 'Agility'],
+ 'Poliwag': ['Hypnosis', 'Water Gun', 'Doubleslap', 'Body Slam', 'Amnesia', 'Hydro Pump'],
+ 'Jynx': ['Lick', 'Doubleslap', 'Ice Punch', 'Body Slam', 'Thrash', 'Blizzard'],
+ 'Moltres': ['Leer', 'Agility', 'Sky Attack'],
+ 'Articuno': ['Blizzard', 'Agility', 'Mist'],
+ 'Zapdos': ['Thunder', 'Agility', 'Light Screen'],
+ 'Meowth': ['Bite', 'Pay Day', 'Screech', 'Fury Swipes', 'Slash'],
+ 'Krabby': ['Vicegrip', 'Guillotine', 'Stomp', 'Crabhammer', 'Harden'],
+ 'Vulpix': ['Quick Attack', 'Roar', 'Confuse Ray', 'Flamethrower', 'Fire Spin'],
+ 'Pikachu': ['Thunder Wave', 'Quick Attack', 'Swift', 'Agility', 'Thunder'],
+ 'Dratini': ['Thunder Wave', 'Agility', 'Slam', 'Dragon Rage', 'Hyper Beam'],
+ 'Dragonair': ['Thunder Wave', 'Agility', 'Slam', 'Dragon Rage', 'Hyper Beam'],
+ 'Kabuto': ['Absorb', 'Slash', 'Leer', 'Hydro Pump'],
+ 'Kabutops': ['Absorb', 'Slash', 'Leer', 'Hydro Pump'],
+ 'Horsea': ['Smokescreen', 'Leer', 'Water Gun', 'Agility', 'Hydro Pump'],
+ 'Seadra': ['Smokescreen', 'Leer', 'Water Gun', 'Agility', 'Hydro Pump'],
+ 'Sandshrew': ['Sand Attack', 'Slash', 'Poison Sting', 'Swift', 'Fury Swipes'],
+ 'Sandslash': ['Sand Attack', 'Slash', 'Poison Sting', 'Swift', 'Fury Swipes'],
+ 'Omanyte': ['Horn Attack', 'Leer', 'Spike Cannon', 'Hydro Pump'],
+ 'Omastar': ['Horn Attack', 'Leer', 'Spike Cannon', 'Hydro Pump'],
+ 'Jigglypuff': ['Pound', 'Disable', 'Defense Curl', 'Doubleslap', 'Rest', 'Body Slam', 'Double Edge'],
+ 'Eevee': ['Quick Attack', 'Tail Whip', 'Bite', 'Take Down'],
+ 'Flareon': ['Quick Attack', 'Ember', 'Tail Whip', 'Bite', 'Leer', 'Fire Spin', 'Rage', 'Flamethrower'],
+ 'Jolteon': ['Quick Attack', 'Thundershock', 'Tail Whip', 'Thunder Wave', 'Double Kick', 'Agility', 'Pin Missile', 'Thunder'],
+ 'Vaporeon': ['Quick Attack', 'Water Gun', 'Tail Whip', 'Bite', 'Acid Armor', 'Haze', 'Mist', 'Hydro Pump'],
+ 'Machop': ['Low Kick', 'Leer', 'Focus Energy', 'Seismic Toss', 'Submission'],
+ 'Zubat': ['Supersonic', 'Bite', 'Confuse Ray', 'Wing Attack', 'Haze'],
+ 'Ekans': ['Poison Sting', 'Bite', 'Glare', 'Screech', 'Acid'],
+ 'Paras': ['Stun Spore', 'Leech Life', 'Spore', 'Slash', 'Growth'],
+ 'Poliwhirl': ['Hypnosis', 'Water Gun', 'Doubleslap', 'Body Slam', 'Amnesia', 'Hydro Pump'],
+ 'Poliwrath': ['Hypnosis', 'Water Gun'],
+ 'Beedrill': ['Fury Attack', 'Focus Energy', 'Twineedle', 'Rage', 'Pin Missile', 'Agility'],
+ 'Dodrio': ['Growl', 'Fury Attack', 'Drill Peck', 'Rage', 'Tri Attack', 'Agility'],
+ 'Primeape': ['Karate Chop', 'Fury Swipes', 'Focus Energy', 'Seismic Toss', 'Thrash'],
+ 'Dugtrio': ['Growl', 'Dig', 'Sand Attack', 'Slash', 'Earthquake'],
+ 'Venomoth': ['Poisonpowder', 'Leech Life', 'Stun Spore', 'Psybeam', 'Sleep Powder', 'Psychic'],
+ 'Dewgong': ['Growl', 'Aurora Beam', 'Rest', 'Take Down', 'Ice Beam'],
+ 'Butterfree': ['Confusion', 'Poisonpowder', 'Stun Spore', 'Sleep Powder', 'Supersonic', 'Whirlwind', 'Psybeam'],
+ 'Machamp': ['Low Kick', 'Leer', 'Focus Energy', 'Seismic Toss', 'Submission'],
+ 'Golduck': ['Tail Whip', 'Disable', 'Confusion', 'Fury Swipes', 'Hydro Pump'],
+ 'Hypno': ['Disable', 'Confusion', 'Headbutt', 'Poison Gas', 'Psychic', 'Meditate'],
+ 'Golbat': ['Supersonic', 'Bite', 'Confuse Ray', 'Wing Attack', 'Haze'],
+ 'Mewtwo': ['Barrier', 'Psychic', 'Recover', 'Mist', 'Amnesia'],
+ 'Snorlax': ['Body Slam', 'Harden', 'Double Edge', 'Hyper Beam'],
+ 'Magikarp': ['Tackle'],
+ 'Muk': ['Poison Gas', 'Minimize', 'Sludge', 'Harden', 'Screech', 'Acid Armor'],
+ 'Kingler': ['Vicegrip', 'Guillotine', 'Stomp', 'Crabhammer', 'Harden'],
+ 'Cloyster': ['Spike Cannon'],
+ 'Electrode': ['Sonicboom', 'Selfdestruct', 'Light Screen', 'Swift', 'Explosion'],
+ 'Weezing': ['Sludge', 'Smokescreen', 'Selfdestruct', 'Haze', 'Explosion'],
+ 'Persian': ['Bite', 'Pay Day', 'Screech', 'Fury Swipes', 'Slash'],
+ 'Marowak': ['Leer', 'Focus Energy', 'Thrash', 'Bonemerang', 'Rage'],
+ 'Haunter': ['Hypnosis', 'Dream Eater'],
+ 'Alakazam': ['Confusion', 'Disable', 'Psybeam', 'Recover', 'Psychic', 'Reflect'],
+ 'Pidgeotto': ['Sand Attack', 'Quick Attack', 'Whirlwind', 'Wing Attack', 'Agility', 'Mirror Move'],
+ 'Pidgeot': ['Sand Attack', 'Quick Attack', 'Whirlwind', 'Wing Attack', 'Agility', 'Mirror Move'],
+ 'Bulbasaur': ['Leech Seed', 'Vine Whip', 'Poisonpowder', 'Razor Leaf', 'Growth', 'Sleep Powder', 'Solarbeam'],
+ 'Venusaur': ['Leech Seed', 'Vine Whip', 'Poisonpowder', 'Razor Leaf', 'Growth', 'Sleep Powder', 'Solarbeam'],
+ 'Tentacruel': ['Supersonic', 'Wrap', 'Poison Sting', 'Water Gun', 'Constrict', 'Barrier', 'Screech', 'Hydro Pump'],
+ 'Goldeen': ['Supersonic', 'Horn Attack', 'Fury Attack', 'Waterfall', 'Horn Drill', 'Agility'],
+ 'Seaking': ['Supersonic', 'Horn Attack', 'Fury Attack', 'Waterfall', 'Horn Drill', 'Agility'],
+ 'Ponyta': ['Tail Whip', 'Stomp', 'Growl', 'Fire Spin', 'Take Down', 'Agility'],
+ 'Rapidash': ['Tail Whip', 'Stomp', 'Growl', 'Fire Spin', 'Take Down', 'Agility'],
+ 'Rattata': ['Quick Attack', 'Hyper Fang', 'Focus Energy', 'Super Fang'],
+ 'Raticate': ['Quick Attack', 'Hyper Fang', 'Focus Energy', 'Super Fang'],
+ 'Nidorino': ['Horn Attack', 'Poison Sting', 'Focus Energy', 'Fury Attack', 'Horn Drill', 'Double Kick'],
+ 'Nidorina': ['Scratch', 'Poison Sting', 'Tail Whip', 'Bite', 'Fury Swipes', 'Double Kick'],
+ 'Geodude': ['Defense Curl', 'Rock Throw', 'Selfdestruct', 'Harden', 'Earthquake', 'Explosion'],
+ 'Porygon': ['Psybeam', 'Recover', 'Agility', 'Tri Attack'],
+ 'Aerodactyl': ['Supersonic', 'Bite', 'Take Down', 'Hyper Beam'],
+ 'Magnemite': ['Sonicboom', 'Thundershock', 'Supersonic', 'Thunder Wave', 'Swift', 'Screech'],
+ 'Charmander': ['Ember', 'Leer', 'Rage', 'Slash', 'Flamethrower', 'Fire Spin'],
+ 'Squirtle': ['Bubble', 'Water Gun', 'Bite', 'Withdraw', 'Skull Bash', 'Hydro Pump'],
+ 'Charmeleon': ['Ember', 'Leer', 'Rage', 'Slash', 'Flamethrower', 'Fire Spin'],
+ 'Wartortle': ['Bubble', 'Water Gun', 'Bite', 'Withdraw', 'Skull Bash', 'Hydro Pump'],
+ 'Charizard': ['Ember', 'Leer', 'Rage', 'Slash', 'Flamethrower', 'Fire Spin'],
+ 'Oddish': ['Poisonpowder', 'Stun Spore', 'Sleep Powder', 'Acid', 'Petal Dance', 'Solarbeam'],
+ 'Gloom': ['Poisonpowder', 'Stun Spore', 'Sleep Powder', 'Acid', 'Petal Dance', 'Solarbeam'],
+ 'Vileplume': ['Poisonpowder', 'Stun Spore', 'Sleep Powder'],
+ 'Bellsprout': ['Wrap', 'Poisonpowder', 'Sleep Powder', 'Stun Spore', 'Acid', 'Razor Leaf', 'Slam'],
+ 'Weepinbell': ['Wrap', 'Poisonpowder', 'Sleep Powder', 'Stun Spore', 'Acid', 'Razor Leaf', 'Slam'],
+ 'Victreebel': ['Wrap', 'Poisonpowder', 'Sleep Powder']
+}
+
+moves = {
+ 'No Move': {'id': 0, 'power': 0, 'type': 'Typeless', 'accuracy': 0, 'pp': 0},
+ 'Pound': {'id': 1, 'power': 40, 'type': 'Normal', 'accuracy': 100, 'pp': 35},
+ 'Karate Chop': {'id': 2, 'power': 50, 'type': 'Normal', 'accuracy': 100, 'pp': 25},
+ 'Doubleslap': {'id': 3, 'power': 15, 'type': 'Normal', 'accuracy': 85, 'pp': 10},
+ 'Comet Punch': {'id': 4, 'power': 18, 'type': 'Normal', 'accuracy': 85, 'pp': 15},
+ 'Mega Punch': {'id': 5, 'power': 80, 'type': 'Normal', 'accuracy': 85, 'pp': 20},
+ 'Pay Day': {'id': 6, 'power': 40, 'type': 'Normal', 'accuracy': 100, 'pp': 20},
+ 'Fire Punch': {'id': 7, 'power': 75, 'type': 'Fire', 'accuracy': 100, 'pp': 15},
+ 'Ice Punch': {'id': 8, 'power': 75, 'type': 'Ice', 'accuracy': 100, 'pp': 15},
+ 'Thunderpunch': {'id': 9, 'power': 75, 'type': 'Electric', 'accuracy': 100, 'pp': 15},
+ 'Scratch': {'id': 10, 'power': 40, 'type': 'Normal', 'accuracy': 100, 'pp': 35},
+ 'Vicegrip': {'id': 11, 'power': 55, 'type': 'Normal', 'accuracy': 100, 'pp': 30},
+ 'Guillotine': {'id': 12, 'power': 1, 'type': 'Normal', 'accuracy': 30, 'pp': 5},
+ 'Razor Wind': {'id': 13, 'power': 80, 'type': 'Normal', 'accuracy': 75, 'pp': 10},
+ 'Swords Dance': {'id': 14, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 30},
+ 'Cut': {'id': 15, 'power': 50, 'type': 'Normal', 'accuracy': 95, 'pp': 30},
+ 'Gust': {'id': 16, 'power': 40, 'type': 'Normal', 'accuracy': 100, 'pp': 35},
+ 'Wing Attack': {'id': 17, 'power': 35, 'type': 'Flying', 'accuracy': 100, 'pp': 35},
+ 'Whirlwind': {'id': 18, 'power': 0, 'type': 'Normal', 'accuracy': 85, 'pp': 20},
+ 'Fly': {'id': 19, 'power': 70, 'type': 'Flying', 'accuracy': 95, 'pp': 15},
+ 'Bind': {'id': 20, 'power': 15, 'type': 'Normal', 'accuracy': 75, 'pp': 20},
+ 'Slam': {'id': 21, 'power': 80, 'type': 'Normal', 'accuracy': 75, 'pp': 20},
+ 'Vine Whip': {'id': 22, 'power': 35, 'type': 'Grass', 'accuracy': 100, 'pp': 10},
+ 'Stomp': {'id': 23, 'power': 65, 'type': 'Normal', 'accuracy': 100, 'pp': 20},
+ 'Double Kick': {'id': 24, 'power': 30, 'type': 'Fighting', 'accuracy': 100, 'pp': 30},
+ 'Mega Kick': {'id': 25, 'power': 120, 'type': 'Normal', 'accuracy': 75, 'pp': 5},
+ 'Jump Kick': {'id': 26, 'power': 70, 'type': 'Fighting', 'accuracy': 95, 'pp': 25},
+ 'Rolling Kick': {'id': 27, 'power': 60, 'type': 'Fighting', 'accuracy': 85, 'pp': 15},
+ 'Sand Attack': {'id': 28, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 15},
+ 'Headbutt': {'id': 29, 'power': 70, 'type': 'Normal', 'accuracy': 100, 'pp': 15},
+ 'Horn Attack': {'id': 30, 'power': 65, 'type': 'Normal', 'accuracy': 100, 'pp': 25},
+ 'Fury Attack': {'id': 31, 'power': 15, 'type': 'Normal', 'accuracy': 85, 'pp': 20},
+ 'Horn Drill': {'id': 32, 'power': 1, 'type': 'Normal', 'accuracy': 30, 'pp': 5},
+ 'Tackle': {'id': 33, 'power': 35, 'type': 'Normal', 'accuracy': 95, 'pp': 35},
+ 'Body Slam': {'id': 34, 'power': 85, 'type': 'Normal', 'accuracy': 100, 'pp': 15},
+ 'Wrap': {'id': 35, 'power': 15, 'type': 'Normal', 'accuracy': 85, 'pp': 20},
+ 'Take Down': {'id': 36, 'power': 90, 'type': 'Normal', 'accuracy': 85, 'pp': 20},
+ 'Thrash': {'id': 37, 'power': 90, 'type': 'Normal', 'accuracy': 100, 'pp': 20},
+ 'Double Edge': {'id': 38, 'power': 100, 'type': 'Normal', 'accuracy': 100, 'pp': 15},
+ 'Tail Whip': {'id': 39, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 30},
+ 'Poison Sting': {'id': 40, 'power': 15, 'type': 'Poison', 'accuracy': 100, 'pp': 35},
+ 'Twineedle': {'id': 41, 'power': 25, 'type': 'Bug', 'accuracy': 100, 'pp': 20},
+ 'Pin Missile': {'id': 42, 'power': 14, 'type': 'Bug', 'accuracy': 85, 'pp': 20},
+ 'Leer': {'id': 43, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 30},
+ 'Bite': {'id': 44, 'power': 60, 'type': 'Normal', 'accuracy': 100, 'pp': 25},
+ 'Growl': {'id': 45, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 40},
+ 'Roar': {'id': 46, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 20},
+ 'Sing': {'id': 47, 'power': 0, 'type': 'Normal', 'accuracy': 55, 'pp': 15},
+ 'Supersonic': {'id': 48, 'power': 0, 'type': 'Normal', 'accuracy': 55, 'pp': 20},
+ 'Sonicboom': {'id': 49, 'power': 1, 'type': 'Normal', 'accuracy': 90, 'pp': 20},
+ 'Disable': {'id': 50, 'power': 0, 'type': 'Normal', 'accuracy': 55, 'pp': 20},
+ 'Acid': {'id': 51, 'power': 40, 'type': 'Poison', 'accuracy': 100, 'pp': 30},
+ 'Ember': {'id': 52, 'power': 40, 'type': 'Fire', 'accuracy': 100, 'pp': 25},
+ 'Flamethrower': {'id': 53, 'power': 95, 'type': 'Fire', 'accuracy': 100, 'pp': 15},
+ 'Mist': {'id': 54, 'power': 0, 'type': 'Ice', 'accuracy': 100, 'pp': 30},
+ 'Water Gun': {'id': 55, 'power': 40, 'type': 'Water', 'accuracy': 100, 'pp': 25},
+ 'Hydro Pump': {'id': 56, 'power': 120, 'type': 'Water', 'accuracy': 80, 'pp': 5},
+ 'Surf': {'id': 57, 'power': 95, 'type': 'Water', 'accuracy': 100, 'pp': 15},
+ 'Ice Beam': {'id': 58, 'power': 95, 'type': 'Ice', 'accuracy': 100, 'pp': 10},
+ 'Blizzard': {'id': 59, 'power': 120, 'type': 'Ice', 'accuracy': 90, 'pp': 5},
+ 'Psybeam': {'id': 60, 'power': 65, 'type': 'Psychic', 'accuracy': 100, 'pp': 20},
+ 'Bubblebeam': {'id': 61, 'power': 65, 'type': 'Water', 'accuracy': 100, 'pp': 20},
+ 'Aurora Beam': {'id': 62, 'power': 65, 'type': 'Ice', 'accuracy': 100, 'pp': 20},
+ 'Hyper Beam': {'id': 63, 'power': 150, 'type': 'Normal', 'accuracy': 90, 'pp': 5},
+ 'Peck': {'id': 64, 'power': 35, 'type': 'Flying', 'accuracy': 100, 'pp': 35},
+ 'Drill Peck': {'id': 65, 'power': 80, 'type': 'Flying', 'accuracy': 100, 'pp': 20},
+ 'Submission': {'id': 66, 'power': 80, 'type': 'Fighting', 'accuracy': 80, 'pp': 25},
+ 'Low Kick': {'id': 67, 'power': 50, 'type': 'Fighting', 'accuracy': 90, 'pp': 20},
+ 'Counter': {'id': 68, 'power': 1, 'type': 'Fighting', 'accuracy': 100, 'pp': 20},
+ 'Seismic Toss': {'id': 69, 'power': 1, 'type': 'Fighting', 'accuracy': 100, 'pp': 20},
+ 'Strength': {'id': 70, 'power': 80, 'type': 'Normal', 'accuracy': 100, 'pp': 15},
+ 'Absorb': {'id': 71, 'power': 20, 'type': 'Grass', 'accuracy': 100, 'pp': 20},
+ 'Mega Drain': {'id': 72, 'power': 40, 'type': 'Grass', 'accuracy': 100, 'pp': 10},
+ 'Leech Seed': {'id': 73, 'power': 0, 'type': 'Grass', 'accuracy': 90, 'pp': 10},
+ 'Growth': {'id': 74, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 40},
+ 'Razor Leaf': {'id': 75, 'power': 55, 'type': 'Grass', 'accuracy': 95, 'pp': 25},
+ 'Solarbeam': {'id': 76, 'power': 120, 'type': 'Grass', 'accuracy': 100, 'pp': 10},
+ 'Poisonpowder': {'id': 77, 'power': 0, 'type': 'Poison', 'accuracy': 75, 'pp': 35},
+ 'Stun Spore': {'id': 78, 'power': 0, 'type': 'Grass', 'accuracy': 75, 'pp': 30},
+ 'Sleep Powder': {'id': 79, 'power': 0, 'type': 'Grass', 'accuracy': 75, 'pp': 15},
+ 'Petal Dance': {'id': 80, 'power': 70, 'type': 'Grass', 'accuracy': 100, 'pp': 20},
+ 'String Shot': {'id': 81, 'power': 0, 'type': 'Bug', 'accuracy': 95, 'pp': 40},
+ 'Dragon Rage': {'id': 82, 'power': 1, 'type': 'Dragon', 'accuracy': 100, 'pp': 10},
+ 'Fire Spin': {'id': 83, 'power': 15, 'type': 'Fire', 'accuracy': 70, 'pp': 15},
+ 'Thundershock': {'id': 84, 'power': 40, 'type': 'Electric', 'accuracy': 100, 'pp': 30},
+ 'Thunderbolt': {'id': 85, 'power': 95, 'type': 'Electric', 'accuracy': 100, 'pp': 15},
+ 'Thunder Wave': {'id': 86, 'power': 0, 'type': 'Electric', 'accuracy': 100, 'pp': 20},
+ 'Thunder': {'id': 87, 'power': 120, 'type': 'Electric', 'accuracy': 70, 'pp': 10},
+ 'Rock Throw': {'id': 88, 'power': 50, 'type': 'Rock', 'accuracy': 65, 'pp': 15},
+ 'Earthquake': {'id': 89, 'power': 100, 'type': 'Ground', 'accuracy': 100, 'pp': 10},
+ 'Fissure': {'id': 90, 'power': 1, 'type': 'Ground', 'accuracy': 30, 'pp': 5},
+ 'Dig': {'id': 91, 'power': 100, 'type': 'Ground', 'accuracy': 100, 'pp': 10},
+ 'Toxic': {'id': 92, 'power': 0, 'type': 'Poison', 'accuracy': 85, 'pp': 10},
+ 'Confusion': {'id': 93, 'power': 50, 'type': 'Psychic', 'accuracy': 100, 'pp': 25},
+ 'Psychic': {'id': 94, 'power': 90, 'type': 'Psychic', 'accuracy': 100, 'pp': 10},
+ 'Hypnosis': {'id': 95, 'power': 0, 'type': 'Psychic', 'accuracy': 60, 'pp': 20},
+ 'Meditate': {'id': 96, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 40},
+ 'Agility': {'id': 97, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 30},
+ 'Quick Attack': {'id': 98, 'power': 40, 'type': 'Normal', 'accuracy': 100, 'pp': 30},
+ 'Rage': {'id': 99, 'power': 20, 'type': 'Normal', 'accuracy': 100, 'pp': 20},
+ 'Teleport': {'id': 100, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 20},
+ 'Night Shade': {'id': 101, 'power': 0, 'type': 'Ghost', 'accuracy': 100, 'pp': 15},
+ 'Mimic': {'id': 102, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 10},
+ 'Screech': {'id': 103, 'power': 0, 'type': 'Normal', 'accuracy': 85, 'pp': 40},
+ 'Double Team': {'id': 104, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 15},
+ 'Recover': {'id': 105, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 20},
+ 'Harden': {'id': 106, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 30},
+ 'Minimize': {'id': 107, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 20},
+ 'Smokescreen': {'id': 108, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 20},
+ 'Confuse Ray': {'id': 109, 'power': 0, 'type': 'Ghost', 'accuracy': 100, 'pp': 10},
+ 'Withdraw': {'id': 110, 'power': 0, 'type': 'Water', 'accuracy': 100, 'pp': 40},
+ 'Defense Curl': {'id': 111, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 40},
+ 'Barrier': {'id': 112, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 30},
+ 'Light Screen': {'id': 113, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 30},
+ 'Haze': {'id': 114, 'power': 0, 'type': 'Ice', 'accuracy': 100, 'pp': 30},
+ 'Reflect': {'id': 115, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 20},
+ 'Focus Energy': {'id': 116, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 30},
+ 'Bide': {'id': 117, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 10},
+ 'Metronome': {'id': 118, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 10},
+ 'Mirror Move': {'id': 119, 'power': 0, 'type': 'Flying', 'accuracy': 100, 'pp': 20},
+ 'Selfdestruct': {'id': 120, 'power': 130, 'type': 'Normal', 'accuracy': 100, 'pp': 5},
+ 'Egg Bomb': {'id': 121, 'power': 100, 'type': 'Normal', 'accuracy': 75, 'pp': 10},
+ 'Lick': {'id': 122, 'power': 20, 'type': 'Ghost', 'accuracy': 100, 'pp': 30},
+ 'Smog': {'id': 123, 'power': 20, 'type': 'Poison', 'accuracy': 70, 'pp': 20},
+ 'Sludge': {'id': 124, 'power': 65, 'type': 'Poison', 'accuracy': 100, 'pp': 20},
+ 'Bone Club': {'id': 125, 'power': 65, 'type': 'Ground', 'accuracy': 85, 'pp': 20},
+ 'Fire Blast': {'id': 126, 'power': 120, 'type': 'Fire', 'accuracy': 85, 'pp': 5},
+ 'Waterfall': {'id': 127, 'power': 80, 'type': 'Water', 'accuracy': 100, 'pp': 15},
+ 'Clamp': {'id': 128, 'power': 35, 'type': 'Water', 'accuracy': 75, 'pp': 10},
+ 'Swift': {'id': 129, 'power': 60, 'type': 'Normal', 'accuracy': 100, 'pp': 20},
+ 'Skull Bash': {'id': 130, 'power': 100, 'type': 'Normal', 'accuracy': 100, 'pp': 15},
+ 'Spike Cannon': {'id': 131, 'power': 20, 'type': 'Normal', 'accuracy': 100, 'pp': 15},
+ 'Constrict': {'id': 132, 'power': 10, 'type': 'Normal', 'accuracy': 100, 'pp': 35},
+ 'Amnesia': {'id': 133, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 20},
+ 'Kinesis': {'id': 134, 'power': 0, 'type': 'Psychic', 'accuracy': 80, 'pp': 15},
+ 'Softboiled': {'id': 135, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 10},
+ 'Hi Jump Kick': {'id': 136, 'power': 85, 'type': 'Fighting', 'accuracy': 90, 'pp': 20},
+ 'Glare': {'id': 137, 'power': 0, 'type': 'Normal', 'accuracy': 75, 'pp': 30},
+ 'Dream Eater': {'id': 138, 'power': 100, 'type': 'Psychic', 'accuracy': 100, 'pp': 15},
+ 'Poison Gas': {'id': 139, 'power': 0, 'type': 'Poison', 'accuracy': 55, 'pp': 40},
+ 'Barrage': {'id': 140, 'power': 15, 'type': 'Normal', 'accuracy': 85, 'pp': 20},
+ 'Leech Life': {'id': 141, 'power': 20, 'type': 'Bug', 'accuracy': 100, 'pp': 15},
+ 'Lovely Kiss': {'id': 142, 'power': 0, 'type': 'Normal', 'accuracy': 75, 'pp': 10},
+ 'Sky Attack': {'id': 143, 'power': 140, 'type': 'Flying', 'accuracy': 90, 'pp': 5},
+ 'Transform': {'id': 144, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 10},
+ 'Bubble': {'id': 145, 'power': 20, 'type': 'Water', 'accuracy': 100, 'pp': 30},
+ 'Dizzy Punch': {'id': 146, 'power': 70, 'type': 'Normal', 'accuracy': 100, 'pp': 10},
+ 'Spore': {'id': 147, 'power': 0, 'type': 'Grass', 'accuracy': 100, 'pp': 15},
+ 'Flash': {'id': 148, 'power': 0, 'type': 'Normal', 'accuracy': 70, 'pp': 20},
+ 'Psywave': {'id': 149, 'power': 1, 'type': 'Psychic', 'accuracy': 80, 'pp': 15},
+ 'Splash': {'id': 150, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 40},
+ 'Acid Armor': {'id': 151, 'power': 0, 'type': 'Poison', 'accuracy': 100, 'pp': 40},
+ 'Crabhammer': {'id': 152, 'power': 90, 'type': 'Water', 'accuracy': 85, 'pp': 10},
+ 'Explosion': {'id': 153, 'power': 170, 'type': 'Normal', 'accuracy': 100, 'pp': 5},
+ 'Fury Swipes': {'id': 154, 'power': 18, 'type': 'Normal', 'accuracy': 80, 'pp': 15},
+ 'Bonemerang': {'id': 155, 'power': 50, 'type': 'Ground', 'accuracy': 90, 'pp': 10},
+ 'Rest': {'id': 156, 'power': 0, 'type': 'Psychic', 'accuracy': 100, 'pp': 10},
+ 'Rock Slide': {'id': 157, 'power': 75, 'type': 'Rock', 'accuracy': 90, 'pp': 10},
+ 'Hyper Fang': {'id': 158, 'power': 80, 'type': 'Normal', 'accuracy': 90, 'pp': 15},
+ 'Sharpen': {'id': 159, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 30},
+ 'Conversion': {'id': 160, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 30},
+ 'Tri Attack': {'id': 161, 'power': 80, 'type': 'Normal', 'accuracy': 100, 'pp': 10},
+ 'Super Fang': {'id': 162, 'power': 1, 'type': 'Normal', 'accuracy': 90, 'pp': 10},
+ 'Slash': {'id': 163, 'power': 70, 'type': 'Normal', 'accuracy': 100, 'pp': 20},
+ 'Substitute': {'id': 164, 'power': 0, 'type': 'Normal', 'accuracy': 100, 'pp': 10},
+ #'Struggle': {'id': 165, 'power': 50, 'type': 'Struggle_Type', 'accuracy': 100, 'pp': 10}
+}
+
+encounter_tables = {'Wild_Super_Rod_A': 2, 'Wild_Super_Rod_B': 2, 'Wild_Super_Rod_C': 3, 'Wild_Super_Rod_D': 2,
+ 'Wild_Super_Rod_E': 2, 'Wild_Super_Rod_F': 4, 'Wild_Super_Rod_G': 4, 'Wild_Super_Rod_H': 4,
+ 'Wild_Super_Rod_I': 4, 'Wild_Super_Rod_J': 4, 'Wild_Route1': 10, 'Wild_Route2': 10,
+ 'Wild_Route22': 10, 'Wild_ViridianForest': 10, 'Wild_Route3': 10, 'Wild_MtMoon1F': 10,
+ 'Wild_MtMoonB1F': 10, 'Wild_MtMoonB2F': 10, 'Wild_Route4': 10, 'Wild_Route24': 10,
+ 'Wild_Route25': 10, 'Wild_Route9': 10, 'Wild_Route5': 10, 'Wild_Route6': 10,
+ 'Wild_Route11': 10, 'Wild_RockTunnel1F': 10, 'Wild_RockTunnelB1F': 10, 'Wild_Route10': 10,
+ 'Wild_Route12': 10, 'Wild_Route8': 10, 'Wild_Route7': 10, 'Wild_PokemonTower3F': 10,
+ 'Wild_PokemonTower4F': 10, 'Wild_PokemonTower5F': 10, 'Wild_PokemonTower6F': 10,
+ 'Wild_PokemonTower7F': 10, 'Wild_Route13': 10, 'Wild_Route14': 10, 'Wild_Route15': 10,
+ 'Wild_Route16': 10, 'Wild_Route17': 10, 'Wild_Route18': 10, 'Wild_SafariZoneCenter': 10,
+ 'Wild_SafariZoneEast': 10, 'Wild_SafariZoneNorth': 10, 'Wild_SafariZoneWest': 10,
+ 'Wild_SeaRoutes': 10, 'Wild_SeafoamIslands1F': 10, 'Wild_SeafoamIslandsB1F': 10,
+ 'Wild_SeafoamIslandsB2F': 10, 'Wild_SeafoamIslandsB3F': 10, 'Wild_SeafoamIslandsB4F': 10,
+ 'Wild_PokemonMansion1F': 10, 'Wild_PokemonMansion2F': 10, 'Wild_PokemonMansion3F': 10,
+ 'Wild_PokemonMansionB1F': 10, 'Wild_Route21': 10, 'Wild_CeruleanCave1F': 10,
+ 'Wild_CeruleanCave2F': 10, 'Wild_CeruleanCaveB1F': 10, 'Wild_PowerPlant': 10,
+ 'Wild_Route23': 10, 'Wild_VictoryRoad2F': 10, 'Wild_VictoryRoad3F': 10,
+ 'Wild_VictoryRoad1F': 10, 'Wild_DiglettsCave': 10, 'Wild_Good_Rod': 2}
+
+hm_moves = ["Cut", "Fly", "Surf", "Strength", "Flash"]
+
+tm_moves = [
+ 'Mega Punch', 'Razor Wind', 'Swords Dance', 'Whirlwind', 'Mega Kick', 'Toxic', 'Horn Drill', 'Body Slam',
+ 'Take Down', 'Double Edge', 'Bubblebeam', 'Water Gun', 'Ice Beam', 'Blizzard', 'Hyper Beam', 'Pay Day',
+ 'Submission', 'Counter', 'Seismic Toss', 'Rage', 'Mega Drain', 'Solarbeam', 'Dragon Rage', 'Thunderbolt', 'Thunder',
+ 'Earthquake', 'Fissure', 'Dig', 'Psychic', 'Teleport', 'Mimic', 'Double Team', 'Reflect', 'Bide', 'Metronome',
+ 'Selfdestruct', 'Egg Bomb', 'Fire Blast', 'Swift', 'Skull Bash', 'Softboiled', 'Dream Eater', 'Sky Attack', 'Rest',
+ 'Thunder Wave', 'Psywave', 'Explosion', 'Rock Slide', 'Tri Attack', 'Substitute'
+]
+
+
+
+first_stage_pokemon = [pokemon for pokemon in pokemon_data.keys() if pokemon not in evolves_from]
+legendary_pokemon = ["Articuno", "Zapdos", "Moltres", "Mewtwo", "Mew"]
+
diff --git a/worlds/pokemon_rb/regions.py b/worlds/pokemon_rb/regions.py
new file mode 100644
index 0000000000..1650e640cb
--- /dev/null
+++ b/worlds/pokemon_rb/regions.py
@@ -0,0 +1,305 @@
+
+from BaseClasses import MultiWorld, Region, Entrance, RegionType, LocationProgressType
+from worlds.generic.Rules import add_item_rule
+from .locations import location_data, PokemonRBLocation
+
+
+def create_region(world: MultiWorld, player: int, name: str, locations_per_region=None, exits=None):
+ ret = Region(name, RegionType.Generic, name, player, world)
+ for location in locations_per_region.get(name, []):
+ if (world.randomize_hidden_items[player].value or "Hidden" not in location.name) and \
+ (world.extra_key_items[player].value or name != "Rock Tunnel B1F" or "Item" not in location.name) and \
+ (world.tea[player].value or location.name != "Celadon City - Mansion Lady"):
+ location.parent_region = ret
+ ret.locations.append(location)
+ if world.randomize_hidden_items[player].value == 2 and "Hidden" in location.name:
+ location.progress_type = LocationProgressType.EXCLUDED
+ add_item_rule(location, lambda i: not (i.advancement or i.useful))
+ if exits:
+ for exit in exits:
+ ret.exits.append(Entrance(player, exit, ret))
+ locations_per_region[name] = []
+ return ret
+
+
+def create_regions(world: MultiWorld, player: int):
+ locations_per_region = {}
+ for location in location_data:
+ locations_per_region.setdefault(location.region, [])
+ locations_per_region[location.region].append(PokemonRBLocation(player, location.name, location.address,
+ location.rom_address))
+ regions = [
+ create_region(world, player, "Menu", locations_per_region),
+ create_region(world, player, "Anywhere", locations_per_region),
+ create_region(world, player, "Fossil", locations_per_region),
+ create_region(world, player, "Pallet Town", locations_per_region),
+ create_region(world, player, "Route 1", locations_per_region),
+ create_region(world, player, "Viridian City", locations_per_region),
+ create_region(world, player, "Viridian City North", locations_per_region),
+ create_region(world, player, "Viridian Gym", locations_per_region),
+ create_region(world, player, "Route 2", locations_per_region),
+ create_region(world, player, "Route 2 East", locations_per_region),
+ create_region(world, player, "Diglett's Cave", locations_per_region),
+ create_region(world, player, "Route 22", locations_per_region),
+ create_region(world, player, "Route 23 South", locations_per_region),
+ create_region(world, player, "Route 23 North", locations_per_region),
+ create_region(world, player, "Viridian Forest", locations_per_region),
+ create_region(world, player, "Pewter City", locations_per_region),
+ create_region(world, player, "Pewter Gym", locations_per_region),
+ create_region(world, player, "Route 3", locations_per_region),
+ create_region(world, player, "Mt Moon 1F", locations_per_region),
+ create_region(world, player, "Mt Moon B1F", locations_per_region),
+ create_region(world, player, "Mt Moon B2F", locations_per_region),
+ create_region(world, player, "Route 4", locations_per_region),
+ create_region(world, player, "Cerulean City", locations_per_region),
+ create_region(world, player, "Cerulean Gym", locations_per_region),
+ create_region(world, player, "Route 24", locations_per_region),
+ create_region(world, player, "Route 25", locations_per_region),
+ create_region(world, player, "Route 9", locations_per_region),
+ create_region(world, player, "Route 10 North", locations_per_region),
+ create_region(world, player, "Rock Tunnel 1F", locations_per_region),
+ create_region(world, player, "Rock Tunnel B1F", locations_per_region),
+ create_region(world, player, "Power Plant", locations_per_region),
+ create_region(world, player, "Route 10 South", locations_per_region),
+ create_region(world, player, "Lavender Town", locations_per_region),
+ create_region(world, player, "Pokemon Tower 1F", locations_per_region),
+ create_region(world, player, "Pokemon Tower 2F", locations_per_region),
+ create_region(world, player, "Pokemon Tower 3F", locations_per_region),
+ create_region(world, player, "Pokemon Tower 4F", locations_per_region),
+ create_region(world, player, "Pokemon Tower 5F", locations_per_region),
+ create_region(world, player, "Pokemon Tower 6F", locations_per_region),
+ create_region(world, player, "Pokemon Tower 7F", locations_per_region),
+ create_region(world, player, "Route 5", locations_per_region),
+ create_region(world, player, "Saffron City", locations_per_region),
+ create_region(world, player, "Saffron Gym", locations_per_region),
+ create_region(world, player, "Copycat's House", locations_per_region),
+ create_region(world, player, "Underground Tunnel North-South", locations_per_region),
+ create_region(world, player, "Route 6", locations_per_region),
+ create_region(world, player, "Vermilion City", locations_per_region),
+ create_region(world, player, "Vermilion Gym", locations_per_region),
+ create_region(world, player, "S.S. Anne 1F", locations_per_region),
+ create_region(world, player, "S.S. Anne B1F", locations_per_region),
+ create_region(world, player, "S.S. Anne 2F", locations_per_region),
+ create_region(world, player, "Route 11", locations_per_region),
+ create_region(world, player, "Route 11 East", locations_per_region),
+ create_region(world, player, "Route 12 North", locations_per_region),
+ create_region(world, player, "Route 12 South", locations_per_region),
+ create_region(world, player, "Route 12 Grass", locations_per_region),
+ create_region(world, player, "Route 12 West", locations_per_region),
+ create_region(world, player, "Route 7", locations_per_region),
+ create_region(world, player, "Underground Tunnel West-East", locations_per_region),
+ create_region(world, player, "Route 8", locations_per_region),
+ create_region(world, player, "Route 8 Grass", locations_per_region),
+ create_region(world, player, "Celadon City", locations_per_region),
+ create_region(world, player, "Celadon Prize Corner", locations_per_region),
+ create_region(world, player, "Celadon Gym", locations_per_region),
+ create_region(world, player, "Route 16", locations_per_region),
+ create_region(world, player, "Route 16 North", locations_per_region),
+ create_region(world, player, "Route 17", locations_per_region),
+ create_region(world, player, "Route 18", locations_per_region),
+ create_region(world, player, "Fuchsia City", locations_per_region),
+ create_region(world, player, "Fuchsia Gym", locations_per_region),
+ create_region(world, player, "Safari Zone Gate", locations_per_region),
+ create_region(world, player, "Safari Zone Center", locations_per_region),
+ create_region(world, player, "Safari Zone East", locations_per_region),
+ create_region(world, player, "Safari Zone North", locations_per_region),
+ create_region(world, player, "Safari Zone West", locations_per_region),
+ create_region(world, player, "Route 15", locations_per_region),
+ create_region(world, player, "Route 14", locations_per_region),
+ create_region(world, player, "Route 13", locations_per_region),
+ create_region(world, player, "Route 19", locations_per_region),
+ create_region(world, player, "Route 20 East", locations_per_region),
+ create_region(world, player, "Route 20 West", locations_per_region),
+ create_region(world, player, "Seafoam Islands 1F", locations_per_region),
+ create_region(world, player, "Seafoam Islands B1F", locations_per_region),
+ create_region(world, player, "Seafoam Islands B2F", locations_per_region),
+ create_region(world, player, "Seafoam Islands B3F", locations_per_region),
+ create_region(world, player, "Seafoam Islands B4F", locations_per_region),
+ create_region(world, player, "Cinnabar Island", locations_per_region),
+ create_region(world, player, "Cinnabar Gym", locations_per_region),
+ create_region(world, player, "Route 21", locations_per_region),
+ create_region(world, player, "Silph Co 1F", locations_per_region),
+ create_region(world, player, "Silph Co 2F", locations_per_region),
+ create_region(world, player, "Silph Co 3F", locations_per_region),
+ create_region(world, player, "Silph Co 4F", locations_per_region),
+ create_region(world, player, "Silph Co 5F", locations_per_region),
+ create_region(world, player, "Silph Co 6F", locations_per_region),
+ create_region(world, player, "Silph Co 7F", locations_per_region),
+ create_region(world, player, "Silph Co 8F", locations_per_region),
+ create_region(world, player, "Silph Co 9F", locations_per_region),
+ create_region(world, player, "Silph Co 10F", locations_per_region),
+ create_region(world, player, "Silph Co 11F", locations_per_region),
+ create_region(world, player, "Rocket Hideout B1F", locations_per_region),
+ create_region(world, player, "Rocket Hideout B2F", locations_per_region),
+ create_region(world, player, "Rocket Hideout B3F", locations_per_region),
+ create_region(world, player, "Rocket Hideout B4F", locations_per_region),
+ create_region(world, player, "Pokemon Mansion 1F", locations_per_region),
+ create_region(world, player, "Pokemon Mansion 2F", locations_per_region),
+ create_region(world, player, "Pokemon Mansion 3F", locations_per_region),
+ create_region(world, player, "Pokemon Mansion B1F", locations_per_region),
+ create_region(world, player, "Victory Road 1F", locations_per_region),
+ create_region(world, player, "Victory Road 2F", locations_per_region),
+ create_region(world, player, "Victory Road 3F", locations_per_region),
+ create_region(world, player, "Indigo Plateau", locations_per_region),
+ create_region(world, player, "Cerulean Cave 1F", locations_per_region),
+ create_region(world, player, "Cerulean Cave 2F", locations_per_region),
+ create_region(world, player, "Cerulean Cave B1F", locations_per_region),
+ create_region(world, player, "Evolution", locations_per_region),
+ ]
+ world.regions += regions
+ connect(world, player, "Menu", "Anywhere", one_way=True)
+ connect(world, player, "Menu", "Pallet Town", one_way=True)
+ connect(world, player, "Menu", "Fossil", lambda state: state.pokemon_rb_fossil_checks(
+ state.world.second_fossil_check_condition[player].value, player), one_way=True)
+ connect(world, player, "Pallet Town", "Route 1")
+ connect(world, player, "Route 1", "Viridian City")
+ connect(world, player, "Viridian City", "Route 22")
+ connect(world, player, "Route 22", "Route 23 South",
+ lambda state: state.pokemon_rb_has_badges(state.world.victory_road_condition[player].value, player))
+ connect(world, player, "Route 23 South", "Route 23 North", lambda state: state.pokemon_rb_can_surf(player))
+ connect(world, player, "Viridian City North", "Viridian Gym", lambda state:
+ state.pokemon_rb_has_badges(state.world.viridian_gym_condition[player].value, player), one_way=True)
+ connect(world, player, "Route 2", "Route 2 East", lambda state: state.pokemon_rb_can_cut(player))
+ connect(world, player, "Route 2 East", "Diglett's Cave", lambda state: state.pokemon_rb_can_cut(player))
+ connect(world, player, "Route 2", "Viridian City North")
+ connect(world, player, "Route 2", "Viridian Forest")
+ connect(world, player, "Route 2", "Pewter City")
+ connect(world, player, "Pewter City", "Pewter Gym", one_way=True)
+ connect(world, player, "Pewter City", "Route 3")
+ connect(world, player, "Route 4", "Route 3", one_way=True)
+ connect(world, player, "Mt Moon 1F", "Mt Moon B1F", one_way=True)
+ connect(world, player, "Mt Moon B1F", "Mt Moon B2F", one_way=True)
+ connect(world, player, "Mt Moon B1F", "Route 4", one_way=True)
+ connect(world, player, "Route 4", "Cerulean City")
+ connect(world, player, "Cerulean City", "Cerulean Gym", one_way=True)
+ connect(world, player, "Cerulean City", "Route 24", one_way=True)
+ connect(world, player, "Route 24", "Route 25", one_way=True)
+ connect(world, player, "Cerulean City", "Route 9", lambda state: state.pokemon_rb_can_cut(player))
+ connect(world, player, "Route 9", "Route 10 North")
+ connect(world, player, "Route 10 North", "Rock Tunnel 1F", lambda state: state.pokemon_rb_can_flash(player))
+ connect(world, player, "Route 10 North", "Power Plant", lambda state: state.pokemon_rb_can_surf(player) and
+ (state.has("Plant Key", player) or not state.world.extra_key_items[player].value), one_way=True)
+ connect(world, player, "Rock Tunnel 1F", "Route 10 South", lambda state: state.pokemon_rb_can_flash(player))
+ connect(world, player, "Rock Tunnel 1F", "Rock Tunnel B1F")
+ connect(world, player, "Lavender Town", "Pokemon Tower 1F", one_way=True)
+ connect(world, player, "Lavender Town", "Pokemon Tower 1F", one_way=True)
+ connect(world, player, "Pokemon Tower 1F", "Pokemon Tower 2F", one_way=True)
+ connect(world, player, "Pokemon Tower 2F", "Pokemon Tower 3F", one_way=True)
+ connect(world, player, "Pokemon Tower 3F", "Pokemon Tower 4F", one_way=True)
+ connect(world, player, "Pokemon Tower 4F", "Pokemon Tower 5F", one_way=True)
+ connect(world, player, "Pokemon Tower 5F", "Pokemon Tower 6F", one_way=True)
+ connect(world, player, "Pokemon Tower 6F", "Pokemon Tower 7F", lambda state: state.has("Silph Scope", player))
+ connect(world, player, "Cerulean City", "Route 5")
+ connect(world, player, "Route 5", "Saffron City", lambda state: state.pokemon_rb_can_pass_guards(player))
+ connect(world, player, "Route 5", "Underground Tunnel North-South")
+ connect(world, player, "Route 6", "Underground Tunnel North-South")
+ connect(world, player, "Route 6", "Saffron City", lambda state: state.pokemon_rb_can_pass_guards(player))
+ connect(world, player, "Route 7", "Saffron City", lambda state: state.pokemon_rb_can_pass_guards(player))
+ connect(world, player, "Route 8", "Saffron City", lambda state: state.pokemon_rb_can_pass_guards(player))
+ connect(world, player, "Saffron City", "Copycat's House", lambda state: state.has("Silph Co Liberated", player), one_way=True)
+ connect(world, player, "Saffron City", "Saffron Gym", lambda state: state.has("Silph Co Liberated", player), one_way=True)
+ connect(world, player, "Route 6", "Vermilion City")
+ connect(world, player, "Vermilion City", "Vermilion Gym", lambda state: state.pokemon_rb_can_surf(player) or state.pokemon_rb_can_cut(player), one_way=True)
+ connect(world, player, "Vermilion City", "S.S. Anne 1F", lambda state: state.has("S.S. Ticket", player), one_way=True)
+ connect(world, player, "S.S. Anne 1F", "S.S. Anne 2F", one_way=True)
+ connect(world, player, "S.S. Anne 1F", "S.S. Anne B1F", one_way=True)
+ connect(world, player, "Vermilion City", "Route 11")
+ connect(world, player, "Vermilion City", "Diglett's Cave")
+ connect(world, player, "Route 12 West", "Route 11 East", lambda state: state.pokemon_rb_can_strength(player) or not state.world.extra_strength_boulders[player].value)
+ connect(world, player, "Route 12 North", "Route 12 South", lambda state: state.has("Poke Flute", player) or state.pokemon_rb_can_surf( player))
+ connect(world, player, "Route 12 West", "Route 12 North", lambda state: state.has("Poke Flute", player))
+ connect(world, player, "Route 12 West", "Route 12 South", lambda state: state.has("Poke Flute", player))
+ connect(world, player, "Route 12 South", "Route 12 Grass", lambda state: state.pokemon_rb_can_cut(player))
+ connect(world, player, "Route 12 North", "Lavender Town")
+ connect(world, player, "Route 7", "Lavender Town")
+ connect(world, player, "Route 10 South", "Lavender Town")
+ connect(world, player, "Route 7", "Underground Tunnel West-East")
+ connect(world, player, "Route 8", "Underground Tunnel West-East")
+ connect(world, player, "Route 8", "Celadon City")
+ connect(world, player, "Route 8", "Route 8 Grass", lambda state: state.pokemon_rb_can_cut(player), one_way=True)
+ connect(world, player, "Route 7", "Celadon City")
+ connect(world, player, "Celadon City", "Celadon Gym", lambda state: state.pokemon_rb_can_cut(player), one_way=True)
+ connect(world, player, "Celadon City", "Celadon Prize Corner")
+ connect(world, player, "Celadon City", "Route 16")
+ connect(world, player, "Route 16", "Route 16 North", lambda state: state.pokemon_rb_can_cut(player), one_way=True)
+ connect(world, player, "Route 16", "Route 17", lambda state: state.has("Poke Flute", player) and state.has("Bicycle", player))
+ connect(world, player, "Route 17", "Route 18", lambda state: state.has("Bicycle", player))
+ connect(world, player, "Fuchsia City", "Fuchsia Gym", one_way=True)
+ connect(world, player, "Fuchsia City", "Route 18")
+ connect(world, player, "Fuchsia City", "Safari Zone Gate", one_way=True)
+ connect(world, player, "Safari Zone Gate", "Safari Zone Center", lambda state: state.has("Safari Pass", player) or not state.world.extra_key_items[player].value, one_way=True)
+ connect(world, player, "Safari Zone Center", "Safari Zone East", one_way=True)
+ connect(world, player, "Safari Zone Center", "Safari Zone West", one_way=True)
+ connect(world, player, "Safari Zone Center", "Safari Zone North", one_way=True)
+ connect(world, player, "Fuchsia City", "Route 15")
+ connect(world, player, "Route 15", "Route 14")
+ connect(world, player, "Route 14", "Route 13")
+ connect(world, player, "Route 13", "Route 12 South", lambda state: state.pokemon_rb_can_strength(player) or state.pokemon_rb_can_surf(player) or not state.world.extra_strength_boulders[player].value)
+ connect(world, player, "Fuchsia City", "Route 19", lambda state: state.pokemon_rb_can_surf(player))
+ connect(world, player, "Route 20 East", "Route 19")
+ connect(world, player, "Route 20 West", "Cinnabar Island", lambda state: state.pokemon_rb_can_surf(player))
+ connect(world, player, "Route 20 West", "Seafoam Islands 1F")
+ connect(world, player, "Route 20 East", "Seafoam Islands 1F", one_way=True)
+ connect(world, player, "Seafoam Islands 1F", "Route 20 East", lambda state: state.pokemon_rb_can_strength(player), one_way=True)
+ connect(world, player, "Viridian City", "Viridian City North", lambda state: state.has("Oak's Parcel", player) or state.world.old_man[player].value == 2 or state.pokemon_rb_can_cut(player))
+ connect(world, player, "Route 3", "Mt Moon 1F", one_way=True)
+ connect(world, player, "Route 11", "Route 11 East", lambda state: state.pokemon_rb_can_strength(player))
+ connect(world, player, "Cinnabar Island", "Cinnabar Gym", lambda state: state.has("Secret Key", player), one_way=True)
+ connect(world, player, "Cinnabar Island", "Pokemon Mansion 1F", lambda state: state.has("Mansion Key", player) or not state.world.extra_key_items[player].value, one_way=True)
+ connect(world, player, "Seafoam Islands 1F", "Seafoam Islands B1F", one_way=True)
+ connect(world, player, "Seafoam Islands B1F", "Seafoam Islands B2F", one_way=True)
+ connect(world, player, "Seafoam Islands B2F", "Seafoam Islands B3F", one_way=True)
+ connect(world, player, "Seafoam Islands B3F", "Seafoam Islands B4F", one_way=True)
+ connect(world, player, "Route 21", "Cinnabar Island", lambda state: state.pokemon_rb_can_surf(player))
+ connect(world, player, "Pallet Town", "Route 21", lambda state: state.pokemon_rb_can_surf(player))
+ connect(world, player, "Saffron City", "Silph Co 1F", lambda state: state.has("Fuji Saved", player), one_way=True)
+ connect(world, player, "Silph Co 1F", "Silph Co 2F", one_way=True)
+ connect(world, player, "Silph Co 2F", "Silph Co 3F", one_way=True)
+ connect(world, player, "Silph Co 3F", "Silph Co 4F", one_way=True)
+ connect(world, player, "Silph Co 4F", "Silph Co 5F", one_way=True)
+ connect(world, player, "Silph Co 5F", "Silph Co 6F", one_way=True)
+ connect(world, player, "Silph Co 6F", "Silph Co 7F", one_way=True)
+ connect(world, player, "Silph Co 7F", "Silph Co 8F", one_way=True)
+ connect(world, player, "Silph Co 8F", "Silph Co 9F", one_way=True)
+ connect(world, player, "Silph Co 9F", "Silph Co 10F", one_way=True)
+ connect(world, player, "Silph Co 10F", "Silph Co 11F", one_way=True)
+ connect(world, player, "Celadon City", "Rocket Hideout B1F", lambda state: state.has("Hideout Key", player) or not state.world.extra_key_items[player].value, one_way=True)
+ connect(world, player, "Rocket Hideout B1F", "Rocket Hideout B2F", one_way=True)
+ connect(world, player, "Rocket Hideout B2F", "Rocket Hideout B3F", one_way=True)
+ connect(world, player, "Rocket Hideout B3F", "Rocket Hideout B4F", one_way=True)
+ connect(world, player, "Pokemon Mansion 1F", "Pokemon Mansion 2F", one_way=True)
+ connect(world, player, "Pokemon Mansion 2F", "Pokemon Mansion 3F", one_way=True)
+ connect(world, player, "Pokemon Mansion 1F", "Pokemon Mansion B1F", one_way=True)
+ connect(world, player, "Route 23 North", "Victory Road 1F", lambda state: state.pokemon_rb_can_strength(player), one_way=True)
+ connect(world, player, "Victory Road 1F", "Victory Road 2F", one_way=True)
+ connect(world, player, "Victory Road 2F", "Victory Road 3F", one_way=True)
+ connect(world, player, "Victory Road 2F", "Indigo Plateau", lambda state: state.pokemon_rb_has_badges(state.world.elite_four_condition[player], player), one_way=True)
+ connect(world, player, "Cerulean City", "Cerulean Cave 1F", lambda state:
+ state.pokemon_rb_cerulean_cave(state.world.cerulean_cave_condition[player].value + (state.world.extra_key_items[player].value * 4), player) and
+ state.pokemon_rb_can_surf(player), one_way=True)
+ connect(world, player, "Cerulean Cave 1F", "Cerulean Cave 2F", one_way=True)
+ connect(world, player, "Cerulean Cave 1F", "Cerulean Cave B1F", lambda state: state.pokemon_rb_can_surf(player), one_way=True)
+ if world.worlds[player].fly_map != "Pallet Town":
+ connect(world, player, "Menu", world.worlds[player].fly_map, lambda state: state.pokemon_rb_can_fly(player), one_way=True,
+ name="Fly to " + world.worlds[player].fly_map)
+
+
+def connect(world: MultiWorld, player: int, source: str, target: str, rule: callable = lambda state: True, one_way=False, name=None):
+ source_region = world.get_region(source, player)
+ target_region = world.get_region(target, player)
+
+ if name is None:
+ name = source + " to " + target
+
+ connection = Entrance(
+ player,
+ name,
+ source_region
+ )
+
+ connection.access_rule = rule
+
+ source_region.exits.append(connection)
+ connection.connect(target_region)
+ if not one_way:
+ connect(world, player, target, source, rule, True)
diff --git a/worlds/pokemon_rb/rom.py b/worlds/pokemon_rb/rom.py
new file mode 100644
index 0000000000..0487387363
--- /dev/null
+++ b/worlds/pokemon_rb/rom.py
@@ -0,0 +1,631 @@
+import os
+import hashlib
+import Utils
+import bsdiff4
+from copy import deepcopy
+from Patch import APDeltaPatch
+from .text import encode_text
+from .rom_addresses import rom_addresses
+from .locations import location_data
+import worlds.pokemon_rb.poke_data as poke_data
+
+
+def choose_forced_type(chances, random):
+ n = random.randint(1, 100)
+ for chance in chances:
+ if chance[0] >= n:
+ return chance[1]
+ return None
+
+
+def filter_moves(moves, type, random):
+ ret = []
+ for move in moves:
+ if poke_data.moves[move]["type"] == type or type is None:
+ ret.append(move)
+ random.shuffle(ret)
+ return ret
+
+
+def get_move(moves, chances, random, starting_move=False):
+ type = choose_forced_type(chances, random)
+ filtered_moves = filter_moves(moves, type, random)
+ for move in filtered_moves:
+ if poke_data.moves[move]["accuracy"] > 80 and poke_data.moves[move]["power"] > 0 or not starting_move:
+ moves.remove(move)
+ return move
+ else:
+ return get_move(moves, [], random, starting_move)
+
+
+def get_encounter_slots(self):
+ encounter_slots = [location for location in location_data if location.type == "Wild Encounter"]
+
+ for location in encounter_slots:
+ if isinstance(location.original_item, list):
+ location.original_item = location.original_item[not self.world.game_version[self.player].value]
+ return encounter_slots
+
+
+def get_base_stat_total(mon):
+ return (poke_data.pokemon_data[mon]["atk"] + poke_data.pokemon_data[mon]["def"]
+ + poke_data.pokemon_data[mon]["hp"] + poke_data.pokemon_data[mon]["spd"]
+ + poke_data.pokemon_data[mon]["spc"])
+
+
+def randomize_pokemon(self, mon, mons_list, randomize_type):
+ if randomize_type in [1, 3]:
+ type_mons = [pokemon for pokemon in mons_list if any([poke_data.pokemon_data[mon][
+ "type1"] in [self.local_poke_data[pokemon]["type1"], self.local_poke_data[pokemon]["type2"]],
+ poke_data.pokemon_data[mon]["type2"] in [self.local_poke_data[pokemon]["type1"],
+ self.local_poke_data[pokemon]["type2"]]])]
+ if not type_mons:
+ type_mons = mons_list.copy()
+ if randomize_type == 3:
+ stat_base = get_base_stat_total(mon)
+ type_mons.sort(key=lambda mon: abs(get_base_stat_total(mon) - stat_base))
+ mon = type_mons[round(self.world.random.triangular(0, len(type_mons) - 1, 0))]
+ if randomize_type == 2:
+ stat_base = get_base_stat_total(mon)
+ mons_list.sort(key=lambda mon: abs(get_base_stat_total(mon) - stat_base))
+ mon = mons_list[round(self.world.random.triangular(0, 50, 0))]
+ elif randomize_type == 4:
+ mon = self.world.random.choice(mons_list)
+ return mon
+
+
+def process_trainer_data(self, data):
+ mons_list = [pokemon for pokemon in poke_data.pokemon_data.keys() if pokemon not in poke_data.legendary_pokemon
+ or self.world.trainer_legendaries[self.player].value]
+ address = rom_addresses["Trainer_Data"]
+ while address < rom_addresses["Trainer_Data_End"]:
+ if data[address] == 255:
+ mode = 1
+ else:
+ mode = 0
+ while True:
+ address += 1
+ if data[address] == 0:
+ address += 1
+ break
+ address += mode
+ mon = None
+ for i in range(1, 4):
+ for l in ["A", "B", "C", "D", "E", "F", "G", "H"]:
+ if rom_addresses[f"Rival_Starter{i}_{l}"] == address:
+ mon = " ".join(self.world.get_location(f"Pallet Town - Starter {i}", self.player).item.name.split()[1:])
+ if l in ["D", "E", "F", "G", "H"] and mon in poke_data.evolves_to:
+ mon = poke_data.evolves_to[mon]
+ if l in ["F", "G", "H"] and mon in poke_data.evolves_to:
+ mon = poke_data.evolves_to[mon]
+ if mon is None and self.world.randomize_trainer_parties[self.player].value:
+ mon = poke_data.id_to_mon[data[address]]
+ mon = randomize_pokemon(self, mon, mons_list, self.world.randomize_trainer_parties[self.player].value)
+ if mon is not None:
+ data[address] = poke_data.pokemon_data[mon]["id"]
+
+
+def process_static_pokemon(self):
+ starter_slots = [location for location in location_data if location.type == "Starter Pokemon"]
+ legendary_slots = [location for location in location_data if location.type == "Legendary Pokemon"]
+ static_slots = [location for location in location_data if location.type in
+ ["Static Pokemon", "Missable Pokemon"]]
+ legendary_mons = [slot.original_item for slot in legendary_slots]
+
+ tower_6F_mons = set()
+ for i in range(1, 11):
+ tower_6F_mons.add(self.world.get_location(f"Pokemon Tower 6F - Wild Pokemon - {i}", self.player).item.name)
+
+ mons_list = [pokemon for pokemon in poke_data.first_stage_pokemon if pokemon not in poke_data.legendary_pokemon
+ or self.world.randomize_legendary_pokemon[self.player].value == 3]
+ if self.world.randomize_legendary_pokemon[self.player].value == 0:
+ for slot in legendary_slots:
+ location = self.world.get_location(slot.name, self.player)
+ location.place_locked_item(self.create_item("Missable " + slot.original_item))
+ elif self.world.randomize_legendary_pokemon[self.player].value == 1:
+ self.world.random.shuffle(legendary_mons)
+ for slot in legendary_slots:
+ location = self.world.get_location(slot.name, self.player)
+ location.place_locked_item(self.create_item("Missable " + legendary_mons.pop()))
+ elif self.world.randomize_legendary_pokemon[self.player].value == 2:
+ static_slots = static_slots + legendary_slots
+ self.world.random.shuffle(static_slots)
+ static_slots.sort(key=lambda s: 0 if s.name == "Pokemon Tower 6F - Restless Soul" else 1)
+ while legendary_slots:
+ swap_slot = legendary_slots.pop()
+ slot = static_slots.pop()
+ slot_type = slot.type.split()[0]
+ if slot_type == "Legendary":
+ slot_type = "Missable"
+ location = self.world.get_location(slot.name, self.player)
+ location.place_locked_item(self.create_item(slot_type + " " + swap_slot.original_item))
+ swap_slot.original_item = slot.original_item
+ elif self.world.randomize_legendary_pokemon[self.player].value == 3:
+ static_slots = static_slots + legendary_slots
+
+ for slot in static_slots:
+ location = self.world.get_location(slot.name, self.player)
+ randomize_type = self.world.randomize_static_pokemon[self.player].value
+ slot_type = slot.type.split()[0]
+ if slot_type == "Legendary":
+ slot_type = "Missable"
+ if not randomize_type:
+ location.place_locked_item(self.create_item(slot_type + " " + slot.original_item))
+ else:
+ mon = self.create_item(slot_type + " " +
+ randomize_pokemon(self, slot.original_item, mons_list, randomize_type))
+ while location.name == "Pokemon Tower 6F - Restless Soul" and mon in tower_6F_mons:
+ mon = self.create_item(slot_type + " " + randomize_pokemon(self, slot.original_item, mons_list,
+ randomize_type))
+ location.place_locked_item(mon)
+
+ for slot in starter_slots:
+ location = self.world.get_location(slot.name, self.player)
+ randomize_type = self.world.randomize_starter_pokemon[self.player].value
+ slot_type = "Missable"
+ if not randomize_type:
+ location.place_locked_item(self.create_item(slot_type + " " + slot.original_item))
+ else:
+ location.place_locked_item(self.create_item(slot_type + " " +
+ randomize_pokemon(self, slot.original_item, mons_list, randomize_type)))
+
+
+def process_wild_pokemon(self):
+
+ encounter_slots = get_encounter_slots(self)
+
+ placed_mons = {pokemon: 0 for pokemon in poke_data.pokemon_data.keys()}
+ if self.world.randomize_wild_pokemon[self.player].value:
+ mons_list = [pokemon for pokemon in poke_data.pokemon_data.keys() if pokemon not in poke_data.legendary_pokemon
+ or self.world.randomize_legendary_pokemon[self.player].value == 3]
+ self.world.random.shuffle(encounter_slots)
+ locations = []
+ for slot in encounter_slots:
+ mon = randomize_pokemon(self, slot.original_item, mons_list, self.world.randomize_wild_pokemon[self.player].value)
+ # if static Pokemon are not randomized, we make sure nothing on Pokemon Tower 6F is a Marowak
+ # if static Pokemon are randomized we deal with that during static encounter randomization
+ while (self.world.randomize_static_pokemon[self.player].value == 0 and mon == "Marowak"
+ and "Pokemon Tower 6F" in slot.name):
+ # to account for the possibility that only one ground type Pokemon exists, match only stats for this fix
+ mon = randomize_pokemon(self, slot.original_item, mons_list, 2)
+ placed_mons[mon] += 1
+ location = self.world.get_location(slot.name, self.player)
+ location.item = self.create_item(mon)
+ location.event = True
+ location.locked = True
+ location.item.location = location
+ locations.append(location)
+
+ mons_to_add = []
+ remaining_pokemon = [pokemon for pokemon in poke_data.pokemon_data.keys() if placed_mons[pokemon] == 0 and
+ (pokemon not in poke_data.legendary_pokemon or self.world.randomize_legendary_pokemon[self.player].value == 3)]
+ if self.world.catch_em_all[self.player].value == 1:
+ mons_to_add = [pokemon for pokemon in poke_data.first_stage_pokemon if placed_mons[pokemon] == 0 and
+ (pokemon not in poke_data.legendary_pokemon or self.world.randomize_legendary_pokemon[self.player].value == 3)]
+ elif self.world.catch_em_all[self.player].value == 2:
+ mons_to_add = remaining_pokemon.copy()
+ logic_needed_mons = max(self.world.oaks_aide_rt_2[self.player].value,
+ self.world.oaks_aide_rt_11[self.player].value,
+ self.world.oaks_aide_rt_15[self.player].value)
+ if self.world.accessibility[self.player] == "minimal":
+ logic_needed_mons = 0
+
+ self.world.random.shuffle(remaining_pokemon)
+ while (len([pokemon for pokemon in placed_mons if placed_mons[pokemon] > 0])
+ + len(mons_to_add) < logic_needed_mons):
+ mons_to_add.append(remaining_pokemon.pop())
+ for mon in mons_to_add:
+ stat_base = get_base_stat_total(mon)
+ candidate_locations = get_encounter_slots(self)
+ if self.world.randomize_wild_pokemon[self.player].value in [1, 3]:
+ candidate_locations = [slot for slot in candidate_locations if any([poke_data.pokemon_data[slot.original_item][
+ "type1"] in [self.local_poke_data[mon]["type1"], self.local_poke_data[mon]["type2"]],
+ poke_data.pokemon_data[slot.original_item]["type2"] in [self.local_poke_data[mon]["type1"],
+ self.local_poke_data[mon]["type2"]]])]
+ if not candidate_locations:
+ candidate_locations = location_data
+ candidate_locations = [self.world.get_location(location.name, self.player) for location in candidate_locations]
+ candidate_locations.sort(key=lambda slot: abs(get_base_stat_total(slot.item.name) - stat_base))
+ for location in candidate_locations:
+ if placed_mons[location.item.name] > 1 or location.item.name not in poke_data.first_stage_pokemon:
+ placed_mons[location.item.name] -= 1
+ location.item = self.create_item(mon)
+ location.item.location = location
+ placed_mons[mon] += 1
+ break
+
+ else:
+ for slot in encounter_slots:
+ location = self.world.get_location(slot.name, self.player)
+ location.item = self.create_item(slot.original_item)
+ location.event = True
+ location.locked = True
+ location.item.location = location
+ placed_mons[location.item.name] += 1
+
+
+def process_pokemon_data(self):
+
+ local_poke_data = deepcopy(poke_data.pokemon_data)
+ learnsets = deepcopy(poke_data.learnsets)
+
+ for mon, mon_data in local_poke_data.items():
+ if self.world.randomize_pokemon_stats[self.player].value == 1:
+ stats = [mon_data["hp"], mon_data["atk"], mon_data["def"], mon_data["spd"], mon_data["spc"]]
+ self.world.random.shuffle(stats)
+ mon_data["hp"] = stats[0]
+ mon_data["atk"] = stats[1]
+ mon_data["def"] = stats[2]
+ mon_data["spd"] = stats[3]
+ mon_data["spc"] = stats[4]
+ elif self.world.randomize_pokemon_stats[self.player].value == 2:
+ old_stats = mon_data["hp"] + mon_data["atk"] + mon_data["def"] + mon_data["spd"] + mon_data["spc"] - 5
+ stats = [1, 1, 1, 1, 1]
+ while old_stats > 0:
+ stat = self.world.random.randint(0, 4)
+ if stats[stat] < 255:
+ old_stats -= 1
+ stats[stat] += 1
+ mon_data["hp"] = stats[0]
+ mon_data["atk"] = stats[1]
+ mon_data["def"] = stats[2]
+ mon_data["spd"] = stats[3]
+ mon_data["spc"] = stats[4]
+ if self.world.randomize_pokemon_types[self.player].value:
+ if self.world.randomize_pokemon_types[self.player].value == 1 and mon in poke_data.evolves_from:
+ type1 = local_poke_data[poke_data.evolves_from[mon]]["type1"]
+ type2 = local_poke_data[poke_data.evolves_from[mon]]["type2"]
+ if type1 == type2:
+ if self.world.secondary_type_chance[self.player].value == -1:
+ if mon_data["type1"] != mon_data["type2"]:
+ while type2 == type1:
+ type2 = self.world.random.choice(list(poke_data.type_names.values()))
+ elif self.world.random.randint(1, 100) <= self.world.secondary_type_chance[self.player].value:
+ type2 = self.world.random.choice(list(poke_data.type_names.values()))
+ else:
+ type1 = self.world.random.choice(list(poke_data.type_names.values()))
+ type2 = type1
+ if ((self.world.secondary_type_chance[self.player].value == -1 and mon_data["type1"]
+ != mon_data["type2"]) or self.world.random.randint(1, 100)
+ <= self.world.secondary_type_chance[self.player].value):
+ while type2 == type1:
+ type2 = self.world.random.choice(list(poke_data.type_names.values()))
+
+ mon_data["type1"] = type1
+ mon_data["type2"] = type2
+ if self.world.randomize_pokemon_movesets[self.player].value:
+ if self.world.randomize_pokemon_movesets[self.player].value == 1:
+ if mon_data["type1"] == "Normal" and mon_data["type2"] == "Normal":
+ chances = [[75, "Normal"]]
+ elif mon_data["type1"] == "Normal" or mon_data["type2"] == "Normal":
+ if mon_data["type1"] == "Normal":
+ second_type = mon_data["type2"]
+ else:
+ second_type = mon_data["type1"]
+ chances = [[30, "Normal"], [85, second_type]]
+ elif mon_data["type1"] == mon_data["type2"]:
+ chances = [[60, mon_data["type1"]], [80, "Normal"]]
+ else:
+ chances = [[50, mon_data["type1"]], [80, mon_data["type2"]], [85, "Normal"]]
+ else:
+ chances = []
+ moves = list(poke_data.moves.keys())
+ for move in ["No Move"] + poke_data.hm_moves:
+ moves.remove(move)
+ mon_data["start move 1"] = get_move(moves, chances, self.world.random, True)
+ for i in range(2, 5):
+ if mon_data[f"start move {i}"] != "No Move" or self.world.start_with_four_moves[
+ self.player].value == 1:
+ mon_data[f"start move {i}"] = get_move(moves, chances, self.world.random)
+ if mon in learnsets:
+ for move_num in range(0, len(learnsets[mon])):
+ learnsets[mon][move_num] = get_move(moves, chances, self.world.random)
+ if self.world.randomize_pokemon_catch_rates[self.player].value:
+ mon_data["catch rate"] = self.world.random.randint(self.world.minimum_catch_rate[self.player], 255)
+ else:
+ mon_data["catch rate"] = max(self.world.minimum_catch_rate[self.player], mon_data["catch rate"])
+
+ if mon in poke_data.evolves_from.keys() and mon_data["type1"] == local_poke_data[poke_data.evolves_from[mon]]["type1"] and mon_data["type2"] == local_poke_data[poke_data.evolves_from[mon]]["type2"]:
+ mon_data["tms"] = local_poke_data[poke_data.evolves_from[mon]]["tms"]
+ elif mon != "Mew":
+ tms_hms = poke_data.tm_moves + poke_data.hm_moves
+ for flag, tm_move in enumerate(tms_hms):
+ if (flag < 50 and self.world.tm_compatibility[self.player].value == 1) or (flag >= 50 and self.world.hm_compatibility[self.player].value == 1):
+ type_match = poke_data.moves[tm_move]["type"] in [mon_data["type1"], mon_data["type2"]]
+ bit = int(self.world.random.randint(1, 100) < [[90, 50, 25], [100, 75, 25]][flag >= 50][0 if type_match else 1 if poke_data.moves[tm_move]["type"] == "Normal" else 2])
+ elif (flag < 50 and self.world.tm_compatibility[self.player].value == 2) or (flag >= 50 and self.world.hm_compatibility[self.player].value == 2):
+ bit = [0, 1][self.world.random.randint(0, 1)]
+ elif (flag < 50 and self.world.tm_compatibility[self.player].value == 3) or (flag >= 50 and self.world.hm_compatibility[self.player].value == 3):
+ bit = 1
+ else:
+ continue
+ if bit:
+ mon_data["tms"][int(flag / 8)] |= 1 << (flag % 8)
+ else:
+ mon_data["tms"][int(flag / 8)] &= ~(1 << (flag % 8))
+
+ self.local_poke_data = local_poke_data
+ self.learnsets = learnsets
+
+
+
+def generate_output(self, output_directory: str):
+ random = self.world.slot_seeds[self.player]
+ game_version = self.world.game_version[self.player].current_key
+ data = bytearray(get_base_rom_bytes(game_version))
+
+ basemd5 = hashlib.md5()
+ basemd5.update(data)
+
+ for location in self.world.get_locations():
+ if location.player != self.player or location.rom_address is None:
+ continue
+ if location.item and location.item.player == self.player:
+ if location.rom_address:
+ rom_address = location.rom_address
+ if not isinstance(rom_address, list):
+ rom_address = [rom_address]
+ for address in rom_address:
+ if location.item.name in poke_data.pokemon_data.keys():
+ data[address] = poke_data.pokemon_data[location.item.name]["id"]
+ elif " ".join(location.item.name.split()[1:]) in poke_data.pokemon_data.keys():
+ data[address] = poke_data.pokemon_data[" ".join(location.item.name.split()[1:])]["id"]
+ else:
+ data[address] = self.item_name_to_id[location.item.name] - 172000000
+
+ else:
+ data[location.rom_address] = 0x2C # AP Item
+ data[rom_addresses['Fly_Location']] = self.fly_map_code
+
+ if self.world.tea[self.player].value:
+ data[rom_addresses["Option_Tea"]] = 1
+ data[rom_addresses["Guard_Drink_List"]] = 0x54
+ data[rom_addresses["Guard_Drink_List"] + 1] = 0
+ data[rom_addresses["Guard_Drink_List"] + 2] = 0
+
+ if self.world.extra_key_items[self.player].value:
+ data[rom_addresses['Options']] |= 4
+ data[rom_addresses["Option_Blind_Trainers"]] = round(self.world.blind_trainers[self.player].value * 2.55)
+ data[rom_addresses['Option_Cerulean_Cave_Condition']] = self.world.cerulean_cave_condition[self.player].value
+ data[rom_addresses['Option_Encounter_Minimum_Steps']] = self.world.minimum_steps_between_encounters[self.player].value
+ data[rom_addresses['Option_Victory_Road_Badges']] = self.world.victory_road_condition[self.player].value
+ data[rom_addresses['Option_Pokemon_League_Badges']] = self.world.elite_four_condition[self.player].value
+ data[rom_addresses['Option_Viridian_Gym_Badges']] = self.world.viridian_gym_condition[self.player].value
+ data[rom_addresses['Option_EXP_Modifier']] = self.world.exp_modifier[self.player].value
+ if not self.world.require_item_finder[self.player].value:
+ data[rom_addresses['Option_Itemfinder']] = 0
+ if self.world.extra_strength_boulders[self.player].value:
+ for i in range(0, 3):
+ data[rom_addresses['Option_Boulders'] + (i * 3)] = 0x15
+ if self.world.extra_key_items[self.player].value:
+ for i in range(0, 4):
+ data[rom_addresses['Option_Rock_Tunnel_Extra_Items'] + (i * 3)] = 0x15
+ if self.world.old_man[self.player].value == 2:
+ data[rom_addresses['Option_Old_Man']] = 0x11
+ data[rom_addresses['Option_Old_Man_Lying']] = 0x15
+ money = str(self.world.starting_money[self.player].value)
+ while len(money) < 6:
+ money = "0" + money
+ data[rom_addresses["Starting_Money_High"]] = int(money[:2], 16)
+ data[rom_addresses["Starting_Money_Middle"]] = int(money[2:4], 16)
+ data[rom_addresses["Starting_Money_Low"]] = int(money[4:], 16)
+ data[rom_addresses["Text_Badges_Needed"]] = encode_text(
+ str(max(self.world.victory_road_condition[self.player].value,
+ self.world.elite_four_condition[self.player].value)))[0]
+ if self.world.badges_needed_for_hm_moves[self.player].value == 0:
+ for hm_move in poke_data.hm_moves:
+ write_bytes(data, bytearray([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
+ rom_addresses["HM_" + hm_move + "_Badge_a"])
+ elif self.extra_badges:
+ written_badges = {}
+ for hm_move, badge in self.extra_badges.items():
+ data[rom_addresses["HM_" + hm_move + "_Badge_b"]] = {"Boulder Badge": 0x47, "Cascade Badge": 0x4F,
+ "Thunder Badge": 0x57, "Rainbow Badge": 0x5F,
+ "Soul Badge": 0x67, "Marsh Badge": 0x6F,
+ "Volcano Badge": 0x77, "Earth Badge": 0x7F}[badge]
+ move_text = hm_move
+ if badge not in ["Marsh Badge", "Volcano Badge", "Earth Badge"]:
+ move_text = ", " + move_text
+ rom_address = rom_addresses["Badge_Text_" + badge.replace(" ", "_")]
+ if badge in written_badges:
+ rom_address += len(written_badges[badge])
+ move_text = ", " + move_text
+ write_bytes(data, encode_text(move_text.upper()), rom_address)
+ written_badges[badge] = move_text
+ for badge in ["Marsh Badge", "Volcano Badge", "Earth Badge"]:
+ if badge not in written_badges:
+ write_bytes(data, encode_text("Nothing"), rom_addresses["Badge_Text_" + badge.replace(" ", "_")])
+
+ chart = deepcopy(poke_data.type_chart)
+ if self.world.randomize_type_matchup_types[self.player].value == 1:
+ attacking_types = []
+ defending_types = []
+ for matchup in chart:
+ attacking_types.append(matchup[0])
+ defending_types.append(matchup[1])
+ random.shuffle(attacking_types)
+ random.shuffle(defending_types)
+ matchups = []
+ while len(attacking_types) > 0:
+ if [attacking_types[0], defending_types[0]] not in matchups:
+ matchups.append([attacking_types.pop(0), defending_types.pop(0)])
+ else:
+ matchup = matchups.pop(0)
+ attacking_types.append(matchup[0])
+ defending_types.append(matchup[1])
+ random.shuffle(attacking_types)
+ random.shuffle(defending_types)
+ for matchup, chart_row in zip(matchups, chart):
+ chart_row[0] = matchup[0]
+ chart_row[1] = matchup[1]
+ elif self.world.randomize_type_matchup_types[self.player].value == 2:
+ used_matchups = []
+ for matchup in chart:
+ matchup[0] = random.choice(list(poke_data.type_names.values()))
+ matchup[1] = random.choice(list(poke_data.type_names.values()))
+ while [matchup[0], matchup[1]] in used_matchups:
+ matchup[0] = random.choice(list(poke_data.type_names.values()))
+ matchup[1] = random.choice(list(poke_data.type_names.values()))
+ used_matchups.append([matchup[0], matchup[1]])
+ if self.world.randomize_type_matchup_type_effectiveness[self.player].value == 1:
+ effectiveness_list = []
+ for matchup in chart:
+ effectiveness_list.append(matchup[2])
+ random.shuffle(effectiveness_list)
+ for (matchup, effectiveness) in zip(chart, effectiveness_list):
+ matchup[2] = effectiveness
+ elif self.world.randomize_type_matchup_type_effectiveness[self.player].value == 2:
+ for matchup in chart:
+ matchup[2] = random.choice([0] + ([5, 20] * 5))
+ elif self.world.randomize_type_matchup_type_effectiveness[self.player].value == 3:
+ for matchup in chart:
+ matchup[2] = random.choice([i for i in range(0, 21) if i != 10])
+ type_loc = rom_addresses["Type_Chart"]
+ for matchup in chart:
+ data[type_loc] = poke_data.type_ids[matchup[0]]
+ data[type_loc + 1] = poke_data.type_ids[matchup[1]]
+ data[type_loc + 2] = matchup[2]
+ type_loc += 3
+ # sort so that super-effective matchups occur first, to prevent dual "not very effective" / "super effective"
+ # matchups from leading to damage being ultimately divided by 2 and then multiplied by 2, which can lead to
+ # damage being reduced by 1 which leads to a "not very effective" message appearing due to my changes
+ # to the way effectiveness messages are generated.
+ self.type_chart = sorted(chart, key=lambda matchup: 0 - matchup[2])
+
+ if self.world.normalize_encounter_chances[self.player].value:
+ chances = [25, 51, 77, 103, 129, 155, 180, 205, 230, 255]
+ for i, chance in enumerate(chances):
+ data[rom_addresses['Encounter_Chances'] + (i * 2)] = chance
+
+ for mon, mon_data in self.local_poke_data.items():
+ if mon == "Mew":
+ address = rom_addresses["Base_Stats_Mew"]
+ else:
+ address = rom_addresses["Base_Stats"] + (28 * (mon_data["dex"] - 1))
+ data[address + 1] = self.local_poke_data[mon]["hp"]
+ data[address + 2] = self.local_poke_data[mon]["atk"]
+ data[address + 3] = self.local_poke_data[mon]["def"]
+ data[address + 4] = self.local_poke_data[mon]["spd"]
+ data[address + 5] = self.local_poke_data[mon]["spc"]
+ data[address + 6] = poke_data.type_ids[self.local_poke_data[mon]["type1"]]
+ data[address + 7] = poke_data.type_ids[self.local_poke_data[mon]["type2"]]
+ data[address + 8] = self.local_poke_data[mon]["catch rate"]
+ data[address + 15] = poke_data.moves[self.local_poke_data[mon]["start move 1"]]["id"]
+ data[address + 16] = poke_data.moves[self.local_poke_data[mon]["start move 2"]]["id"]
+ data[address + 17] = poke_data.moves[self.local_poke_data[mon]["start move 3"]]["id"]
+ data[address + 18] = poke_data.moves[self.local_poke_data[mon]["start move 4"]]["id"]
+ write_bytes(data, self.local_poke_data[mon]["tms"], address + 20)
+ if mon in self.learnsets:
+ address = rom_addresses["Learnset_" + mon.replace(" ", "")]
+ for i, move in enumerate(self.learnsets[mon]):
+ data[(address + 1) + i * 2] = poke_data.moves[move]["id"]
+
+ data[rom_addresses["Option_Aide_Rt2"]] = self.world.oaks_aide_rt_2[self.player]
+ data[rom_addresses["Option_Aide_Rt11"]] = self.world.oaks_aide_rt_11[self.player]
+ data[rom_addresses["Option_Aide_Rt15"]] = self.world.oaks_aide_rt_15[self.player]
+
+ if self.world.safari_zone_normal_battles[self.player].value == 1:
+ data[rom_addresses["Option_Safari_Zone_Battle_Type"]] = 255
+
+ if self.world.reusable_tms[self.player].value:
+ data[rom_addresses["Option_Reusable_TMs"]] = 0xC9
+
+ process_trainer_data(self, data)
+
+ mons = [mon["id"] for mon in poke_data.pokemon_data.values()]
+ random.shuffle(mons)
+ data[rom_addresses['Title_Mon_First']] = mons.pop()
+ for mon in range(0, 16):
+ data[rom_addresses['Title_Mons'] + mon] = mons.pop()
+ if self.world.game_version[self.player].value:
+ mons.sort(key=lambda mon: 0 if mon == self.world.get_location("Pallet Town - Starter 1", self.player).item.name
+ else 1 if mon == self.world.get_location("Pallet Town - Starter 2", self.player).item.name else
+ 2 if mon == self.world.get_location("Pallet Town - Starter 3", self.player).item.name else 3)
+ else:
+ mons.sort(key=lambda mon: 0 if mon == self.world.get_location("Pallet Town - Starter 2", self.player).item.name
+ else 1 if mon == self.world.get_location("Pallet Town - Starter 1", self.player).item.name else
+ 2 if mon == self.world.get_location("Pallet Town - Starter 3", self.player).item.name else 3)
+ write_bytes(data, encode_text(self.world.seed_name, 20, True), rom_addresses['Title_Seed'])
+
+ slot_name = self.world.player_name[self.player]
+ slot_name.replace("@", " ")
+ slot_name.replace("<", " ")
+ slot_name.replace(">", " ")
+ write_bytes(data, encode_text(slot_name, 16, True, True), rom_addresses['Title_Slot_Name'])
+
+ write_bytes(data, self.trainer_name, rom_addresses['Player_Name'])
+ write_bytes(data, self.rival_name, rom_addresses['Rival_Name'])
+
+ write_bytes(data, basemd5.digest(), 0xFFCC)
+ write_bytes(data, self.world.seed_name.encode(), 0xFFDC)
+ write_bytes(data, self.world.player_name[self.player].encode(), 0xFFF0)
+
+
+
+ outfilepname = f'_P{self.player}'
+ outfilepname += f"_{self.world.get_file_safe_player_name(self.player).replace(' ', '_')}" \
+ if self.world.player_name[self.player] != 'Player%d' % self.player else ''
+ rompath = os.path.join(output_directory, f'AP_{self.world.seed_name}{outfilepname}.gb')
+ with open(rompath, 'wb') as outfile:
+ outfile.write(data)
+ if self.world.game_version[self.player].current_key == "red":
+ patch = RedDeltaPatch(os.path.splitext(rompath)[0] + RedDeltaPatch.patch_file_ending, player=self.player,
+ player_name=self.world.player_name[self.player], patched_path=rompath)
+ else:
+ patch = BlueDeltaPatch(os.path.splitext(rompath)[0] + BlueDeltaPatch.patch_file_ending, player=self.player,
+ player_name=self.world.player_name[self.player], patched_path=rompath)
+
+ patch.write()
+ os.unlink(rompath)
+
+
+def write_bytes(data, byte_array, address):
+ for byte in byte_array:
+ data[address] = byte
+ address += 1
+
+
+def get_base_rom_bytes(game_version: str, hash: str="") -> bytes:
+ file_name = get_base_rom_path(game_version)
+ with open(file_name, "rb") as file:
+ base_rom_bytes = bytes(file.read())
+ if hash:
+ basemd5 = hashlib.md5()
+ basemd5.update(base_rom_bytes)
+ if hash != basemd5.hexdigest():
+ raise Exception('Supplied Base Rom does not match known MD5 for US(1.0) release. '
+ 'Get the correct game and version, then dump it')
+ with open(os.path.join(os.path.dirname(__file__), f'basepatch_{game_version}.bsdiff4'), 'rb') as stream:
+ base_patch = bytes(stream.read())
+ base_rom_bytes = bsdiff4.patch(base_rom_bytes, base_patch)
+ return base_rom_bytes
+
+
+def get_base_rom_path(game_version: str) -> str:
+ options = Utils.get_options()
+ file_name = options["pokemon_rb_options"][f"{game_version}_rom_file"]
+ if not os.path.exists(file_name):
+ file_name = Utils.local_path(file_name)
+ return file_name
+
+
+class BlueDeltaPatch(APDeltaPatch):
+ patch_file_ending = ".apblue"
+ hash = "50927e843568814f7ed45ec4f944bd8b"
+ game_version = "blue"
+ game = "Pokemon Red and Blue"
+ result_file_ending = ".gb"
+ @classmethod
+ def get_source_data(cls) -> bytes:
+ return get_base_rom_bytes(cls.game_version, cls.hash)
+
+
+class RedDeltaPatch(APDeltaPatch):
+ patch_file_ending = ".apred"
+ hash = "3d45c1ee9abd5738df46d2bdda8b57dc"
+ game_version = "red"
+ game = "Pokemon Red and Blue"
+ result_file_ending = ".gb"
+ @classmethod
+ def get_source_data(cls) -> bytes:
+ return get_base_rom_bytes(cls.game_version, cls.hash)
diff --git a/worlds/pokemon_rb/rom_addresses.py b/worlds/pokemon_rb/rom_addresses.py
new file mode 100644
index 0000000000..c04a75bcc4
--- /dev/null
+++ b/worlds/pokemon_rb/rom_addresses.py
@@ -0,0 +1,588 @@
+rom_addresses = {
+ "Option_Encounter_Minimum_Steps": 0x3c3,
+ "Option_Blind_Trainers": 0x317e,
+ "Base_Stats_Mew": 0x425b,
+ "Title_Mon_First": 0x436e,
+ "Title_Mons": 0x4547,
+ "Player_Name": 0x4569,
+ "Rival_Name": 0x4571,
+ "Title_Seed": 0x5dfe,
+ "Title_Slot_Name": 0x5e1e,
+ "PC_Item": 0x61ec,
+ "PC_Item_Quantity": 0x61f1,
+ "Options": 0x61f9,
+ "Fly_Location": 0x61fe,
+ "Option_Old_Man": 0xcaef,
+ "Option_Old_Man_Lying": 0xcaf2,
+ "Option_Boulders": 0xcd98,
+ "Option_Rock_Tunnel_Extra_Items": 0xcda1,
+ "Wild_Route1": 0xd0fb,
+ "Wild_Route2": 0xd111,
+ "Wild_Route22": 0xd127,
+ "Wild_ViridianForest": 0xd13d,
+ "Wild_Route3": 0xd153,
+ "Wild_MtMoon1F": 0xd169,
+ "Wild_MtMoonB1F": 0xd17f,
+ "Wild_MtMoonB2F": 0xd195,
+ "Wild_Route4": 0xd1ab,
+ "Wild_Route24": 0xd1c1,
+ "Wild_Route25": 0xd1d7,
+ "Wild_Route9": 0xd1ed,
+ "Wild_Route5": 0xd203,
+ "Wild_Route6": 0xd219,
+ "Wild_Route11": 0xd22f,
+ "Wild_RockTunnel1F": 0xd245,
+ "Wild_RockTunnelB1F": 0xd25b,
+ "Wild_Route10": 0xd271,
+ "Wild_Route12": 0xd287,
+ "Wild_Route8": 0xd29d,
+ "Wild_Route7": 0xd2b3,
+ "Wild_PokemonTower3F": 0xd2cd,
+ "Wild_PokemonTower4F": 0xd2e3,
+ "Wild_PokemonTower5F": 0xd2f9,
+ "Wild_PokemonTower6F": 0xd30f,
+ "Wild_PokemonTower7F": 0xd325,
+ "Wild_Route13": 0xd33b,
+ "Wild_Route14": 0xd351,
+ "Wild_Route15": 0xd367,
+ "Wild_Route16": 0xd37d,
+ "Wild_Route17": 0xd393,
+ "Wild_Route18": 0xd3a9,
+ "Wild_SafariZoneCenter": 0xd3bf,
+ "Wild_SafariZoneEast": 0xd3d5,
+ "Wild_SafariZoneNorth": 0xd3eb,
+ "Wild_SafariZoneWest": 0xd401,
+ "Wild_SeaRoutes": 0xd418,
+ "Wild_SeafoamIslands1F": 0xd42d,
+ "Wild_SeafoamIslandsB1F": 0xd443,
+ "Wild_SeafoamIslandsB2F": 0xd459,
+ "Wild_SeafoamIslandsB3F": 0xd46f,
+ "Wild_SeafoamIslandsB4F": 0xd485,
+ "Wild_PokemonMansion1F": 0xd49b,
+ "Wild_PokemonMansion2F": 0xd4b1,
+ "Wild_PokemonMansion3F": 0xd4c7,
+ "Wild_PokemonMansionB1F": 0xd4dd,
+ "Wild_Route21": 0xd4f3,
+ "Wild_Surf_Route21": 0xd508,
+ "Wild_CeruleanCave1F": 0xd51d,
+ "Wild_CeruleanCave2F": 0xd533,
+ "Wild_CeruleanCaveB1F": 0xd549,
+ "Wild_PowerPlant": 0xd55f,
+ "Wild_Route23": 0xd575,
+ "Wild_VictoryRoad2F": 0xd58b,
+ "Wild_VictoryRoad3F": 0xd5a1,
+ "Wild_VictoryRoad1F": 0xd5b7,
+ "Wild_DiglettsCave": 0xd5cd,
+ "Ghost_Battle5": 0xd723,
+ "HM_Surf_Badge_a": 0xda11,
+ "HM_Surf_Badge_b": 0xda16,
+ "Wild_Old_Rod": 0xe313,
+ "Wild_Good_Rod": 0xe340,
+ "Option_Reusable_TMs": 0xe60c,
+ "Wild_Super_Rod_A": 0xea40,
+ "Wild_Super_Rod_B": 0xea45,
+ "Wild_Super_Rod_C": 0xea4a,
+ "Wild_Super_Rod_D": 0xea51,
+ "Wild_Super_Rod_E": 0xea56,
+ "Wild_Super_Rod_F": 0xea5b,
+ "Wild_Super_Rod_G": 0xea64,
+ "Wild_Super_Rod_H": 0xea6d,
+ "Wild_Super_Rod_I": 0xea76,
+ "Wild_Super_Rod_J": 0xea7f,
+ "Starting_Money_High": 0xf949,
+ "Starting_Money_Middle": 0xf94c,
+ "Starting_Money_Low": 0xf94f,
+ "HM_Fly_Badge_a": 0x1318e,
+ "HM_Fly_Badge_b": 0x13193,
+ "HM_Cut_Badge_a": 0x131c4,
+ "HM_Cut_Badge_b": 0x131c9,
+ "HM_Strength_Badge_a": 0x131f4,
+ "HM_Strength_Badge_b": 0x131f9,
+ "HM_Flash_Badge_a": 0x13208,
+ "HM_Flash_Badge_b": 0x1320d,
+ "Encounter_Chances": 0x13911,
+ "Option_Viridian_Gym_Badges": 0x1901d,
+ "Event_Sleepy_Guy": 0x191bc,
+ "Starter2_K": 0x195a8,
+ "Starter3_K": 0x195b0,
+ "Event_Rocket_Thief": 0x196cc,
+ "Option_Cerulean_Cave_Condition": 0x1986c,
+ "Event_Stranded_Man": 0x19b2b,
+ "Event_Rivals_Sister": 0x19cf9,
+ "Option_Pokemon_League_Badges": 0x19e16,
+ "Missable_Silph_Co_4F_Item_1": 0x1a0d7,
+ "Missable_Silph_Co_4F_Item_2": 0x1a0de,
+ "Missable_Silph_Co_4F_Item_3": 0x1a0e5,
+ "Missable_Silph_Co_5F_Item_1": 0x1a337,
+ "Missable_Silph_Co_5F_Item_2": 0x1a33e,
+ "Missable_Silph_Co_5F_Item_3": 0x1a345,
+ "Missable_Silph_Co_6F_Item_1": 0x1a5ad,
+ "Missable_Silph_Co_6F_Item_2": 0x1a5b4,
+ "Event_Free_Sample": 0x1cade,
+ "Starter1_F": 0x1cca5,
+ "Starter2_F": 0x1cca9,
+ "Starter2_G": 0x1cde2,
+ "Starter3_G": 0x1cdea,
+ "Starter2_H": 0x1d0e5,
+ "Starter1_H": 0x1d0ef,
+ "Starter3_I": 0x1d0f6,
+ "Starter2_I": 0x1d100,
+ "Starter1_D": 0x1d107,
+ "Starter3_D": 0x1d111,
+ "Starter2_E": 0x1d2eb,
+ "Starter3_E": 0x1d2f3,
+ "Event_Oaks_Gift": 0x1d373,
+ "Event_Pokemart_Quest": 0x1d566,
+ "Event_Bicycle_Shop": 0x1d805,
+ "Text_Bicycle": 0x1d898,
+ "Event_Fuji": 0x1d9cd,
+ "Static_Encounter_Mew": 0x1dc4e,
+ "Gift_Eevee": 0x1dcc7,
+ "Event_Mr_Psychic": 0x1ddcf,
+ "Static_Encounter_Voltorb_A": 0x1e397,
+ "Static_Encounter_Voltorb_B": 0x1e39f,
+ "Static_Encounter_Voltorb_C": 0x1e3a7,
+ "Static_Encounter_Electrode_A": 0x1e3af,
+ "Static_Encounter_Voltorb_D": 0x1e3b7,
+ "Static_Encounter_Voltorb_E": 0x1e3bf,
+ "Static_Encounter_Electrode_B": 0x1e3c7,
+ "Static_Encounter_Voltorb_F": 0x1e3cf,
+ "Static_Encounter_Zapdos": 0x1e3d7,
+ "Missable_Power_Plant_Item_1": 0x1e3df,
+ "Missable_Power_Plant_Item_2": 0x1e3e6,
+ "Missable_Power_Plant_Item_3": 0x1e3ed,
+ "Missable_Power_Plant_Item_4": 0x1e3f4,
+ "Missable_Power_Plant_Item_5": 0x1e3fb,
+ "Event_Rt16_House_Woman": 0x1e5d4,
+ "Option_Victory_Road_Badges": 0x1e6a5,
+ "Event_Bill": 0x1e8d6,
+ "Starter1_O": 0x372b0,
+ "Starter2_O": 0x372b4,
+ "Starter3_O": 0x372b8,
+ "Base_Stats": 0x383de,
+ "Starter3_C": 0x39cf2,
+ "Starter1_C": 0x39cf8,
+ "Trainer_Data": 0x39d99,
+ "Rival_Starter2_A": 0x3a1e5,
+ "Rival_Starter3_A": 0x3a1e8,
+ "Rival_Starter1_A": 0x3a1eb,
+ "Rival_Starter2_B": 0x3a1f1,
+ "Rival_Starter3_B": 0x3a1f7,
+ "Rival_Starter1_B": 0x3a1fd,
+ "Rival_Starter2_C": 0x3a207,
+ "Rival_Starter3_C": 0x3a211,
+ "Rival_Starter1_C": 0x3a21b,
+ "Rival_Starter2_D": 0x3a409,
+ "Rival_Starter3_D": 0x3a413,
+ "Rival_Starter1_D": 0x3a41d,
+ "Rival_Starter2_E": 0x3a429,
+ "Rival_Starter3_E": 0x3a435,
+ "Rival_Starter1_E": 0x3a441,
+ "Rival_Starter2_F": 0x3a44d,
+ "Rival_Starter3_F": 0x3a459,
+ "Rival_Starter1_F": 0x3a465,
+ "Rival_Starter2_G": 0x3a473,
+ "Rival_Starter3_G": 0x3a481,
+ "Rival_Starter1_G": 0x3a48f,
+ "Rival_Starter2_H": 0x3a49d,
+ "Rival_Starter3_H": 0x3a4ab,
+ "Rival_Starter1_H": 0x3a4b9,
+ "Trainer_Data_End": 0x3a52e,
+ "Learnset_Rhydon": 0x3b1d9,
+ "Learnset_Kangaskhan": 0x3b1e7,
+ "Learnset_NidoranM": 0x3b1f6,
+ "Learnset_Clefairy": 0x3b208,
+ "Learnset_Spearow": 0x3b219,
+ "Learnset_Voltorb": 0x3b228,
+ "Learnset_Nidoking": 0x3b234,
+ "Learnset_Slowbro": 0x3b23c,
+ "Learnset_Ivysaur": 0x3b24f,
+ "Learnset_Exeggutor": 0x3b25f,
+ "Learnset_Lickitung": 0x3b263,
+ "Learnset_Exeggcute": 0x3b273,
+ "Learnset_Grimer": 0x3b284,
+ "Learnset_Gengar": 0x3b292,
+ "Learnset_NidoranF": 0x3b29b,
+ "Learnset_Nidoqueen": 0x3b2a9,
+ "Learnset_Cubone": 0x3b2b4,
+ "Learnset_Rhyhorn": 0x3b2c3,
+ "Learnset_Lapras": 0x3b2d1,
+ "Learnset_Mew": 0x3b2e1,
+ "Learnset_Gyarados": 0x3b2eb,
+ "Learnset_Shellder": 0x3b2fb,
+ "Learnset_Tentacool": 0x3b30a,
+ "Learnset_Gastly": 0x3b31f,
+ "Learnset_Scyther": 0x3b325,
+ "Learnset_Staryu": 0x3b337,
+ "Learnset_Blastoise": 0x3b347,
+ "Learnset_Pinsir": 0x3b355,
+ "Learnset_Tangela": 0x3b363,
+ "Learnset_Growlithe": 0x3b379,
+ "Learnset_Onix": 0x3b385,
+ "Learnset_Fearow": 0x3b391,
+ "Learnset_Pidgey": 0x3b3a0,
+ "Learnset_Slowpoke": 0x3b3b1,
+ "Learnset_Kadabra": 0x3b3c9,
+ "Learnset_Graveler": 0x3b3e1,
+ "Learnset_Chansey": 0x3b3ef,
+ "Learnset_Machoke": 0x3b407,
+ "Learnset_MrMime": 0x3b413,
+ "Learnset_Hitmonlee": 0x3b41f,
+ "Learnset_Hitmonchan": 0x3b42b,
+ "Learnset_Arbok": 0x3b437,
+ "Learnset_Parasect": 0x3b443,
+ "Learnset_Psyduck": 0x3b452,
+ "Learnset_Drowzee": 0x3b461,
+ "Learnset_Golem": 0x3b46f,
+ "Learnset_Magmar": 0x3b47f,
+ "Learnset_Electabuzz": 0x3b48f,
+ "Learnset_Magneton": 0x3b49b,
+ "Learnset_Koffing": 0x3b4ac,
+ "Learnset_Mankey": 0x3b4bd,
+ "Learnset_Seel": 0x3b4cc,
+ "Learnset_Diglett": 0x3b4db,
+ "Learnset_Tauros": 0x3b4e7,
+ "Learnset_Farfetchd": 0x3b4f9,
+ "Learnset_Venonat": 0x3b508,
+ "Learnset_Dragonite": 0x3b516,
+ "Learnset_Doduo": 0x3b52b,
+ "Learnset_Poliwag": 0x3b53c,
+ "Learnset_Jynx": 0x3b54a,
+ "Learnset_Moltres": 0x3b558,
+ "Learnset_Articuno": 0x3b560,
+ "Learnset_Zapdos": 0x3b568,
+ "Learnset_Meowth": 0x3b575,
+ "Learnset_Krabby": 0x3b584,
+ "Learnset_Vulpix": 0x3b59a,
+ "Learnset_Pikachu": 0x3b5ac,
+ "Learnset_Dratini": 0x3b5c1,
+ "Learnset_Dragonair": 0x3b5d0,
+ "Learnset_Kabuto": 0x3b5df,
+ "Learnset_Kabutops": 0x3b5e9,
+ "Learnset_Horsea": 0x3b5f6,
+ "Learnset_Seadra": 0x3b602,
+ "Learnset_Sandshrew": 0x3b615,
+ "Learnset_Sandslash": 0x3b621,
+ "Learnset_Omanyte": 0x3b630,
+ "Learnset_Omastar": 0x3b63a,
+ "Learnset_Jigglypuff": 0x3b648,
+ "Learnset_Eevee": 0x3b666,
+ "Learnset_Flareon": 0x3b670,
+ "Learnset_Jolteon": 0x3b682,
+ "Learnset_Vaporeon": 0x3b694,
+ "Learnset_Machop": 0x3b6a9,
+ "Learnset_Zubat": 0x3b6b8,
+ "Learnset_Ekans": 0x3b6c7,
+ "Learnset_Paras": 0x3b6d6,
+ "Learnset_Poliwhirl": 0x3b6e6,
+ "Learnset_Poliwrath": 0x3b6f4,
+ "Learnset_Beedrill": 0x3b704,
+ "Learnset_Dodrio": 0x3b714,
+ "Learnset_Primeape": 0x3b722,
+ "Learnset_Dugtrio": 0x3b72e,
+ "Learnset_Venomoth": 0x3b73a,
+ "Learnset_Dewgong": 0x3b748,
+ "Learnset_Butterfree": 0x3b762,
+ "Learnset_Machamp": 0x3b772,
+ "Learnset_Golduck": 0x3b780,
+ "Learnset_Hypno": 0x3b78c,
+ "Learnset_Golbat": 0x3b79a,
+ "Learnset_Mewtwo": 0x3b7a6,
+ "Learnset_Snorlax": 0x3b7b2,
+ "Learnset_Magikarp": 0x3b7bf,
+ "Learnset_Muk": 0x3b7c7,
+ "Learnset_Kingler": 0x3b7d7,
+ "Learnset_Cloyster": 0x3b7e3,
+ "Learnset_Electrode": 0x3b7e9,
+ "Learnset_Weezing": 0x3b7f7,
+ "Learnset_Persian": 0x3b803,
+ "Learnset_Marowak": 0x3b80f,
+ "Learnset_Haunter": 0x3b827,
+ "Learnset_Alakazam": 0x3b832,
+ "Learnset_Pidgeotto": 0x3b843,
+ "Learnset_Pidgeot": 0x3b851,
+ "Learnset_Bulbasaur": 0x3b864,
+ "Learnset_Venusaur": 0x3b874,
+ "Learnset_Tentacruel": 0x3b884,
+ "Learnset_Goldeen": 0x3b89b,
+ "Learnset_Seaking": 0x3b8a9,
+ "Learnset_Ponyta": 0x3b8c2,
+ "Learnset_Rapidash": 0x3b8d0,
+ "Learnset_Rattata": 0x3b8e1,
+ "Learnset_Raticate": 0x3b8eb,
+ "Learnset_Nidorino": 0x3b8f9,
+ "Learnset_Nidorina": 0x3b90b,
+ "Learnset_Geodude": 0x3b91c,
+ "Learnset_Porygon": 0x3b92a,
+ "Learnset_Aerodactyl": 0x3b934,
+ "Learnset_Magnemite": 0x3b942,
+ "Learnset_Charmander": 0x3b957,
+ "Learnset_Squirtle": 0x3b968,
+ "Learnset_Charmeleon": 0x3b979,
+ "Learnset_Wartortle": 0x3b98a,
+ "Learnset_Charizard": 0x3b998,
+ "Learnset_Oddish": 0x3b9b1,
+ "Learnset_Gloom": 0x3b9c3,
+ "Learnset_Vileplume": 0x3b9d1,
+ "Learnset_Bellsprout": 0x3b9dc,
+ "Learnset_Weepinbell": 0x3b9f0,
+ "Learnset_Victreebel": 0x3ba00,
+ "Type_Chart": 0x3e4b6,
+ "Type_Chart_Divider": 0x3e5ac,
+ "Ghost_Battle3": 0x3efd9,
+ "Missable_Pokemon_Mansion_1F_Item_1": 0x443d6,
+ "Missable_Pokemon_Mansion_1F_Item_2": 0x443dd,
+ "Map_Rock_TunnelF": 0x44676,
+ "Missable_Victory_Road_3F_Item_1": 0x44b07,
+ "Missable_Victory_Road_3F_Item_2": 0x44b0e,
+ "Missable_Rocket_Hideout_B1F_Item_1": 0x44d2d,
+ "Missable_Rocket_Hideout_B1F_Item_2": 0x44d34,
+ "Missable_Rocket_Hideout_B2F_Item_1": 0x4511d,
+ "Missable_Rocket_Hideout_B2F_Item_2": 0x45124,
+ "Missable_Rocket_Hideout_B2F_Item_3": 0x4512b,
+ "Missable_Rocket_Hideout_B2F_Item_4": 0x45132,
+ "Missable_Rocket_Hideout_B3F_Item_1": 0x4536f,
+ "Missable_Rocket_Hideout_B3F_Item_2": 0x45376,
+ "Missable_Rocket_Hideout_B4F_Item_1": 0x45627,
+ "Missable_Rocket_Hideout_B4F_Item_2": 0x4562e,
+ "Missable_Rocket_Hideout_B4F_Item_3": 0x45635,
+ "Missable_Rocket_Hideout_B4F_Item_4": 0x4563c,
+ "Missable_Rocket_Hideout_B4F_Item_5": 0x45643,
+ "Missable_Safari_Zone_East_Item_1": 0x458b2,
+ "Missable_Safari_Zone_East_Item_2": 0x458b9,
+ "Missable_Safari_Zone_East_Item_3": 0x458c0,
+ "Missable_Safari_Zone_East_Item_4": 0x458c7,
+ "Missable_Safari_Zone_North_Item_1": 0x45a12,
+ "Missable_Safari_Zone_North_Item_2": 0x45a19,
+ "Missable_Safari_Zone_Center_Item": 0x45bf9,
+ "Missable_Cerulean_Cave_2F_Item_1": 0x45e36,
+ "Missable_Cerulean_Cave_2F_Item_2": 0x45e3d,
+ "Missable_Cerulean_Cave_2F_Item_3": 0x45e44,
+ "Static_Encounter_Mewtwo": 0x45f44,
+ "Missable_Cerulean_Cave_B1F_Item_1": 0x45f4c,
+ "Missable_Cerulean_Cave_B1F_Item_2": 0x45f53,
+ "Missable_Rock_Tunnel_B1F_Item_1": 0x4619f,
+ "Missable_Rock_Tunnel_B1F_Item_2": 0x461a6,
+ "Missable_Rock_Tunnel_B1F_Item_3": 0x461ad,
+ "Missable_Rock_Tunnel_B1F_Item_4": 0x461b4,
+ "Static_Encounter_Articuno": 0x4690c,
+ "Hidden_Item_Viridian_Forest_1": 0x46e6d,
+ "Hidden_Item_Viridian_Forest_2": 0x46e73,
+ "Hidden_Item_MtMoonB2F_1": 0x46e7a,
+ "Hidden_Item_MtMoonB2F_2": 0x46e80,
+ "Hidden_Item_Route_25_1": 0x46e94,
+ "Hidden_Item_Route_25_2": 0x46e9a,
+ "Hidden_Item_Route_9": 0x46ea1,
+ "Hidden_Item_SS_Anne_Kitchen": 0x46eb4,
+ "Hidden_Item_SS_Anne_B1F": 0x46ebb,
+ "Hidden_Item_Route_10_1": 0x46ec2,
+ "Hidden_Item_Route_10_2": 0x46ec8,
+ "Hidden_Item_Rocket_Hideout_B1F": 0x46ecf,
+ "Hidden_Item_Rocket_Hideout_B3F": 0x46ed6,
+ "Hidden_Item_Rocket_Hideout_B4F": 0x46edd,
+ "Hidden_Item_Pokemon_Tower_5F": 0x46ef1,
+ "Hidden_Item_Route_13_1": 0x46ef8,
+ "Hidden_Item_Route_13_2": 0x46efe,
+ "Hidden_Item_Safari_Zone_West": 0x46f0c,
+ "Hidden_Item_Silph_Co_5F": 0x46f13,
+ "Hidden_Item_Silph_Co_9F": 0x46f1a,
+ "Hidden_Item_Copycats_House": 0x46f21,
+ "Hidden_Item_Cerulean_Cave_1F": 0x46f28,
+ "Hidden_Item_Cerulean_Cave_B1F": 0x46f2f,
+ "Hidden_Item_Power_Plant_1": 0x46f36,
+ "Hidden_Item_Power_Plant_2": 0x46f3c,
+ "Hidden_Item_Seafoam_Islands_B2F": 0x46f43,
+ "Hidden_Item_Seafoam_Islands_B4F": 0x46f4a,
+ "Hidden_Item_Pokemon_Mansion_1F": 0x46f51,
+ "Hidden_Item_Pokemon_Mansion_3F": 0x46f65,
+ "Hidden_Item_Pokemon_Mansion_B1F": 0x46f72,
+ "Hidden_Item_Route_23_1": 0x46f85,
+ "Hidden_Item_Route_23_2": 0x46f8b,
+ "Hidden_Item_Route_23_3": 0x46f91,
+ "Hidden_Item_Victory_Road_2F_1": 0x46f98,
+ "Hidden_Item_Victory_Road_2F_2": 0x46f9e,
+ "Hidden_Item_Unused_6F": 0x46fa5,
+ "Hidden_Item_Viridian_City": 0x46fb3,
+ "Hidden_Item_Route_11": 0x47060,
+ "Hidden_Item_Route_12": 0x47067,
+ "Hidden_Item_Route_17_1": 0x47075,
+ "Hidden_Item_Route_17_2": 0x4707b,
+ "Hidden_Item_Route_17_3": 0x47081,
+ "Hidden_Item_Route_17_4": 0x47087,
+ "Hidden_Item_Route_17_5": 0x4708d,
+ "Hidden_Item_Underground_Path_NS_1": 0x47094,
+ "Hidden_Item_Underground_Path_NS_2": 0x4709a,
+ "Hidden_Item_Underground_Path_WE_1": 0x470a1,
+ "Hidden_Item_Underground_Path_WE_2": 0x470a7,
+ "Hidden_Item_Celadon_City": 0x470ae,
+ "Hidden_Item_Seafoam_Islands_B3F": 0x470b5,
+ "Hidden_Item_Vermilion_City": 0x470bc,
+ "Hidden_Item_Cerulean_City": 0x470c3,
+ "Hidden_Item_Route_4": 0x470ca,
+ "Event_Counter": 0x482d3,
+ "Event_Thirsty_Girl_Lemonade": 0x484f9,
+ "Event_Thirsty_Girl_Soda": 0x4851d,
+ "Event_Thirsty_Girl_Water": 0x48541,
+ "Option_Tea": 0x4871d,
+ "Event_Mansion_Lady": 0x4872a,
+ "Badge_Celadon_Gym": 0x48a1b,
+ "Event_Celadon_Gym": 0x48a2f,
+ "Event_Gambling_Addict": 0x49293,
+ "Gift_Magikarp": 0x49430,
+ "Option_Aide_Rt11": 0x4958d,
+ "Event_Rt11_Oaks_Aide": 0x49591,
+ "Event_Mourning_Girl": 0x4968b,
+ "Option_Aide_Rt15": 0x49776,
+ "Event_Rt_15_Oaks_Aide": 0x4977a,
+ "Missable_Mt_Moon_1F_Item_1": 0x49c75,
+ "Missable_Mt_Moon_1F_Item_2": 0x49c7c,
+ "Missable_Mt_Moon_1F_Item_3": 0x49c83,
+ "Missable_Mt_Moon_1F_Item_4": 0x49c8a,
+ "Missable_Mt_Moon_1F_Item_5": 0x49c91,
+ "Missable_Mt_Moon_1F_Item_6": 0x49c98,
+ "Dome_Fossil_Text": 0x4a001,
+ "Event_Dome_Fossil": 0x4a021,
+ "Helix_Fossil_Text": 0x4a05d,
+ "Event_Helix_Fossil": 0x4a07d,
+ "Missable_Mt_Moon_B2F_Item_1": 0x4a166,
+ "Missable_Mt_Moon_B2F_Item_2": 0x4a16d,
+ "Missable_Safari_Zone_West_Item_1": 0x4a34f,
+ "Missable_Safari_Zone_West_Item_2": 0x4a356,
+ "Missable_Safari_Zone_West_Item_3": 0x4a35d,
+ "Missable_Safari_Zone_West_Item_4": 0x4a364,
+ "Event_Safari_Zone_Secret_House": 0x4a469,
+ "Missable_Route_24_Item": 0x506e6,
+ "Missable_Route_25_Item": 0x5080b,
+ "Starter2_B": 0x50fce,
+ "Starter3_B": 0x50fd0,
+ "Starter1_B": 0x50fd2,
+ "Starter2_A": 0x510f1,
+ "Starter3_A": 0x510f3,
+ "Starter1_A": 0x510f5,
+ "Option_Badge_Goal": 0x51317,
+ "Event_Nugget_Bridge": 0x5148f,
+ "Static_Encounter_Moltres": 0x51939,
+ "Missable_Victory_Road_2F_Item_1": 0x51941,
+ "Missable_Victory_Road_2F_Item_2": 0x51948,
+ "Missable_Victory_Road_2F_Item_3": 0x5194f,
+ "Missable_Victory_Road_2F_Item_4": 0x51956,
+ "Starter2_L": 0x51c85,
+ "Starter3_L": 0x51c8d,
+ "Gift_Lapras": 0x51d83,
+ "Missable_Silph_Co_7F_Item_1": 0x51f0d,
+ "Missable_Silph_Co_7F_Item_2": 0x51f14,
+ "Missable_Pokemon_Mansion_2F_Item": 0x520c9,
+ "Missable_Pokemon_Mansion_3F_Item_1": 0x522e2,
+ "Missable_Pokemon_Mansion_3F_Item_2": 0x522e9,
+ "Missable_Pokemon_Mansion_B1F_Item_1": 0x5248c,
+ "Missable_Pokemon_Mansion_B1F_Item_2": 0x52493,
+ "Missable_Pokemon_Mansion_B1F_Item_3": 0x5249a,
+ "Missable_Pokemon_Mansion_B1F_Item_4": 0x524a1,
+ "Missable_Pokemon_Mansion_B1F_Item_5": 0x524ae,
+ "Option_Safari_Zone_Battle_Type": 0x525c3,
+ "Prize_Mon_A2": 0x5282f,
+ "Prize_Mon_B2": 0x52830,
+ "Prize_Mon_C2": 0x52831,
+ "Prize_Mon_D2": 0x5283a,
+ "Prize_Mon_E2": 0x5283b,
+ "Prize_Mon_F2": 0x5283c,
+ "Prize_Mon_A": 0x52960,
+ "Prize_Mon_B": 0x52962,
+ "Prize_Mon_C": 0x52964,
+ "Prize_Mon_D": 0x52966,
+ "Prize_Mon_E": 0x52968,
+ "Prize_Mon_F": 0x5296a,
+ "Missable_Route_2_Item_1": 0x5404a,
+ "Missable_Route_2_Item_2": 0x54051,
+ "Missable_Route_4_Item": 0x543df,
+ "Missable_Route_9_Item": 0x546fd,
+ "Option_EXP_Modifier": 0x552c5,
+ "Rod_Vermilion_City_Fishing_Guru": 0x560df,
+ "Rod_Fuchsia_City_Fishing_Brother": 0x561eb,
+ "Rod_Route12_Fishing_Brother": 0x564ee,
+ "Missable_Route_12_Item_1": 0x58704,
+ "Missable_Route_12_Item_2": 0x5870b,
+ "Missable_Route_15_Item": 0x589c7,
+ "Ghost_Battle6": 0x58df0,
+ "Static_Encounter_Snorlax_A": 0x5969b,
+ "Static_Encounter_Snorlax_B": 0x599db,
+ "Event_Pokemon_Fan_Club": 0x59c8b,
+ "Event_Scared_Woman": 0x59e1f,
+ "Missable_Silph_Co_3F_Item": 0x5a0cb,
+ "Missable_Silph_Co_10F_Item_1": 0x5a281,
+ "Missable_Silph_Co_10F_Item_2": 0x5a288,
+ "Missable_Silph_Co_10F_Item_3": 0x5a28f,
+ "Guard_Drink_List": 0x5a600,
+ "Event_Museum": 0x5c266,
+ "Badge_Pewter_Gym": 0x5c3ed,
+ "Event_Pewter_Gym": 0x5c401,
+ "Badge_Cerulean_Gym": 0x5c716,
+ "Event_Cerulean_Gym": 0x5c72a,
+ "Badge_Vermilion_Gym": 0x5caba,
+ "Event_Vermillion_Gym": 0x5cace,
+ "Event_Copycat": 0x5cca9,
+ "Gift_Hitmonlee": 0x5cf1a,
+ "Gift_Hitmonchan": 0x5cf62,
+ "Badge_Saffron_Gym": 0x5d079,
+ "Event_Saffron_Gym": 0x5d08d,
+ "Option_Aide_Rt2": 0x5d5f2,
+ "Event_Route_2_Oaks_Aide": 0x5d5f6,
+ "Missable_Victory_Road_1F_Item_1": 0x5dae6,
+ "Missable_Victory_Road_1F_Item_2": 0x5daed,
+ "Starter2_J": 0x6060e,
+ "Starter3_J": 0x60616,
+ "Missable_Pokemon_Tower_3F_Item": 0x60787,
+ "Missable_Pokemon_Tower_4F_Item_1": 0x608b5,
+ "Missable_Pokemon_Tower_4F_Item_2": 0x608bc,
+ "Missable_Pokemon_Tower_4F_Item_3": 0x608c3,
+ "Missable_Pokemon_Tower_5F_Item": 0x60a80,
+ "Ghost_Battle1": 0x60b33,
+ "Ghost_Battle2": 0x60c0a,
+ "Missable_Pokemon_Tower_6F_Item_1": 0x60c85,
+ "Missable_Pokemon_Tower_6F_Item_2": 0x60c8c,
+ "Gift_Aerodactyl": 0x61064,
+ "Gift_Omanyte": 0x61068,
+ "Gift_Kabuto": 0x6106c,
+ "Missable_Viridian_Forest_Item_1": 0x6122c,
+ "Missable_Viridian_Forest_Item_2": 0x61233,
+ "Missable_Viridian_Forest_Item_3": 0x6123a,
+ "Starter2_M": 0x61450,
+ "Starter3_M": 0x61458,
+ "Event_SS_Anne_Captain": 0x618c3,
+ "Missable_SS_Anne_1F_Item": 0x61ac0,
+ "Missable_SS_Anne_2F_Item_1": 0x61ced,
+ "Missable_SS_Anne_2F_Item_2": 0x61d00,
+ "Missable_SS_Anne_B1F_Item_1": 0x61ee3,
+ "Missable_SS_Anne_B1F_Item_2": 0x61eea,
+ "Missable_SS_Anne_B1F_Item_3": 0x61ef1,
+ "Event_Silph_Co_President": 0x622ed,
+ "Ghost_Battle4": 0x708e1,
+ "Badge_Viridian_Gym": 0x749ca,
+ "Event_Viridian_Gym": 0x749de,
+ "Missable_Viridian_Gym_Item": 0x74c63,
+ "Missable_Cerulean_Cave_1F_Item_1": 0x74d68,
+ "Missable_Cerulean_Cave_1F_Item_2": 0x74d6f,
+ "Missable_Cerulean_Cave_1F_Item_3": 0x74d76,
+ "Event_Warden": 0x7512a,
+ "Missable_Wardens_House_Item": 0x751b7,
+ "Badge_Fuchsia_Gym": 0x755cd,
+ "Event_Fuschia_Gym": 0x755e1,
+ "Badge_Cinnabar_Gym": 0x75995,
+ "Event_Cinnabar_Gym": 0x759a9,
+ "Event_Lab_Scientist": 0x75dd6,
+ "Fossils_Needed_For_Second_Item": 0x75ea3,
+ "Event_Dome_Fossil_B": 0x75f20,
+ "Event_Helix_Fossil_B": 0x75f40,
+ "Starter2_N": 0x76169,
+ "Starter3_N": 0x76171,
+ "Option_Itemfinder": 0x76864,
+ "Text_Badges_Needed": 0x92304,
+ "Badge_Text_Boulder_Badge": 0x990b3,
+ "Badge_Text_Cascade_Badge": 0x990cb,
+ "Badge_Text_Thunder_Badge": 0x99111,
+ "Badge_Text_Rainbow_Badge": 0x9912e,
+ "Badge_Text_Soul_Badge": 0x99177,
+ "Badge_Text_Marsh_Badge": 0x9918c,
+ "Badge_Text_Volcano_Badge": 0x991d6,
+ "Badge_Text_Earth_Badge": 0x991f3,
+}
diff --git a/worlds/pokemon_rb/rules.py b/worlds/pokemon_rb/rules.py
new file mode 100644
index 0000000000..0e97b705db
--- /dev/null
+++ b/worlds/pokemon_rb/rules.py
@@ -0,0 +1,165 @@
+from ..generic.Rules import add_item_rule, add_rule
+
+def set_rules(world, player):
+
+ add_item_rule(world.get_location("Pallet Town - Player's PC", player),
+ lambda i: i.player == player and "Badge" not in i.name)
+
+ access_rules = {
+
+ "Pallet Town - Rival's Sister": lambda state: state.has("Oak's Parcel", player),
+ "Pallet Town - Oak's Post-Route-22-Rival Gift": lambda state: state.has("Oak's Parcel", player),
+ "Viridian City - Sleepy Guy": lambda state: state.pokemon_rb_can_cut(player) or state.pokemon_rb_can_surf(player),
+ "Route 2 - Oak's Aide": lambda state: state.pokemon_rb_has_pokemon(state.world.oaks_aide_rt_2[player].value + 5, player),
+ "Pewter City - Museum": lambda state: state.pokemon_rb_can_cut(player),
+ "Cerulean City - Bicycle Shop": lambda state: state.has("Bike Voucher", player),
+ "Lavender Town - Mr. Fuji": lambda state: state.has("Fuji Saved", player),
+ "Vermilion Gym - Lt. Surge 1": lambda state: state.pokemon_rb_can_cut(player or state.pokemon_rb_can_surf(player)),
+ "Vermilion Gym - Lt. Surge 2": lambda state: state.pokemon_rb_can_cut(player or state.pokemon_rb_can_surf(player)),
+ "Route 11 - Oak's Aide": lambda state: state.pokemon_rb_has_pokemon(state.world.oaks_aide_rt_11[player].value + 5, player),
+ "Celadon City - Stranded Man": lambda state: state.pokemon_rb_can_surf(player),
+ "Silph Co 11F - Silph Co President": lambda state: state.has("Card Key", player),
+ "Fuchsia City - Safari Zone Warden": lambda state: state.has("Gold Teeth", player),
+ "Route 12 - Island Item": lambda state: state.pokemon_rb_can_surf(player),
+ "Route 12 - Item Behind Cuttable Tree": lambda state: state.pokemon_rb_can_cut(player),
+ "Route 15 - Item": lambda state: state.pokemon_rb_can_cut(player),
+ "Route 25 - Item": lambda state: state.pokemon_rb_can_cut(player),
+ "Fuchsia City - Warden's House Item": lambda state: state.pokemon_rb_can_strength(player),
+ "Rocket Hideout B4F - Southwest Item (Lift Key)": lambda state: state.has("Lift Key", player),
+ "Rocket Hideout B4F - Giovanni Item (Lift Key)": lambda state: state.has("Lift Key", player),
+ "Silph Co 3F - Item (Card Key)": lambda state: state.has("Card Key", player),
+ "Silph Co 4F - Left Item (Card Key)": lambda state: state.has("Card Key", player),
+ "Silph Co 4F - Middle Item (Card Key)": lambda state: state.has("Card Key", player),
+ "Silph Co 4F - Right Item (Card Key)": lambda state: state.has("Card Key", player),
+ "Silph Co 5F - Northwest Item (Card Key)": lambda state: state.has("Card Key", player),
+ "Silph Co 6F - West Item (Card Key)": lambda state: state.has("Card Key", player),
+ "Silph Co 6F - Southwest Item (Card Key)": lambda state: state.has("Card Key", player),
+ "Silph Co 7F - East Item (Card Key)": lambda state: state.has("Card Key", player),
+ "Safari Zone Center - Island Item": lambda state: state.pokemon_rb_can_surf(player),
+
+ "Silph Co 11F - Silph Co Liberated": lambda state: state.has("Card Key", player),
+
+ "Pallet Town - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
+ "Pallet Town - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
+ "Route 22 - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
+ "Route 22 - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
+ "Route 24 - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
+ "Route 24 - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
+ "Route 24 - Super Rod Pokemon - 3": lambda state: state.has("Super Rod", player),
+ "Route 6 - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
+ "Route 6 - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
+ "Route 10 - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
+ "Route 10 - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
+ "Safari Zone Center - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
+ "Safari Zone Center - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
+ "Safari Zone Center - Super Rod Pokemon - 3": lambda state: state.has("Super Rod", player),
+ "Safari Zone Center - Super Rod Pokemon - 4": lambda state: state.has("Super Rod", player),
+ "Route 12 - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
+ "Route 12 - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
+ "Route 12 - Super Rod Pokemon - 3": lambda state: state.has("Super Rod", player),
+ "Route 12 - Super Rod Pokemon - 4": lambda state: state.has("Super Rod", player),
+ "Route 19 - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
+ "Route 19 - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
+ "Route 19 - Super Rod Pokemon - 3": lambda state: state.has("Super Rod", player),
+ "Route 19 - Super Rod Pokemon - 4": lambda state: state.has("Super Rod", player),
+ "Route 23 - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
+ "Route 23 - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
+ "Route 23 - Super Rod Pokemon - 3": lambda state: state.has("Super Rod", player),
+ "Route 23 - Super Rod Pokemon - 4": lambda state: state.has("Super Rod", player),
+ "Fuchsia City - Super Rod Pokemon - 1": lambda state: state.has("Super Rod", player),
+ "Fuchsia City - Super Rod Pokemon - 2": lambda state: state.has("Super Rod", player),
+ "Fuchsia City - Super Rod Pokemon - 3": lambda state: state.has("Super Rod", player),
+ "Fuchsia City - Super Rod Pokemon - 4": lambda state: state.has("Super Rod", player),
+ "Anywhere - Good Rod Pokemon - 1": lambda state: state.has("Good Rod", player),
+ "Anywhere - Good Rod Pokemon - 2": lambda state: state.has("Good Rod", player),
+ "Anywhere - Old Rod Pokemon": lambda state: state.has("Old Rod", player),
+ "Celadon Prize Corner - Pokemon Prize - 1": lambda state: state.has("Coin Case", player),
+ "Celadon Prize Corner - Pokemon Prize - 2": lambda state: state.has("Coin Case", player),
+ "Celadon Prize Corner - Pokemon Prize - 3": lambda state: state.has("Coin Case", player),
+ "Celadon Prize Corner - Pokemon Prize - 4": lambda state: state.has("Coin Case", player),
+ "Celadon Prize Corner - Pokemon Prize - 5": lambda state: state.has("Coin Case", player),
+ "Celadon Prize Corner - Pokemon Prize - 6": lambda state: state.has("Coin Case", player),
+ "Cinnabar Island - Old Amber Pokemon": lambda state: state.has("Old Amber", player),
+ "Cinnabar Island - Helix Fossil Pokemon": lambda state: state.has("Helix Fossil", player),
+ "Cinnabar Island - Dome Fossil Pokemon": lambda state: state.has("Dome Fossil", player),
+ "Route 12 - Sleeping Pokemon": lambda state: state.has("Poke Flute", player),
+ "Route 16 - Sleeping Pokemon": lambda state: state.has("Poke Flute", player),
+ "Seafoam Islands B4F - Legendary Pokemon": lambda state: state.pokemon_rb_can_strength(player),
+ "Vermilion City - Legendary Pokemon": lambda state: state.pokemon_rb_can_surf(player) and state.has("S.S. Ticket", player)
+ }
+
+ hidden_item_access_rules = {
+ "Viridian Forest - Hidden Item Northwest by Trainer": lambda state: state.pokemon_rb_can_get_hidden_items(
+ player),
+ "Viridian Forest - Hidden Item Entrance Tree": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Mt Moon B2F - Hidden Item Dead End Before Fossils": lambda state: state.pokemon_rb_can_get_hidden_items(
+ player),
+ "Route 25 - Hidden Item Fence Outside Bill's House": lambda state: state.pokemon_rb_can_get_hidden_items(
+ player),
+ "Route 9 - Hidden Item Rock By Grass": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "S.S. Anne 1F - Hidden Item Kitchen Trash": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "S.S. Anne B1F - Hidden Item Under Pillow": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Route 10 - Hidden Item Behind Rock Tunnel Entrance Tree": lambda
+ state: state.pokemon_rb_can_get_hidden_items(player),
+ "Route 10 - Hidden Item Rock": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Rocket Hideout B1F - Hidden Item Pot Plant": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Rocket Hideout B3F - Hidden Item Near East Item": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Rocket Hideout B4F - Hidden Item Behind Giovanni": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Pokemon Tower 5F - Hidden Item Near West Staircase": lambda state: state.pokemon_rb_can_get_hidden_items(
+ player),
+ "Route 13 - Hidden Item Dead End Boulder": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Route 13 - Hidden Item Dead End By Water Corner": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Pokemon Mansion B1F - Hidden Item Secret Key Room Corner": lambda state: state.pokemon_rb_can_get_hidden_items(
+ player),
+ "Safari Zone West - Hidden Item Secret House Statue": lambda state: state.pokemon_rb_can_get_hidden_items(
+ player),
+ "Silph Co 5F - Hidden Item Pot Plant": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Silph Co 9F - Hidden Item Nurse Bed": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Copycat's House - Hidden Item Desk": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Cerulean Cave 1F - Hidden Item Center Rocks": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Cerulean Cave B1F - Hidden Item Northeast Rocks": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Power Plant - Hidden Item Central Dead End": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Power Plant - Hidden Item Before Zapdos": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Seafoam Islands B2F - Hidden Item Rock": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Seafoam Islands B4F - Hidden Item Corner Island": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Pokemon Mansion 1F - Hidden Item Block Near Entrance Carpet": lambda
+ state: state.pokemon_rb_can_get_hidden_items(player),
+ "Pokemon Mansion 3F - Hidden Item Behind Burglar": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Route 23 - Hidden Item Rocks Before Final Guard": lambda state: state.pokemon_rb_can_get_hidden_items(
+ player),
+ "Route 23 - Hidden Item East Tree After Water": lambda state: state.pokemon_rb_can_get_hidden_items(
+ player),
+ "Route 23 - Hidden Item On Island": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Victory Road 2F - Hidden Item Rock Before Moltres": lambda state: state.pokemon_rb_can_get_hidden_items(
+ player),
+ "Victory Road 2F - Hidden Item Rock In Final Room": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Viridian City - Hidden Item Cuttable Tree": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Route 11 - Hidden Item Isolated Tree Near Gate": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Route 12 - Hidden Item Tree Near Gate": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Route 17 - Hidden Item In Grass": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Route 17 - Hidden Item Near Northernmost Sign": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Route 17 - Hidden Item East Center": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Route 17 - Hidden Item West Center": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Route 17 - Hidden Item Before Final Bridge": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Underground Tunnel North-South - Hidden Item Near Northern Stairs": lambda
+ state: state.pokemon_rb_can_get_hidden_items(player),
+ "Underground Tunnel North-South - Hidden Item Near Southern Stairs": lambda
+ state: state.pokemon_rb_can_get_hidden_items(player),
+ "Underground Tunnel West-East - Hidden Item West": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Underground Tunnel West-East - Hidden Item East": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Celadon City - Hidden Item Dead End Near Cuttable Tree": lambda state: state.pokemon_rb_can_get_hidden_items(
+ player),
+ "Route 25 - Hidden Item Northeast Of Grass": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Mt Moon B2F - Hidden Item Lone Rock": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Seafoam Islands B3F - Hidden Item Rock": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ "Vermilion City - Hidden Item In Water Near Fan Club": lambda state: state.pokemon_rb_can_get_hidden_items(
+ player),
+ "Cerulean City - Hidden Item Gym Badge Guy's Backyard": lambda state: state.pokemon_rb_can_get_hidden_items(
+ player),
+ "Route 4 - Hidden Item Plateau East Of Mt Moon": lambda state: state.pokemon_rb_can_get_hidden_items(player),
+ }
+ for loc, rule in access_rules.items():
+ add_rule(world.get_location(loc, player), rule)
+ if world.randomize_hidden_items[player].value != 0:
+ for loc, rule in hidden_item_access_rules.items():
+ add_rule(world.get_location(loc, player), rule)
diff --git a/worlds/pokemon_rb/text.py b/worlds/pokemon_rb/text.py
new file mode 100644
index 0000000000..e15623d4b8
--- /dev/null
+++ b/worlds/pokemon_rb/text.py
@@ -0,0 +1,147 @@
+special_chars = {
+ "PKMN": 0x4A,
+ "'d": 0xBB,
+ "'l": 0xBC,
+ "'t": 0xBE,
+ "'v": 0xBF,
+ "PK": 0xE1,
+ "MN": 0xE2,
+ "'r": 0xE4,
+ "'m": 0xE5,
+ "MALE": 0xEF,
+ "FEMALE": 0xF5,
+}
+
+char_map = {
+ "@": 0x50, # String terminator
+ "#": 0x54, # Poké
+ "‘": 0x70,
+ "’": 0x71,
+ "“": 0x72,
+ "”": 0x73,
+ "·": 0x74,
+ "…": 0x75,
+ " ": 0x7F,
+ "A": 0x80,
+ "B": 0x81,
+ "C": 0x82,
+ "D": 0x83,
+ "E": 0x84,
+ "F": 0x85,
+ "G": 0x86,
+ "H": 0x87,
+ "I": 0x88,
+ "J": 0x89,
+ "K": 0x8A,
+ "L": 0x8B,
+ "M": 0x8C,
+ "N": 0x8D,
+ "O": 0x8E,
+ "P": 0x8F,
+ "Q": 0x90,
+ "R": 0x91,
+ "S": 0x92,
+ "T": 0x93,
+ "U": 0x94,
+ "V": 0x95,
+ "W": 0x96,
+ "X": 0x97,
+ "Y": 0x98,
+ "Z": 0x99,
+ "(": 0x9A,
+ ")": 0x9B,
+ ":": 0x9C,
+ ";": 0x9D,
+ "[": 0x9E,
+ "]": 0x9F,
+ "a": 0xA0,
+ "b": 0xA1,
+ "c": 0xA2,
+ "d": 0xA3,
+ "e": 0xA4,
+ "f": 0xA5,
+ "g": 0xA6,
+ "h": 0xA7,
+ "i": 0xA8,
+ "j": 0xA9,
+ "k": 0xAA,
+ "l": 0xAB,
+ "m": 0xAC,
+ "n": 0xAD,
+ "o": 0xAE,
+ "p": 0xAF,
+ "q": 0xB0,
+ "r": 0xB1,
+ "s": 0xB2,
+ "t": 0xB3,
+ "u": 0xB4,
+ "v": 0xB5,
+ "w": 0xB6,
+ "x": 0xB7,
+ "y": 0xB8,
+ "z": 0xB9,
+ "é": 0xBA,
+ "'": 0xE0,
+ "-": 0xE3,
+ "?": 0xE6,
+ "!": 0xE7,
+ ".": 0xE8,
+ "♂": 0xEF,
+ "¥": 0xF0,
+ "$": 0xF0,
+ "×": 0xF1,
+ "/": 0xF3,
+ ",": 0xF4,
+ "♀": 0xF5,
+ "0": 0xF6,
+ "1": 0xF7,
+ "2": 0xF8,
+ "3": 0xF9,
+ "4": 0xFA,
+ "5": 0xFB,
+ "6": 0xFC,
+ "7": 0xFD,
+ "8": 0xFE,
+ "9": 0xFF,
+}
+
+unsafe_chars = ["@", "#", "PKMN"]
+
+
+def encode_text(text: str, length: int=0, whitespace=False, force=False, safety=False):
+ encoded_text = bytearray()
+ spec_char = ""
+ special = False
+ for char in text:
+ if char == ">":
+ if spec_char in unsafe_chars and safety:
+ raise KeyError(f"Disallowed Pokemon text special character '<{spec_char}>'")
+ try:
+ encoded_text.append(special_chars[spec_char])
+ except KeyError:
+ if force:
+ encoded_text.append(char_map[" "])
+ else:
+ raise KeyError(f"Invalid Pokemon text special character '<{spec_char}>'")
+ spec_char = ""
+ special = False
+ elif char == "<":
+ spec_char = ""
+ special = True
+ elif special is True:
+ spec_char += char
+ else:
+ if char in unsafe_chars and safety:
+ raise KeyError(f"Disallowed Pokemon text character '{char}'")
+ try:
+ encoded_text.append(char_map[char])
+ except KeyError:
+ if force:
+ encoded_text.append(char_map[" "])
+ else:
+ raise KeyError(f"Invalid Pokemon text character '{char}'")
+ if length > 0:
+ encoded_text = encoded_text[:length]
+ while whitespace and len(encoded_text) < length:
+ encoded_text.append(char_map[" " if whitespace is True else whitespace])
+ return encoded_text
diff --git a/worlds/sc2wol/docs/en_Starcraft 2 Wings of Liberty.md b/worlds/sc2wol/docs/en_Starcraft 2 Wings of Liberty.md
index 8fa20c86f9..f7c8519a2a 100644
--- a/worlds/sc2wol/docs/en_Starcraft 2 Wings of Liberty.md
+++ b/worlds/sc2wol/docs/en_Starcraft 2 Wings of Liberty.md
@@ -15,7 +15,7 @@ missions. When you receive items, they will immediately become available, even d
notified via a text box in the top-right corner of the game screen. (The text client for StarCraft 2 also records all
items in all worlds.)
-Missions are launched only through the text client. The Hyperion is never visited. Aditionally, credits are not used.
+Missions are launched only through the text client. The Hyperion is never visited. Additionally, credits are not used.
## What is the goal of this game when randomized?
diff --git a/worlds/sc2wol/docs/setup_en.md b/worlds/sc2wol/docs/setup_en.md
index 1539a21291..267c8430aa 100644
--- a/worlds/sc2wol/docs/setup_en.md
+++ b/worlds/sc2wol/docs/setup_en.md
@@ -13,9 +13,10 @@ to obtain a config file for StarCraft 2.
1. Install StarCraft 2 and Archipelago using the first two links above. (The StarCraft 2 client for Archipelago is
included by default.)
-2. Click the third link above and follow the instructions there.
-3. Linux users should also follow the instructions found at the bottom of this page
- (["Running in Linux"](#running-in-linux)).
+ - Linux users should also follow the instructions found at the bottom of this page
+ (["Running in Linux"](#running-in-linux)).
+2. Run ArchipelagoStarcraft2Client.exe.
+3. Type the command `/download_data`. This will automatically install the Maps and Data files from the third link above.
## Where do I get a config file (aka "YAML") for this game?
@@ -40,16 +41,9 @@ Check out [Creating a YAML](https://archipelago.gg/tutorial/Archipelago/setup/en
## The game isn't launching when I try to start a mission.
-First, check the log file for issues (stored at `[Archipelago Directory]/logs/SC2Client.txt`). If the below fix doesn't
-work for you, and you can't figure out the log file, visit our [Discord's](https://discord.com/invite/8Z65BR2)
-tech-support channel for help. Please include a specific description of what's going wrong and attach your log file to
-your message.
-
-### Check your installation
-
-Make sure you've followed the installation instructions completely. Specifically, make sure that you've placed the Maps
-and Mods folders directly inside the StarCraft II installation folder. They should be in the same location as the
-SC2Data, Support, Support64, and Versions folders.
+First, check the log file for issues (stored at `[Archipelago Directory]/logs/SC2Client.txt`). If you can't figure out
+the log file, visit our [Discord's](https://discord.com/invite/8Z65BR2) tech-support channel for help. Please include a
+specific description of what's going wrong and attach your log file to your message.
## Running in Linux
diff --git a/worlds/sm64ex/__init__.py b/worlds/sm64ex/__init__.py
index 8cf2f74350..b1eef4ff2c 100644
--- a/worlds/sm64ex/__init__.py
+++ b/worlds/sm64ex/__init__.py
@@ -36,7 +36,7 @@ class SM64World(World):
location_name_to_id = location_table
data_version = 8
- required_client_version = (0, 3, 0)
+ required_client_version = (0, 3, 5)
area_connections: typing.Dict[int, int]
diff --git a/worlds/sm64ex/docs/setup_en.md b/worlds/sm64ex/docs/setup_en.md
index d77e091359..acf9432fe5 100644
--- a/worlds/sm64ex/docs/setup_en.md
+++ b/worlds/sm64ex/docs/setup_en.md
@@ -3,8 +3,10 @@
## Required Software
- Super Mario 64 US Rom (Japanese may work also. Europe and Shindou not supported)
-- Either of [sm64pclauncher](https://github.com/N00byKing/sm64pclauncher/releases) or
-- Cloning and building [sm64ex](https://github.com/N00byKing/sm64ex) manually.
+- Either of
+ - [sm64pclauncher](https://github.com/N00byKing/sm64pclauncher/releases) or
+ - Cloning and building [sm64ex](https://github.com/N00byKing/sm64ex) manually
+- Optional, for sending [commands](/tutorial/Archipelago/commands/en) like `!hint`: the TextClient from [the most recent Archipelago release](https://github.com/ArchipelagoMW/Archipelago/releases)
NOTE: The above linked sm64pclauncher is a special version designed to work with the Archipelago build of sm64ex.
You can use other sm64-port based builds with it, but you can't use a different launcher with the Archipelago build of sm64ex.
@@ -25,7 +27,9 @@ Then follow the steps below
6. Set the location where you installed MSYS when prompted. Check the "Install Dependencies" Checkbox
7. Set the Repo link to `https://github.com/N00byKing/sm64ex` and the Branch to `archipelago` (Top two boxes). You can choose the folder (Secound Box) at will, as long as it does not exist yet
8. Point the Launcher to your Super Mario 64 US/JP Rom, and set the Region correspondingly
-9. Set Build Options. Recommended: `-jn` where `n` is the Number of CPU Cores, to build faster.
+9. Set Build Options and press build.
+ - Recommended: To build faster, use `-jn` where `n` is the number of CPU cores to use (e.g., `-j4` to use 4 cores).
+ - Optional: Add options from [this list](https://github.com/sm64pc/sm64ex/wiki/Build-options), separated by spaces (e.g., `-j4 BETTERCAMERA=1`).
10. SM64EX will now be compiled. The Launcher will appear to have crashed, but this is not likely the case. Best wait a bit, but there may be a problem if it takes longer than 10 Minutes
After it's done, the Build list should have another entry titled with what you named the folder in step 7.
@@ -55,6 +59,9 @@ In case you are using the Archipelago Website, the IP should be `archipelago.gg`
Should the connection fail (for example when using the wrong name or IP/Port combination) the game will inform you of that.
Additionally, any time the game is not connected (for example when the connection is unstable) it will attempt to reconnect and display a status text.
+**Important:** You must start a new file for every new seed you play. Using `⭐x0` files is **not** sufficient.
+Failing to use a new file may make some locations unavailable. However, this can be fixed without losing any progress by exiting and starting a new file.
+
# Playing offline
To play offline, first generate a seed on the game's settings page.
@@ -83,6 +90,11 @@ with its name.
When using a US Rom, the In-Game messages are missing some letters: `J Q V X Z` and `?`.
The Japanese Version should have no problem displaying these.
+### Toad does not have an item for me.
+
+This happens when you load an existing file that had already received an item from that toad.
+To resolve this, exit and start from a `NEW` file. The server will automatically restore your progress.
+
### What happens if I lose connection?
SM64EX tries to reconnect a few times, so be patient.
diff --git a/worlds/smz3/data/zsm.ips b/worlds/smz3/data/zsm.ips
index 2d6027d5e5..25e543d987 100644
Binary files a/worlds/smz3/data/zsm.ips and b/worlds/smz3/data/zsm.ips differ
diff --git a/worlds/witness/Options.py b/worlds/witness/Options.py
index 2cb9ade007..b858eb1954 100644
--- a/worlds/witness/Options.py
+++ b/worlds/witness/Options.py
@@ -7,7 +7,7 @@ from Options import Toggle, DefaultOnToggle, Option, Range, Choice
# "Play the randomizer in hardmode"
# display_name = "Hard Mode"
-class DisableNonRandomizedPuzzles(DefaultOnToggle):
+class DisableNonRandomizedPuzzles(Toggle):
"""Disables puzzles that cannot be randomized.
This includes many puzzles that heavily involve the environment, such as Shadows, Monastery or Orchard.
The lasers for those areas will be activated as you solve optional puzzles throughout the island."""
@@ -59,8 +59,9 @@ class ShuffleVaultBoxes(Toggle):
class ShufflePostgame(Toggle):
- """Adds locations into the pool that are guaranteed to become accessible before or at the same time as your goal.
- Use this if you don't play with forfeit on victory."""
+ """Adds locations into the pool that are guaranteed to become accessible after or at the same time as your goal.
+ Use this if you don't play with forfeit on victory. IMPORTANT NOTE: The possibility of your second
+ "Progressive Dots" showing up in the Caves is ignored, they will still be considered "postgame" in base settings."""
display_name = "Shuffle Postgame"
@@ -75,6 +76,13 @@ class VictoryCondition(Choice):
option_mountain_box_long = 3
+class PuzzleRandomization(Choice):
+ """Puzzles in this randomizer are randomly generated. This setting changes the difficulty/types of puzzles."""
+ display_name = "Puzzle Randomization"
+ option_sigma_normal = 0
+ option_sigma_expert = 1
+
+
class MountainLasers(Range):
"""Sets the amount of beams required to enter the final area."""
display_name = "Required Lasers for Mountain Entry"
@@ -108,8 +116,17 @@ class PuzzleSkipAmount(Range):
default = 5
+class HintAmount(Range):
+ """Adds hints to Audio Logs. Hints will have the same number of duplicates, as many as will fit. Remaining Audio
+ Logs will have junk hints."""
+ display_name = "Hints on Audio Logs"
+ range_start = 0
+ range_end = 49
+ default = 10
+
+
the_witness_options: Dict[str, type] = {
- # "hard_mode": HardMode,
+ "puzzle_randomization": PuzzleRandomization,
"shuffle_symbols": ShuffleSymbols,
"shuffle_doors": ShuffleDoors,
"shuffle_lasers": ShuffleLasers,
@@ -123,6 +140,7 @@ the_witness_options: Dict[str, type] = {
"early_secret_area": EarlySecretArea,
"trap_percentage": TrapPercentage,
"puzzle_skip_amount": PuzzleSkipAmount,
+ "hint_amount": HintAmount,
}
diff --git a/worlds/witness/WitnessItems.txt b/worlds/witness/WitnessItems.txt
index fd9b10f97a..d58edab0cb 100644
--- a/worlds/witness/WitnessItems.txt
+++ b/worlds/witness/WitnessItems.txt
@@ -15,6 +15,8 @@ Progression:
71 - Black/White Squares
72 - Colored Squares
80 - Arrows
+200 - Progressive Dots - Dots,Full Dots
+260 - Progressive Stars - Stars,Stars + Same Colored Symbol
Usefuls:
101 - Functioning Brain - False
diff --git a/worlds/witness/WitnessLogic.txt b/worlds/witness/WitnessLogic.txt
index 650cf14c52..236d220701 100644
--- a/worlds/witness/WitnessLogic.txt
+++ b/worlds/witness/WitnessLogic.txt
@@ -120,7 +120,7 @@ Door - 0x03313 (Second Gate) - 0x032FF
Orchard End (Orchard):
Desert Outside (Desert) - Main Island - True - Desert Floodlight Room - 0x09FEE:
-158652 - 0x0CC7B (Vault) - True - Dots & Shapers & Rotated Shapers & Negative Shapers
+158652 - 0x0CC7B (Vault) - True - Dots & Shapers & Rotated Shapers & Negative Shapers & Full Dots
158653 - 0x0339E (Vault Box) - 0x0CC7B - True
158602 - 0x17CE7 (Discard) - True - Triangles
158076 - 0x00698 (Surface 1) - True - True
@@ -338,7 +338,7 @@ Keep 3rd Pressure Plate (Keep) - Keep 4th Pressure Plate - 0x01CD5:
158202 - 0x01CD3 (Pressure Plates 3) - 0x0A3BB - Shapers & Black/White Squares & Colored Squares
Door - 0x01CD5 (Pressure Plates 3 Exit) - 0x01CD3
-Keep 4th Pressure Plate (Keep) - Keep - 0x09E3D - Keep Tower - 0x01D40:
+Keep 4th Pressure Plate (Keep) - Shadows - 0x09E3D - Keep Tower - 0x01D40:
158203 - 0x0A3AD (Reset Pressure Plates 4) - True - True
158204 - 0x01D3F (Pressure Plates 4) - 0x0A3AD - Shapers & Dots & Symmetry
Door - 0x01D40 (Pressure Plates 4 Exit) - 0x01D3F
@@ -358,7 +358,7 @@ Door - 0x04F8F (Tower Shortcut) - 0x0361B
158705 - 0x03317 (Laser Panel Pressure Plates) - 0x01D3F - Shapers & Black/White Squares & Colored Squares & Stars & Stars + Same Colored Symbol & Dots
Laser - 0x014BB (Laser) - 0x0360E | 0x03317
-Outside Monastery (Monastery) - Main Island - True - Main Island - 0x0364E - Inside Monastery - 0x0C128 & 0x0C153 - Monastery Garden - 0x03750:
+Outside Monastery (Monastery) - Main Island - True - Inside Monastery - 0x0C128 & 0x0C153 - Monastery Garden - 0x03750:
158207 - 0x03713 (Shortcut Panel) - True - True
Door - 0x0364E (Shortcut) - 0x03713
158208 - 0x00B10 (Entry Left) - True - True
@@ -390,11 +390,11 @@ Door - 0x0A0C9 (Cargo Box Entry) - 0x0A0C8
158221 - 0x28AE3 (Vines) - 0x18590 - True
158222 - 0x28938 (Apple Tree) - 0x28AE3 - True
158223 - 0x079DF (Triple Exit) - 0x28938 - True
-158235 - 0x2899C (Wooden Roof Lower Row 1) - True - Rotated Shapers & Dots
-158236 - 0x28A33 (Wooden Roof Lower Row 2) - 0x2899C - Shapers & Dots
-158237 - 0x28ABF (Wooden Roof Lower Row 3) - 0x28A33 - Shapers & Rotated Shapers & Dots
-158238 - 0x28AC0 (Wooden Roof Lower Row 4) - 0x28ABF - Rotated Shapers & Dots
-158239 - 0x28AC1 (Wooden Roof Lower Row 5) - 0x28AC0 - Rotated Shapers & Dots
+158235 - 0x2899C (Wooden Roof Lower Row 1) - True - Rotated Shapers & Dots & Full Dots
+158236 - 0x28A33 (Wooden Roof Lower Row 2) - 0x2899C - Shapers & Dots & Full Dots
+158237 - 0x28ABF (Wooden Roof Lower Row 3) - 0x28A33 - Shapers & Rotated Shapers & Dots & Full Dots
+158238 - 0x28AC0 (Wooden Roof Lower Row 4) - 0x28ABF - Rotated Shapers & Dots & Full Dots
+158239 - 0x28AC1 (Wooden Roof Lower Row 5) - 0x28AC0 - Rotated Shapers & Dots & Full Dots
Door - 0x034F5 (Wooden Roof Stairs) - 0x28AC1
158225 - 0x28998 (Tinted Glass Door Panel) - True - Stars & Rotated Shapers
Door - 0x28A61 (Tinted Glass Door) - 0x28998
@@ -421,7 +421,7 @@ Town Red Rooftop (Town):
158224 - 0x28B39 (Tall Hexagonal) - 0x079DF - True
Town Wooden Rooftop (Town):
-158240 - 0x28AD9 (Wooden Rooftop) - 0x28AC1 - Rotated Shapers & Dots & Eraser
+158240 - 0x28AD9 (Wooden Rooftop) - 0x28AC1 - Rotated Shapers & Dots & Eraser & Full Dots
Town Church (Town):
158227 - 0x28A69 (Church Lattice) - 0x03BB0 - True
@@ -819,14 +819,14 @@ Mountain Path to Caves (Mountain Bottom Floor) - Mountain Bottom Floor Rock - 0x
Door - 0x2D77D (Caves Entry) - 0x00FF8
158448 - 0x334E1 (Rock Control) - True - True
-Caves (Caves) - Main Island - 0x2D73F - Main Island - 0x2D859 - Path to Challenge - 0x019A5:
+Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Path to Challenge - 0x019A5:
158451 - 0x335AB (Elevator Inside Control) - True - Dots & Black/White Squares
158452 - 0x335AC (Elevator Upper Outside Control) - 0x335AB - Black/White Squares
158453 - 0x3369D (Elevator Lower Outside Control) - 0x335AB - Black/White Squares & Dots
-158454 - 0x00190 (Blue Tunnel Right First 1) - True - Dots & Triangles
-158455 - 0x00558 (Blue Tunnel Right First 2) - 0x00190 - Dots & Triangles
-158456 - 0x00567 (Blue Tunnel Right First 3) - 0x00558 - Dots & Triangles
-158457 - 0x006FE (Blue Tunnel Right First 4) - 0x00567 - Dots & Triangles
+158454 - 0x00190 (Blue Tunnel Right First 1) - True - Dots & Triangles & Full Dots
+158455 - 0x00558 (Blue Tunnel Right First 2) - 0x00190 - Dots & Triangles & Full Dots
+158456 - 0x00567 (Blue Tunnel Right First 3) - 0x00558 - Dots & Triangles & Full Dots
+158457 - 0x006FE (Blue Tunnel Right First 4) - 0x00567 - Dots & Triangles & Full Dots
158458 - 0x01A0D (Blue Tunnel Left First 1) - True - Symmetry & Triangles
158459 - 0x008B8 (Blue Tunnel Left Second 1) - True - Black/White Squares & Triangles
158460 - 0x00973 (Blue Tunnel Left Second 2) - 0x008B8 - Stars & Triangles
@@ -849,12 +849,12 @@ Caves (Caves) - Main Island - 0x2D73F - Main Island - 0x2D859 - Path to Challeng
158479 - 0x288FC (Second Wooden Beam) - True - Black/White Squares & Shapers & Rotated Shapers
158480 - 0x289E7 (Third Wooden Beam) - True - Stars & Black/White Squares
158481 - 0x288AA (Fourth Wooden Beam) - True - Stars & Shapers
-158482 - 0x17FB9 (Left Upstairs Single) - True - Shapers & Dots & Negative Shapers
-158483 - 0x0A16B (Left Upstairs Left Row 1) - True - Dots
-158484 - 0x0A2CE (Left Upstairs Left Row 2) - 0x0A16B - Stars & Dots
-158485 - 0x0A2D7 (Left Upstairs Left Row 3) - 0x0A2CE - Dots & Black/White Squares & Stars + Same Colored Symbol & Stars
-158486 - 0x0A2DD (Left Upstairs Left Row 4) - 0x0A2D7 - Shapers & Dots
-158487 - 0x0A2EA (Left Upstairs Left Row 5) - 0x0A2DD - Rotated Shapers & Dots
+158482 - 0x17FB9 (Left Upstairs Single) - True - Shapers & Dots & Negative Shapers & Full Dots
+158483 - 0x0A16B (Left Upstairs Left Row 1) - True - Dots & Full Dots
+158484 - 0x0A2CE (Left Upstairs Left Row 2) - 0x0A16B - Stars & Dots & Full Dots
+158485 - 0x0A2D7 (Left Upstairs Left Row 3) - 0x0A2CE - Dots & Black/White Squares & Stars + Same Colored Symbol & Stars & Full Dots
+158486 - 0x0A2DD (Left Upstairs Left Row 4) - 0x0A2D7 - Shapers & Dots & Full Dots
+158487 - 0x0A2EA (Left Upstairs Left Row 5) - 0x0A2DD - Rotated Shapers & Dots & Full Dots
158488 - 0x0008F (Right Upstairs Left Row 1) - True - Dots & Invisible Dots
158489 - 0x0006B (Right Upstairs Left Row 2) - 0x0008F - Dots & Invisible Dots
158490 - 0x0008B (Right Upstairs Left Row 3) - 0x0006B - Dots & Invisible Dots
@@ -913,7 +913,7 @@ Door - 0x09E87 (Town Shortcut) - 0x09E85
Final Room (Mountain Final Room) - Elevator - 0x339BB & 0x33961:
158522 - 0x0383A (Right Pillar 1) - True - Stars
158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Stars & Dots
-158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots
+158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Full Dots
158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Dots & Symmetry
158526 - 0x0383D (Left Pillar 1) - True - Dots
158527 - 0x0383F (Left Pillar 2) - 0x0383D - Black/White Squares
diff --git a/worlds/witness/WitnessLogicExpert.txt b/worlds/witness/WitnessLogicExpert.txt
new file mode 100644
index 0000000000..3c44006054
--- /dev/null
+++ b/worlds/witness/WitnessLogicExpert.txt
@@ -0,0 +1,932 @@
+First Hallway (First Hallway) - Entry - True - Tutorial - 0x00182:
+158000 - 0x00064 (Straight) - True - True
+158001 - 0x00182 (Bend) - 0x00064 - True
+
+Tutorial (Tutorial) - Outside Tutorial - True:
+158002 - 0x00293 (Front Center) - True - Dots
+158003 - 0x00295 (Center Left) - 0x00293 - Dots
+158004 - 0x002C2 (Front Left) - 0x00295 - Dots
+158005 - 0x0A3B5 (Back Left) - True - Full Dots
+158006 - 0x0A3B2 (Back Right) - True - Full Dots
+158007 - 0x03629 (Gate Open) - 0x002C2 & 0x0A3B5 - Symmetry & Dots
+158008 - 0x03505 (Gate Close) - 0x2FAF6 - False
+158009 - 0x0C335 (Pillar) - True - Triangles - True
+158010 - 0x0C373 (Patio Floor) - 0x0C335 - Dots
+
+Outside Tutorial (Outside Tutorial) - Outside Tutorial Path To Outpost - 0x03BA2:
+158650 - 0x033D4 (Vault) - True - Full Dots & Squares & Black/White Squares
+158651 - 0x03481 (Vault Box) - 0x033D4 - True
+158013 - 0x0005D (Shed Row 1) - True - Full Dots
+158014 - 0x0005E (Shed Row 2) - 0x0005D - Full Dots
+158015 - 0x0005F (Shed Row 3) - 0x0005E - Full Dots
+158016 - 0x00060 (Shed Row 4) - 0x0005F - Full Dots
+158017 - 0x00061 (Shed Row 5) - 0x00060 - Full Dots
+158018 - 0x018AF (Tree Row 1) - True - Squares & Black/White Squares
+158019 - 0x0001B (Tree Row 2) - 0x018AF - Squares & Black/White Squares
+158020 - 0x012C9 (Tree Row 3) - 0x0001B - Squares & Black/White Squares
+158021 - 0x0001C (Tree Row 4) - 0x012C9 - Squares & Black/White Squares & Dots
+158022 - 0x0001D (Tree Row 5) - 0x0001C - Squares & Black/White Squares & Dots
+158023 - 0x0001E (Tree Row 6) - 0x0001D - Squares & Black/White Squares & Dots
+158024 - 0x0001F (Tree Row 7) - 0x0001E - Squares & Black/White Squares & Full Dots
+158025 - 0x00020 (Tree Row 8) - 0x0001F - Squares & Black/White Squares & Full Dots
+158026 - 0x00021 (Tree Row 9) - 0x00020 - Squares & Black/White Squares & Full Dots
+Door - 0x03BA2 (Outpost Path) - 0x0A3B5
+
+Outside Tutorial Path To Outpost (Outside Tutorial) - Outside Tutorial Outpost - 0x0A170:
+158011 - 0x0A171 (Outpost Entry Panel) - True - Full Dots & Triangles
+Door - 0x0A170 (Outpost Entry) - 0x0A171
+
+Outside Tutorial Outpost (Outside Tutorial) - Outside Tutorial - 0x04CA3:
+158012 - 0x04CA4 (Outpost Exit Panel) - True - Full Dots & Shapers & Rotated Shapers
+Door - 0x04CA3 (Outpost Exit) - 0x04CA4
+158600 - 0x17CFB (Discard) - True - Arrows
+
+Main Island () - Outside Tutorial - True:
+
+Outside Glass Factory (Glass Factory) - Main Island - True - Inside Glass Factory - 0x01A29:
+158027 - 0x01A54 (Entry Panel) - True - Symmetry
+Door - 0x01A29 (Entry) - 0x01A54
+158601 - 0x3C12B (Discard) - True - Arrows
+
+Inside Glass Factory (Glass Factory) - Inside Glass Factory Behind Back Wall - 0x0D7ED:
+158028 - 0x00086 (Back Wall 1) - True - Symmetry & Dots
+158029 - 0x00087 (Back Wall 2) - 0x00086 - Symmetry & Dots
+158030 - 0x00059 (Back Wall 3) - 0x00087 - Symmetry & Dots
+158031 - 0x00062 (Back Wall 4) - 0x00059 - Symmetry & Dots
+158032 - 0x0005C (Back Wall 5) - 0x00062 - Symmetry & Dots
+158033 - 0x0008D (Front 1) - 0x0005C - Symmetry
+158034 - 0x00081 (Front 2) - 0x0008D - Symmetry
+158035 - 0x00083 (Front 3) - 0x00081 - Symmetry
+158036 - 0x00084 (Melting 1) - 0x00083 - Symmetry & Dots
+158037 - 0x00082 (Melting 2) - 0x00084 - Symmetry & Dots
+158038 - 0x0343A (Melting 3) - 0x00082 - Symmetry & Dots
+Door - 0x0D7ED (Back Wall) - 0x0005C
+
+Inside Glass Factory Behind Back Wall (Glass Factory) - Boat - 0x17CC8:
+158039 - 0x17CC8 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat
+
+Outside Symmetry Island (Symmetry Island) - Main Island - True - Symmetry Island Lower - 0x17F3E:
+158040 - 0x000B0 (Lower Panel) - 0x0343A - Triangles
+Door - 0x17F3E (Lower) - 0x000B0
+
+Symmetry Island Lower (Symmetry Island) - Symmetry Island Upper - 0x18269:
+158041 - 0x00022 (Right 1) - True - Symmetry & Triangles
+158042 - 0x00023 (Right 2) - 0x00022 - Symmetry & Triangles
+158043 - 0x00024 (Right 3) - 0x00023 - Symmetry & Triangles
+158044 - 0x00025 (Right 4) - 0x00024 - Symmetry & Triangles
+158045 - 0x00026 (Right 5) - 0x00025 - Symmetry & Triangles
+158046 - 0x0007C (Back 1) - 0x00026 - Symmetry & Colored Dots & Dots
+158047 - 0x0007E (Back 2) - 0x0007C - Symmetry & Colored Squares
+158048 - 0x00075 (Back 3) - 0x0007E - Symmetry & Stars
+158049 - 0x00073 (Back 4) - 0x00075 - Symmetry & Shapers
+158050 - 0x00077 (Back 5) - 0x00073 - Symmetry & Triangles
+158051 - 0x00079 (Back 6) - 0x00077 - Symmetry & Dots & Colored Dots & Eraser
+158052 - 0x00065 (Left 1) - 0x00079 - Symmetry & Colored Dots & Triangles
+158053 - 0x0006D (Left 2) - 0x00065 - Symmetry & Colored Dots & Triangles
+158054 - 0x00072 (Left 3) - 0x0006D - Symmetry & Colored Dots & Triangles
+158055 - 0x0006F (Left 4) - 0x00072 - Symmetry & Colored Dots & Triangles
+158056 - 0x00070 (Left 5) - 0x0006F - Symmetry & Colored Dots & Triangles
+158057 - 0x00071 (Left 6) - 0x00070 - Symmetry & Triangles
+158058 - 0x00076 (Left 7) - 0x00071 - Symmetry & Triangles
+158059 - 0x009B8 (Scenery Outlines 1) - True - Symmetry & Environment
+158060 - 0x003E8 (Scenery Outlines 2) - 0x009B8 - Symmetry & Environment
+158061 - 0x00A15 (Scenery Outlines 3) - 0x003E8 - Symmetry & Environment
+158062 - 0x00B53 (Scenery Outlines 4) - 0x00A15 - Symmetry & Environment
+158063 - 0x00B8D (Scenery Outlines 5) - 0x00B53 - Symmetry & Environment
+158064 - 0x1C349 (Upper Panel) - 0x00076 - Symmetry & Triangles
+Door - 0x18269 (Upper) - 0x1C349
+
+Symmetry Island Upper (Symmetry Island):
+158065 - 0x00A52 (Yellow 1) - True - Symmetry & Colored Dots
+158066 - 0x00A57 (Yellow 2) - 0x00A52 - Symmetry & Colored Dots
+158067 - 0x00A5B (Yellow 3) - 0x00A57 - Symmetry & Colored Dots
+158068 - 0x00A61 (Blue 1) - 0x00A52 - Symmetry & Colored Dots
+158069 - 0x00A64 (Blue 2) - 0x00A61 & 0x00A52 - Symmetry & Colored Dots
+158070 - 0x00A68 (Blue 3) - 0x00A64 & 0x00A57 - Symmetry & Colored Dots
+158700 - 0x0360D (Laser Panel) - 0x00A68 - True
+Laser - 0x00509 (Laser) - 0x0360D - True
+
+Orchard (Orchard) - Main Island - True - Orchard Beyond First Gate - 0x03307:
+158071 - 0x00143 (Apple Tree 1) - True - Environment
+158072 - 0x0003B (Apple Tree 2) - 0x00143 - Environment
+158073 - 0x00055 (Apple Tree 3) - 0x0003B - Environment
+Door - 0x03307 (First Gate) - 0x00055
+
+Orchard Beyond First Gate (Orchard) - Orchard End - 0x03313:
+158074 - 0x032F7 (Apple Tree 4) - 0x00055 - Environment
+158075 - 0x032FF (Apple Tree 5) - 0x032F7 - Environment
+Door - 0x03313 (Second Gate) - 0x032FF
+
+Orchard End (Orchard):
+
+Desert Outside (Desert) - Main Island - True - Desert Floodlight Room - 0x09FEE:
+158652 - 0x0CC7B (Vault) - True - Full Dots & Stars & Stars + Same Colored Symbol & Eraser & Triangles & Shapers & Negative Shapers & Colored Squares
+158653 - 0x0339E (Vault Box) - 0x0CC7B - True
+158602 - 0x17CE7 (Discard) - True - Arrows
+158076 - 0x00698 (Surface 1) - True - Reflection
+158077 - 0x0048F (Surface 2) - 0x00698 - Reflection
+158078 - 0x09F92 (Surface 3) - 0x0048F & 0x09FA0 - Reflection
+158079 - 0x09FA0 (Surface 3 Control) - 0x0048F - True
+158080 - 0x0A036 (Surface 4) - 0x09F92 - Reflection
+158081 - 0x09DA6 (Surface 5) - 0x09F92 - Reflection
+158082 - 0x0A049 (Surface 6) - 0x09F92 - Reflection
+158083 - 0x0A053 (Surface 7) - 0x0A036 & 0x09DA6 & 0x0A049 - Reflection
+158084 - 0x09F94 (Surface 8) - 0x0A053 & 0x09F86 - Reflection
+158085 - 0x09F86 (Surface 8 Control) - 0x0A053 - True
+158086 - 0x0C339 (Light Room Entry Panel) - 0x09F94 - True
+Door - 0x09FEE (Light Room Entry) - 0x0C339 - True
+158701 - 0x03608 (Laser Panel) - 0x012D7 & 0x0A15F - True
+Laser - 0x012FB (Laser) - 0x03608
+
+Desert Floodlight Room (Desert) - Desert Pond Room - 0x0C2C3:
+158087 - 0x09FAA (Light Control) - True - True
+158088 - 0x00422 (Light Room 1) - 0x09FAA - Reflection
+158089 - 0x006E3 (Light Room 2) - 0x09FAA - Reflection
+158090 - 0x0A02D (Light Room 3) - 0x09FAA & 0x00422 & 0x006E3 - Reflection
+Door - 0x0C2C3 (Pond Room Entry) - 0x0A02D
+
+Desert Pond Room (Desert) - Desert Water Levels Room - 0x0A24B:
+158091 - 0x00C72 (Pond Room 1) - True - Reflection
+158092 - 0x0129D (Pond Room 2) - 0x00C72 - Reflection
+158093 - 0x008BB (Pond Room 3) - 0x0129D - Reflection
+158094 - 0x0078D (Pond Room 4) - 0x008BB - Reflection
+158095 - 0x18313 (Pond Room 5) - 0x0078D - Reflection
+158096 - 0x0A249 (Flood Room Entry Panel) - 0x18313 - Reflection
+Door - 0x0A24B (Flood Room Entry) - 0x0A249
+
+Desert Water Levels Room (Desert) - Desert Elevator Room - 0x0C316:
+158097 - 0x1C2DF (Reduce Water Level Far Left) - True - True
+158098 - 0x1831E (Reduce Water Level Far Right) - True - True
+158099 - 0x1C260 (Reduce Water Level Near Left) - True - True
+158100 - 0x1831C (Reduce Water Level Near Right) - True - True
+158101 - 0x1C2F3 (Raise Water Level Far Left) - True - True
+158102 - 0x1831D (Raise Water Level Far Right) - True - True
+158103 - 0x1C2B1 (Raise Water Level Near Left) - True - True
+158104 - 0x1831B (Raise Water Level Near Right) - True - True
+158105 - 0x04D18 (Flood Room 1) - 0x1C260 & 0x1831C - Reflection
+158106 - 0x01205 (Flood Room 2) - 0x04D18 & 0x1C260 & 0x1831C - Reflection
+158107 - 0x181AB (Flood Room 3) - 0x01205 & 0x1C260 & 0x1831C - Reflection
+158108 - 0x0117A (Flood Room 4) - 0x181AB & 0x1C260 & 0x1831C - Reflection
+158109 - 0x17ECA (Flood Room 5) - 0x0117A & 0x1C260 & 0x1831C - Reflection
+158110 - 0x18076 (Flood Room 6) - 0x17ECA & 0x1C260 & 0x1831C - Reflection
+Door - 0x0C316 (Elevator Room Entry) - 0x18076
+
+Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x012FB:
+158111 - 0x17C31 (Final Transparent) - True - Reflection
+158113 - 0x012D7 (Final Hexagonal) - 0x17C31 & 0x0A015 - Reflection
+158114 - 0x0A015 (Final Hexagonal Control) - 0x17C31 - True
+158115 - 0x0A15C (Final Bent 1) - True - Reflection
+158116 - 0x09FFF (Final Bent 2) - 0x0A15C - Reflection
+158117 - 0x0A15F (Final Bent 3) - 0x09FFF - Reflection
+
+Desert Lowest Level Inbetween Shortcuts (Desert):
+
+Outside Quarry (Quarry) - Main Island - True - Quarry Between Entrys - 0x09D6F:
+158118 - 0x09E57 (Entry 1 Panel) - True - Squares & Black/White Squares & Triangles
+158120 - 0x17CC4 (Elevator Control) - 0x0367C - Dots & Eraser
+158603 - 0x17CF0 (Discard) - True - Arrows
+158702 - 0x03612 (Laser Panel) - 0x0A3D0 & 0x0367C - Eraser & Triangles & Stars & Stars + Same Colored Symbol
+Laser - 0x01539 (Laser) - 0x03612
+Door - 0x09D6F (Entry 1) - 0x09E57
+
+Quarry Between Entrys (Quarry) - Quarry - 0x17C07:
+158119 - 0x17C09 (Entry 2 Panel) - True - Shapers & Triangles
+Door - 0x17C07 (Entry 2) - 0x17C09
+
+Quarry (Quarry) - Quarry Mill Ground Floor - 0x02010:
+158121 - 0x01E5A (Mill Entry Left Panel) - True - Squares & Black/White Squares & Stars & Stars + Same Colored Symbol
+158122 - 0x01E59 (Mill Entry Right Panel) - True - Triangles
+Door - 0x02010 (Mill Entry) - 0x01E59 & 0x01E5A
+
+Quarry Mill Ground Floor (Quarry Mill) - Quarry - 0x275FF - Quarry Mill Middle Floor - 0x03678 - Outside Quarry - 0x17CE8:
+158123 - 0x275ED (Side Exit Panel) - True - True
+Door - 0x275FF (Side Exit) - 0x275ED
+158124 - 0x03678 (Lower Ramp Control) - True - Dots & Eraser
+158145 - 0x17CAC (Roof Exit Panel) - True - True
+Door - 0x17CE8 (Roof Exit) - 0x17CAC
+
+Quarry Mill Middle Floor (Quarry Mill) - Quarry Mill Ground Floor - 0x03675 - Quarry Mill Upper Floor - 0x03679:
+158125 - 0x00E0C (Lower Row 1) - True - Dots & Eraser
+158126 - 0x01489 (Lower Row 2) - 0x00E0C - Triangles & Eraser
+158127 - 0x0148A (Lower Row 3) - 0x01489 - Triangles & Eraser
+158128 - 0x014D9 (Lower Row 4) - 0x0148A - Triangles & Eraser
+158129 - 0x014E7 (Lower Row 5) - 0x014D9 - Triangles & Eraser
+158130 - 0x014E8 (Lower Row 6) - 0x014E7 - Triangles & Eraser
+158131 - 0x03679 (Lower Lift Control) - 0x014E8 - Dots & Eraser
+
+Quarry Mill Upper Floor (Quarry Mill) - Quarry Mill Middle Floor - 0x03676 & 0x03679 - Quarry Mill Ground Floor - 0x0368A:
+158132 - 0x03676 (Upper Ramp Control) - True - Dots & Eraser
+158133 - 0x03675 (Upper Lift Control) - True - Dots & Eraser
+158134 - 0x00557 (Upper Row 1) - True - Squares & Colored Squares & Eraser & Stars & Stars + Same Colored Symbol
+158135 - 0x005F1 (Upper Row 2) - 0x00557 - Squares & Colored Squares & Eraser & Stars & Stars + Same Colored Symbol
+158136 - 0x00620 (Upper Row 3) - 0x005F1 - Squares & Colored Squares & Eraser & Stars & Stars + Same Colored Symbol
+158137 - 0x009F5 (Upper Row 4) - 0x00620 - Squares & Colored Squares & Eraser & Stars & Stars + Same Colored Symbol
+158138 - 0x0146C (Upper Row 5) - 0x009F5 - Squares & Colored Squares & Eraser & Stars & Stars + Same Colored Symbol
+158139 - 0x3C12D (Upper Row 6) - 0x0146C - Squares & Colored Squares & Eraser & Stars & Stars + Same Colored Symbol
+158140 - 0x03686 (Upper Row 7) - 0x3C12D - Squares & Colored Squares & Eraser & Stars & Stars + Same Colored Symbol
+158141 - 0x014E9 (Upper Row 8) - 0x03686 - Squares & Colored Squares & Eraser & Stars & Stars + Same Colored Symbol
+158142 - 0x03677 (Stair Control) - True - Squares & Colored Squares & Eraser
+Door - 0x0368A (Stairs) - 0x03677
+158143 - 0x3C125 (Control Room Left) - 0x0367C - Squares & Black/White Squares & Full Dots & Eraser
+158144 - 0x0367C (Control Room Right) - 0x014E9 - Squares & Colored Squares & Triangles & Eraser & Stars & Stars + Same Colored Symbol
+
+Quarry Boathouse (Quarry Boathouse) - Quarry - True - Quarry Boathouse Upper Front - 0x03852 - Quarry Boathouse Behind Staircase - 0x2769B:
+158146 - 0x034D4 (Intro Left) - True - Stars & Eraser
+158147 - 0x021D5 (Intro Right) - True - Shapers & Eraser
+158148 - 0x03852 (Ramp Height Control) - 0x034D4 & 0x021D5 - Rotated Shapers
+158166 - 0x17CA6 (Boat Spawn) - True - Boat
+Door - 0x2769B (Dock) - 0x17CA6
+Door - 0x27163 (Dock Invis Barrier) - 0x17CA6
+
+Quarry Boathouse Behind Staircase (Quarry Boathouse) - Boat - 0x17CA6:
+
+Quarry Boathouse Upper Front (Quarry Boathouse) - Quarry Boathouse Upper Middle - 0x17C50:
+158149 - 0x021B3 (Front Row 1) - True - Shapers & Eraser & Negative Shapers
+158150 - 0x021B4 (Front Row 2) - 0x021B3 - Shapers & Eraser & Negative Shapers
+158151 - 0x021B0 (Front Row 3) - 0x021B4 - Shapers & Eraser & Negative Shapers
+158152 - 0x021AF (Front Row 4) - 0x021B0 - Shapers & Eraser & Negative Shapers
+158153 - 0x021AE (Front Row 5) - 0x021AF - Shapers & Eraser & Negative Shapers
+Door - 0x17C50 (First Barrier) - 0x021AE
+
+Quarry Boathouse Upper Middle (Quarry Boathouse) - Quarry Boathouse Upper Back - 0x03858:
+158154 - 0x03858 (Ramp Horizontal Control) - True - Shapers & Eraser
+
+Quarry Boathouse Upper Back (Quarry Boathouse) - Quarry Boathouse Upper Middle - 0x3865F:
+158155 - 0x38663 (Second Barrier Panel) - True - True
+Door - 0x3865F (Second Barrier) - 0x38663
+158156 - 0x021B5 (Back First Row 1) - True - Stars & Stars + Same Colored Symbol & Eraser
+158157 - 0x021B6 (Back First Row 2) - 0x021B5 - Stars & Stars + Same Colored Symbol & Eraser
+158158 - 0x021B7 (Back First Row 3) - 0x021B6 - Stars & Stars + Same Colored Symbol & Eraser
+158159 - 0x021BB (Back First Row 4) - 0x021B7 - Stars & Stars + Same Colored Symbol & Eraser
+158160 - 0x09DB5 (Back First Row 5) - 0x021BB - Stars & Stars + Same Colored Symbol & Eraser
+158161 - 0x09DB1 (Back First Row 6) - 0x09DB5 - Eraser & Shapers
+158162 - 0x3C124 (Back First Row 7) - 0x09DB1 - Eraser & Shapers
+158163 - 0x09DB3 (Back First Row 8) - 0x3C124 - Eraser & Shapers & Stars & Stars + Same Colored Symbol
+158164 - 0x09DB4 (Back First Row 9) - 0x09DB3 - Eraser & Shapers & Stars & Stars + Same Colored Symbol
+158165 - 0x275FA (Hook Control) - True - Shapers & Eraser
+158167 - 0x0A3CB (Back Second Row 1) - 0x09DB4 - Stars & Eraser & Shapers & Negative Shapers & Stars + Same Colored Symbol
+158168 - 0x0A3CC (Back Second Row 2) - 0x0A3CB - Stars & Eraser & Shapers & Negative Shapers & Stars + Same Colored Symbol
+158169 - 0x0A3D0 (Back Second Row 3) - 0x0A3CC - Stars & Eraser & Shapers & Negative Shapers & Stars + Same Colored Symbol
+
+Shadows (Shadows) - Main Island - True - Shadows Ledge - 0x19B24 - Shadows Laser Room - 0x194B2 & 0x19665:
+158170 - 0x334DB (Door Timer Outside) - True - True
+Door - 0x19B24 (Timed Door) - 0x334DB
+158171 - 0x0AC74 (Intro 6) - 0x0A8DC - Shadows Avoid
+158172 - 0x0AC7A (Intro 7) - 0x0AC74 - Shadows Avoid
+158173 - 0x0A8E0 (Intro 8) - 0x0AC7A - Shadows Avoid
+158174 - 0x386FA (Far 1) - 0x0A8E0 - Shadows Avoid & Environment
+158175 - 0x1C33F (Far 2) - 0x386FA - Shadows Avoid & Environment
+158176 - 0x196E2 (Far 3) - 0x1C33F - Shadows Avoid & Environment
+158177 - 0x1972A (Far 4) - 0x196E2 - Shadows Avoid & Environment
+158178 - 0x19809 (Far 5) - 0x1972A - Shadows Avoid & Environment
+158179 - 0x19806 (Far 6) - 0x19809 - Shadows Avoid & Environment
+158180 - 0x196F8 (Far 7) - 0x19806 - Shadows Avoid & Environment
+158181 - 0x1972F (Far 8) - 0x196F8 - Shadows Avoid & Environment
+Door - 0x194B2 (Laser Entry Right) - 0x1972F
+158182 - 0x19797 (Near 1) - 0x0A8E0 - Shadows Follow
+158183 - 0x1979A (Near 2) - 0x19797 - Shadows Follow
+158184 - 0x197E0 (Near 3) - 0x1979A - Shadows Follow
+158185 - 0x197E8 (Near 4) - 0x197E0 - Shadows Follow
+158186 - 0x197E5 (Near 5) - 0x197E8 - Shadows Follow
+Door - 0x19665 (Laser Entry Left) - 0x197E5
+
+Shadows Ledge (Shadows) - Shadows - 0x1855B - Quarry - 0x19865 & 0x0A2DF:
+158187 - 0x334DC (Door Timer Inside) - True - True
+158188 - 0x198B5 (Intro 1) - True - Shadows Avoid
+158189 - 0x198BD (Intro 2) - 0x198B5 - Shadows Avoid
+158190 - 0x198BF (Intro 3) - 0x198BD & 0x334DC & 0x19B24 - Shadows Avoid
+Door - 0x19865 (Quarry Barrier) - 0x198BF
+Door - 0x0A2DF (Quarry Barrier 2) - 0x198BF
+158191 - 0x19771 (Intro 4) - 0x198BF - Shadows Avoid
+158192 - 0x0A8DC (Intro 5) - 0x19771 - Shadows Avoid
+Door - 0x1855B (Ledge Barrier) - 0x0A8DC
+Door - 0x19ADE (Ledge Barrier 2) - 0x0A8DC
+
+Shadows Laser Room (Shadows):
+158703 - 0x19650 (Laser Panel) - True - Shadows Avoid & Shadows Follow
+Laser - 0x181B3 (Laser) - 0x19650
+
+Keep (Keep) - Main Island - True - Keep 2nd Maze - 0x01954 - Keep 2nd Pressure Plate - 0x01BEC:
+158193 - 0x00139 (Hedge Maze 1) - True - Environment
+158197 - 0x0A3A8 (Reset Pressure Plates 1) - True - True
+158198 - 0x033EA (Pressure Plates 1) - 0x0A3A8 - Pressure Plates & Colored Squares & Triangles & Stars & Stars + Same Colored Symbol
+Door - 0x01954 (Hedge Maze 1 Exit) - 0x00139
+Door - 0x01BEC (Pressure Plates 1 Exit) - 0x033EA
+
+Keep 2nd Maze (Keep) - Keep - 0x018CE - Keep 3rd Maze - True:
+Door - 0x018CE (Hedge Maze 2 Shortcut) - 0x00139
+158194 - 0x019DC (Hedge Maze 2) - True - Environment
+Door - 0x019D8 (Hedge Maze 2 Exit) - 0x019DC
+
+Keep 3rd Maze (Keep) - Keep - 0x019B5 - Keep 4th Maze - 0x019E6:
+Door - 0x019B5 (Hedge Maze 3 Shortcut) - 0x019DC
+158195 - 0x019E7 (Hedge Maze 3) - True - Environment & Sound
+Door - 0x019E6 (Hedge Maze 3 Exit) - 0x019E7
+
+Keep 4th Maze (Keep) - Keep - 0x0199A - Keep Tower - 0x01A0E:
+Door - 0x0199A (Hedge Maze 4 Shortcut) - 0x019E7
+158196 - 0x01A0F (Hedge Maze 4) - True - Environment
+Door - 0x01A0E (Hedge Maze 4 Exit) - 0x01A0F
+
+Keep 2nd Pressure Plate (Keep) - Keep 3rd Pressure Plate - True:
+158199 - 0x0A3B9 (Reset Pressure Plates 2) - True - True
+158200 - 0x01BE9 (Pressure Plates 2) - PP2 Weirdness - Pressure Plates & Stars & Stars + Same Colored Symbol & Squares & Black/White Squares & Shapers & Rotated Shapers
+Door - 0x01BEA (Pressure Plates 2 Exit) - 0x01BE9
+
+Keep 3rd Pressure Plate (Keep) - Keep 4th Pressure Plate - 0x01CD5:
+158201 - 0x0A3BB (Reset Pressure Plates 3) - True - True
+158202 - 0x01CD3 (Pressure Plates 3) - 0x0A3BB - Pressure Plates & Black/White Squares & Triangles & Shapers & Rotated Shapers
+Door - 0x01CD5 (Pressure Plates 3 Exit) - 0x01CD3
+
+Keep 4th Pressure Plate (Keep) - Shadows - 0x09E3D - Keep Tower - 0x01D40:
+158203 - 0x0A3AD (Reset Pressure Plates 4) - True - True
+158204 - 0x01D3F (Pressure Plates 4) - 0x0A3AD - Pressure Plates & Shapers & Triangles & Stars & Stars + Same Colored Symbol
+Door - 0x01D40 (Pressure Plates 4 Exit) - 0x01D3F
+158604 - 0x17D27 (Discard) - True - Arrows
+158205 - 0x09E49 (Shadows Shortcut Panel) - True - True
+Door - 0x09E3D (Shadows Shortcut) - 0x09E49
+
+Shipwreck (Shipwreck) - Keep 3rd Pressure Plate - True:
+158654 - 0x00AFB (Vault) - True - Symmetry & Sound & Sound Dots & Colored Dots
+158655 - 0x03535 (Vault Box) - 0x00AFB - True
+158605 - 0x17D28 (Discard) - True - Arrows
+
+Keep Tower (Keep) - Keep - 0x04F8F:
+158206 - 0x0361B (Tower Shortcut Panel) - True - True
+Door - 0x04F8F (Tower Shortcut) - 0x0361B
+158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - Environment & Sound
+158705 - 0x03317 (Laser Panel Pressure Plates) - 0x01BE9 - Shapers & Rotated Shapers & Triangles & Stars & Stars + Same Colored Symbol & Colored Squares & Black/White Squares
+Laser - 0x014BB (Laser) - 0x0360E | 0x03317
+
+Outside Monastery (Monastery) - Main Island - True - Inside Monastery - 0x0C128 & 0x0C153 - Monastery Garden - 0x03750:
+158207 - 0x03713 (Shortcut Panel) - True - True
+Door - 0x0364E (Shortcut) - 0x03713
+158208 - 0x00B10 (Entry Left) - True - True
+158209 - 0x00C92 (Entry Right) - True - True
+Door - 0x0C128 (Entry Inner) - 0x00B10
+Door - 0x0C153 (Entry Outer) - 0x00C92
+158210 - 0x00290 (Outside 1) - 0x09D9B - Environment
+158211 - 0x00038 (Outside 2) - 0x09D9B & 0x00290 - Environment
+158212 - 0x00037 (Outside 3) - 0x09D9B & 0x00038 - Environment
+Door - 0x03750 (Garden Entry) - 0x00037
+158706 - 0x17CA4 (Laser Panel) - 0x193A6 - True
+Laser - 0x17C65 (Laser) - 0x17CA4
+
+Inside Monastery (Monastery):
+158213 - 0x09D9B (Shutters Control) - True - Dots
+158214 - 0x193A7 (Inside 1) - 0x00037 - Environment
+158215 - 0x193AA (Inside 2) - 0x193A7 - Environment
+158216 - 0x193AB (Inside 3) - 0x193AA - Environment
+158217 - 0x193A6 (Inside 4) - 0x193AB - Environment
+
+Monastery Garden (Monastery):
+
+Town (Town) - Main Island - True - Boat - 0x0A054 - Town Maze Rooftop - 0x28AA2 - Town Church - True - Town Wooden Rooftop - 0x034F5 - RGB House - 0x28A61 - Windmill Interior - 0x1845B - Town Inside Cargo Box - 0x0A0C9:
+158218 - 0x0A054 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat
+158219 - 0x0A0C8 (Cargo Box Entry Panel) - True - Squares & Black/White Squares & Shapers & Triangles
+Door - 0x0A0C9 (Cargo Box Entry) - 0x0A0C8
+158707 - 0x09F98 (Desert Laser Redirect) - True - True
+158220 - 0x18590 (Transparent) - True - Symmetry & Environment
+158221 - 0x28AE3 (Vines) - 0x18590 - Shadows Follow & Environment
+158222 - 0x28938 (Apple Tree) - 0x28AE3 - Environment
+158223 - 0x079DF (Triple Exit) - 0x28938 - Shadows Avoid & Environment & Reflection
+158235 - 0x2899C (Wooden Roof Lower Row 1) - True - Triangles & Full Dots
+158236 - 0x28A33 (Wooden Roof Lower Row 2) - 0x2899C - Triangles & Full Dots
+158237 - 0x28ABF (Wooden Roof Lower Row 3) - 0x28A33 - Triangles & Full Dots
+158238 - 0x28AC0 (Wooden Roof Lower Row 4) - 0x28ABF - Triangles & Full Dots
+158239 - 0x28AC1 (Wooden Roof Lower Row 5) - 0x28AC0 - Triangles & Full Dots
+Door - 0x034F5 (Wooden Roof Stairs) - 0x28AC1
+158225 - 0x28998 (Tinted Glass Door Panel) - True - Stars & Rotated Shapers & Stars + Same Colored Symbol
+Door - 0x28A61 (Tinted Glass Door) - 0x28A0D
+158226 - 0x28A0D (Church Entry Panel) - 0x28998 - Stars & RGB & Environment
+Door - 0x03BB0 (Church Entry) - 0x03C08
+158228 - 0x28A79 (Maze Stair Control) - True - Environment
+Door - 0x28AA2 (Maze Stairs) - 0x28A79
+158241 - 0x17F5F (Windmill Entry Panel) - True - Dots
+Door - 0x1845B (Windmill Entry) - 0x17F5F
+
+Town Inside Cargo Box (Town):
+158606 - 0x17D01 (Cargo Box Discard) - True - Arrows
+
+Town Maze Rooftop (Town) - Town Red Rooftop - 0x2896A:
+158229 - 0x2896A (Maze Rooftop Bridge Control) - True - Shapers
+
+Town Red Rooftop (Town):
+158607 - 0x17C71 (Rooftop Discard) - True - Arrows
+158230 - 0x28AC7 (Red Rooftop 1) - True - Symmetry & Shapers
+158231 - 0x28AC8 (Red Rooftop 2) - 0x28AC7 - Symmetry & Shapers
+158232 - 0x28ACA (Red Rooftop 3) - 0x28AC8 - Symmetry & Shapers
+158233 - 0x28ACB (Red Rooftop 4) - 0x28ACA - Symmetry & Shapers
+158234 - 0x28ACC (Red Rooftop 5) - 0x28ACB - Symmetry & Shapers
+158224 - 0x28B39 (Tall Hexagonal) - 0x079DF - Reflection
+
+Town Wooden Rooftop (Town):
+158240 - 0x28AD9 (Wooden Rooftop) - 0x28AC1 - Triangles & Full Dots & Eraser
+
+Town Church (Town):
+158227 - 0x28A69 (Church Lattice) - 0x03BB0 - Environment
+
+RGB House (Town) - RGB Room - 0x2897B:
+158242 - 0x034E4 (Sound Room Left) - True - Sound
+158243 - 0x034E3 (Sound Room Right) - True - Sound & Sound Dots
+Door - 0x2897B (RGB House Stairs) - 0x034E4 & 0x034E3
+
+RGB Room (Town):
+158244 - 0x334D8 (RGB Control) - True - Rotated Shapers & RGB & Squares & Colored Squares & Triangles
+158245 - 0x03C0C (RGB Room Left) - 0x334D8 - RGB & Squares & Colored Squares & Black/White Squares & Eraser
+158246 - 0x03C08 (RGB Room Right) - 0x334D8 & 0x03C0C - RGB & Symmetry & Dots & Colored Dots & Triangles
+
+Town Tower (Town Tower) - Town - True - Town Tower Top - 0x27798 & 0x27799 & 0x2779A & 0x2779C:
+Door - 0x27798 (First Door) - 0x28ACC
+Door - 0x2779C (Second Door) - 0x28AD9
+Door - 0x27799 (Third Door) - 0x28A69
+Door - 0x2779A (Fourth Door) - 0x28B39
+
+Town Tower Top (Town):
+158708 - 0x032F5 (Laser Panel) - True - True
+Laser - 0x032F9 (Laser) - 0x032F5
+
+Windmill Interior (Windmill) - Theater - 0x17F88:
+158247 - 0x17D02 (Turn Control) - True - Dots
+158248 - 0x17F89 (Theater Entry Panel) - True - Squares & Black/White Squares & Eraser & Triangles
+Door - 0x17F88 (Theater Entry) - 0x17F89
+
+Theater (Theater) - Town - 0x0A16D | 0x3CCDF:
+158656 - 0x00815 (Video Input) - True - True
+158657 - 0x03553 (Tutorial Video) - 0x00815 & 0x03481 - True
+158658 - 0x03552 (Desert Video) - 0x00815 & 0x0339E - True
+158659 - 0x0354E (Jungle Video) - 0x00815 & 0x03702 - True
+158660 - 0x03549 (Challenge Video) - 0x00815 & 0x0356B - True
+158661 - 0x0354F (Shipwreck Video) - 0x00815 & 0x03535 - True
+158662 - 0x03545 (Mountain Video) - 0x00815 & 0x03542 - True
+158249 - 0x0A168 (Exit Left Panel) - True - Black/White Squares & Stars & Stars + Same Colored Symbol & Eraser
+158250 - 0x33AB2 (Exit Right Panel) - True - Eraser & Triangles & Shapers
+Door - 0x0A16D (Exit Left) - 0x0A168
+Door - 0x3CCDF (Exit Right) - 0x33AB2
+158608 - 0x17CF7 (Discard) - True - Arrows
+
+Jungle (Jungle) - Main Island - True - Outside Jungle River - 0x3873B - Boat - 0x17CDF:
+158251 - 0x17CDF (Shore Boat Spawn) - True - Boat
+158609 - 0x17F9B (Discard) - True - Triangles
+158252 - 0x002C4 (First Row 1) - True - Sound
+158253 - 0x00767 (First Row 2) - 0x002C4 - Sound
+158254 - 0x002C6 (First Row 3) - 0x00767 - Sound
+158255 - 0x0070E (Second Row 1) - 0x002C6 - Sound
+158256 - 0x0070F (Second Row 2) - 0x0070E - Sound
+158257 - 0x0087D (Second Row 3) - 0x0070F - Sound
+158258 - 0x002C7 (Second Row 4) - 0x0087D - Sound
+158259 - 0x17CAB (Popup Wall Control) - 0x002C7 - True
+Door - 0x1475B (Popup Wall) - 0x17CAB
+158260 - 0x0026D (Popup Wall 1) - 0x1475B - Sound & Sound Dots & Symmetry
+158261 - 0x0026E (Popup Wall 2) - 0x0026D - Sound & Sound Dots & Symmetry
+158262 - 0x0026F (Popup Wall 3) - 0x0026E - Sound & Sound Dots & Symmetry
+158263 - 0x00C3F (Popup Wall 4) - 0x0026F - Sound & Sound Dots & Symmetry
+158264 - 0x00C41 (Popup Wall 5) - 0x00C3F - Sound & Sound Dots & Symmetry
+158265 - 0x014B2 (Popup Wall 6) - 0x00C41 - Sound & Sound Dots & Symmetry
+158709 - 0x03616 (Laser Panel) - 0x014B2 - True
+Laser - 0x00274 (Laser) - 0x03616
+158266 - 0x337FA (Laser Shortcut Panel) - True - True
+Door - 0x3873B (Laser Shortcut) - 0x337FA
+
+Outside Jungle River (River) - Main Island - True - Monastery Garden - 0x0CF2A:
+158267 - 0x17CAA (Monastery Shortcut Panel) - True - Environment
+Door - 0x0CF2A (Monastery Shortcut) - 0x17CAA
+158663 - 0x15ADD (Vault) - True - Environment & Black/White Squares & Dots
+158664 - 0x03702 (Vault Box) - 0x15ADD - True
+
+Outside Bunker (Bunker) - Main Island - True - Bunker - 0x0C2A4:
+158268 - 0x17C2E (Entry Panel) - True - Squares & Black/White Squares & Colored Squares
+Door - 0x0C2A4 (Entry) - 0x17C2E
+
+Bunker (Bunker) - Bunker Glass Room - 0x17C79:
+158269 - 0x09F7D (Intro Left 1) - True - Squares & Colored Squares
+158270 - 0x09FDC (Intro Left 2) - 0x09F7D - Squares & Colored Squares & Black/White Squares
+158271 - 0x09FF7 (Intro Left 3) - 0x09FDC - Squares & Colored Squares & Black/White Squares
+158272 - 0x09F82 (Intro Left 4) - 0x09FF7 - Squares & Colored Squares & Black/White Squares
+158273 - 0x09FF8 (Intro Left 5) - 0x09F82 - Squares & Colored Squares & Black/White Squares
+158274 - 0x09D9F (Intro Back 1) - 0x09FF8 - Squares & Colored Squares & Black/White Squares
+158275 - 0x09DA1 (Intro Back 2) - 0x09D9F - Squares & Colored Squares
+158276 - 0x09DA2 (Intro Back 3) - 0x09DA1 - Squares & Colored Squares
+158277 - 0x09DAF (Intro Back 4) - 0x09DA2 - Squares & Colored Squares
+158278 - 0x0A099 (Tinted Glass Door Panel) - 0x09DAF - True
+Door - 0x17C79 (Tinted Glass Door) - 0x0A099
+
+Bunker Glass Room (Bunker) - Bunker Ultraviolet Room - 0x0C2A3:
+158279 - 0x0A010 (Glass Room 1) - True - Squares & Colored Squares & RGB & Environment
+158280 - 0x0A01B (Glass Room 2) - 0x0A010 - Squares & Colored Squares & Black/White Squares & RGB & Environment
+158281 - 0x0A01F (Glass Room 3) - 0x0A01B - Squares & Colored Squares & Black/White Squares & RGB & Environment
+Door - 0x0C2A3 (UV Room Entry) - 0x0A01F
+
+Bunker Ultraviolet Room (Bunker) - Bunker Elevator Section - 0x0A08D:
+158282 - 0x34BC5 (Drop-Down Door Open) - True - True
+158283 - 0x34BC6 (Drop-Down Door Close) - 0x34BC5 - True
+158284 - 0x17E63 (UV Room 1) - 0x34BC5 - Squares & Colored Squares & RGB & Environment
+158285 - 0x17E67 (UV Room 2) - 0x17E63 & 0x34BC6 - Squares & Colored Squares & Black/White Squares & RGB
+Door - 0x0A08D (Elevator Room Entry) - 0x17E67
+
+Bunker Elevator Section (Bunker) - Bunker Laser Platform - 0x0A079:
+158286 - 0x0A079 (Elevator Control) - True - Squares & Colored Squares & Black/White Squares & RGB
+
+Bunker Laser Platform (Bunker):
+158710 - 0x09DE0 (Laser Panel) - True - True
+Laser - 0x0C2B2 (Laser) - 0x09DE0
+
+Outside Swamp (Swamp) - Swamp Entry Area - 0x00C1C - Main Island - True:
+158287 - 0x0056E (Entry Panel) - True - Rotated Shapers & Black/White Squares & Triangles
+Door - 0x00C1C (Entry) - 0x0056E
+
+Swamp Entry Area (Swamp) - Swamp Sliding Bridge - TrueOneWay:
+158288 - 0x00469 (Intro Front 1) - True - Black/White Squares & Shapers
+158289 - 0x00472 (Intro Front 2) - 0x00469 - Black/White Squares & Shapers & Rotated Shapers
+158290 - 0x00262 (Intro Front 3) - 0x00472 - Black/White Squares & Rotated Shapers
+158291 - 0x00474 (Intro Front 4) - 0x00262 - Black/White Squares & Shapers & Rotated Shapers
+158292 - 0x00553 (Intro Front 5) - 0x00474 - Black/White Squares & Shapers & Rotated Shapers
+158293 - 0x0056F (Intro Front 6) - 0x00553 - Black/White Squares & Shapers & Rotated Shapers
+158294 - 0x00390 (Intro Back 1) - 0x0056F - Shapers & Triangles
+158295 - 0x010CA (Intro Back 2) - 0x00390 - Shapers & Rotated Shapers & Triangles
+158296 - 0x00983 (Intro Back 3) - 0x010CA - Rotated Shapers & Triangles
+158297 - 0x00984 (Intro Back 4) - 0x00983 - Shapers & Rotated Shapers & Triangles
+158298 - 0x00986 (Intro Back 5) - 0x00984 - Shapers & Rotated Shapers & Triangles
+158299 - 0x00985 (Intro Back 6) - 0x00986 - Rotated Shapers & Triangles & Black/White Squares
+158300 - 0x00987 (Intro Back 7) - 0x00985 - Shapers & Rotated Shapers & Triangles & Black/White Squares
+158301 - 0x181A9 (Intro Back 8) - 0x00987 - Rotated Shapers & Triangles & Black/White Squares
+
+Swamp Sliding Bridge (Swamp) - Swamp Entry Area - 0x00609 - Swamp Near Platform - 0x00609:
+158302 - 0x00609 (Sliding Bridge) - True - Shapers & Black/White Squares
+
+Swamp Near Platform (Swamp) - Swamp Cyan Underwater - 0x04B7F - Swamp Near Boat - 0x38AE6 - Swamp Between Bridges Near - 0x184B7 - Swamp Sliding Bridge - TrueOneWay:
+158313 - 0x00982 (Platform Row 1) - True - Rotated Shapers
+158314 - 0x0097F (Platform Row 2) - 0x00982 - Rotated Shapers
+158315 - 0x0098F (Platform Row 3) - 0x0097F - Rotated Shapers
+158316 - 0x00990 (Platform Row 4) - 0x0098F - Rotated Shapers
+Door - 0x184B7 (Between Bridges First Door) - 0x00990
+158317 - 0x17C0D (Platform Shortcut Left Panel) - True - Rotated Shapers
+158318 - 0x17C0E (Platform Shortcut Right Panel) - True - Rotated Shapers
+Door - 0x38AE6 (Platform Shortcut Door) - 0x17C0E
+Door - 0x04B7F (Cyan Water Pump) - 0x00006
+
+Swamp Cyan Underwater (Swamp):
+158307 - 0x00002 (Cyan Underwater 1) - True - Shapers & Negative Shapers & Black/White Squares
+158308 - 0x00004 (Cyan Underwater 2) - 0x00002 - Shapers & Negative Shapers & Triangles
+158309 - 0x00005 (Cyan Underwater 3) - 0x00004 - Shapers & Negative Shapers & Triangles & Black/White Squares
+158310 - 0x013E6 (Cyan Underwater 4) - 0x00005 - Shapers & Negative Shapers & Triangles & Black/White Squares
+158311 - 0x00596 (Cyan Underwater 5) - 0x013E6 - Shapers & Negative Shapers & Triangles & Black/White Squares
+158312 - 0x18488 (Cyan Underwater Sliding Bridge Control) - True - Shapers
+
+Swamp Between Bridges Near (Swamp) - Swamp Between Bridges Far - 0x18507:
+158303 - 0x00999 (Between Bridges Near Row 1) - 0x00990 - Rotated Shapers
+158304 - 0x0099D (Between Bridges Near Row 2) - 0x00999 - Rotated Shapers
+158305 - 0x009A0 (Between Bridges Near Row 3) - 0x0099D - Rotated Shapers
+158306 - 0x009A1 (Between Bridges Near Row 4) - 0x009A0 - Rotated Shapers
+Door - 0x18507 (Between Bridges Second Door) - 0x009A1
+
+Swamp Between Bridges Far (Swamp) - Swamp Red Underwater - 0x183F2 - Swamp Rotating Bridge - TrueOneWay:
+158319 - 0x00007 (Between Bridges Far Row 1) - 0x009A1 - Rotated Shapers & Full Dots
+158320 - 0x00008 (Between Bridges Far Row 2) - 0x00007 - Rotated Shapers & Full Dots
+158321 - 0x00009 (Between Bridges Far Row 3) - 0x00008 - Rotated Shapers & Shapers & Full Dots
+158322 - 0x0000A (Between Bridges Far Row 4) - 0x00009 - Rotated Shapers & Shapers & Full Dots
+Door - 0x183F2 (Red Water Pump) - 0x00596
+
+Swamp Red Underwater (Swamp) - Swamp Maze - 0x014D1:
+158323 - 0x00001 (Red Underwater 1) - True - Shapers & Negative Shapers & Full Dots
+158324 - 0x014D2 (Red Underwater 2) - True - Shapers & Negative Shapers & Full Dots
+158325 - 0x014D4 (Red Underwater 3) - True - Shapers & Negative Shapers & Full Dots
+158326 - 0x014D1 (Red Underwater 4) - True - Shapers & Negative Shapers & Full Dots
+Door - 0x305D5 (Red Underwater Exit) - 0x014D1
+
+Swamp Rotating Bridge (Swamp) - Swamp Between Bridges Far - 0x181F5 - Swamp Near Boat - 0x181F5 - Swamp Purple Area - 0x181F5:
+158327 - 0x181F5 (Rotating Bridge) - True - Rotated Shapers & Shapers & Stars & Colored Squares & Triangles & Stars + Same Colored Symbol
+
+Swamp Near Boat (Swamp) - Swamp Rotating Bridge - TrueOneWay - Swamp Blue Underwater - 0x18482:
+158328 - 0x09DB8 (Boat Spawn) - True - Boat
+158329 - 0x003B2 (Beyond Rotating Bridge 1) - 0x0000A - Shapers & Full Dots
+158330 - 0x00A1E (Beyond Rotating Bridge 2) - 0x003B2 - Rotated Shapers & Shapers & Full Dots
+158331 - 0x00C2E (Beyond Rotating Bridge 3) - 0x00A1E - Shapers & Full Dots
+158332 - 0x00E3A (Beyond Rotating Bridge 4) - 0x00C2E - Shapers & Full Dots
+158339 - 0x17E2B (Long Bridge Control) - True - Rotated Shapers & Shapers
+Door - 0x18482 (Blue Water Pump) - 0x00E3A
+
+Swamp Purple Area (Swamp) - Swamp Rotating Bridge - TrueOneWay - Swamp Purple Underwater - 0x0A1D6:
+Door - 0x0A1D6 (Purple Water Pump) - 0x00E3A
+
+Swamp Purple Underwater (Swamp):
+158333 - 0x009A6 (Purple Underwater) - True - Shapers & Triangles & Black/White Squares & Rotated Shapers
+
+Swamp Blue Underwater (Swamp):
+158334 - 0x009AB (Blue Underwater 1) - True - Shapers & Negative Shapers
+158335 - 0x009AD (Blue Underwater 2) - 0x009AB - Shapers & Negative Shapers
+158336 - 0x009AE (Blue Underwater 3) - 0x009AD - Shapers & Negative Shapers
+158337 - 0x009AF (Blue Underwater 4) - 0x009AE - Shapers & Negative Shapers
+158338 - 0x00006 (Blue Underwater 5) - 0x009AF - Shapers & Negative Shapers
+
+Swamp Maze (Swamp) - Swamp Laser Area - 0x17C0A & 0x17E07:
+158340 - 0x17C0A (Maze Control) - True - Shapers & Negative Shapers & Rotated Shapers & Environment
+158112 - 0x17E07 (Maze Control Other Side) - True - Shapers & Negative Shapers & Rotated Shapers & Environment
+
+Swamp Laser Area (Swamp) - Outside Swamp - 0x2D880:
+158711 - 0x03615 (Laser Panel) - True - True
+Laser - 0x00BF6 (Laser) - 0x03615
+158341 - 0x17C05 (Laser Shortcut Left Panel) - True - Shapers & Stars & Negative Shapers & Stars + Same Colored Symbol
+158342 - 0x17C02 (Laser Shortcut Right Panel) - 0x17C05 - Shapers & Negative Shapers & Rotated Shapers
+Door - 0x2D880 (Laser Shortcut) - 0x17C02
+
+Treehouse Entry Area (Treehouse) - Treehouse Between Doors - 0x0C309:
+158343 - 0x17C95 (Boat Spawn) - True - Boat
+158344 - 0x0288C (First Door Panel) - True - Stars & Stars + Same Colored Symbol & Triangles
+Door - 0x0C309 (First Door) - 0x0288C
+
+Treehouse Between Doors (Treehouse) - Treehouse Yellow Bridge - 0x0C310:
+158345 - 0x02886 (Second Door Panel) - True - Stars & Stars + Same Colored Symbol & Triangles
+Door - 0x0C310 (Second Door) - 0x02886
+
+Treehouse Yellow Bridge (Treehouse) - Treehouse After Yellow Bridge - 0x17DC4:
+158346 - 0x17D72 (Yellow Bridge 1) - True - Stars & Stars + Same Colored Symbol & Triangles
+158347 - 0x17D8F (Yellow Bridge 2) - 0x17D72 - Stars & Stars + Same Colored Symbol & Triangles
+158348 - 0x17D74 (Yellow Bridge 3) - 0x17D8F - Stars & Stars + Same Colored Symbol & Triangles
+158349 - 0x17DAC (Yellow Bridge 4) - 0x17D74 - Stars & Stars + Same Colored Symbol & Triangles
+158350 - 0x17D9E (Yellow Bridge 5) - 0x17DAC - Stars & Stars + Same Colored Symbol & Triangles
+158351 - 0x17DB9 (Yellow Bridge 6) - 0x17D9E - Stars & Stars + Same Colored Symbol & Triangles
+158352 - 0x17D9C (Yellow Bridge 7) - 0x17DB9 - Stars & Stars + Same Colored Symbol & Triangles
+158353 - 0x17DC2 (Yellow Bridge 8) - 0x17D9C - Stars & Stars + Same Colored Symbol & Triangles
+158354 - 0x17DC4 (Yellow Bridge 9) - 0x17DC2 - Stars & Stars + Same Colored Symbol & Triangles
+
+Treehouse After Yellow Bridge (Treehouse) - Treehouse Junction - 0x0A181:
+158355 - 0x0A182 (Third Door Panel) - True - Stars & Stars + Same Colored Symbol & Triangles & Colored Squares
+Door - 0x0A181 (Third Door) - 0x0A182
+
+Treehouse Junction (Treehouse) - Treehouse Right Orange Bridge - True - Treehouse First Purple Bridge - True - Treehouse Green Bridge - True:
+158356 - 0x2700B (Laser House Door Timer Outside Control) - True - True
+
+Treehouse First Purple Bridge (Treehouse) - Treehouse Second Purple Bridge - 0x17D6C:
+158357 - 0x17DC8 (First Purple Bridge 1) - True - Stars & Full Dots
+158358 - 0x17DC7 (First Purple Bridge 2) - 0x17DC8 - Stars & Full Dots
+158359 - 0x17CE4 (First Purple Bridge 3) - 0x17DC7 - Stars & Full Dots
+158360 - 0x17D2D (First Purple Bridge 4) - 0x17CE4 - Stars & Full Dots
+158361 - 0x17D6C (First Purple Bridge 5) - 0x17D2D - Stars & Full Dots
+
+Treehouse Right Orange Bridge (Treehouse) - Treehouse Bridge Platform - 0x17DA2:
+158391 - 0x17D88 (Right Orange Bridge 1) - True - Stars & Stars + Same Colored Symbol & Triangles
+158392 - 0x17DB4 (Right Orange Bridge 2) - 0x17D88 - Stars & Stars + Same Colored Symbol & Triangles
+158393 - 0x17D8C (Right Orange Bridge 3) - 0x17DB4 - Stars & Stars + Same Colored Symbol & Triangles
+158394 - 0x17CE3 (Right Orange Bridge 4 & Directional) - 0x17D8C - Triangles
+158395 - 0x17DCD (Right Orange Bridge 5) - 0x17CE3 - Stars & Stars + Same Colored Symbol & Triangles
+158396 - 0x17DB2 (Right Orange Bridge 6) - 0x17DCD - Stars & Stars + Same Colored Symbol & Triangles
+158397 - 0x17DCC (Right Orange Bridge 7) - 0x17DB2 - Stars & Stars + Same Colored Symbol & Triangles
+158398 - 0x17DCA (Right Orange Bridge 8) - 0x17DCC - Stars & Stars + Same Colored Symbol & Triangles
+158399 - 0x17D8E (Right Orange Bridge 9) - 0x17DCA - Stars & Stars + Same Colored Symbol & Triangles
+158400 - 0x17DB7 (Right Orange Bridge 10 & Directional) - 0x17D8E - Triangles
+158401 - 0x17DB1 (Right Orange Bridge 11) - 0x17DB7 - Stars & Stars + Same Colored Symbol & Triangles
+158402 - 0x17DA2 (Right Orange Bridge 12) - 0x17DB1 - Stars & Stars + Same Colored Symbol & Triangles
+
+Treehouse Bridge Platform (Treehouse) - Main Island - 0x0C32D:
+158404 - 0x037FF (Bridge Control) - True - Stars
+Door - 0x0C32D (Drawbridge) - 0x037FF
+
+Treehouse Second Purple Bridge (Treehouse) - Treehouse Left Orange Bridge - 0x17DC6:
+158362 - 0x17D9B (Second Purple Bridge 1) - True - Stars & Black/White Squares & Triangles & Stars + Same Colored Symbol
+158363 - 0x17D99 (Second Purple Bridge 2) - 0x17D9B - Stars & Black/White Squares & Triangles & Stars + Same Colored Symbol
+158364 - 0x17DAA (Second Purple Bridge 3) - 0x17D99 - Stars & Black/White Squares & Triangles & Stars + Same Colored Symbol
+158365 - 0x17D97 (Second Purple Bridge 4) - 0x17DAA - Stars & Black/White Squares & Colored Squares & Triangles & Stars + Same Colored Symbol
+158366 - 0x17BDF (Second Purple Bridge 5) - 0x17D97 - Stars & Colored Squares & Triangles & Stars + Same Colored Symbol
+158367 - 0x17D91 (Second Purple Bridge 6) - 0x17BDF - Stars & Colored Squares & Triangles & Stars + Same Colored Symbol
+158368 - 0x17DC6 (Second Purple Bridge 7) - 0x17D91 - Stars & Colored Squares & Triangles & Stars + Same Colored Symbol
+
+Treehouse Left Orange Bridge (Treehouse) - Treehouse Laser Room Front Platform - 0x17DDE - Treehouse Laser Room Back Platform - 0x17DDB:
+158376 - 0x17DB3 (Left Orange Bridge 1) - True - Stars & Black/White Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers
+158377 - 0x17DB5 (Left Orange Bridge 2) - 0x17DB3 - Stars & Black/White Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers
+158378 - 0x17DB6 (Left Orange Bridge 3) - 0x17DB5 - Stars & Black/White Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers
+158379 - 0x17DC0 (Left Orange Bridge 4) - 0x17DB6 - Stars & Black/White Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers
+158380 - 0x17DD7 (Left Orange Bridge 5) - 0x17DC0 - Stars & Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers
+158381 - 0x17DD9 (Left Orange Bridge 6) - 0x17DD7 - Stars & Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers
+158382 - 0x17DB8 (Left Orange Bridge 7) - 0x17DD9 - Stars & Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers
+158383 - 0x17DDC (Left Orange Bridge 8) - 0x17DB8 - Stars & Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers
+158384 - 0x17DD1 (Left Orange Bridge 9 & Directional) - 0x17DDC - Stars & Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers & Black/White Squares
+158385 - 0x17DDE (Left Orange Bridge 10) - 0x17DD1 - Stars & Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers & Black/White Squares
+158386 - 0x17DE3 (Left Orange Bridge 11) - 0x17DDE - Stars & Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers & Black/White Squares
+158387 - 0x17DEC (Left Orange Bridge 12) - 0x17DE3 - Stars & Colored Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers & Black/White Squares
+158388 - 0x17DAE (Left Orange Bridge 13) - 0x17DEC & 0x03613 - Stars & Black/White Squares & Stars + Same Colored Symbol & Shapers & Rotated Shapers & Triangles
+158389 - 0x17DB0 (Left Orange Bridge 14) - 0x17DAE - Stars & Black/White Squares & Stars + Same Colored Symbol
+158390 - 0x17DDB (Left Orange Bridge 15) - 0x17DB0 - Stars & Black/White Squares & Stars + Same Colored Symbol
+
+Treehouse Green Bridge (Treehouse):
+158369 - 0x17E3C (Green Bridge 1) - True - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol
+158370 - 0x17E4D (Green Bridge 2) - 0x17E3C - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol
+158371 - 0x17E4F (Green Bridge 3) - 0x17E4D - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol
+158372 - 0x17E52 (Green Bridge 4 & Directional) - 0x17E4F - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol
+158373 - 0x17E5B (Green Bridge 5) - 0x17E52 - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol
+158374 - 0x17E5F (Green Bridge 6) - 0x17E5B - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol & Triangles
+158375 - 0x17E61 (Green Bridge 7) - 0x17E5F - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol & Triangles
+158610 - 0x17FA9 (Green Bridge Discard) - 0x17E61 - Arrows
+
+Treehouse Laser Room Front Platform (Treehouse) - Treehouse Laser Room - 0x0C323:
+Door - 0x0C323 (Laser House Entry) - 0x17DA2 & 0x2700B & 0x17DEC
+
+Treehouse Laser Room Back Platform (Treehouse):
+158611 - 0x17FA0 (Laser Discard) - True - Arrows
+
+Treehouse Laser Room (Treehouse):
+158712 - 0x03613 (Laser Panel) - True - True
+158403 - 0x17CBC (Laser House Door Timer Inside) - True - True
+Laser - 0x028A4 (Laser) - 0x03613
+
+Mountainside (Mountainside) - Main Island - True - Mountaintop - True:
+158612 - 0x17C42 (Discard) - True - Arrows
+158665 - 0x002A6 (Vault) - True - Symmetry & Colored Squares & Triangles & Stars & Stars + Same Colored Symbol
+158666 - 0x03542 (Vault Box) - 0x002A6 - True
+
+Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34:
+158405 - 0x0042D (River Shape) - True - True
+158406 - 0x09F7F (Box Short) - 7 Lasers - True
+158407 - 0x17C34 (Trap Door Triple Exit) - 0x09F7F - Stars & Black/White Squares & Stars + Same Colored Symbol & Triangles
+158800 - 0xFFF00 (Box Long) - 7 Lasers & 11 Lasers & 0x17C34 - True
+
+Mountain Top Layer (Mountain Floor 1) - Mountain Top Layer Bridge - 0x09E39:
+158408 - 0x09E39 (Light Bridge Controller) - True - Eraser & Triangles
+
+Mountain Top Layer Bridge (Mountain Floor 1) - Mountain Floor 2 - 0x09E54:
+158409 - 0x09E7A (Right Row 1) - True - Black/White Squares & Dots
+158410 - 0x09E71 (Right Row 2) - 0x09E7A - Black/White Squares & Triangles
+158411 - 0x09E72 (Right Row 3) - 0x09E71 - Black/White Squares & Triangles & Shapers
+158412 - 0x09E69 (Right Row 4) - 0x09E72 - Stars & Black/White Squares & Stars + Same Colored Symbol & Rotated Shapers
+158413 - 0x09E7B (Right Row 5) - 0x09E69 - Stars & Black/White Squares & Stars + Same Colored Symbol & Eraser & Dots & Triangles & Shapers
+158414 - 0x09E73 (Left Row 1) - True - Black/White Squares & Triangles
+158415 - 0x09E75 (Left Row 2) - 0x09E73 - Black/White Squares & Shapers & Rotated Shapers
+158416 - 0x09E78 (Left Row 3) - 0x09E75 - Stars & Triangles & Stars + Same Colored Symbol & Shapers & Rotated Shapers
+158417 - 0x09E79 (Left Row 4) - 0x09E78 - Stars & Colored Squares & Stars + Same Colored Symbol & Triangles
+158418 - 0x09E6C (Left Row 5) - 0x09E79 - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol
+158419 - 0x09E6F (Left Row 6) - 0x09E6C - Symmetry & Stars & Colored Squares & Black/White Squares & Stars + Same Colored Symbol & Symmetry
+158420 - 0x09E6B (Left Row 7) - 0x09E6F - Symmetry & Full Dots & Triangles
+158421 - 0x33AF5 (Back Row 1) - True - Symmetry & Black/White Squares & Triangles
+158422 - 0x33AF7 (Back Row 2) - 0x33AF5 - Symmetry & Stars & Triangles & Stars + Same Colored Symbol
+158423 - 0x09F6E (Back Row 3) - 0x33AF7 - Symmetry & Stars & Shapers & Stars + Same Colored Symbol
+158424 - 0x09EAD (Trash Pillar 1) - True - Rotated Shapers & Stars
+158425 - 0x09EAF (Trash Pillar 2) - 0x09EAD - Rotated Shapers & Triangles
+Door - 0x09E54 (Exit) - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B
+
+Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Blue Bridge - 0x09E86:
+158426 - 0x09FD3 (Near Row 1) - True - Stars & Colored Squares & Stars + Same Colored Symbol
+158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Stars & Triangles & Stars + Same Colored Symbol
+158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol
+158429 - 0x09FD7 (Near Row 4) - 0x09FD6 - Stars
+158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Stars & Stars + Same Colored Symbol & Rotated Shapers & Eraser
+Door - 0x09FFB (Staircase Near) - 0x09FD8
+
+Mountain Floor 2 Blue Bridge (Mountain Floor 2) - Mountain Floor 2 Beyond Bridge - TrueOneWay - Mountain Floor 2 At Door - TrueOneWay:
+
+Mountain Floor 2 At Door (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EDD:
+Door - 0x09EDD (Elevator Room Entry) - 0x09ED8 & 0x09E86
+
+Mountain Floor 2 Light Bridge Room Near (Mountain Floor 2):
+158431 - 0x09E86 (Light Bridge Controller Near) - True - Shapers & Dots
+
+Mountain Floor 2 Beyond Bridge (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Far - 0x09E07:
+158432 - 0x09FCC (Far Row 1) - True - Triangles
+158433 - 0x09FCE (Far Row 2) - 0x09FCC - Black/White Squares & Stars & Stars + Same Colored Symbol
+158434 - 0x09FCF (Far Row 3) - 0x09FCE - Stars & Triangles & Stars + Same Colored Symbol
+158435 - 0x09FD0 (Far Row 4) - 0x09FCF - Rotated Shapers & Negative Shapers
+158436 - 0x09FD1 (Far Row 5) - 0x09FD0 - Dots
+158437 - 0x09FD2 (Far Row 6) - 0x09FD1 - Rotated Shapers
+Door - 0x09E07 (Staircase Far) - 0x09FD2
+
+Mountain Floor 2 Light Bridge Room Far (Mountain Floor 2):
+158438 - 0x09ED8 (Light Bridge Controller Far) - True - Shapers & Dots
+
+Mountain Floor 2 Elevator Room (Mountain Floor 2) - Mountain Floor 2 Elevator - TrueOneWay:
+158613 - 0x17F93 (Elevator Discard) - True - Arrows
+
+Mountain Floor 2 Elevator (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EEB - Mountain Third Layer - 0x09EEB:
+158439 - 0x09EEB (Elevator Control Panel) - True - Dots
+
+Mountain Third Layer (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueOneWay - Mountain Bottom Floor - 0x09F89:
+158440 - 0x09FC1 (Giant Puzzle Bottom Left) - True - Shapers & Negative Shapers
+158441 - 0x09F8E (Giant Puzzle Bottom Right) - True - Shapers & Eraser
+158442 - 0x09F01 (Giant Puzzle Top Right) - True - Shapers & Eraser
+158443 - 0x09EFF (Giant Puzzle Top Left) - True - Shapers & Eraser
+158444 - 0x09FDA (Giant Puzzle) - 0x09FC1 & 0x09F8E & 0x09F01 & 0x09EFF - Shapers & Symmetry
+Door - 0x09F89 (Exit) - 0x09FDA
+
+Mountain Bottom Floor (Mountain Bottom Floor) - Mountain Bottom Floor Rock - 0x17FA2 - Final Room - 0x0C141:
+158614 - 0x17FA2 (Discard) - 0xFFF00 - Arrows
+158445 - 0x01983 (Final Room Entry Left) - True - Shapers & Stars
+158446 - 0x01987 (Final Room Entry Right) - True - Squares & Colored Squares & Dots
+Door - 0x0C141 (Final Room Entry) - 0x01983 & 0x01987
+
+Mountain Bottom Floor Rock (Mountain Bottom Floor) - Mountain Bottom Floor - 0x17F33 - Mountain Path to Caves - 0x17F33:
+Door - 0x17F33 (Rock Open) - True
+
+Mountain Path to Caves (Mountain Bottom Floor) - Mountain Bottom Floor Rock - 0x334E1 - Caves - 0x2D77D:
+158447 - 0x00FF8 (Caves Entry Panel) - True - Arrows & Black/White Squares
+Door - 0x2D77D (Caves Entry) - 0x00FF8
+158448 - 0x334E1 (Rock Control) - True - True
+
+Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Path to Challenge - 0x019A5:
+158451 - 0x335AB (Elevator Inside Control) - True - Dots & Squares & Black/White Squares
+158452 - 0x335AC (Elevator Upper Outside Control) - 0x335AB - Squares & Black/White Squares
+158453 - 0x3369D (Elevator Lower Outside Control) - 0x335AB - Squares & Black/White Squares & Dots
+158454 - 0x00190 (Blue Tunnel Right First 1) - True - Arrows
+158455 - 0x00558 (Blue Tunnel Right First 2) - 0x00190 - Arrows
+158456 - 0x00567 (Blue Tunnel Right First 3) - 0x00558 - Arrows
+158457 - 0x006FE (Blue Tunnel Right First 4) - 0x00567 - Arrows
+158458 - 0x01A0D (Blue Tunnel Left First 1) - True - Arrows & Symmetry
+158459 - 0x008B8 (Blue Tunnel Left Second 1) - True - Arrows & Triangles
+158460 - 0x00973 (Blue Tunnel Left Second 2) - 0x008B8 - Arrows & Triangles
+158461 - 0x0097B (Blue Tunnel Left Second 3) - 0x00973 - Arrows & Triangles
+158462 - 0x0097D (Blue Tunnel Left Second 4) - 0x0097B - Arrows & Triangles
+158463 - 0x0097E (Blue Tunnel Left Second 5) - 0x0097D - Arrows & Triangles
+158464 - 0x00994 (Blue Tunnel Right Second 1) - True - Arrows & Shapers & Rotated Shapers
+158465 - 0x334D5 (Blue Tunnel Right Second 2) - 0x00994 - Arrows & Rotated Shapers
+158466 - 0x00995 (Blue Tunnel Right Second 3) - 0x334D5 - Arrows & Shapers & Rotated Shapers
+158467 - 0x00996 (Blue Tunnel Right Second 4) - 0x00995 - Arrows & Shapers & Rotated Shapers
+158468 - 0x00998 (Blue Tunnel Right Second 5) - 0x00996 - Arrows & Shapers
+158469 - 0x009A4 (Blue Tunnel Left Third 1) - True - Arrows & Stars
+158470 - 0x018A0 (Blue Tunnel Right Third 1) - True - Arrows & Symmetry
+158471 - 0x00A72 (Blue Tunnel Left Fourth 1) - True - Arrows & Shapers & Negative Shapers
+158472 - 0x32962 (First Floor Left) - True - Full Dots & Rotated Shapers
+158473 - 0x32966 (First Floor Grounded) - True - Stars & Triangles & Rotated Shapers & Black/White Squares & Stars + Same Colored Symbol
+158474 - 0x01A31 (First Floor Middle) - True - Stars
+158475 - 0x00B71 (First Floor Right) - True - Full Dots & Eraser & Stars & Stars + Same Colored Symbol & Colored Squares & Shapers & Negative Shapers
+158478 - 0x288EA (First Wooden Beam) - True - Stars
+158479 - 0x288FC (Second Wooden Beam) - True - Shapers & Eraser
+158480 - 0x289E7 (Third Wooden Beam) - True - Eraser & Triangles
+158481 - 0x288AA (Fourth Wooden Beam) - True - Full Dots & Negative Shapers & Shapers
+158482 - 0x17FB9 (Left Upstairs Single) - True - Full Dots & Arrows & Black/White Squares
+158483 - 0x0A16B (Left Upstairs Left Row 1) - True - Full Dots & Arrows
+158484 - 0x0A2CE (Left Upstairs Left Row 2) - 0x0A16B - Full Dots & Arrows
+158485 - 0x0A2D7 (Left Upstairs Left Row 3) - 0x0A2CE - Full Dots & Arrows
+158486 - 0x0A2DD (Left Upstairs Left Row 4) - 0x0A2D7 - Full Dots & Arrows
+158487 - 0x0A2EA (Left Upstairs Left Row 5) - 0x0A2DD - Full Dots & Arrows
+158488 - 0x0008F (Right Upstairs Left Row 1) - True - Dots & Black/White Squares & Colored Squares
+158489 - 0x0006B (Right Upstairs Left Row 2) - 0x0008F - Stars & Black/White Squares & Colored Squares & Stars + Same Colored Symbol
+158490 - 0x0008B (Right Upstairs Left Row 3) - 0x0006B - Stars & Black/White Squares & Colored Squares & Stars + Same Colored Symbol & Triangles
+158491 - 0x0008C (Right Upstairs Left Row 4) - 0x0008B - Stars & Stars + Same Colored Symbol & Shapers & Rotated Shapers
+158492 - 0x0008A (Right Upstairs Left Row 5) - 0x0008C - Stars & Stars + Same Colored Symbol & Shapers & Rotated Shapers & Eraser & Triangles
+158493 - 0x00089 (Right Upstairs Left Row 6) - 0x0008A - Shapers & Negative Shapers & Dots
+158494 - 0x0006A (Right Upstairs Left Row 7) - 0x00089 - Stars & Dots
+158495 - 0x0006C (Right Upstairs Left Row 8) - 0x0006A - Dots & Stars & Stars + Same Colored Symbol & Eraser
+158496 - 0x00027 (Right Upstairs Right Row 1) - True - Colored Squares & Black/White Squares & Eraser
+158497 - 0x00028 (Right Upstairs Right Row 2) - 0x00027 - Shapers & Symmetry
+158498 - 0x00029 (Right Upstairs Right Row 3) - 0x00028 - Symmetry & Triangles & Eraser
+158476 - 0x09DD5 (Lone Pillar) - True - Arrows
+Door - 0x019A5 (Pillar Door) - 0x09DD5
+158449 - 0x021D7 (Mountain Shortcut Panel) - True - Stars & Stars + Same Colored Symbol & Triangles & Eraser
+Door - 0x2D73F (Mountain Shortcut Door) - 0x021D7
+158450 - 0x17CF2 (Swamp Shortcut Panel) - True - Arrows
+Door - 0x2D859 (Swamp Shortcut Door) - 0x17CF2
+
+Path to Challenge (Caves) - Challenge - 0x0A19A:
+158477 - 0x0A16E (Challenge Entry Panel) - True - Stars & Arrows & Stars + Same Colored Symbol
+Door - 0x0A19A (Challenge Entry) - 0x0A16E
+
+Challenge (Challenge) - Tunnels - 0x0348A:
+158499 - 0x0A332 (Start Timer) - 11 Lasers - True
+158500 - 0x0088E (Small Basic) - 0x0A332 - True
+158501 - 0x00BAF (Big Basic) - 0x0088E - True
+158502 - 0x00BF3 (Square) - 0x00BAF - Squares & Black/White Squares
+158503 - 0x00C09 (Maze Map) - 0x00BF3 - Dots
+158504 - 0x00CDB (Stars and Dots) - 0x00C09 - Stars & Dots
+158505 - 0x0051F (Symmetry) - 0x00CDB - Symmetry & Colored Dots & Dots
+158506 - 0x00524 (Stars and Shapers) - 0x0051F - Stars & Shapers
+158507 - 0x00CD4 (Big Basic 2) - 0x00524 - True
+158508 - 0x00CB9 (Choice Squares Right) - 0x00CD4 - Squares & Black/White Squares
+158509 - 0x00CA1 (Choice Squares Middle) - 0x00CD4 - Squares & Black/White Squares
+158510 - 0x00C80 (Choice Squares Left) - 0x00CD4 - Squares & Black/White Squares
+158511 - 0x00C68 (Choice Squares 2 Right) - 0x00CB9 | 0x00CA1 | 0x00C80 - Squares & Black/White Squares & Colored Squares
+158512 - 0x00C59 (Choice Squares 2 Middle) - 0x00CB9 | 0x00CA1 | 0x00C80 - Squares & Black/White Squares & Colored Squares
+158513 - 0x00C22 (Choice Squares 2 Left) - 0x00CB9 | 0x00CA1 | 0x00C80 - Squares & Black/White Squares & Colored Squares
+158514 - 0x034F4 (Maze Hidden 1) - 0x00C68 | 0x00C59 | 0x00C22 - Triangles
+158515 - 0x034EC (Maze Hidden 2) - 0x00C68 | 0x00C59 | 0x00C22 - Triangles
+158516 - 0x1C31A (Dots Pillar) - 0x034F4 & 0x034EC - Dots & Symmetry & Pillar
+158517 - 0x1C319 (Squares Pillar) - 0x034F4 & 0x034EC - Squares & Black/White Squares & Symmetry & Pillar
+158667 - 0x0356B (Vault Box) - 0x1C31A & 0x1C319 - True
+158518 - 0x039B4 (Tunnels Entry Panel) - True - Arrows
+Door - 0x0348A (Tunnels Entry) - 0x039B4
+
+Tunnels (Tunnels) - Windmill Interior - 0x27739 - Desert Lowest Level Inbetween Shortcuts - 0x27263 - Town - 0x09E87:
+158668 - 0x2FAF6 (Vault Box) - True - True
+158519 - 0x27732 (Theater Shortcut Panel) - True - True
+Door - 0x27739 (Theater Shortcut) - 0x27732
+158520 - 0x2773D (Desert Shortcut Panel) - True - True
+Door - 0x27263 (Desert Shortcut) - 0x2773D
+158521 - 0x09E85 (Town Shortcut Panel) - True - Arrows
+Door - 0x09E87 (Town Shortcut) - 0x09E85
+
+Final Room (Mountain Final Room) - Elevator - 0x339BB & 0x33961:
+158522 - 0x0383A (Right Pillar 1) - True - Stars & Eraser & Triangles & Stars + Same Colored Symbol
+158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Full Dots & Triangles
+158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Shapers & Stars & Negative Shapers & Stars + Same Colored Symbol
+158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Eraser & Symmetry & Stars & Stars + Same Colored Symbols & Negative Shapers & Shapers
+158526 - 0x0383D (Left Pillar 1) - True - Stars & Black/White Squares & Stars + Same Colored Symbol
+158527 - 0x0383F (Left Pillar 2) - 0x0383D - Triangles
+158528 - 0x03859 (Left Pillar 3) - 0x0383F - Symmetry & Shapers & Black/White Squares
+158529 - 0x339BB (Left Pillar 4) - 0x03859 - Symmetry & Black/White Squares & Stars & Stars + Same Colored Symbol & Triangles
+
+Elevator (Mountain Final Room):
+158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True
+158531 - 0x3D9A7 (Elevator Door Close Right) - True - True
+158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True
+158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True
+158534 - 0x3D9AA (Back Wall Left) - 0x3D9A6 | 0x3D9A7 - True
+158535 - 0x3D9A8 (Back Wall Right) - 0x3D9A6 | 0x3D9A7 - True
+158536 - 0x3D9A9 (Elevator Start) - 0x3D9AA | 0x3D9A8 - True
+
+Boat (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Treehouse Entry Area - TrueOneWay - Quarry Boathouse Behind Staircase - TrueOneWay - Inside Glass Factory Behind Back Wall - TrueOneWay:
diff --git a/worlds/witness/__init__.py b/worlds/witness/__init__.py
index 0f8f0d75c0..7f5b9d2bfb 100644
--- a/worlds/witness/__init__.py
+++ b/worlds/witness/__init__.py
@@ -1,18 +1,20 @@
"""
Archipelago init file for The Witness
"""
-
import typing
from BaseClasses import Region, RegionType, Location, MultiWorld, Item, Entrance, Tutorial, ItemClassification
+from .hints import get_always_hint_locations, get_always_hint_items, get_priority_hint_locations, \
+ get_priority_hint_items, make_hints, generate_joke_hints
from ..AutoWorld import World, WebWorld
-from .player_logic import StaticWitnessLogic, WitnessPlayerLogic
+from .player_logic import WitnessPlayerLogic
+from .static_logic import StaticWitnessLogic
from .locations import WitnessPlayerLocations, StaticWitnessLocations
from .items import WitnessItem, StaticWitnessItems, WitnessPlayerItems
from .rules import set_rules
from .regions import WitnessRegions
from .Options import is_option_enabled, the_witness_options, get_option_value
-from .utils import best_junk_to_add_based_on_weights
+from .utils import best_junk_to_add_based_on_weights, get_audio_logs
from logging import warning
@@ -36,7 +38,7 @@ class WitnessWorld(World):
"""
game = "The Witness"
topology_present = False
- data_version = 7
+ data_version = 8
static_logic = StaticWitnessLogic()
static_locat = StaticWitnessLocations()
@@ -59,6 +61,8 @@ class WitnessWorld(World):
'door_hexes': self.items.DOORS,
'symbols_not_in_the_game': self.items.SYMBOLS_NOT_IN_THE_GAME,
'disabled_panels': self.player_logic.COMPLETELY_DISABLED_CHECKS,
+ 'log_ids_to_hints': self.log_ids_to_hints,
+ 'progressive_item_lists': self.items.MULTI_LISTS_BY_CODE
}
def generate_early(self):
@@ -77,6 +81,8 @@ class WitnessWorld(World):
self.items = WitnessPlayerItems(self.locat, self.world, self.player, self.player_logic)
self.regio = WitnessRegions(self.locat)
+ self.log_ids_to_hints = dict()
+
self.junk_items_created = {key: 0 for key in self.items.JUNK_WEIGHTS.keys()}
def generate_basic(self):
@@ -84,17 +90,18 @@ class WitnessWorld(World):
pool = []
items_by_name = dict()
for item in self.items.ITEM_TABLE:
- witness_item = self.create_item(item)
- if item in self.items.PROGRESSION_TABLE:
- pool.append(witness_item)
- items_by_name[item] = witness_item
+ for i in range(0, self.items.PROG_ITEM_AMOUNTS[item]):
+ if item in self.items.PROGRESSION_TABLE:
+ witness_item = self.create_item(item)
+ pool.append(witness_item)
+ items_by_name[item] = witness_item
less_junk = 0
# Put good item on first check if symbol shuffle is on
symbols = is_option_enabled(self.world, self.player, "shuffle_symbols")
- if symbols:
+ if symbols and get_option_value(self.world, self.player, "puzzle_randomization") != 1:
random_good_item = self.world.random.choice(self.items.GOOD_ITEMS)
first_check = self.world.get_location(
@@ -138,9 +145,39 @@ class WitnessWorld(World):
set_rules(self.world, self.player, self.player_logic, self.locat)
def fill_slot_data(self) -> dict:
- slot_data = self._get_slot_data()
+ hint_amount = get_option_value(self.world, self.player, "hint_amount")
- slot_data["hard_mode"] = False
+ credits_hint = ("This Randomizer", "is brought to you by", "NewSoupVi, Jarno, jbzdarkid, sigma144", -1)
+
+ audio_logs = get_audio_logs().copy()
+
+ if hint_amount != 0:
+ generated_hints = make_hints(self.world, self.player, hint_amount)
+
+ self.world.random.shuffle(audio_logs)
+
+ duplicates = len(audio_logs) // hint_amount
+
+ for _ in range(0, hint_amount):
+ hint = generated_hints.pop()
+
+ for _ in range(0, duplicates):
+ audio_log = audio_logs.pop()
+ self.log_ids_to_hints[int(audio_log, 16)] = hint
+
+ if audio_logs:
+ audio_log = audio_logs.pop()
+ self.log_ids_to_hints[int(audio_log, 16)] = credits_hint
+
+ joke_hints = generate_joke_hints(self.world, len(audio_logs))
+
+ while audio_logs:
+ audio_log = audio_logs.pop()
+ self.log_ids_to_hints[int(audio_log, 16)] = joke_hints.pop()
+
+ # generate hints done
+
+ slot_data = self._get_slot_data()
for option_name in the_witness_options:
slot_data[option_name] = get_option_value(
diff --git a/worlds/witness/hints.py b/worlds/witness/hints.py
new file mode 100644
index 0000000000..3ee010ea2e
--- /dev/null
+++ b/worlds/witness/hints.py
@@ -0,0 +1,281 @@
+from BaseClasses import MultiWorld
+from .Options import is_option_enabled, get_option_value
+
+joke_hints = [
+ ("Quaternions", "break", "my brain"),
+ ("Eclipse", "has nothing", "but you should do it anyway"),
+ ("", "Beep", ""),
+ ("Putting in custom subtitles", "shouldn't have been", "as hard as it was..."),
+ ("BK mode", "is right", "around the corner"),
+ ("", "You can do it!", ""),
+ ("", "I believe in you!", ""),
+ ("The person playing", "is", "cute <3"),
+ ("dash dot, dash dash dash", "dash, dot dot dot dot, dot dot", "dash dot, dash dash dot"),
+ ("When you think about it,", "there are actually a lot of", "bubbles in a stream"),
+ ("Never gonna give you up", "Never gonna let you down", "Never gonna run around and desert you"),
+ ("Thanks to", "the Archipelago developers", "for making this possible."),
+ ("Have you tried ChecksFinder?", "If you like puzzles,", "you might enjoy it!"),
+ ("Have you tried Dark Souls III?", "A tough game like this", "feels better when friends are helping you!"),
+ ("Have you tried Donkey Kong Country 3?", "A legendary game", "from a golden age of platformers!"),
+ ("Have you tried Factorio?", "Alone in an unknown world.", "Sound familiar?"),
+ ("Have you tried Final Fantasy?", "Experience a classic game", "improved to fit modern standards!"),
+ ("Have you tried Hollow Knight?", "Another independent hit", "revolutionising a genre!"),
+ ("Have you tried A Link to the Past?", "The Archipelago game", "that started it all!"),
+ ("Have you tried Meritous?", "You should know that obscure games", "are often groundbreaking!"),
+ ("Have you tried Ocarine of Time?", "One of the biggest randomizers,", "big inspiration for this one's features!"),
+ ("Have you tried Raft?", "Haven't you always wanted to explore", "the ocean surrounding this island?"),
+ ("Have you tried Risk of Rain 2?", "I haven't either.", "But I hear it's incredible!"),
+ ("Have you tried Rogue Legacy?", "After solving so many puzzles", "it's the perfect way to rest your brain."),
+ ("Have you tried Secret of Evermore?", "I haven't either", "But I hear it's great!"),
+ ("Have you tried Slay the Spire?", "Experience the thrill of combat", "without needing fast fingers!"),
+ ("Have you tried SMZ3?", "Why play one incredible game", "when you can play 2 at once?"),
+ ("Have you tried Starcraft 2?", "Use strategy and management", "to crush your enemies!"),
+ ("Have you tried Super Mario 64?", "3-dimensional games like this", "owe everything to that game."),
+ ("Have you tried Super Metroid?", "A classic game", "that started a whole genre."),
+ ("Have you tried Timespinner?", "Everyone who plays it", "ends up loving it!"),
+ ("Have you tried VVVVVV?", "Experience the essence of gaming", "distilled into its purest form!"),
+ ("Have you tried The Witness?", "Oh. I guess you already have.", " Thanks for playing!"),
+ ("One day I was fascinated", "by the subject of", "generation of waves by wind"),
+ ("I don't like sandwiches", "Why would you think I like sandwiches?", "Have you ever seen me with a sandwich?"),
+ ("Where are you right now?", "I'm at soup!", "What do you mean you're at soup?"),
+ ("Remember to ask", "in the Archipelago Discord", "what the Functioning Brain does."),
+ ("", "Don't use your puzzle skips", "you might need them later"),
+ ("", "For an extra challenge", "Try playing blindfolded"),
+ ("Go to the top of the mountain", "and see if you can see", "your house"),
+ ("Yellow = Red + Green", "Cyan = Green + Blue", "Magenta = Red + Blue"),
+ ("", "Maybe that panel really is unsolvable", ""),
+ ("", "Did you make sure it was plugged in?", ""),
+ ("", "Do not look into laser with remaining eye", ""),
+ ("", "Try pressing Space to jump", ""),
+ ("The Witness is a Doom clone.", "Just replace the demons", "with puzzles"),
+ ("", "Test Hint please ignore", ""),
+ ("Shapers can never be placed", "outside the panel boundaries", "even if subtracted."),
+ ("", "The Keep laser panels use", "the same trick on both sides!"),
+ ("Can't get past a door? Try going around.", "Can't go around? Try building a", "nether portal."),
+ ("", "We've been trying to reach you", "about your car's extended warranty"),
+ ("I hate this game. I hate this game.", "I hate this game.", "-chess player Bobby Fischer"),
+ ("Dear Mario,", "Please come to the castle.", "I've baked a cake for you!"),
+ ("Have you tried waking up?", "", "Yeah, me neither."),
+ ("Why do they call it The Witness,", "when wit game the player view", "play of with the game."),
+ ("", "THE WIND FISH IN NAME ONLY", "FOR IT IS NEITHER"),
+ ("Like this game? Try The Wit.nes,", "Understand, INSIGHT, Taiji", "What the Witness?, and Tametsi."),
+ ("", "In a race", "It's survival of the Witnesst"),
+ ("", "This hint has been removed", "We apologize for your inconvenience."),
+ ("", "O-----------", ""),
+ ("Circle is draw", "Square is separate", "Line is win"),
+ ("Circle is draw", "Star is pair", "Line is win"),
+ ("Circle is draw", "Circle is copy", "Line is win"),
+ ("Circle is draw", "Dot is eat", "Line is win"),
+ ("Circle is start", "Walk is draw", "Line is win"),
+ ("Circle is start", "Line is win", "Witness is you"),
+ ("Can't find any items?", "Consider a relaxing boat trip", "around the island"),
+ ("", "Don't forget to like, comment, and subscribe", ""),
+ ("Ah crap, gimme a second.", "[papers rustling]", "Sorry, nothing."),
+ ("", "Trying to get a hint?", "Too bad."),
+ ("", "Here's a hint:", "Get good at the game."),
+ ("", "I'm still not entirely sure", "what we're witnessing here."),
+ ("Have you found a red page yet?", "No?", "Then have you found a blue page?"),
+ (
+ "And here we see the Witness player,",
+ "seeking answers where there are none-",
+ "Did someone turn on the loudspeaker?"
+ ),
+ (
+ "Hints suggested by:",
+ "IHNN, Beaker, MrPokemon11, Ember, TheM8, NewSoupVi,",
+ "KF, Yoshi348, Berserker, BowlinJim, oddGarrett, Pink Switch."
+ ),
+]
+
+
+def get_always_hint_items(world: MultiWorld, player: int):
+ priority = [
+ "Boat",
+ "Mountain Bottom Floor Final Room Entry (Door)",
+ "Caves Mountain Shortcut (Door)",
+ "Caves Swamp Shortcut (Door)",
+ "Caves Exits to Main Island",
+ ]
+
+ difficulty = get_option_value(world, player, "puzzle_randomization")
+ discards = is_option_enabled(world, player, "shuffle_discards")
+
+ if discards:
+ if difficulty == 1:
+ priority.append("Arrows")
+ else:
+ priority.append("Triangles")
+
+ return priority
+
+
+def get_always_hint_locations(world: MultiWorld, player: int):
+ return {
+ "Swamp Purple Underwater",
+ "Shipwreck Vault Box",
+ "Challenge Vault Box",
+ "Mountain Bottom Floor Discard",
+ }
+
+
+def get_priority_hint_items(world: MultiWorld, player: int):
+ priority = {
+ "Negative Shapers",
+ "Sound Dots",
+ "Colored Dots",
+ "Stars + Same Colored Symbol",
+ "Swamp Entry (Panel)",
+ "Swamp Laser Shortcut (Door)",
+ }
+
+ if is_option_enabled(world, player, "shuffle_lasers"):
+ lasers = {
+ "Symmetry Laser",
+ "Desert Laser",
+ "Town Laser",
+ "Keep Laser",
+ "Swamp Laser",
+ "Treehouse Laser",
+ "Monastery Laser",
+ "Jungle Laser",
+ "Quarry Laser",
+ "Bunker Laser",
+ "Shadows Laser",
+ }
+
+ if get_option_value(world, player, "doors") >= 2:
+ priority.add("Desert Laser")
+ lasers.remove("Desert Laser")
+ priority.update(world.random.sample(lasers, 2))
+
+ else:
+ priority.update(world.random.sample(lasers, 3))
+
+ return priority
+
+
+def get_priority_hint_locations(world: MultiWorld, player: int):
+ return {
+ "Town RGB Room Left",
+ "Town RGB Room Right",
+ "Treehouse Green Bridge 7",
+ "Treehouse Green Bridge Discard",
+ "Shipwreck Discard",
+ "Desert Vault Box",
+ "Mountainside Vault Box",
+ "Mountainside Discard",
+ }
+
+
+def make_hint_from_item(world: MultiWorld, player: int, item: str):
+ location_obj = world.find_item(item, player).item.location
+ location_name = location_obj.name
+ if location_obj.player != player:
+ location_name += " (" + world.get_player_name(location_obj.player) + ")"
+
+ return location_name, item, location_obj.address if(location_obj.player == player) else -1
+
+
+def make_hint_from_location(world: MultiWorld, player: int, location: str):
+ location_obj = world.get_location(location, player)
+ item_obj = world.get_location(location, player).item
+ item_name = item_obj.name
+ if item_obj.player != player:
+ item_name += " (" + world.get_player_name(item_obj.player) + ")"
+
+ return location, item_name, location_obj.address if(location_obj.player == player) else -1
+
+
+def make_hints(world: MultiWorld, player: int, hint_amount: int):
+ hints = list()
+
+ prog_items_in_this_world = {
+ item.name for item in world.get_items()
+ if item.player == player and item.code and item.advancement
+ }
+ loc_in_this_world = {
+ location.name for location in world.get_locations()
+ if location.player == player and not location.event
+ }
+
+ always_locations = [
+ location for location in get_always_hint_locations(world, player)
+ if location in loc_in_this_world
+ ]
+ always_items = [
+ item for item in get_always_hint_items(world, player)
+ if item in prog_items_in_this_world
+ ]
+ priority_locations = [
+ location for location in get_priority_hint_locations(world, player)
+ if location in loc_in_this_world
+ ]
+ priority_items = [
+ item for item in get_priority_hint_items(world, player)
+ if item in prog_items_in_this_world
+ ]
+
+ always_hint_pairs = dict()
+
+ for item in always_items:
+ hint_pair = make_hint_from_item(world, player, item)
+ always_hint_pairs[hint_pair[0]] = (hint_pair[1], True, hint_pair[2])
+
+ for location in always_locations:
+ hint_pair = make_hint_from_location(world, player, location)
+ always_hint_pairs[hint_pair[0]] = (hint_pair[1], False, hint_pair[2])
+
+ priority_hint_pairs = dict()
+
+ for item in priority_items:
+ hint_pair = make_hint_from_item(world, player, item)
+ priority_hint_pairs[hint_pair[0]] = (hint_pair[1], True, hint_pair[2])
+
+ for location in priority_locations:
+ hint_pair = make_hint_from_location(world, player, location)
+ priority_hint_pairs[hint_pair[0]] = (hint_pair[1], False, hint_pair[2])
+
+ for loc, item in always_hint_pairs.items():
+ if item[1]:
+ hints.append((item[0], "can be found at", loc, item[2]))
+ else:
+ hints.append((loc, "contains", item[0], item[2]))
+
+ next_random_hint_is_item = world.random.randint(0, 2)
+
+ prog_items_in_this_world = sorted(list(prog_items_in_this_world))
+ locations_in_this_world = sorted(list(loc_in_this_world))
+
+ world.random.shuffle(prog_items_in_this_world)
+ world.random.shuffle(locations_in_this_world)
+
+ while len(hints) < hint_amount:
+ if priority_hint_pairs:
+ loc = world.random.choice(list(priority_hint_pairs.keys()))
+ item = priority_hint_pairs[loc]
+ del priority_hint_pairs[loc]
+
+ if item[1]:
+ hints.append((item[0], "can be found at", loc, item[2]))
+ else:
+ hints.append((loc, "contains", item[0], item[2]))
+ continue
+
+ if next_random_hint_is_item:
+ if not prog_items_in_this_world:
+ next_random_hint_is_item = not next_random_hint_is_item
+ continue
+
+ hint = make_hint_from_item(world, player, prog_items_in_this_world.pop())
+ hints.append((hint[1], "can be found at", hint[0], hint[2]))
+ else:
+ hint = make_hint_from_location(world, player, locations_in_this_world.pop())
+ hints.append((hint[0], "contains", hint[1], hint[2]))
+
+ next_random_hint_is_item = not next_random_hint_is_item
+
+ return hints
+
+
+def generate_joke_hints(world: MultiWorld, amount: int):
+ return [(x, y, z, -1) for (x, y, z) in world.random.sample(joke_hints, amount)]
diff --git a/worlds/witness/items.py b/worlds/witness/items.py
index 9ffd5a1173..cbb1554096 100644
--- a/worlds/witness/items.py
+++ b/worlds/witness/items.py
@@ -2,6 +2,7 @@
Defines progression, junk and event items for The Witness
"""
import copy
+from collections import defaultdict
from typing import Dict, NamedTuple, Optional, Set
from BaseClasses import Item, MultiWorld
@@ -96,6 +97,10 @@ class WitnessPlayerItems:
Class that defines Items for a single world
"""
+ @staticmethod
+ def code(item_name: str):
+ return StaticWitnessItems.ALL_ITEM_TABLE[item_name].code
+
def __init__(self, locat: WitnessPlayerLocations, world: MultiWorld, player: int, player_logic: WitnessPlayerLogic):
"""Adds event items after logic changes due to options"""
self.EVENT_ITEM_TABLE = dict()
@@ -105,6 +110,8 @@ class WitnessPlayerItems:
self.ITEM_ID_TO_DOOR_HEX = dict()
self.DOORS = set()
+ self.PROG_ITEM_AMOUNTS = defaultdict(lambda: 1)
+
self.SYMBOLS_NOT_IN_THE_GAME = set()
self.EXTRA_AMOUNTS = {
@@ -118,8 +125,17 @@ class WitnessPlayerItems:
if item in StaticWitnessLogic.ALL_SYMBOL_ITEMS:
self.SYMBOLS_NOT_IN_THE_GAME.add(StaticWitnessItems.ALL_ITEM_TABLE[item[0]].code)
else:
+ if item[0] in StaticWitnessLogic.PROGRESSIVE_TO_ITEMS:
+ self.PROG_ITEM_AMOUNTS[item[0]] = len(player_logic.MULTI_LISTS[item[0]])
+
self.PROGRESSION_TABLE[item[0]] = self.ITEM_TABLE[item[0]]
+ self.MULTI_LISTS_BY_CODE = dict()
+
+ for item in self.PROG_ITEM_AMOUNTS:
+ multi_list = player_logic.MULTI_LISTS[item]
+ self.MULTI_LISTS_BY_CODE[self.code(item)] = [self.code(single_item) for single_item in multi_list]
+
for entity_hex, items in player_logic.DOOR_ITEMS_BY_ID.items():
entity_hex_int = int(entity_hex, 16)
@@ -138,11 +154,11 @@ class WitnessPlayerItems:
if doors and symbols:
self.GOOD_ITEMS = [
- "Dots", "Black/White Squares", "Symmetry"
+ "Progressive Dots", "Black/White Squares", "Symmetry"
]
elif symbols:
self.GOOD_ITEMS = [
- "Dots", "Black/White Squares", "Stars",
+ "Progressive Dots", "Black/White Squares", "Progressive Stars",
"Shapers", "Symmetry"
]
@@ -151,6 +167,10 @@ class WitnessPlayerItems:
if not is_option_enabled(world, player, "disable_non_randomized_puzzles"):
self.GOOD_ITEMS.append("Colored Squares")
+ self.GOOD_ITEMS = [
+ StaticWitnessLogic.ITEMS_TO_PROGRESSIVE.get(item, item) for item in self.GOOD_ITEMS
+ ]
+
for event_location in locat.EVENT_LOCATION_TABLE:
location = player_logic.EVENT_ITEM_PAIRS[event_location]
self.EVENT_ITEM_TABLE[location] = ItemData(None, True, True)
diff --git a/worlds/witness/locations.py b/worlds/witness/locations.py
index ea0728b16c..99bd3ece83 100644
--- a/worlds/witness/locations.py
+++ b/worlds/witness/locations.py
@@ -3,7 +3,8 @@ Defines constants for different types of locations in the game
"""
from .Options import is_option_enabled, get_option_value
-from .player_logic import StaticWitnessLogic, WitnessPlayerLogic
+from .player_logic import WitnessPlayerLogic
+from .static_logic import StaticWitnessLogic
class StaticWitnessLocations:
@@ -52,8 +53,6 @@ class StaticWitnessLocations:
"Desert Light Room 3",
"Desert Pond Room 5",
"Desert Flood Room 6",
- "Desert Final Bent 3",
- "Desert Final Hexagonal",
"Desert Laser Panel",
"Quarry Mill Lower Row 6",
@@ -247,6 +246,10 @@ class WitnessPlayerLocations:
StaticWitnessLocations.GENERAL_LOCATIONS
)
+ if get_option_value(world, player, "puzzle_randomization") == 1:
+ self.CHECK_LOCATIONS.remove("Keep Pressure Plates 4")
+ self.CHECK_LOCATIONS.add("Keep Pressure Plates 2")
+
doors = get_option_value(world, player, "shuffle_doors") >= 2
earlyutm = is_option_enabled(world, player, "early_secret_area")
victory = get_option_value(world, player, "victory_condition")
diff --git a/worlds/witness/player_logic.py b/worlds/witness/player_logic.py
index 4840ea0a5d..a58ad8ef7d 100644
--- a/worlds/witness/player_logic.py
+++ b/worlds/witness/player_logic.py
@@ -39,7 +39,7 @@ class WitnessPlayerLogic:
if panel_hex in self.COMPLETELY_DISABLED_CHECKS:
return frozenset()
- check_obj = StaticWitnessLogic.CHECKS_BY_HEX[panel_hex]
+ check_obj = self.REFERENCE_LOGIC.CHECKS_BY_HEX[panel_hex]
these_items = frozenset({frozenset()})
@@ -47,17 +47,21 @@ class WitnessPlayerLogic:
these_items = self.DEPENDENT_REQUIREMENTS_BY_HEX[panel_hex]["items"]
these_items = frozenset({
- subset.intersection(self.PROG_ITEMS_ACTUALLY_IN_THE_GAME)
+ subset.intersection(self.THEORETICAL_ITEMS_NO_MULTI)
for subset in these_items
})
+ for subset in these_items:
+ self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI.update(subset)
+
if panel_hex in self.DOOR_ITEMS_BY_ID:
door_items = frozenset({frozenset([item]) for item in self.DOOR_ITEMS_BY_ID[panel_hex]})
all_options = set()
- for items_option in these_items:
- for dependentItem in door_items:
+ for dependentItem in door_items:
+ self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI.update(dependentItem)
+ for items_option in these_items:
all_options.add(items_option.union(dependentItem))
if panel_hex != "0x28A0D":
@@ -76,11 +80,11 @@ class WitnessPlayerLogic:
dependent_items_for_option = frozenset({frozenset()})
for option_panel in option:
- dep_obj = StaticWitnessLogic.CHECKS_BY_HEX.get(option_panel)
+ dep_obj = self.REFERENCE_LOGIC.CHECKS_BY_HEX.get(option_panel)
if option_panel in self.COMPLETELY_DISABLED_CHECKS:
new_items = frozenset()
- elif option_panel in {"7 Lasers", "11 Lasers"}:
+ elif option_panel in {"7 Lasers", "11 Lasers", "PP2 Weirdness"}:
new_items = frozenset({frozenset([option_panel])})
# If a panel turns on when a panel in a different region turns on,
# the latter panel will be an "event panel", unless it ends up being
@@ -113,20 +117,26 @@ class WitnessPlayerLogic:
"""Makes a single logic adjustment based on additional logic file"""
if adj_type == "Items":
- if line not in StaticWitnessItems.ALL_ITEM_TABLE:
- raise RuntimeError("Item \"" + line + "\" does not exit.")
+ line_split = line.split(" - ")
+ item = line_split[0]
- self.PROG_ITEMS_ACTUALLY_IN_THE_GAME.add(line)
+ if item not in StaticWitnessItems.ALL_ITEM_TABLE:
+ raise RuntimeError("Item \"" + item + "\" does not exit.")
- if line in StaticWitnessLogic.ALL_DOOR_ITEMS_AS_DICT:
- panel_hexes = StaticWitnessLogic.ALL_DOOR_ITEMS_AS_DICT[line][2]
+ self.THEORETICAL_ITEMS.add(item)
+ self.THEORETICAL_ITEMS_NO_MULTI.update(StaticWitnessLogic.PROGRESSIVE_TO_ITEMS.get(item, [item]))
+
+ if item in StaticWitnessLogic.ALL_DOOR_ITEMS_AS_DICT:
+ panel_hexes = StaticWitnessLogic.ALL_DOOR_ITEMS_AS_DICT[item][2]
for panel_hex in panel_hexes:
- self.DOOR_ITEMS_BY_ID.setdefault(panel_hex, set()).add(line)
+ self.DOOR_ITEMS_BY_ID.setdefault(panel_hex, set()).add(item)
return
if adj_type == "Remove Items":
- self.PROG_ITEMS_ACTUALLY_IN_THE_GAME.discard(line)
+ self.THEORETICAL_ITEMS.discard(line)
+ for i in StaticWitnessLogic.PROGRESSIVE_TO_ITEMS.get(line, [line]):
+ self.THEORETICAL_ITEMS_NO_MULTI.discard(i)
if line in StaticWitnessLogic.ALL_DOOR_ITEMS_AS_DICT:
panel_hexes = StaticWitnessLogic.ALL_DOOR_ITEMS_AS_DICT[line][2]
@@ -265,11 +275,22 @@ class WitnessPlayerLogic:
self.REQUIREMENTS_BY_HEX[check_hex] = indep_requirement
+ for item in self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI:
+ if item not in self.THEORETICAL_ITEMS:
+ corresponding_multi = StaticWitnessLogic.ITEMS_TO_PROGRESSIVE[item]
+ self.PROG_ITEMS_ACTUALLY_IN_THE_GAME.add(corresponding_multi)
+ multi_list = StaticWitnessLogic.PROGRESSIVE_TO_ITEMS[StaticWitnessLogic.ITEMS_TO_PROGRESSIVE[item]]
+ multi_list = [item for item in multi_list if item in self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI]
+ self.MULTI_AMOUNTS[item] = multi_list.index(item) + 1
+ self.MULTI_LISTS[corresponding_multi] = multi_list
+ else:
+ self.PROG_ITEMS_ACTUALLY_IN_THE_GAME.add(item)
+
def make_event_item_pair(self, panel):
"""
Makes a pair of an event panel and its event item
"""
- name = StaticWitnessLogic.CHECKS_BY_HEX[panel]["checkName"] + " Solved"
+ name = self.REFERENCE_LOGIC.CHECKS_BY_HEX[panel]["checkName"] + " Solved"
pair = (name, self.EVENT_ITEM_NAMES[panel])
return pair
@@ -287,7 +308,7 @@ class WitnessPlayerLogic:
if panel == "TrueOneWay":
continue
- if StaticWitnessLogic.CHECKS_BY_HEX[panel]["region"]["name"] != region_name:
+ if self.REFERENCE_LOGIC.CHECKS_BY_HEX[panel]["region"]["name"] != region_name:
self.EVENT_PANELS_FROM_REGIONS.add(panel)
self.EVENT_PANELS.update(self.EVENT_PANELS_FROM_PANELS)
@@ -306,12 +327,24 @@ class WitnessPlayerLogic:
self.EVENT_PANELS_FROM_PANELS = set()
self.EVENT_PANELS_FROM_REGIONS = set()
+ self.THEORETICAL_ITEMS = set()
+ self.THEORETICAL_ITEMS_NO_MULTI = set()
+ self.MULTI_AMOUNTS = dict()
+ self.MULTI_LISTS = dict()
+ self.PROG_ITEMS_ACTUALLY_IN_THE_GAME_NO_MULTI = set()
self.PROG_ITEMS_ACTUALLY_IN_THE_GAME = set()
self.DOOR_ITEMS_BY_ID = dict()
self.STARTING_INVENTORY = set()
- self.CONNECTIONS_BY_REGION_NAME = copy.copy(StaticWitnessLogic.STATIC_CONNECTIONS_BY_REGION_NAME)
- self.DEPENDENT_REQUIREMENTS_BY_HEX = copy.copy(StaticWitnessLogic.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX)
+ self.DIFFICULTY = get_option_value(world, player, "puzzle_randomization")
+
+ if self.DIFFICULTY == 0:
+ self.REFERENCE_LOGIC = StaticWitnessLogic.sigma_normal
+ elif self.DIFFICULTY == 1:
+ self.REFERENCE_LOGIC = StaticWitnessLogic.sigma_expert
+
+ self.CONNECTIONS_BY_REGION_NAME = copy.copy(self.REFERENCE_LOGIC.STATIC_CONNECTIONS_BY_REGION_NAME)
+ self.DEPENDENT_REQUIREMENTS_BY_HEX = copy.copy(self.REFERENCE_LOGIC.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX)
self.REQUIREMENTS_BY_HEX = dict()
# Determining which panels need to be events is a difficult process.
@@ -333,6 +366,7 @@ class WitnessPlayerLogic:
"0x019DC": "Keep Hedges 2 Knowledge",
"0x019E7": "Keep Hedges 3 Knowledge",
"0x01D3F": "Keep Laser Panel (Pressure Plates) Activates",
+ "0x01BE9": "Keep Laser Panel (Pressure Plates) Activates",
"0x09F7F": "Mountain Access",
"0x0367C": "Quarry Laser Mill Requirement Met",
"0x009A1": "Swamp Between Bridges Far 1 Activates",
@@ -374,6 +408,9 @@ class WitnessPlayerLogic:
"0x0356B": "Challenge Video Pattern Knowledge",
"0x0A15F": "Desert Laser Panel Shutters Open (1)",
"0x012D7": "Desert Laser Panel Shutters Open (2)",
+ "0x03613": "Treehouse Orange Bridge 13 Turns On",
+ "0x17DEC": "Treehouse Laser House Access Requirement",
+ "0x03C08": "Town Church Entry Opens",
}
self.ALWAYS_EVENT_NAMES_BY_HEX = {
diff --git a/worlds/witness/regions.py b/worlds/witness/regions.py
index 143f3e77e5..e17acf7343 100644
--- a/worlds/witness/regions.py
+++ b/worlds/witness/regions.py
@@ -4,7 +4,8 @@ and connects them with the proper requirements
"""
from BaseClasses import MultiWorld, Entrance
-from . import StaticWitnessLogic
+from .static_logic import StaticWitnessLogic
+from .Options import get_option_value
from .locations import WitnessPlayerLocations
from .player_logic import WitnessPlayerLogic
@@ -39,7 +40,7 @@ class WitnessRegions:
connection = Entrance(
player,
- source + " to " + target + " via " + str(panel_hex_to_solve_set),
+ source + " to " + target,
source_region
)
@@ -58,16 +59,23 @@ class WitnessRegions:
create_region(world, player, 'Menu', self.locat, None, ["The Splashscreen?"]),
]
+ difficulty = get_option_value(world, player, "puzzle_randomization")
+
+ if difficulty == 1:
+ reference_logic = StaticWitnessLogic.sigma_expert
+ else:
+ reference_logic = StaticWitnessLogic.sigma_normal
+
all_locations = set()
- for region_name, region in StaticWitnessLogic.ALL_REGIONS_BY_NAME.items():
+ for region_name, region in reference_logic.ALL_REGIONS_BY_NAME.items():
locations_for_this_region = [
- StaticWitnessLogic.CHECKS_BY_HEX[panel]["checkName"] for panel in region["panels"]
- if StaticWitnessLogic.CHECKS_BY_HEX[panel]["checkName"] in self.locat.CHECK_LOCATION_TABLE
+ reference_logic.CHECKS_BY_HEX[panel]["checkName"] for panel in region["panels"]
+ if reference_logic.CHECKS_BY_HEX[panel]["checkName"] in self.locat.CHECK_LOCATION_TABLE
]
locations_for_this_region += [
- StaticWitnessLogic.CHECKS_BY_HEX[panel]["checkName"] + " Solved" for panel in region["panels"]
- if StaticWitnessLogic.CHECKS_BY_HEX[panel]["checkName"] + " Solved" in self.locat.EVENT_LOCATION_TABLE
+ reference_logic.CHECKS_BY_HEX[panel]["checkName"] + " Solved" for panel in region["panels"]
+ if reference_logic.CHECKS_BY_HEX[panel]["checkName"] + " Solved" in self.locat.EVENT_LOCATION_TABLE
]
all_locations = all_locations | set(locations_for_this_region)
@@ -76,7 +84,7 @@ class WitnessRegions:
create_region(world, player, region_name, self.locat, locations_for_this_region)
]
- for region_name, region in StaticWitnessLogic.ALL_REGIONS_BY_NAME.items():
+ for region_name, region in reference_logic.ALL_REGIONS_BY_NAME.items():
for connection in player_logic.CONNECTIONS_BY_REGION_NAME[region_name]:
if connection[0] == "Entry":
continue
@@ -87,7 +95,7 @@ class WitnessRegions:
for subset in connection[1]:
if all({panel in player_logic.DOOR_ITEMS_BY_ID for panel in subset}):
- if all({StaticWitnessLogic.CHECKS_BY_HEX[panel]["id"] is None for panel in subset}):
+ if all({reference_logic.CHECKS_BY_HEX[panel]["id"] is None for panel in subset}):
self.connect(world, player, connection[0], region_name, player_logic, frozenset({subset}))
self.connect(world, player, region_name, connection[0], player_logic, connection[1])
diff --git a/worlds/witness/rules.py b/worlds/witness/rules.py
index 2b9888b361..02efc5ef4c 100644
--- a/worlds/witness/rules.py
+++ b/worlds/witness/rules.py
@@ -91,13 +91,64 @@ class WitnessLogic(LogicMixin):
if not self._witness_has_lasers(world, player, get_option_value(world, player, "challenge_lasers")):
valid_option = False
break
+ elif item == "PP2 Weirdness":
+ hedge_2_access = (
+ self.can_reach("Keep 2nd Maze to Keep", "Entrance", player)
+ or self.can_reach("Keep to Keep 2nd Maze", "Entrance", player)
+ )
+
+ hedge_3_access = (
+ self.can_reach("Keep 3rd Maze to Keep", "Entrance", player)
+ or self.can_reach("Keep 2nd Maze to Keep 3rd Maze", "Entrance", player)
+ and hedge_2_access
+ )
+
+ hedge_4_access = (
+ self.can_reach("Keep 4th Maze to Keep", "Entrance", player)
+ or self.can_reach("Keep 3rd Maze to Keep 4th Maze", "Entrance", player)
+ and hedge_3_access
+ )
+
+ hedge_access = (
+ self.can_reach("Keep 4th Maze to Keep Tower", "Entrance", player)
+ and self.can_reach("Keep", "Region", player)
+ and hedge_4_access
+ )
+
+ backwards_to_fourth = (
+ self.can_reach("Keep", "Region", player)
+ and self.can_reach("Keep 4th Pressure Plate to Keep Tower", "Entrance", player)
+ and (
+ self.can_reach("Keep Tower to Keep", "Entrance", player)
+ or hedge_access
+ )
+ )
+
+ backwards_access = (
+ self.can_reach("Keep 3rd Pressure Plate to Keep 4th Pressure Plate", "Entrance", player)
+ and backwards_to_fourth
+
+ or self.can_reach("Main Island", "Region", player)
+ and self.can_reach("Keep 4th Pressure Plate to Shadows", "Entrance", player)
+ )
+
+ front_access = (
+ self.can_reach("Keep to Keep 2nd Pressure Plate", 'Entrance', player)
+ and self.can_reach("Keep", "Region", player)
+ )
+
+ if not (front_access and backwards_access):
+ valid_option = False
+ break
elif item in player_logic.EVENT_PANELS:
if not self._witness_can_solve_panel(item, world, player, player_logic, locat):
valid_option = False
break
elif not self.has(item, player):
- valid_option = False
- break
+ prog_dict = StaticWitnessLogic.ITEMS_TO_PROGRESSIVE
+ if not (item in prog_dict and self.has(prog_dict[item], player, player_logic.MULTI_AMOUNTS[item])):
+ valid_option = False
+ break
if valid_option:
return True
diff --git a/worlds/witness/settings/Audio_Logs.txt b/worlds/witness/settings/Audio_Logs.txt
new file mode 100644
index 0000000000..27ce126778
--- /dev/null
+++ b/worlds/witness/settings/Audio_Logs.txt
@@ -0,0 +1,49 @@
+0x3C0F7
+0x3C0FD
+0x32A00
+0x3C0FE
+0x3C100
+0x3C0F4
+0x3C102
+0x3C10D
+0x3C10E
+0x3C10B
+0x0074F
+0x012C7
+0x329FF
+0x3C106
+0x33AFF
+0x011F9
+0x00763
+0x32A08
+0x3C101
+0x3C0FF
+0x3C103
+0x00A0F
+0x339A9
+0x015C0
+0x33B36
+0x3C10C
+0x32A0E
+0x329FE
+0x32A07
+0x00761
+0x3C109
+0x33B37
+0x3C107
+0x3C0F3
+0x015B7
+0x3C10A
+0x32A0A
+0x015C1
+0x3C12A
+0x3C104
+0x3C105
+0x339A8
+0x0050A
+0x338BD
+0x3C135
+0x338C9
+0x338D7
+0x338C1
+0x338CA
\ No newline at end of file
diff --git a/worlds/witness/settings/Disable_Unrandomized.txt b/worlds/witness/settings/Disable_Unrandomized.txt
index 4f26e3136a..43c69409fc 100644
--- a/worlds/witness/settings/Disable_Unrandomized.txt
+++ b/worlds/witness/settings/Disable_Unrandomized.txt
@@ -52,6 +52,10 @@ Disabled Locations:
0x019E7 (Keep Hedge Maze 3)
0x01A0F (Keep Hedge Maze 4)
0x0360E (Laser Hedges)
+0x03307 (First Gate)
+0x03313 (Second Gate)
+0x0C128 (Entry Inner)
+0x0C153 (Entry Outer)
0x00B10 (Monastery Entry Left)
0x00C92 (Monastery Entry Right)
0x00290 (Monastery Outside 1)
@@ -83,6 +87,10 @@ Disabled Locations:
0x15ADD (River Outside Vault)
0x03702 (River Vault Box)
0x17CAA (Monastery Shortcut Panel)
+0x0C2A4 (Bunker Entry)
+0x17C79 (Tinted Glass Door)
+0x0C2A3 (UV Room Entry)
+0x0A08D (Elevator Room Entry)
0x17C2E (Door to Bunker)
0x09F7D (Bunker Intro Left 1)
0x09FDC (Bunker Intro Left 2)
diff --git a/worlds/witness/settings/Symbol_Shuffle.txt b/worlds/witness/settings/Symbol_Shuffle.txt
index d03391f5c5..3d0342f5e2 100644
--- a/worlds/witness/settings/Symbol_Shuffle.txt
+++ b/worlds/witness/settings/Symbol_Shuffle.txt
@@ -1,5 +1,6 @@
Items:
-Dots
+Arrows
+Progressive Dots
Colored Dots
Sound Dots
Symmetry
@@ -8,7 +9,6 @@ Eraser
Shapers
Rotated Shapers
Negative Shapers
-Stars
-Stars + Same Colored Symbol
+Progressive Stars
Black/White Squares
Colored Squares
\ No newline at end of file
diff --git a/worlds/witness/static_logic.py b/worlds/witness/static_logic.py
index 646957c462..18a0e19cff 100644
--- a/worlds/witness/static_logic.py
+++ b/worlds/witness/static_logic.py
@@ -1,73 +1,15 @@
import os
-from .utils import define_new_region, parse_lambda
+from .utils import define_new_region, parse_lambda, lazy
-class StaticWitnessLogic:
- ALL_SYMBOL_ITEMS = set()
- ALL_DOOR_ITEMS = set()
- ALL_DOOR_ITEMS_AS_DICT = dict()
- ALL_USEFULS = set()
- ALL_TRAPS = set()
- ALL_BOOSTS = set()
- CONNECTIONS_TO_SEVER_BY_DOOR_HEX = dict()
-
- EVENT_PANELS_FROM_REGIONS = set()
-
- # All regions with a list of panels in them and the connections to other regions, before logic adjustments
- ALL_REGIONS_BY_NAME = dict()
- STATIC_CONNECTIONS_BY_REGION_NAME = dict()
-
- CHECKS_BY_HEX = dict()
- CHECKS_BY_NAME = dict()
- STATIC_DEPENDENT_REQUIREMENTS_BY_HEX = dict()
-
- def parse_items(self):
- """
- Parses currently defined items from WitnessItems.txt
- """
-
- path = os.path.join(os.path.dirname(__file__), "WitnessItems.txt")
- with open(path, "r", encoding="utf-8") as file:
- current_set = self.ALL_SYMBOL_ITEMS
-
- for line in file.readlines():
- line = line.strip()
-
- if line == "Progression:":
- current_set = self.ALL_SYMBOL_ITEMS
- continue
- if line == "Boosts:":
- current_set = self.ALL_BOOSTS
- continue
- if line == "Traps:":
- current_set = self.ALL_TRAPS
- continue
- if line == "Usefuls:":
- current_set = self.ALL_USEFULS
- continue
- if line == "Doors:":
- current_set = self.ALL_DOOR_ITEMS
- continue
- if line == "":
- continue
-
- line_split = line.split(" - ")
-
- if current_set is self.ALL_USEFULS:
- current_set.add((line_split[1], int(line_split[0]), line_split[2] == "True"))
- elif current_set is self.ALL_DOOR_ITEMS:
- new_door = (line_split[1], int(line_split[0]), frozenset(line_split[2].split(",")))
- current_set.add(new_door)
- self.ALL_DOOR_ITEMS_AS_DICT[line_split[1]] = new_door
- else:
- current_set.add((line_split[1], int(line_split[0])))
-
- def read_logic_file(self):
+class StaticWitnessLogicObj:
+ def read_logic_file(self, file_path="WitnessLogic.txt"):
"""
Reads the logic file and does the initial population of data structures
"""
- path = os.path.join(os.path.dirname(__file__), "WitnessLogic.txt")
+ path = os.path.join(os.path.dirname(__file__), file_path)
+
with open(path, "r", encoding="utf-8") as file:
current_region = dict()
@@ -157,6 +99,99 @@ class StaticWitnessLogic:
current_region["panels"].add(check_hex)
+ def __init__(self, file_path="WitnessLogic.txt"):
+ # All regions with a list of panels in them and the connections to other regions, before logic adjustments
+ self.ALL_REGIONS_BY_NAME = dict()
+ self.STATIC_CONNECTIONS_BY_REGION_NAME = dict()
+
+ self.CHECKS_BY_HEX = dict()
+ self.CHECKS_BY_NAME = dict()
+ self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX = dict()
+
+ self.read_logic_file(file_path)
+
+
+class StaticWitnessLogic:
+ ALL_SYMBOL_ITEMS = set()
+ ITEMS_TO_PROGRESSIVE = dict()
+ PROGRESSIVE_TO_ITEMS = dict()
+ ALL_DOOR_ITEMS = set()
+ ALL_DOOR_ITEMS_AS_DICT = dict()
+ ALL_USEFULS = set()
+ ALL_TRAPS = set()
+ ALL_BOOSTS = set()
+ CONNECTIONS_TO_SEVER_BY_DOOR_HEX = dict()
+
+ ALL_REGIONS_BY_NAME = dict()
+ STATIC_CONNECTIONS_BY_REGION_NAME = dict()
+
+ CHECKS_BY_HEX = dict()
+ CHECKS_BY_NAME = dict()
+ STATIC_DEPENDENT_REQUIREMENTS_BY_HEX = dict()
+
+ def parse_items(self):
+ """
+ Parses currently defined items from WitnessItems.txt
+ """
+
+ path = os.path.join(os.path.dirname(__file__), "WitnessItems.txt")
+ with open(path, "r", encoding="utf-8") as file:
+ current_set = self.ALL_SYMBOL_ITEMS
+
+ for line in file.readlines():
+ line = line.strip()
+
+ if line == "Progression:":
+ current_set = self.ALL_SYMBOL_ITEMS
+ continue
+ if line == "Boosts:":
+ current_set = self.ALL_BOOSTS
+ continue
+ if line == "Traps:":
+ current_set = self.ALL_TRAPS
+ continue
+ if line == "Usefuls:":
+ current_set = self.ALL_USEFULS
+ continue
+ if line == "Doors:":
+ current_set = self.ALL_DOOR_ITEMS
+ continue
+ if line == "":
+ continue
+
+ line_split = line.split(" - ")
+
+ if current_set is self.ALL_USEFULS:
+ current_set.add((line_split[1], int(line_split[0]), line_split[2] == "True"))
+ elif current_set is self.ALL_DOOR_ITEMS:
+ new_door = (line_split[1], int(line_split[0]), frozenset(line_split[2].split(",")))
+ current_set.add(new_door)
+ self.ALL_DOOR_ITEMS_AS_DICT[line_split[1]] = new_door
+ else:
+ if len(line_split) > 2:
+ progressive_items = line_split[2].split(",")
+ for i, value in enumerate(progressive_items):
+ self.ITEMS_TO_PROGRESSIVE[value] = line_split[1]
+ self.PROGRESSIVE_TO_ITEMS[line_split[1]] = progressive_items
+ current_set.add((line_split[1], int(line_split[0])))
+ continue
+ current_set.add((line_split[1], int(line_split[0])))
+
+ @lazy
+ def sigma_expert(self) -> StaticWitnessLogicObj:
+ return StaticWitnessLogicObj("WitnessLogicExpert.txt")
+
+ @lazy
+ def sigma_normal(self) -> StaticWitnessLogicObj:
+ return StaticWitnessLogicObj("WitnessLogic.txt")
+
def __init__(self):
self.parse_items()
- self.read_logic_file()
+
+ self.ALL_REGIONS_BY_NAME.update(self.sigma_normal.ALL_REGIONS_BY_NAME)
+ self.STATIC_CONNECTIONS_BY_REGION_NAME.update(self.sigma_normal.STATIC_CONNECTIONS_BY_REGION_NAME)
+
+ self.CHECKS_BY_HEX.update(self.sigma_normal.CHECKS_BY_HEX)
+ self.CHECKS_BY_NAME.update(self.sigma_normal.CHECKS_BY_NAME)
+ self.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX.update(self.sigma_normal.STATIC_DEPENDENT_REQUIREMENTS_BY_HEX)
+
diff --git a/worlds/witness/utils.py b/worlds/witness/utils.py
index 809b2b1c3d..a69dabbb7c 100644
--- a/worlds/witness/utils.py
+++ b/worlds/witness/utils.py
@@ -89,6 +89,22 @@ def parse_lambda(lambda_string):
return lambda_set
+class lazy(object):
+ def __init__(self, func, name=None):
+ self.func = func
+ self.name = name if name is not None else func.__name__
+ self.__doc__ = func.__doc__
+
+ def __get__(self, instance, class_):
+ if instance is None:
+ res = self.func(class_)
+ setattr(class_, self.name, res)
+ return res
+ res = self.func(instance)
+ setattr(instance, self.name, res)
+ return res
+
+
def get_adjustment_file(adjustment_file):
path = os.path.join(os.path.dirname(__file__), adjustment_file)
@@ -134,3 +150,8 @@ def get_doors_max_list():
@cache_argsless
def get_laser_shuffle():
return get_adjustment_file("settings/Laser_Shuffle.txt")
+
+
+@cache_argsless
+def get_audio_logs():
+ return get_adjustment_file("settings/Audio_Logs.txt")