mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-14 11:33:47 -07:00
Compare commits
25 Commits
0.4.3
...
misc-webho
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c06d1193a | ||
|
|
6b48f9aac5 | ||
|
|
bc11c9dfd4 | ||
|
|
24403eba1b | ||
|
|
e377068d1f | ||
|
|
c7c94eebeb | ||
|
|
9d38725688 | ||
|
|
18bf7425c4 | ||
|
|
17127a4117 | ||
|
|
5d9b47355e | ||
|
|
f9761ad4e5 | ||
|
|
485aa23afd | ||
|
|
e08deff6f9 | ||
|
|
d5d630dcf0 | ||
|
|
58b696e986 | ||
|
|
a3907e800b | ||
|
|
5c640c6c52 | ||
|
|
fe6096464c | ||
|
|
5bf3de45f4 | ||
|
|
f33babc420 | ||
|
|
1c9199761b | ||
|
|
368fa64914 | ||
|
|
e114ed5566 | ||
|
|
5d47c5b316 | ||
|
|
812dc413e5 |
4
.github/workflows/unittests.yml
vendored
4
.github/workflows/unittests.yml
vendored
@@ -54,9 +54,9 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pytest pytest-subtests
|
||||
pip install pytest pytest-subtests pytest-xdist
|
||||
python ModuleUpdate.py --yes --force --append "WebHostLib/requirements.txt"
|
||||
python Launcher.py --update_settings # make sure host.yaml exists for tests
|
||||
- name: Unittests
|
||||
run: |
|
||||
pytest
|
||||
pytest -n auto
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -27,16 +27,20 @@
|
||||
*.archipelago
|
||||
*.apsave
|
||||
*.BIN
|
||||
*.puml
|
||||
|
||||
setups
|
||||
build
|
||||
bundle/components.wxs
|
||||
dist
|
||||
/prof/
|
||||
README.html
|
||||
.vs/
|
||||
EnemizerCLI/
|
||||
/Players/
|
||||
/SNI/
|
||||
/sni-*/
|
||||
/appimagetool*
|
||||
/host.yaml
|
||||
/options.yaml
|
||||
/config.yaml
|
||||
@@ -139,6 +143,7 @@ ipython_config.py
|
||||
.venv*
|
||||
env/
|
||||
venv/
|
||||
/venv*/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
@@ -8,6 +8,7 @@ import secrets
|
||||
import typing # this can go away when Python 3.8 support is dropped
|
||||
from argparse import Namespace
|
||||
from collections import ChainMap, Counter, deque
|
||||
from collections.abc import Collection
|
||||
from enum import IntEnum, IntFlag
|
||||
from typing import Any, Callable, Dict, Iterable, Iterator, List, NamedTuple, Optional, Set, Tuple, TypedDict, Union, \
|
||||
Type, ClassVar
|
||||
@@ -202,14 +203,7 @@ class MultiWorld():
|
||||
self.player_types[new_id] = NetUtils.SlotType.group
|
||||
self._region_cache[new_id] = {}
|
||||
world_type = AutoWorld.AutoWorldRegister.world_types[game]
|
||||
for option_key, option in world_type.option_definitions.items():
|
||||
getattr(self, option_key)[new_id] = option(option.default)
|
||||
for option_key, option in Options.common_options.items():
|
||||
getattr(self, option_key)[new_id] = option(option.default)
|
||||
for option_key, option in Options.per_game_common_options.items():
|
||||
getattr(self, option_key)[new_id] = option(option.default)
|
||||
|
||||
self.worlds[new_id] = world_type(self, new_id)
|
||||
self.worlds[new_id] = world_type.create_group(self, new_id, players)
|
||||
self.worlds[new_id].collect_item = classmethod(AutoWorld.World.collect_item).__get__(self.worlds[new_id])
|
||||
self.player_name[new_id] = name
|
||||
|
||||
@@ -364,7 +358,7 @@ class MultiWorld():
|
||||
for r_location in region.locations:
|
||||
self._location_cache[r_location.name, player] = r_location
|
||||
|
||||
def get_regions(self, player=None):
|
||||
def get_regions(self, player: Optional[int] = None) -> Collection[Region]:
|
||||
return self.regions if player is None else self._region_cache[player].values()
|
||||
|
||||
def get_region(self, regionname: str, player: int) -> Region:
|
||||
|
||||
9
BizHawkClient.py
Normal file
9
BizHawkClient.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import ModuleUpdate
|
||||
ModuleUpdate.update()
|
||||
|
||||
from worlds._bizhawk.context import launch
|
||||
|
||||
if __name__ == "__main__":
|
||||
launch()
|
||||
6
Fill.py
6
Fill.py
@@ -753,8 +753,6 @@ def distribute_planned(world: MultiWorld) -> None:
|
||||
else: # not reachable with swept state
|
||||
non_early_locations[loc.player].append(loc.name)
|
||||
|
||||
# TODO: remove. Preferably by implementing key drop
|
||||
from worlds.alttp.Regions import key_drop_data
|
||||
world_name_lookup = world.world_name_lookup
|
||||
|
||||
block_value = typing.Union[typing.List[str], typing.Dict[str, typing.Any], str]
|
||||
@@ -897,10 +895,6 @@ def distribute_planned(world: MultiWorld) -> None:
|
||||
for item_name in items:
|
||||
item = world.worlds[player].create_item(item_name)
|
||||
for location in reversed(candidates):
|
||||
if location in key_drop_data:
|
||||
warn(
|
||||
f"Can't place '{item_name}' at '{placement.location}', as key drop shuffle locations are not supported yet.")
|
||||
continue
|
||||
if not location.item:
|
||||
if location.item_rule(item):
|
||||
if location.can_fill(world.state, item, False):
|
||||
|
||||
33
Launcher.py
33
Launcher.py
@@ -50,17 +50,22 @@ def open_host_yaml():
|
||||
def open_patch():
|
||||
suffixes = []
|
||||
for c in components:
|
||||
if isfile(get_exe(c)[-1]):
|
||||
suffixes += c.file_identifier.suffixes if c.type == Type.CLIENT and \
|
||||
isinstance(c.file_identifier, SuffixIdentifier) else []
|
||||
if c.type == Type.CLIENT and \
|
||||
isinstance(c.file_identifier, SuffixIdentifier) and \
|
||||
(c.script_name is None or isfile(get_exe(c)[-1])):
|
||||
suffixes += c.file_identifier.suffixes
|
||||
try:
|
||||
filename = open_filename('Select patch', (('Patches', suffixes),))
|
||||
filename = open_filename("Select patch", (("Patches", suffixes),))
|
||||
except Exception as e:
|
||||
messagebox('Error', str(e), error=True)
|
||||
messagebox("Error", str(e), error=True)
|
||||
else:
|
||||
file, component = identify(filename)
|
||||
if file and component:
|
||||
launch([*get_exe(component), file], component.cli)
|
||||
exe = get_exe(component)
|
||||
if exe is None or not isfile(exe[-1]):
|
||||
exe = get_exe("Launcher")
|
||||
|
||||
launch([*exe, file], component.cli)
|
||||
|
||||
|
||||
def generate_yamls():
|
||||
@@ -107,7 +112,7 @@ def identify(path: Union[None, str]):
|
||||
return None, None
|
||||
for component in components:
|
||||
if component.handles_file(path):
|
||||
return path, component
|
||||
return path, component
|
||||
elif path == component.display_name or path == component.script_name:
|
||||
return None, component
|
||||
return None, None
|
||||
@@ -117,25 +122,25 @@ def get_exe(component: Union[str, Component]) -> Optional[Sequence[str]]:
|
||||
if isinstance(component, str):
|
||||
name = component
|
||||
component = None
|
||||
if name.startswith('Archipelago'):
|
||||
if name.startswith("Archipelago"):
|
||||
name = name[11:]
|
||||
if name.endswith('.exe'):
|
||||
if name.endswith(".exe"):
|
||||
name = name[:-4]
|
||||
if name.endswith('.py'):
|
||||
if name.endswith(".py"):
|
||||
name = name[:-3]
|
||||
if not name:
|
||||
return None
|
||||
for c in components:
|
||||
if c.script_name == name or c.frozen_name == f'Archipelago{name}':
|
||||
if c.script_name == name or c.frozen_name == f"Archipelago{name}":
|
||||
component = c
|
||||
break
|
||||
if not component:
|
||||
return None
|
||||
if is_frozen():
|
||||
suffix = '.exe' if is_windows else ''
|
||||
return [local_path(f'{component.frozen_name}{suffix}')]
|
||||
suffix = ".exe" if is_windows else ""
|
||||
return [local_path(f"{component.frozen_name}{suffix}")] if component.frozen_name else None
|
||||
else:
|
||||
return [sys.executable, local_path(f'{component.script_name}.py')]
|
||||
return [sys.executable, local_path(f"{component.script_name}.py")] if component.script_name else None
|
||||
|
||||
|
||||
def launch(exe, in_terminal=False):
|
||||
|
||||
128
Utils.py
128
Utils.py
@@ -13,6 +13,7 @@ import io
|
||||
import collections
|
||||
import importlib
|
||||
import logging
|
||||
import warnings
|
||||
|
||||
from argparse import Namespace
|
||||
from settings import Settings, get_settings
|
||||
@@ -29,6 +30,7 @@ except ImportError:
|
||||
if typing.TYPE_CHECKING:
|
||||
import tkinter
|
||||
import pathlib
|
||||
from BaseClasses import Region
|
||||
|
||||
|
||||
def tuplize_version(version: str) -> Version:
|
||||
@@ -215,7 +217,13 @@ def get_cert_none_ssl_context():
|
||||
def get_public_ipv4() -> str:
|
||||
import socket
|
||||
import urllib.request
|
||||
ip = socket.gethostbyname(socket.gethostname())
|
||||
try:
|
||||
ip = socket.gethostbyname(socket.gethostname())
|
||||
except socket.gaierror:
|
||||
# if hostname or resolvconf is not set up properly, this may fail
|
||||
warnings.warn("Could not resolve own hostname, falling back to 127.0.0.1")
|
||||
ip = "127.0.0.1"
|
||||
|
||||
ctx = get_cert_none_ssl_context()
|
||||
try:
|
||||
ip = urllib.request.urlopen("https://checkip.amazonaws.com/", context=ctx, timeout=10).read().decode("utf8").strip()
|
||||
@@ -233,7 +241,13 @@ def get_public_ipv4() -> str:
|
||||
def get_public_ipv6() -> str:
|
||||
import socket
|
||||
import urllib.request
|
||||
ip = socket.gethostbyname(socket.gethostname())
|
||||
try:
|
||||
ip = socket.gethostbyname(socket.gethostname())
|
||||
except socket.gaierror:
|
||||
# if hostname or resolvconf is not set up properly, this may fail
|
||||
warnings.warn("Could not resolve own hostname, falling back to ::1")
|
||||
ip = "::1"
|
||||
|
||||
ctx = get_cert_none_ssl_context()
|
||||
try:
|
||||
ip = urllib.request.urlopen("https://v6.ident.me", context=ctx, timeout=10).read().decode("utf8").strip()
|
||||
@@ -766,3 +780,113 @@ def freeze_support() -> None:
|
||||
import multiprocessing
|
||||
_extend_freeze_support()
|
||||
multiprocessing.freeze_support()
|
||||
|
||||
|
||||
def visualize_regions(root_region: Region, file_name: str, *,
|
||||
show_entrance_names: bool = False, show_locations: bool = True, show_other_regions: bool = True,
|
||||
linetype_ortho: bool = True) -> None:
|
||||
"""Visualize the layout of a world as a PlantUML diagram.
|
||||
|
||||
:param root_region: The region from which to start the diagram from. (Usually the "Menu" region of your world.)
|
||||
:param file_name: The name of the destination .puml file.
|
||||
:param show_entrance_names: (default False) If enabled, the name of the entrance will be shown near each connection.
|
||||
:param show_locations: (default True) If enabled, the locations will be listed inside each region.
|
||||
Priority locations will be shown in bold.
|
||||
Excluded locations will be stricken out.
|
||||
Locations without ID will be shown in italics.
|
||||
Locked locations will be shown with a padlock icon.
|
||||
For filled locations, the item name will be shown after the location name.
|
||||
Progression items will be shown in bold.
|
||||
Items without ID will be shown in italics.
|
||||
:param show_other_regions: (default True) If enabled, regions that can't be reached by traversing exits are shown.
|
||||
:param linetype_ortho: (default True) If enabled, orthogonal straight line parts will be used; otherwise polylines.
|
||||
|
||||
Example usage in World code:
|
||||
from Utils import visualize_regions
|
||||
visualize_regions(self.multiworld.get_region("Menu", self.player), "my_world.puml")
|
||||
|
||||
Example usage in Main code:
|
||||
from Utils import visualize_regions
|
||||
for player in world.player_ids:
|
||||
visualize_regions(world.get_region("Menu", player), f"{world.get_out_file_name_base(player)}.puml")
|
||||
"""
|
||||
assert root_region.multiworld, "The multiworld attribute of root_region has to be filled"
|
||||
from BaseClasses import Entrance, Item, Location, LocationProgressType, MultiWorld, Region
|
||||
from collections import deque
|
||||
import re
|
||||
|
||||
uml: typing.List[str] = list()
|
||||
seen: typing.Set[Region] = set()
|
||||
regions: typing.Deque[Region] = deque((root_region,))
|
||||
multiworld: MultiWorld = root_region.multiworld
|
||||
|
||||
def fmt(obj: Union[Entrance, Item, Location, Region]) -> str:
|
||||
name = obj.name
|
||||
if isinstance(obj, Item):
|
||||
name = multiworld.get_name_string_for_object(obj)
|
||||
if obj.advancement:
|
||||
name = f"**{name}**"
|
||||
if obj.code is None:
|
||||
name = f"//{name}//"
|
||||
if isinstance(obj, Location):
|
||||
if obj.progress_type == LocationProgressType.PRIORITY:
|
||||
name = f"**{name}**"
|
||||
elif obj.progress_type == LocationProgressType.EXCLUDED:
|
||||
name = f"--{name}--"
|
||||
if obj.address is None:
|
||||
name = f"//{name}//"
|
||||
return re.sub("[\".:]", "", name)
|
||||
|
||||
def visualize_exits(region: Region) -> None:
|
||||
for exit_ in region.exits:
|
||||
if exit_.connected_region:
|
||||
if show_entrance_names:
|
||||
uml.append(f"\"{fmt(region)}\" --> \"{fmt(exit_.connected_region)}\" : \"{fmt(exit_)}\"")
|
||||
else:
|
||||
try:
|
||||
uml.remove(f"\"{fmt(exit_.connected_region)}\" --> \"{fmt(region)}\"")
|
||||
uml.append(f"\"{fmt(exit_.connected_region)}\" <--> \"{fmt(region)}\"")
|
||||
except ValueError:
|
||||
uml.append(f"\"{fmt(region)}\" --> \"{fmt(exit_.connected_region)}\"")
|
||||
else:
|
||||
uml.append(f"circle \"unconnected exit:\\n{fmt(exit_)}\"")
|
||||
uml.append(f"\"{fmt(region)}\" --> \"unconnected exit:\\n{fmt(exit_)}\"")
|
||||
|
||||
def visualize_locations(region: Region) -> None:
|
||||
any_lock = any(location.locked for location in region.locations)
|
||||
for location in region.locations:
|
||||
lock = "<&lock-locked> " if location.locked else "<&lock-unlocked,color=transparent> " if any_lock else ""
|
||||
if location.item:
|
||||
uml.append(f"\"{fmt(region)}\" : {{method}} {lock}{fmt(location)}: {fmt(location.item)}")
|
||||
else:
|
||||
uml.append(f"\"{fmt(region)}\" : {{field}} {lock}{fmt(location)}")
|
||||
|
||||
def visualize_region(region: Region) -> None:
|
||||
uml.append(f"class \"{fmt(region)}\"")
|
||||
if show_locations:
|
||||
visualize_locations(region)
|
||||
visualize_exits(region)
|
||||
|
||||
def visualize_other_regions() -> None:
|
||||
if other_regions := [region for region in multiworld.get_regions(root_region.player) if region not in seen]:
|
||||
uml.append("package \"other regions\" <<Cloud>> {")
|
||||
for region in other_regions:
|
||||
uml.append(f"class \"{fmt(region)}\"")
|
||||
uml.append("}")
|
||||
|
||||
uml.append("@startuml")
|
||||
uml.append("hide circle")
|
||||
uml.append("hide empty members")
|
||||
if linetype_ortho:
|
||||
uml.append("skinparam linetype ortho")
|
||||
while regions:
|
||||
if (current_region := regions.popleft()) not in seen:
|
||||
seen.add(current_region)
|
||||
visualize_region(current_region)
|
||||
regions.extend(exit_.connected_region for exit_ in current_region.exits if exit_.connected_region)
|
||||
if show_other_regions:
|
||||
visualize_other_regions()
|
||||
uml.append("@enduml")
|
||||
|
||||
with open(file_name, "wt", encoding="utf-8") as f:
|
||||
f.write("\n".join(uml))
|
||||
|
||||
@@ -11,6 +11,7 @@ import socket
|
||||
import threading
|
||||
import time
|
||||
import typing
|
||||
import sys
|
||||
|
||||
import websockets
|
||||
from pony.orm import commit, db_session, select
|
||||
@@ -164,8 +165,10 @@ def run_server_process(room_id, ponyconfig: dict, static_server_data: dict,
|
||||
db.generate_mapping(check_tables=False)
|
||||
|
||||
async def main():
|
||||
import gc
|
||||
if "worlds" in sys.modules:
|
||||
raise Exception("Worlds system should not be loaded in the custom server.")
|
||||
|
||||
import gc
|
||||
Utils.init_logging(str(room_id), write_mode="a")
|
||||
ctx = WebHostContext(static_server_data)
|
||||
ctx.load(room_id)
|
||||
|
||||
@@ -32,29 +32,34 @@ def page_not_found(err):
|
||||
|
||||
# Start Playing Page
|
||||
@app.route('/start-playing')
|
||||
@cache.cached()
|
||||
def start_playing():
|
||||
return render_template(f"startPlaying.html")
|
||||
|
||||
|
||||
@app.route('/weighted-settings')
|
||||
@cache.cached()
|
||||
def weighted_settings():
|
||||
return render_template(f"weighted-settings.html")
|
||||
|
||||
|
||||
# Player settings pages
|
||||
@app.route('/games/<string:game>/player-settings')
|
||||
@cache.cached()
|
||||
def player_settings(game):
|
||||
return render_template(f"player-settings.html", game=game, theme=get_world_theme(game))
|
||||
|
||||
|
||||
# Game Info Pages
|
||||
@app.route('/games/<string:game>/info/<string:lang>')
|
||||
@cache.cached()
|
||||
def game_info(game, lang):
|
||||
return render_template('gameInfo.html', game=game, lang=lang, theme=get_world_theme(game))
|
||||
|
||||
|
||||
# List of supported games
|
||||
@app.route('/games')
|
||||
@cache.cached()
|
||||
def games():
|
||||
worlds = {}
|
||||
for game, world in AutoWorldRegister.world_types.items():
|
||||
@@ -64,21 +69,25 @@ def games():
|
||||
|
||||
|
||||
@app.route('/tutorial/<string:game>/<string:file>/<string:lang>')
|
||||
@cache.cached()
|
||||
def tutorial(game, file, lang):
|
||||
return render_template("tutorial.html", game=game, file=file, lang=lang, theme=get_world_theme(game))
|
||||
|
||||
|
||||
@app.route('/tutorial/')
|
||||
@cache.cached()
|
||||
def tutorial_landing():
|
||||
return render_template("tutorialLanding.html")
|
||||
|
||||
|
||||
@app.route('/faq/<string:lang>/')
|
||||
@cache.cached()
|
||||
def faq(lang):
|
||||
return render_template("faq.html", lang=lang)
|
||||
|
||||
|
||||
@app.route('/glossary/<string:lang>/')
|
||||
@cache.cached()
|
||||
def terms(lang):
|
||||
return render_template("glossary.html", lang=lang)
|
||||
|
||||
@@ -147,7 +156,7 @@ def host_room(room: UUID):
|
||||
|
||||
@app.route('/favicon.ico')
|
||||
def favicon():
|
||||
return send_from_directory(os.path.join(app.root_path, 'static/static'),
|
||||
return send_from_directory(os.path.join(app.root_path, "static", "static"),
|
||||
'favicon.ico', mimetype='image/vnd.microsoft.icon')
|
||||
|
||||
|
||||
@@ -167,6 +176,7 @@ def get_datapackage():
|
||||
|
||||
@app.route('/index')
|
||||
@app.route('/sitemap')
|
||||
@cache.cached()
|
||||
def get_sitemap():
|
||||
available_games: List[Dict[str, Union[str, bool]]] = []
|
||||
for game, world in AutoWorldRegister.world_types.items():
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
flask>=2.2.3
|
||||
pony>=0.7.16; python_version <= '3.10'
|
||||
pony @ https://github.com/Berserker66/pony/releases/download/v0.7.16/pony-0.7.16-py3-none-any.whl#0.7.16 ; python_version >= '3.11'
|
||||
pony>=0.7.17
|
||||
waitress>=2.1.2
|
||||
Flask-Caching>=2.0.2
|
||||
Flask-Compress>=1.14
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
window.addEventListener('load', () => {
|
||||
document.getElementById('js-enabled').style.display = 'block';
|
||||
const gameHeaders = document.getElementsByClassName('collapse-toggle');
|
||||
Array.from(gameHeaders).forEach((header) => {
|
||||
const gameName = header.getAttribute('data-game');
|
||||
|
||||
@@ -235,9 +235,6 @@ html{
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
#landing .variable{
|
||||
color: #ffff00;
|
||||
}
|
||||
|
||||
.landing-deco{
|
||||
position: absolute;
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#games h2 .game-name{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#games p.collapsed{
|
||||
display: none;
|
||||
}
|
||||
@@ -51,3 +55,7 @@
|
||||
#games #page-controls button{
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
#games #js-enabled{
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -49,9 +49,9 @@
|
||||
our crazy idea into a reality.
|
||||
</p>
|
||||
<p>
|
||||
<span class="variable">{{ seeds }}</span>
|
||||
<a href="{{ url_for("stats") }}">{{ seeds }}</a>
|
||||
games were generated and
|
||||
<span class="variable">{{ rooms }}</span>
|
||||
<a href="{{ url_for("stats") }}">{{ rooms }}</a>
|
||||
were hosted in the last 7 days.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -12,17 +12,25 @@
|
||||
<div id="games" class="markdown">
|
||||
<h1>Currently Supported Games</h1>
|
||||
<div>
|
||||
<label for="game-search">Search for your game below!</label><br />
|
||||
<label for="game-search">
|
||||
Search for your game below!
|
||||
<noscript>(You need to enable Javascript for this to work.)</noscript>
|
||||
</label><br />
|
||||
<div id="page-controls">
|
||||
<input id="game-search" placeholder="Search by title..." autofocus />
|
||||
<button id="expand-all">Expand All</button>
|
||||
<button id="collapse-all">Collapse All</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- This is always written to the page, but is hidden by default. If the user has JS enabled,
|
||||
it will be un-hidden immediately when the page is ready. -->
|
||||
<div id="js-enabled">
|
||||
{% for game_name in worlds | title_sorted %}
|
||||
{% set world = worlds[game_name] %}
|
||||
<h2 class="collapse-toggle" data-game="{{ game_name }}">
|
||||
<span id="{{ game_name }}-arrow" class="collapse-arrow">▶</span> {{ game_name }}
|
||||
<span id="{{ game_name }}-arrow" class="collapse-arrow">▶</span>
|
||||
<span class="game-name">{{ game_name }}</span>
|
||||
</h2>
|
||||
<p id="{{ game_name }}" class="collapsed">
|
||||
{{ world.__doc__ | default("No description provided.", true) }}<br />
|
||||
@@ -44,5 +52,34 @@
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- This is only printed when the user has JS disabled, allowing them to see all the information for
|
||||
each game in a noscript-friendly way. -->
|
||||
<noscript>
|
||||
{% for game_name in worlds | title_sorted %}
|
||||
{% set world = worlds[game_name] %}
|
||||
<h2 class="collapse-toggle">{{ game_name }}</h2>
|
||||
<p id="{{ game_name }}">
|
||||
{{ world.__doc__ | default("No description provided.", true) }}<br />
|
||||
<a href="{{ url_for("game_info", game=game_name, lang="en") }}">Game Page</a>
|
||||
{% if world.web.tutorials %}
|
||||
<span class="link-spacer">|</span>
|
||||
<a href="{{ url_for("tutorial_landing") }}#{{ game_name }}">Setup Guides</a>
|
||||
{% endif %}
|
||||
{% if world.web.settings_page is string %}
|
||||
<span class="link-spacer">|</span>
|
||||
<a href="{{ world.web.settings_page }}">Settings Page</a>
|
||||
{% elif world.web.settings_page %}
|
||||
<span class="link-spacer">|</span>
|
||||
<a href="{{ url_for("player_settings", game=game_name) }}">Settings Page</a>
|
||||
{% endif %}
|
||||
{% if world.web.bug_report_page %}
|
||||
<span class="link-spacer">|</span>
|
||||
<a href="{{ world.web.bug_report_page }}">Report a Bug</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% endfor %}
|
||||
</noscript>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td>Rooms: </td>
|
||||
<td>
|
||||
<td>
|
||||
{% call macros.list_rooms(seed.rooms | selectattr("owner", "eq", session["_id"])) %}
|
||||
<li>
|
||||
<a href="{{ url_for("new_room", seed=seed.id) }}">Create New Room</a>
|
||||
|
||||
119
data/lua/base64.lua
Normal file
119
data/lua/base64.lua
Normal file
@@ -0,0 +1,119 @@
|
||||
-- This file originates from this repository: https://github.com/iskolbin/lbase64
|
||||
-- It was modified to translate between base64 strings and lists of bytes instead of base64 strings and strings.
|
||||
|
||||
local base64 = {}
|
||||
|
||||
local extract = _G.bit32 and _G.bit32.extract -- Lua 5.2/Lua 5.3 in compatibility mode
|
||||
if not extract then
|
||||
if _G._VERSION == "Lua 5.4" then
|
||||
extract = load[[return function( v, from, width )
|
||||
return ( v >> from ) & ((1 << width) - 1)
|
||||
end]]()
|
||||
elseif _G.bit then -- LuaJIT
|
||||
local shl, shr, band = _G.bit.lshift, _G.bit.rshift, _G.bit.band
|
||||
extract = function( v, from, width )
|
||||
return band( shr( v, from ), shl( 1, width ) - 1 )
|
||||
end
|
||||
elseif _G._VERSION == "Lua 5.1" then
|
||||
extract = function( v, from, width )
|
||||
local w = 0
|
||||
local flag = 2^from
|
||||
for i = 0, width-1 do
|
||||
local flag2 = flag + flag
|
||||
if v % flag2 >= flag then
|
||||
w = w + 2^i
|
||||
end
|
||||
flag = flag2
|
||||
end
|
||||
return w
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function base64.makeencoder( s62, s63, spad )
|
||||
local encoder = {}
|
||||
for b64code, char in pairs{[0]='A','B','C','D','E','F','G','H','I','J',
|
||||
'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y',
|
||||
'Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n',
|
||||
'o','p','q','r','s','t','u','v','w','x','y','z','0','1','2',
|
||||
'3','4','5','6','7','8','9',s62 or '+',s63 or'/',spad or'='} do
|
||||
encoder[b64code] = char:byte()
|
||||
end
|
||||
return encoder
|
||||
end
|
||||
|
||||
function base64.makedecoder( s62, s63, spad )
|
||||
local decoder = {}
|
||||
for b64code, charcode in pairs( base64.makeencoder( s62, s63, spad )) do
|
||||
decoder[charcode] = b64code
|
||||
end
|
||||
return decoder
|
||||
end
|
||||
|
||||
local DEFAULT_ENCODER = base64.makeencoder()
|
||||
local DEFAULT_DECODER = base64.makedecoder()
|
||||
|
||||
local char, concat = string.char, table.concat
|
||||
|
||||
function base64.encode( arr, encoder )
|
||||
encoder = encoder or DEFAULT_ENCODER
|
||||
local t, k, n = {}, 1, #arr
|
||||
local lastn = n % 3
|
||||
for i = 1, n-lastn, 3 do
|
||||
local a, b, c = arr[i], arr[i + 1], arr[i + 2]
|
||||
local v = a*0x10000 + b*0x100 + c
|
||||
local s
|
||||
s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)])
|
||||
t[k] = s
|
||||
k = k + 1
|
||||
end
|
||||
if lastn == 2 then
|
||||
local a, b = arr[n-1], arr[n]
|
||||
local v = a*0x10000 + b*0x100
|
||||
t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[64])
|
||||
elseif lastn == 1 then
|
||||
local v = arr[n]*0x10000
|
||||
t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[64], encoder[64])
|
||||
end
|
||||
return concat( t )
|
||||
end
|
||||
|
||||
function base64.decode( b64, decoder )
|
||||
decoder = decoder or DEFAULT_DECODER
|
||||
local pattern = '[^%w%+%/%=]'
|
||||
if decoder then
|
||||
local s62, s63
|
||||
for charcode, b64code in pairs( decoder ) do
|
||||
if b64code == 62 then s62 = charcode
|
||||
elseif b64code == 63 then s63 = charcode
|
||||
end
|
||||
end
|
||||
pattern = ('[^%%w%%%s%%%s%%=]'):format( char(s62), char(s63) )
|
||||
end
|
||||
b64 = b64:gsub( pattern, '' )
|
||||
local t, k = {}, 1
|
||||
local n = #b64
|
||||
local padding = b64:sub(-2) == '==' and 2 or b64:sub(-1) == '=' and 1 or 0
|
||||
for i = 1, padding > 0 and n-4 or n, 4 do
|
||||
local a, b, c, d = b64:byte( i, i+3 )
|
||||
local s
|
||||
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d]
|
||||
table.insert(t,extract(v,16,8))
|
||||
table.insert(t,extract(v,8,8))
|
||||
table.insert(t,extract(v,0,8))
|
||||
end
|
||||
if padding == 1 then
|
||||
local a, b, c = b64:byte( n-3, n-1 )
|
||||
local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40
|
||||
table.insert(t,extract(v,16,8))
|
||||
table.insert(t,extract(v,8,8))
|
||||
elseif padding == 2 then
|
||||
local a, b = b64:byte( n-3, n-2 )
|
||||
local v = decoder[a]*0x40000 + decoder[b]*0x1000
|
||||
table.insert(t,extract(v,16,8))
|
||||
end
|
||||
return t
|
||||
end
|
||||
|
||||
return base64
|
||||
564
data/lua/connector_bizhawk_generic.lua
Normal file
564
data/lua/connector_bizhawk_generic.lua
Normal file
@@ -0,0 +1,564 @@
|
||||
--[[
|
||||
Copyright (c) 2023 Zunawe
|
||||
|
||||
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.
|
||||
]]
|
||||
|
||||
local SCRIPT_VERSION = 1
|
||||
|
||||
--[[
|
||||
This script expects to receive JSON and will send JSON back. A message should
|
||||
be a list of 1 or more requests which will be executed in order. Each request
|
||||
will have a corresponding response in the same order.
|
||||
|
||||
Every individual request and response is a JSON object with at minimum one
|
||||
field `type`. The value of `type` determines what other fields may exist.
|
||||
|
||||
To get the script version, instead of JSON, send "VERSION" to get the script
|
||||
version directly (e.g. "2").
|
||||
|
||||
#### Ex. 1
|
||||
|
||||
Request: `[{"type": "PING"}]`
|
||||
|
||||
Response: `[{"type": "PONG"}]`
|
||||
|
||||
---
|
||||
|
||||
#### Ex. 2
|
||||
|
||||
Request: `[{"type": "LOCK"}, {"type": "HASH"}]`
|
||||
|
||||
Response: `[{"type": "LOCKED"}, {"type": "HASH_RESPONSE", "value": "F7D18982"}]`
|
||||
|
||||
---
|
||||
|
||||
#### Ex. 3
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
[
|
||||
{"type": "GUARD", "address": 100, "expected_data": "aGVsbG8=", "domain": "System Bus"},
|
||||
{"type": "READ", "address": 500, "size": 4, "domain": "ROM"}
|
||||
]
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
[
|
||||
{"type": "GUARD_RESPONSE", "address": 100, "value": true},
|
||||
{"type": "READ_RESPONSE", "value": "dGVzdA=="}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### Ex. 4
|
||||
|
||||
Request:
|
||||
|
||||
```json
|
||||
[
|
||||
{"type": "GUARD", "address": 100, "expected_data": "aGVsbG8=", "domain": "System Bus"},
|
||||
{"type": "READ", "address": 500, "size": 4, "domain": "ROM"}
|
||||
]
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
[
|
||||
{"type": "GUARD_RESPONSE", "address": 100, "value": false},
|
||||
{"type": "GUARD_RESPONSE", "address": 100, "value": false}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Supported Request Types
|
||||
|
||||
- `PING`
|
||||
Does nothing; resets timeout.
|
||||
|
||||
Expected Response Type: `PONG`
|
||||
|
||||
- `SYSTEM`
|
||||
Returns the system of the currently loaded ROM (N64, GBA, etc...).
|
||||
|
||||
Expected Response Type: `SYSTEM_RESPONSE`
|
||||
|
||||
- `PREFERRED_CORES`
|
||||
Returns the user's default cores for systems with multiple cores. If the
|
||||
current ROM's system has multiple cores, the one that is currently
|
||||
running is very probably the preferred core.
|
||||
|
||||
Expected Response Type: `PREFERRED_CORES_RESPONSE`
|
||||
|
||||
- `HASH`
|
||||
Returns the hash of the currently loaded ROM calculated by BizHawk.
|
||||
|
||||
Expected Response Type: `HASH_RESPONSE`
|
||||
|
||||
- `GUARD`
|
||||
Checks a section of memory against `expected_data`. If the bytes starting
|
||||
at `address` do not match `expected_data`, the response will have `value`
|
||||
set to `false`, and all subsequent requests will not be executed and
|
||||
receive the same `GUARD_RESPONSE`.
|
||||
|
||||
Expected Response Type: `GUARD_RESPONSE`
|
||||
|
||||
Additional Fields:
|
||||
- `address` (`int`): The address of the memory to check
|
||||
- `expected_data` (string): A base64 string of contiguous data
|
||||
- `domain` (`string`): The name of the memory domain the address
|
||||
corresponds to
|
||||
|
||||
- `LOCK`
|
||||
Halts emulation and blocks on incoming requests until an `UNLOCK` request
|
||||
is received or the client times out. All requests processed while locked
|
||||
will happen on the same frame.
|
||||
|
||||
Expected Response Type: `LOCKED`
|
||||
|
||||
- `UNLOCK`
|
||||
Resumes emulation after the current list of requests is done being
|
||||
executed.
|
||||
|
||||
Expected Response Type: `UNLOCKED`
|
||||
|
||||
- `READ`
|
||||
Reads an array of bytes at the provided address.
|
||||
|
||||
Expected Response Type: `READ_RESPONSE`
|
||||
|
||||
Additional Fields:
|
||||
- `address` (`int`): The address of the memory to read
|
||||
- `size` (`int`): The number of bytes to read
|
||||
- `domain` (`string`): The name of the memory domain the address
|
||||
corresponds to
|
||||
|
||||
- `WRITE`
|
||||
Writes an array of bytes to the provided address.
|
||||
|
||||
Expected Response Type: `WRITE_RESPONSE`
|
||||
|
||||
Additional Fields:
|
||||
- `address` (`int`): The address of the memory to write to
|
||||
- `value` (`string`): A base64 string representing the data to write
|
||||
- `domain` (`string`): The name of the memory domain the address
|
||||
corresponds to
|
||||
|
||||
- `DISPLAY_MESSAGE`
|
||||
Adds a message to the message queue which will be displayed using
|
||||
`gui.addmessage` according to the message interval.
|
||||
|
||||
Expected Response Type: `DISPLAY_MESSAGE_RESPONSE`
|
||||
|
||||
Additional Fields:
|
||||
- `message` (`string`): The string to display
|
||||
|
||||
- `SET_MESSAGE_INTERVAL`
|
||||
Sets the minimum amount of time to wait between displaying messages.
|
||||
Potentially useful if you add many messages quickly but want players
|
||||
to be able to read each of them.
|
||||
|
||||
Expected Response Type: `SET_MESSAGE_INTERVAL_RESPONSE`
|
||||
|
||||
Additional Fields:
|
||||
- `value` (`number`): The number of seconds to set the interval to
|
||||
|
||||
|
||||
### Response Types
|
||||
|
||||
- `PONG`
|
||||
Acknowledges `PING`.
|
||||
|
||||
- `SYSTEM_RESPONSE`
|
||||
Contains the name of the system for currently running ROM.
|
||||
|
||||
Additional Fields:
|
||||
- `value` (`string`): The returned system name
|
||||
|
||||
- `PREFERRED_CORES_RESPONSE`
|
||||
Contains the user's preferred cores for systems with multiple supported
|
||||
cores. Currently includes NES, SNES, GB, GBC, DGB, SGB, PCE, PCECD, and
|
||||
SGX.
|
||||
|
||||
Additional Fields:
|
||||
- `value` (`{[string]: [string]}`): A dictionary map from system name to
|
||||
core name
|
||||
|
||||
- `HASH_RESPONSE`
|
||||
Contains the hash of the currently loaded ROM calculated by BizHawk.
|
||||
|
||||
Additional Fields:
|
||||
- `value` (`string`): The returned hash
|
||||
|
||||
- `GUARD_RESPONSE`
|
||||
The result of an attempted `GUARD` request.
|
||||
|
||||
Additional Fields:
|
||||
- `value` (`boolean`): true if the memory was validated, false if not
|
||||
- `address` (`int`): The address of the memory that was invalid (the same
|
||||
address provided by the `GUARD`, not the address of the individual invalid
|
||||
byte)
|
||||
|
||||
- `LOCKED`
|
||||
Acknowledges `LOCK`.
|
||||
|
||||
- `UNLOCKED`
|
||||
Acknowledges `UNLOCK`.
|
||||
|
||||
- `READ_RESPONSE`
|
||||
Contains the result of a `READ` request.
|
||||
|
||||
Additional Fields:
|
||||
- `value` (`string`): A base64 string representing the read data
|
||||
|
||||
- `WRITE_RESPONSE`
|
||||
Acknowledges `WRITE`.
|
||||
|
||||
- `DISPLAY_MESSAGE_RESPONSE`
|
||||
Acknowledges `DISPLAY_MESSAGE`.
|
||||
|
||||
- `SET_MESSAGE_INTERVAL_RESPONSE`
|
||||
Acknowledges `SET_MESSAGE_INTERVAL`.
|
||||
|
||||
- `ERROR`
|
||||
Signifies that something has gone wrong while processing a request.
|
||||
|
||||
Additional Fields:
|
||||
- `err` (`string`): A description of the problem
|
||||
]]
|
||||
|
||||
local base64 = require("base64")
|
||||
local socket = require("socket")
|
||||
local json = require("json")
|
||||
|
||||
-- Set to log incoming requests
|
||||
-- Will cause lag due to large console output
|
||||
local DEBUG = false
|
||||
|
||||
local SOCKET_PORT = 43055
|
||||
|
||||
local STATE_NOT_CONNECTED = 0
|
||||
local STATE_CONNECTED = 1
|
||||
|
||||
local server = nil
|
||||
local client_socket = nil
|
||||
|
||||
local current_state = STATE_NOT_CONNECTED
|
||||
|
||||
local timeout_timer = 0
|
||||
local message_timer = 0
|
||||
local message_interval = 0
|
||||
local prev_time = 0
|
||||
local current_time = 0
|
||||
|
||||
local locked = false
|
||||
|
||||
local rom_hash = nil
|
||||
|
||||
local lua_major, lua_minor = _VERSION:match("Lua (%d+)%.(%d+)")
|
||||
lua_major = tonumber(lua_major)
|
||||
lua_minor = tonumber(lua_minor)
|
||||
|
||||
if lua_major > 5 or (lua_major == 5 and lua_minor >= 3) then
|
||||
require("lua_5_3_compat")
|
||||
end
|
||||
|
||||
local bizhawk_version = client.getversion()
|
||||
local bizhawk_major, bizhawk_minor, bizhawk_patch = bizhawk_version:match("(%d+)%.(%d+)%.?(%d*)")
|
||||
bizhawk_major = tonumber(bizhawk_major)
|
||||
bizhawk_minor = tonumber(bizhawk_minor)
|
||||
if bizhawk_patch == "" then
|
||||
bizhawk_patch = 0
|
||||
else
|
||||
bizhawk_patch = tonumber(bizhawk_patch)
|
||||
end
|
||||
|
||||
function queue_push (self, value)
|
||||
self[self.right] = value
|
||||
self.right = self.right + 1
|
||||
end
|
||||
|
||||
function queue_is_empty (self)
|
||||
return self.right == self.left
|
||||
end
|
||||
|
||||
function queue_shift (self)
|
||||
value = self[self.left]
|
||||
self[self.left] = nil
|
||||
self.left = self.left + 1
|
||||
return value
|
||||
end
|
||||
|
||||
function new_queue ()
|
||||
local queue = {left = 1, right = 1}
|
||||
return setmetatable(queue, {__index = {is_empty = queue_is_empty, push = queue_push, shift = queue_shift}})
|
||||
end
|
||||
|
||||
local message_queue = new_queue()
|
||||
|
||||
function lock ()
|
||||
locked = true
|
||||
client_socket:settimeout(2)
|
||||
end
|
||||
|
||||
function unlock ()
|
||||
locked = false
|
||||
client_socket:settimeout(0)
|
||||
end
|
||||
|
||||
function process_request (req)
|
||||
local res = {}
|
||||
|
||||
if req["type"] == "PING" then
|
||||
res["type"] = "PONG"
|
||||
|
||||
elseif req["type"] == "SYSTEM" then
|
||||
res["type"] = "SYSTEM_RESPONSE"
|
||||
res["value"] = emu.getsystemid()
|
||||
|
||||
elseif req["type"] == "PREFERRED_CORES" then
|
||||
local preferred_cores = client.getconfig().PreferredCores
|
||||
res["type"] = "PREFERRED_CORES_RESPONSE"
|
||||
res["value"] = {}
|
||||
res["value"]["NES"] = preferred_cores.NES
|
||||
res["value"]["SNES"] = preferred_cores.SNES
|
||||
res["value"]["GB"] = preferred_cores.GB
|
||||
res["value"]["GBC"] = preferred_cores.GBC
|
||||
res["value"]["DGB"] = preferred_cores.DGB
|
||||
res["value"]["SGB"] = preferred_cores.SGB
|
||||
res["value"]["PCE"] = preferred_cores.PCE
|
||||
res["value"]["PCECD"] = preferred_cores.PCECD
|
||||
res["value"]["SGX"] = preferred_cores.SGX
|
||||
|
||||
elseif req["type"] == "HASH" then
|
||||
res["type"] = "HASH_RESPONSE"
|
||||
res["value"] = rom_hash
|
||||
|
||||
elseif req["type"] == "GUARD" then
|
||||
res["type"] = "GUARD_RESPONSE"
|
||||
local expected_data = base64.decode(req["expected_data"])
|
||||
|
||||
local actual_data = memory.read_bytes_as_array(req["address"], #expected_data, req["domain"])
|
||||
|
||||
local data_is_validated = true
|
||||
for i, byte in ipairs(actual_data) do
|
||||
if byte ~= expected_data[i] then
|
||||
data_is_validated = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
res["value"] = data_is_validated
|
||||
res["address"] = req["address"]
|
||||
|
||||
elseif req["type"] == "LOCK" then
|
||||
res["type"] = "LOCKED"
|
||||
lock()
|
||||
|
||||
elseif req["type"] == "UNLOCK" then
|
||||
res["type"] = "UNLOCKED"
|
||||
unlock()
|
||||
|
||||
elseif req["type"] == "READ" then
|
||||
res["type"] = "READ_RESPONSE"
|
||||
res["value"] = base64.encode(memory.read_bytes_as_array(req["address"], req["size"], req["domain"]))
|
||||
|
||||
elseif req["type"] == "WRITE" then
|
||||
res["type"] = "WRITE_RESPONSE"
|
||||
memory.write_bytes_as_array(req["address"], base64.decode(req["value"]), req["domain"])
|
||||
|
||||
elseif req["type"] == "DISPLAY_MESSAGE" then
|
||||
res["type"] = "DISPLAY_MESSAGE_RESPONSE"
|
||||
message_queue:push(req["message"])
|
||||
|
||||
elseif req["type"] == "SET_MESSAGE_INTERVAL" then
|
||||
res["type"] = "SET_MESSAGE_INTERVAL_RESPONSE"
|
||||
message_interval = req["value"]
|
||||
|
||||
else
|
||||
res["type"] = "ERROR"
|
||||
res["err"] = "Unknown command: "..req["type"]
|
||||
end
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
-- Receive data from AP client and send message back
|
||||
function send_receive ()
|
||||
local message, err = client_socket:receive()
|
||||
|
||||
-- Handle errors
|
||||
if err == "closed" then
|
||||
if current_state == STATE_CONNECTED then
|
||||
print("Connection to client closed")
|
||||
end
|
||||
current_state = STATE_NOT_CONNECTED
|
||||
return
|
||||
elseif err == "timeout" then
|
||||
unlock()
|
||||
return
|
||||
elseif err ~= nil then
|
||||
print(err)
|
||||
current_state = STATE_NOT_CONNECTED
|
||||
unlock()
|
||||
return
|
||||
end
|
||||
|
||||
-- Reset timeout timer
|
||||
timeout_timer = 5
|
||||
|
||||
-- Process received data
|
||||
if DEBUG then
|
||||
print("Received Message ["..emu.framecount().."]: "..'"'..message..'"')
|
||||
end
|
||||
|
||||
if message == "VERSION" then
|
||||
local result, err client_socket:send(tostring(SCRIPT_VERSION).."\n")
|
||||
else
|
||||
local res = {}
|
||||
local data = json.decode(message)
|
||||
local failed_guard_response = nil
|
||||
for i, req in ipairs(data) do
|
||||
if failed_guard_response ~= nil then
|
||||
res[i] = failed_guard_response
|
||||
else
|
||||
-- An error is more likely to cause an NLua exception than to return an error here
|
||||
local status, response = pcall(process_request, req)
|
||||
if status then
|
||||
res[i] = response
|
||||
|
||||
-- If the GUARD validation failed, skip the remaining commands
|
||||
if response["type"] == "GUARD_RESPONSE" and not response["value"] then
|
||||
failed_guard_response = response
|
||||
end
|
||||
else
|
||||
res[i] = {type = "ERROR", err = response}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
client_socket:send(json.encode(res).."\n")
|
||||
end
|
||||
end
|
||||
|
||||
function main ()
|
||||
server, err = socket.bind("localhost", SOCKET_PORT)
|
||||
if err ~= nil then
|
||||
print(err)
|
||||
return
|
||||
end
|
||||
|
||||
while true do
|
||||
current_time = socket.socket.gettime()
|
||||
timeout_timer = timeout_timer - (current_time - prev_time)
|
||||
message_timer = message_timer - (current_time - prev_time)
|
||||
prev_time = current_time
|
||||
|
||||
if message_timer <= 0 and not message_queue:is_empty() then
|
||||
gui.addmessage(message_queue:shift())
|
||||
message_timer = message_interval
|
||||
end
|
||||
|
||||
if current_state == STATE_NOT_CONNECTED then
|
||||
if emu.framecount() % 60 == 0 then
|
||||
server:settimeout(2)
|
||||
local client, timeout = server:accept()
|
||||
if timeout == nil then
|
||||
print("Client connected")
|
||||
current_state = STATE_CONNECTED
|
||||
client_socket = client
|
||||
client_socket:settimeout(0)
|
||||
else
|
||||
print("No client found. Trying again...")
|
||||
end
|
||||
end
|
||||
else
|
||||
repeat
|
||||
send_receive()
|
||||
until not locked
|
||||
|
||||
if timeout_timer <= 0 then
|
||||
print("Client timed out")
|
||||
current_state = STATE_NOT_CONNECTED
|
||||
end
|
||||
end
|
||||
|
||||
coroutine.yield()
|
||||
end
|
||||
end
|
||||
|
||||
event.onexit(function ()
|
||||
print("\n-- Restarting Script --\n")
|
||||
if server ~= nil then
|
||||
server:close()
|
||||
end
|
||||
end)
|
||||
|
||||
if bizhawk_major < 2 or (bizhawk_major == 2 and bizhawk_minor < 7) then
|
||||
print("Must use BizHawk 2.7.0 or newer")
|
||||
elseif bizhawk_major > 2 or (bizhawk_major == 2 and bizhawk_minor > 9) then
|
||||
print("Warning: This version of BizHawk is newer than this script. If it doesn't work, consider downgrading to 2.9.")
|
||||
else
|
||||
if emu.getsystemid() == "NULL" then
|
||||
print("No ROM is loaded. Please load a ROM.")
|
||||
while emu.getsystemid() == "NULL" do
|
||||
emu.frameadvance()
|
||||
end
|
||||
end
|
||||
|
||||
rom_hash = gameinfo.getromhash()
|
||||
|
||||
print("Waiting for client to connect. Emulation will freeze intermittently until a client is found.\n")
|
||||
|
||||
local co = coroutine.create(main)
|
||||
function tick ()
|
||||
local status, err = coroutine.resume(co)
|
||||
|
||||
if not status then
|
||||
print("\nERROR: "..err)
|
||||
print("Consider reporting this crash.\n")
|
||||
|
||||
if server ~= nil then
|
||||
server:close()
|
||||
end
|
||||
|
||||
co = coroutine.create(main)
|
||||
end
|
||||
end
|
||||
|
||||
-- Gambatte has a setting which can cause script execution to become
|
||||
-- misaligned, so for GB and GBC we explicitly set the callback on
|
||||
-- vblank instead.
|
||||
-- https://github.com/TASEmulators/BizHawk/issues/3711
|
||||
if emu.getsystemid() == "GB" or emu.getsystemid() == "GBC" then
|
||||
event.onmemoryexecute(tick, 0x40, "tick", "System Bus")
|
||||
else
|
||||
event.onframeend(tick)
|
||||
end
|
||||
|
||||
while true do
|
||||
emu.frameadvance()
|
||||
end
|
||||
end
|
||||
@@ -559,6 +559,12 @@ def generate_basic(self) -> None:
|
||||
# in most cases it's better to do this at the same time the itempool is
|
||||
# filled to avoid accidental duplicates:
|
||||
# manually placed and still in the itempool
|
||||
|
||||
# for debugging purposes, you may want to visualize the layout of your world. Uncomment the following code to
|
||||
# write a PlantUML diagram to the file "my_world.puml" that can help you see whether your regions and locations
|
||||
# are connected and placed as desired
|
||||
# from Utils import visualize_regions
|
||||
# visualize_regions(self.multiworld.get_region("Menu", self.player), "my_world.puml")
|
||||
```
|
||||
|
||||
### Setting Rules
|
||||
|
||||
@@ -74,6 +74,7 @@ Name: "client/sni/sm"; Description: "SNI Client - Super Metroid Patch Setup";
|
||||
Name: "client/sni/dkc3"; Description: "SNI Client - Donkey Kong Country 3 Patch Setup"; Types: full playing; Flags: disablenouninstallwarning
|
||||
Name: "client/sni/smw"; Description: "SNI Client - Super Mario World Patch Setup"; Types: full playing; Flags: disablenouninstallwarning
|
||||
Name: "client/sni/l2ac"; Description: "SNI Client - Lufia II Ancient Cave Patch Setup"; Types: full playing; Flags: disablenouninstallwarning
|
||||
Name: "client/bizhawk"; Description: "BizHawk Client"; Types: full playing
|
||||
Name: "client/factorio"; Description: "Factorio"; Types: full playing
|
||||
Name: "client/kh2"; Description: "Kingdom Hearts 2"; Types: full playing
|
||||
Name: "client/minecraft"; Description: "Minecraft"; Types: full playing; ExtraDiskSpaceRequired: 226894278
|
||||
@@ -122,6 +123,7 @@ Source: "{#source_path}\ArchipelagoServer.exe"; DestDir: "{app}"; Flags: ignorev
|
||||
Source: "{#source_path}\ArchipelagoFactorioClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/factorio
|
||||
Source: "{#source_path}\ArchipelagoTextClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/text
|
||||
Source: "{#source_path}\ArchipelagoSNIClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/sni
|
||||
Source: "{#source_path}\ArchipelagoBizHawkClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/bizhawk
|
||||
Source: "{#source_path}\ArchipelagoLinksAwakeningClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/ladx
|
||||
Source: "{#source_path}\ArchipelagoLttPAdjuster.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/sni/lttp or generator/lttp
|
||||
Source: "{#source_path}\ArchipelagoMinecraftClient.exe"; DestDir: "{app}"; Flags: ignoreversion; Components: client/minecraft
|
||||
@@ -146,6 +148,7 @@ Name: "{group}\{#MyAppName} Launcher"; Filename: "{app}\ArchipelagoLauncher.exe"
|
||||
Name: "{group}\{#MyAppName} Server"; Filename: "{app}\ArchipelagoServer"; Components: server
|
||||
Name: "{group}\{#MyAppName} Text Client"; Filename: "{app}\ArchipelagoTextClient.exe"; Components: client/text
|
||||
Name: "{group}\{#MyAppName} SNI Client"; Filename: "{app}\ArchipelagoSNIClient.exe"; Components: client/sni
|
||||
Name: "{group}\{#MyAppName} BizHawk Client"; Filename: "{app}\ArchipelagoBizHawkClient.exe"; Components: client/bizhawk
|
||||
Name: "{group}\{#MyAppName} Factorio Client"; Filename: "{app}\ArchipelagoFactorioClient.exe"; Components: client/factorio
|
||||
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
|
||||
@@ -166,6 +169,7 @@ Name: "{commondesktop}\{#MyAppName} Folder"; Filename: "{app}"; Tasks: desktopic
|
||||
Name: "{commondesktop}\{#MyAppName} Launcher"; Filename: "{app}\ArchipelagoLauncher.exe"; Tasks: desktopicon
|
||||
Name: "{commondesktop}\{#MyAppName} Server"; Filename: "{app}\ArchipelagoServer"; Tasks: desktopicon; Components: server
|
||||
Name: "{commondesktop}\{#MyAppName} SNI Client"; Filename: "{app}\ArchipelagoSNIClient.exe"; Tasks: desktopicon; Components: client/sni
|
||||
Name: "{commondesktop}\{#MyAppName} BizHawk Client"; Filename: "{app}\ArchipelagoBizHawkClient.exe"; Tasks: desktopicon; Components: client/bizhawk
|
||||
Name: "{commondesktop}\{#MyAppName} Factorio Client"; Filename: "{app}\ArchipelagoFactorioClient.exe"; Tasks: desktopicon; Components: client/factorio
|
||||
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
|
||||
|
||||
5
kvui.py
5
kvui.py
@@ -7,7 +7,10 @@ if sys.platform == "win32":
|
||||
import ctypes
|
||||
# kivy 2.2.0 introduced DPI awareness on Windows, but it makes the UI enter an infinitely recursive re-layout
|
||||
# by setting the application to not DPI Aware, Windows handles scaling the entire window on its own, ignoring kivy's
|
||||
ctypes.windll.shcore.SetProcessDpiAwareness(0)
|
||||
try:
|
||||
ctypes.windll.shcore.SetProcessDpiAwareness(0)
|
||||
except FileNotFoundError: # shcore may not be found on <= Windows 7
|
||||
pass # TODO: remove silent except when Python 3.8 is phased out.
|
||||
|
||||
os.environ["KIVY_NO_CONSOLELOG"] = "1"
|
||||
os.environ["KIVY_NO_FILELOG"] = "1"
|
||||
|
||||
@@ -26,7 +26,7 @@ name: YourName{number} # Your name in-game. Spaces will be replaced with undersc
|
||||
game: # Pick a game to play
|
||||
A Link to the Past: 1
|
||||
requires:
|
||||
version: 0.3.3 # Version of Archipelago required for this yaml to work as expected.
|
||||
version: 0.4.3 # Version of Archipelago required for this yaml to work as expected.
|
||||
A Link to the Past:
|
||||
progression_balancing:
|
||||
# A system that can move progression earlier, to try and prevent the player from getting stuck and bored early.
|
||||
@@ -114,6 +114,9 @@ A Link to the Past:
|
||||
different_world: 0
|
||||
universal: 0
|
||||
start_with: 0
|
||||
key_drop_shuffle: # Shuffle keys found in pots or dropped from killed enemies
|
||||
off: 50
|
||||
on: 0
|
||||
compass_shuffle: # Compass Placement
|
||||
original_dungeon: 50
|
||||
own_dungeons: 0
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import pathlib
|
||||
import typing
|
||||
import unittest
|
||||
from argparse import Namespace
|
||||
|
||||
import Utils
|
||||
from test.general import gen_steps
|
||||
from worlds import AutoWorld
|
||||
from worlds.AutoWorld import call_all
|
||||
|
||||
file_path = pathlib.Path(__file__).parent.parent
|
||||
Utils.local_path.cached_path = file_path
|
||||
|
||||
from BaseClasses import MultiWorld, CollectionState, ItemClassification, Item
|
||||
from worlds.alttp.Items import ItemFactory
|
||||
|
||||
@@ -25,8 +20,9 @@ class TestBase(unittest.TestCase):
|
||||
state = CollectionState(self.multiworld)
|
||||
for item in items:
|
||||
item.classification = ItemClassification.progression
|
||||
state.collect(item)
|
||||
state.collect(item, event=True)
|
||||
state.sweep_for_events()
|
||||
state.update_reachable_regions(1)
|
||||
self._state_cache[self.multiworld, tuple(items)] = state
|
||||
return state
|
||||
|
||||
@@ -53,7 +49,8 @@ class TestBase(unittest.TestCase):
|
||||
with self.subTest(msg="Reach Location", location=location, access=access, items=items,
|
||||
all_except=all_except, path=path, entry=i):
|
||||
|
||||
self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), access)
|
||||
self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), access,
|
||||
f"failed {self.multiworld.get_location(location, 1)} with: {item_pool}")
|
||||
|
||||
# check for partial solution
|
||||
if not all_except and access: # we are not supposed to be able to reach location with partial inventory
|
||||
@@ -61,7 +58,10 @@ class TestBase(unittest.TestCase):
|
||||
with self.subTest(msg="Location reachable without required item", location=location,
|
||||
items=item_pool[0], missing_item=missing_item, entry=i):
|
||||
state = self._get_items_partial(item_pool, missing_item)
|
||||
self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), False)
|
||||
|
||||
self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), False,
|
||||
f"failed {self.multiworld.get_location(location, 1)}: succeeded with "
|
||||
f"{missing_item} removed from: {item_pool}")
|
||||
|
||||
def run_entrance_tests(self, access_pool):
|
||||
for i, (entrance, access, *item_pool) in enumerate(access_pool):
|
||||
@@ -80,7 +80,8 @@ class TestBase(unittest.TestCase):
|
||||
with self.subTest(msg="Entrance reachable without required item", entrance=entrance,
|
||||
items=item_pool[0], missing_item=missing_item, entry=i):
|
||||
state = self._get_items_partial(item_pool, missing_item)
|
||||
self.assertEqual(self.multiworld.get_entrance(entrance, 1).can_reach(state), False)
|
||||
self.assertEqual(self.multiworld.get_entrance(entrance, 1).can_reach(state), False,
|
||||
f"failed {self.multiworld.get_entrance(entrance, 1)} with: {item_pool}")
|
||||
|
||||
def _get_items(self, item_pool, all_except):
|
||||
if all_except and len(all_except) > 0:
|
||||
@@ -135,13 +136,16 @@ class WorldTestBase(unittest.TestCase):
|
||||
call_all(self.multiworld, step)
|
||||
|
||||
# methods that can be called within tests
|
||||
def collect_all_but(self, item_names: typing.Union[str, typing.Iterable[str]]) -> None:
|
||||
def collect_all_but(self, item_names: typing.Union[str, typing.Iterable[str]],
|
||||
state: typing.Optional[CollectionState] = None) -> None:
|
||||
"""Collects all pre-placed items and items in the multiworld itempool except those provided"""
|
||||
if isinstance(item_names, str):
|
||||
item_names = (item_names,)
|
||||
if not state:
|
||||
state = self.multiworld.state
|
||||
for item in self.multiworld.get_items():
|
||||
if item.name not in item_names:
|
||||
self.multiworld.state.collect(item)
|
||||
state.collect(item)
|
||||
|
||||
def get_item_by_name(self, item_name: str) -> Item:
|
||||
"""Returns the first item found in placed items, or in the itempool with the matching name"""
|
||||
@@ -168,6 +172,12 @@ class WorldTestBase(unittest.TestCase):
|
||||
items = (items,)
|
||||
for item in items:
|
||||
self.multiworld.state.collect(item)
|
||||
|
||||
def remove_by_name(self, item_names: typing.Union[str, typing.Iterable[str]]) -> typing.List[Item]:
|
||||
"""Remove all of the items in the item pool with the given names from state"""
|
||||
items = self.get_items_by_name(item_names)
|
||||
self.remove(items)
|
||||
return items
|
||||
|
||||
def remove(self, items: typing.Union[Item, typing.Iterable[Item]]) -> None:
|
||||
"""Removes the provided item(s) from state"""
|
||||
@@ -192,23 +202,32 @@ class WorldTestBase(unittest.TestCase):
|
||||
|
||||
def assertAccessDependency(self,
|
||||
locations: typing.List[str],
|
||||
possible_items: typing.Iterable[typing.Iterable[str]]) -> None:
|
||||
possible_items: typing.Iterable[typing.Iterable[str]],
|
||||
only_check_listed: bool = False) -> None:
|
||||
"""Asserts that the provided locations can't be reached without the listed items but can be reached with any
|
||||
one of the provided combinations"""
|
||||
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.multiworld.get_locations():
|
||||
loc_reachable = self.multiworld.state.can_reach(location)
|
||||
self.assertEqual(loc_reachable, location.name not in locations,
|
||||
f"{location.name} is reachable without {all_items}" if loc_reachable
|
||||
else f"{location.name} is not reachable without {all_items}")
|
||||
for item_names in possible_items:
|
||||
items = self.collect_by_name(item_names)
|
||||
state = CollectionState(self.multiworld)
|
||||
self.collect_all_but(all_items, state)
|
||||
if only_check_listed:
|
||||
for location in locations:
|
||||
self.assertTrue(self.can_reach_location(location),
|
||||
self.assertFalse(state.can_reach(location, "Location", 1), f"{location} is reachable without {all_items}")
|
||||
else:
|
||||
for location in self.multiworld.get_locations():
|
||||
loc_reachable = state.can_reach(location, "Location", 1)
|
||||
self.assertEqual(loc_reachable, location.name not in locations,
|
||||
f"{location.name} is reachable without {all_items}" if loc_reachable
|
||||
else f"{location.name} is not reachable without {all_items}")
|
||||
for item_names in possible_items:
|
||||
items = self.get_items_by_name(item_names)
|
||||
for item in items:
|
||||
state.collect(item)
|
||||
for location in locations:
|
||||
self.assertTrue(state.can_reach(location, "Location", 1),
|
||||
f"{location} not reachable with {item_names}")
|
||||
self.remove(items)
|
||||
for item in items:
|
||||
state.remove(item)
|
||||
|
||||
def assertBeatable(self, beatable: bool):
|
||||
"""Asserts that the game can be beaten with the current state"""
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import pathlib
|
||||
import warnings
|
||||
|
||||
import settings
|
||||
@@ -5,3 +6,12 @@ import settings
|
||||
warnings.simplefilter("always")
|
||||
settings.no_gui = True
|
||||
settings.skip_autosave = True
|
||||
|
||||
import ModuleUpdate
|
||||
|
||||
ModuleUpdate.update_ran = True # don't upgrade
|
||||
|
||||
import Utils
|
||||
|
||||
Utils.local_path.cached_path = pathlib.Path(__file__).parent.parent
|
||||
Utils.user_path() # initialize cached_path
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
# Tests for Generate.py (ArchipelagoGenerate.exe)
|
||||
|
||||
import unittest
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
import os.path
|
||||
import os
|
||||
import ModuleUpdate
|
||||
ModuleUpdate.update_ran = True # don't upgrade
|
||||
|
||||
import Generate
|
||||
|
||||
|
||||
|
||||
@@ -358,6 +358,21 @@ class World(metaclass=AutoWorldRegister):
|
||||
logging.warning(f"World {self} is generating a filler item without custom filler pool.")
|
||||
return self.multiworld.random.choice(tuple(self.item_name_to_id.keys()))
|
||||
|
||||
@classmethod
|
||||
def create_group(cls, multiworld: "MultiWorld", new_player_id: int, players: Set[int]) -> World:
|
||||
"""Creates a group, which is an instance of World that is responsible for multiple others.
|
||||
An example case is ItemLinks creating these."""
|
||||
import Options
|
||||
|
||||
for option_key, option in cls.option_definitions.items():
|
||||
getattr(multiworld, option_key)[new_player_id] = option(option.default)
|
||||
for option_key, option in Options.common_options.items():
|
||||
getattr(multiworld, option_key)[new_player_id] = option(option.default)
|
||||
for option_key, option in Options.per_game_common_options.items():
|
||||
getattr(multiworld, option_key)[new_player_id] = option(option.default)
|
||||
|
||||
return cls(multiworld, new_player_id)
|
||||
|
||||
# decent place to implement progressive items, in most cases can stay as-is
|
||||
def collect_item(self, state: "CollectionState", item: "Item", remove: bool = False) -> Optional[str]:
|
||||
"""Collect an item name into state. For speed reasons items that aren't logically useful get skipped.
|
||||
|
||||
@@ -89,6 +89,9 @@ components: List[Component] = [
|
||||
Component('SNI Client', 'SNIClient',
|
||||
file_identifier=SuffixIdentifier('.apz3', '.apm3', '.apsoe', '.aplttp', '.apsm', '.apsmz3', '.apdkc3',
|
||||
'.apsmw', '.apl2ac')),
|
||||
# BizHawk
|
||||
Component("BizHawk Client", "BizHawkClient", component_type=Type.CLIENT,
|
||||
file_identifier=SuffixIdentifier()),
|
||||
Component('Links Awakening DX Client', 'LinksAwakeningClient',
|
||||
file_identifier=SuffixIdentifier('.apladx')),
|
||||
Component('LttP Adjuster', 'LttPAdjuster'),
|
||||
|
||||
326
worlds/_bizhawk/__init__.py
Normal file
326
worlds/_bizhawk/__init__.py
Normal file
@@ -0,0 +1,326 @@
|
||||
"""
|
||||
A module for interacting with BizHawk through `connector_bizhawk_generic.lua`.
|
||||
|
||||
Any mention of `domain` in this module refers to the names BizHawk gives to memory domains in its own lua api. They are
|
||||
naively passed to BizHawk without validation or modification.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import enum
|
||||
import json
|
||||
import typing
|
||||
|
||||
|
||||
BIZHAWK_SOCKET_PORT = 43055
|
||||
EXPECTED_SCRIPT_VERSION = 1
|
||||
|
||||
|
||||
class ConnectionStatus(enum.IntEnum):
|
||||
NOT_CONNECTED = 1
|
||||
TENTATIVE = 2
|
||||
CONNECTED = 3
|
||||
|
||||
|
||||
class BizHawkContext:
|
||||
streams: typing.Optional[typing.Tuple[asyncio.StreamReader, asyncio.StreamWriter]]
|
||||
connection_status: ConnectionStatus
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.streams = None
|
||||
self.connection_status = ConnectionStatus.NOT_CONNECTED
|
||||
|
||||
|
||||
class NotConnectedError(Exception):
|
||||
"""Raised when something tries to make a request to the connector script before a connection has been established"""
|
||||
pass
|
||||
|
||||
|
||||
class RequestFailedError(Exception):
|
||||
"""Raised when the connector script did not respond to a request"""
|
||||
pass
|
||||
|
||||
|
||||
class ConnectorError(Exception):
|
||||
"""Raised when the connector script encounters an error while processing a request"""
|
||||
pass
|
||||
|
||||
|
||||
class SyncError(Exception):
|
||||
"""Raised when the connector script responded with a mismatched response type"""
|
||||
pass
|
||||
|
||||
|
||||
async def connect(ctx: BizHawkContext) -> bool:
|
||||
"""Attempts to establish a connection with the connector script. Returns True if successful."""
|
||||
try:
|
||||
ctx.streams = await asyncio.open_connection("localhost", BIZHAWK_SOCKET_PORT)
|
||||
ctx.connection_status = ConnectionStatus.TENTATIVE
|
||||
return True
|
||||
except (TimeoutError, ConnectionRefusedError):
|
||||
ctx.streams = None
|
||||
ctx.connection_status = ConnectionStatus.NOT_CONNECTED
|
||||
return False
|
||||
|
||||
|
||||
def disconnect(ctx: BizHawkContext) -> None:
|
||||
"""Closes the connection to the connector script."""
|
||||
if ctx.streams is not None:
|
||||
ctx.streams[1].close()
|
||||
ctx.streams = None
|
||||
ctx.connection_status = ConnectionStatus.NOT_CONNECTED
|
||||
|
||||
|
||||
async def get_script_version(ctx: BizHawkContext) -> int:
|
||||
if ctx.streams is None:
|
||||
raise NotConnectedError("You tried to send a request before a connection to BizHawk was made")
|
||||
|
||||
try:
|
||||
reader, writer = ctx.streams
|
||||
writer.write("VERSION".encode("ascii") + b"\n")
|
||||
await asyncio.wait_for(writer.drain(), timeout=5)
|
||||
|
||||
version = await asyncio.wait_for(reader.readline(), timeout=5)
|
||||
|
||||
if version == b"":
|
||||
writer.close()
|
||||
ctx.streams = None
|
||||
ctx.connection_status = ConnectionStatus.NOT_CONNECTED
|
||||
raise RequestFailedError("Connection closed")
|
||||
|
||||
return int(version.decode("ascii"))
|
||||
except asyncio.TimeoutError as exc:
|
||||
writer.close()
|
||||
ctx.streams = None
|
||||
ctx.connection_status = ConnectionStatus.NOT_CONNECTED
|
||||
raise RequestFailedError("Connection timed out") from exc
|
||||
except ConnectionResetError as exc:
|
||||
writer.close()
|
||||
ctx.streams = None
|
||||
ctx.connection_status = ConnectionStatus.NOT_CONNECTED
|
||||
raise RequestFailedError("Connection reset") from exc
|
||||
|
||||
|
||||
async def send_requests(ctx: BizHawkContext, req_list: typing.List[typing.Dict[str, typing.Any]]) -> typing.List[typing.Dict[str, typing.Any]]:
|
||||
"""Sends a list of requests to the BizHawk connector and returns their responses.
|
||||
|
||||
It's likely you want to use the wrapper functions instead of this."""
|
||||
if ctx.streams is None:
|
||||
raise NotConnectedError("You tried to send a request before a connection to BizHawk was made")
|
||||
|
||||
try:
|
||||
reader, writer = ctx.streams
|
||||
writer.write(json.dumps(req_list).encode("utf-8") + b"\n")
|
||||
await asyncio.wait_for(writer.drain(), timeout=5)
|
||||
|
||||
res = await asyncio.wait_for(reader.readline(), timeout=5)
|
||||
|
||||
if res == b"":
|
||||
writer.close()
|
||||
ctx.streams = None
|
||||
ctx.connection_status = ConnectionStatus.NOT_CONNECTED
|
||||
raise RequestFailedError("Connection closed")
|
||||
|
||||
if ctx.connection_status == ConnectionStatus.TENTATIVE:
|
||||
ctx.connection_status = ConnectionStatus.CONNECTED
|
||||
|
||||
ret = json.loads(res.decode("utf-8"))
|
||||
for response in ret:
|
||||
if response["type"] == "ERROR":
|
||||
raise ConnectorError(response["err"])
|
||||
|
||||
return ret
|
||||
except asyncio.TimeoutError as exc:
|
||||
writer.close()
|
||||
ctx.streams = None
|
||||
ctx.connection_status = ConnectionStatus.NOT_CONNECTED
|
||||
raise RequestFailedError("Connection timed out") from exc
|
||||
except ConnectionResetError as exc:
|
||||
writer.close()
|
||||
ctx.streams = None
|
||||
ctx.connection_status = ConnectionStatus.NOT_CONNECTED
|
||||
raise RequestFailedError("Connection reset") from exc
|
||||
|
||||
|
||||
async def ping(ctx: BizHawkContext) -> None:
|
||||
"""Sends a PING request and receives a PONG response."""
|
||||
res = (await send_requests(ctx, [{"type": "PING"}]))[0]
|
||||
|
||||
if res["type"] != "PONG":
|
||||
raise SyncError(f"Expected response of type PONG but got {res['type']}")
|
||||
|
||||
|
||||
async def get_hash(ctx: BizHawkContext) -> str:
|
||||
"""Gets the system name for the currently loaded ROM"""
|
||||
res = (await send_requests(ctx, [{"type": "HASH"}]))[0]
|
||||
|
||||
if res["type"] != "HASH_RESPONSE":
|
||||
raise SyncError(f"Expected response of type HASH_RESPONSE but got {res['type']}")
|
||||
|
||||
return res["value"]
|
||||
|
||||
|
||||
async def get_system(ctx: BizHawkContext) -> str:
|
||||
"""Gets the system name for the currently loaded ROM"""
|
||||
res = (await send_requests(ctx, [{"type": "SYSTEM"}]))[0]
|
||||
|
||||
if res["type"] != "SYSTEM_RESPONSE":
|
||||
raise SyncError(f"Expected response of type SYSTEM_RESPONSE but got {res['type']}")
|
||||
|
||||
return res["value"]
|
||||
|
||||
|
||||
async def get_cores(ctx: BizHawkContext) -> typing.Dict[str, str]:
|
||||
"""Gets the preferred cores for systems with multiple cores. Only systems with multiple available cores have
|
||||
entries."""
|
||||
res = (await send_requests(ctx, [{"type": "PREFERRED_CORES"}]))[0]
|
||||
|
||||
if res["type"] != "PREFERRED_CORES_RESPONSE":
|
||||
raise SyncError(f"Expected response of type PREFERRED_CORES_RESPONSE but got {res['type']}")
|
||||
|
||||
return res["value"]
|
||||
|
||||
|
||||
async def lock(ctx: BizHawkContext) -> None:
|
||||
"""Locks BizHawk in anticipation of receiving more requests this frame.
|
||||
|
||||
Consider using guarded reads and writes instead of locks if possible.
|
||||
|
||||
While locked, emulation will halt and the connector will block on incoming requests until an `UNLOCK` request is
|
||||
sent. Remember to unlock when you're done, or the emulator will appear to freeze.
|
||||
|
||||
Sending multiple lock commands is the same as sending one."""
|
||||
res = (await send_requests(ctx, [{"type": "LOCK"}]))[0]
|
||||
|
||||
if res["type"] != "LOCKED":
|
||||
raise SyncError(f"Expected response of type LOCKED but got {res['type']}")
|
||||
|
||||
|
||||
async def unlock(ctx: BizHawkContext) -> None:
|
||||
"""Unlocks BizHawk to allow it to resume emulation. See `lock` for more info.
|
||||
|
||||
Sending multiple unlock commands is the same as sending one."""
|
||||
res = (await send_requests(ctx, [{"type": "UNLOCK"}]))[0]
|
||||
|
||||
if res["type"] != "UNLOCKED":
|
||||
raise SyncError(f"Expected response of type UNLOCKED but got {res['type']}")
|
||||
|
||||
|
||||
async def display_message(ctx: BizHawkContext, message: str) -> None:
|
||||
"""Displays the provided message in BizHawk's message queue."""
|
||||
res = (await send_requests(ctx, [{"type": "DISPLAY_MESSAGE", "message": message}]))[0]
|
||||
|
||||
if res["type"] != "DISPLAY_MESSAGE_RESPONSE":
|
||||
raise SyncError(f"Expected response of type DISPLAY_MESSAGE_RESPONSE but got {res['type']}")
|
||||
|
||||
|
||||
async def set_message_interval(ctx: BizHawkContext, value: float) -> None:
|
||||
"""Sets the minimum amount of time in seconds to wait between queued messages. The default value of 0 will allow one
|
||||
new message to display per frame."""
|
||||
res = (await send_requests(ctx, [{"type": "SET_MESSAGE_INTERVAL", "value": value}]))[0]
|
||||
|
||||
if res["type"] != "SET_MESSAGE_INTERVAL_RESPONSE":
|
||||
raise SyncError(f"Expected response of type SET_MESSAGE_INTERVAL_RESPONSE but got {res['type']}")
|
||||
|
||||
|
||||
async def guarded_read(ctx: BizHawkContext, read_list: typing.List[typing.Tuple[int, int, str]],
|
||||
guard_list: typing.List[typing.Tuple[int, typing.Iterable[int], str]]) -> typing.Optional[typing.List[bytes]]:
|
||||
"""Reads an array of bytes at 1 or more addresses if and only if every byte in guard_list matches its expected
|
||||
value.
|
||||
|
||||
Items in read_list should be organized (address, size, domain) where
|
||||
- `address` is the address of the first byte of data
|
||||
- `size` is the number of bytes to read
|
||||
- `domain` is the name of the region of memory the address corresponds to
|
||||
|
||||
Items in `guard_list` should be organized `(address, expected_data, domain)` where
|
||||
- `address` is the address of the first byte of data
|
||||
- `expected_data` is the bytes that the data starting at this address is expected to match
|
||||
- `domain` is the name of the region of memory the address corresponds to
|
||||
|
||||
Returns None if any item in guard_list failed to validate. Otherwise returns a list of bytes in the order they
|
||||
were requested."""
|
||||
res = await send_requests(ctx, [{
|
||||
"type": "GUARD",
|
||||
"address": address,
|
||||
"expected_data": base64.b64encode(bytes(expected_data)).decode("ascii"),
|
||||
"domain": domain
|
||||
} for address, expected_data, domain in guard_list] + [{
|
||||
"type": "READ",
|
||||
"address": address,
|
||||
"size": size,
|
||||
"domain": domain
|
||||
} for address, size, domain in read_list])
|
||||
|
||||
ret: typing.List[bytes] = []
|
||||
for item in res:
|
||||
if item["type"] == "GUARD_RESPONSE":
|
||||
if not item["value"]:
|
||||
return None
|
||||
else:
|
||||
if item["type"] != "READ_RESPONSE":
|
||||
raise SyncError(f"Expected response of type READ_RESPONSE or GUARD_RESPONSE but got {res['type']}")
|
||||
|
||||
ret.append(base64.b64decode(item["value"]))
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
async def read(ctx: BizHawkContext, read_list: typing.List[typing.Tuple[int, int, str]]) -> typing.List[bytes]:
|
||||
"""Reads data at 1 or more addresses.
|
||||
|
||||
Items in `read_list` should be organized `(address, size, domain)` where
|
||||
- `address` is the address of the first byte of data
|
||||
- `size` is the number of bytes to read
|
||||
- `domain` is the name of the region of memory the address corresponds to
|
||||
|
||||
Returns a list of bytes in the order they were requested."""
|
||||
return await guarded_read(ctx, read_list, [])
|
||||
|
||||
|
||||
async def guarded_write(ctx: BizHawkContext, write_list: typing.List[typing.Tuple[int, typing.Iterable[int], str]],
|
||||
guard_list: typing.List[typing.Tuple[int, typing.Iterable[int], str]]) -> bool:
|
||||
"""Writes data to 1 or more addresses if and only if every byte in guard_list matches its expected value.
|
||||
|
||||
Items in `write_list` should be organized `(address, value, domain)` where
|
||||
- `address` is the address of the first byte of data
|
||||
- `value` is a list of bytes to write, in order, starting at `address`
|
||||
- `domain` is the name of the region of memory the address corresponds to
|
||||
|
||||
Items in `guard_list` should be organized `(address, expected_data, domain)` where
|
||||
- `address` is the address of the first byte of data
|
||||
- `expected_data` is the bytes that the data starting at this address is expected to match
|
||||
- `domain` is the name of the region of memory the address corresponds to
|
||||
|
||||
Returns False if any item in guard_list failed to validate. Otherwise returns True."""
|
||||
res = await send_requests(ctx, [{
|
||||
"type": "GUARD",
|
||||
"address": address,
|
||||
"expected_data": base64.b64encode(bytes(expected_data)).decode("ascii"),
|
||||
"domain": domain
|
||||
} for address, expected_data, domain in guard_list] + [{
|
||||
"type": "WRITE",
|
||||
"address": address,
|
||||
"value": base64.b64encode(bytes(value)).decode("ascii"),
|
||||
"domain": domain
|
||||
} for address, value, domain in write_list])
|
||||
|
||||
for item in res:
|
||||
if item["type"] == "GUARD_RESPONSE":
|
||||
if not item["value"]:
|
||||
return False
|
||||
else:
|
||||
if item["type"] != "WRITE_RESPONSE":
|
||||
raise SyncError(f"Expected response of type WRITE_RESPONSE or GUARD_RESPONSE but got {res['type']}")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def write(ctx: BizHawkContext, write_list: typing.List[typing.Tuple[int, typing.Iterable[int], str]]) -> None:
|
||||
"""Writes data to 1 or more addresses.
|
||||
|
||||
Items in write_list should be organized `(address, value, domain)` where
|
||||
- `address` is the address of the first byte of data
|
||||
- `value` is a list of bytes to write, in order, starting at `address`
|
||||
- `domain` is the name of the region of memory the address corresponds to"""
|
||||
await guarded_write(ctx, write_list, [])
|
||||
87
worlds/_bizhawk/client.py
Normal file
87
worlds/_bizhawk/client.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""
|
||||
A module containing the BizHawkClient base class and metaclass
|
||||
"""
|
||||
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
from typing import TYPE_CHECKING, Any, ClassVar, Dict, Optional, Tuple, Union
|
||||
|
||||
from worlds.LauncherComponents import Component, SuffixIdentifier, Type, components, launch_subprocess
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .context import BizHawkClientContext
|
||||
else:
|
||||
BizHawkClientContext = object
|
||||
|
||||
|
||||
class AutoBizHawkClientRegister(abc.ABCMeta):
|
||||
game_handlers: ClassVar[Dict[Tuple[str, ...], Dict[str, BizHawkClient]]] = {}
|
||||
|
||||
def __new__(cls, name: str, bases: Tuple[type, ...], namespace: Dict[str, Any]) -> AutoBizHawkClientRegister:
|
||||
new_class = super().__new__(cls, name, bases, namespace)
|
||||
|
||||
if "system" in namespace:
|
||||
systems = (namespace["system"],) if type(namespace["system"]) is str else tuple(sorted(namespace["system"]))
|
||||
if systems not in AutoBizHawkClientRegister.game_handlers:
|
||||
AutoBizHawkClientRegister.game_handlers[systems] = {}
|
||||
|
||||
if "game" in namespace:
|
||||
AutoBizHawkClientRegister.game_handlers[systems][namespace["game"]] = new_class()
|
||||
|
||||
return new_class
|
||||
|
||||
@staticmethod
|
||||
async def get_handler(ctx: BizHawkClientContext, system: str) -> Optional[BizHawkClient]:
|
||||
for systems, handlers in AutoBizHawkClientRegister.game_handlers.items():
|
||||
if system in systems:
|
||||
for handler in handlers.values():
|
||||
if await handler.validate_rom(ctx):
|
||||
return handler
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class BizHawkClient(abc.ABC, metaclass=AutoBizHawkClientRegister):
|
||||
system: ClassVar[Union[str, Tuple[str, ...]]]
|
||||
"""The system that the game this client is for runs on"""
|
||||
|
||||
game: ClassVar[str]
|
||||
"""The game this client is for"""
|
||||
|
||||
@abc.abstractmethod
|
||||
async def validate_rom(self, ctx: BizHawkClientContext) -> bool:
|
||||
"""Should return whether the currently loaded ROM should be handled by this client. You might read the game name
|
||||
from the ROM header, for example. This function will only be asked to validate ROMs from the system set by the
|
||||
client class, so you do not need to check the system yourself.
|
||||
|
||||
Once this function has determined that the ROM should be handled by this client, it should also modify `ctx`
|
||||
as necessary (such as setting `ctx.game = self.game`, modifying `ctx.items_handling`, etc...)."""
|
||||
...
|
||||
|
||||
async def set_auth(self, ctx: BizHawkClientContext) -> None:
|
||||
"""Should set ctx.auth in anticipation of sending a `Connected` packet. You may override this if you store slot
|
||||
name in your patched ROM. If ctx.auth is not set after calling, the player will be prompted to enter their
|
||||
username."""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
async def game_watcher(self, ctx: BizHawkClientContext) -> None:
|
||||
"""Runs on a loop with the approximate interval `ctx.watcher_timeout`. The currently loaded ROM is guaranteed
|
||||
to have passed your validator when this function is called, and the emulator is very likely to be connected."""
|
||||
...
|
||||
|
||||
def on_package(self, ctx: BizHawkClientContext, cmd: str, args: dict) -> None:
|
||||
"""For handling packages from the server. Called from `BizHawkClientContext.on_package`."""
|
||||
pass
|
||||
|
||||
|
||||
def launch_client(*args) -> None:
|
||||
from .context import launch
|
||||
launch_subprocess(launch, name="BizHawkClient")
|
||||
|
||||
|
||||
if not any(component.script_name == "BizHawkClient" for component in components):
|
||||
components.append(Component("BizHawk Client", "BizHawkClient", component_type=Type.CLIENT, func=launch_client,
|
||||
file_identifier=SuffixIdentifier()))
|
||||
188
worlds/_bizhawk/context.py
Normal file
188
worlds/_bizhawk/context.py
Normal file
@@ -0,0 +1,188 @@
|
||||
"""
|
||||
A module containing context and functions relevant to running the client. This module should only be imported for type
|
||||
checking or launching the client, otherwise it will probably cause circular import issues.
|
||||
"""
|
||||
|
||||
|
||||
import asyncio
|
||||
import traceback
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from CommonClient import CommonContext, ClientCommandProcessor, get_base_parser, server_loop, logger, gui_enabled
|
||||
import Patch
|
||||
import Utils
|
||||
|
||||
from . import BizHawkContext, ConnectionStatus, RequestFailedError, connect, disconnect, get_hash, get_script_version, \
|
||||
get_system, ping
|
||||
from .client import BizHawkClient, AutoBizHawkClientRegister
|
||||
|
||||
|
||||
EXPECTED_SCRIPT_VERSION = 1
|
||||
|
||||
|
||||
class BizHawkClientCommandProcessor(ClientCommandProcessor):
|
||||
def _cmd_bh(self):
|
||||
"""Shows the current status of the client's connection to BizHawk"""
|
||||
if isinstance(self.ctx, BizHawkClientContext):
|
||||
if self.ctx.bizhawk_ctx.connection_status == ConnectionStatus.NOT_CONNECTED:
|
||||
logger.info("BizHawk Connection Status: Not Connected")
|
||||
elif self.ctx.bizhawk_ctx.connection_status == ConnectionStatus.TENTATIVE:
|
||||
logger.info("BizHawk Connection Status: Tentatively Connected")
|
||||
elif self.ctx.bizhawk_ctx.connection_status == ConnectionStatus.CONNECTED:
|
||||
logger.info("BizHawk Connection Status: Connected")
|
||||
|
||||
|
||||
class BizHawkClientContext(CommonContext):
|
||||
command_processor = BizHawkClientCommandProcessor
|
||||
client_handler: Optional[BizHawkClient]
|
||||
slot_data: Optional[Dict[str, Any]] = None
|
||||
rom_hash: Optional[str] = None
|
||||
bizhawk_ctx: BizHawkContext
|
||||
|
||||
watcher_timeout: float
|
||||
"""The maximum amount of time the game watcher loop will wait for an update from the server before executing"""
|
||||
|
||||
def __init__(self, server_address: Optional[str], password: Optional[str]):
|
||||
super().__init__(server_address, password)
|
||||
self.client_handler = None
|
||||
self.bizhawk_ctx = BizHawkContext()
|
||||
self.watcher_timeout = 0.5
|
||||
|
||||
def run_gui(self):
|
||||
from kvui import GameManager
|
||||
|
||||
class BizHawkManager(GameManager):
|
||||
base_title = "Archipelago BizHawk Client"
|
||||
|
||||
self.ui = BizHawkManager(self)
|
||||
self.ui_task = asyncio.create_task(self.ui.async_run(), name="UI")
|
||||
|
||||
def on_package(self, cmd, args):
|
||||
if cmd == "Connected":
|
||||
self.slot_data = args.get("slot_data", None)
|
||||
|
||||
if self.client_handler is not None:
|
||||
self.client_handler.on_package(self, cmd, args)
|
||||
|
||||
|
||||
async def _game_watcher(ctx: BizHawkClientContext):
|
||||
showed_connecting_message = False
|
||||
showed_connected_message = False
|
||||
showed_no_handler_message = False
|
||||
|
||||
while not ctx.exit_event.is_set():
|
||||
try:
|
||||
await asyncio.wait_for(ctx.watcher_event.wait(), ctx.watcher_timeout)
|
||||
except asyncio.TimeoutError:
|
||||
pass
|
||||
|
||||
ctx.watcher_event.clear()
|
||||
|
||||
try:
|
||||
if ctx.bizhawk_ctx.connection_status == ConnectionStatus.NOT_CONNECTED:
|
||||
showed_connected_message = False
|
||||
|
||||
if not showed_connecting_message:
|
||||
logger.info("Waiting to connect to BizHawk...")
|
||||
showed_connecting_message = True
|
||||
|
||||
if not await connect(ctx.bizhawk_ctx):
|
||||
continue
|
||||
|
||||
showed_no_handler_message = False
|
||||
|
||||
script_version = await get_script_version(ctx.bizhawk_ctx)
|
||||
|
||||
if script_version != EXPECTED_SCRIPT_VERSION:
|
||||
logger.info(f"Connector script is incompatible. Expected version {EXPECTED_SCRIPT_VERSION} but got {script_version}. Disconnecting.")
|
||||
disconnect(ctx.bizhawk_ctx)
|
||||
continue
|
||||
|
||||
showed_connecting_message = False
|
||||
|
||||
await ping(ctx.bizhawk_ctx)
|
||||
|
||||
if not showed_connected_message:
|
||||
showed_connected_message = True
|
||||
logger.info("Connected to BizHawk")
|
||||
|
||||
rom_hash = await get_hash(ctx.bizhawk_ctx)
|
||||
if ctx.rom_hash is not None and ctx.rom_hash != rom_hash:
|
||||
if ctx.server is not None:
|
||||
logger.info(f"ROM changed. Disconnecting from server.")
|
||||
await ctx.disconnect(True)
|
||||
|
||||
ctx.auth = None
|
||||
ctx.username = None
|
||||
ctx.rom_hash = rom_hash
|
||||
|
||||
if ctx.client_handler is None:
|
||||
system = await get_system(ctx.bizhawk_ctx)
|
||||
ctx.client_handler = await AutoBizHawkClientRegister.get_handler(ctx, system)
|
||||
|
||||
if ctx.client_handler is None:
|
||||
if not showed_no_handler_message:
|
||||
logger.info("No handler was found for this game")
|
||||
showed_no_handler_message = True
|
||||
continue
|
||||
else:
|
||||
showed_no_handler_message = False
|
||||
logger.info(f"Running handler for {ctx.client_handler.game}")
|
||||
|
||||
except RequestFailedError as exc:
|
||||
logger.info(f"Lost connection to BizHawk: {exc.args[0]}")
|
||||
continue
|
||||
|
||||
# Get slot name and send `Connect`
|
||||
if ctx.server is not None and ctx.username is None:
|
||||
await ctx.client_handler.set_auth(ctx)
|
||||
|
||||
if ctx.auth is None:
|
||||
await ctx.get_username()
|
||||
|
||||
await ctx.send_connect()
|
||||
|
||||
await ctx.client_handler.game_watcher(ctx)
|
||||
|
||||
|
||||
async def _run_game(rom: str):
|
||||
import webbrowser
|
||||
webbrowser.open(rom)
|
||||
|
||||
|
||||
async def _patch_and_run_game(patch_file: str):
|
||||
metadata, output_file = Patch.create_rom_file(patch_file)
|
||||
Utils.async_start(_run_game(output_file))
|
||||
|
||||
|
||||
def launch() -> None:
|
||||
async def main():
|
||||
parser = get_base_parser()
|
||||
parser.add_argument("patch_file", default="", type=str, nargs="?", help="Path to an Archipelago patch file")
|
||||
args = parser.parse_args()
|
||||
|
||||
ctx = BizHawkClientContext(args.connect, args.password)
|
||||
ctx.server_task = asyncio.create_task(server_loop(ctx), name="ServerLoop")
|
||||
|
||||
if gui_enabled:
|
||||
ctx.run_gui()
|
||||
ctx.run_cli()
|
||||
|
||||
if args.patch_file != "":
|
||||
Utils.async_start(_patch_and_run_game(args.patch_file))
|
||||
|
||||
watcher_task = asyncio.create_task(_game_watcher(ctx), name="GameWatcher")
|
||||
|
||||
try:
|
||||
await watcher_task
|
||||
except Exception as e:
|
||||
logger.error("".join(traceback.format_exception(e)))
|
||||
|
||||
await ctx.exit_event.wait()
|
||||
await ctx.shutdown()
|
||||
|
||||
Utils.init_logging("BizHawkClient", exception_logger="Client")
|
||||
import colorama
|
||||
colorama.init()
|
||||
asyncio.run(main())
|
||||
colorama.deinit()
|
||||
@@ -107,7 +107,7 @@ location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10),
|
||||
"Hyrule Castle - Zelda's Chest": (0x80, 0x10),
|
||||
'Hyrule Castle - Big Key Drop': (0x80, 0x400),
|
||||
'Sewers - Dark Cross': (0x32, 0x10),
|
||||
'Hyrule Castle - Key Rat Key Drop': (0x21, 0x400),
|
||||
'Sewers - Key Rat Key Drop': (0x21, 0x400),
|
||||
'Sewers - Secret Room - Left': (0x11, 0x10),
|
||||
'Sewers - Secret Room - Middle': (0x11, 0x20),
|
||||
'Sewers - Secret Room - Right': (0x11, 0x40),
|
||||
|
||||
@@ -8,7 +8,7 @@ from Fill import fill_restrictive
|
||||
|
||||
from .Bosses import BossFactory, Boss
|
||||
from .Items import ItemFactory
|
||||
from .Regions import lookup_boss_drops
|
||||
from .Regions import lookup_boss_drops, key_drop_data
|
||||
from .Options import smallkey_shuffle
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
@@ -81,15 +81,17 @@ def create_dungeons(world: "ALTTPWorld"):
|
||||
return dungeon
|
||||
|
||||
ES = make_dungeon('Hyrule Castle', None, ['Hyrule Castle', 'Sewers', 'Sewer Drop', 'Sewers (Dark)', 'Sanctuary'],
|
||||
None, [ItemFactory('Small Key (Hyrule Castle)', player)],
|
||||
ItemFactory('Big Key (Hyrule Castle)', player),
|
||||
ItemFactory(['Small Key (Hyrule Castle)'] * 4, player),
|
||||
[ItemFactory('Map (Hyrule Castle)', player)])
|
||||
EP = make_dungeon('Eastern Palace', 'Armos Knights', ['Eastern Palace'],
|
||||
ItemFactory('Big Key (Eastern Palace)', player), [],
|
||||
ItemFactory('Big Key (Eastern Palace)', player),
|
||||
ItemFactory(['Small Key (Eastern Palace)'] * 2, player),
|
||||
ItemFactory(['Map (Eastern Palace)', 'Compass (Eastern Palace)'], player))
|
||||
DP = make_dungeon('Desert Palace', 'Lanmolas',
|
||||
['Desert Palace North', 'Desert Palace Main (Inner)', 'Desert Palace Main (Outer)',
|
||||
'Desert Palace East'], ItemFactory('Big Key (Desert Palace)', player),
|
||||
[ItemFactory('Small Key (Desert Palace)', player)],
|
||||
ItemFactory(['Small Key (Desert Palace)'] * 4, player),
|
||||
ItemFactory(['Map (Desert Palace)', 'Compass (Desert Palace)'], player))
|
||||
ToH = make_dungeon('Tower of Hera', 'Moldorm',
|
||||
['Tower of Hera (Bottom)', 'Tower of Hera (Basement)', 'Tower of Hera (Top)'],
|
||||
@@ -105,7 +107,8 @@ def create_dungeons(world: "ALTTPWorld"):
|
||||
ItemFactory(['Small Key (Palace of Darkness)'] * 6, player),
|
||||
ItemFactory(['Map (Palace of Darkness)', 'Compass (Palace of Darkness)'], player))
|
||||
TT = make_dungeon('Thieves Town', 'Blind', ['Thieves Town (Entrance)', 'Thieves Town (Deep)', 'Blind Fight'],
|
||||
ItemFactory('Big Key (Thieves Town)', player), [ItemFactory('Small Key (Thieves Town)', player)],
|
||||
ItemFactory('Big Key (Thieves Town)', player),
|
||||
ItemFactory(['Small Key (Thieves Town)'] * 3, player),
|
||||
ItemFactory(['Map (Thieves Town)', 'Compass (Thieves Town)'], player))
|
||||
SW = make_dungeon('Skull Woods', 'Mothula', ['Skull Woods Final Section (Entrance)', 'Skull Woods First Section',
|
||||
'Skull Woods Second Section', 'Skull Woods Second Section (Drop)',
|
||||
@@ -113,52 +116,54 @@ def create_dungeons(world: "ALTTPWorld"):
|
||||
'Skull Woods First Section (Right)',
|
||||
'Skull Woods First Section (Left)', 'Skull Woods First Section (Top)'],
|
||||
ItemFactory('Big Key (Skull Woods)', player),
|
||||
ItemFactory(['Small Key (Skull Woods)'] * 3, player),
|
||||
ItemFactory(['Small Key (Skull Woods)'] * 5, player),
|
||||
ItemFactory(['Map (Skull Woods)', 'Compass (Skull Woods)'], player))
|
||||
SP = make_dungeon('Swamp Palace', 'Arrghus',
|
||||
['Swamp Palace (Entrance)', 'Swamp Palace (First Room)', 'Swamp Palace (Starting Area)',
|
||||
'Swamp Palace (Center)', 'Swamp Palace (North)'], ItemFactory('Big Key (Swamp Palace)', player),
|
||||
[ItemFactory('Small Key (Swamp Palace)', player)],
|
||||
'Swamp Palace (West)', 'Swamp Palace (Center)', 'Swamp Palace (North)'],
|
||||
ItemFactory('Big Key (Swamp Palace)', player),
|
||||
ItemFactory(['Small Key (Swamp Palace)'] * 6, player),
|
||||
ItemFactory(['Map (Swamp Palace)', 'Compass (Swamp Palace)'], player))
|
||||
IP = make_dungeon('Ice Palace', 'Kholdstare',
|
||||
['Ice Palace (Entrance)', 'Ice Palace (Main)', 'Ice Palace (East)', 'Ice Palace (East Top)',
|
||||
'Ice Palace (Kholdstare)'], ItemFactory('Big Key (Ice Palace)', player),
|
||||
ItemFactory(['Small Key (Ice Palace)'] * 2, player),
|
||||
['Ice Palace (Entrance)', 'Ice Palace (Second Section)', 'Ice Palace (Main)', 'Ice Palace (East)',
|
||||
'Ice Palace (East Top)', 'Ice Palace (Kholdstare)'], ItemFactory('Big Key (Ice Palace)', player),
|
||||
ItemFactory(['Small Key (Ice Palace)'] * 6, player),
|
||||
ItemFactory(['Map (Ice Palace)', 'Compass (Ice Palace)'], player))
|
||||
MM = make_dungeon('Misery Mire', 'Vitreous',
|
||||
['Misery Mire (Entrance)', 'Misery Mire (Main)', 'Misery Mire (West)', 'Misery Mire (Final Area)',
|
||||
'Misery Mire (Vitreous)'], ItemFactory('Big Key (Misery Mire)', player),
|
||||
ItemFactory(['Small Key (Misery Mire)'] * 3, player),
|
||||
ItemFactory(['Small Key (Misery Mire)'] * 6, player),
|
||||
ItemFactory(['Map (Misery Mire)', 'Compass (Misery Mire)'], player))
|
||||
TR = make_dungeon('Turtle Rock', 'Trinexx',
|
||||
['Turtle Rock (Entrance)', 'Turtle Rock (First Section)', 'Turtle Rock (Chain Chomp Room)',
|
||||
'Turtle Rock (Pokey Room)',
|
||||
'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Turtle Rock (Crystaroller Room)',
|
||||
'Turtle Rock (Dark Room)', 'Turtle Rock (Eye Bridge)', 'Turtle Rock (Trinexx)'],
|
||||
ItemFactory('Big Key (Turtle Rock)', player),
|
||||
ItemFactory(['Small Key (Turtle Rock)'] * 4, player),
|
||||
ItemFactory(['Small Key (Turtle Rock)'] * 6, player),
|
||||
ItemFactory(['Map (Turtle Rock)', 'Compass (Turtle Rock)'], player))
|
||||
|
||||
if multiworld.mode[player] != 'inverted':
|
||||
AT = make_dungeon('Agahnims Tower', 'Agahnim', ['Agahnims Tower', 'Agahnim 1'], None,
|
||||
ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), [])
|
||||
ItemFactory(['Small Key (Agahnims Tower)'] * 4, player), [])
|
||||
GT = make_dungeon('Ganons Tower', 'Agahnim2',
|
||||
['Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)', 'Ganons Tower (Compass Room)',
|
||||
'Ganons Tower (Hookshot Room)', 'Ganons Tower (Map Room)', 'Ganons Tower (Firesnake Room)',
|
||||
'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)', 'Ganons Tower (Top)',
|
||||
'Ganons Tower (Before Moldorm)', 'Ganons Tower (Moldorm)', 'Agahnim 2'],
|
||||
ItemFactory('Big Key (Ganons Tower)', player),
|
||||
ItemFactory(['Small Key (Ganons Tower)'] * 4, player),
|
||||
ItemFactory(['Small Key (Ganons Tower)'] * 8, player),
|
||||
ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], player))
|
||||
else:
|
||||
AT = make_dungeon('Inverted Agahnims Tower', 'Agahnim', ['Inverted Agahnims Tower', 'Agahnim 1'], None,
|
||||
ItemFactory(['Small Key (Agahnims Tower)'] * 2, player), [])
|
||||
ItemFactory(['Small Key (Agahnims Tower)'] * 4, player), [])
|
||||
GT = make_dungeon('Inverted Ganons Tower', 'Agahnim2',
|
||||
['Inverted Ganons Tower (Entrance)', 'Ganons Tower (Tile Room)',
|
||||
'Ganons Tower (Compass Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower (Map Room)',
|
||||
'Ganons Tower (Firesnake Room)', 'Ganons Tower (Teleport Room)', 'Ganons Tower (Bottom)',
|
||||
'Ganons Tower (Top)', 'Ganons Tower (Before Moldorm)', 'Ganons Tower (Moldorm)',
|
||||
'Agahnim 2'], ItemFactory('Big Key (Ganons Tower)', player),
|
||||
ItemFactory(['Small Key (Ganons Tower)'] * 4, player),
|
||||
ItemFactory(['Small Key (Ganons Tower)'] * 8, player),
|
||||
ItemFactory(['Map (Ganons Tower)', 'Compass (Ganons Tower)'], player))
|
||||
|
||||
GT.bosses['bottom'] = BossFactory('Armos Knights', player)
|
||||
@@ -195,10 +200,11 @@ def fill_dungeons_restrictive(multiworld: MultiWorld):
|
||||
dungeon_specific: set = set()
|
||||
for subworld in multiworld.get_game_worlds("A Link to the Past"):
|
||||
player = subworld.player
|
||||
localized |= {(player, item_name) for item_name in
|
||||
subworld.dungeon_local_item_names}
|
||||
dungeon_specific |= {(player, item_name) for item_name in
|
||||
subworld.dungeon_specific_item_names}
|
||||
if player not in multiworld.groups:
|
||||
localized |= {(player, item_name) for item_name in
|
||||
subworld.dungeon_local_item_names}
|
||||
dungeon_specific |= {(player, item_name) for item_name in
|
||||
subworld.dungeon_specific_item_names}
|
||||
|
||||
if localized:
|
||||
in_dungeon_items = [item for item in get_dungeon_item_pool(multiworld) if (item.player, item.name) in localized]
|
||||
@@ -249,7 +255,16 @@ def fill_dungeons_restrictive(multiworld: MultiWorld):
|
||||
if all_state_base.has("Triforce", player):
|
||||
all_state_base.remove(multiworld.worlds[player].create_item("Triforce"))
|
||||
|
||||
fill_restrictive(multiworld, all_state_base, locations, in_dungeon_items, True, True, allow_excluded=True)
|
||||
for (player, key_drop_shuffle) in multiworld.key_drop_shuffle.items():
|
||||
if not key_drop_shuffle and player not in multiworld.groups:
|
||||
for key_loc in key_drop_data:
|
||||
key_data = key_drop_data[key_loc]
|
||||
all_state_base.remove(ItemFactory(key_data[3], player))
|
||||
loc = multiworld.get_location(key_loc, player)
|
||||
|
||||
if loc in all_state_base.events:
|
||||
all_state_base.events.remove(loc)
|
||||
fill_restrictive(multiworld, all_state_base, locations, in_dungeon_items, True, True)
|
||||
|
||||
|
||||
dungeon_music_addresses = {'Eastern Palace - Prize': [0x1559A],
|
||||
|
||||
@@ -3134,6 +3134,7 @@ mandatory_connections = [('Links House S&Q', 'Links House'),
|
||||
('Swamp Palace Moat', 'Swamp Palace (First Room)'),
|
||||
('Swamp Palace Small Key Door', 'Swamp Palace (Starting Area)'),
|
||||
('Swamp Palace (Center)', 'Swamp Palace (Center)'),
|
||||
('Swamp Palace (West)', 'Swamp Palace (West)'),
|
||||
('Swamp Palace (North)', 'Swamp Palace (North)'),
|
||||
('Thieves Town Big Key Door', 'Thieves Town (Deep)'),
|
||||
('Skull Woods Torch Room', 'Skull Woods Final Section (Mothula)'),
|
||||
@@ -3148,7 +3149,8 @@ mandatory_connections = [('Links House S&Q', 'Links House'),
|
||||
('Blind Fight', 'Blind Fight'),
|
||||
('Desert Palace Pots (Outer)', 'Desert Palace Main (Inner)'),
|
||||
('Desert Palace Pots (Inner)', 'Desert Palace Main (Outer)'),
|
||||
('Ice Palace Entrance Room', 'Ice Palace (Main)'),
|
||||
('Ice Palace (Main)', 'Ice Palace (Main)'),
|
||||
('Ice Palace (Second Section)', 'Ice Palace (Second Section)'),
|
||||
('Ice Palace (East)', 'Ice Palace (East)'),
|
||||
('Ice Palace (East Top)', 'Ice Palace (East Top)'),
|
||||
('Ice Palace (Kholdstare)', 'Ice Palace (Kholdstare)'),
|
||||
@@ -3158,9 +3160,11 @@ mandatory_connections = [('Links House S&Q', 'Links House'),
|
||||
('Misery Mire (Vitreous)', 'Misery Mire (Vitreous)'),
|
||||
('Turtle Rock Entrance Gap', 'Turtle Rock (First Section)'),
|
||||
('Turtle Rock Entrance Gap Reverse', 'Turtle Rock (Entrance)'),
|
||||
('Turtle Rock Pokey Room', 'Turtle Rock (Chain Chomp Room)'),
|
||||
('Turtle Rock Entrance to Pokey Room', 'Turtle Rock (Pokey Room)'),
|
||||
('Turtle Rock (Pokey Room) (South)', 'Turtle Rock (First Section)'),
|
||||
('Turtle Rock (Pokey Room) (North)', 'Turtle Rock (Chain Chomp Room)'),
|
||||
('Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Second Section)'),
|
||||
('Turtle Rock (Chain Chomp Room) (South)', 'Turtle Rock (First Section)'),
|
||||
('Turtle Rock (Chain Chomp Room) (South)', 'Turtle Rock (Pokey Room)'),
|
||||
('Turtle Rock Chain Chomp Staircase', 'Turtle Rock (Chain Chomp Room)'),
|
||||
('Turtle Rock (Big Chest) (North)', 'Turtle Rock (Second Section)'),
|
||||
('Turtle Rock Big Key Door', 'Turtle Rock (Crystaroller Room)'),
|
||||
@@ -3285,6 +3289,7 @@ inverted_mandatory_connections = [('Links House S&Q', 'Inverted Links House'),
|
||||
('Swamp Palace Moat', 'Swamp Palace (First Room)'),
|
||||
('Swamp Palace Small Key Door', 'Swamp Palace (Starting Area)'),
|
||||
('Swamp Palace (Center)', 'Swamp Palace (Center)'),
|
||||
('Swamp Palace (West)', 'Swamp Palace (West)'),
|
||||
('Swamp Palace (North)', 'Swamp Palace (North)'),
|
||||
('Thieves Town Big Key Door', 'Thieves Town (Deep)'),
|
||||
('Skull Woods Torch Room', 'Skull Woods Final Section (Mothula)'),
|
||||
@@ -3299,7 +3304,8 @@ inverted_mandatory_connections = [('Links House S&Q', 'Inverted Links House'),
|
||||
('Blind Fight', 'Blind Fight'),
|
||||
('Desert Palace Pots (Outer)', 'Desert Palace Main (Inner)'),
|
||||
('Desert Palace Pots (Inner)', 'Desert Palace Main (Outer)'),
|
||||
('Ice Palace Entrance Room', 'Ice Palace (Main)'),
|
||||
('Ice Palace (Main)', 'Ice Palace (Main)'),
|
||||
('Ice Palace (Second Section)', 'Ice Palace (Second Section)'),
|
||||
('Ice Palace (East)', 'Ice Palace (East)'),
|
||||
('Ice Palace (East Top)', 'Ice Palace (East Top)'),
|
||||
('Ice Palace (Kholdstare)', 'Ice Palace (Kholdstare)'),
|
||||
@@ -3309,9 +3315,11 @@ inverted_mandatory_connections = [('Links House S&Q', 'Inverted Links House'),
|
||||
('Misery Mire (Vitreous)', 'Misery Mire (Vitreous)'),
|
||||
('Turtle Rock Entrance Gap', 'Turtle Rock (First Section)'),
|
||||
('Turtle Rock Entrance Gap Reverse', 'Turtle Rock (Entrance)'),
|
||||
('Turtle Rock Pokey Room', 'Turtle Rock (Chain Chomp Room)'),
|
||||
('Turtle Rock Entrance to Pokey Room', 'Turtle Rock (Pokey Room)'),
|
||||
('Turtle Rock (Pokey Room) (South)', 'Turtle Rock (First Section)'),
|
||||
('Turtle Rock (Pokey Room) (North)', 'Turtle Rock (Chain Chomp Room)'),
|
||||
('Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Second Section)'),
|
||||
('Turtle Rock (Chain Chomp Room) (South)', 'Turtle Rock (First Section)'),
|
||||
('Turtle Rock (Chain Chomp Room) (South)', 'Turtle Rock (Pokey Room)'),
|
||||
('Turtle Rock Chain Chomp Staircase', 'Turtle Rock (Chain Chomp Room)'),
|
||||
('Turtle Rock (Big Chest) (North)', 'Turtle Rock (Second Section)'),
|
||||
('Turtle Rock Big Key Door', 'Turtle Rock (Crystaroller Room)'),
|
||||
|
||||
@@ -149,41 +149,37 @@ def create_inverted_regions(world, player):
|
||||
create_lw_region(world, player, 'Desert Palace Entrance (North) Spot', None,
|
||||
['Desert Palace Entrance (North)', 'Desert Ledge Return Rocks',
|
||||
'Desert Palace North Mirror Spot']),
|
||||
create_dungeon_region(world, player, 'Desert Palace Main (Outer)', 'Desert Palace',
|
||||
['Desert Palace - Big Chest', 'Desert Palace - Torch', 'Desert Palace - Map Chest'],
|
||||
['Desert Palace Pots (Outer)', 'Desert Palace Exit (West)', 'Desert Palace Exit (East)',
|
||||
'Desert Palace East Wing']),
|
||||
create_dungeon_region(world, player, 'Desert Palace Main (Inner)', 'Desert Palace', None,
|
||||
['Desert Palace Exit (South)', 'Desert Palace Pots (Inner)']),
|
||||
create_dungeon_region(world, player, 'Desert Palace East', 'Desert Palace',
|
||||
['Desert Palace - Compass Chest', 'Desert Palace - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Desert Palace Main (Outer)', 'Desert Palace', ['Desert Palace - Big Chest', 'Desert Palace - Torch', 'Desert Palace - Map Chest'],
|
||||
['Desert Palace Pots (Outer)', 'Desert Palace Exit (West)', 'Desert Palace Exit (East)', 'Desert Palace East Wing']),
|
||||
create_dungeon_region(world, player, 'Desert Palace Main (Inner)', 'Desert Palace', None, ['Desert Palace Exit (South)', 'Desert Palace Pots (Inner)']),
|
||||
create_dungeon_region(world, player, 'Desert Palace East', 'Desert Palace', ['Desert Palace - Compass Chest', 'Desert Palace - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Desert Palace North', 'Desert Palace',
|
||||
['Desert Palace - Boss', 'Desert Palace - Prize'], ['Desert Palace Exit (North)']),
|
||||
['Desert Palace - Desert Tiles 1 Pot Key', 'Desert Palace - Beamos Hall Pot Key',
|
||||
'Desert Palace - Desert Tiles 2 Pot Key',
|
||||
'Desert Palace - Boss', 'Desert Palace - Prize'], ['Desert Palace Exit (North)']),
|
||||
create_dungeon_region(world, player, 'Eastern Palace', 'Eastern Palace',
|
||||
['Eastern Palace - Compass Chest', 'Eastern Palace - Big Chest',
|
||||
'Eastern Palace - Cannonball Chest',
|
||||
'Eastern Palace - Big Key Chest', 'Eastern Palace - Map Chest', 'Eastern Palace - Boss',
|
||||
'Eastern Palace - Prize'], ['Eastern Palace Exit']),
|
||||
'Eastern Palace - Dark Square Pot Key', 'Eastern Palace - Dark Eyegore Key Drop',
|
||||
'Eastern Palace - Big Key Chest',
|
||||
'Eastern Palace - Map Chest', 'Eastern Palace - Boss', 'Eastern Palace - Prize'],
|
||||
['Eastern Palace Exit']),
|
||||
create_lw_region(world, player, 'Master Sword Meadow', ['Master Sword Pedestal']),
|
||||
create_cave_region(world, player, 'Lost Woods Gamble', 'a game of chance'),
|
||||
create_lw_region(world, player, 'Hyrule Castle Ledge', None,
|
||||
['Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (West)', 'Inverted Ganons Tower',
|
||||
'Hyrule Castle Ledge Courtyard Drop', 'Inverted Pyramid Hole']),
|
||||
create_lw_region(world, player, 'Hyrule Castle Ledge', None, ['Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (West)', 'Inverted Ganons Tower', 'Hyrule Castle Ledge Courtyard Drop', 'Inverted Pyramid Hole']),
|
||||
create_dungeon_region(world, player, 'Hyrule Castle', 'Hyrule Castle',
|
||||
['Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest',
|
||||
'Hyrule Castle - Zelda\'s Chest'],
|
||||
'Hyrule Castle - Zelda\'s Chest',
|
||||
'Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop',
|
||||
'Hyrule Castle - Big Key Drop'],
|
||||
['Hyrule Castle Exit (East)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (South)',
|
||||
'Throne Room']),
|
||||
create_dungeon_region(world, player, 'Sewer Drop', 'a drop\'s exit', None, ['Sewer Drop']), # This exists only to be referenced for access checks
|
||||
create_dungeon_region(world, player, 'Sewers (Dark)', 'a drop\'s exit', ['Sewers - Dark Cross'],
|
||||
['Sewers Door']),
|
||||
create_dungeon_region(world, player, 'Sewers', 'a drop\'s exit',
|
||||
['Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle',
|
||||
'Sewers - Secret Room - Right'], ['Sanctuary Push Door', 'Sewers Back Door']),
|
||||
create_dungeon_region(world, player, 'Sewers (Dark)', 'a drop\'s exit', ['Sewers - Dark Cross', 'Sewers - Key Rat Key Drop'], ['Sewers Door']),
|
||||
create_dungeon_region(world, player, 'Sewers', 'a drop\'s exit', ['Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle',
|
||||
'Sewers - Secret Room - Right'], ['Sanctuary Push Door', 'Sewers Back Door']),
|
||||
create_dungeon_region(world, player, 'Sanctuary', 'a drop\'s exit', ['Sanctuary'], ['Sanctuary Exit']),
|
||||
create_dungeon_region(world, player, 'Inverted Agahnims Tower', 'Castle Tower',
|
||||
['Castle Tower - Room 03', 'Castle Tower - Dark Maze'],
|
||||
['Agahnim 1', 'Inverted Agahnims Tower Exit']),
|
||||
create_dungeon_region(world, player, 'Inverted Agahnims Tower', 'Castle Tower', ['Castle Tower - Room 03', 'Castle Tower - Dark Maze', 'Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop'], ['Agahnim 1', 'Inverted Agahnims Tower Exit']),
|
||||
create_dungeon_region(world, player, 'Agahnim 1', 'Castle Tower', ['Agahnim 1'], None),
|
||||
create_cave_region(world, player, 'Old Man Cave', 'a connector', ['Old Man'],
|
||||
['Old Man Cave Exit (East)', 'Old Man Cave Exit (West)']),
|
||||
@@ -253,14 +249,9 @@ def create_inverted_regions(world, player):
|
||||
'Death Mountain (Top) Mirror Spot']),
|
||||
create_dw_region(world, player, 'Bumper Cave Ledge', ['Bumper Cave Ledge'],
|
||||
['Bumper Cave Ledge Drop', 'Bumper Cave (Top)']),
|
||||
create_dungeon_region(world, player, 'Tower of Hera (Bottom)', 'Tower of Hera',
|
||||
['Tower of Hera - Basement Cage', 'Tower of Hera - Map Chest'],
|
||||
['Tower of Hera Small Key Door', 'Tower of Hera Big Key Door', 'Tower of Hera Exit']),
|
||||
create_dungeon_region(world, player, 'Tower of Hera (Basement)', 'Tower of Hera',
|
||||
['Tower of Hera - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Tower of Hera (Top)', 'Tower of Hera',
|
||||
['Tower of Hera - Compass Chest', 'Tower of Hera - Big Chest', 'Tower of Hera - Boss',
|
||||
'Tower of Hera - Prize']),
|
||||
create_dungeon_region(world, player, 'Tower of Hera (Bottom)', 'Tower of Hera', ['Tower of Hera - Basement Cage', 'Tower of Hera - Map Chest'], ['Tower of Hera Small Key Door', 'Tower of Hera Big Key Door', 'Tower of Hera Exit']),
|
||||
create_dungeon_region(world, player, 'Tower of Hera (Basement)', 'Tower of Hera', ['Tower of Hera - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Tower of Hera (Top)', 'Tower of Hera', ['Tower of Hera - Compass Chest', 'Tower of Hera - Big Chest', 'Tower of Hera - Boss', 'Tower of Hera - Prize']),
|
||||
|
||||
create_dw_region(world, player, 'East Dark World', ['Pyramid'],
|
||||
['Pyramid Fairy', 'South Dark World Bridge', 'Palace of Darkness',
|
||||
@@ -360,128 +351,82 @@ def create_inverted_regions(world, player):
|
||||
['Floating Island Drop', 'Hookshot Cave Back Entrance']),
|
||||
create_cave_region(world, player, 'Mimic Cave', 'Mimic Cave', ['Mimic Cave']),
|
||||
|
||||
create_dungeon_region(world, player, 'Swamp Palace (Entrance)', 'Swamp Palace', None,
|
||||
['Swamp Palace Moat', 'Swamp Palace Exit']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (First Room)', 'Swamp Palace', ['Swamp Palace - Entrance'],
|
||||
['Swamp Palace Small Key Door']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (Starting Area)', 'Swamp Palace',
|
||||
['Swamp Palace - Map Chest'], ['Swamp Palace (Center)']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (Center)', 'Swamp Palace',
|
||||
['Swamp Palace - Big Chest', 'Swamp Palace - Compass Chest',
|
||||
'Swamp Palace - Big Key Chest', 'Swamp Palace - West Chest'], ['Swamp Palace (North)']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (North)', 'Swamp Palace',
|
||||
['Swamp Palace - Flooded Room - Left', 'Swamp Palace - Flooded Room - Right',
|
||||
'Swamp Palace - Waterfall Room', 'Swamp Palace - Boss', 'Swamp Palace - Prize']),
|
||||
create_dungeon_region(world, player, 'Thieves Town (Entrance)', 'Thieves\' Town',
|
||||
['Thieves\' Town - Big Key Chest',
|
||||
'Thieves\' Town - Map Chest',
|
||||
'Thieves\' Town - Compass Chest',
|
||||
'Thieves\' Town - Ambush Chest'], ['Thieves Town Big Key Door', 'Thieves Town Exit']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (Entrance)', 'Swamp Palace', None, ['Swamp Palace Moat', 'Swamp Palace Exit']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (First Room)', 'Swamp Palace', ['Swamp Palace - Entrance'], ['Swamp Palace Small Key Door']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (Starting Area)', 'Swamp Palace', ['Swamp Palace - Map Chest', 'Swamp Palace - Pot Row Pot Key',
|
||||
'Swamp Palace - Trench 1 Pot Key'], ['Swamp Palace (Center)']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (Center)', 'Swamp Palace', ['Swamp Palace - Big Chest', 'Swamp Palace - Compass Chest', 'Swamp Palace - Hookshot Pot Key',
|
||||
'Swamp Palace - Trench 2 Pot Key'], ['Swamp Palace (North)', 'Swamp Palace (West)']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (West)', 'Swamp Palace', ['Swamp Palace - Big Key Chest', 'Swamp Palace - West Chest']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (North)', 'Swamp Palace', ['Swamp Palace - Flooded Room - Left', 'Swamp Palace - Flooded Room - Right',
|
||||
'Swamp Palace - Waterway Pot Key', 'Swamp Palace - Waterfall Room',
|
||||
'Swamp Palace - Boss', 'Swamp Palace - Prize']),
|
||||
create_dungeon_region(world, player, 'Thieves Town (Entrance)', 'Thieves\' Town', ['Thieves\' Town - Big Key Chest',
|
||||
'Thieves\' Town - Map Chest',
|
||||
'Thieves\' Town - Compass Chest',
|
||||
'Thieves\' Town - Ambush Chest'], ['Thieves Town Big Key Door', 'Thieves Town Exit']),
|
||||
create_dungeon_region(world, player, 'Thieves Town (Deep)', 'Thieves\' Town', ['Thieves\' Town - Attic',
|
||||
'Thieves\' Town - Big Chest',
|
||||
'Thieves\' Town - Blind\'s Cell'],
|
||||
'Thieves\' Town - Big Chest',
|
||||
'Thieves\' Town - Hallway Pot Key',
|
||||
'Thieves\' Town - Spike Switch Pot Key',
|
||||
'Thieves\' Town - Blind\'s Cell'],
|
||||
['Blind Fight']),
|
||||
create_dungeon_region(world, player, 'Blind Fight', 'Thieves\' Town',
|
||||
['Thieves\' Town - Boss', 'Thieves\' Town - Prize']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section', 'Skull Woods', ['Skull Woods - Map Chest'],
|
||||
['Skull Woods First Section Exit', 'Skull Woods First Section Bomb Jump',
|
||||
'Skull Woods First Section South Door', 'Skull Woods First Section West Door']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section (Right)', 'Skull Woods',
|
||||
['Skull Woods - Pinball Room'], ['Skull Woods First Section (Right) North Door']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section (Left)', 'Skull Woods',
|
||||
['Skull Woods - Compass Chest', 'Skull Woods - Pot Prison'],
|
||||
['Skull Woods First Section (Left) Door to Exit',
|
||||
'Skull Woods First Section (Left) Door to Right']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section (Top)', 'Skull Woods',
|
||||
['Skull Woods - Big Chest'], ['Skull Woods First Section (Top) One-Way Path']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Second Section (Drop)', 'Skull Woods', None,
|
||||
['Skull Woods Second Section (Drop)']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Second Section', 'Skull Woods',
|
||||
['Skull Woods - Big Key Chest'],
|
||||
['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Final Section (Entrance)', 'Skull Woods',
|
||||
['Skull Woods - Bridge Room'],
|
||||
['Skull Woods Torch Room', 'Skull Woods Final Section Exit']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Final Section (Mothula)', 'Skull Woods',
|
||||
['Skull Woods - Boss', 'Skull Woods - Prize']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Entrance)', 'Ice Palace', None,
|
||||
['Ice Palace Entrance Room', 'Ice Palace Exit']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Main)', 'Ice Palace',
|
||||
['Ice Palace - Compass Chest', 'Ice Palace - Freezor Chest',
|
||||
'Ice Palace - Big Chest', 'Ice Palace - Iced T Room'],
|
||||
['Ice Palace (East)', 'Ice Palace (Kholdstare)']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (East)', 'Ice Palace', ['Ice Palace - Spike Room'],
|
||||
['Ice Palace (East Top)']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (East Top)', 'Ice Palace',
|
||||
['Ice Palace - Big Key Chest', 'Ice Palace - Map Chest']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Kholdstare)', 'Ice Palace',
|
||||
['Ice Palace - Boss', 'Ice Palace - Prize']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Entrance)', 'Misery Mire', None,
|
||||
['Misery Mire Entrance Gap', 'Misery Mire Exit']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Main)', 'Misery Mire',
|
||||
['Misery Mire - Big Chest', 'Misery Mire - Map Chest', 'Misery Mire - Main Lobby',
|
||||
'Misery Mire - Bridge Chest', 'Misery Mire - Spike Chest'],
|
||||
['Misery Mire (West)', 'Misery Mire Big Key Door']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (West)', 'Misery Mire',
|
||||
['Misery Mire - Compass Chest', 'Misery Mire - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Final Area)', 'Misery Mire', None,
|
||||
['Misery Mire (Vitreous)']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Vitreous)', 'Misery Mire',
|
||||
['Misery Mire - Boss', 'Misery Mire - Prize']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Entrance)', 'Turtle Rock', None,
|
||||
['Turtle Rock Entrance Gap', 'Turtle Rock Exit (Front)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (First Section)', 'Turtle Rock',
|
||||
['Turtle Rock - Compass Chest', 'Turtle Rock - Roller Room - Left',
|
||||
'Turtle Rock - Roller Room - Right'],
|
||||
['Turtle Rock Pokey Room', 'Turtle Rock Entrance Gap Reverse']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock',
|
||||
['Turtle Rock - Chain Chomps'],
|
||||
create_dungeon_region(world, player, 'Blind Fight', 'Thieves\' Town', ['Thieves\' Town - Boss', 'Thieves\' Town - Prize']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section', 'Skull Woods', ['Skull Woods - Map Chest'], ['Skull Woods First Section Exit', 'Skull Woods First Section Bomb Jump', 'Skull Woods First Section South Door', 'Skull Woods First Section West Door']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section (Right)', 'Skull Woods', ['Skull Woods - Pinball Room'], ['Skull Woods First Section (Right) North Door']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section (Left)', 'Skull Woods', ['Skull Woods - Compass Chest', 'Skull Woods - Pot Prison'], ['Skull Woods First Section (Left) Door to Exit', 'Skull Woods First Section (Left) Door to Right']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section (Top)', 'Skull Woods', ['Skull Woods - Big Chest'], ['Skull Woods First Section (Top) One-Way Path']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Second Section (Drop)', 'Skull Woods', None, ['Skull Woods Second Section (Drop)']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Second Section', 'Skull Woods', ['Skull Woods - Big Key Chest', 'Skull Woods - West Lobby Pot Key'], ['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Final Section (Entrance)', 'Skull Woods', ['Skull Woods - Bridge Room', 'Skull Woods - Spike Corner Key Drop'], ['Skull Woods Torch Room', 'Skull Woods Final Section Exit']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Final Section (Mothula)', 'Skull Woods', ['Skull Woods - Boss', 'Skull Woods - Prize']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Entrance)', 'Ice Palace', ['Ice Palace - Jelly Key Drop'], ['Ice Palace (Second Section)', 'Ice Palace Exit']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Second Section)', 'Ice Palace', ['Ice Palace - Conveyor Key Drop', 'Ice Palace - Compass Chest'], ['Ice Palace (Main)']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Main)', 'Ice Palace', ['Ice Palace - Freezor Chest',
|
||||
'Ice Palace - Many Pots Pot Key',
|
||||
'Ice Palace - Big Chest', 'Ice Palace - Iced T Room'], ['Ice Palace (East)', 'Ice Palace (Kholdstare)']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (East)', 'Ice Palace', ['Ice Palace - Spike Room'], ['Ice Palace (East Top)']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (East Top)', 'Ice Palace', ['Ice Palace - Big Key Chest', 'Ice Palace - Map Chest', 'Ice Palace - Hammer Block Key Drop']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Kholdstare)', 'Ice Palace', ['Ice Palace - Boss', 'Ice Palace - Prize']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Entrance)', 'Misery Mire', None, ['Misery Mire Entrance Gap', 'Misery Mire Exit']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Main)', 'Misery Mire', ['Misery Mire - Big Chest', 'Misery Mire - Map Chest', 'Misery Mire - Main Lobby',
|
||||
'Misery Mire - Bridge Chest', 'Misery Mire - Spike Chest',
|
||||
'Misery Mire - Spikes Pot Key', 'Misery Mire - Fishbone Pot Key',
|
||||
'Misery Mire - Conveyor Crystal Key Drop'], ['Misery Mire (West)', 'Misery Mire Big Key Door']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (West)', 'Misery Mire', ['Misery Mire - Compass Chest', 'Misery Mire - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Final Area)', 'Misery Mire', None, ['Misery Mire (Vitreous)']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Vitreous)', 'Misery Mire', ['Misery Mire - Boss', 'Misery Mire - Prize']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Entrance)', 'Turtle Rock', None, ['Turtle Rock Entrance Gap', 'Turtle Rock Exit (Front)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (First Section)', 'Turtle Rock', ['Turtle Rock - Compass Chest', 'Turtle Rock - Roller Room - Left',
|
||||
'Turtle Rock - Roller Room - Right'],
|
||||
['Turtle Rock Entrance to Pokey Room', 'Turtle Rock Entrance Gap Reverse']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Pokey Room)', 'Turtle Rock', ['Turtle Rock - Pokey 1 Key Drop'], ['Turtle Rock (Pokey Room) (North)', 'Turtle Rock (Pokey Room) (South)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock', ['Turtle Rock - Chain Chomps'],
|
||||
['Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Chain Chomp Room) (South)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Second Section)', 'Turtle Rock',
|
||||
['Turtle Rock - Big Key Chest'],
|
||||
['Turtle Rock - Big Key Chest', 'Turtle Rock - Pokey 2 Key Drop'],
|
||||
['Turtle Rock Ledge Exit (West)', 'Turtle Rock Chain Chomp Staircase',
|
||||
'Turtle Rock Big Key Door']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Big Chest)', 'Turtle Rock', ['Turtle Rock - Big Chest'],
|
||||
['Turtle Rock (Big Chest) (North)', 'Turtle Rock Ledge Exit (East)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Crystaroller Room)', 'Turtle Rock',
|
||||
['Turtle Rock - Crystaroller Room'],
|
||||
['Turtle Rock Dark Room Staircase', 'Turtle Rock Big Key Door Reverse']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Dark Room)', 'Turtle Rock', None,
|
||||
['Turtle Rock (Dark Room) (North)', 'Turtle Rock (Dark Room) (South)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Eye Bridge)', 'Turtle Rock',
|
||||
['Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right',
|
||||
'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'],
|
||||
['Turtle Rock Dark Room (South)', 'Turtle Rock (Trinexx)',
|
||||
'Turtle Rock Isolated Ledge Exit']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Trinexx)', 'Turtle Rock',
|
||||
['Turtle Rock - Boss', 'Turtle Rock - Prize']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Entrance)', 'Palace of Darkness',
|
||||
['Palace of Darkness - Shooter Room'],
|
||||
['Palace of Darkness Bridge Room', 'Palace of Darkness Bonk Wall',
|
||||
'Palace of Darkness Exit']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Center)', 'Palace of Darkness',
|
||||
['Palace of Darkness - The Arena - Bridge', 'Palace of Darkness - Stalfos Basement'],
|
||||
['Palace of Darkness Big Key Chest Staircase', 'Palace of Darkness (North)',
|
||||
'Palace of Darkness Big Key Door']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Big Key Chest)', 'Palace of Darkness',
|
||||
['Palace of Darkness - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Bonk Section)', 'Palace of Darkness',
|
||||
['Palace of Darkness - The Arena - Ledge', 'Palace of Darkness - Map Chest'],
|
||||
['Palace of Darkness Hammer Peg Drop']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (North)', 'Palace of Darkness',
|
||||
['Palace of Darkness - Compass Chest', 'Palace of Darkness - Dark Basement - Left',
|
||||
'Palace of Darkness - Dark Basement - Right'],
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Big Chest)', 'Turtle Rock', ['Turtle Rock - Big Chest'], ['Turtle Rock (Big Chest) (North)', 'Turtle Rock Ledge Exit (East)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Crystaroller Room)', 'Turtle Rock', ['Turtle Rock - Crystaroller Room'], ['Turtle Rock Dark Room Staircase', 'Turtle Rock Big Key Door Reverse']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Dark Room)', 'Turtle Rock', None, ['Turtle Rock (Dark Room) (North)', 'Turtle Rock (Dark Room) (South)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Eye Bridge)', 'Turtle Rock', ['Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right',
|
||||
'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'],
|
||||
['Turtle Rock Dark Room (South)', 'Turtle Rock (Trinexx)', 'Turtle Rock Isolated Ledge Exit']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Trinexx)', 'Turtle Rock', ['Turtle Rock - Boss', 'Turtle Rock - Prize']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Entrance)', 'Palace of Darkness', ['Palace of Darkness - Shooter Room'], ['Palace of Darkness Bridge Room', 'Palace of Darkness Bonk Wall', 'Palace of Darkness Exit']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Center)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Bridge', 'Palace of Darkness - Stalfos Basement'],
|
||||
['Palace of Darkness Big Key Chest Staircase', 'Palace of Darkness (North)', 'Palace of Darkness Big Key Door']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Big Key Chest)', 'Palace of Darkness', ['Palace of Darkness - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Bonk Section)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Ledge', 'Palace of Darkness - Map Chest'], ['Palace of Darkness Hammer Peg Drop']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (North)', 'Palace of Darkness', ['Palace of Darkness - Compass Chest', 'Palace of Darkness - Dark Basement - Left', 'Palace of Darkness - Dark Basement - Right'],
|
||||
['Palace of Darkness Spike Statue Room Door', 'Palace of Darkness Maze Door']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Maze)', 'Palace of Darkness',
|
||||
['Palace of Darkness - Dark Maze - Top', 'Palace of Darkness - Dark Maze - Bottom',
|
||||
'Palace of Darkness - Big Chest']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Harmless Hellway)', 'Palace of Darkness',
|
||||
['Palace of Darkness - Harmless Hellway']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Final Section)', 'Palace of Darkness',
|
||||
['Palace of Darkness - Boss', 'Palace of Darkness - Prize']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Maze)', 'Palace of Darkness', ['Palace of Darkness - Dark Maze - Top', 'Palace of Darkness - Dark Maze - Bottom', 'Palace of Darkness - Big Chest']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Harmless Hellway)', 'Palace of Darkness', ['Palace of Darkness - Harmless Hellway']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Final Section)', 'Palace of Darkness', ['Palace of Darkness - Boss', 'Palace of Darkness - Prize']),
|
||||
create_dungeon_region(world, player, 'Inverted Ganons Tower (Entrance)', 'Ganon\'s Tower',
|
||||
['Ganons Tower - Bob\'s Torch', 'Ganons Tower - Hope Room - Left',
|
||||
'Ganons Tower - Hope Room - Right'],
|
||||
'Ganons Tower - Hope Room - Right', 'Ganons Tower - Conveyor Cross Pot Key'],
|
||||
['Ganons Tower (Tile Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower Big Key Door',
|
||||
'Inverted Ganons Tower Exit']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Tile Room)', 'Ganon\'s Tower', ['Ganons Tower - Tile Room'],
|
||||
@@ -489,10 +434,13 @@ def create_inverted_regions(world, player):
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Compass Room)', 'Ganon\'s Tower',
|
||||
['Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right',
|
||||
'Ganons Tower - Compass Room - Bottom Left',
|
||||
'Ganons Tower - Compass Room - Bottom Right'], ['Ganons Tower (Bottom) (East)']),
|
||||
'Ganons Tower - Compass Room - Bottom Right',
|
||||
'Ganons Tower - Conveyor Star Pits Pot Key'],
|
||||
['Ganons Tower (Bottom) (East)']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Hookshot Room)', 'Ganon\'s Tower',
|
||||
['Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right',
|
||||
'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right'],
|
||||
'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right',
|
||||
'Ganons Tower - Double Switch Pot Key'],
|
||||
['Ganons Tower (Map Room)', 'Ganons Tower (Double Switch Room)']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Map Room)', 'Ganon\'s Tower', ['Ganons Tower - Map Chest']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Firesnake Room)', 'Ganon\'s Tower',
|
||||
@@ -501,21 +449,21 @@ def create_inverted_regions(world, player):
|
||||
['Ganons Tower - Randomizer Room - Top Left',
|
||||
'Ganons Tower - Randomizer Room - Top Right',
|
||||
'Ganons Tower - Randomizer Room - Bottom Left',
|
||||
'Ganons Tower - Randomizer Room - Bottom Right'], ['Ganons Tower (Bottom) (West)']),
|
||||
'Ganons Tower - Randomizer Room - Bottom Right'],
|
||||
['Ganons Tower (Bottom) (West)']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Bottom)', 'Ganon\'s Tower',
|
||||
['Ganons Tower - Bob\'s Chest', 'Ganons Tower - Big Chest',
|
||||
'Ganons Tower - Big Key Room - Left',
|
||||
'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Top)', 'Ganon\'s Tower', None,
|
||||
['Ganons Tower Torch Rooms']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Top)', 'Ganon\'s Tower', None, ['Ganons Tower Torch Rooms']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Before Moldorm)', 'Ganon\'s Tower',
|
||||
['Ganons Tower - Mini Helmasaur Room - Left',
|
||||
'Ganons Tower - Mini Helmasaur Room - Right',
|
||||
'Ganons Tower - Pre-Moldorm Chest'], ['Ganons Tower Moldorm Door']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Moldorm)', 'Ganon\'s Tower', None,
|
||||
['Ganons Tower Moldorm Gap']),
|
||||
create_dungeon_region(world, player, 'Agahnim 2', 'Ganon\'s Tower',
|
||||
['Ganons Tower - Validation Chest', 'Agahnim 2'], None),
|
||||
'Ganons Tower - Pre-Moldorm Chest', 'Ganons Tower - Mini Helmasaur Key Drop'],
|
||||
['Ganons Tower Moldorm Door']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Moldorm)', 'Ganon\'s Tower', None, ['Ganons Tower Moldorm Gap']),
|
||||
|
||||
create_dungeon_region(world, player, 'Agahnim 2', 'Ganon\'s Tower', ['Ganons Tower - Validation Chest', 'Agahnim 2'], None),
|
||||
create_cave_region(world, player, 'Pyramid', 'a drop\'s exit', ['Ganon'], ['Ganon Drop']),
|
||||
create_cave_region(world, player, 'Bottom of Pyramid', 'a drop\'s exit', None, ['Pyramid Exit']),
|
||||
create_dw_region(world, player, 'Pyramid Ledge', None, ['Pyramid Drop']), # houlihan room exits here in inverted
|
||||
|
||||
@@ -12,6 +12,7 @@ from .EntranceShuffle import connect_entrance
|
||||
from .Items import ItemFactory, GetBeemizerItem
|
||||
from .Options import smallkey_shuffle, compass_shuffle, bigkey_shuffle, map_shuffle, LTTPBosses
|
||||
from .StateHelpers import has_triforce_pieces, has_melee_weapon
|
||||
from .Regions import key_drop_data
|
||||
|
||||
# This file sets the item pools for various modes. Timed modes and triforce hunt are enforced first, and then extra items are specified per mode to fill in the remaining space.
|
||||
# Some basic items that various modes require are placed here, including pendants and crystals. Medallion requirements for the two relevant entrances are also decided.
|
||||
@@ -80,7 +81,7 @@ difficulties = {
|
||||
basicglove=basicgloves,
|
||||
alwaysitems=alwaysitems,
|
||||
legacyinsanity=legacyinsanity,
|
||||
universal_keys=['Small Key (Universal)'] * 28,
|
||||
universal_keys=['Small Key (Universal)'] * 29,
|
||||
extras=[easyfirst15extra, easysecond15extra, easythird10extra, easyfourth5extra, easyfinal25extra],
|
||||
progressive_sword_limit=8,
|
||||
progressive_shield_limit=6,
|
||||
@@ -112,7 +113,7 @@ difficulties = {
|
||||
basicglove=basicgloves,
|
||||
alwaysitems=alwaysitems,
|
||||
legacyinsanity=legacyinsanity,
|
||||
universal_keys=['Small Key (Universal)'] * 18 + ['Rupees (20)'] * 10,
|
||||
universal_keys=['Small Key (Universal)'] * 19 + ['Rupees (20)'] * 10,
|
||||
extras=[normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
||||
progressive_sword_limit=4,
|
||||
progressive_shield_limit=3,
|
||||
@@ -144,7 +145,7 @@ difficulties = {
|
||||
basicglove=basicgloves,
|
||||
alwaysitems=alwaysitems,
|
||||
legacyinsanity=legacyinsanity,
|
||||
universal_keys=['Small Key (Universal)'] * 12 + ['Rupees (5)'] * 16,
|
||||
universal_keys=['Small Key (Universal)'] * 13 + ['Rupees (5)'] * 16,
|
||||
extras=[normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
||||
progressive_sword_limit=3,
|
||||
progressive_shield_limit=2,
|
||||
@@ -176,7 +177,7 @@ difficulties = {
|
||||
basicglove=basicgloves,
|
||||
alwaysitems=alwaysitems,
|
||||
legacyinsanity=legacyinsanity,
|
||||
universal_keys=['Small Key (Universal)'] * 12 + ['Rupees (5)'] * 16,
|
||||
universal_keys=['Small Key (Universal)'] * 13 + ['Rupees (5)'] * 16,
|
||||
extras=[normalfirst15extra, normalsecond15extra, normalthird10extra, normalfourth5extra, normalfinal25extra],
|
||||
progressive_sword_limit=2,
|
||||
progressive_shield_limit=1,
|
||||
@@ -212,7 +213,7 @@ for diff in {'easy', 'normal', 'hard', 'expert'}:
|
||||
basicglove=['Nothing'] * 2,
|
||||
alwaysitems=['Ice Rod'] + ['Nothing'] * 19,
|
||||
legacyinsanity=['Nothing'] * 2,
|
||||
universal_keys=['Nothing'] * 28,
|
||||
universal_keys=['Nothing'] * 29,
|
||||
extras=[['Nothing'] * 15, ['Nothing'] * 15, ['Nothing'] * 10, ['Nothing'] * 5, ['Nothing'] * 25],
|
||||
progressive_sword_limit=difficulties[diff].progressive_sword_limit,
|
||||
progressive_shield_limit=difficulties[diff].progressive_shield_limit,
|
||||
@@ -281,7 +282,6 @@ def generate_itempool(world):
|
||||
itempool.extend(['Arrows (10)'] * 7)
|
||||
if multiworld.smallkey_shuffle[player] == smallkey_shuffle.option_universal:
|
||||
itempool.extend(itemdiff.universal_keys)
|
||||
itempool.append('Small Key (Universal)')
|
||||
|
||||
for item in itempool:
|
||||
multiworld.push_precollected(ItemFactory(item, player))
|
||||
@@ -374,11 +374,38 @@ def generate_itempool(world):
|
||||
|
||||
dungeon_items = [item for item in get_dungeon_item_pool_player(world)
|
||||
if item.name not in multiworld.worlds[player].dungeon_local_item_names]
|
||||
dungeon_item_replacements = difficulties[multiworld.difficulty[player]].extras[0]\
|
||||
+ difficulties[multiworld.difficulty[player]].extras[1]\
|
||||
+ difficulties[multiworld.difficulty[player]].extras[2]\
|
||||
+ difficulties[multiworld.difficulty[player]].extras[3]\
|
||||
+ difficulties[multiworld.difficulty[player]].extras[4]
|
||||
|
||||
for key_loc in key_drop_data:
|
||||
key_data = key_drop_data[key_loc]
|
||||
drop_item = ItemFactory(key_data[3], player)
|
||||
if multiworld.goal[player] == 'icerodhunt' or not multiworld.key_drop_shuffle[player]:
|
||||
if drop_item in dungeon_items:
|
||||
dungeon_items.remove(drop_item)
|
||||
else:
|
||||
dungeon = drop_item.name.split("(")[1].split(")")[0]
|
||||
if multiworld.mode[player] == 'inverted':
|
||||
if dungeon == "Agahnims Tower":
|
||||
dungeon = "Inverted Agahnims Tower"
|
||||
if dungeon == "Ganons Tower":
|
||||
dungeon = "Inverted Ganons Tower"
|
||||
if drop_item in world.dungeons[dungeon].small_keys:
|
||||
world.dungeons[dungeon].small_keys.remove(drop_item)
|
||||
elif world.dungeons[dungeon].big_key is not None and world.dungeons[dungeon].big_key == drop_item:
|
||||
world.dungeons[dungeon].big_key = None
|
||||
if not multiworld.key_drop_shuffle[player]:
|
||||
# key drop item was removed from the pool because key drop shuffle is off
|
||||
# and it will now place the removed key into its original location
|
||||
loc = multiworld.get_location(key_loc, player)
|
||||
loc.place_locked_item(drop_item)
|
||||
loc.address = None
|
||||
elif multiworld.goal[player] == 'icerodhunt':
|
||||
# key drop item removed because of icerodhunt
|
||||
multiworld.itempool.append(ItemFactory(GetBeemizerItem(world, player, 'Nothing'), player))
|
||||
multiworld.push_precollected(drop_item)
|
||||
elif "Small" in key_data[3] and multiworld.smallkey_shuffle[player] == smallkey_shuffle.option_universal:
|
||||
# key drop shuffle and universal keys are on. Add universal keys in place of key drop keys.
|
||||
multiworld.itempool.append(ItemFactory(GetBeemizerItem(world, player, 'Small Key (Universal)'), player))
|
||||
dungeon_item_replacements = sum(difficulties[multiworld.difficulty[player]].extras, []) * 2
|
||||
multiworld.random.shuffle(dungeon_item_replacements)
|
||||
if multiworld.goal[player] == 'icerodhunt':
|
||||
for item in dungeon_items:
|
||||
@@ -391,7 +418,7 @@ def generate_itempool(world):
|
||||
or (multiworld.bigkey_shuffle[player] == bigkey_shuffle.option_start_with and item.type == 'BigKey')
|
||||
or (multiworld.compass_shuffle[player] == compass_shuffle.option_start_with and item.type == 'Compass')
|
||||
or (multiworld.map_shuffle[player] == map_shuffle.option_start_with and item.type == 'Map')):
|
||||
dungeon_items.remove(item)
|
||||
dungeon_items.pop(x)
|
||||
multiworld.push_precollected(item)
|
||||
multiworld.itempool.append(ItemFactory(dungeon_item_replacements.pop(), player))
|
||||
multiworld.itempool.extend([item for item in dungeon_items])
|
||||
@@ -639,14 +666,27 @@ def get_pool_core(world, player: int):
|
||||
pool = ['Rupees (5)' if item in replace else item for item in pool]
|
||||
if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal:
|
||||
pool.extend(diff.universal_keys)
|
||||
item_to_place = 'Small Key (Universal)' if goal != 'icerodhunt' else 'Nothing'
|
||||
if mode == 'standard':
|
||||
key_location = world.random.choice(
|
||||
['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest',
|
||||
'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
|
||||
place_item(key_location, item_to_place)
|
||||
else:
|
||||
pool.extend([item_to_place])
|
||||
if world.key_drop_shuffle[player] and world.goal[player] != 'icerodhunt':
|
||||
key_locations = ['Secret Passage', 'Hyrule Castle - Map Guard Key Drop']
|
||||
key_location = world.random.choice(key_locations)
|
||||
key_locations.remove(key_location)
|
||||
place_item(key_location, "Small Key (Universal)")
|
||||
key_locations += ['Hyrule Castle - Boomerang Guard Key Drop', 'Hyrule Castle - Boomerang Chest',
|
||||
'Hyrule Castle - Map Chest']
|
||||
key_location = world.random.choice(key_locations)
|
||||
key_locations.remove(key_location)
|
||||
place_item(key_location, "Small Key (Universal)")
|
||||
key_locations += ['Hyrule Castle - Big Key Drop', 'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross']
|
||||
key_location = world.random.choice(key_locations)
|
||||
key_locations.remove(key_location)
|
||||
place_item(key_location, "Small Key (Universal)")
|
||||
key_locations += ['Sewers - Key Rat Key Drop']
|
||||
key_location = world.random.choice(key_locations)
|
||||
place_item(key_location, "Small Key (Universal)")
|
||||
pool = pool[:-3]
|
||||
if world.key_drop_shuffle[player]:
|
||||
pass # pool.extend([item_to_place] * (len(key_drop_data) - 1))
|
||||
|
||||
return (pool, placed_items, precollected_items, clock_mode, treasure_hunt_count, treasure_hunt_icon,
|
||||
additional_pieces_to_place)
|
||||
@@ -799,7 +839,9 @@ def make_custom_item_pool(world, player):
|
||||
pool.extend(['Moon Pearl'] * customitemarray[28])
|
||||
|
||||
if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal:
|
||||
itemtotal = itemtotal - 28 # Corrects for small keys not being in item pool in universal mode
|
||||
itemtotal = itemtotal - 28 # Corrects for small keys not being in item pool in universal Mode
|
||||
if world.key_drop_shuffle[player]:
|
||||
itemtotal = itemtotal - (len(key_drop_data) - 1)
|
||||
if itemtotal < total_items_to_place:
|
||||
pool.extend(['Nothing'] * (total_items_to_place - itemtotal))
|
||||
logging.warning(f"Pool was filled up with {total_items_to_place - itemtotal} Nothing's for player {player}")
|
||||
|
||||
@@ -101,6 +101,11 @@ class map_shuffle(DungeonItem):
|
||||
display_name = "Map Shuffle"
|
||||
|
||||
|
||||
class key_drop_shuffle(Toggle):
|
||||
"""Shuffle keys found in pots and dropped from killed enemies."""
|
||||
display_name = "Key Drop Shuffle"
|
||||
default = False
|
||||
|
||||
class Crystals(Range):
|
||||
range_start = 0
|
||||
range_end = 7
|
||||
@@ -432,6 +437,7 @@ alttp_options: typing.Dict[str, type(Option)] = {
|
||||
"open_pyramid": OpenPyramid,
|
||||
"bigkey_shuffle": bigkey_shuffle,
|
||||
"smallkey_shuffle": smallkey_shuffle,
|
||||
"key_drop_shuffle": key_drop_shuffle,
|
||||
"compass_shuffle": compass_shuffle,
|
||||
"map_shuffle": map_shuffle,
|
||||
"progressive": Progressive,
|
||||
|
||||
@@ -14,42 +14,26 @@ def create_regions(world, player):
|
||||
world.regions += [
|
||||
create_lw_region(world, player, 'Menu', None, ['Links House S&Q', 'Sanctuary S&Q', 'Old Man S&Q']),
|
||||
create_lw_region(world, player, 'Light World', ['Mushroom', 'Bottle Merchant', 'Flute Spot', 'Sunken Treasure',
|
||||
'Purple Chest', 'Flute Activation Spot'],
|
||||
["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Zoras River',
|
||||
'Kings Grave Outer Rocks', 'Dam',
|
||||
'Links House', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut',
|
||||
'Kakariko Well Drop', 'Kakariko Well Cave',
|
||||
'Blacksmiths Hut', 'Bat Cave Drop Ledge', 'Bat Cave Cave', 'Sick Kids House', 'Hobo Bridge',
|
||||
'Lost Woods Hideout Drop', 'Lost Woods Hideout Stump',
|
||||
'Lumberjack Tree Tree', 'Lumberjack Tree Cave', 'Mini Moldorm Cave', 'Ice Rod Cave',
|
||||
'Lake Hylia Central Island Pier',
|
||||
'Bonk Rock Cave', 'Library', 'Potion Shop', 'Two Brothers House (East)',
|
||||
'Desert Palace Stairs', 'Eastern Palace', 'Master Sword Meadow',
|
||||
'Sanctuary', 'Sanctuary Grave', 'Death Mountain Entrance Rock', 'Flute Spot 1',
|
||||
'Dark Desert Teleporter', 'East Hyrule Teleporter', 'South Hyrule Teleporter',
|
||||
'Kakariko Teleporter',
|
||||
'Elder House (East)', 'Elder House (West)', 'North Fairy Cave', 'North Fairy Cave Drop',
|
||||
'Lost Woods Gamble', 'Snitch Lady (East)', 'Snitch Lady (West)', 'Tavern (Front)',
|
||||
'Bush Covered House', 'Light World Bomb Hut', 'Kakariko Shop', 'Long Fairy Cave',
|
||||
'Good Bee Cave', '20 Rupee Cave', 'Cave Shop (Lake Hylia)', 'Waterfall of Wishing',
|
||||
'Hyrule Castle Main Gate',
|
||||
'Bonk Fairy (Light)', '50 Rupee Cave', 'Fortune Teller (Light)', 'Lake Hylia Fairy',
|
||||
'Light Hype Fairy', 'Desert Fairy', 'Lumberjack House', 'Lake Hylia Fortune Teller',
|
||||
'Kakariko Gamble Game', 'Top of Pyramid']),
|
||||
create_lw_region(world, player, 'Death Mountain Entrance', None,
|
||||
['Old Man Cave (West)', 'Death Mountain Entrance Drop']),
|
||||
create_lw_region(world, player, 'Lake Hylia Central Island', None,
|
||||
['Capacity Upgrade', 'Lake Hylia Central Island Teleporter']),
|
||||
'Purple Chest', 'Flute Activation Spot'],
|
||||
["Blinds Hideout", "Hyrule Castle Secret Entrance Drop", 'Zoras River', 'Kings Grave Outer Rocks', 'Dam',
|
||||
'Links House', 'Tavern North', 'Chicken House', 'Aginahs Cave', 'Sahasrahlas Hut', 'Kakariko Well Drop', 'Kakariko Well Cave',
|
||||
'Blacksmiths Hut', 'Bat Cave Drop Ledge', 'Bat Cave Cave', 'Sick Kids House', 'Hobo Bridge', 'Lost Woods Hideout Drop', 'Lost Woods Hideout Stump',
|
||||
'Lumberjack Tree Tree', 'Lumberjack Tree Cave', 'Mini Moldorm Cave', 'Ice Rod Cave', 'Lake Hylia Central Island Pier',
|
||||
'Bonk Rock Cave', 'Library', 'Potion Shop', 'Two Brothers House (East)', 'Desert Palace Stairs', 'Eastern Palace', 'Master Sword Meadow',
|
||||
'Sanctuary', 'Sanctuary Grave', 'Death Mountain Entrance Rock', 'Flute Spot 1', 'Dark Desert Teleporter', 'East Hyrule Teleporter', 'South Hyrule Teleporter', 'Kakariko Teleporter',
|
||||
'Elder House (East)', 'Elder House (West)', 'North Fairy Cave', 'North Fairy Cave Drop', 'Lost Woods Gamble', 'Snitch Lady (East)', 'Snitch Lady (West)', 'Tavern (Front)',
|
||||
'Bush Covered House', 'Light World Bomb Hut', 'Kakariko Shop', 'Long Fairy Cave', 'Good Bee Cave', '20 Rupee Cave', 'Cave Shop (Lake Hylia)', 'Waterfall of Wishing', 'Hyrule Castle Main Gate',
|
||||
'Bonk Fairy (Light)', '50 Rupee Cave', 'Fortune Teller (Light)', 'Lake Hylia Fairy', 'Light Hype Fairy', 'Desert Fairy', 'Lumberjack House', 'Lake Hylia Fortune Teller', 'Kakariko Gamble Game', 'Top of Pyramid']),
|
||||
create_lw_region(world, player, 'Death Mountain Entrance', None, ['Old Man Cave (West)', 'Death Mountain Entrance Drop']),
|
||||
create_lw_region(world, player, 'Lake Hylia Central Island', None, ['Capacity Upgrade', 'Lake Hylia Central Island Teleporter']),
|
||||
create_cave_region(world, player, 'Blinds Hideout', 'a bounty of five items', ["Blind\'s Hideout - Top",
|
||||
"Blind\'s Hideout - Left",
|
||||
"Blind\'s Hideout - Right",
|
||||
"Blind\'s Hideout - Far Left",
|
||||
"Blind\'s Hideout - Far Right"]),
|
||||
create_cave_region(world, player, 'Hyrule Castle Secret Entrance', 'a drop\'s exit',
|
||||
['Link\'s Uncle', 'Secret Passage'], ['Hyrule Castle Secret Entrance Exit']),
|
||||
"Blind\'s Hideout - Left",
|
||||
"Blind\'s Hideout - Right",
|
||||
"Blind\'s Hideout - Far Left",
|
||||
"Blind\'s Hideout - Far Right"]),
|
||||
create_cave_region(world, player, 'Hyrule Castle Secret Entrance', 'a drop\'s exit', ['Link\'s Uncle', 'Secret Passage'], ['Hyrule Castle Secret Entrance Exit']),
|
||||
create_lw_region(world, player, 'Zoras River', ['King Zora', 'Zora\'s Ledge']),
|
||||
create_cave_region(world, player, 'Waterfall of Wishing', 'a cave with two chests',
|
||||
['Waterfall Fairy - Left', 'Waterfall Fairy - Right']),
|
||||
create_cave_region(world, player, 'Waterfall of Wishing', 'a cave with two chests', ['Waterfall Fairy - Left', 'Waterfall Fairy - Right']),
|
||||
create_lw_region(world, player, 'Kings Grave Area', None, ['Kings Grave', 'Kings Grave Inner Rocks']),
|
||||
create_cave_region(world, player, 'Kings Grave', 'a cave with a chest', ['King\'s Tomb']),
|
||||
create_cave_region(world, player, 'North Fairy Cave', 'a drop\'s exit', None, ['North Fairy Cave Exit']),
|
||||
@@ -57,8 +41,7 @@ def create_regions(world, player):
|
||||
create_cave_region(world, player, 'Links House', 'your house', ['Link\'s House'], ['Links House Exit']),
|
||||
create_cave_region(world, player, 'Chris Houlihan Room', 'I AM ERROR', None, ['Chris Houlihan Room Exit']),
|
||||
create_cave_region(world, player, 'Tavern', 'the tavern', ['Kakariko Tavern']),
|
||||
create_cave_region(world, player, 'Elder House', 'a connector', None,
|
||||
['Elder House Exit (East)', 'Elder House Exit (West)']),
|
||||
create_cave_region(world, player, 'Elder House', 'a connector', None, ['Elder House Exit (East)', 'Elder House Exit (West)']),
|
||||
create_cave_region(world, player, 'Snitch Lady (East)', 'a boring house'),
|
||||
create_cave_region(world, player, 'Snitch Lady (West)', 'a boring house'),
|
||||
create_cave_region(world, player, 'Bush Covered House', 'the grass man'),
|
||||
@@ -79,12 +62,9 @@ def create_regions(world, player):
|
||||
create_cave_region(world, player, 'Dark Death Mountain Healer Fairy', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Chicken House', 'a house with a chest', ['Chicken House']),
|
||||
create_cave_region(world, player, 'Aginahs Cave', 'a cave with a chest', ['Aginah\'s Cave']),
|
||||
create_cave_region(world, player, 'Sahasrahlas Hut', 'Sahasrahla',
|
||||
['Sahasrahla\'s Hut - Left', 'Sahasrahla\'s Hut - Middle', 'Sahasrahla\'s Hut - Right',
|
||||
'Sahasrahla']),
|
||||
create_cave_region(world, player, 'Kakariko Well (top)', 'a drop\'s exit',
|
||||
['Kakariko Well - Top', 'Kakariko Well - Left', 'Kakariko Well - Middle',
|
||||
'Kakariko Well - Right', 'Kakariko Well - Bottom'], ['Kakariko Well (top to bottom)']),
|
||||
create_cave_region(world, player, 'Sahasrahlas Hut', 'Sahasrahla', ['Sahasrahla\'s Hut - Left', 'Sahasrahla\'s Hut - Middle', 'Sahasrahla\'s Hut - Right', 'Sahasrahla']),
|
||||
create_cave_region(world, player, 'Kakariko Well (top)', 'a drop\'s exit', ['Kakariko Well - Top', 'Kakariko Well - Left', 'Kakariko Well - Middle',
|
||||
'Kakariko Well - Right', 'Kakariko Well - Bottom'], ['Kakariko Well (top to bottom)']),
|
||||
create_cave_region(world, player, 'Kakariko Well (bottom)', 'a drop\'s exit', None, ['Kakariko Well Exit']),
|
||||
create_cave_region(world, player, 'Blacksmiths Hut', 'the smith', ['Blacksmith', 'Missing Smith']),
|
||||
create_lw_region(world, player, 'Bat Cave Drop Ledge', None, ['Bat Cave Drop']),
|
||||
@@ -92,12 +72,9 @@ def create_regions(world, player):
|
||||
create_cave_region(world, player, 'Bat Cave (left)', 'a drop\'s exit', None, ['Bat Cave Exit']),
|
||||
create_cave_region(world, player, 'Sick Kids House', 'the sick kid', ['Sick Kid']),
|
||||
create_lw_region(world, player, 'Hobo Bridge', ['Hobo']),
|
||||
create_cave_region(world, player, 'Lost Woods Hideout (top)', 'a drop\'s exit', ['Lost Woods Hideout'],
|
||||
['Lost Woods Hideout (top to bottom)']),
|
||||
create_cave_region(world, player, 'Lost Woods Hideout (bottom)', 'a drop\'s exit', None,
|
||||
['Lost Woods Hideout Exit']),
|
||||
create_cave_region(world, player, 'Lumberjack Tree (top)', 'a drop\'s exit', ['Lumberjack Tree'],
|
||||
['Lumberjack Tree (top to bottom)']),
|
||||
create_cave_region(world, player, 'Lost Woods Hideout (top)', 'a drop\'s exit', ['Lost Woods Hideout'], ['Lost Woods Hideout (top to bottom)']),
|
||||
create_cave_region(world, player, 'Lost Woods Hideout (bottom)', 'a drop\'s exit', None, ['Lost Woods Hideout Exit']),
|
||||
create_cave_region(world, player, 'Lumberjack Tree (top)', 'a drop\'s exit', ['Lumberjack Tree'], ['Lumberjack Tree (top to bottom)']),
|
||||
create_cave_region(world, player, 'Lumberjack Tree (bottom)', 'a drop\'s exit', None, ['Lumberjack Tree Exit']),
|
||||
create_lw_region(world, player, 'Cave 45 Ledge', None, ['Cave 45']),
|
||||
create_cave_region(world, player, 'Cave 45', 'a cave with an item', ['Cave 45']),
|
||||
@@ -105,9 +82,8 @@ def create_regions(world, player):
|
||||
create_cave_region(world, player, 'Graveyard Cave', 'a cave with an item', ['Graveyard Cave']),
|
||||
create_cave_region(world, player, 'Checkerboard Cave', 'a cave with an item', ['Checkerboard Cave']),
|
||||
create_cave_region(world, player, 'Long Fairy Cave', 'a fairy fountain'),
|
||||
create_cave_region(world, player, 'Mini Moldorm Cave', 'a bounty of five items',
|
||||
['Mini Moldorm Cave - Far Left', 'Mini Moldorm Cave - Left', 'Mini Moldorm Cave - Right',
|
||||
'Mini Moldorm Cave - Far Right', 'Mini Moldorm Cave - Generous Guy']),
|
||||
create_cave_region(world, player, 'Mini Moldorm Cave', 'a bounty of five items', ['Mini Moldorm Cave - Far Left', 'Mini Moldorm Cave - Left', 'Mini Moldorm Cave - Right',
|
||||
'Mini Moldorm Cave - Far Right', 'Mini Moldorm Cave - Generous Guy']),
|
||||
create_cave_region(world, player, 'Ice Rod Cave', 'a cave with a chest', ['Ice Rod Cave']),
|
||||
create_cave_region(world, player, 'Good Bee Cave', 'a cold bee'),
|
||||
create_cave_region(world, player, '20 Rupee Cave', 'a cave with some cash'),
|
||||
@@ -119,91 +95,56 @@ def create_regions(world, player):
|
||||
create_cave_region(world, player, 'Potion Shop', 'the potion shop', ['Potion Shop']),
|
||||
create_lw_region(world, player, 'Lake Hylia Island', ['Lake Hylia Island']),
|
||||
create_cave_region(world, player, 'Capacity Upgrade', 'the queen of fairies'),
|
||||
create_cave_region(world, player, 'Two Brothers House', 'a connector', None,
|
||||
['Two Brothers House Exit (East)', 'Two Brothers House Exit (West)']),
|
||||
create_cave_region(world, player, 'Two Brothers House', 'a connector', None, ['Two Brothers House Exit (East)', 'Two Brothers House Exit (West)']),
|
||||
create_lw_region(world, player, 'Maze Race Ledge', ['Maze Race'], ['Two Brothers House (West)']),
|
||||
create_cave_region(world, player, '50 Rupee Cave', 'a cave with some cash'),
|
||||
create_lw_region(world, player, 'Desert Ledge', ['Desert Ledge'],
|
||||
['Desert Palace Entrance (North) Rocks', 'Desert Palace Entrance (West)']),
|
||||
create_lw_region(world, player, 'Desert Ledge', ['Desert Ledge'], ['Desert Palace Entrance (North) Rocks', 'Desert Palace Entrance (West)']),
|
||||
create_lw_region(world, player, 'Desert Ledge (Northeast)', None, ['Checkerboard Cave']),
|
||||
create_lw_region(world, player, 'Desert Palace Stairs', None, ['Desert Palace Entrance (South)']),
|
||||
create_lw_region(world, player, 'Desert Palace Lone Stairs', None,
|
||||
['Desert Palace Stairs Drop', 'Desert Palace Entrance (East)']),
|
||||
create_lw_region(world, player, 'Desert Palace Entrance (North) Spot', None,
|
||||
['Desert Palace Entrance (North)', 'Desert Ledge Return Rocks']),
|
||||
create_dungeon_region(world, player, 'Desert Palace Main (Outer)', 'Desert Palace',
|
||||
['Desert Palace - Big Chest', 'Desert Palace - Torch', 'Desert Palace - Map Chest'],
|
||||
['Desert Palace Pots (Outer)', 'Desert Palace Exit (West)', 'Desert Palace Exit (East)',
|
||||
'Desert Palace East Wing']),
|
||||
create_dungeon_region(world, player, 'Desert Palace Main (Inner)', 'Desert Palace', None,
|
||||
['Desert Palace Exit (South)', 'Desert Palace Pots (Inner)']),
|
||||
create_dungeon_region(world, player, 'Desert Palace East', 'Desert Palace',
|
||||
['Desert Palace - Compass Chest', 'Desert Palace - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Desert Palace North', 'Desert Palace',
|
||||
['Desert Palace - Boss', 'Desert Palace - Prize'], ['Desert Palace Exit (North)']),
|
||||
create_dungeon_region(world, player, 'Eastern Palace', 'Eastern Palace',
|
||||
['Eastern Palace - Compass Chest', 'Eastern Palace - Big Chest',
|
||||
'Eastern Palace - Cannonball Chest',
|
||||
'Eastern Palace - Big Key Chest', 'Eastern Palace - Map Chest', 'Eastern Palace - Boss',
|
||||
'Eastern Palace - Prize'], ['Eastern Palace Exit']),
|
||||
create_lw_region(world, player, 'Desert Palace Lone Stairs', None, ['Desert Palace Stairs Drop', 'Desert Palace Entrance (East)']),
|
||||
create_lw_region(world, player, 'Desert Palace Entrance (North) Spot', None, ['Desert Palace Entrance (North)', 'Desert Ledge Return Rocks']),
|
||||
create_dungeon_region(world, player, 'Desert Palace Main (Outer)', 'Desert Palace', ['Desert Palace - Big Chest', 'Desert Palace - Torch', 'Desert Palace - Map Chest'],
|
||||
['Desert Palace Pots (Outer)', 'Desert Palace Exit (West)', 'Desert Palace Exit (East)', 'Desert Palace East Wing']),
|
||||
create_dungeon_region(world, player, 'Desert Palace Main (Inner)', 'Desert Palace', None, ['Desert Palace Exit (South)', 'Desert Palace Pots (Inner)']),
|
||||
create_dungeon_region(world, player, 'Desert Palace East', 'Desert Palace', ['Desert Palace - Compass Chest', 'Desert Palace - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Desert Palace North', 'Desert Palace', ['Desert Palace - Desert Tiles 1 Pot Key', 'Desert Palace - Beamos Hall Pot Key', 'Desert Palace - Desert Tiles 2 Pot Key',
|
||||
'Desert Palace - Boss', 'Desert Palace - Prize'], ['Desert Palace Exit (North)']),
|
||||
create_dungeon_region(world, player, 'Eastern Palace', 'Eastern Palace', ['Eastern Palace - Compass Chest', 'Eastern Palace - Big Chest', 'Eastern Palace - Cannonball Chest',
|
||||
'Eastern Palace - Dark Square Pot Key', 'Eastern Palace - Dark Eyegore Key Drop', 'Eastern Palace - Big Key Chest',
|
||||
'Eastern Palace - Map Chest', 'Eastern Palace - Boss', 'Eastern Palace - Prize'], ['Eastern Palace Exit']),
|
||||
create_lw_region(world, player, 'Master Sword Meadow', ['Master Sword Pedestal']),
|
||||
create_cave_region(world, player, 'Lost Woods Gamble', 'a game of chance'),
|
||||
create_lw_region(world, player, 'Hyrule Castle Courtyard', None,
|
||||
['Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Entrance (South)']),
|
||||
create_lw_region(world, player, 'Hyrule Castle Ledge', None,
|
||||
['Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (West)', 'Agahnims Tower',
|
||||
'Hyrule Castle Ledge Courtyard Drop']),
|
||||
create_dungeon_region(world, player, 'Hyrule Castle', 'Hyrule Castle',
|
||||
['Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest',
|
||||
'Hyrule Castle - Zelda\'s Chest'],
|
||||
['Hyrule Castle Exit (East)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (South)',
|
||||
'Throne Room']),
|
||||
create_lw_region(world, player, 'Hyrule Castle Courtyard', None, ['Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Entrance (South)']),
|
||||
create_lw_region(world, player, 'Hyrule Castle Ledge', None, ['Hyrule Castle Entrance (East)', 'Hyrule Castle Entrance (West)', 'Agahnims Tower', 'Hyrule Castle Ledge Courtyard Drop']),
|
||||
create_dungeon_region(world, player, 'Hyrule Castle', 'Hyrule Castle', ['Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest', 'Hyrule Castle - Zelda\'s Chest',
|
||||
'Hyrule Castle - Map Guard Key Drop', 'Hyrule Castle - Boomerang Guard Key Drop', 'Hyrule Castle - Big Key Drop'],
|
||||
['Hyrule Castle Exit (East)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (South)', 'Throne Room']),
|
||||
create_dungeon_region(world, player, 'Sewer Drop', 'a drop\'s exit', None, ['Sewer Drop']), # This exists only to be referenced for access checks
|
||||
create_dungeon_region(world, player, 'Sewers (Dark)', 'a drop\'s exit', ['Sewers - Dark Cross'],
|
||||
['Sewers Door']),
|
||||
create_dungeon_region(world, player, 'Sewers', 'a drop\'s exit',
|
||||
['Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle',
|
||||
'Sewers - Secret Room - Right'], ['Sanctuary Push Door', 'Sewers Back Door']),
|
||||
create_dungeon_region(world, player, 'Sewers (Dark)', 'a drop\'s exit', ['Sewers - Dark Cross', 'Sewers - Key Rat Key Drop'], ['Sewers Door']),
|
||||
create_dungeon_region(world, player, 'Sewers', 'a drop\'s exit', ['Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle',
|
||||
'Sewers - Secret Room - Right'], ['Sanctuary Push Door', 'Sewers Back Door']),
|
||||
create_dungeon_region(world, player, 'Sanctuary', 'a drop\'s exit', ['Sanctuary'], ['Sanctuary Exit']),
|
||||
create_dungeon_region(world, player, 'Agahnims Tower', 'Castle Tower',
|
||||
['Castle Tower - Room 03', 'Castle Tower - Dark Maze'],
|
||||
['Agahnim 1', 'Agahnims Tower Exit']),
|
||||
create_dungeon_region(world, player, 'Agahnims Tower', 'Castle Tower', ['Castle Tower - Room 03', 'Castle Tower - Dark Maze', 'Castle Tower - Dark Archer Key Drop', 'Castle Tower - Circle of Pots Key Drop'], ['Agahnim 1', 'Agahnims Tower Exit']),
|
||||
create_dungeon_region(world, player, 'Agahnim 1', 'Castle Tower', ['Agahnim 1'], None),
|
||||
create_cave_region(world, player, 'Old Man Cave', 'a connector', ['Old Man'],
|
||||
['Old Man Cave Exit (East)', 'Old Man Cave Exit (West)']),
|
||||
create_cave_region(world, player, 'Old Man House', 'a connector', None,
|
||||
['Old Man House Exit (Bottom)', 'Old Man House Front to Back']),
|
||||
create_cave_region(world, player, 'Old Man House Back', 'a connector', None,
|
||||
['Old Man House Exit (Top)', 'Old Man House Back to Front']),
|
||||
create_lw_region(world, player, 'Death Mountain', None,
|
||||
['Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)',
|
||||
'Death Mountain Return Cave (East)', 'Spectacle Rock Cave', 'Spectacle Rock Cave Peak',
|
||||
'Spectacle Rock Cave (Bottom)', 'Broken Bridge (West)', 'Death Mountain Teleporter']),
|
||||
create_cave_region(world, player, 'Death Mountain Return Cave', 'a connector', None,
|
||||
['Death Mountain Return Cave Exit (West)', 'Death Mountain Return Cave Exit (East)']),
|
||||
create_lw_region(world, player, 'Death Mountain Return Ledge', None,
|
||||
['Death Mountain Return Ledge Drop', 'Death Mountain Return Cave (West)']),
|
||||
create_cave_region(world, player, 'Spectacle Rock Cave (Top)', 'a connector', ['Spectacle Rock Cave'],
|
||||
['Spectacle Rock Cave Drop', 'Spectacle Rock Cave Exit (Top)']),
|
||||
create_cave_region(world, player, 'Spectacle Rock Cave (Bottom)', 'a connector', None,
|
||||
['Spectacle Rock Cave Exit']),
|
||||
create_cave_region(world, player, 'Spectacle Rock Cave (Peak)', 'a connector', None,
|
||||
['Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave Exit (Peak)']),
|
||||
create_lw_region(world, player, 'East Death Mountain (Bottom)', None,
|
||||
['Broken Bridge (East)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)',
|
||||
'East Death Mountain Teleporter', 'Hookshot Fairy', 'Fairy Ascension Rocks',
|
||||
'Spiral Cave (Bottom)']),
|
||||
create_cave_region(world, player, 'Old Man Cave', 'a connector', ['Old Man'], ['Old Man Cave Exit (East)', 'Old Man Cave Exit (West)']),
|
||||
create_cave_region(world, player, 'Old Man House', 'a connector', None, ['Old Man House Exit (Bottom)', 'Old Man House Front to Back']),
|
||||
create_cave_region(world, player, 'Old Man House Back', 'a connector', None, ['Old Man House Exit (Top)', 'Old Man House Back to Front']),
|
||||
create_lw_region(world, player, 'Death Mountain', None, ['Old Man Cave (East)', 'Old Man House (Bottom)', 'Old Man House (Top)', 'Death Mountain Return Cave (East)', 'Spectacle Rock Cave', 'Spectacle Rock Cave Peak', 'Spectacle Rock Cave (Bottom)', 'Broken Bridge (West)', 'Death Mountain Teleporter']),
|
||||
create_cave_region(world, player, 'Death Mountain Return Cave', 'a connector', None, ['Death Mountain Return Cave Exit (West)', 'Death Mountain Return Cave Exit (East)']),
|
||||
create_lw_region(world, player, 'Death Mountain Return Ledge', None, ['Death Mountain Return Ledge Drop', 'Death Mountain Return Cave (West)']),
|
||||
create_cave_region(world, player, 'Spectacle Rock Cave (Top)', 'a connector', ['Spectacle Rock Cave'], ['Spectacle Rock Cave Drop', 'Spectacle Rock Cave Exit (Top)']),
|
||||
create_cave_region(world, player, 'Spectacle Rock Cave (Bottom)', 'a connector', None, ['Spectacle Rock Cave Exit']),
|
||||
create_cave_region(world, player, 'Spectacle Rock Cave (Peak)', 'a connector', None, ['Spectacle Rock Cave Peak Drop', 'Spectacle Rock Cave Exit (Peak)']),
|
||||
create_lw_region(world, player, 'East Death Mountain (Bottom)', None, ['Broken Bridge (East)', 'Paradox Cave (Bottom)', 'Paradox Cave (Middle)', 'East Death Mountain Teleporter', 'Hookshot Fairy', 'Fairy Ascension Rocks', 'Spiral Cave (Bottom)']),
|
||||
create_cave_region(world, player, 'Hookshot Fairy', 'fairies deep in a cave'),
|
||||
create_cave_region(world, player, 'Paradox Cave Front', 'a connector', None,
|
||||
['Paradox Cave Push Block Reverse', 'Paradox Cave Exit (Bottom)',
|
||||
'Light World Death Mountain Shop']),
|
||||
create_cave_region(world, player, 'Paradox Cave Front', 'a connector', None, ['Paradox Cave Push Block Reverse', 'Paradox Cave Exit (Bottom)', 'Light World Death Mountain Shop']),
|
||||
create_cave_region(world, player, 'Paradox Cave Chest Area', 'a connector', ['Paradox Cave Lower - Far Left',
|
||||
'Paradox Cave Lower - Left',
|
||||
'Paradox Cave Lower - Right',
|
||||
'Paradox Cave Lower - Far Right',
|
||||
'Paradox Cave Lower - Middle',
|
||||
'Paradox Cave Upper - Left',
|
||||
'Paradox Cave Upper - Right'],
|
||||
'Paradox Cave Lower - Left',
|
||||
'Paradox Cave Lower - Right',
|
||||
'Paradox Cave Lower - Far Right',
|
||||
'Paradox Cave Lower - Middle',
|
||||
'Paradox Cave Upper - Left',
|
||||
'Paradox Cave Upper - Right'],
|
||||
['Paradox Cave Push Block', 'Paradox Cave Bomb Jump']),
|
||||
create_cave_region(world, player, 'Paradox Cave', 'a connector', None,
|
||||
['Paradox Cave Exit (Middle)', 'Paradox Cave Exit (Top)', 'Paradox Cave Drop']),
|
||||
@@ -342,162 +283,98 @@ def create_regions(world, player):
|
||||
create_lw_region(world, player, 'Mimic Cave Ledge', None, ['Mimic Cave']),
|
||||
create_cave_region(world, player, 'Mimic Cave', 'Mimic Cave', ['Mimic Cave']),
|
||||
|
||||
create_dungeon_region(world, player, 'Swamp Palace (Entrance)', 'Swamp Palace', None,
|
||||
['Swamp Palace Moat', 'Swamp Palace Exit']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (First Room)', 'Swamp Palace', ['Swamp Palace - Entrance'],
|
||||
['Swamp Palace Small Key Door']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (Starting Area)', 'Swamp Palace',
|
||||
['Swamp Palace - Map Chest'], ['Swamp Palace (Center)']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (Center)', 'Swamp Palace',
|
||||
['Swamp Palace - Big Chest', 'Swamp Palace - Compass Chest',
|
||||
'Swamp Palace - Big Key Chest', 'Swamp Palace - West Chest'], ['Swamp Palace (North)']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (North)', 'Swamp Palace',
|
||||
['Swamp Palace - Flooded Room - Left', 'Swamp Palace - Flooded Room - Right',
|
||||
'Swamp Palace - Waterfall Room', 'Swamp Palace - Boss', 'Swamp Palace - Prize']),
|
||||
create_dungeon_region(world, player, 'Thieves Town (Entrance)', 'Thieves\' Town',
|
||||
['Thieves\' Town - Big Key Chest',
|
||||
'Thieves\' Town - Map Chest',
|
||||
'Thieves\' Town - Compass Chest',
|
||||
'Thieves\' Town - Ambush Chest'], ['Thieves Town Big Key Door', 'Thieves Town Exit']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (Entrance)', 'Swamp Palace', None, ['Swamp Palace Moat', 'Swamp Palace Exit']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (First Room)', 'Swamp Palace', ['Swamp Palace - Entrance'], ['Swamp Palace Small Key Door']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (Starting Area)', 'Swamp Palace', ['Swamp Palace - Map Chest', 'Swamp Palace - Pot Row Pot Key',
|
||||
'Swamp Palace - Trench 1 Pot Key'], ['Swamp Palace (Center)']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (Center)', 'Swamp Palace', ['Swamp Palace - Big Chest', 'Swamp Palace - Compass Chest', 'Swamp Palace - Hookshot Pot Key',
|
||||
'Swamp Palace - Trench 2 Pot Key'], ['Swamp Palace (North)', 'Swamp Palace (West)']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (West)', 'Swamp Palace', ['Swamp Palace - Big Key Chest', 'Swamp Palace - West Chest']),
|
||||
create_dungeon_region(world, player, 'Swamp Palace (North)', 'Swamp Palace', ['Swamp Palace - Flooded Room - Left', 'Swamp Palace - Flooded Room - Right',
|
||||
'Swamp Palace - Waterway Pot Key', 'Swamp Palace - Waterfall Room',
|
||||
'Swamp Palace - Boss', 'Swamp Palace - Prize']),
|
||||
create_dungeon_region(world, player, 'Thieves Town (Entrance)', 'Thieves\' Town', ['Thieves\' Town - Big Key Chest',
|
||||
'Thieves\' Town - Map Chest',
|
||||
'Thieves\' Town - Compass Chest',
|
||||
'Thieves\' Town - Ambush Chest'], ['Thieves Town Big Key Door', 'Thieves Town Exit']),
|
||||
create_dungeon_region(world, player, 'Thieves Town (Deep)', 'Thieves\' Town', ['Thieves\' Town - Attic',
|
||||
'Thieves\' Town - Big Chest',
|
||||
'Thieves\' Town - Blind\'s Cell'],
|
||||
['Blind Fight']),
|
||||
create_dungeon_region(world, player, 'Blind Fight', 'Thieves\' Town',
|
||||
['Thieves\' Town - Boss', 'Thieves\' Town - Prize']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section', 'Skull Woods', ['Skull Woods - Map Chest'],
|
||||
['Skull Woods First Section Exit', 'Skull Woods First Section Bomb Jump',
|
||||
'Skull Woods First Section South Door', 'Skull Woods First Section West Door']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section (Right)', 'Skull Woods',
|
||||
['Skull Woods - Pinball Room'], ['Skull Woods First Section (Right) North Door']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section (Left)', 'Skull Woods',
|
||||
['Skull Woods - Compass Chest', 'Skull Woods - Pot Prison'],
|
||||
['Skull Woods First Section (Left) Door to Exit',
|
||||
'Skull Woods First Section (Left) Door to Right']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section (Top)', 'Skull Woods',
|
||||
['Skull Woods - Big Chest'], ['Skull Woods First Section (Top) One-Way Path']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Second Section (Drop)', 'Skull Woods', None,
|
||||
['Skull Woods Second Section (Drop)']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Second Section', 'Skull Woods',
|
||||
['Skull Woods - Big Key Chest'],
|
||||
['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Final Section (Entrance)', 'Skull Woods',
|
||||
['Skull Woods - Bridge Room'],
|
||||
['Skull Woods Torch Room', 'Skull Woods Final Section Exit']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Final Section (Mothula)', 'Skull Woods',
|
||||
['Skull Woods - Boss', 'Skull Woods - Prize']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Entrance)', 'Ice Palace', None,
|
||||
['Ice Palace Entrance Room', 'Ice Palace Exit']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Main)', 'Ice Palace',
|
||||
['Ice Palace - Compass Chest', 'Ice Palace - Freezor Chest',
|
||||
'Ice Palace - Big Chest', 'Ice Palace - Iced T Room'],
|
||||
['Ice Palace (East)', 'Ice Palace (Kholdstare)']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (East)', 'Ice Palace', ['Ice Palace - Spike Room'],
|
||||
['Ice Palace (East Top)']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (East Top)', 'Ice Palace',
|
||||
['Ice Palace - Big Key Chest', 'Ice Palace - Map Chest']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Kholdstare)', 'Ice Palace',
|
||||
['Ice Palace - Boss', 'Ice Palace - Prize']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Entrance)', 'Misery Mire', None,
|
||||
['Misery Mire Entrance Gap', 'Misery Mire Exit']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Main)', 'Misery Mire',
|
||||
['Misery Mire - Big Chest', 'Misery Mire - Map Chest', 'Misery Mire - Main Lobby',
|
||||
'Misery Mire - Bridge Chest', 'Misery Mire - Spike Chest'],
|
||||
['Misery Mire (West)', 'Misery Mire Big Key Door']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (West)', 'Misery Mire',
|
||||
['Misery Mire - Compass Chest', 'Misery Mire - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Final Area)', 'Misery Mire', None,
|
||||
['Misery Mire (Vitreous)']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Vitreous)', 'Misery Mire',
|
||||
['Misery Mire - Boss', 'Misery Mire - Prize']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Entrance)', 'Turtle Rock', None,
|
||||
['Turtle Rock Entrance Gap', 'Turtle Rock Exit (Front)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (First Section)', 'Turtle Rock',
|
||||
['Turtle Rock - Compass Chest', 'Turtle Rock - Roller Room - Left',
|
||||
'Turtle Rock - Roller Room - Right'],
|
||||
['Turtle Rock Pokey Room', 'Turtle Rock Entrance Gap Reverse']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock',
|
||||
['Turtle Rock - Chain Chomps'],
|
||||
['Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Chain Chomp Room) (South)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Second Section)', 'Turtle Rock',
|
||||
['Turtle Rock - Big Key Chest'],
|
||||
['Turtle Rock Ledge Exit (West)', 'Turtle Rock Chain Chomp Staircase',
|
||||
'Turtle Rock Big Key Door']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Big Chest)', 'Turtle Rock', ['Turtle Rock - Big Chest'],
|
||||
['Turtle Rock (Big Chest) (North)', 'Turtle Rock Ledge Exit (East)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Crystaroller Room)', 'Turtle Rock',
|
||||
['Turtle Rock - Crystaroller Room'],
|
||||
['Turtle Rock Dark Room Staircase', 'Turtle Rock Big Key Door Reverse']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Dark Room)', 'Turtle Rock', None,
|
||||
['Turtle Rock (Dark Room) (North)', 'Turtle Rock (Dark Room) (South)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Eye Bridge)', 'Turtle Rock',
|
||||
['Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right',
|
||||
'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'],
|
||||
['Turtle Rock Dark Room (South)', 'Turtle Rock (Trinexx)',
|
||||
'Turtle Rock Isolated Ledge Exit']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Trinexx)', 'Turtle Rock',
|
||||
['Turtle Rock - Boss', 'Turtle Rock - Prize']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Entrance)', 'Palace of Darkness',
|
||||
['Palace of Darkness - Shooter Room'],
|
||||
['Palace of Darkness Bridge Room', 'Palace of Darkness Bonk Wall',
|
||||
'Palace of Darkness Exit']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Center)', 'Palace of Darkness',
|
||||
['Palace of Darkness - The Arena - Bridge', 'Palace of Darkness - Stalfos Basement'],
|
||||
['Palace of Darkness Big Key Chest Staircase', 'Palace of Darkness (North)',
|
||||
'Palace of Darkness Big Key Door']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Big Key Chest)', 'Palace of Darkness',
|
||||
['Palace of Darkness - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Bonk Section)', 'Palace of Darkness',
|
||||
['Palace of Darkness - The Arena - Ledge', 'Palace of Darkness - Map Chest'],
|
||||
['Palace of Darkness Hammer Peg Drop']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (North)', 'Palace of Darkness',
|
||||
['Palace of Darkness - Compass Chest', 'Palace of Darkness - Dark Basement - Left',
|
||||
'Palace of Darkness - Dark Basement - Right'],
|
||||
'Thieves\' Town - Big Chest',
|
||||
'Thieves\' Town - Hallway Pot Key',
|
||||
'Thieves\' Town - Spike Switch Pot Key',
|
||||
'Thieves\' Town - Blind\'s Cell'], ['Blind Fight']),
|
||||
create_dungeon_region(world, player, 'Blind Fight', 'Thieves\' Town', ['Thieves\' Town - Boss', 'Thieves\' Town - Prize']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section', 'Skull Woods', ['Skull Woods - Map Chest'], ['Skull Woods First Section Exit', 'Skull Woods First Section Bomb Jump', 'Skull Woods First Section South Door', 'Skull Woods First Section West Door']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section (Right)', 'Skull Woods', ['Skull Woods - Pinball Room'], ['Skull Woods First Section (Right) North Door']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section (Left)', 'Skull Woods', ['Skull Woods - Compass Chest', 'Skull Woods - Pot Prison'], ['Skull Woods First Section (Left) Door to Exit', 'Skull Woods First Section (Left) Door to Right']),
|
||||
create_dungeon_region(world, player, 'Skull Woods First Section (Top)', 'Skull Woods', ['Skull Woods - Big Chest'], ['Skull Woods First Section (Top) One-Way Path']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Second Section (Drop)', 'Skull Woods', None, ['Skull Woods Second Section (Drop)']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Second Section', 'Skull Woods', ['Skull Woods - Big Key Chest', 'Skull Woods - West Lobby Pot Key'], ['Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Final Section (Entrance)', 'Skull Woods', ['Skull Woods - Bridge Room'], ['Skull Woods Torch Room', 'Skull Woods Final Section Exit']),
|
||||
create_dungeon_region(world, player, 'Skull Woods Final Section (Mothula)', 'Skull Woods', ['Skull Woods - Spike Corner Key Drop', 'Skull Woods - Boss', 'Skull Woods - Prize']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Entrance)', 'Ice Palace', ['Ice Palace - Jelly Key Drop'], ['Ice Palace (Second Section)', 'Ice Palace Exit']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Second Section)', 'Ice Palace', ['Ice Palace - Conveyor Key Drop', 'Ice Palace - Compass Chest'], ['Ice Palace (Main)']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Main)', 'Ice Palace', ['Ice Palace - Freezor Chest',
|
||||
'Ice Palace - Many Pots Pot Key',
|
||||
'Ice Palace - Big Chest', 'Ice Palace - Iced T Room'], ['Ice Palace (East)', 'Ice Palace (Kholdstare)']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (East)', 'Ice Palace', ['Ice Palace - Spike Room'], ['Ice Palace (East Top)']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (East Top)', 'Ice Palace', ['Ice Palace - Big Key Chest', 'Ice Palace - Map Chest', 'Ice Palace - Hammer Block Key Drop']),
|
||||
create_dungeon_region(world, player, 'Ice Palace (Kholdstare)', 'Ice Palace', ['Ice Palace - Boss', 'Ice Palace - Prize']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Entrance)', 'Misery Mire', None, ['Misery Mire Entrance Gap', 'Misery Mire Exit']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Main)', 'Misery Mire', ['Misery Mire - Big Chest', 'Misery Mire - Map Chest', 'Misery Mire - Main Lobby',
|
||||
'Misery Mire - Bridge Chest', 'Misery Mire - Spike Chest',
|
||||
'Misery Mire - Spikes Pot Key', 'Misery Mire - Fishbone Pot Key',
|
||||
'Misery Mire - Conveyor Crystal Key Drop'], ['Misery Mire (West)', 'Misery Mire Big Key Door']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (West)', 'Misery Mire', ['Misery Mire - Compass Chest', 'Misery Mire - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Final Area)', 'Misery Mire', None, ['Misery Mire (Vitreous)']),
|
||||
create_dungeon_region(world, player, 'Misery Mire (Vitreous)', 'Misery Mire', ['Misery Mire - Boss', 'Misery Mire - Prize']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Entrance)', 'Turtle Rock', None, ['Turtle Rock Entrance Gap', 'Turtle Rock Exit (Front)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (First Section)', 'Turtle Rock', ['Turtle Rock - Compass Chest', 'Turtle Rock - Roller Room - Left',
|
||||
'Turtle Rock - Roller Room - Right'],
|
||||
['Turtle Rock Entrance to Pokey Room', 'Turtle Rock Entrance Gap Reverse']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Pokey Room)', 'Turtle Rock', ['Turtle Rock - Pokey 1 Key Drop'], ['Turtle Rock (Pokey Room) (North)', 'Turtle Rock (Pokey Room) (South)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Chain Chomp Room)', 'Turtle Rock', ['Turtle Rock - Chain Chomps'], ['Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Chain Chomp Room) (South)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Second Section)', 'Turtle Rock', ['Turtle Rock - Big Key Chest', 'Turtle Rock - Pokey 2 Key Drop'], ['Turtle Rock Ledge Exit (West)', 'Turtle Rock Chain Chomp Staircase', 'Turtle Rock Big Key Door']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Big Chest)', 'Turtle Rock', ['Turtle Rock - Big Chest'], ['Turtle Rock (Big Chest) (North)', 'Turtle Rock Ledge Exit (East)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Crystaroller Room)', 'Turtle Rock', ['Turtle Rock - Crystaroller Room'], ['Turtle Rock Dark Room Staircase', 'Turtle Rock Big Key Door Reverse']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Dark Room)', 'Turtle Rock', None, ['Turtle Rock (Dark Room) (North)', 'Turtle Rock (Dark Room) (South)']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Eye Bridge)', 'Turtle Rock', ['Turtle Rock - Eye Bridge - Bottom Left', 'Turtle Rock - Eye Bridge - Bottom Right',
|
||||
'Turtle Rock - Eye Bridge - Top Left', 'Turtle Rock - Eye Bridge - Top Right'],
|
||||
['Turtle Rock Dark Room (South)', 'Turtle Rock (Trinexx)', 'Turtle Rock Isolated Ledge Exit']),
|
||||
create_dungeon_region(world, player, 'Turtle Rock (Trinexx)', 'Turtle Rock', ['Turtle Rock - Boss', 'Turtle Rock - Prize']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Entrance)', 'Palace of Darkness', ['Palace of Darkness - Shooter Room'], ['Palace of Darkness Bridge Room', 'Palace of Darkness Bonk Wall', 'Palace of Darkness Exit']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Center)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Bridge', 'Palace of Darkness - Stalfos Basement'],
|
||||
['Palace of Darkness Big Key Chest Staircase', 'Palace of Darkness (North)', 'Palace of Darkness Big Key Door']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Big Key Chest)', 'Palace of Darkness', ['Palace of Darkness - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Bonk Section)', 'Palace of Darkness', ['Palace of Darkness - The Arena - Ledge', 'Palace of Darkness - Map Chest'], ['Palace of Darkness Hammer Peg Drop']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (North)', 'Palace of Darkness', ['Palace of Darkness - Compass Chest', 'Palace of Darkness - Dark Basement - Left', 'Palace of Darkness - Dark Basement - Right'],
|
||||
['Palace of Darkness Spike Statue Room Door', 'Palace of Darkness Maze Door']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Maze)', 'Palace of Darkness',
|
||||
['Palace of Darkness - Dark Maze - Top', 'Palace of Darkness - Dark Maze - Bottom',
|
||||
'Palace of Darkness - Big Chest']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Harmless Hellway)', 'Palace of Darkness',
|
||||
['Palace of Darkness - Harmless Hellway']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Final Section)', 'Palace of Darkness',
|
||||
['Palace of Darkness - Boss', 'Palace of Darkness - Prize']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Entrance)', 'Ganon\'s Tower',
|
||||
['Ganons Tower - Bob\'s Torch', 'Ganons Tower - Hope Room - Left',
|
||||
'Ganons Tower - Hope Room - Right'],
|
||||
['Ganons Tower (Tile Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower Big Key Door',
|
||||
'Ganons Tower Exit']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Tile Room)', 'Ganon\'s Tower', ['Ganons Tower - Tile Room'],
|
||||
['Ganons Tower (Tile Room) Key Door']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Compass Room)', 'Ganon\'s Tower',
|
||||
['Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right',
|
||||
'Ganons Tower - Compass Room - Bottom Left',
|
||||
'Ganons Tower - Compass Room - Bottom Right'], ['Ganons Tower (Bottom) (East)']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Hookshot Room)', 'Ganon\'s Tower',
|
||||
['Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right',
|
||||
'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right'],
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Maze)', 'Palace of Darkness', ['Palace of Darkness - Dark Maze - Top', 'Palace of Darkness - Dark Maze - Bottom', 'Palace of Darkness - Big Chest']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Harmless Hellway)', 'Palace of Darkness', ['Palace of Darkness - Harmless Hellway']),
|
||||
create_dungeon_region(world, player, 'Palace of Darkness (Final Section)', 'Palace of Darkness', ['Palace of Darkness - Boss', 'Palace of Darkness - Prize']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Entrance)', 'Ganon\'s Tower', ['Ganons Tower - Bob\'s Torch', 'Ganons Tower - Hope Room - Left',
|
||||
'Ganons Tower - Hope Room - Right', 'Ganons Tower - Conveyor Cross Pot Key'],
|
||||
['Ganons Tower (Tile Room)', 'Ganons Tower (Hookshot Room)', 'Ganons Tower Big Key Door', 'Ganons Tower Exit']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Tile Room)', 'Ganon\'s Tower', ['Ganons Tower - Tile Room'], ['Ganons Tower (Tile Room) Key Door']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Compass Room)', 'Ganon\'s Tower', ['Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right',
|
||||
'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Compass Room - Bottom Right',
|
||||
'Ganons Tower - Conveyor Star Pits Pot Key'],
|
||||
['Ganons Tower (Bottom) (East)']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Hookshot Room)', 'Ganon\'s Tower', ['Ganons Tower - DMs Room - Top Left', 'Ganons Tower - DMs Room - Top Right',
|
||||
'Ganons Tower - DMs Room - Bottom Left', 'Ganons Tower - DMs Room - Bottom Right',
|
||||
'Ganons Tower - Double Switch Pot Key'],
|
||||
['Ganons Tower (Map Room)', 'Ganons Tower (Double Switch Room)']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Map Room)', 'Ganon\'s Tower', ['Ganons Tower - Map Chest']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Firesnake Room)', 'Ganon\'s Tower',
|
||||
['Ganons Tower - Firesnake Room'], ['Ganons Tower (Firesnake Room)']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Teleport Room)', 'Ganon\'s Tower',
|
||||
['Ganons Tower - Randomizer Room - Top Left',
|
||||
'Ganons Tower - Randomizer Room - Top Right',
|
||||
'Ganons Tower - Randomizer Room - Bottom Left',
|
||||
'Ganons Tower - Randomizer Room - Bottom Right'], ['Ganons Tower (Bottom) (West)']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Bottom)', 'Ganon\'s Tower',
|
||||
['Ganons Tower - Bob\'s Chest', 'Ganons Tower - Big Chest',
|
||||
'Ganons Tower - Big Key Room - Left',
|
||||
'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Top)', 'Ganon\'s Tower', None,
|
||||
['Ganons Tower Torch Rooms']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Before Moldorm)', 'Ganon\'s Tower',
|
||||
['Ganons Tower - Mini Helmasaur Room - Left',
|
||||
'Ganons Tower - Mini Helmasaur Room - Right',
|
||||
'Ganons Tower - Pre-Moldorm Chest'], ['Ganons Tower Moldorm Door']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Moldorm)', 'Ganon\'s Tower', None,
|
||||
['Ganons Tower Moldorm Gap']),
|
||||
create_dungeon_region(world, player, 'Agahnim 2', 'Ganon\'s Tower',
|
||||
['Ganons Tower - Validation Chest', 'Agahnim 2'], None),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Firesnake Room)', 'Ganon\'s Tower', ['Ganons Tower - Firesnake Room'], ['Ganons Tower (Firesnake Room)']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Teleport Room)', 'Ganon\'s Tower', ['Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right',
|
||||
'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right'],
|
||||
['Ganons Tower (Bottom) (West)']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Bottom)', 'Ganon\'s Tower', ['Ganons Tower - Bob\'s Chest', 'Ganons Tower - Big Chest', 'Ganons Tower - Big Key Room - Left',
|
||||
'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Big Key Chest']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Top)', 'Ganon\'s Tower', None, ['Ganons Tower Torch Rooms']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Before Moldorm)', 'Ganon\'s Tower', ['Ganons Tower - Mini Helmasaur Room - Left', 'Ganons Tower - Mini Helmasaur Room - Right',
|
||||
'Ganons Tower - Pre-Moldorm Chest', 'Ganons Tower - Mini Helmasaur Key Drop'], ['Ganons Tower Moldorm Door']),
|
||||
create_dungeon_region(world, player, 'Ganons Tower (Moldorm)', 'Ganon\'s Tower', None, ['Ganons Tower Moldorm Gap']),
|
||||
create_dungeon_region(world, player, 'Agahnim 2', 'Ganon\'s Tower', ['Ganons Tower - Validation Chest', 'Agahnim 2'], None),
|
||||
create_cave_region(world, player, 'Pyramid', 'a drop\'s exit', ['Ganon'], ['Ganon Drop']),
|
||||
create_cave_region(world, player, 'Bottom of Pyramid', 'a drop\'s exit', None, ['Pyramid Exit']),
|
||||
create_dw_region(world, player, 'Pyramid Ledge', None, ['Pyramid Entrance', 'Pyramid Drop']),
|
||||
@@ -533,8 +410,12 @@ def _create_region(world: MultiWorld, player: int, name: str, type: LTTPRegionTy
|
||||
ret.exits.append(Entrance(player, exit, ret))
|
||||
if locations:
|
||||
for location in locations:
|
||||
address, player_address, crystal, hint_text = location_table[location]
|
||||
ret.locations.append(ALttPLocation(player, location, address, crystal, hint_text, ret, player_address))
|
||||
if location in key_drop_data:
|
||||
ko_hint = key_drop_data[location][2]
|
||||
ret.locations.append(ALttPLocation(player, location, key_drop_data[location][1], False, ko_hint, ret, key_drop_data[location][0]))
|
||||
else:
|
||||
address, player_address, crystal, hint_text = location_table[location]
|
||||
ret.locations.append(ALttPLocation(player, location, address, crystal, hint_text, ret, player_address))
|
||||
return ret
|
||||
|
||||
|
||||
@@ -587,39 +468,39 @@ old_location_address_to_new_location_address = {
|
||||
|
||||
|
||||
key_drop_data = {
|
||||
'Hyrule Castle - Map Guard Key Drop': [0x140036, 0x140037],
|
||||
'Hyrule Castle - Boomerang Guard Key Drop': [0x140033, 0x140034],
|
||||
'Hyrule Castle - Key Rat Key Drop': [0x14000c, 0x14000d],
|
||||
'Hyrule Castle - Big Key Drop': [0x14003c, 0x14003d],
|
||||
'Eastern Palace - Dark Square Pot Key': [0x14005a, 0x14005b],
|
||||
'Eastern Palace - Dark Eyegore Key Drop': [0x140048, 0x140049],
|
||||
'Desert Palace - Desert Tiles 1 Pot Key': [0x140030, 0x140031],
|
||||
'Desert Palace - Beamos Hall Pot Key': [0x14002a, 0x14002b],
|
||||
'Desert Palace - Desert Tiles 2 Pot Key': [0x140027, 0x140028],
|
||||
'Castle Tower - Dark Archer Key Drop': [0x140060, 0x140061],
|
||||
'Castle Tower - Circle of Pots Key Drop': [0x140051, 0x140052],
|
||||
'Swamp Palace - Pot Row Pot Key': [0x140018, 0x140019],
|
||||
'Swamp Palace - Trench 1 Pot Key': [0x140015, 0x140016],
|
||||
'Swamp Palace - Hookshot Pot Key': [0x140012, 0x140013],
|
||||
'Swamp Palace - Trench 2 Pot Key': [0x14000f, 0x140010],
|
||||
'Swamp Palace - Waterway Pot Key': [0x140009, 0x14000a],
|
||||
'Skull Woods - West Lobby Pot Key': [0x14002d, 0x14002e],
|
||||
'Skull Woods - Spike Corner Key Drop': [0x14001b, 0x14001c],
|
||||
'Thieves\' Town - Hallway Pot Key': [0x14005d, 0x14005e],
|
||||
'Thieves\' Town - Spike Switch Pot Key': [0x14004e, 0x14004f],
|
||||
'Ice Palace - Jelly Key Drop': [0x140003, 0x140004],
|
||||
'Ice Palace - Conveyor Key Drop': [0x140021, 0x140022],
|
||||
'Ice Palace - Hammer Block Key Drop': [0x140024, 0x140025],
|
||||
'Ice Palace - Many Pots Pot Key': [0x140045, 0x140046],
|
||||
'Misery Mire - Spikes Pot Key': [0x140054, 0x140055],
|
||||
'Misery Mire - Fishbone Pot Key': [0x14004b, 0x14004c],
|
||||
'Misery Mire - Conveyor Crystal Key Drop': [0x140063, 0x140064],
|
||||
'Turtle Rock - Pokey 1 Key Drop': [0x140057, 0x140058],
|
||||
'Turtle Rock - Pokey 2 Key Drop': [0x140006, 0x140007],
|
||||
'Ganons Tower - Conveyor Cross Pot Key': [0x14003f, 0x140040],
|
||||
'Ganons Tower - Double Switch Pot Key': [0x140042, 0x140043],
|
||||
'Ganons Tower - Conveyor Star Pits Pot Key': [0x140039, 0x14003a],
|
||||
'Ganons Tower - Mini Helmasaur Key Drop': [0x14001e, 0x14001f]
|
||||
'Hyrule Castle - Map Guard Key Drop': [0x140036, 0x140037, 'in Hyrule Castle', 'Small Key (Hyrule Castle)'],
|
||||
'Hyrule Castle - Boomerang Guard Key Drop': [0x140033, 0x140034, 'in Hyrule Castle', 'Small Key (Hyrule Castle)'],
|
||||
'Sewers - Key Rat Key Drop': [0x14000c, 0x14000d, 'in the sewers', 'Small Key (Hyrule Castle)'],
|
||||
'Hyrule Castle - Big Key Drop': [0x14003c, 0x14003d, 'in Hyrule Castle', 'Big Key (Hyrule Castle)'],
|
||||
'Eastern Palace - Dark Square Pot Key': [0x14005a, 0x14005b, 'in Eastern Palace', 'Small Key (Eastern Palace)'],
|
||||
'Eastern Palace - Dark Eyegore Key Drop': [0x140048, 0x140049, 'in Eastern Palace', 'Small Key (Eastern Palace)'],
|
||||
'Desert Palace - Desert Tiles 1 Pot Key': [0x140030, 0x140031, 'in Desert Palace', 'Small Key (Desert Palace)'],
|
||||
'Desert Palace - Beamos Hall Pot Key': [0x14002a, 0x14002b, 'in Desert Palace', 'Small Key (Desert Palace)'],
|
||||
'Desert Palace - Desert Tiles 2 Pot Key': [0x140027, 0x140028, 'in Desert Palace', 'Small Key (Desert Palace)'],
|
||||
'Castle Tower - Dark Archer Key Drop': [0x140060, 0x140061, 'in Castle Tower', 'Small Key (Agahnims Tower)'],
|
||||
'Castle Tower - Circle of Pots Key Drop': [0x140051, 0x140052, 'in Castle Tower', 'Small Key (Agahnims Tower)'],
|
||||
'Swamp Palace - Pot Row Pot Key': [0x140018, 0x140019, 'in Swamp Palace', 'Small Key (Swamp Palace)'],
|
||||
'Swamp Palace - Trench 1 Pot Key': [0x140015, 0x140016, 'in Swamp Palace', 'Small Key (Swamp Palace)'],
|
||||
'Swamp Palace - Hookshot Pot Key': [0x140012, 0x140013, 'in Swamp Palace', 'Small Key (Swamp Palace)'],
|
||||
'Swamp Palace - Trench 2 Pot Key': [0x14000f, 0x140010, 'in Swamp Palace', 'Small Key (Swamp Palace)'],
|
||||
'Swamp Palace - Waterway Pot Key': [0x140009, 0x14000a, 'in Swamp Palace', 'Small Key (Swamp Palace)'],
|
||||
'Skull Woods - West Lobby Pot Key': [0x14002d, 0x14002e, 'in Skull Woods', 'Small Key (Skull Woods)'],
|
||||
'Skull Woods - Spike Corner Key Drop': [0x14001b, 0x14001c, 'near Mothula', 'Small Key (Skull Woods)'],
|
||||
"Thieves' Town - Hallway Pot Key": [0x14005d, 0x14005e, "in Thieves' Town", 'Small Key (Thieves Town)'],
|
||||
"Thieves' Town - Spike Switch Pot Key": [0x14004e, 0x14004f, "in Thieves' Town", 'Small Key (Thieves Town)'],
|
||||
'Ice Palace - Jelly Key Drop': [0x140003, 0x140004, 'in Ice Palace', 'Small Key (Ice Palace)'],
|
||||
'Ice Palace - Conveyor Key Drop': [0x140021, 0x140022, 'in Ice Palace', 'Small Key (Ice Palace)'],
|
||||
'Ice Palace - Hammer Block Key Drop': [0x140024, 0x140025, 'in Ice Palace', 'Small Key (Ice Palace)'],
|
||||
'Ice Palace - Many Pots Pot Key': [0x140045, 0x140046, 'in Ice Palace', 'Small Key (Ice Palace)'],
|
||||
'Misery Mire - Spikes Pot Key': [0x140054, 0x140055 , 'in Misery Mire', 'Small Key (Misery Mire)'],
|
||||
'Misery Mire - Fishbone Pot Key': [0x14004b, 0x14004c, 'in forgotten Mire', 'Small Key (Misery Mire)'],
|
||||
'Misery Mire - Conveyor Crystal Key Drop': [0x140063, 0x140064 , 'in Misery Mire', 'Small Key (Misery Mire)'],
|
||||
'Turtle Rock - Pokey 1 Key Drop': [0x140057, 0x140058, 'in Turtle Rock', 'Small Key (Turtle Rock)'],
|
||||
'Turtle Rock - Pokey 2 Key Drop': [0x140006, 0x140007, 'in Turtle Rock', 'Small Key (Turtle Rock)'],
|
||||
'Ganons Tower - Conveyor Cross Pot Key': [0x14003f, 0x140040, "in Ganon's Tower", 'Small Key (Ganons Tower)'],
|
||||
'Ganons Tower - Double Switch Pot Key': [0x140042, 0x140043, "in Ganon's Tower", 'Small Key (Ganons Tower)'],
|
||||
'Ganons Tower - Conveyor Star Pits Pot Key': [0x140039, 0x14003a, "in Ganon's Tower", 'Small Key (Ganons Tower)'],
|
||||
'Ganons Tower - Mini Helmasaur Key Drop': [0x14001e, 0x14001f, "atop Ganon's Tower", 'Small Key (Ganons Tower)']
|
||||
}
|
||||
|
||||
# tuple contents:
|
||||
|
||||
@@ -25,7 +25,7 @@ from Utils import local_path, user_path, int16_as_bytes, int32_as_bytes, snes_to
|
||||
|
||||
from .Shops import ShopType, ShopPriceType
|
||||
from .Dungeons import dungeon_music_addresses
|
||||
from .Regions import old_location_address_to_new_location_address
|
||||
from .Regions import old_location_address_to_new_location_address, key_drop_data
|
||||
from .Text import MultiByteTextMapper, text_addresses, Credits, TextTable
|
||||
from .Text import Uncle_texts, Ganon1_texts, TavernMan_texts, Sahasrahla2_texts, Triforce_texts, \
|
||||
Blind_texts, \
|
||||
@@ -428,6 +428,18 @@ def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory):
|
||||
rom.write_byte(0x04DE81, 6)
|
||||
rom.write_byte(0x1B0101, 0) # Do not close boss room door on entry.
|
||||
|
||||
# Moblins attached to "key drop" locations crash the game when dropping their item when Key Drop Shuffle is on.
|
||||
# Replace them with a Slime enemy if they are placed.
|
||||
if multiworld.key_drop_shuffle[player]:
|
||||
key_drop_enemies = {
|
||||
0x4DA20, 0x4DA5C, 0x4DB7F, 0x4DD73, 0x4DDC3, 0x4DE07, 0x4E201,
|
||||
0x4E20A, 0x4E326, 0x4E4F7, 0x4E686, 0x4E70C, 0x4E7C8, 0x4E7FA
|
||||
}
|
||||
for enemy in key_drop_enemies:
|
||||
if rom.read_byte(enemy) == 0x12:
|
||||
logging.debug(f"Moblin found and replaced at {enemy} in world {player}")
|
||||
rom.write_byte(enemy, 0x8F)
|
||||
|
||||
for used in (randopatch_path, options_path):
|
||||
try:
|
||||
os.remove(used)
|
||||
@@ -897,6 +909,29 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
|
||||
credits_total += 30 if 'w' in world.shop_shuffle[player] else 27
|
||||
|
||||
rom.write_byte(0x187010, credits_total) # dynamic credits
|
||||
|
||||
if world.key_drop_shuffle[player]:
|
||||
rom.write_byte(0x140000, 1) # enable key drop shuffle
|
||||
credits_total += len(key_drop_data)
|
||||
# update dungeon counters
|
||||
rom.write_byte(0x187001, 12) # Hyrule Castle
|
||||
rom.write_byte(0x187002, 8) # Eastern Palace
|
||||
rom.write_byte(0x187003, 9) # Desert Palace
|
||||
rom.write_byte(0x187004, 4) # Agahnims Tower
|
||||
rom.write_byte(0x187005, 15) # Swamp Palace
|
||||
rom.write_byte(0x187007, 11) # Misery Mire
|
||||
rom.write_byte(0x187008, 10) # Skull Woods
|
||||
rom.write_byte(0x187009, 12) # Ice Palace
|
||||
rom.write_byte(0x18700B, 10) # Thieves Town
|
||||
rom.write_byte(0x18700C, 14) # Turtle Rock
|
||||
rom.write_byte(0x18700D, 31) # Ganons Tower
|
||||
# update credits GT Big Key counter
|
||||
gt_bigkey_top, gt_bigkey_bottom = credits_digit(5)
|
||||
rom.write_byte(0x118B6A, gt_bigkey_top)
|
||||
rom.write_byte(0x118B88, gt_bigkey_bottom)
|
||||
|
||||
|
||||
|
||||
# collection rate address: 238C37
|
||||
first_top, first_bot = credits_digit((credits_total / 100) % 10)
|
||||
mid_top, mid_bot = credits_digit((credits_total / 10) % 10)
|
||||
@@ -1824,10 +1859,10 @@ def apply_oof_sfx(rom, oof: str):
|
||||
# (We need to insert the second sigil at the end)
|
||||
rom.write_bytes(0x12803A, oof_bytes)
|
||||
rom.write_bytes(0x12803A + len(oof_bytes), [0xEB, 0xEB])
|
||||
|
||||
|
||||
#Enemizer patch: prevent Enemizer from overwriting $3188 in SPC memory with an unused sound effect ("WHAT")
|
||||
rom.write_bytes(0x13000D, [0x00, 0x00, 0x00, 0x08])
|
||||
|
||||
|
||||
|
||||
def apply_rom_settings(rom, beep, color, quickswap, menuspeed, music: bool, sprite: str, oof: str, palettes_options,
|
||||
world=None, player=1, allow_random_on_event=False, reduceflashing=False,
|
||||
|
||||
@@ -231,26 +231,41 @@ def global_rules(world, player):
|
||||
set_rule(world.get_location('Hookshot Cave - Bottom Left', player), lambda state: state.has('Hookshot', player))
|
||||
|
||||
set_rule(world.get_entrance('Sewers Door', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player) or (
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) or (
|
||||
world.smallkey_shuffle[player] == smallkey_shuffle.option_universal and world.mode[
|
||||
player] == 'standard')) # standard universal small keys cannot access the shop
|
||||
set_rule(world.get_entrance('Sewers Back Door', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player))
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4))
|
||||
set_rule(world.get_entrance('Agahnim 1', player),
|
||||
lambda state: has_sword(state, player) and state._lttp_has_key('Small Key (Agahnims Tower)', player, 2))
|
||||
lambda state: has_sword(state, player) and state._lttp_has_key('Small Key (Agahnims Tower)', player, 4))
|
||||
|
||||
set_rule(world.get_location('Castle Tower - Room 03', player), lambda state: can_kill_most_things(state, player, 8))
|
||||
set_rule(world.get_location('Castle Tower - Dark Maze', player),
|
||||
lambda state: can_kill_most_things(state, player, 8) and state._lttp_has_key('Small Key (Agahnims Tower)',
|
||||
player))
|
||||
|
||||
set_rule(world.get_location('Castle Tower - Dark Archer Key Drop', player),
|
||||
lambda state: can_kill_most_things(state, player, 8) and state._lttp_has_key('Small Key (Agahnims Tower)',
|
||||
player, 2))
|
||||
set_rule(world.get_location('Castle Tower - Circle of Pots Key Drop', player),
|
||||
lambda state: can_kill_most_things(state, player, 8) and state._lttp_has_key('Small Key (Agahnims Tower)',
|
||||
player, 3))
|
||||
set_always_allow(world.get_location('Eastern Palace - Big Key Chest', player),
|
||||
lambda state, item: item.name == 'Big Key (Eastern Palace)' and item.player == player)
|
||||
set_rule(world.get_location('Eastern Palace - Big Key Chest', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Eastern Palace)', player, 2) or
|
||||
((location_item_name(state, 'Eastern Palace - Big Key Chest', player) == ('Big Key (Eastern Palace)', player)
|
||||
and state.has('Small Key (Eastern Palace)', player))))
|
||||
set_rule(world.get_location('Eastern Palace - Dark Eyegore Key Drop', player),
|
||||
lambda state: state.has('Big Key (Eastern Palace)', player))
|
||||
set_rule(world.get_location('Eastern Palace - Big Chest', player),
|
||||
lambda state: state.has('Big Key (Eastern Palace)', player))
|
||||
ep_boss = world.get_location('Eastern Palace - Boss', player)
|
||||
set_rule(ep_boss, lambda state: state.has('Big Key (Eastern Palace)', player) and
|
||||
state._lttp_has_key('Small Key (Eastern Palace)', player, 2) and
|
||||
ep_boss.parent_region.dungeon.boss.can_defeat(state))
|
||||
ep_prize = world.get_location('Eastern Palace - Prize', player)
|
||||
set_rule(ep_prize, lambda state: state.has('Big Key (Eastern Palace)', player) and
|
||||
state._lttp_has_key('Small Key (Eastern Palace)', player, 2) and
|
||||
ep_prize.parent_region.dungeon.boss.can_defeat(state))
|
||||
if not world.enemy_shuffle[player]:
|
||||
add_rule(ep_boss, lambda state: can_shoot_arrows(state, player))
|
||||
@@ -258,9 +273,13 @@ def global_rules(world, player):
|
||||
|
||||
set_rule(world.get_location('Desert Palace - Big Chest', player), lambda state: state.has('Big Key (Desert Palace)', player))
|
||||
set_rule(world.get_location('Desert Palace - Torch', player), lambda state: state.has('Pegasus Boots', player))
|
||||
set_rule(world.get_entrance('Desert Palace East Wing', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player))
|
||||
set_rule(world.get_location('Desert Palace - Prize', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player) and state.has('Big Key (Desert Palace)', player) and has_fire_source(state, player) and state.multiworld.get_location('Desert Palace - Prize', player).parent_region.dungeon.boss.can_defeat(state))
|
||||
set_rule(world.get_location('Desert Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player) and state.has('Big Key (Desert Palace)', player) and has_fire_source(state, player) and state.multiworld.get_location('Desert Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state))
|
||||
|
||||
set_rule(world.get_entrance('Desert Palace East Wing', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4))
|
||||
set_rule(world.get_location('Desert Palace - Big Key Chest', player), lambda state: can_kill_most_things(state, player))
|
||||
set_rule(world.get_location('Desert Palace - Beamos Hall Pot Key', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 2) and can_kill_most_things(state, player))
|
||||
set_rule(world.get_location('Desert Palace - Desert Tiles 2 Pot Key', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 3) and can_kill_most_things(state, player))
|
||||
set_rule(world.get_location('Desert Palace - Prize', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4) and state.has('Big Key (Desert Palace)', player) and has_fire_source(state, player) and state.multiworld.get_location('Desert Palace - Prize', player).parent_region.dungeon.boss.can_defeat(state))
|
||||
set_rule(world.get_location('Desert Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Desert Palace)', player, 4) and state.has('Big Key (Desert Palace)', player) and has_fire_source(state, player) and state.multiworld.get_location('Desert Palace - Boss', player).parent_region.dungeon.boss.can_defeat(state))
|
||||
|
||||
# logic patch to prevent placing a crystal in Desert that's required to reach the required keys
|
||||
if not (world.smallkey_shuffle[player] and world.bigkey_shuffle[player]):
|
||||
@@ -275,57 +294,98 @@ def global_rules(world, player):
|
||||
|
||||
set_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Flippers', player) and state.has('Open Floodgate', player))
|
||||
set_rule(world.get_entrance('Swamp Palace Small Key Door', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player))
|
||||
set_rule(world.get_entrance('Swamp Palace (Center)', player), lambda state: state.has('Hammer', player))
|
||||
set_rule(world.get_location('Swamp Palace - Trench 1 Pot Key', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 2))
|
||||
set_rule(world.get_entrance('Swamp Palace (Center)', player), lambda state: state.has('Hammer', player) and state._lttp_has_key('Small Key (Swamp Palace)', player, 3))
|
||||
set_rule(world.get_location('Swamp Palace - Hookshot Pot Key', player), lambda state: state.has('Hookshot', player))
|
||||
set_rule(world.get_entrance('Swamp Palace (West)', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6)
|
||||
if state.has('Hookshot', player)
|
||||
else state._lttp_has_key('Small Key (Swamp Palace)', player, 4))
|
||||
set_rule(world.get_location('Swamp Palace - Big Chest', player), lambda state: state.has('Big Key (Swamp Palace)', player))
|
||||
if world.accessibility[player] != 'locations':
|
||||
allow_self_locking_items(world.get_location('Swamp Palace - Big Chest', player), 'Big Key (Swamp Palace)')
|
||||
set_rule(world.get_entrance('Swamp Palace (North)', player), lambda state: state.has('Hookshot', player))
|
||||
set_rule(world.get_entrance('Swamp Palace (North)', player), lambda state: state.has('Hookshot', player) and state._lttp_has_key('Small Key (Swamp Palace)', player, 5))
|
||||
if not world.smallkey_shuffle[player] and world.logic[player] not in ['hybridglitches', 'nologic']:
|
||||
forbid_item(world.get_location('Swamp Palace - Entrance', player), 'Big Key (Swamp Palace)', player)
|
||||
set_rule(world.get_location('Swamp Palace - Prize', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6))
|
||||
set_rule(world.get_location('Swamp Palace - Boss', player), lambda state: state._lttp_has_key('Small Key (Swamp Palace)', player, 6))
|
||||
|
||||
set_rule(world.get_entrance('Thieves Town Big Key Door', player), lambda state: state.has('Big Key (Thieves Town)', player))
|
||||
set_rule(world.get_entrance('Blind Fight', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player))
|
||||
set_rule(world.get_location('Thieves\' Town - Big Chest', player), lambda state: (state._lttp_has_key('Small Key (Thieves Town)', player)) and state.has('Hammer', player))
|
||||
|
||||
if world.worlds[player].dungeons["Thieves Town"].boss.enemizer_name == "Blind":
|
||||
set_rule(world.get_entrance('Blind Fight', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3))
|
||||
|
||||
set_rule(world.get_location('Thieves\' Town - Big Chest', player),
|
||||
lambda state: (state._lttp_has_key('Small Key (Thieves Town)', player, 3)) and state.has('Hammer', player))
|
||||
if world.accessibility[player] != 'locations':
|
||||
allow_self_locking_items(world.get_location('Thieves\' Town - Big Chest', player), 'Small Key (Thieves Town)')
|
||||
set_rule(world.get_location('Thieves\' Town - Attic', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player))
|
||||
|
||||
set_rule(world.get_entrance('Skull Woods First Section South Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player))
|
||||
set_rule(world.get_entrance('Skull Woods First Section (Right) North Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player))
|
||||
set_rule(world.get_entrance('Skull Woods First Section West Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 2)) # ideally would only be one key, but we may have spent thst key already on escaping the right section
|
||||
set_rule(world.get_entrance('Skull Woods First Section (Left) Door to Exit', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 2))
|
||||
set_rule(world.get_location('Thieves\' Town - Attic', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3))
|
||||
set_rule(world.get_location('Thieves\' Town - Spike Switch Pot Key', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Thieves Town)', player))
|
||||
|
||||
# We need so many keys in the SW doors because they are all reachable as the last door (except for the door to mothula)
|
||||
set_rule(world.get_entrance('Skull Woods First Section South Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5))
|
||||
set_rule(world.get_entrance('Skull Woods First Section (Right) North Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5))
|
||||
set_rule(world.get_entrance('Skull Woods First Section West Door', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5))
|
||||
set_rule(world.get_entrance('Skull Woods First Section (Left) Door to Exit', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5))
|
||||
set_rule(world.get_location('Skull Woods - Big Chest', player), lambda state: state.has('Big Key (Skull Woods)', player))
|
||||
if world.accessibility[player] != 'locations':
|
||||
allow_self_locking_items(world.get_location('Skull Woods - Big Chest', player), 'Big Key (Skull Woods)')
|
||||
set_rule(world.get_entrance('Skull Woods Torch Room', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 3) and state.has('Fire Rod', player) and has_sword(state, player)) # sword required for curtain
|
||||
set_rule(world.get_entrance('Skull Woods Torch Room', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 4) and state.has('Fire Rod', player) and has_sword(state, player)) # sword required for curtain
|
||||
add_rule(world.get_location('Skull Woods - Prize', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5))
|
||||
add_rule(world.get_location('Skull Woods - Boss', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 5))
|
||||
|
||||
set_rule(world.get_entrance('Ice Palace Entrance Room', player), lambda state: can_melt_things(state, player))
|
||||
set_rule(world.get_location('Ice Palace - Jelly Key Drop', player), lambda state: can_melt_things(state, player))
|
||||
set_rule(world.get_entrance('Ice Palace (Second Section)', player), lambda state: can_melt_things(state, player) and state._lttp_has_key('Small Key (Ice Palace)', player))
|
||||
set_rule(world.get_entrance('Ice Palace (Main)', player), lambda state: state._lttp_has_key('Small Key (Ice Palace)', player, 2))
|
||||
set_rule(world.get_location('Ice Palace - Big Chest', player), lambda state: state.has('Big Key (Ice Palace)', player))
|
||||
set_rule(world.get_entrance('Ice Palace (Kholdstare)', player), lambda state: can_lift_rocks(state, player) and state.has('Hammer', player) and state.has('Big Key (Ice Palace)', player) and (state._lttp_has_key('Small Key (Ice Palace)', player, 2) or (state.has('Cane of Somaria', player) and state._lttp_has_key('Small Key (Ice Palace)', player, 1))))
|
||||
set_rule(world.get_entrance('Ice Palace (East)', player), lambda state: (state.has('Hookshot', player) or (
|
||||
item_name_in_location_names(state, 'Big Key (Ice Palace)', player, [('Ice Palace - Spike Room', player), ('Ice Palace - Big Key Chest', player), ('Ice Palace - Map Chest', player)]) and state._lttp_has_key('Small Key (Ice Palace)', player))) and (state.multiworld.can_take_damage[player] or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player)))
|
||||
set_rule(world.get_entrance('Ice Palace (Kholdstare)', player), lambda state: can_lift_rocks(state, player) and state.has('Hammer', player) and state.has('Big Key (Ice Palace)', player) and (state._lttp_has_key('Small Key (Ice Palace)', player, 6) or (state.has('Cane of Somaria', player) and state._lttp_has_key('Small Key (Ice Palace)', player, 5))))
|
||||
# This is a complicated rule, so let's break it down.
|
||||
# Hookshot always suffices to get to the right side.
|
||||
# Also, once you get over there, you have to cross the spikes, so that's the last line.
|
||||
# Alternatively, we could not have hookshot. Then we open the keydoor into right side in order to get there.
|
||||
# This is conditional on whether we have the big key or not, as big key opens the ability to waste more keys.
|
||||
# Specifically, if we have big key we can burn 2 extra keys near the boss and will need +2 keys. That's all of them as this could be the last door.
|
||||
# Hence if big key is available then it's 6 keys, otherwise 4 keys.
|
||||
# If key_drop is off, then we have 3 drop keys available, and can never satisfy the 6 key requirement because one key is on right side,
|
||||
# so this reduces perfectly to original logic.
|
||||
set_rule(world.get_entrance('Ice Palace (East)', player), lambda state: (state.has('Hookshot', player) or
|
||||
(state._lttp_has_key('Small Key (Ice Palace)', player, 4)
|
||||
if item_name_in_location_names(state, 'Big Key (Ice Palace)', player, [('Ice Palace - Spike Room', player),
|
||||
('Ice Palace - Hammer Block Key Drop', player),
|
||||
('Ice Palace - Big Key Chest', player),
|
||||
('Ice Palace - Map Chest', player)])
|
||||
else state._lttp_has_key('Small Key (Ice Palace)', player, 6))) and
|
||||
(state.multiworld.can_take_damage[player] or state.has('Hookshot', player) or state.has('Cape', player) or state.has('Cane of Byrna', player)))
|
||||
set_rule(world.get_entrance('Ice Palace (East Top)', player), lambda state: can_lift_rocks(state, player) and state.has('Hammer', player))
|
||||
|
||||
set_rule(world.get_entrance('Misery Mire Entrance Gap', player), lambda state: (state.has('Pegasus Boots', player) or state.has('Hookshot', player)) and (has_sword(state, player) or state.has('Fire Rod', player) or state.has('Ice Rod', player) or state.has('Hammer', player) or state.has('Cane of Somaria', player) or can_shoot_arrows(state, player))) # need to defeat wizzrobes, bombs don't work ...
|
||||
set_rule(world.get_location('Misery Mire - Fishbone Pot Key', player), lambda state: state.has('Big Key (Misery Mire)', player) or state._lttp_has_key('Small Key (Misery Mire)', player, 4))
|
||||
|
||||
set_rule(world.get_location('Misery Mire - Big Chest', player), lambda state: state.has('Big Key (Misery Mire)', player))
|
||||
set_rule(world.get_location('Misery Mire - Spike Chest', player), lambda state: (state.multiworld.can_take_damage[player] and has_hearts(state, player, 4)) or state.has('Cane of Byrna', player) or state.has('Cape', player))
|
||||
set_rule(world.get_entrance('Misery Mire Big Key Door', player), lambda state: state.has('Big Key (Misery Mire)', player))
|
||||
# you can squander the free small key from the pot by opening the south door to the north west switch room, locking you out of accessing a color switch ...
|
||||
# big key gives backdoor access to that from the teleporter in the north west
|
||||
set_rule(world.get_location('Misery Mire - Map Chest', player), lambda state: state._lttp_has_key('Small Key (Misery Mire)', player, 1) or state.has('Big Key (Misery Mire)', player))
|
||||
set_rule(world.get_location('Misery Mire - Main Lobby', player), lambda state: state._lttp_has_key('Small Key (Misery Mire)', player, 1) or state._lttp_has_key('Big Key (Misery Mire)', player))
|
||||
# How to access crystal switch:
|
||||
# If have big key: then you will need 2 small keys to be able to hit switch and return to main area, as you can burn key in dark room
|
||||
# If not big key: cannot burn key in dark room, hence need only 1 key. all doors immediately available lead to a crystal switch.
|
||||
# The listed chests are those which can be reached if you can reach a crystal switch.
|
||||
set_rule(world.get_location('Misery Mire - Map Chest', player), lambda state: state._lttp_has_key('Small Key (Misery Mire)', player, 2))
|
||||
set_rule(world.get_location('Misery Mire - Main Lobby', player), lambda state: state._lttp_has_key('Small Key (Misery Mire)', player, 2))
|
||||
# we can place a small key in the West wing iff it also contains/blocks the Big Key, as we cannot reach and softlock with the basement key door yet
|
||||
set_rule(world.get_entrance('Misery Mire (West)', player), lambda state: state._lttp_has_key('Small Key (Misery Mire)', player, 2) if ((
|
||||
location_item_name(state, 'Misery Mire - Compass Chest', player) in [('Big Key (Misery Mire)', player)]) or
|
||||
(
|
||||
location_item_name(state, 'Misery Mire - Big Key Chest', player) in [('Big Key (Misery Mire)', player)])) else state._lttp_has_key('Small Key (Misery Mire)', player, 3))
|
||||
set_rule(world.get_location('Misery Mire - Conveyor Crystal Key Drop', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Misery Mire)', player, 4)
|
||||
if location_item_name(state, 'Misery Mire - Compass Chest', player) == ('Big Key (Misery Mire)', player) or location_item_name(state, 'Misery Mire - Big Key Chest', player) == ('Big Key (Misery Mire)', player) or location_item_name(state, 'Misery Mire - Conveyor Crystal Key Drop', player) == ('Big Key (Misery Mire)', player)
|
||||
else state._lttp_has_key('Small Key (Misery Mire)', player, 5))
|
||||
set_rule(world.get_entrance('Misery Mire (West)', player), lambda state: state._lttp_has_key('Small Key (Misery Mire)', player, 5)
|
||||
if ((location_item_name(state, 'Misery Mire - Compass Chest', player) in [('Big Key (Misery Mire)', player)]) or (location_item_name(state, 'Misery Mire - Big Key Chest', player) in [('Big Key (Misery Mire)', player)]))
|
||||
else state._lttp_has_key('Small Key (Misery Mire)', player, 6))
|
||||
set_rule(world.get_location('Misery Mire - Compass Chest', player), lambda state: has_fire_source(state, player))
|
||||
set_rule(world.get_location('Misery Mire - Big Key Chest', player), lambda state: has_fire_source(state, player))
|
||||
set_rule(world.get_entrance('Misery Mire (Vitreous)', player), lambda state: state.has('Cane of Somaria', player))
|
||||
|
||||
set_rule(world.get_entrance('Turtle Rock Entrance Gap', player), lambda state: state.has('Cane of Somaria', player))
|
||||
set_rule(world.get_entrance('Turtle Rock Entrance Gap Reverse', player), lambda state: state.has('Cane of Somaria', player))
|
||||
set_rule(world.get_location('Turtle Rock - Compass Chest', player), lambda state: state.has('Cane of Somaria', player)) # We could get here from the middle section without Cane as we don't cross the entrance gap!
|
||||
set_rule(world.get_location('Turtle Rock - Compass Chest', player), lambda state: state.has('Cane of Somaria', player))
|
||||
set_rule(world.get_location('Turtle Rock - Roller Room - Left', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player))
|
||||
set_rule(world.get_location('Turtle Rock - Roller Room - Right', player), lambda state: state.has('Cane of Somaria', player) and state.has('Fire Rod', player))
|
||||
set_rule(world.get_location('Turtle Rock - Big Chest', player), lambda state: state.has('Big Key (Turtle Rock)', player) and (state.has('Cane of Somaria', player) or state.has('Hookshot', player)))
|
||||
@@ -337,7 +397,7 @@ def global_rules(world, player):
|
||||
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
|
||||
set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
|
||||
set_rule(world.get_location('Turtle Rock - Eye Bridge - Top Right', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
|
||||
set_rule(world.get_entrance('Turtle Rock (Trinexx)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 4) and state.has('Big Key (Turtle Rock)', player) and state.has('Cane of Somaria', player))
|
||||
set_rule(world.get_entrance('Turtle Rock (Trinexx)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 6) and state.has('Big Key (Turtle Rock)', player) and state.has('Cane of Somaria', player))
|
||||
|
||||
if not world.enemy_shuffle[player]:
|
||||
set_rule(world.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: can_shoot_arrows(state, player))
|
||||
@@ -361,35 +421,46 @@ def global_rules(world, player):
|
||||
|
||||
# these key rules are conservative, you might be able to get away with more lenient rules
|
||||
randomizer_room_chests = ['Ganons Tower - Randomizer Room - Top Left', 'Ganons Tower - Randomizer Room - Top Right', 'Ganons Tower - Randomizer Room - Bottom Left', 'Ganons Tower - Randomizer Room - Bottom Right']
|
||||
compass_room_chests = ['Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right', 'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Compass Room - Bottom Right']
|
||||
compass_room_chests = ['Ganons Tower - Compass Room - Top Left', 'Ganons Tower - Compass Room - Top Right', 'Ganons Tower - Compass Room - Bottom Left', 'Ganons Tower - Compass Room - Bottom Right', 'Ganons Tower - Conveyor Star Pits Pot Key']
|
||||
back_chests = ['Ganons Tower - Bob\'s Chest', 'Ganons Tower - Big Chest', 'Ganons Tower - Big Key Room - Left', 'Ganons Tower - Big Key Room - Right', 'Ganons Tower - Big Key Chest']
|
||||
|
||||
|
||||
set_rule(world.get_location('Ganons Tower - Bob\'s Torch', player), lambda state: state.has('Pegasus Boots', player))
|
||||
set_rule(world.get_entrance('Ganons Tower (Tile Room)', player), lambda state: state.has('Cane of Somaria', player))
|
||||
set_rule(world.get_entrance('Ganons Tower (Hookshot Room)', player), lambda state: state.has('Hammer', player) and (state.has('Hookshot', player) or state.has('Pegasus Boots', player)))
|
||||
set_rule(world.get_entrance('Ganons Tower (Map Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 4) or (
|
||||
location_item_name(state, 'Ganons Tower - Map Chest', player) in [('Big Key (Ganons Tower)', player), ('Small Key (Ganons Tower)', player)] and state._lttp_has_key('Small Key (Ganons Tower)', player, 3)))
|
||||
if world.accessibility[player] != 'locations':
|
||||
set_always_allow(world.get_location('Ganons Tower - Map Chest', player), lambda state, item: item.name == 'Small Key (Ganons Tower)' and item.player == player and state._lttp_has_key('Small Key (Ganons Tower)', player, 3) and state.can_reach('Ganons Tower (Hookshot Room)', 'region', player))
|
||||
if world.pot_shuffle[player]:
|
||||
# Pot Shuffle can move this check into the hookshot room
|
||||
set_rule(world.get_location('Ganons Tower - Conveyor Cross Pot Key', player), lambda state: state.has('Hammer', player) and (state.has('Hookshot', player) or state.has('Pegasus Boots', player)))
|
||||
set_rule(world.get_entrance('Ganons Tower (Map Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 8) or (
|
||||
location_item_name(state, 'Ganons Tower - Map Chest', player) in [('Big Key (Ganons Tower)', player)] and state._lttp_has_key('Small Key (Ganons Tower)', player, 6)))
|
||||
|
||||
# It is possible to need more than 2 keys to get through this entrance if you spend keys elsewhere. We reflect this in the chest requirements.
|
||||
# However we need to leave these at the lower values to derive that with 3 keys it is always possible to reach Bob and Ice Armos.
|
||||
set_rule(world.get_entrance('Ganons Tower (Double Switch Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 2))
|
||||
# It is possible to need more than 3 keys ....
|
||||
set_rule(world.get_entrance('Ganons Tower (Firesnake Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 3))
|
||||
# this seemed to be causing generation failure, disable for now
|
||||
# if world.accessibility[player] != 'locations':
|
||||
# set_always_allow(world.get_location('Ganons Tower - Map Chest', player), lambda state, item: item.name == 'Small Key (Ganons Tower)' and item.player == player and state._lttp_has_key('Small Key (Ganons Tower)', player, 7) and state.can_reach('Ganons Tower (Hookshot Room)', 'region', player))
|
||||
|
||||
#The actual requirements for these rooms to avoid key-lock
|
||||
set_rule(world.get_location('Ganons Tower - Firesnake Room', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 3) or ((
|
||||
item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests, [player] * len(randomizer_room_chests))) or item_name_in_location_names(state, 'Small Key (Ganons Tower)', player, [('Ganons Tower - Firesnake Room', player)])) and state._lttp_has_key('Small Key (Ganons Tower)', player, 2)))
|
||||
# It is possible to need more than 6 keys to get through this entrance if you spend keys elsewhere. We reflect this in the chest requirements.
|
||||
# However we need to leave these at the lower values to derive that with 7 keys it is always possible to reach Bob and Ice Armos.
|
||||
set_rule(world.get_entrance('Ganons Tower (Double Switch Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 6))
|
||||
# It is possible to need more than 7 keys ....
|
||||
set_rule(world.get_entrance('Ganons Tower (Firesnake Room)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or (
|
||||
item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests + back_chests, [player] * len(randomizer_room_chests + back_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 5)))
|
||||
|
||||
# The actual requirements for these rooms to avoid key-lock
|
||||
set_rule(world.get_location('Ganons Tower - Firesnake Room', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or
|
||||
((item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests, [player] * len(randomizer_room_chests))) or item_name_in_location_names(state, 'Small Key (Ganons Tower)', player, [('Ganons Tower - Firesnake Room', player)])) and state._lttp_has_key('Small Key (Ganons Tower)', player, 5)))
|
||||
for location in randomizer_room_chests:
|
||||
set_rule(world.get_location(location, player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 4) or (
|
||||
item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests, [player] * len(randomizer_room_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 3)))
|
||||
set_rule(world.get_location(location, player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 8) or (
|
||||
item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(randomizer_room_chests, [player] * len(randomizer_room_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 6)))
|
||||
|
||||
# Once again it is possible to need more than 3 keys...
|
||||
set_rule(world.get_entrance('Ganons Tower (Tile Room) Key Door', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 3) and state.has('Fire Rod', player))
|
||||
# Once again it is possible to need more than 7 keys...
|
||||
set_rule(world.get_entrance('Ganons Tower (Tile Room) Key Door', player), lambda state: state.has('Fire Rod', player) and (state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or (
|
||||
item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(compass_room_chests, [player] * len(compass_room_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 5))))
|
||||
set_rule(world.get_entrance('Ganons Tower (Bottom) (East)', player), lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or (
|
||||
item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(back_chests, [player] * len(back_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 5)))
|
||||
# Actual requirements
|
||||
for location in compass_room_chests:
|
||||
set_rule(world.get_location(location, player), lambda state: state.has('Fire Rod', player) and (state._lttp_has_key('Small Key (Ganons Tower)', player, 4) or (
|
||||
item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(compass_room_chests, [player] * len(compass_room_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 3))))
|
||||
set_rule(world.get_location(location, player), lambda state: state.has('Fire Rod', player) and (state._lttp_has_key('Small Key (Ganons Tower)', player, 7) or (
|
||||
item_name_in_location_names(state, 'Big Key (Ganons Tower)', player, zip(compass_room_chests, [player] * len(compass_room_chests))) and state._lttp_has_key('Small Key (Ganons Tower)', player, 5))))
|
||||
|
||||
set_rule(world.get_location('Ganons Tower - Big Chest', player), lambda state: state.has('Big Key (Ganons Tower)', player))
|
||||
|
||||
@@ -408,9 +479,9 @@ def global_rules(world, player):
|
||||
set_rule(world.get_entrance('Ganons Tower Torch Rooms', player),
|
||||
lambda state: has_fire_source(state, player) and state.multiworld.get_entrance('Ganons Tower Torch Rooms', player).parent_region.dungeon.bosses['middle'].can_defeat(state))
|
||||
set_rule(world.get_location('Ganons Tower - Pre-Moldorm Chest', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 3))
|
||||
lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7))
|
||||
set_rule(world.get_entrance('Ganons Tower Moldorm Door', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 4))
|
||||
lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 8))
|
||||
set_rule(world.get_entrance('Ganons Tower Moldorm Gap', player),
|
||||
lambda state: state.has('Hookshot', player) and state.multiworld.get_entrance('Ganons Tower Moldorm Gap', player).parent_region.dungeon.bosses['top'].can_defeat(state))
|
||||
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player))
|
||||
@@ -797,15 +868,21 @@ def add_conditional_lamps(world, player):
|
||||
if world.mode[player] != 'inverted':
|
||||
add_conditional_lamp('Agahnim 1', 'Agahnims Tower', 'Entrance')
|
||||
add_conditional_lamp('Castle Tower - Dark Maze', 'Agahnims Tower')
|
||||
add_conditional_lamp('Castle Tower - Dark Archer Key Drop', 'Agahnims Tower')
|
||||
add_conditional_lamp('Castle Tower - Circle of Pots Key Drop', 'Agahnims Tower')
|
||||
else:
|
||||
add_conditional_lamp('Agahnim 1', 'Inverted Agahnims Tower', 'Entrance')
|
||||
add_conditional_lamp('Castle Tower - Dark Maze', 'Inverted Agahnims Tower')
|
||||
add_conditional_lamp('Castle Tower - Dark Archer Key Drop', 'Inverted Agahnims Tower')
|
||||
add_conditional_lamp('Castle Tower - Circle of Pots Key Drop', 'Inverted Agahnims Tower')
|
||||
add_conditional_lamp('Old Man', 'Old Man Cave')
|
||||
add_conditional_lamp('Old Man Cave Exit (East)', 'Old Man Cave', 'Entrance')
|
||||
add_conditional_lamp('Death Mountain Return Cave Exit (East)', 'Death Mountain Return Cave', 'Entrance')
|
||||
add_conditional_lamp('Death Mountain Return Cave Exit (West)', 'Death Mountain Return Cave', 'Entrance')
|
||||
add_conditional_lamp('Old Man House Front to Back', 'Old Man House', 'Entrance')
|
||||
add_conditional_lamp('Old Man House Back to Front', 'Old Man House', 'Entrance')
|
||||
add_conditional_lamp('Eastern Palace - Dark Square Pot Key', 'Eastern Palace')
|
||||
add_conditional_lamp('Eastern Palace - Dark Eyegore Key Drop', 'Eastern Palace', 'Location', True)
|
||||
add_conditional_lamp('Eastern Palace - Big Key Chest', 'Eastern Palace')
|
||||
add_conditional_lamp('Eastern Palace - Boss', 'Eastern Palace', 'Location', True)
|
||||
add_conditional_lamp('Eastern Palace - Prize', 'Eastern Palace', 'Location', True)
|
||||
@@ -817,17 +894,32 @@ def add_conditional_lamps(world, player):
|
||||
|
||||
|
||||
def open_rules(world, player):
|
||||
# softlock protection as you can reach the sewers small key door with a guard drop key
|
||||
set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player))
|
||||
def basement_key_rule(state):
|
||||
if location_item_name(state, 'Sewers - Key Rat Key Drop', player) == ("Small Key (Hyrule Castle)", player):
|
||||
return state._lttp_has_key("Small Key (Hyrule Castle)", player, 2)
|
||||
else:
|
||||
return state._lttp_has_key("Small Key (Hyrule Castle)", player, 3)
|
||||
|
||||
set_rule(world.get_location('Hyrule Castle - Boomerang Guard Key Drop', player), basement_key_rule)
|
||||
set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player), basement_key_rule)
|
||||
|
||||
set_rule(world.get_location('Sewers - Key Rat Key Drop', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 3))
|
||||
|
||||
set_rule(world.get_location('Hyrule Castle - Big Key Drop', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4))
|
||||
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player))
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) and
|
||||
state.has('Big Key (Hyrule Castle)', player))
|
||||
|
||||
|
||||
def swordless_rules(world, player):
|
||||
set_rule(world.get_entrance('Agahnim 1', player), lambda state: (state.has('Hammer', player) or state.has('Fire Rod', player) or can_shoot_arrows(state, player) or state.has('Cane of Somaria', player)) and state._lttp_has_key('Small Key (Agahnims Tower)', player, 2))
|
||||
set_rule(world.get_entrance('Skull Woods Torch Room', player), lambda state: state._lttp_has_key('Small Key (Skull Woods)', player, 3) and state.has('Fire Rod', player)) # no curtain
|
||||
set_rule(world.get_entrance('Ice Palace Entrance Room', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player)) #in swordless mode bombos pads are present in the relevant parts of ice palace
|
||||
|
||||
set_rule(world.get_location('Ice Palace - Jelly Key Drop', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player))
|
||||
set_rule(world.get_entrance('Ice Palace (Second Section)', player), lambda state: (state.has('Fire Rod', player) or state.has('Bombos', player)) and state._lttp_has_key('Small Key (Ice Palace)', player))
|
||||
|
||||
set_rule(world.get_entrance('Ganon Drop', player), lambda state: state.has('Hammer', player)) # need to damage ganon to get tiles to drop
|
||||
|
||||
if world.mode[player] != 'inverted':
|
||||
@@ -850,11 +942,27 @@ def add_connection(parent_name, target_name, entrance_name, world, player):
|
||||
def standard_rules(world, player):
|
||||
add_connection('Menu', 'Hyrule Castle Secret Entrance', 'Uncle S&Q', world, player)
|
||||
world.get_entrance('Uncle S&Q', player).hide_path = True
|
||||
set_rule(world.get_entrance('Throne Room', player), lambda state: state.can_reach('Hyrule Castle - Zelda\'s Chest', 'Location', player))
|
||||
set_rule(world.get_entrance('Hyrule Castle Exit (East)', player), lambda state: state.can_reach('Sanctuary', 'Region', player))
|
||||
set_rule(world.get_entrance('Hyrule Castle Exit (West)', player), lambda state: state.can_reach('Sanctuary', 'Region', player))
|
||||
set_rule(world.get_entrance('Links House S&Q', player), lambda state: state.can_reach('Sanctuary', 'Region', player))
|
||||
set_rule(world.get_entrance('Sanctuary S&Q', player), lambda state: state.can_reach('Sanctuary', 'Region', player))
|
||||
|
||||
if world.smallkey_shuffle[player] != smallkey_shuffle.option_universal:
|
||||
set_rule(world.get_location('Hyrule Castle - Boomerang Guard Key Drop', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1))
|
||||
set_rule(world.get_location('Hyrule Castle - Boomerang Chest', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 1))
|
||||
|
||||
set_rule(world.get_location('Hyrule Castle - Big Key Drop', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 2))
|
||||
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 2) and
|
||||
state.has('Big Key (Hyrule Castle)', player))
|
||||
|
||||
set_rule(world.get_location('Sewers - Key Rat Key Drop', player),
|
||||
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 3))
|
||||
|
||||
def toss_junk_item(world, player):
|
||||
items = ['Rupees (20)', 'Bombs (3)', 'Arrows (10)', 'Rupees (5)', 'Rupee (1)', 'Bombs (10)',
|
||||
'Single Arrow', 'Rupees (50)', 'Rupees (100)', 'Single Bomb', 'Bee', 'Bee Trap',
|
||||
@@ -869,7 +977,7 @@ def toss_junk_item(world, player):
|
||||
|
||||
def set_trock_key_rules(world, player):
|
||||
# First set all relevant locked doors to impassible.
|
||||
for entrance in ['Turtle Rock Dark Room Staircase', 'Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Chain Chomp Room) (South)', 'Turtle Rock Pokey Room', 'Turtle Rock Big Key Door']:
|
||||
for entrance in ['Turtle Rock Dark Room Staircase', 'Turtle Rock (Chain Chomp Room) (North)', 'Turtle Rock (Chain Chomp Room) (South)', 'Turtle Rock Entrance to Pokey Room', 'Turtle Rock (Pokey Room) (South)', 'Turtle Rock (Pokey Room) (North)', 'Turtle Rock Big Key Door']:
|
||||
set_rule(world.get_entrance(entrance, player), lambda state: False)
|
||||
|
||||
all_state = world.get_all_state(use_cache=False)
|
||||
@@ -892,6 +1000,7 @@ def set_trock_key_rules(world, player):
|
||||
if can_reach_middle and not can_reach_back and not can_reach_front:
|
||||
normal_regions = all_state.reachable_regions[player].copy()
|
||||
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)', player), lambda state: True)
|
||||
set_rule(world.get_entrance('Turtle Rock (Pokey Room) (South)', player), lambda state: True)
|
||||
all_state.update_reachable_regions(player)
|
||||
front_locked_regions = all_state.reachable_regions[player].difference(normal_regions)
|
||||
front_locked_locations = set((location.name, player) for region in front_locked_regions for location in region.locations)
|
||||
@@ -903,26 +1012,33 @@ def set_trock_key_rules(world, player):
|
||||
# otherwise crystaroller room might not be properly marked as reachable through the back.
|
||||
set_rule(world.get_entrance('Turtle Rock Big Key Door', player), lambda state: state.has('Big Key (Turtle Rock)', player))
|
||||
|
||||
# No matter what, the key requirement for going from the middle to the bottom should be three keys.
|
||||
set_rule(world.get_entrance('Turtle Rock Dark Room Staircase', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 3))
|
||||
# No matter what, the key requirement for going from the middle to the bottom should be five keys.
|
||||
set_rule(world.get_entrance('Turtle Rock Dark Room Staircase', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 5))
|
||||
|
||||
# Now we need to set rules based on which entrances we have access to. The most important point is whether we have back access. If we have back access, we
|
||||
# might open all the locked doors in any order so we need maximally restrictive rules.
|
||||
# might open all the locked doors in any order, so we need maximally restrictive rules.
|
||||
if can_reach_back:
|
||||
set_rule(world.get_location('Turtle Rock - Big Key Chest', player), lambda state: (state._lttp_has_key('Small Key (Turtle Rock)', player, 4) or location_item_name(state, 'Turtle Rock - Big Key Chest', player) == ('Small Key (Turtle Rock)', player)))
|
||||
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 4))
|
||||
# Only consider wasting the key on the Trinexx door for going from the front entrance to middle section. If other key doors are accessible, then these doors can be avoided
|
||||
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 3))
|
||||
set_rule(world.get_entrance('Turtle Rock Pokey Room', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 2))
|
||||
else:
|
||||
# Middle to front requires 2 keys if the back is locked, otherwise 4
|
||||
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 2)
|
||||
if item_name_in_location_names(state, 'Big Key (Turtle Rock)', player, front_locked_locations)
|
||||
else state._lttp_has_key('Small Key (Turtle Rock)', player, 4))
|
||||
set_rule(world.get_location('Turtle Rock - Big Key Chest', player), lambda state: (state._lttp_has_key('Small Key (Turtle Rock)', player, 6) or location_item_name(state, 'Turtle Rock - Big Key Chest', player) == ('Small Key (Turtle Rock)', player)))
|
||||
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 5))
|
||||
set_rule(world.get_entrance('Turtle Rock (Pokey Room) (South)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 6))
|
||||
|
||||
# Front to middle requires 2 keys (if the middle is accessible then these doors can be avoided, otherwise no keys can be wasted)
|
||||
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 2))
|
||||
set_rule(world.get_entrance('Turtle Rock Pokey Room', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 1))
|
||||
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 6))
|
||||
set_rule(world.get_entrance('Turtle Rock (Pokey Room) (North)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 6))
|
||||
set_rule(world.get_entrance('Turtle Rock Entrance to Pokey Room', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 5))
|
||||
else:
|
||||
# Middle to front requires 3 keys if the back is locked by this door, otherwise 5
|
||||
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (South)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 3)
|
||||
if item_name_in_location_names(state, 'Big Key (Turtle Rock)', player, front_locked_locations.union({('Turtle Rock - Pokey 1 Key Drop', player)}))
|
||||
else state._lttp_has_key('Small Key (Turtle Rock)', player, 5))
|
||||
# Middle to front requires 4 keys if the back is locked by this door, otherwise 6
|
||||
set_rule(world.get_entrance('Turtle Rock (Pokey Room) (South)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 4)
|
||||
if item_name_in_location_names(state, 'Big Key (Turtle Rock)', player, front_locked_locations)
|
||||
else state._lttp_has_key('Small Key (Turtle Rock)', player, 6))
|
||||
|
||||
# Front to middle requires 3 keys (if the middle is accessible then these doors can be avoided, otherwise no keys can be wasted)
|
||||
set_rule(world.get_entrance('Turtle Rock (Chain Chomp Room) (North)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 3))
|
||||
set_rule(world.get_entrance('Turtle Rock (Pokey Room) (North)', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 2))
|
||||
set_rule(world.get_entrance('Turtle Rock Entrance to Pokey Room', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, 1))
|
||||
|
||||
set_rule(world.get_location('Turtle Rock - Big Key Chest', player), lambda state: state._lttp_has_key('Small Key (Turtle Rock)', player, tr_big_key_chest_keys_needed(state)))
|
||||
|
||||
@@ -933,8 +1049,8 @@ def set_trock_key_rules(world, player):
|
||||
if item in [('Small Key (Turtle Rock)', player)]:
|
||||
return 0
|
||||
if item in [('Big Key (Turtle Rock)', player)]:
|
||||
return 2
|
||||
return 4
|
||||
return 4
|
||||
return 6
|
||||
|
||||
# If TR is only accessible from the middle, the big key must be further restricted to prevent softlock potential
|
||||
if not can_reach_front and not world.smallkey_shuffle[player]:
|
||||
@@ -943,10 +1059,12 @@ def set_trock_key_rules(world, player):
|
||||
if not can_reach_big_chest:
|
||||
# Must not go in the Chain Chomps chest - only 2 other chests available and 3+ keys required for all other chests
|
||||
forbid_item(world.get_location('Turtle Rock - Chain Chomps', player), 'Big Key (Turtle Rock)', player)
|
||||
forbid_item(world.get_location('Turtle Rock - Pokey 2 Key Drop', player), 'Big Key (Turtle Rock)', player)
|
||||
if world.accessibility[player] == 'locations' and world.goal[player] != 'icerodhunt':
|
||||
if world.bigkey_shuffle[player] and can_reach_big_chest:
|
||||
# Must not go in the dungeon - all 3 available chests (Chomps, Big Chest, Crystaroller) must be keys to access laser bridge, and the big key is required first
|
||||
for location in ['Turtle Rock - Chain Chomps', 'Turtle Rock - Compass Chest',
|
||||
'Turtle Rock - Pokey 1 Key Drop', 'Turtle Rock - Pokey 2 Key Drop',
|
||||
'Turtle Rock - Roller Room - Left', 'Turtle Rock - Roller Room - Right']:
|
||||
forbid_item(world.get_location(location, player), 'Big Key (Turtle Rock)', player)
|
||||
else:
|
||||
|
||||
@@ -66,9 +66,12 @@ def underworld_glitches_rules(world, player):
|
||||
fix_fake_worlds = world.fix_fake_world[player]
|
||||
|
||||
# Ice Palace Entrance Clip
|
||||
# This is the easiest one since it's a simple internal clip. Just need to also add melting to freezor chest since it's otherwise assumed.
|
||||
add_rule(world.get_entrance('Ice Palace Entrance Room', player), lambda state: can_bomb_clip(state, world.get_region('Ice Palace (Entrance)', player), player), combine='or')
|
||||
# This is the easiest one since it's a simple internal clip.
|
||||
# Need to also add melting to freezor chest since it's otherwise assumed.
|
||||
# Also can pick up the first jelly key from behind.
|
||||
add_rule(world.get_entrance('Ice Palace (Main)', player), lambda state: can_bomb_clip(state, world.get_region('Ice Palace (Entrance)', player), player), combine='or')
|
||||
add_rule(world.get_location('Ice Palace - Freezor Chest', player), lambda state: can_melt_things(state, player))
|
||||
add_rule(world.get_location('Ice Palace - Jelly Key Drop', player), lambda state: can_bomb_clip(state, world.get_region('Ice Palace (Entrance)', player), player), combine='or')
|
||||
|
||||
|
||||
# Kiki Skip
|
||||
|
||||
@@ -15,7 +15,7 @@ from .ItemPool import generate_itempool, difficulties
|
||||
from .Items import item_init_table, item_name_groups, item_table, GetBeemizerItem
|
||||
from .Options import alttp_options, smallkey_shuffle
|
||||
from .Regions import lookup_name_to_id, create_regions, mark_light_world_regions, lookup_vanilla_location_to_entrance, \
|
||||
is_main_entrance
|
||||
is_main_entrance, key_drop_data
|
||||
from .Client import ALTTPSNIClient
|
||||
from .Rom import LocalRom, patch_rom, patch_race_rom, check_enemizer, patch_enemizer, apply_rom_settings, \
|
||||
get_hash_string, get_base_rom_path, LttPDeltaPatch
|
||||
@@ -303,6 +303,8 @@ class ALTTPWorld(World):
|
||||
world.local_items[player].value |= self.item_name_groups[option.item_name_group]
|
||||
elif option == "different_world":
|
||||
world.non_local_items[player].value |= self.item_name_groups[option.item_name_group]
|
||||
if world.mode[player] == "standard":
|
||||
world.non_local_items[player].value -= {"Small Key (Hyrule Castle)"}
|
||||
elif option.in_dungeon:
|
||||
self.dungeon_local_item_names |= self.item_name_groups[option.item_name_group]
|
||||
if option == "original_dungeon":
|
||||
@@ -478,12 +480,17 @@ class ALTTPWorld(World):
|
||||
break
|
||||
else:
|
||||
raise FillError('Unable to place dungeon prizes')
|
||||
if world.mode[player] == 'standard' and world.smallkey_shuffle[player] \
|
||||
and world.smallkey_shuffle[player] != smallkey_shuffle.option_universal and \
|
||||
world.smallkey_shuffle[player] != smallkey_shuffle.option_own_dungeons:
|
||||
world.local_early_items[player]["Small Key (Hyrule Castle)"] = 1
|
||||
|
||||
@classmethod
|
||||
def stage_pre_fill(cls, world):
|
||||
from .Dungeons import fill_dungeons_restrictive
|
||||
fill_dungeons_restrictive(world)
|
||||
|
||||
|
||||
@classmethod
|
||||
def stage_post_fill(cls, world):
|
||||
ShopSlotFill(world)
|
||||
@@ -618,7 +625,6 @@ class ALTTPWorld(World):
|
||||
@classmethod
|
||||
def stage_fill_hook(cls, world, progitempool, usefulitempool, filleritempool, fill_locations):
|
||||
trash_counts = {}
|
||||
|
||||
for player in world.get_game_players("A Link to the Past"):
|
||||
if not world.ganonstower_vanilla[player] or \
|
||||
world.logic[player] in {'owglitches', 'hybridglitches', "nologic"}:
|
||||
@@ -792,7 +798,7 @@ class ALTTPWorld(World):
|
||||
slot_options = ["crystals_needed_for_gt", "crystals_needed_for_ganon", "open_pyramid",
|
||||
"bigkey_shuffle", "smallkey_shuffle", "compass_shuffle", "map_shuffle",
|
||||
"progressive", "swordless", "retro_bow", "retro_caves", "shop_item_slots",
|
||||
"boss_shuffle", "pot_shuffle", "enemy_shuffle"]
|
||||
"boss_shuffle", "pot_shuffle", "enemy_shuffle", "key_drop_shuffle"]
|
||||
|
||||
slot_data = {option_name: getattr(self.multiworld, option_name)[self.player].value for option_name in slot_options}
|
||||
|
||||
@@ -803,11 +809,11 @@ class ALTTPWorld(World):
|
||||
'mm_medalion': self.multiworld.required_medallions[self.player][0],
|
||||
'tr_medalion': self.multiworld.required_medallions[self.player][1],
|
||||
'shop_shuffle': self.multiworld.shop_shuffle[self.player],
|
||||
'entrance_shuffle': self.multiworld.shuffle[self.player]
|
||||
'entrance_shuffle': self.multiworld.shuffle[self.player],
|
||||
}
|
||||
)
|
||||
return slot_data
|
||||
|
||||
|
||||
|
||||
def get_same_seed(world, seed_def: tuple) -> str:
|
||||
seeds: typing.Dict[tuple, str] = getattr(world, "__named_seeds", {})
|
||||
|
||||
@@ -16,6 +16,18 @@ class TestAgahnimsTower(TestDungeon):
|
||||
["Castle Tower - Dark Maze", False, [], ['Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']],
|
||||
["Castle Tower - Dark Maze", True, ['Progressive Sword', 'Small Key (Agahnims Tower)', 'Lamp']],
|
||||
|
||||
["Castle Tower - Dark Archer Key Drop", False, []],
|
||||
["Castle Tower - Dark Archer Key Drop", False, ['Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)']],
|
||||
["Castle Tower - Dark Archer Key Drop", False, [], ['Lamp']],
|
||||
["Castle Tower - Dark Archer Key Drop", False, [], ['Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']],
|
||||
["Castle Tower - Dark Archer Key Drop", True, ['Progressive Sword', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Lamp']],
|
||||
|
||||
["Castle Tower - Circle of Pots Key Drop", False, []],
|
||||
["Castle Tower - Circle of Pots Key Drop", False, ['Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)']],
|
||||
["Castle Tower - Circle of Pots Key Drop", False, [], ['Lamp']],
|
||||
["Castle Tower - Circle of Pots Key Drop", False, [], ['Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']],
|
||||
["Castle Tower - Circle of Pots Key Drop", True, ['Progressive Sword', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Lamp']],
|
||||
|
||||
["Agahnim 1", False, []],
|
||||
["Agahnim 1", False, ['Small Key (Agahnims Tower)'], ['Small Key (Agahnims Tower)']],
|
||||
["Agahnim 1", False, [], ['Progressive Sword']],
|
||||
|
||||
@@ -18,12 +18,27 @@ class TestDesertPalace(TestDungeon):
|
||||
|
||||
["Desert Palace - Compass Chest", False, []],
|
||||
["Desert Palace - Compass Chest", False, [], ['Small Key (Desert Palace)']],
|
||||
["Desert Palace - Compass Chest", True, ['Small Key (Desert Palace)']],
|
||||
["Desert Palace - Compass Chest", False, ['Progressive Sword', 'Hammer', 'Fire Rod', 'Ice Rod', 'Progressive Bow', 'Cane of Somaria', 'Cane of Byrna']],
|
||||
["Desert Palace - Compass Chest", False, ['Small Key (Desert Palace)']],
|
||||
["Desert Palace - Compass Chest", True, ['Progressive Sword', 'Small Key (Desert Palace)']],
|
||||
|
||||
#@todo: Require a real weapon for enemizer?
|
||||
["Desert Palace - Big Key Chest", False, []],
|
||||
["Desert Palace - Big Key Chest", False, [], ['Small Key (Desert Palace)']],
|
||||
["Desert Palace - Big Key Chest", True, ['Small Key (Desert Palace)']],
|
||||
["Desert Palace - Big Key Chest", False, ['Progressive Sword', 'Hammer', 'Fire Rod', 'Ice Rod', 'Progressive Bow', 'Cane of Somaria', 'Cane of Byrna']],
|
||||
["Desert Palace - Big Key Chest", False, ['Small Key (Desert Palace)']],
|
||||
["Desert Palace - Big Key Chest", True, ['Progressive Sword', 'Small Key (Desert Palace)']],
|
||||
|
||||
["Desert Palace - Desert Tiles 1 Pot Key", True, []],
|
||||
|
||||
["Desert Palace - Beamos Hall Pot Key", False, []],
|
||||
["Desert Palace - Beamos Hall Pot Key", False, [], ['Small Key (Desert Palace)']],
|
||||
["Desert Palace - Beamos Hall Pot Key", False, ['Progressive Sword', 'Hammer', 'Fire Rod', 'Ice Rod', 'Progressive Bow', 'Cane of Somaria', 'Cane of Byrna']],
|
||||
["Desert Palace - Beamos Hall Pot Key", True, ['Small Key (Desert Palace)', 'Progressive Sword']],
|
||||
|
||||
["Desert Palace - Desert Tiles 2 Pot Key", False, []],
|
||||
["Desert Palace - Desert Tiles 2 Pot Key", False, ['Small Key (Desert Palace)']],
|
||||
["Desert Palace - Desert Tiles 2 Pot Key", False, ['Progressive Sword', 'Hammer', 'Fire Rod', 'Ice Rod', 'Progressive Bow', 'Cane of Somaria', 'Cane of Byrna']],
|
||||
["Desert Palace - Desert Tiles 2 Pot Key", True, ['Small Key (Desert Palace)', 'Progressive Sword']],
|
||||
|
||||
["Desert Palace - Boss", False, []],
|
||||
["Desert Palace - Boss", False, [], ['Small Key (Desert Palace)']],
|
||||
@@ -33,7 +48,6 @@ class TestDesertPalace(TestDungeon):
|
||||
["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Fire Rod']],
|
||||
["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Progressive Sword']],
|
||||
["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Hammer']],
|
||||
["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Ice Rod']],
|
||||
["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Cane of Somaria']],
|
||||
["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Cane of Byrna']],
|
||||
])
|
||||
@@ -61,6 +61,7 @@ class TestDungeon(unittest.TestCase):
|
||||
|
||||
for item in items:
|
||||
item.classification = ItemClassification.progression
|
||||
state.collect(item)
|
||||
state.collect(item, event=True) # event=True prevents running sweep_for_events() and picking up
|
||||
state.sweep_for_events() # key drop keys repeatedly
|
||||
|
||||
self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), access)
|
||||
self.assertEqual(self.multiworld.get_location(location, 1).can_reach(state), access, f"failed {self.multiworld.get_location(location, 1)} with: {item_pool}")
|
||||
@@ -18,7 +18,8 @@ class TestEasternPalace(TestDungeon):
|
||||
|
||||
["Eastern Palace - Big Key Chest", False, []],
|
||||
["Eastern Palace - Big Key Chest", False, [], ['Lamp']],
|
||||
["Eastern Palace - Big Key Chest", True, ['Lamp']],
|
||||
["Eastern Palace - Big Key Chest", True, ['Lamp', 'Small Key (Eastern Palace)', 'Small Key (Eastern Palace)']],
|
||||
["Eastern Palace - Big Key Chest", True, ['Lamp', 'Big Key (Eastern Palace)']],
|
||||
|
||||
#@todo: Advanced?
|
||||
["Eastern Palace - Boss", False, []],
|
||||
|
||||
@@ -33,46 +33,50 @@ class TestGanonsTower(TestDungeon):
|
||||
["Ganons Tower - Randomizer Room - Top Left", False, []],
|
||||
["Ganons Tower - Randomizer Room - Top Left", False, [], ['Hammer']],
|
||||
["Ganons Tower - Randomizer Room - Top Left", False, [], ['Hookshot']],
|
||||
["Ganons Tower - Randomizer Room - Top Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Randomizer Room - Top Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Fire Rod', 'Cane of Somaria']],
|
||||
["Ganons Tower - Randomizer Room - Top Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
|
||||
["Ganons Tower - Randomizer Room - Top Right", False, []],
|
||||
["Ganons Tower - Randomizer Room - Top Right", False, [], ['Hammer']],
|
||||
["Ganons Tower - Randomizer Room - Top Right", False, [], ['Hookshot']],
|
||||
["Ganons Tower - Randomizer Room - Top Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Randomizer Room - Top Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Fire Rod', 'Cane of Somaria']],
|
||||
["Ganons Tower - Randomizer Room - Top Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
|
||||
["Ganons Tower - Randomizer Room - Bottom Left", False, []],
|
||||
["Ganons Tower - Randomizer Room - Bottom Left", False, [], ['Hammer']],
|
||||
["Ganons Tower - Randomizer Room - Bottom Left", False, [], ['Hookshot']],
|
||||
["Ganons Tower - Randomizer Room - Bottom Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Randomizer Room - Bottom Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Fire Rod', 'Cane of Somaria']],
|
||||
["Ganons Tower - Randomizer Room - Bottom Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
|
||||
["Ganons Tower - Randomizer Room - Bottom Right", False, []],
|
||||
["Ganons Tower - Randomizer Room - Bottom Right", False, [], ['Hammer']],
|
||||
["Ganons Tower - Randomizer Room - Bottom Right", False, [], ['Hookshot']],
|
||||
["Ganons Tower - Randomizer Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Randomizer Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer', 'Fire Rod', 'Cane of Somaria']],
|
||||
["Ganons Tower - Randomizer Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
|
||||
["Ganons Tower - Firesnake Room", False, []],
|
||||
["Ganons Tower - Firesnake Room", False, [], ['Hammer']],
|
||||
["Ganons Tower - Firesnake Room", False, [], ['Hookshot']],
|
||||
["Ganons Tower - Firesnake Room", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Firesnake Room", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
|
||||
["Ganons Tower - Map Chest", False, []],
|
||||
["Ganons Tower - Map Chest", False, [], ['Hammer']],
|
||||
["Ganons Tower - Map Chest", False, [], ['Hookshot', 'Pegasus Boots']],
|
||||
["Ganons Tower - Map Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Map Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hammer', 'Pegasus Boots']],
|
||||
["Ganons Tower - Map Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Map Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hammer', 'Pegasus Boots']],
|
||||
|
||||
["Ganons Tower - Big Chest", False, []],
|
||||
["Ganons Tower - Big Chest", False, [], ['Big Key (Ganons Tower)']],
|
||||
["Ganons Tower - Big Chest", True, ['Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']],
|
||||
["Ganons Tower - Big Chest", True, ['Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Big Chest", True, ['Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']],
|
||||
["Ganons Tower - Big Chest", True, ['Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
|
||||
["Ganons Tower - Hope Room - Left", True, []],
|
||||
|
||||
["Ganons Tower - Hope Room - Right", True, []],
|
||||
|
||||
["Ganons Tower - Bob's Chest", False, []],
|
||||
["Ganons Tower - Bob's Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']],
|
||||
["Ganons Tower - Bob's Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Bob's Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']],
|
||||
["Ganons Tower - Bob's Chest", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
|
||||
["Ganons Tower - Tile Room", False, []],
|
||||
["Ganons Tower - Tile Room", False, [], ['Cane of Somaria']],
|
||||
@@ -81,34 +85,34 @@ class TestGanonsTower(TestDungeon):
|
||||
["Ganons Tower - Compass Room - Top Left", False, []],
|
||||
["Ganons Tower - Compass Room - Top Left", False, [], ['Cane of Somaria']],
|
||||
["Ganons Tower - Compass Room - Top Left", False, [], ['Fire Rod']],
|
||||
["Ganons Tower - Compass Room - Top Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']],
|
||||
["Ganons Tower - Compass Room - Top Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']],
|
||||
|
||||
["Ganons Tower - Compass Room - Top Right", False, []],
|
||||
["Ganons Tower - Compass Room - Top Right", False, [], ['Cane of Somaria']],
|
||||
["Ganons Tower - Compass Room - Top Right", False, [], ['Fire Rod']],
|
||||
["Ganons Tower - Compass Room - Top Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']],
|
||||
["Ganons Tower - Compass Room - Top Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']],
|
||||
|
||||
["Ganons Tower - Compass Room - Bottom Left", False, []],
|
||||
["Ganons Tower - Compass Room - Bottom Left", False, [], ['Cane of Somaria']],
|
||||
["Ganons Tower - Compass Room - Bottom Left", False, [], ['Fire Rod']],
|
||||
["Ganons Tower - Compass Room - Bottom Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']],
|
||||
["Ganons Tower - Compass Room - Bottom Left", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']],
|
||||
|
||||
["Ganons Tower - Compass Room - Bottom Right", False, []],
|
||||
["Ganons Tower - Compass Room - Bottom Right", False, [], ['Cane of Somaria']],
|
||||
["Ganons Tower - Compass Room - Bottom Right", False, [], ['Fire Rod']],
|
||||
["Ganons Tower - Compass Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']],
|
||||
["Ganons Tower - Compass Room - Bottom Right", True, ['Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Cane of Somaria']],
|
||||
|
||||
["Ganons Tower - Big Key Chest", False, []],
|
||||
["Ganons Tower - Big Key Chest", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']],
|
||||
["Ganons Tower - Big Key Chest", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Big Key Chest", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']],
|
||||
["Ganons Tower - Big Key Chest", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
|
||||
["Ganons Tower - Big Key Room - Left", False, []],
|
||||
["Ganons Tower - Big Key Room - Left", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']],
|
||||
["Ganons Tower - Big Key Room - Left", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Big Key Room - Left", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']],
|
||||
["Ganons Tower - Big Key Room - Left", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
|
||||
["Ganons Tower - Big Key Room - Right", False, []],
|
||||
["Ganons Tower - Big Key Room - Right", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']],
|
||||
["Ganons Tower - Big Key Room - Right", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Big Key Room - Right", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']],
|
||||
["Ganons Tower - Big Key Room - Right", True, ['Progressive Bow', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
|
||||
|
||||
["Ganons Tower - Mini Helmasaur Room - Left", False, []],
|
||||
["Ganons Tower - Mini Helmasaur Room - Left", False, [], ['Progressive Bow']],
|
||||
@@ -128,8 +132,8 @@ class TestGanonsTower(TestDungeon):
|
||||
["Ganons Tower - Pre-Moldorm Chest", False, [], ['Progressive Bow']],
|
||||
["Ganons Tower - Pre-Moldorm Chest", False, [], ['Big Key (Ganons Tower)']],
|
||||
["Ganons Tower - Pre-Moldorm Chest", False, [], ['Lamp', 'Fire Rod']],
|
||||
["Ganons Tower - Pre-Moldorm Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp']],
|
||||
["Ganons Tower - Pre-Moldorm Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod']],
|
||||
["Ganons Tower - Pre-Moldorm Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp']],
|
||||
["Ganons Tower - Pre-Moldorm Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod']],
|
||||
|
||||
["Ganons Tower - Validation Chest", False, []],
|
||||
["Ganons Tower - Validation Chest", False, [], ['Hookshot']],
|
||||
@@ -137,8 +141,8 @@ class TestGanonsTower(TestDungeon):
|
||||
["Ganons Tower - Validation Chest", False, [], ['Big Key (Ganons Tower)']],
|
||||
["Ganons Tower - Validation Chest", False, [], ['Lamp', 'Fire Rod']],
|
||||
["Ganons Tower - Validation Chest", False, [], ['Progressive Sword', 'Hammer']],
|
||||
["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Progressive Sword']],
|
||||
["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Progressive Sword']],
|
||||
["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Progressive Sword']],
|
||||
["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Progressive Sword']],
|
||||
["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Lamp', 'Hookshot', 'Hammer']],
|
||||
["Ganons Tower - Validation Chest", True, ['Progressive Bow', 'Big Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Hammer']],
|
||||
])
|
||||
@@ -72,8 +72,9 @@ class TestIcePalace(TestDungeon):
|
||||
["Ice Palace - Boss", False, [], ['Big Key (Ice Palace)']],
|
||||
["Ice Palace - Boss", False, [], ['Fire Rod', 'Bombos']],
|
||||
["Ice Palace - Boss", False, [], ['Fire Rod', 'Progressive Sword']],
|
||||
["Ice Palace - Boss", True, ['Progressive Glove', 'Big Key (Ice Palace)', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']],
|
||||
["Ice Palace - Boss", True, ['Progressive Glove', 'Big Key (Ice Palace)', 'Fire Rod', 'Hammer', 'Cane of Somaria', 'Small Key (Ice Palace)']],
|
||||
["Ice Palace - Boss", True, ['Progressive Glove', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']],
|
||||
["Ice Palace - Boss", True, ['Progressive Glove', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword', 'Hammer', 'Cane of Somaria', 'Small Key (Ice Palace)']],
|
||||
# need hookshot now to reach the right side for the 6th key
|
||||
["Ice Palace - Boss", True, ['Progressive Glove', 'Big Key (Ice Palace)', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']],
|
||||
["Ice Palace - Boss", True, ['Progressive Glove', 'Big Key (Ice Palace)', 'Fire Rod', 'Hammer', 'Cane of Somaria', 'Small Key (Ice Palace)', 'Hookshot']],
|
||||
["Ice Palace - Boss", True, ['Progressive Glove', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']],
|
||||
["Ice Palace - Boss", True, ['Progressive Glove', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword', 'Hammer', 'Cane of Somaria', 'Small Key (Ice Palace)', 'Hookshot']],
|
||||
])
|
||||
@@ -26,18 +26,18 @@ class TestSkullWoods(TestDungeon):
|
||||
["Skull Woods - Big Chest", False, [], ['Never in logic']],
|
||||
|
||||
["Skull Woods - Compass Chest", False, []],
|
||||
["Skull Woods - Compass Chest", False, ['Small Key (Skull Woods)'], ['Small Key (Skull Woods)']],
|
||||
["Skull Woods - Compass Chest", True, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)']],
|
||||
["Skull Woods - Compass Chest", False, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)'], ['Small Key (Skull Woods)']],
|
||||
["Skull Woods - Compass Chest", True, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)']],
|
||||
|
||||
["Skull Woods - Map Chest", True, []],
|
||||
|
||||
["Skull Woods - Pot Prison", False, []],
|
||||
["Skull Woods - Pot Prison", False, ['Small Key (Skull Woods)'], ['Small Key (Skull Woods)']],
|
||||
["Skull Woods - Pot Prison", True, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)']],
|
||||
["Skull Woods - Pot Prison", False, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)'], ['Small Key (Skull Woods)']],
|
||||
["Skull Woods - Pot Prison", True, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)']],
|
||||
|
||||
["Skull Woods - Pinball Room", False, []],
|
||||
["Skull Woods - Pinball Room", False, [], ['Small Key (Skull Woods)']],
|
||||
["Skull Woods - Pinball Room", True, ['Small Key (Skull Woods)']]
|
||||
["Skull Woods - Pinball Room", False, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)'], ['Small Key (Skull Woods)']],
|
||||
["Skull Woods - Pinball Room", True, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)']],
|
||||
])
|
||||
|
||||
def testSkullWoodsLeftOnly(self):
|
||||
@@ -50,8 +50,8 @@ class TestSkullWoods(TestDungeon):
|
||||
["Skull Woods - Compass Chest", True, []],
|
||||
|
||||
["Skull Woods - Map Chest", False, []],
|
||||
["Skull Woods - Map Chest", False, [], ['Small Key (Skull Woods)']],
|
||||
["Skull Woods - Map Chest", True, ['Small Key (Skull Woods)']],
|
||||
["Skull Woods - Map Chest", False, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)'], ['Small Key (Skull Woods)']],
|
||||
["Skull Woods - Map Chest", True, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)']],
|
||||
|
||||
["Skull Woods - Pot Prison", True, []],
|
||||
|
||||
@@ -67,18 +67,18 @@ class TestSkullWoods(TestDungeon):
|
||||
["Skull Woods - Big Chest", True, ['Big Key (Skull Woods)']],
|
||||
|
||||
["Skull Woods - Compass Chest", False, []],
|
||||
["Skull Woods - Compass Chest", False, ['Small Key (Skull Woods)'], ['Small Key (Skull Woods)']],
|
||||
["Skull Woods - Compass Chest", True, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)']],
|
||||
["Skull Woods - Compass Chest", False, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)'], ['Small Key (Skull Woods)']],
|
||||
["Skull Woods - Compass Chest", True, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)']],
|
||||
|
||||
["Skull Woods - Map Chest", True, []],
|
||||
|
||||
["Skull Woods - Pot Prison", False, []],
|
||||
["Skull Woods - Pot Prison", False, ['Small Key (Skull Woods)'], ['Small Key (Skull Woods)']],
|
||||
["Skull Woods - Pot Prison", True, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)']],
|
||||
["Skull Woods - Pot Prison", False, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)'], ['Small Key (Skull Woods)']],
|
||||
["Skull Woods - Pot Prison", True, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)']],
|
||||
|
||||
["Skull Woods - Pinball Room", False, []],
|
||||
["Skull Woods - Pinball Room", False, [], ['Small Key (Skull Woods)']],
|
||||
["Skull Woods - Pinball Room", True, ['Small Key (Skull Woods)']]
|
||||
["Skull Woods - Pinball Room", False, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)'], ['Small Key (Skull Woods)']],
|
||||
["Skull Woods - Pinball Room", True, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)']]
|
||||
])
|
||||
|
||||
def testSkullWoodsMiddle(self):
|
||||
@@ -94,6 +94,6 @@ class TestSkullWoods(TestDungeon):
|
||||
["Skull Woods - Boss", False, []],
|
||||
["Skull Woods - Boss", False, [], ['Fire Rod']],
|
||||
["Skull Woods - Boss", False, [], ['Progressive Sword']],
|
||||
["Skull Woods - Boss", False, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)'], ['Small Key (Skull Woods)']],
|
||||
["Skull Woods - Boss", True, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Fire Rod', 'Progressive Sword']],
|
||||
["Skull Woods - Boss", False, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)'], ['Small Key (Skull Woods)']],
|
||||
["Skull Woods - Boss", True, ['Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Small Key (Skull Woods)', 'Fire Rod', 'Progressive Sword']],
|
||||
])
|
||||
@@ -6,10 +6,6 @@ class TestThievesTown(TestDungeon):
|
||||
def testThievesTown(self):
|
||||
self.starting_regions = ['Thieves Town (Entrance)']
|
||||
self.run_tests([
|
||||
["Thieves' Town - Attic", False, []],
|
||||
["Thieves' Town - Attic", False, [], ['Big Key (Thieves Town)']],
|
||||
["Thieves' Town - Attic", False, [], ['Small Key (Thieves Town)']],
|
||||
["Thieves' Town - Attic", True, ['Big Key (Thieves Town)', 'Small Key (Thieves Town)']],
|
||||
|
||||
["Thieves' Town - Big Key Chest", True, []],
|
||||
|
||||
@@ -19,6 +15,19 @@ class TestThievesTown(TestDungeon):
|
||||
|
||||
["Thieves' Town - Ambush Chest", True, []],
|
||||
|
||||
["Thieves' Town - Hallway Pot Key", False, []],
|
||||
["Thieves' Town - Hallway Pot Key", False, [], ['Big Key (Thieves Town)']],
|
||||
["Thieves' Town - Hallway Pot Key", True, ['Big Key (Thieves Town)']],
|
||||
|
||||
["Thieves' Town - Spike Switch Pot Key", False, []],
|
||||
["Thieves' Town - Spike Switch Pot Key", False, [], ['Big Key (Thieves Town)']],
|
||||
["Thieves' Town - Spike Switch Pot Key", True, ['Big Key (Thieves Town)']],
|
||||
|
||||
["Thieves' Town - Attic", False, []],
|
||||
["Thieves' Town - Attic", False, [], ['Big Key (Thieves Town)']],
|
||||
["Thieves' Town - Attic", False, [], ['Small Key (Thieves Town)']],
|
||||
["Thieves' Town - Attic", True, ['Big Key (Thieves Town)', 'Small Key (Thieves Town)']],
|
||||
|
||||
["Thieves' Town - Big Chest", False, []],
|
||||
["Thieves' Town - Big Chest", False, [], ['Big Key (Thieves Town)']],
|
||||
["Thieves' Town - Big Chest", False, [], ['Small Key (Thieves Town)']],
|
||||
@@ -31,7 +40,6 @@ class TestThievesTown(TestDungeon):
|
||||
|
||||
["Thieves' Town - Boss", False, []],
|
||||
["Thieves' Town - Boss", False, [], ['Big Key (Thieves Town)']],
|
||||
["Thieves' Town - Boss", False, [], ['Small Key (Thieves Town)']],
|
||||
["Thieves' Town - Boss", False, [], ['Hammer', 'Progressive Sword', 'Cane of Somaria', 'Cane of Byrna']],
|
||||
["Thieves' Town - Boss", True, ['Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Hammer']],
|
||||
["Thieves' Town - Boss", True, ['Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Progressive Sword']],
|
||||
|
||||
@@ -18,10 +18,9 @@ class TestInvertedTurtleRock(TestInverted):
|
||||
|
||||
["Turtle Rock - Chain Chomps", False, []],
|
||||
["Turtle Rock - Chain Chomps", False, [], ['Magic Mirror', 'Cane of Somaria']],
|
||||
# Item rando only needs 1 key. ER needs to consider the case when the back is accessible, but not the middle (key wasted on Trinexx door)
|
||||
["Turtle Rock - Chain Chomps", False, ['Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Chain Chomps", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Chain Chomps", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Chain Chomps", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Chain Chomps", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Chain Chomps", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']],
|
||||
["Turtle Rock - Chain Chomps", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']],
|
||||
["Turtle Rock - Chain Chomps", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot']],
|
||||
@@ -55,8 +54,8 @@ class TestInvertedTurtleRock(TestInverted):
|
||||
["Turtle Rock - Big Chest", False, [], ['Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Chest", False, [], ['Magic Mirror', 'Cane of Somaria']],
|
||||
["Turtle Rock - Big Chest", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria']],
|
||||
["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hookshot']],
|
||||
["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']],
|
||||
@@ -66,8 +65,8 @@ class TestInvertedTurtleRock(TestInverted):
|
||||
|
||||
["Turtle Rock - Big Key Chest", False, []],
|
||||
["Turtle Rock - Big Key Chest", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Key Chest", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Key Chest", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Key Chest", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Key Chest", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
# Mirror in from ledge, use left side entrance, have enough keys to get to the chest
|
||||
["Turtle Rock - Big Key Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Key Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
@@ -80,8 +79,8 @@ class TestInvertedTurtleRock(TestInverted):
|
||||
["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)', 'Lamp']],
|
||||
["Turtle Rock - Crystaroller Room", False, [], ['Magic Mirror', 'Cane of Somaria']],
|
||||
["Turtle Rock - Crystaroller Room", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot']],
|
||||
@@ -98,9 +97,9 @@ class TestInvertedTurtleRock(TestInverted):
|
||||
["Turtle Rock - Boss", False, [], ['Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", False, [], ['Magic Mirror', 'Lamp']],
|
||||
["Turtle Rock - Boss", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Flute', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Magic Upgrade (1/2)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)','Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Flute', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Magic Upgrade (1/2)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)','Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Flute', 'Magic Mirror', 'Moon Pearl', 'Hookshot', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']]
|
||||
])
|
||||
@@ -115,12 +114,12 @@ class TestInvertedTurtleRock(TestInverted):
|
||||
[location, False, [], ['Magic Mirror', 'Cane of Somaria']],
|
||||
[location, False, [], ['Magic Mirror', 'Lamp']],
|
||||
[location, False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
|
||||
|
||||
# Mirroring into Eye Bridge does not require Cane of Somaria
|
||||
[location, True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Byrna']],
|
||||
|
||||
@@ -20,8 +20,8 @@ class TestInvertedTurtleRock(TestInvertedMinor):
|
||||
["Turtle Rock - Chain Chomps", False, [], ['Magic Mirror', 'Cane of Somaria']],
|
||||
# Item rando only needs 1 key. ER needs to consider the case when the back is accessible, but not the middle (key wasted on Trinexx door)
|
||||
["Turtle Rock - Chain Chomps", False, ['Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Chain Chomps", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Chain Chomps", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Chain Chomps", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Chain Chomps", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Chain Chomps", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']],
|
||||
["Turtle Rock - Chain Chomps", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']],
|
||||
["Turtle Rock - Chain Chomps", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot']],
|
||||
@@ -55,8 +55,8 @@ class TestInvertedTurtleRock(TestInvertedMinor):
|
||||
["Turtle Rock - Big Chest", False, [], ['Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Chest", False, [], ['Magic Mirror', 'Cane of Somaria']],
|
||||
["Turtle Rock - Big Chest", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria']],
|
||||
["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hookshot']],
|
||||
["Turtle Rock - Big Chest", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']],
|
||||
@@ -66,8 +66,8 @@ class TestInvertedTurtleRock(TestInvertedMinor):
|
||||
|
||||
["Turtle Rock - Big Key Chest", False, []],
|
||||
["Turtle Rock - Big Key Chest", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Key Chest", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Key Chest", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Key Chest", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Key Chest", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
# Mirror in from ledge, use left side entrance, have enough keys to get to the chest
|
||||
["Turtle Rock - Big Key Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Big Key Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
@@ -80,8 +80,8 @@ class TestInvertedTurtleRock(TestInvertedMinor):
|
||||
["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)', 'Lamp']],
|
||||
["Turtle Rock - Crystaroller Room", False, [], ['Magic Mirror', 'Cane of Somaria']],
|
||||
["Turtle Rock - Crystaroller Room", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot']],
|
||||
["Turtle Rock - Crystaroller Room", True, ['Big Key (Turtle Rock)', 'Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot']],
|
||||
@@ -98,9 +98,9 @@ class TestInvertedTurtleRock(TestInvertedMinor):
|
||||
["Turtle Rock - Boss", False, [], ['Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", False, [], ['Magic Mirror', 'Lamp']],
|
||||
["Turtle Rock - Boss", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Flute', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Magic Upgrade (1/2)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)','Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Flute', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Bottle', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Progressive Sword', 'Cane of Somaria', 'Magic Upgrade (1/2)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)','Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']],
|
||||
["Turtle Rock - Boss", True, ['Ice Rod', 'Fire Rod', 'Flute', 'Magic Mirror', 'Moon Pearl', 'Hookshot', 'Hammer', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']]
|
||||
])
|
||||
@@ -116,12 +116,12 @@ class TestInvertedTurtleRock(TestInvertedMinor):
|
||||
[location, False, [], ['Magic Mirror', 'Cane of Somaria']],
|
||||
[location, False, [], ['Magic Mirror', 'Lamp']],
|
||||
[location, False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Magic Mirror', 'Small Key (Turtle Rock)']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cane of Byrna']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cane of Byrna']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Cape']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Cape']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Lamp', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
|
||||
[location, True, ['Big Key (Turtle Rock)', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Shield', 'Progressive Shield', 'Progressive Shield']],
|
||||
|
||||
# Mirroring into Eye Bridge does not require Cane of Somaria
|
||||
[location, True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Byrna']],
|
||||
|
||||
@@ -6,6 +6,7 @@ class TestDungeons(TestVanillaOWG):
|
||||
def testFirstDungeonChests(self):
|
||||
self.run_location_tests([
|
||||
["Hyrule Castle - Map Chest", True, []],
|
||||
["Hyrule Castle - Map Guard Key Drop", True, []],
|
||||
|
||||
["Sanctuary", True, []],
|
||||
|
||||
|
||||
@@ -4193,8 +4193,9 @@ def rules(blasphemousworld):
|
||||
# Items
|
||||
set_rule(world.get_location("BotSS: Platforming gauntlet", player),
|
||||
lambda state: (
|
||||
state.has("D17BZ02S01[FrontR]", player)
|
||||
or state.has_all({"Dash Ability", "Wall Climb Ability"}, player)
|
||||
#state.has("D17BZ02S01[FrontR]", player) or
|
||||
# TODO: actually fix this once door rando is real
|
||||
state.has_all({"Dash Ability", "Wall Climb Ability"}, player)
|
||||
))
|
||||
# Doors
|
||||
set_rule(world.get_entrance("D17BZ02S01[FrontR]", player),
|
||||
|
||||
@@ -52,6 +52,14 @@ class DarkSouls3World(World):
|
||||
required_client_version = (0, 4, 2)
|
||||
item_name_to_id = DarkSouls3Item.get_name_to_id()
|
||||
location_name_to_id = DarkSouls3Location.get_name_to_id()
|
||||
item_name_groups = {
|
||||
"Cinders": {
|
||||
"Cinders of a Lord - Abyss Watcher",
|
||||
"Cinders of a Lord - Aldrich",
|
||||
"Cinders of a Lord - Yhorm the Giant",
|
||||
"Cinders of a Lord - Lothric Prince"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def __init__(self, multiworld: MultiWorld, player: int):
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import orjson
|
||||
import logging
|
||||
import os
|
||||
import string
|
||||
@@ -20,7 +20,7 @@ pool = ThreadPoolExecutor(1)
|
||||
|
||||
|
||||
def load_json_data(data_name: str) -> Union[List[str], Dict[str, Any]]:
|
||||
return json.loads(pkgutil.get_data(__name__, "data/" + data_name + ".json").decode())
|
||||
return orjson.loads(pkgutil.get_data(__name__, "data/" + data_name + ".json"))
|
||||
|
||||
|
||||
techs_future = pool.submit(load_json_data, "techs")
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
factorio-rcon-py>=2.0.1
|
||||
orjson>=3.9.7
|
||||
|
||||
@@ -55,6 +55,9 @@ from .patches import tradeSequence as _
|
||||
from . import hints
|
||||
|
||||
from .patches import bank34
|
||||
from .utils import formatText
|
||||
from ..Options import TrendyGame, Palette
|
||||
from .roomEditor import RoomEditor, Object
|
||||
from .patches.aesthetics import rgb_to_bin, bin_to_rgb
|
||||
|
||||
from .locations.keyLocation import KeyLocation
|
||||
@@ -134,7 +137,7 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
patches.core.fixWrongWarp(rom)
|
||||
patches.core.alwaysAllowSecretBook(rom)
|
||||
patches.core.injectMainLoop(rom)
|
||||
|
||||
|
||||
from ..Options import ShuffleSmallKeys, ShuffleNightmareKeys
|
||||
|
||||
if ap_settings["shuffle_small_keys"] != ShuffleSmallKeys.option_original_dungeon or ap_settings["shuffle_nightmare_keys"] != ShuffleNightmareKeys.option_original_dungeon:
|
||||
@@ -239,7 +242,7 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
patches.core.quickswap(rom, 1)
|
||||
elif settings.quickswap == 'b':
|
||||
patches.core.quickswap(rom, 0)
|
||||
|
||||
|
||||
world_setup = logic.world_setup
|
||||
|
||||
JUNK_HINT = 0.33
|
||||
@@ -263,7 +266,7 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
name = "Your"
|
||||
else:
|
||||
name = f"{multiworld.player_name[location.item.player]}'s"
|
||||
|
||||
|
||||
if isinstance(location, LinksAwakeningLocation):
|
||||
location_name = location.ladxr_item.metadata.name
|
||||
else:
|
||||
@@ -323,7 +326,7 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
|
||||
# TODO: if 0 or 4, 5, remove inaccurate conveyor tiles
|
||||
|
||||
from .roomEditor import RoomEditor, Object
|
||||
|
||||
room_editor = RoomEditor(rom, 0x2A0)
|
||||
|
||||
if ap_settings["trendy_game"] == TrendyGame.option_easy:
|
||||
@@ -352,12 +355,12 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
}
|
||||
def speed():
|
||||
return rnd.randint(*speeds[ap_settings["trendy_game"]])
|
||||
rom.banks[0x4][0x76A0-0x4000] = 0xFF - speed()
|
||||
rom.banks[0x4][0x76A0-0x4000] = 0xFF - speed()
|
||||
rom.banks[0x4][0x76A2-0x4000] = speed()
|
||||
rom.banks[0x4][0x76A6-0x4000] = speed()
|
||||
rom.banks[0x4][0x76A8-0x4000] = 0xFF - speed()
|
||||
if int(ap_settings["trendy_game"]) >= TrendyGame.option_hardest:
|
||||
rom.banks[0x4][0x76A1-0x4000] = 0xFF - speed()
|
||||
rom.banks[0x4][0x76A1-0x4000] = 0xFF - speed()
|
||||
rom.banks[0x4][0x76A3-0x4000] = speed()
|
||||
rom.banks[0x4][0x76A5-0x4000] = speed()
|
||||
rom.banks[0x4][0x76A7-0x4000] = 0xFF - speed()
|
||||
@@ -374,12 +377,14 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
[0x0f, 0x38, 0x0f],
|
||||
[0x30, 0x62, 0x30],
|
||||
[0x8b, 0xac, 0x0f],
|
||||
[0x9b, 0xbc, 0x0f],
|
||||
[0x9b, 0xbc, 0x0f],
|
||||
]
|
||||
for color in gb_colors:
|
||||
for channel in range(3):
|
||||
color[channel] = color[channel] * 31 // 0xbc
|
||||
|
||||
|
||||
if ap_settings["warp_improvements"]:
|
||||
patches.core.addWarpImprovements(rom, ap_settings["additional_warp_points"])
|
||||
|
||||
palette = ap_settings["palette"]
|
||||
if palette != Palette.option_normal:
|
||||
@@ -410,7 +415,7 @@ def generateRom(args, settings, ap_settings, auth, seed_name, logic, rnd=None, m
|
||||
for address in range(start, end, 2):
|
||||
packed = (rom.banks[bank][address + 1] << 8) | rom.banks[bank][address]
|
||||
r,g,b = bin_to_rgb(packed)
|
||||
|
||||
|
||||
# 1 bit
|
||||
if palette == Palette.option_1bit:
|
||||
r &= 0b10000
|
||||
|
||||
@@ -255,8 +255,9 @@ noWrapDown:
|
||||
""" % (type, map, room, x, y)), fill_nop=True)
|
||||
|
||||
# Patch the RAM clear not to delete our custom dialog when we screen transition
|
||||
# This is kind of horrible as it relies on bank 1 being loaded, lol
|
||||
rom.patch(0x01, 0x042C, "C629", "6B7E")
|
||||
rom.patch(0x01, 0x3E6B, 0x3FFF, ASM("""
|
||||
rom.patch(0x01, 0x3E6B, 0x3E7B, ASM("""
|
||||
ld bc, $A0
|
||||
call $29DC
|
||||
ld bc, $1200
|
||||
@@ -537,3 +538,205 @@ OAMData:
|
||||
gfx_low = "\n".join([line.split(" ")[n] for line in tile_graphics.split("\n")[8:]])
|
||||
rom.banks[0x38][0x1400+n*0x20:0x1410+n*0x20] = utils.createTileData(gfx_high)
|
||||
rom.banks[0x38][0x1410+n*0x20:0x1420+n*0x20] = utils.createTileData(gfx_low)
|
||||
|
||||
def addWarpImprovements(rom, extra_warps):
|
||||
# Patch in a warp icon
|
||||
tile = utils.createTileData( \
|
||||
"""11111111
|
||||
10000000
|
||||
10200320
|
||||
10323200
|
||||
10033300
|
||||
10023230
|
||||
10230020
|
||||
10000000""", key="0231")
|
||||
MINIMAP_BASE = 0x3800
|
||||
|
||||
# This is replacing a junk tile never used on the minimap
|
||||
rom.banks[0x2C][MINIMAP_BASE + len(tile) * 0x65 : MINIMAP_BASE + len(tile) * 0x66] = tile
|
||||
|
||||
# Allow using ENTITY_WARP for finding which map sections are warps
|
||||
# Interesting - 3CA0 should be free, but something has pushed all the code forward a byte
|
||||
rom.patch(0x02, 0x3CA1, None, ASM("""
|
||||
ld e, $0F
|
||||
ld d, $00
|
||||
warp_search_loop:
|
||||
; Warp search loop
|
||||
ld hl, $C3A0
|
||||
add hl, de ; $5FE1: $19
|
||||
ld a, [hl] ; $5FE2: $7E
|
||||
cp $61 ; ENTITY_WARP
|
||||
jr nz, search_continue ; if it's not a warp, check the next one
|
||||
ld hl, $C280
|
||||
add hl, de
|
||||
ld a, [hl]
|
||||
and a
|
||||
jr z, search_continue ; if this is despawned, check the next one
|
||||
found:
|
||||
jp $511B ; found
|
||||
search_continue:
|
||||
dec e
|
||||
ld a, e
|
||||
cp $FF
|
||||
jr nz, warp_search_loop
|
||||
|
||||
not_found:
|
||||
jp $512B
|
||||
|
||||
"""))
|
||||
|
||||
# Insert redirect to above code
|
||||
rom.patch(0x02, 0x1109, ASM("""
|
||||
ldh a, [$F6]
|
||||
cp 1
|
||||
|
||||
"""), ASM("""
|
||||
jp $7CA1
|
||||
nop
|
||||
"""))
|
||||
# Leaves some extra bytes behind, if we need more space in 0x02
|
||||
|
||||
# On warp hole, open map instead
|
||||
rom.patch(0x19, 0x1DB9, None, ASM("""
|
||||
ld a, 7 ; Set GAMEPLAY_MAP
|
||||
ld [$DB95], a
|
||||
ld a, 0 ; reset subtype
|
||||
ld [$DB96], a
|
||||
ld a, 1 ; Set flag for using teleport
|
||||
ld [$FFDD], a
|
||||
|
||||
ret
|
||||
"""), fill_nop=True)
|
||||
|
||||
# Patch over some instructions that decided if we are in debug mode holding some
|
||||
# buttons with instead checking for FFDD (why FFDD? It appears to be never used anywhere, so we repurpose it for "is in teleport mode")
|
||||
rom.banks[0x01][0x17B8] = 0xDD
|
||||
rom.banks[0x01][0x17B9] = 0xFF
|
||||
rom.banks[0x01][0x17FD] = 0xDD
|
||||
rom.banks[0x01][0x17FE] = 0xFF
|
||||
|
||||
# If in warp mode, don't allow manual exit
|
||||
rom.patch(0x01, 0x1800, "20021E60", ASM("jp nz, $5818"), fill_nop=True)
|
||||
|
||||
# Allow warp with just B
|
||||
rom.banks[0x01][0x17C0] = 0x20
|
||||
|
||||
# Allow cursor to move over black squares
|
||||
# This allows warping to undiscovered areas - a fine cheat, but needs a check for wOverworldRoomStatus in the warp code
|
||||
CHEAT_WARP_ANYWHERE = False
|
||||
if CHEAT_WARP_ANYWHERE:
|
||||
rom.patch(0x01, 0x1AE8, None, ASM("jp $5AF5"))
|
||||
|
||||
# This disables the arrows around the selection bubble
|
||||
#rom.patch(0x01, 0x1B6F, None, ASM("ret"), fill_nop=True)
|
||||
|
||||
# Fix lag when moving the cursor
|
||||
# One option - just disable the delay code
|
||||
#rom.patch(0x01, 0x1A76, 0x1A76+3, ASM("xor a"), fill_nop=True)
|
||||
#rom.banks[0x01][0x1A7C] = 0
|
||||
# Another option - just remove the animation
|
||||
rom.banks[0x01][0x1B20] = 0
|
||||
rom.banks[0x01][0x1B3B] = 0
|
||||
|
||||
# Patch the icon for all teleports
|
||||
all_warps = [0x01, 0x95, 0x2C, 0xEC]
|
||||
|
||||
|
||||
if extra_warps:
|
||||
# mamu
|
||||
all_warps.append(0x45)
|
||||
# Tweak the flute location
|
||||
rom.banks[0x14][0x0E95] += 0x10
|
||||
rom.banks[0x14][0x0EA3] += 0x01
|
||||
|
||||
mamu_pond = RoomEditor(rom, 0x45)
|
||||
# Remove some tall grass so we can add a warp instead
|
||||
mamu_pond.changeObject(1, 6, 0xE8)
|
||||
mamu_pond.moveObject(1, 6, 3, 5)
|
||||
mamu_pond.addEntity(3, 5, 0x61)
|
||||
|
||||
mamu_pond.store(rom)
|
||||
|
||||
# eagle
|
||||
all_warps.append(0x0F)
|
||||
room = RoomEditor(rom, 0x0F)
|
||||
# Move one cliff edge and change it into a pit
|
||||
room.changeObject(7, 6, 0xE8)
|
||||
room.moveObject(7, 6, 6, 4)
|
||||
|
||||
# Add the warp
|
||||
room.addEntity(6, 4, 0x61)
|
||||
# move the two corners
|
||||
room.moveObject(6, 7, 7, 7)
|
||||
room.moveObject(6, 6, 7, 6)
|
||||
for object in room.objects:
|
||||
# Extend the lower wall
|
||||
if ((object.x == 0 and object.y == 7)
|
||||
# Extend the lower floor
|
||||
or (object.x == 0 and object.y == 6)):
|
||||
room.overlay[object.x + object.count + object.y * 10] = object.type_id
|
||||
object.count += 1
|
||||
room.store(rom)
|
||||
|
||||
for warp in all_warps:
|
||||
# Set icon
|
||||
rom.banks[0x20][0x168B + warp] = 0x55
|
||||
# Set text
|
||||
if not rom.banks[0x01][0x1959 + warp]:
|
||||
rom.banks[0x01][0x1959 + warp] = 0x42
|
||||
# Set palette
|
||||
# rom.banks[0x20][0x178B + 0x95] = 0x1
|
||||
|
||||
# Setup [?!] icon on map and associated text
|
||||
rom.banks[0x01][0x1909 + 0x42] = 0x2B
|
||||
rom.texts[0x02B] = utils.formatText('Warp')
|
||||
|
||||
# call warp function (why not just jmp?!)
|
||||
rom.patch(0x01, 0x17C3, None, ASM("""
|
||||
call $7E7B
|
||||
ret
|
||||
"""))
|
||||
|
||||
# Build a switch statement by hand
|
||||
warp_jump = "".join(f"cp ${hex(warp)[2:]}\njr z, success\n" for warp in all_warps)
|
||||
|
||||
rom.patch(0x01, 0x3E7B, None, ASM(f"""
|
||||
TeleportHandler:
|
||||
|
||||
ld a, [$DBB4] ; Load the current selected tile
|
||||
; TODO: check if actually revealed so we can have free movement
|
||||
; Check cursor against different tiles to see if we are selecting a warp
|
||||
{warp_jump}
|
||||
jr exit
|
||||
|
||||
success:
|
||||
ld a, $0B
|
||||
ld [$DB95], a ; Gameplay type
|
||||
xor a
|
||||
ld [$D401], a ; wWarp0MapCategory
|
||||
ldh [$DD], a ; unset teleport flag(!!!)
|
||||
ld [$D402], a ; wWarp0Map
|
||||
ld a, [$DBB4] ; wDBB4
|
||||
ld [$D403], a ; wWarp0Room
|
||||
|
||||
ld a, $68
|
||||
ld [$D404], a ; wWarp0DestinationX
|
||||
ldh [$98], a ; LinkPositionY
|
||||
ld [$D475], a
|
||||
ld a, $70
|
||||
ld [$D405], a ; wWarp0DestinationY
|
||||
ldh [$99], a ; LinkPositionX
|
||||
ld a, $66
|
||||
ld [$D416], a ; wWarp0PositionTileIndex
|
||||
ld a, $07
|
||||
ld [$DB96], a ; wGameplaySubtype
|
||||
ldh a, [$A2]
|
||||
ld [$DBC8], a
|
||||
call $0C83 ; ApplyMapFadeOutTransition
|
||||
xor a ; $5DF3: $AF
|
||||
ld [$C167], a ; $5DF4: $EA $67 $C1
|
||||
|
||||
exit:
|
||||
ret
|
||||
"""))
|
||||
|
||||
|
||||
@@ -378,7 +378,20 @@ class Palette(Choice):
|
||||
option_greyscale = 3
|
||||
option_pink = 4
|
||||
option_inverted = 5
|
||||
|
||||
|
||||
class WarpImprovements(DefaultOffToggle):
|
||||
"""
|
||||
[On] Adds remake style warp screen to the game. Choose your warp destination on the map after jumping in a portal and press B to select.
|
||||
[Off] No change
|
||||
"""
|
||||
|
||||
class AdditionalWarpPoints(DefaultOffToggle):
|
||||
"""
|
||||
[On] (requires warp improvements) Adds a warp point at Crazy Tracy's house (the Mambo teleport spot) and Eagle's Tower
|
||||
[Off] No change
|
||||
"""
|
||||
|
||||
|
||||
links_awakening_options: typing.Dict[str, typing.Type[Option]] = {
|
||||
'logic': Logic,
|
||||
# 'heartpiece': DefaultOnToggle, # description='Includes heart pieces in the item pool'),
|
||||
@@ -400,6 +413,8 @@ links_awakening_options: typing.Dict[str, typing.Type[Option]] = {
|
||||
# 'bowwow': Bowwow,
|
||||
# 'overworld': Overworld,
|
||||
'link_palette': LinkPalette,
|
||||
'warp_improvements': WarpImprovements,
|
||||
'additional_warp_points': AdditionalWarpPoints,
|
||||
'trendy_game': TrendyGame,
|
||||
'gfxmod': GfxMod,
|
||||
'palette': Palette,
|
||||
|
||||
@@ -182,8 +182,10 @@ class MessengerHardRules(MessengerRules):
|
||||
"Searing Crags Seal - Raining Rocks": lambda state: self.has_vertical(state) or self.can_destroy_projectiles(state),
|
||||
"Searing Crags Seal - Rhythm Rocks": lambda state: self.has_vertical(state) or self.can_destroy_projectiles(state),
|
||||
"Searing Crags - Power Thistle": lambda state: self.has_vertical(state) or self.can_destroy_projectiles(state),
|
||||
"Glacial Peak Seal - Ice Climbers": self.has_vertical,
|
||||
"Glacial Peak Seal - Ice Climbers": lambda state: self.has_vertical(state) or self.can_dboost(state),
|
||||
"Glacial Peak Seal - Projectile Spike Pit": self.true,
|
||||
"Glacial Peak Seal - Glacial Air Swag": lambda state: self.has_windmill(state) or self.has_vertical(state),
|
||||
"Glacial Peak Mega Shard": lambda state: self.has_windmill(state) or self.has_vertical(state),
|
||||
"Cloud Ruins Seal - Ghost Pit": self.true,
|
||||
"Bamboo Creek - Claustro": self.has_wingsuit,
|
||||
"Tower of Time Seal - Lantern Climb": self.has_wingsuit,
|
||||
@@ -201,10 +203,7 @@ class MessengerHardRules(MessengerRules):
|
||||
"Elemental Skylands - Key of Symbiosis": lambda state: self.has_windmill(state) or self.can_dboost(state),
|
||||
"Autumn Hills Seal - Spike Ball Darts": lambda state: (self.has_dart(state) and self.has_windmill(state))
|
||||
or self.has_wingsuit(state),
|
||||
"Glacial Peak Seal - Glacial Air Swag": self.has_windmill,
|
||||
"Glacial Peak Seal - Ice Climbers": lambda state: self.has_wingsuit(state) or self.can_dboost(state),
|
||||
"Underworld Seal - Fireball Wave": lambda state: state.has_all({"Lightfoot Tabi", "Windmill Shuriken"},
|
||||
self.player),
|
||||
"Underworld Seal - Fireball Wave": self.has_windmill,
|
||||
}
|
||||
|
||||
def has_windmill(self, state: CollectionState) -> bool:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from typing import Iterable, List
|
||||
|
||||
from BaseClasses import ItemClassification
|
||||
from . import MessengerTestBase
|
||||
|
||||
@@ -5,6 +7,7 @@ from . import MessengerTestBase
|
||||
class HardLogicTest(MessengerTestBase):
|
||||
options = {
|
||||
"logic_level": "hard",
|
||||
"shuffle_shards": "true",
|
||||
}
|
||||
|
||||
def testVertical(self) -> None:
|
||||
@@ -19,16 +22,20 @@ class HardLogicTest(MessengerTestBase):
|
||||
"Autumn Hills - Climbing Claws", "Autumn Hills - Key of Hope", "Autumn Hills - Leaf Golem",
|
||||
"Autumn Hills Seal - Trip Saws", "Autumn Hills Seal - Double Swing Saws",
|
||||
"Autumn Hills Seal - Spike Ball Swing", "Autumn Hills Seal - Spike Ball Darts",
|
||||
"Autumn Hills Mega Shard", "Hidden Entrance Mega Shard",
|
||||
# forlorn temple
|
||||
"Forlorn Temple - Demon King",
|
||||
"Forlorn Temple Seal - Rocket Maze", "Forlorn Temple Seal - Rocket Sunset",
|
||||
"Sunny Day Mega Shard", "Down Under Mega Shard",
|
||||
# catacombs
|
||||
"Catacombs - Necro", "Catacombs - Ruxxtin's Amulet", "Catacombs - Ruxxtin",
|
||||
"Catacombs Seal - Triple Spike Crushers", "Catacombs Seal - Crusher Gauntlet", "Catacombs Seal - Dirty Pond",
|
||||
"Catacombs Mega Shard",
|
||||
# bamboo creek
|
||||
"Bamboo Creek - Claustro",
|
||||
"Bamboo Creek Seal - Spike Crushers and Doors", "Bamboo Creek Seal - Spike Ball Pits",
|
||||
"Bamboo Creek Seal - Spike Crushers and Doors v2",
|
||||
"Above Entrance Mega Shard", "Abandoned Mega Shard", "Time Loop Mega Shard",
|
||||
# howling grotto
|
||||
"Howling Grotto - Emerald Golem", "Howling Grotto Seal - Crushing Pits", "Howling Grotto Seal - Crushing Pits",
|
||||
# searing crags
|
||||
@@ -36,6 +43,7 @@ class HardLogicTest(MessengerTestBase):
|
||||
# cloud ruins
|
||||
"Cloud Ruins - Acro", "Cloud Ruins Seal - Ghost Pit",
|
||||
"Cloud Ruins Seal - Toothbrush Alley", "Cloud Ruins Seal - Saw Pit", "Cloud Ruins Seal - Money Farm Room",
|
||||
"Cloud Entrance Mega Shard", "Time Warp Mega Shard", "Money Farm Room Mega Shard 1", "Money Farm Room Mega Shard 2",
|
||||
# underworld
|
||||
"Underworld Seal - Rising Fanta", "Underworld Seal - Sharp and Windy Climb",
|
||||
# elemental skylands
|
||||
@@ -73,6 +81,18 @@ class HardLogicTest(MessengerTestBase):
|
||||
item = self.get_item_by_name("Rope Dart")
|
||||
self.collect(item)
|
||||
self.assertTrue(self.can_reach_location(special_loc))
|
||||
|
||||
def testGlacial(self) -> None:
|
||||
"""Test Glacial Peak locations."""
|
||||
self.assertAccessDependency(["Glacial Peak Seal - Ice Climbers"],
|
||||
[["Second Wind", "Meditation"], ["Rope Dart"], ["Wingsuit"]],
|
||||
True)
|
||||
self.assertAccessDependency(["Glacial Peak Seal - Projectile Spike Pit"],
|
||||
[["Strike of the Ninja"], ["Windmill Shuriken"], ["Rope Dart"], ["Wingsuit"]],
|
||||
True)
|
||||
self.assertAccessDependency(["Glacial Peak Seal - Glacial Air Swag", "Glacial Peak Mega Shard"],
|
||||
[["Windmill Shuriken"], ["Wingsuit"], ["Rope Dart"]],
|
||||
True)
|
||||
|
||||
|
||||
class NoLogicTest(MessengerTestBase):
|
||||
|
||||
@@ -181,6 +181,7 @@ class Item:
|
||||
keycard = re.compile("^Card")
|
||||
smMap = re.compile("^SmMap")
|
||||
|
||||
def IsNameDungeonItem(item_name): return Item.dungeon.match(item_name)
|
||||
def IsDungeonItem(self): return self.dungeon.match(self.Type.name)
|
||||
def IsBigKey(self): return self.bigKey.match(self.Type.name)
|
||||
def IsKey(self): return self.key.match(self.Type.name)
|
||||
|
||||
@@ -221,7 +221,9 @@ class SMZ3World(World):
|
||||
if (self.smz3World.Config.Keysanity):
|
||||
progressionItems = self.progression + self.dungeon + self.keyCardsItems + self.SmMapsItems
|
||||
else:
|
||||
progressionItems = self.progression
|
||||
progressionItems = self.progression
|
||||
# Dungeons items here are not in the itempool and will be prefilled locally so they must stay local
|
||||
self.multiworld.non_local_items[self.player].value -= frozenset(item_name for item_name in self.item_names if TotalSMZ3Item.Item.IsNameDungeonItem(item_name))
|
||||
for item in self.keyCardsItems:
|
||||
self.multiworld.push_precollected(SMZ3Item(item.Type.name, ItemClassification.filler, item.Type, self.item_name_to_id[item.Type.name], self.player, item))
|
||||
|
||||
@@ -548,11 +550,8 @@ class SMZ3World(World):
|
||||
|
||||
def JunkFillGT(self, factor):
|
||||
poolLength = len(self.multiworld.itempool)
|
||||
playerGroups = self.multiworld.get_player_groups(self.player)
|
||||
playerGroups.add(self.player)
|
||||
junkPoolIdx = [i for i in range(0, poolLength)
|
||||
if self.multiworld.itempool[i].classification in (ItemClassification.filler, ItemClassification.trap) and
|
||||
self.multiworld.itempool[i].player in playerGroups]
|
||||
if self.multiworld.itempool[i].classification in (ItemClassification.filler, ItemClassification.trap)]
|
||||
toRemove = []
|
||||
for loc in self.locations.values():
|
||||
# commenting this for now since doing a partial GT pre fill would allow for non SMZ3 progression in GT
|
||||
@@ -563,6 +562,7 @@ class SMZ3World(World):
|
||||
poolLength = len(junkPoolIdx)
|
||||
# start looking at a random starting index and loop at start if no match found
|
||||
start = self.multiworld.random.randint(0, poolLength)
|
||||
itemFromPool = None
|
||||
for off in range(0, poolLength):
|
||||
i = (start + off) % poolLength
|
||||
candidate = self.multiworld.itempool[junkPoolIdx[i]]
|
||||
@@ -570,6 +570,7 @@ class SMZ3World(World):
|
||||
itemFromPool = candidate
|
||||
toRemove.append(junkPoolIdx[i])
|
||||
break
|
||||
assert itemFromPool is not None, "Can't find anymore item(s) to pre fill GT"
|
||||
self.multiworld.push_item(loc, itemFromPool, False)
|
||||
loc.event = False
|
||||
toRemove.sort(reverse = True)
|
||||
|
||||
@@ -10,7 +10,6 @@ import logging
|
||||
|
||||
from BaseClasses import ItemClassification, LocationProgressType, \
|
||||
MultiWorld, Item, CollectionState, Entrance, Tutorial
|
||||
from .config import detect_test
|
||||
from .logic import cs_to_zz_locs
|
||||
from .region import ZillionLocation, ZillionRegion
|
||||
from .options import ZillionStartChar, zillion_options, validate
|
||||
@@ -25,6 +24,7 @@ from zilliandomizer.system import System
|
||||
from zilliandomizer.logic_components.items import RESCUE, items as zz_items, Item as ZzItem
|
||||
from zilliandomizer.logic_components.locations import Location as ZzLocation, Req
|
||||
from zilliandomizer.options import Chars
|
||||
from zilliandomizer.patch import detect_test
|
||||
|
||||
from ..AutoWorld import World, WebWorld
|
||||
|
||||
|
||||
@@ -2,20 +2,3 @@ import os
|
||||
|
||||
base_id = 8675309
|
||||
zillion_map = os.path.join(os.path.dirname(__file__), "empty-zillion-map-row-col-labels-281.png")
|
||||
|
||||
|
||||
def detect_test() -> bool:
|
||||
"""
|
||||
Parts of generation that are in unit tests need the rom.
|
||||
This is to detect whether we are running unit tests
|
||||
so we can work around the need for the rom.
|
||||
"""
|
||||
import __main__
|
||||
try:
|
||||
if "test" in __main__.__file__:
|
||||
return True
|
||||
except AttributeError:
|
||||
# In some environments, __main__ doesn't have __file__
|
||||
# We'll assume that's not unit tests.
|
||||
pass
|
||||
return False
|
||||
|
||||
@@ -1 +1 @@
|
||||
zilliandomizer @ git+https://github.com/beauxq/zilliandomizer@4b27d115269db25fe73b0471b73495f41df1323c#0.5.3
|
||||
zilliandomizer @ git+https://github.com/beauxq/zilliandomizer@d7122bcbeda40da5db26d60fad06246a1331706f#0.5.4
|
||||
|
||||
Reference in New Issue
Block a user