diff --git a/.github/labeler.yml b/.github/labeler.yml
new file mode 100644
index 000000000000..2743104f410e
--- /dev/null
+++ b/.github/labeler.yml
@@ -0,0 +1,31 @@
+'is: documentation':
+- changed-files:
+ - all-globs-to-all-files: '{**/docs/**,**/README.md}'
+
+'affects: webhost':
+- changed-files:
+ - all-globs-to-any-file: 'WebHost.py'
+ - all-globs-to-any-file: 'WebHostLib/**/*'
+
+'affects: core':
+- changed-files:
+ - all-globs-to-any-file:
+ - '!*Client.py'
+ - '!README.md'
+ - '!LICENSE'
+ - '!*.yml'
+ - '!.gitignore'
+ - '!**/docs/**'
+ - '!typings/kivy/**'
+ - '!test/**'
+ - '!data/**'
+ - '!.run/**'
+ - '!.github/**'
+ - '!worlds_disabled/**'
+ - '!worlds/**'
+ - '!WebHost.py'
+ - '!WebHostLib/**'
+ - any-glob-to-any-file: # exceptions to the above rules of "stuff that isn't core"
+ - 'worlds/generic/**/*.py'
+ - 'worlds/*.py'
+ - 'CommonClient.py'
diff --git a/.github/workflows/label-pull-requests.yml b/.github/workflows/label-pull-requests.yml
new file mode 100644
index 000000000000..42881aa49d9b
--- /dev/null
+++ b/.github/workflows/label-pull-requests.yml
@@ -0,0 +1,44 @@
+name: Label Pull Request
+on:
+ pull_request_target:
+ types: ['opened', 'reopened', 'synchronize', 'ready_for_review', 'converted_to_draft', 'closed']
+ branches: ['main']
+permissions:
+ contents: read
+ pull-requests: write
+
+jobs:
+ labeler:
+ name: 'Apply content-based labels'
+ if: github.event.action == 'opened' || github.event.action == 'reopened' || github.event.action == 'synchronize'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/labeler@v5
+ with:
+ sync-labels: true
+ peer_review:
+ name: 'Apply peer review label'
+ if: >-
+ (github.event.action == 'opened' || github.event.action == 'reopened' ||
+ github.event.action == 'ready_for_review') && !github.event.pull_request.draft
+ runs-on: ubuntu-latest
+ steps:
+ - name: 'Add label'
+ run: "gh pr edit \"$PR_URL\" --add-label 'waiting-on: peer-review'"
+ env:
+ PR_URL: ${{ github.event.pull_request.html_url }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ unblock_draft_prs:
+ name: 'Remove waiting-on labels'
+ if: github.event.action == 'converted_to_draft' || github.event.action == 'closed'
+ runs-on: ubuntu-latest
+ steps:
+ - name: 'Remove labels'
+ run: |-
+ gh pr edit "$PR_URL" --remove-label 'waiting-on: peer-review' \
+ --remove-label 'waiting-on: core-review' \
+ --remove-label 'waiting-on: world-maintainer' \
+ --remove-label 'waiting-on: author'
+ env:
+ PR_URL: ${{ github.event.pull_request.html_url }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/scan-build.yml b/.github/workflows/scan-build.yml
new file mode 100644
index 000000000000..5234d862b4d3
--- /dev/null
+++ b/.github/workflows/scan-build.yml
@@ -0,0 +1,65 @@
+name: Native Code Static Analysis
+
+on:
+ push:
+ paths:
+ - '**.c'
+ - '**.cc'
+ - '**.cpp'
+ - '**.cxx'
+ - '**.h'
+ - '**.hh'
+ - '**.hpp'
+ - '**.pyx'
+ - 'setup.py'
+ - 'requirements.txt'
+ - '.github/workflows/scan-build.yml'
+ pull_request:
+ paths:
+ - '**.c'
+ - '**.cc'
+ - '**.cpp'
+ - '**.cxx'
+ - '**.h'
+ - '**.hh'
+ - '**.hpp'
+ - '**.pyx'
+ - 'setup.py'
+ - 'requirements.txt'
+ - '.github/workflows/scan-build.yml'
+
+jobs:
+ scan-build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+ - name: Install newer Clang
+ run: |
+ wget https://apt.llvm.org/llvm.sh
+ chmod +x ./llvm.sh
+ sudo ./llvm.sh 17
+ - name: Install scan-build command
+ run: |
+ sudo apt install clang-tools-17
+ - name: Get a recent python
+ uses: actions/setup-python@v5
+ with:
+ python-version: '3.11'
+ - name: Install dependencies
+ run: |
+ python -m venv venv
+ source venv/bin/activate
+ python -m pip install --upgrade pip -r requirements.txt
+ - name: scan-build
+ run: |
+ source venv/bin/activate
+ scan-build-17 --status-bugs -o scan-build-reports -disable-checker deadcode.DeadStores python setup.py build -y
+ - name: Store report
+ if: failure()
+ uses: actions/upload-artifact@v4
+ with:
+ name: scan-build-reports
+ path: scan-build-reports
diff --git a/BaseClasses.py b/BaseClasses.py
index 38598d42d999..2be9a9820d07 100644
--- a/BaseClasses.py
+++ b/BaseClasses.py
@@ -18,11 +18,14 @@
import Options
import Utils
+if typing.TYPE_CHECKING:
+ from worlds import AutoWorld
+
class Group(TypedDict, total=False):
name: str
game: str
- world: auto_world
+ world: "AutoWorld.World"
players: Set[int]
item_pool: Set[str]
replacement_items: Dict[int, Optional[str]]
@@ -55,7 +58,7 @@ class MultiWorld():
plando_texts: List[Dict[str, str]]
plando_items: List[List[Dict[str, Any]]]
plando_connections: List
- worlds: Dict[int, auto_world]
+ worlds: Dict[int, "AutoWorld.World"]
groups: Dict[int, Group]
regions: RegionManager
itempool: List[Item]
@@ -107,10 +110,14 @@ def __iadd__(self, other: Iterable[Region]):
return self
def append(self, region: Region):
+ assert region.name not in self.region_cache[region.player], \
+ f"{region.name} already exists in region cache."
self.region_cache[region.player][region.name] = region
def extend(self, regions: Iterable[Region]):
for region in regions:
+ assert region.name not in self.region_cache[region.player], \
+ f"{region.name} already exists in region cache."
self.region_cache[region.player][region.name] = region
def add_group(self, new_id: int):
@@ -156,11 +163,11 @@ def __init__(self, players: int):
self.fix_trock_doors = self.AttributeProxy(
lambda player: self.shuffle[player] != 'vanilla' or self.mode[player] == 'inverted')
self.fix_skullwoods_exit = self.AttributeProxy(
- lambda player: self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'])
+ lambda player: self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeons_simple'])
self.fix_palaceofdarkness_exit = self.AttributeProxy(
- lambda player: self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'])
+ lambda player: self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeons_simple'])
self.fix_trock_exit = self.AttributeProxy(
- lambda player: self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeonssimple'])
+ lambda player: self.shuffle[player] not in ['vanilla', 'simple', 'restricted', 'dungeons_simple'])
for player in range(1, players + 1):
def set_player_attr(attr, val):
@@ -219,6 +226,8 @@ def get_all_ids(self) -> Tuple[int, ...]:
def add_group(self, name: str, game: str, players: Set[int] = frozenset()) -> Tuple[int, Group]:
"""Create a group with name and return the assigned player ID and group.
If a group of this name already exists, the set of players is extended instead of creating a new one."""
+ from worlds import AutoWorld
+
for group_id, group in self.groups.items():
if group["name"] == name:
group["players"] |= players
@@ -253,6 +262,8 @@ def set_seed(self, seed: Optional[int] = None, secure: bool = False, name: Optio
def set_options(self, args: Namespace) -> None:
# TODO - remove this section once all worlds use options dataclasses
+ from worlds import AutoWorld
+
all_keys: Set[str] = {key for player in self.player_ids for key in
AutoWorld.AutoWorldRegister.world_types[self.game[player]].options_dataclass.type_hints}
for option_key in all_keys:
@@ -270,6 +281,8 @@ def set_options(self, args: Namespace) -> None:
for option_key in options_dataclass.type_hints})
def set_item_links(self):
+ from worlds import AutoWorld
+
item_links = {}
replacement_prio = [False, True, None]
for player in self.player_ids:
@@ -572,9 +585,10 @@ def fulfills_accessibility(self, state: Optional[CollectionState] = None):
def location_condition(location: Location):
"""Determine if this location has to be accessible, location is already filtered by location_relevant"""
- if location.player in players["minimal"]:
- return False
- return True
+ if location.player in players["locations"] or (location.item and location.item.player not in
+ players["minimal"]):
+ return True
+ return False
def location_relevant(location: Location):
"""Determine if this location is relevant to sweep."""
@@ -703,14 +717,23 @@ def can_reach(self,
assert isinstance(player, int), "can_reach: player is required if spot is str"
# try to resolve a name
if resolution_hint == 'Location':
- spot = self.multiworld.get_location(spot, player)
+ return self.can_reach_location(spot, player)
elif resolution_hint == 'Entrance':
- spot = self.multiworld.get_entrance(spot, player)
+ return self.can_reach_entrance(spot, player)
else:
# default to Region
- spot = self.multiworld.get_region(spot, player)
+ return self.can_reach_region(spot, player)
return spot.can_reach(self)
+ def can_reach_location(self, spot: str, player: int) -> bool:
+ return self.multiworld.get_location(spot, player).can_reach(self)
+
+ def can_reach_entrance(self, spot: str, player: int) -> bool:
+ return self.multiworld.get_entrance(spot, player).can_reach(self)
+
+ def can_reach_region(self, spot: str, player: int) -> bool:
+ return self.multiworld.get_region(spot, player).can_reach(self)
+
def sweep_for_events(self, key_only: bool = False, locations: Optional[Iterable[Location]] = None) -> None:
if locations is None:
locations = self.multiworld.get_filled_locations()
@@ -823,8 +846,8 @@ def __repr__(self):
return self.__str__()
def __str__(self):
- world = self.parent_region.multiworld if self.parent_region else None
- return world.get_name_string_for_object(self) if world else f'{self.name} (Player {self.player})'
+ multiworld = self.parent_region.multiworld if self.parent_region else None
+ return multiworld.get_name_string_for_object(self) if multiworld else f'{self.name} (Player {self.player})'
class Region:
@@ -867,6 +890,8 @@ def __delitem__(self, index: int) -> None:
del(self.region_manager.location_cache[location.player][location.name])
def insert(self, index: int, value: Location) -> None:
+ assert value.name not in self.region_manager.location_cache[value.player], \
+ f"{value.name} already exists in the location cache."
self._list.insert(index, value)
self.region_manager.location_cache[value.player][value.name] = value
@@ -877,6 +902,8 @@ def __delitem__(self, index: int) -> None:
del(self.region_manager.entrance_cache[entrance.player][entrance.name])
def insert(self, index: int, value: Entrance) -> None:
+ assert value.name not in self.region_manager.entrance_cache[value.player], \
+ f"{value.name} already exists in the entrance cache."
self._list.insert(index, value)
self.region_manager.entrance_cache[value.player][value.name] = value
@@ -1040,8 +1067,8 @@ def __repr__(self):
return self.__str__()
def __str__(self):
- world = self.parent_region.multiworld if self.parent_region and self.parent_region.multiworld else None
- return world.get_name_string_for_object(self) if world else f'{self.name} (Player {self.player})'
+ multiworld = self.parent_region.multiworld if self.parent_region and self.parent_region.multiworld else None
+ return multiworld.get_name_string_for_object(self) if multiworld else f'{self.name} (Player {self.player})'
def __hash__(self):
return hash((self.name, self.player))
@@ -1175,7 +1202,7 @@ def set_entrance(self, entrance: str, exit_: str, direction: str, player: int) -
{"player": player, "entrance": entrance, "exit": exit_, "direction": direction}
def create_playthrough(self, create_paths: bool = True) -> None:
- """Destructive to the world while it is run, damage gets repaired afterwards."""
+ """Destructive to the multiworld while it is run, damage gets repaired afterwards."""
from itertools import chain
# get locations containing progress items
multiworld = self.multiworld
@@ -1262,12 +1289,12 @@ def create_playthrough(self, create_paths: bool = True) -> None:
for location in sphere:
state.collect(location.item, True, location)
- required_locations -= sphere
-
collection_spheres.append(sphere)
logging.debug('Calculated final sphere %i, containing %i of %i progress items.', len(collection_spheres),
len(sphere), len(required_locations))
+
+ required_locations -= sphere
if not sphere:
raise RuntimeError(f'Not all required items reachable. Unreachable locations: {required_locations}')
@@ -1326,6 +1353,8 @@ def get_path(state: CollectionState, region: Region) -> List[Union[Tuple[str, st
get_path(state, multiworld.get_region('Inverted Big Bomb Shop', player))
def to_file(self, filename: str) -> None:
+ from worlds import AutoWorld
+
def write_option(option_key: str, option_obj: Options.AssembleOptions) -> None:
res = getattr(self.multiworld.worlds[player].options, option_key)
display_name = getattr(option_obj, "display_name", option_key)
@@ -1449,8 +1478,3 @@ def get_seed(seed: Optional[int] = None) -> int:
random.seed(None)
return random.randint(0, pow(10, seeddigits) - 1)
return seed
-
-
-from worlds import AutoWorld
-
-auto_world = AutoWorld.World
diff --git a/CommonClient.py b/CommonClient.py
index 736cf4922f40..c75ca3fd806e 100644
--- a/CommonClient.py
+++ b/CommonClient.py
@@ -941,4 +941,5 @@ async def main(args):
if __name__ == '__main__':
+ logging.getLogger().setLevel(logging.INFO) # force log-level to work around log level resetting to WARNING
run_as_textclient()
diff --git a/Fill.py b/Fill.py
index 525d27d3388e..ae44710469e4 100644
--- a/Fill.py
+++ b/Fill.py
@@ -27,12 +27,12 @@ def sweep_from_pool(base_state: CollectionState, itempool: typing.Sequence[Item]
return new_state
-def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations: typing.List[Location],
+def fill_restrictive(multiworld: MultiWorld, base_state: CollectionState, locations: typing.List[Location],
item_pool: typing.List[Item], single_player_placement: bool = False, lock: bool = False,
swap: bool = True, on_place: typing.Optional[typing.Callable[[Location], None]] = None,
allow_partial: bool = False, allow_excluded: bool = False, name: str = "Unknown") -> None:
"""
- :param world: Multiworld to be filled.
+ :param multiworld: Multiworld to be filled.
:param base_state: State assumed before fill.
:param locations: Locations to be filled with item_pool
:param item_pool: Items to fill into the locations
@@ -68,7 +68,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations:
maximum_exploration_state = sweep_from_pool(
base_state, item_pool + unplaced_items)
- has_beaten_game = world.has_beaten_game(maximum_exploration_state)
+ has_beaten_game = multiworld.has_beaten_game(maximum_exploration_state)
while items_to_place:
# if we have run out of locations to fill,break out of this loop
@@ -80,8 +80,8 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations:
spot_to_fill: typing.Optional[Location] = None
# if minimal accessibility, only check whether location is reachable if game not beatable
- if world.worlds[item_to_place.player].options.accessibility == Accessibility.option_minimal:
- perform_access_check = not world.has_beaten_game(maximum_exploration_state,
+ if multiworld.worlds[item_to_place.player].options.accessibility == Accessibility.option_minimal:
+ perform_access_check = not multiworld.has_beaten_game(maximum_exploration_state,
item_to_place.player) \
if single_player_placement else not has_beaten_game
else:
@@ -122,11 +122,11 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations:
# Verify placing this item won't reduce available locations, which would be a useless swap.
prev_state = swap_state.copy()
prev_loc_count = len(
- world.get_reachable_locations(prev_state))
+ multiworld.get_reachable_locations(prev_state))
swap_state.collect(item_to_place, True)
new_loc_count = len(
- world.get_reachable_locations(swap_state))
+ multiworld.get_reachable_locations(swap_state))
if new_loc_count >= prev_loc_count:
# Add this item to the existing placement, and
@@ -156,7 +156,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations:
else:
unplaced_items.append(item_to_place)
continue
- world.push_item(spot_to_fill, item_to_place, False)
+ multiworld.push_item(spot_to_fill, item_to_place, False)
spot_to_fill.locked = lock
placements.append(spot_to_fill)
spot_to_fill.event = item_to_place.advancement
@@ -173,7 +173,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations:
# validate all placements and remove invalid ones
state = sweep_from_pool(base_state, [])
for placement in placements:
- if world.accessibility[placement.item.player] != "minimal" and not placement.can_reach(state):
+ if multiworld.worlds[placement.item.player].options.accessibility != "minimal" and not placement.can_reach(state):
placement.item.location = None
unplaced_items.append(placement.item)
placement.item = None
@@ -188,7 +188,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations:
if excluded_locations:
for location in excluded_locations:
location.progress_type = location.progress_type.DEFAULT
- fill_restrictive(world, base_state, excluded_locations, unplaced_items, single_player_placement, lock,
+ fill_restrictive(multiworld, base_state, excluded_locations, unplaced_items, single_player_placement, lock,
swap, on_place, allow_partial, False)
for location in excluded_locations:
if not location.item:
@@ -196,7 +196,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations:
if not allow_partial and len(unplaced_items) > 0 and len(locations) > 0:
# There are leftover unplaceable items and locations that won't accept them
- if world.can_beat_game():
+ if multiworld.can_beat_game():
logging.warning(
f'Not all items placed. Game beatable anyway. (Could not place {unplaced_items})')
else:
@@ -206,7 +206,7 @@ def fill_restrictive(world: MultiWorld, base_state: CollectionState, locations:
item_pool.extend(unplaced_items)
-def remaining_fill(world: MultiWorld,
+def remaining_fill(multiworld: MultiWorld,
locations: typing.List[Location],
itempool: typing.List[Item]) -> None:
unplaced_items: typing.List[Item] = []
@@ -261,7 +261,7 @@ def remaining_fill(world: MultiWorld,
unplaced_items.append(item_to_place)
continue
- world.push_item(spot_to_fill, item_to_place, False)
+ multiworld.push_item(spot_to_fill, item_to_place, False)
placements.append(spot_to_fill)
placed += 1
if not placed % 1000:
@@ -278,19 +278,19 @@ def remaining_fill(world: MultiWorld,
itempool.extend(unplaced_items)
-def fast_fill(world: MultiWorld,
+def fast_fill(multiworld: MultiWorld,
item_pool: typing.List[Item],
fill_locations: typing.List[Location]) -> typing.Tuple[typing.List[Item], typing.List[Location]]:
placing = min(len(item_pool), len(fill_locations))
for item, location in zip(item_pool, fill_locations):
- world.push_item(location, item, False)
+ multiworld.push_item(location, item, False)
return item_pool[placing:], fill_locations[placing:]
-def accessibility_corrections(world: MultiWorld, state: CollectionState, locations, pool=[]):
+def accessibility_corrections(multiworld: MultiWorld, state: CollectionState, locations, pool=[]):
maximum_exploration_state = sweep_from_pool(state, pool)
- minimal_players = {player for player in world.player_ids if world.worlds[player].options.accessibility == "minimal"}
- unreachable_locations = [location for location in world.get_locations() if location.player in minimal_players and
+ minimal_players = {player for player in multiworld.player_ids if multiworld.worlds[player].options.accessibility == "minimal"}
+ unreachable_locations = [location for location in multiworld.get_locations() if location.player in minimal_players and
not location.can_reach(maximum_exploration_state)]
for location in unreachable_locations:
if (location.item is not None and location.item.advancement and location.address is not None and not
@@ -304,36 +304,36 @@ def accessibility_corrections(world: MultiWorld, state: CollectionState, locatio
locations.append(location)
if pool and locations:
locations.sort(key=lambda loc: loc.progress_type != LocationProgressType.PRIORITY)
- fill_restrictive(world, state, locations, pool, name="Accessibility Corrections")
+ fill_restrictive(multiworld, state, locations, pool, name="Accessibility Corrections")
-def inaccessible_location_rules(world: MultiWorld, state: CollectionState, locations):
+def inaccessible_location_rules(multiworld: MultiWorld, state: CollectionState, locations):
maximum_exploration_state = sweep_from_pool(state)
unreachable_locations = [location for location in locations if not location.can_reach(maximum_exploration_state)]
if unreachable_locations:
def forbid_important_item_rule(item: Item):
- return not ((item.classification & 0b0011) and world.worlds[item.player].options.accessibility != 'minimal')
+ return not ((item.classification & 0b0011) and multiworld.worlds[item.player].options.accessibility != 'minimal')
for location in unreachable_locations:
add_item_rule(location, forbid_important_item_rule)
-def distribute_early_items(world: MultiWorld,
+def distribute_early_items(multiworld: MultiWorld,
fill_locations: typing.List[Location],
itempool: typing.List[Item]) -> typing.Tuple[typing.List[Location], typing.List[Item]]:
""" returns new fill_locations and itempool """
early_items_count: typing.Dict[typing.Tuple[str, int], typing.List[int]] = {}
- for player in world.player_ids:
- items = itertools.chain(world.early_items[player], world.local_early_items[player])
+ for player in multiworld.player_ids:
+ items = itertools.chain(multiworld.early_items[player], multiworld.local_early_items[player])
for item in items:
- early_items_count[item, player] = [world.early_items[player].get(item, 0),
- world.local_early_items[player].get(item, 0)]
+ early_items_count[item, player] = [multiworld.early_items[player].get(item, 0),
+ multiworld.local_early_items[player].get(item, 0)]
if early_items_count:
early_locations: typing.List[Location] = []
early_priority_locations: typing.List[Location] = []
loc_indexes_to_remove: typing.Set[int] = set()
- base_state = world.state.copy()
- base_state.sweep_for_events(locations=(loc for loc in world.get_filled_locations() if loc.address is None))
+ base_state = multiworld.state.copy()
+ base_state.sweep_for_events(locations=(loc for loc in multiworld.get_filled_locations() if loc.address is None))
for i, loc in enumerate(fill_locations):
if loc.can_reach(base_state):
if loc.progress_type == LocationProgressType.PRIORITY:
@@ -345,8 +345,8 @@ def distribute_early_items(world: MultiWorld,
early_prog_items: typing.List[Item] = []
early_rest_items: typing.List[Item] = []
- early_local_prog_items: typing.Dict[int, typing.List[Item]] = {player: [] for player in world.player_ids}
- early_local_rest_items: typing.Dict[int, typing.List[Item]] = {player: [] for player in world.player_ids}
+ early_local_prog_items: typing.Dict[int, typing.List[Item]] = {player: [] for player in multiworld.player_ids}
+ early_local_rest_items: typing.Dict[int, typing.List[Item]] = {player: [] for player in multiworld.player_ids}
item_indexes_to_remove: typing.Set[int] = set()
for i, item in enumerate(itempool):
if (item.name, item.player) in early_items_count:
@@ -370,28 +370,28 @@ def distribute_early_items(world: MultiWorld,
if len(early_items_count) == 0:
break
itempool = [item for i, item in enumerate(itempool) if i not in item_indexes_to_remove]
- for player in world.player_ids:
+ for player in multiworld.player_ids:
player_local = early_local_rest_items[player]
- fill_restrictive(world, base_state,
+ fill_restrictive(multiworld, base_state,
[loc for loc in early_locations if loc.player == player],
player_local, lock=True, allow_partial=True, name=f"Local Early Items P{player}")
if player_local:
logging.warning(f"Could not fulfill rules of early items: {player_local}")
early_rest_items.extend(early_local_rest_items[player])
early_locations = [loc for loc in early_locations if not loc.item]
- fill_restrictive(world, base_state, early_locations, early_rest_items, lock=True, allow_partial=True,
+ fill_restrictive(multiworld, base_state, early_locations, early_rest_items, lock=True, allow_partial=True,
name="Early Items")
early_locations += early_priority_locations
- for player in world.player_ids:
+ for player in multiworld.player_ids:
player_local = early_local_prog_items[player]
- fill_restrictive(world, base_state,
+ fill_restrictive(multiworld, base_state,
[loc for loc in early_locations if loc.player == player],
player_local, lock=True, allow_partial=True, name=f"Local Early Progression P{player}")
if player_local:
logging.warning(f"Could not fulfill rules of early items: {player_local}")
early_prog_items.extend(player_local)
early_locations = [loc for loc in early_locations if not loc.item]
- fill_restrictive(world, base_state, early_locations, early_prog_items, lock=True, allow_partial=True,
+ fill_restrictive(multiworld, base_state, early_locations, early_prog_items, lock=True, allow_partial=True,
name="Early Progression")
unplaced_early_items = early_rest_items + early_prog_items
if unplaced_early_items:
@@ -400,18 +400,18 @@ def distribute_early_items(world: MultiWorld,
itempool += unplaced_early_items
fill_locations.extend(early_locations)
- world.random.shuffle(fill_locations)
+ multiworld.random.shuffle(fill_locations)
return fill_locations, itempool
-def distribute_items_restrictive(world: MultiWorld) -> None:
- fill_locations = sorted(world.get_unfilled_locations())
- world.random.shuffle(fill_locations)
+def distribute_items_restrictive(multiworld: MultiWorld) -> None:
+ fill_locations = sorted(multiworld.get_unfilled_locations())
+ multiworld.random.shuffle(fill_locations)
# get items to distribute
- itempool = sorted(world.itempool)
- world.random.shuffle(itempool)
+ itempool = sorted(multiworld.itempool)
+ multiworld.random.shuffle(itempool)
- fill_locations, itempool = distribute_early_items(world, fill_locations, itempool)
+ fill_locations, itempool = distribute_early_items(multiworld, fill_locations, itempool)
progitempool: typing.List[Item] = []
usefulitempool: typing.List[Item] = []
@@ -425,7 +425,7 @@ def distribute_items_restrictive(world: MultiWorld) -> None:
else:
filleritempool.append(item)
- call_all(world, "fill_hook", progitempool, usefulitempool, filleritempool, fill_locations)
+ call_all(multiworld, "fill_hook", progitempool, usefulitempool, filleritempool, fill_locations)
locations: typing.Dict[LocationProgressType, typing.List[Location]] = {
loc_type: [] for loc_type in LocationProgressType}
@@ -446,34 +446,34 @@ def mark_for_locking(location: Location):
if prioritylocations:
# "priority fill"
- fill_restrictive(world, world.state, prioritylocations, progitempool, swap=False, on_place=mark_for_locking,
+ fill_restrictive(multiworld, multiworld.state, prioritylocations, progitempool, swap=False, on_place=mark_for_locking,
name="Priority")
- accessibility_corrections(world, world.state, prioritylocations, progitempool)
+ accessibility_corrections(multiworld, multiworld.state, prioritylocations, progitempool)
defaultlocations = prioritylocations + defaultlocations
if progitempool:
# "advancement/progression fill"
- fill_restrictive(world, world.state, defaultlocations, progitempool, name="Progression")
+ fill_restrictive(multiworld, multiworld.state, defaultlocations, progitempool, name="Progression")
if progitempool:
raise FillError(
f'Not enough locations for progress items. There are {len(progitempool)} more items than locations')
- accessibility_corrections(world, world.state, defaultlocations)
+ accessibility_corrections(multiworld, multiworld.state, defaultlocations)
for location in lock_later:
if location.item:
location.locked = True
del mark_for_locking, lock_later
- inaccessible_location_rules(world, world.state, defaultlocations)
+ inaccessible_location_rules(multiworld, multiworld.state, defaultlocations)
- remaining_fill(world, excludedlocations, filleritempool)
+ remaining_fill(multiworld, excludedlocations, filleritempool)
if excludedlocations:
raise FillError(
f"Not enough filler items for excluded locations. There are {len(excludedlocations)} more locations than items")
restitempool = filleritempool + usefulitempool
- remaining_fill(world, defaultlocations, restitempool)
+ remaining_fill(multiworld, defaultlocations, restitempool)
unplaced = restitempool
unfilled = defaultlocations
@@ -481,40 +481,40 @@ def mark_for_locking(location: Location):
if unplaced or unfilled:
logging.warning(
f'Unplaced items({len(unplaced)}): {unplaced} - Unfilled Locations({len(unfilled)}): {unfilled}')
- items_counter = Counter(location.item.player for location in world.get_locations() if location.item)
- locations_counter = Counter(location.player for location in world.get_locations())
+ items_counter = Counter(location.item.player for location in multiworld.get_locations() if location.item)
+ locations_counter = Counter(location.player for location in multiworld.get_locations())
items_counter.update(item.player for item in unplaced)
locations_counter.update(location.player for location in unfilled)
print_data = {"items": items_counter, "locations": locations_counter}
logging.info(f'Per-Player counts: {print_data})')
-def flood_items(world: MultiWorld) -> None:
+def flood_items(multiworld: MultiWorld) -> None:
# get items to distribute
- world.random.shuffle(world.itempool)
- itempool = world.itempool
+ multiworld.random.shuffle(multiworld.itempool)
+ itempool = multiworld.itempool
progress_done = False
# sweep once to pick up preplaced items
- world.state.sweep_for_events()
+ multiworld.state.sweep_for_events()
- # fill world from top of itempool while we can
+ # fill multiworld from top of itempool while we can
while not progress_done:
- location_list = world.get_unfilled_locations()
- world.random.shuffle(location_list)
+ location_list = multiworld.get_unfilled_locations()
+ multiworld.random.shuffle(location_list)
spot_to_fill = None
for location in location_list:
- if location.can_fill(world.state, itempool[0]):
+ if location.can_fill(multiworld.state, itempool[0]):
spot_to_fill = location
break
if spot_to_fill:
item = itempool.pop(0)
- world.push_item(spot_to_fill, item, True)
+ multiworld.push_item(spot_to_fill, item, True)
continue
# ran out of spots, check if we need to step in and correct things
- if len(world.get_reachable_locations()) == len(world.get_locations()):
+ if len(multiworld.get_reachable_locations()) == len(multiworld.get_locations()):
progress_done = True
continue
@@ -524,7 +524,7 @@ def flood_items(world: MultiWorld) -> None:
for item in itempool:
if item.advancement:
candidate_item_to_place = item
- if world.unlocks_new_location(item):
+ if multiworld.unlocks_new_location(item):
item_to_place = item
break
@@ -537,15 +537,15 @@ def flood_items(world: MultiWorld) -> None:
raise FillError('No more progress items left to place.')
# find item to replace with progress item
- location_list = world.get_reachable_locations()
- world.random.shuffle(location_list)
+ location_list = multiworld.get_reachable_locations()
+ multiworld.random.shuffle(location_list)
for location in location_list:
if location.item is not None and not location.item.advancement:
# safe to replace
replace_item = location.item
replace_item.location = None
itempool.append(replace_item)
- world.push_item(location, item_to_place, True)
+ multiworld.push_item(location, item_to_place, True)
itempool.remove(item_to_place)
break
@@ -755,7 +755,7 @@ def swap_location_item(location_1: Location, location_2: Location, check_locked:
location_1.event, location_2.event = location_2.event, location_1.event
-def distribute_planned(world: MultiWorld) -> None:
+def distribute_planned(multiworld: MultiWorld) -> None:
def warn(warning: str, force: typing.Union[bool, str]) -> None:
if force in [True, 'fail', 'failure', 'none', False, 'warn', 'warning']:
logging.warning(f'{warning}')
@@ -768,24 +768,24 @@ def failed(warning: str, force: typing.Union[bool, str]) -> None:
else:
warn(warning, force)
- swept_state = world.state.copy()
+ swept_state = multiworld.state.copy()
swept_state.sweep_for_events()
- reachable = frozenset(world.get_reachable_locations(swept_state))
+ reachable = frozenset(multiworld.get_reachable_locations(swept_state))
early_locations: typing.Dict[int, typing.List[str]] = collections.defaultdict(list)
non_early_locations: typing.Dict[int, typing.List[str]] = collections.defaultdict(list)
- for loc in world.get_unfilled_locations():
+ for loc in multiworld.get_unfilled_locations():
if loc in reachable:
early_locations[loc.player].append(loc.name)
else: # not reachable with swept state
non_early_locations[loc.player].append(loc.name)
- world_name_lookup = world.world_name_lookup
+ world_name_lookup = multiworld.world_name_lookup
block_value = typing.Union[typing.List[str], typing.Dict[str, typing.Any], str]
plando_blocks: typing.List[typing.Dict[str, typing.Any]] = []
- player_ids = set(world.player_ids)
+ player_ids = set(multiworld.player_ids)
for player in player_ids:
- for block in world.plando_items[player]:
+ for block in multiworld.plando_items[player]:
block['player'] = player
if 'force' not in block:
block['force'] = 'silent'
@@ -799,12 +799,12 @@ def failed(warning: str, force: typing.Union[bool, str]) -> None:
else:
target_world = block['world']
- if target_world is False or world.players == 1: # target own world
+ if target_world is False or multiworld.players == 1: # target own world
worlds: typing.Set[int] = {player}
elif target_world is True: # target any worlds besides own
- worlds = set(world.player_ids) - {player}
+ worlds = set(multiworld.player_ids) - {player}
elif target_world is None: # target all worlds
- worlds = set(world.player_ids)
+ worlds = set(multiworld.player_ids)
elif type(target_world) == list: # list of target worlds
worlds = set()
for listed_world in target_world:
@@ -814,9 +814,9 @@ def failed(warning: str, force: typing.Union[bool, str]) -> None:
continue
worlds.add(world_name_lookup[listed_world])
elif type(target_world) == int: # target world by slot number
- if target_world not in range(1, world.players + 1):
+ if target_world not in range(1, multiworld.players + 1):
failed(
- f"Cannot place item in world {target_world} as it is not in range of (1, {world.players})",
+ f"Cannot place item in world {target_world} as it is not in range of (1, {multiworld.players})",
block['force'])
continue
worlds = {target_world}
@@ -844,7 +844,7 @@ def failed(warning: str, force: typing.Union[bool, str]) -> None:
item_list: typing.List[str] = []
for key, value in items.items():
if value is True:
- value = world.itempool.count(world.worlds[player].create_item(key))
+ value = multiworld.itempool.count(multiworld.worlds[player].create_item(key))
item_list += [key] * value
items = item_list
if isinstance(items, str):
@@ -894,17 +894,17 @@ def failed(warning: str, force: typing.Union[bool, str]) -> None:
count = block['count']
failed(f"Plando count {count} greater than locations specified", block['force'])
block['count'] = len(block['locations'])
- block['count']['target'] = world.random.randint(block['count']['min'], block['count']['max'])
+ block['count']['target'] = multiworld.random.randint(block['count']['min'], block['count']['max'])
if block['count']['target'] > 0:
plando_blocks.append(block)
# shuffle, but then sort blocks by number of locations minus number of items,
# so less-flexible blocks get priority
- world.random.shuffle(plando_blocks)
+ multiworld.random.shuffle(plando_blocks)
plando_blocks.sort(key=lambda block: (len(block['locations']) - block['count']['target']
if len(block['locations']) > 0
- else len(world.get_unfilled_locations(player)) - block['count']['target']))
+ else len(multiworld.get_unfilled_locations(player)) - block['count']['target']))
for placement in plando_blocks:
player = placement['player']
@@ -915,19 +915,19 @@ def failed(warning: str, force: typing.Union[bool, str]) -> None:
maxcount = placement['count']['target']
from_pool = placement['from_pool']
- candidates = list(world.get_unfilled_locations_for_players(locations, sorted(worlds)))
- world.random.shuffle(candidates)
- world.random.shuffle(items)
+ candidates = list(multiworld.get_unfilled_locations_for_players(locations, sorted(worlds)))
+ multiworld.random.shuffle(candidates)
+ multiworld.random.shuffle(items)
count = 0
err: typing.List[str] = []
successful_pairs: typing.List[typing.Tuple[Item, Location]] = []
for item_name in items:
- item = world.worlds[player].create_item(item_name)
+ item = multiworld.worlds[player].create_item(item_name)
for location in reversed(candidates):
if (location.address is None) == (item.code is None): # either both None or both not None
if not location.item:
if location.item_rule(item):
- if location.can_fill(world.state, item, False):
+ if location.can_fill(multiworld.state, item, False):
successful_pairs.append((item, location))
candidates.remove(location)
count = count + 1
@@ -945,21 +945,21 @@ def failed(warning: str, force: typing.Union[bool, str]) -> None:
if count < placement['count']['min']:
m = placement['count']['min']
failed(
- f"Plando block failed to place {m - count} of {m} item(s) for {world.player_name[player]}, error(s): {' '.join(err)}",
+ f"Plando block failed to place {m - count} of {m} item(s) for {multiworld.player_name[player]}, error(s): {' '.join(err)}",
placement['force'])
for (item, location) in successful_pairs:
- world.push_item(location, item, collect=False)
+ multiworld.push_item(location, item, collect=False)
location.event = True # flag location to be checked during fill
location.locked = True
logging.debug(f"Plando placed {item} at {location}")
if from_pool:
try:
- world.itempool.remove(item)
+ multiworld.itempool.remove(item)
except ValueError:
warn(
- f"Could not remove {item} from pool for {world.player_name[player]} as it's already missing from it.",
+ f"Could not remove {item} from pool for {multiworld.player_name[player]} as it's already missing from it.",
placement['force'])
except Exception as e:
raise Exception(
- f"Error running plando for player {player} ({world.player_name[player]})") from e
+ f"Error running plando for player {player} ({multiworld.player_name[player]})") from e
diff --git a/Generate.py b/Generate.py
index e19a7a973f23..725a7e9fec35 100644
--- a/Generate.py
+++ b/Generate.py
@@ -315,20 +315,6 @@ def prefer_int(input_data: str) -> Union[str, int]:
return input_data
-goals = {
- 'ganon': 'ganon',
- 'crystals': 'crystals',
- 'bosses': 'bosses',
- 'pedestal': 'pedestal',
- 'ganon_pedestal': 'ganonpedestal',
- 'triforce_hunt': 'triforcehunt',
- 'local_triforce_hunt': 'localtriforcehunt',
- 'ganon_triforce_hunt': 'ganontriforcehunt',
- 'local_ganon_triforce_hunt': 'localganontriforcehunt',
- 'ice_rod_hunt': 'icerodhunt',
-}
-
-
def roll_percentage(percentage: Union[int, float]) -> bool:
"""Roll a percentage chance.
percentage is expected to be in range [0, 100]"""
@@ -357,15 +343,6 @@ def roll_meta_option(option_key, game: str, category_dict: Dict) -> Any:
if options[option_key].supports_weighting:
return get_choice(option_key, category_dict)
return category_dict[option_key]
- if game == "A Link to the Past": # TODO wow i hate this
- if option_key in {"glitches_required", "dark_room_logic", "entrance_shuffle", "goals", "triforce_pieces_mode",
- "triforce_pieces_percentage", "triforce_pieces_available", "triforce_pieces_extra",
- "triforce_pieces_required", "shop_shuffle", "mode", "item_pool", "item_functionality",
- "boss_shuffle", "enemy_damage", "enemy_health", "timer", "countdown_start_time",
- "red_clock_time", "blue_clock_time", "green_clock_time", "dungeon_counters", "shuffle_prizes",
- "misery_mire_medallion", "turtle_rock_medallion", "sprite_pool", "sprite",
- "random_sprite_on_event"}:
- return get_choice(option_key, category_dict)
raise Exception(f"Error generating meta option {option_key} for {game}.")
@@ -485,120 +462,23 @@ def roll_settings(weights: dict, plando_options: PlandoOptions = PlandoOptions.b
handle_option(ret, game_weights, option_key, option, plando_options)
if PlandoOptions.items in plando_options:
ret.plando_items = game_weights.get("plando_items", [])
- if ret.game == "Minecraft" or ret.game == "Ocarina of Time":
- # bad hardcoded behavior to make this work for now
- ret.plando_connections = []
- if PlandoOptions.connections in plando_options:
- options = game_weights.get("plando_connections", [])
- for placement in options:
- if roll_percentage(get_choice("percentage", placement, 100)):
- ret.plando_connections.append(PlandoConnection(
- get_choice("entrance", placement),
- get_choice("exit", placement),
- get_choice("direction", placement)
- ))
- elif ret.game == "A Link to the Past":
+ if ret.game == "A Link to the Past":
roll_alttp_settings(ret, game_weights, plando_options)
+ if PlandoOptions.connections in plando_options:
+ ret.plando_connections = []
+ options = game_weights.get("plando_connections", [])
+ for placement in options:
+ if roll_percentage(get_choice("percentage", placement, 100)):
+ ret.plando_connections.append(PlandoConnection(
+ get_choice("entrance", placement),
+ get_choice("exit", placement),
+ get_choice("direction", placement, "both")
+ ))
return ret
def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
- if "dungeon_items" in weights and get_choice_legacy('dungeon_items', weights, "none") != "none":
- raise Exception(f"dungeon_items key in A Link to the Past was removed, but is present in these weights as {get_choice_legacy('dungeon_items', weights, False)}.")
- glitches_required = get_choice_legacy('glitches_required', weights)
- if glitches_required not in [None, 'none', 'no_logic', 'overworld_glitches', 'hybrid_major_glitches', 'minor_glitches']:
- logging.warning("Only NMG, OWG, HMG and No Logic supported")
- glitches_required = 'none'
- ret.logic = {None: 'noglitches', 'none': 'noglitches', 'no_logic': 'nologic', 'overworld_glitches': 'owglitches',
- 'minor_glitches': 'minorglitches', 'hybrid_major_glitches': 'hybridglitches'}[
- glitches_required]
-
- ret.dark_room_logic = get_choice_legacy("dark_room_logic", weights, "lamp")
- if not ret.dark_room_logic: # None/False
- ret.dark_room_logic = "none"
- if ret.dark_room_logic == "sconces":
- ret.dark_room_logic = "torches"
- if ret.dark_room_logic not in {"lamp", "torches", "none"}:
- raise ValueError(f"Unknown Dark Room Logic: \"{ret.dark_room_logic}\"")
-
- entrance_shuffle = get_choice_legacy('entrance_shuffle', weights, 'vanilla')
- if entrance_shuffle.startswith('none-'):
- ret.shuffle = 'vanilla'
- else:
- ret.shuffle = entrance_shuffle if entrance_shuffle != 'none' else 'vanilla'
-
- goal = get_choice_legacy('goals', weights, 'ganon')
-
- ret.goal = goals[goal]
-
-
- extra_pieces = get_choice_legacy('triforce_pieces_mode', weights, 'available')
-
- ret.triforce_pieces_required = LttPOptions.TriforcePieces.from_any(get_choice_legacy('triforce_pieces_required', weights, 20))
-
- # sum a percentage to required
- if extra_pieces == 'percentage':
- percentage = max(100, float(get_choice_legacy('triforce_pieces_percentage', weights, 150))) / 100
- ret.triforce_pieces_available = int(round(ret.triforce_pieces_required * percentage, 0))
- # vanilla mode (specify how many pieces are)
- elif extra_pieces == 'available':
- ret.triforce_pieces_available = LttPOptions.TriforcePieces.from_any(
- get_choice_legacy('triforce_pieces_available', weights, 30))
- # required pieces + fixed extra
- elif extra_pieces == 'extra':
- extra_pieces = max(0, int(get_choice_legacy('triforce_pieces_extra', weights, 10)))
- ret.triforce_pieces_available = ret.triforce_pieces_required + extra_pieces
-
- # change minimum to required pieces to avoid problems
- ret.triforce_pieces_available = min(max(ret.triforce_pieces_required, int(ret.triforce_pieces_available)), 90)
-
- ret.shop_shuffle = get_choice_legacy('shop_shuffle', weights, '')
- if not ret.shop_shuffle:
- ret.shop_shuffle = ''
-
- ret.mode = get_choice_legacy("mode", weights)
-
- ret.difficulty = get_choice_legacy('item_pool', weights)
-
- ret.item_functionality = get_choice_legacy('item_functionality', weights)
-
-
- ret.enemy_damage = {None: 'default',
- 'default': 'default',
- 'shuffled': 'shuffled',
- 'random': 'chaos', # to be removed
- 'chaos': 'chaos',
- }[get_choice_legacy('enemy_damage', weights)]
-
- ret.enemy_health = get_choice_legacy('enemy_health', weights)
-
- ret.timer = {'none': False,
- None: False,
- False: False,
- 'timed': 'timed',
- 'timed_ohko': 'timed-ohko',
- 'ohko': 'ohko',
- 'timed_countdown': 'timed-countdown',
- 'display': 'display'}[get_choice_legacy('timer', weights, False)]
-
- ret.countdown_start_time = int(get_choice_legacy('countdown_start_time', weights, 10))
- ret.red_clock_time = int(get_choice_legacy('red_clock_time', weights, -2))
- ret.blue_clock_time = int(get_choice_legacy('blue_clock_time', weights, 2))
- ret.green_clock_time = int(get_choice_legacy('green_clock_time', weights, 4))
-
- ret.dungeon_counters = get_choice_legacy('dungeon_counters', weights, 'default')
-
- ret.shuffle_prizes = get_choice_legacy('shuffle_prizes', weights, "g")
-
- ret.required_medallions = [get_choice_legacy("misery_mire_medallion", weights, "random"),
- get_choice_legacy("turtle_rock_medallion", weights, "random")]
-
- for index, medallion in enumerate(ret.required_medallions):
- ret.required_medallions[index] = {"ether": "Ether", "quake": "Quake", "bombos": "Bombos", "random": "random"} \
- .get(medallion.lower(), None)
- if not ret.required_medallions[index]:
- raise Exception(f"unknown Medallion {medallion} for {'misery mire' if index == 0 else 'turtle rock'}")
ret.plando_texts = {}
if PlandoOptions.texts in plando_options:
@@ -612,17 +492,6 @@ def roll_alttp_settings(ret: argparse.Namespace, weights, plando_options):
raise Exception(f"No text target \"{at}\" found.")
ret.plando_texts[at] = str(get_choice_legacy("text", placement))
- ret.plando_connections = []
- if PlandoOptions.connections in plando_options:
- options = weights.get("plando_connections", [])
- for placement in options:
- if roll_percentage(get_choice_legacy("percentage", placement, 100)):
- ret.plando_connections.append(PlandoConnection(
- get_choice_legacy("entrance", placement),
- get_choice_legacy("exit", placement),
- get_choice_legacy("direction", placement, "both")
- ))
-
ret.sprite_pool = weights.get('sprite_pool', [])
ret.sprite = get_choice_legacy('sprite', weights, "Link")
if 'random_sprite_on_event' in weights:
diff --git a/Launcher.py b/Launcher.py
index 9e184bf1088d..890957958391 100644
--- a/Launcher.py
+++ b/Launcher.py
@@ -161,7 +161,7 @@ def launch(exe, in_terminal=False):
def run_gui():
- from kvui import App, ContainerLayout, GridLayout, Button, Label
+ from kvui import App, ContainerLayout, GridLayout, Button, Label, ScrollBox, Widget
from kivy.uix.image import AsyncImage
from kivy.uix.relativelayout import RelativeLayout
@@ -185,11 +185,16 @@ def build(self):
self.container = ContainerLayout()
self.grid = GridLayout(cols=2)
self.container.add_widget(self.grid)
- self.grid.add_widget(Label(text="General"))
- self.grid.add_widget(Label(text="Clients"))
- button_layout = self.grid # make buttons fill the window
-
- def build_button(component: Component):
+ self.grid.add_widget(Label(text="General", size_hint_y=None, height=40))
+ self.grid.add_widget(Label(text="Clients", size_hint_y=None, height=40))
+ tool_layout = ScrollBox()
+ tool_layout.layout.orientation = "vertical"
+ self.grid.add_widget(tool_layout)
+ client_layout = ScrollBox()
+ client_layout.layout.orientation = "vertical"
+ self.grid.add_widget(client_layout)
+
+ def build_button(component: Component) -> Widget:
"""
Builds a button widget for a given component.
@@ -200,31 +205,26 @@ def build_button(component: Component):
None. The button is added to the parent grid layout.
"""
- button = Button(text=component.display_name)
+ button = Button(text=component.display_name, size_hint_y=None, height=40)
button.component = component
button.bind(on_release=self.component_action)
if component.icon != "icon":
image = AsyncImage(source=icon_paths[component.icon],
size=(38, 38), size_hint=(None, 1), pos=(5, 0))
- box_layout = RelativeLayout()
+ box_layout = RelativeLayout(size_hint_y=None, height=40)
box_layout.add_widget(button)
box_layout.add_widget(image)
- button_layout.add_widget(box_layout)
- else:
- button_layout.add_widget(button)
+ return box_layout
+ return button
for (tool, client) in itertools.zip_longest(itertools.chain(
self._tools.items(), self._miscs.items(), self._adjusters.items()), self._clients.items()):
# column 1
if tool:
- build_button(tool[1])
- else:
- button_layout.add_widget(Label())
+ tool_layout.layout.add_widget(build_button(tool[1]))
# column 2
if client:
- build_button(client[1])
- else:
- button_layout.add_widget(Label())
+ client_layout.layout.add_widget(build_button(client[1]))
return self.container
diff --git a/LinksAwakeningClient.py b/LinksAwakeningClient.py
index f3fc9d2cdb72..a51645feac92 100644
--- a/LinksAwakeningClient.py
+++ b/LinksAwakeningClient.py
@@ -348,7 +348,8 @@ async def wait_for_retroarch_connection(self):
await asyncio.sleep(1.0)
continue
self.stop_bizhawk_spam = False
- logger.info(f"Connected to Retroarch {version.decode('ascii')} running {rom_name.decode('ascii')}")
+ logger.info(f"Connected to Retroarch {version.decode('ascii', errors='replace')} "
+ f"running {rom_name.decode('ascii', errors='replace')}")
return
except (BlockingIOError, TimeoutError, ConnectionResetError):
await asyncio.sleep(1.0)
diff --git a/Main.py b/Main.py
index e49d8e781df9..f1d2f63692d6 100644
--- a/Main.py
+++ b/Main.py
@@ -30,49 +30,49 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
output_path.cached_path = args.outputpath
start = time.perf_counter()
- # initialize the world
- world = MultiWorld(args.multi)
+ # initialize the multiworld
+ multiworld = MultiWorld(args.multi)
logger = logging.getLogger()
- world.set_seed(seed, args.race, str(args.outputname) if args.outputname else None)
- world.plando_options = args.plando_options
-
- world.shuffle = args.shuffle.copy()
- world.logic = args.logic.copy()
- world.mode = args.mode.copy()
- world.difficulty = args.difficulty.copy()
- world.item_functionality = args.item_functionality.copy()
- world.timer = args.timer.copy()
- world.goal = args.goal.copy()
- world.boss_shuffle = args.shufflebosses.copy()
- world.enemy_health = args.enemy_health.copy()
- world.enemy_damage = args.enemy_damage.copy()
- world.beemizer_total_chance = args.beemizer_total_chance.copy()
- world.beemizer_trap_chance = args.beemizer_trap_chance.copy()
- world.countdown_start_time = args.countdown_start_time.copy()
- world.red_clock_time = args.red_clock_time.copy()
- world.blue_clock_time = args.blue_clock_time.copy()
- world.green_clock_time = args.green_clock_time.copy()
- world.dungeon_counters = args.dungeon_counters.copy()
- world.triforce_pieces_available = args.triforce_pieces_available.copy()
- world.triforce_pieces_required = args.triforce_pieces_required.copy()
- world.shop_shuffle = args.shop_shuffle.copy()
- world.shuffle_prizes = args.shuffle_prizes.copy()
- world.sprite_pool = args.sprite_pool.copy()
- world.dark_room_logic = args.dark_room_logic.copy()
- world.plando_items = args.plando_items.copy()
- world.plando_texts = args.plando_texts.copy()
- world.plando_connections = args.plando_connections.copy()
- world.required_medallions = args.required_medallions.copy()
- world.game = args.game.copy()
- world.player_name = args.name.copy()
- world.sprite = args.sprite.copy()
- world.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player option.
-
- world.set_options(args)
- world.set_item_links()
- world.state = CollectionState(world)
- logger.info('Archipelago Version %s - Seed: %s\n', __version__, world.seed)
+ multiworld.set_seed(seed, args.race, str(args.outputname) if args.outputname else None)
+ multiworld.plando_options = args.plando_options
+
+ multiworld.shuffle = args.shuffle.copy()
+ multiworld.logic = args.logic.copy()
+ multiworld.mode = args.mode.copy()
+ multiworld.difficulty = args.difficulty.copy()
+ multiworld.item_functionality = args.item_functionality.copy()
+ multiworld.timer = args.timer.copy()
+ multiworld.goal = args.goal.copy()
+ multiworld.boss_shuffle = args.shufflebosses.copy()
+ multiworld.enemy_health = args.enemy_health.copy()
+ multiworld.enemy_damage = args.enemy_damage.copy()
+ multiworld.beemizer_total_chance = args.beemizer_total_chance.copy()
+ multiworld.beemizer_trap_chance = args.beemizer_trap_chance.copy()
+ multiworld.countdown_start_time = args.countdown_start_time.copy()
+ multiworld.red_clock_time = args.red_clock_time.copy()
+ multiworld.blue_clock_time = args.blue_clock_time.copy()
+ multiworld.green_clock_time = args.green_clock_time.copy()
+ multiworld.dungeon_counters = args.dungeon_counters.copy()
+ multiworld.triforce_pieces_available = args.triforce_pieces_available.copy()
+ multiworld.triforce_pieces_required = args.triforce_pieces_required.copy()
+ multiworld.shop_shuffle = args.shop_shuffle.copy()
+ multiworld.shuffle_prizes = args.shuffle_prizes.copy()
+ multiworld.sprite_pool = args.sprite_pool.copy()
+ multiworld.dark_room_logic = args.dark_room_logic.copy()
+ multiworld.plando_items = args.plando_items.copy()
+ multiworld.plando_texts = args.plando_texts.copy()
+ multiworld.plando_connections = args.plando_connections.copy()
+ multiworld.required_medallions = args.required_medallions.copy()
+ multiworld.game = args.game.copy()
+ multiworld.player_name = args.name.copy()
+ multiworld.sprite = args.sprite.copy()
+ multiworld.glitch_triforce = args.glitch_triforce # This is enabled/disabled globally, no per player option.
+
+ multiworld.set_options(args)
+ multiworld.set_item_links()
+ multiworld.state = CollectionState(multiworld)
+ logger.info('Archipelago Version %s - Seed: %s\n', __version__, multiworld.seed)
logger.info(f"Found {len(AutoWorld.AutoWorldRegister.world_types)} World Types:")
longest_name = max(len(text) for text in AutoWorld.AutoWorldRegister.world_types)
@@ -103,93 +103,93 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
# This assertion method should not be necessary to run if we are not outputting any multidata.
if not args.skip_output:
- AutoWorld.call_stage(world, "assert_generate")
+ AutoWorld.call_stage(multiworld, "assert_generate")
- AutoWorld.call_all(world, "generate_early")
+ AutoWorld.call_all(multiworld, "generate_early")
logger.info('')
- for player in world.player_ids:
- for item_name, count in world.worlds[player].options.start_inventory.value.items():
+ for player in multiworld.player_ids:
+ for item_name, count in multiworld.worlds[player].options.start_inventory.value.items():
for _ in range(count):
- world.push_precollected(world.create_item(item_name, player))
+ multiworld.push_precollected(multiworld.create_item(item_name, player))
- for item_name, count in getattr(world.worlds[player].options,
+ for item_name, count in getattr(multiworld.worlds[player].options,
"start_inventory_from_pool",
StartInventoryPool({})).value.items():
for _ in range(count):
- world.push_precollected(world.create_item(item_name, player))
+ multiworld.push_precollected(multiworld.create_item(item_name, player))
# remove from_pool items also from early items handling, as starting is plenty early.
- early = world.early_items[player].get(item_name, 0)
+ early = multiworld.early_items[player].get(item_name, 0)
if early:
- world.early_items[player][item_name] = max(0, early-count)
+ multiworld.early_items[player][item_name] = max(0, early-count)
remaining_count = count-early
if remaining_count > 0:
- local_early = world.early_local_items[player].get(item_name, 0)
+ local_early = multiworld.early_local_items[player].get(item_name, 0)
if local_early:
- world.early_items[player][item_name] = max(0, local_early - remaining_count)
+ multiworld.early_items[player][item_name] = max(0, local_early - remaining_count)
del local_early
del early
- logger.info('Creating World.')
- AutoWorld.call_all(world, "create_regions")
+ logger.info('Creating MultiWorld.')
+ AutoWorld.call_all(multiworld, "create_regions")
logger.info('Creating Items.')
- AutoWorld.call_all(world, "create_items")
+ AutoWorld.call_all(multiworld, "create_items")
logger.info('Calculating Access Rules.')
- for player in world.player_ids:
+ for player in multiworld.player_ids:
# items can't be both local and non-local, prefer local
- world.worlds[player].options.non_local_items.value -= world.worlds[player].options.local_items.value
- world.worlds[player].options.non_local_items.value -= set(world.local_early_items[player])
+ multiworld.worlds[player].options.non_local_items.value -= multiworld.worlds[player].options.local_items.value
+ multiworld.worlds[player].options.non_local_items.value -= set(multiworld.local_early_items[player])
- AutoWorld.call_all(world, "set_rules")
+ AutoWorld.call_all(multiworld, "set_rules")
- for player in world.player_ids:
- exclusion_rules(world, player, world.worlds[player].options.exclude_locations.value)
- world.worlds[player].options.priority_locations.value -= world.worlds[player].options.exclude_locations.value
- for location_name in world.worlds[player].options.priority_locations.value:
+ for player in multiworld.player_ids:
+ exclusion_rules(multiworld, player, multiworld.worlds[player].options.exclude_locations.value)
+ multiworld.worlds[player].options.priority_locations.value -= multiworld.worlds[player].options.exclude_locations.value
+ for location_name in multiworld.worlds[player].options.priority_locations.value:
try:
- location = world.get_location(location_name, player)
+ location = multiworld.get_location(location_name, player)
except KeyError as e: # failed to find the given location. Check if it's a legitimate location
- if location_name not in world.worlds[player].location_name_to_id:
+ if location_name not in multiworld.worlds[player].location_name_to_id:
raise Exception(f"Unable to prioritize location {location_name} in player {player}'s world.") from e
else:
location.progress_type = LocationProgressType.PRIORITY
# Set local and non-local item rules.
- if world.players > 1:
- locality_rules(world)
+ if multiworld.players > 1:
+ locality_rules(multiworld)
else:
- world.worlds[1].options.non_local_items.value = set()
- world.worlds[1].options.local_items.value = set()
+ multiworld.worlds[1].options.non_local_items.value = set()
+ multiworld.worlds[1].options.local_items.value = set()
- AutoWorld.call_all(world, "generate_basic")
+ AutoWorld.call_all(multiworld, "generate_basic")
# remove starting inventory from pool items.
# Because some worlds don't actually create items during create_items this has to be as late as possible.
- if any(getattr(world.worlds[player].options, "start_inventory_from_pool", None) for player in world.player_ids):
+ if any(getattr(multiworld.worlds[player].options, "start_inventory_from_pool", None) for player in multiworld.player_ids):
new_items: List[Item] = []
depletion_pool: Dict[int, Dict[str, int]] = {
- player: getattr(world.worlds[player].options,
+ player: getattr(multiworld.worlds[player].options,
"start_inventory_from_pool",
StartInventoryPool({})).value.copy()
- for player in world.player_ids
+ for player in multiworld.player_ids
}
for player, items in depletion_pool.items():
- player_world: AutoWorld.World = world.worlds[player]
+ player_world: AutoWorld.World = multiworld.worlds[player]
for count in items.values():
for _ in range(count):
new_items.append(player_world.create_filler())
target: int = sum(sum(items.values()) for items in depletion_pool.values())
- for i, item in enumerate(world.itempool):
+ for i, item in enumerate(multiworld.itempool):
if depletion_pool[item.player].get(item.name, 0):
target -= 1
depletion_pool[item.player][item.name] -= 1
# quick abort if we have found all items
if not target:
- new_items.extend(world.itempool[i+1:])
+ new_items.extend(multiworld.itempool[i+1:])
break
else:
new_items.append(item)
@@ -199,19 +199,19 @@ def main(args, seed=None, baked_server_options: Optional[Dict[str, object]] = No
for player, remaining_items in depletion_pool.items():
remaining_items = {name: count for name, count in remaining_items.items() if count}
if remaining_items:
- raise Exception(f"{world.get_player_name(player)}"
+ raise Exception(f"{multiworld.get_player_name(player)}"
f" is trying to remove items from their pool that don't exist: {remaining_items}")
- assert len(world.itempool) == len(new_items), "Item Pool amounts should not change."
- world.itempool[:] = new_items
+ assert len(multiworld.itempool) == len(new_items), "Item Pool amounts should not change."
+ multiworld.itempool[:] = new_items
# temporary home for item links, should be moved out of Main
- for group_id, group in world.groups.items():
+ for group_id, group in multiworld.groups.items():
def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[
Optional[Dict[int, Dict[str, int]]], Optional[Dict[str, int]]
]:
classifications: Dict[str, int] = collections.defaultdict(int)
counters = {player: {name: 0 for name in shared_pool} for player in players}
- for item in world.itempool:
+ for item in multiworld.itempool:
if item.player in counters and item.name in shared_pool:
counters[item.player][item.name] += 1
classifications[item.name] |= item.classification
@@ -246,13 +246,13 @@ def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[
new_item.classification |= classifications[item_name]
new_itempool.append(new_item)
- region = Region("Menu", group_id, world, "ItemLink")
- world.regions.append(region)
+ region = Region("Menu", group_id, multiworld, "ItemLink")
+ multiworld.regions.append(region)
locations = region.locations
- for item in world.itempool:
+ for item in multiworld.itempool:
count = common_item_count.get(item.player, {}).get(item.name, 0)
if count:
- loc = Location(group_id, f"Item Link: {item.name} -> {world.player_name[item.player]} {count}",
+ loc = Location(group_id, f"Item Link: {item.name} -> {multiworld.player_name[item.player]} {count}",
None, region)
loc.access_rule = lambda state, item_name = item.name, group_id_ = group_id, count_ = count: \
state.has(item_name, group_id_, count_)
@@ -263,10 +263,10 @@ def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[
else:
new_itempool.append(item)
- itemcount = len(world.itempool)
- world.itempool = new_itempool
+ itemcount = len(multiworld.itempool)
+ multiworld.itempool = new_itempool
- while itemcount > len(world.itempool):
+ while itemcount > len(multiworld.itempool):
items_to_add = []
for player in group["players"]:
if group["link_replacement"]:
@@ -274,64 +274,64 @@ def find_common_pool(players: Set[int], shared_pool: Set[str]) -> Tuple[
else:
item_player = player
if group["replacement_items"][player]:
- items_to_add.append(AutoWorld.call_single(world, "create_item", item_player,
+ items_to_add.append(AutoWorld.call_single(multiworld, "create_item", item_player,
group["replacement_items"][player]))
else:
- items_to_add.append(AutoWorld.call_single(world, "create_filler", item_player))
- world.random.shuffle(items_to_add)
- world.itempool.extend(items_to_add[:itemcount - len(world.itempool)])
+ items_to_add.append(AutoWorld.call_single(multiworld, "create_filler", item_player))
+ multiworld.random.shuffle(items_to_add)
+ multiworld.itempool.extend(items_to_add[:itemcount - len(multiworld.itempool)])
- if any(world.item_links.values()):
- world._all_state = None
+ if any(multiworld.item_links.values()):
+ multiworld._all_state = None
logger.info("Running Item Plando.")
- distribute_planned(world)
+ distribute_planned(multiworld)
logger.info('Running Pre Main Fill.')
- AutoWorld.call_all(world, "pre_fill")
+ AutoWorld.call_all(multiworld, "pre_fill")
- logger.info(f'Filling the world with {len(world.itempool)} items.')
+ logger.info(f'Filling the multiworld with {len(multiworld.itempool)} items.')
- if world.algorithm == 'flood':
- flood_items(world) # different algo, biased towards early game progress items
- elif world.algorithm == 'balanced':
- distribute_items_restrictive(world)
+ if multiworld.algorithm == 'flood':
+ flood_items(multiworld) # different algo, biased towards early game progress items
+ elif multiworld.algorithm == 'balanced':
+ distribute_items_restrictive(multiworld)
- AutoWorld.call_all(world, 'post_fill')
+ AutoWorld.call_all(multiworld, 'post_fill')
- if world.players > 1 and not args.skip_prog_balancing:
- balance_multiworld_progression(world)
+ if multiworld.players > 1 and not args.skip_prog_balancing:
+ balance_multiworld_progression(multiworld)
else:
logger.info("Progression balancing skipped.")
# we're about to output using multithreading, so we're removing the global random state to prevent accidental use
- world.random.passthrough = False
+ multiworld.random.passthrough = False
if args.skip_output:
logger.info('Done. Skipped output/spoiler generation. Total Time: %s', time.perf_counter() - start)
- return world
+ return multiworld
logger.info(f'Beginning output...')
- outfilebase = 'AP_' + world.seed_name
+ outfilebase = 'AP_' + multiworld.seed_name
output = tempfile.TemporaryDirectory()
with output as temp_dir:
- output_players = [player for player in world.player_ids if AutoWorld.World.generate_output.__code__
- is not world.worlds[player].generate_output.__code__]
+ output_players = [player for player in multiworld.player_ids if AutoWorld.World.generate_output.__code__
+ is not multiworld.worlds[player].generate_output.__code__]
with concurrent.futures.ThreadPoolExecutor(len(output_players) + 2) as pool:
- check_accessibility_task = pool.submit(world.fulfills_accessibility)
+ check_accessibility_task = pool.submit(multiworld.fulfills_accessibility)
- output_file_futures = [pool.submit(AutoWorld.call_stage, world, "generate_output", temp_dir)]
+ output_file_futures = [pool.submit(AutoWorld.call_stage, multiworld, "generate_output", temp_dir)]
for player in output_players:
# skip starting a thread for methods that say "pass".
output_file_futures.append(
- pool.submit(AutoWorld.call_single, world, "generate_output", player, temp_dir))
+ pool.submit(AutoWorld.call_single, multiworld, "generate_output", player, temp_dir))
# collect ER hint info
er_hint_data: Dict[int, Dict[int, str]] = {}
- AutoWorld.call_all(world, 'extend_hint_information', er_hint_data)
+ AutoWorld.call_all(multiworld, 'extend_hint_information', er_hint_data)
def write_multidata():
import NetUtils
@@ -340,38 +340,38 @@ def write_multidata():
games = {}
minimum_versions = {"server": AutoWorld.World.required_server_version, "clients": client_versions}
slot_info = {}
- names = [[name for player, name in sorted(world.player_name.items())]]
- for slot in world.player_ids:
- player_world: AutoWorld.World = world.worlds[slot]
+ names = [[name for player, name in sorted(multiworld.player_name.items())]]
+ for slot in multiworld.player_ids:
+ player_world: AutoWorld.World = multiworld.worlds[slot]
minimum_versions["server"] = max(minimum_versions["server"], player_world.required_server_version)
client_versions[slot] = player_world.required_client_version
- games[slot] = world.game[slot]
- slot_info[slot] = NetUtils.NetworkSlot(names[0][slot - 1], world.game[slot],
- world.player_types[slot])
- for slot, group in world.groups.items():
- games[slot] = world.game[slot]
- slot_info[slot] = NetUtils.NetworkSlot(group["name"], world.game[slot], world.player_types[slot],
+ games[slot] = multiworld.game[slot]
+ slot_info[slot] = NetUtils.NetworkSlot(names[0][slot - 1], multiworld.game[slot],
+ multiworld.player_types[slot])
+ for slot, group in multiworld.groups.items():
+ games[slot] = multiworld.game[slot]
+ slot_info[slot] = NetUtils.NetworkSlot(group["name"], multiworld.game[slot], multiworld.player_types[slot],
group_members=sorted(group["players"]))
precollected_items = {player: [item.code for item in world_precollected if type(item.code) == int]
- for player, world_precollected in world.precollected_items.items()}
- precollected_hints = {player: set() for player in range(1, world.players + 1 + len(world.groups))}
+ for player, world_precollected in multiworld.precollected_items.items()}
+ precollected_hints = {player: set() for player in range(1, multiworld.players + 1 + len(multiworld.groups))}
- for slot in world.player_ids:
- slot_data[slot] = world.worlds[slot].fill_slot_data()
+ for slot in multiworld.player_ids:
+ slot_data[slot] = multiworld.worlds[slot].fill_slot_data()
def precollect_hint(location):
entrance = er_hint_data.get(location.player, {}).get(location.address, "")
hint = NetUtils.Hint(location.item.player, location.player, location.address,
location.item.code, False, entrance, location.item.flags)
precollected_hints[location.player].add(hint)
- if location.item.player not in world.groups:
+ if location.item.player not in multiworld.groups:
precollected_hints[location.item.player].add(hint)
else:
- for player in world.groups[location.item.player]["players"]:
+ for player in multiworld.groups[location.item.player]["players"]:
precollected_hints[player].add(hint)
- locations_data: Dict[int, Dict[int, Tuple[int, int, int]]] = {player: {} for player in world.player_ids}
- for location in world.get_filled_locations():
+ locations_data: Dict[int, Dict[int, Tuple[int, int, int]]] = {player: {} for player in multiworld.player_ids}
+ for location in multiworld.get_filled_locations():
if type(location.address) == int:
assert location.item.code is not None, "item code None should be event, " \
"location.address should then also be None. Location: " \
@@ -381,18 +381,18 @@ def precollect_hint(location):
f"{locations_data[location.player][location.address]}")
locations_data[location.player][location.address] = \
location.item.code, location.item.player, location.item.flags
- if location.name in world.worlds[location.player].options.start_location_hints:
+ if location.name in multiworld.worlds[location.player].options.start_location_hints:
precollect_hint(location)
- elif location.item.name in world.worlds[location.item.player].options.start_hints:
+ elif location.item.name in multiworld.worlds[location.item.player].options.start_hints:
precollect_hint(location)
- elif any([location.item.name in world.worlds[player].options.start_hints
- for player in world.groups.get(location.item.player, {}).get("players", [])]):
+ elif any([location.item.name in multiworld.worlds[player].options.start_hints
+ for player in multiworld.groups.get(location.item.player, {}).get("players", [])]):
precollect_hint(location)
# embedded data package
data_package = {
game_world.game: worlds.network_data_package["games"][game_world.game]
- for game_world in world.worlds.values()
+ for game_world in multiworld.worlds.values()
}
checks_in_area: Dict[int, Dict[str, Union[int, List[int]]]] = {}
@@ -400,7 +400,7 @@ def precollect_hint(location):
multidata = {
"slot_data": slot_data,
"slot_info": slot_info,
- "connect_names": {name: (0, player) for player, name in world.player_name.items()},
+ "connect_names": {name: (0, player) for player, name in multiworld.player_name.items()},
"locations": locations_data,
"checks_in_area": checks_in_area,
"server_options": baked_server_options,
@@ -410,10 +410,10 @@ def precollect_hint(location):
"version": tuple(version_tuple),
"tags": ["AP"],
"minimum_versions": minimum_versions,
- "seed_name": world.seed_name,
+ "seed_name": multiworld.seed_name,
"datapackage": data_package,
}
- AutoWorld.call_all(world, "modify_multidata", multidata)
+ AutoWorld.call_all(multiworld, "modify_multidata", multidata)
multidata = zlib.compress(pickle.dumps(multidata), 9)
@@ -423,7 +423,7 @@ def precollect_hint(location):
output_file_futures.append(pool.submit(write_multidata))
if not check_accessibility_task.result():
- if not world.can_beat_game():
+ if not multiworld.can_beat_game():
raise Exception("Game appears as unbeatable. Aborting.")
else:
logger.warning("Location Accessibility requirements not fulfilled.")
@@ -436,12 +436,12 @@ def precollect_hint(location):
if args.spoiler > 1:
logger.info('Calculating playthrough.')
- world.spoiler.create_playthrough(create_paths=args.spoiler > 2)
+ multiworld.spoiler.create_playthrough(create_paths=args.spoiler > 2)
if args.spoiler:
- world.spoiler.to_file(os.path.join(temp_dir, '%s_Spoiler.txt' % outfilebase))
+ multiworld.spoiler.to_file(os.path.join(temp_dir, '%s_Spoiler.txt' % outfilebase))
- zipfilename = output_path(f"AP_{world.seed_name}.zip")
+ zipfilename = output_path(f"AP_{multiworld.seed_name}.zip")
logger.info(f"Creating final archive at {zipfilename}")
with zipfile.ZipFile(zipfilename, mode="w", compression=zipfile.ZIP_DEFLATED,
compresslevel=9) as zf:
@@ -449,4 +449,4 @@ def precollect_hint(location):
zf.write(file.path, arcname=file.name)
logger.info('Done. Enjoy. Total Time: %s', time.perf_counter() - start)
- return world
+ return multiworld
diff --git a/MultiServer.py b/MultiServer.py
index 15ed22d715e8..62dab3298e6b 100644
--- a/MultiServer.py
+++ b/MultiServer.py
@@ -656,7 +656,8 @@ def get_aliased_name(self, team: int, slot: int):
else:
return self.player_names[team, slot]
- def notify_hints(self, team: int, hints: typing.List[NetUtils.Hint], only_new: bool = False):
+ def notify_hints(self, team: int, hints: typing.List[NetUtils.Hint], only_new: bool = False,
+ recipients: typing.Sequence[int] = None):
"""Send and remember hints."""
if only_new:
hints = [hint for hint in hints if hint not in self.hints[team, hint.finding_player]]
@@ -685,12 +686,13 @@ def notify_hints(self, team: int, hints: typing.List[NetUtils.Hint], only_new: b
for slot in new_hint_events:
self.on_new_hint(team, slot)
for slot, hint_data in concerns.items():
- clients = self.clients[team].get(slot)
- if not clients:
- continue
- client_hints = [datum[1] for datum in sorted(hint_data, key=lambda x: x[0].finding_player == slot)]
- for client in clients:
- async_start(self.send_msgs(client, client_hints))
+ if recipients is None or slot in recipients:
+ clients = self.clients[team].get(slot)
+ if not clients:
+ continue
+ client_hints = [datum[1] for datum in sorted(hint_data, key=lambda x: x[0].finding_player == slot)]
+ for client in clients:
+ async_start(self.send_msgs(client, client_hints))
# "events"
@@ -1429,9 +1431,13 @@ def get_hints(self, input_text: str, for_location: bool = False) -> bool:
hints = {hint.re_check(self.ctx, self.client.team) for hint in
self.ctx.hints[self.client.team, self.client.slot]}
self.ctx.hints[self.client.team, self.client.slot] = hints
- self.ctx.notify_hints(self.client.team, list(hints))
+ self.ctx.notify_hints(self.client.team, list(hints), recipients=(self.client.slot,))
self.output(f"A hint costs {self.ctx.get_hint_cost(self.client.slot)} points. "
f"You have {points_available} points.")
+ if hints and Utils.version_tuple < (0, 5, 0):
+ self.output("It was recently changed, so that the above hints are only shown to you. "
+ "If you meant to alert another player of an above hint, "
+ "please let them know of the content or to run !hint themselves.")
return True
elif input_text.isnumeric():
diff --git a/OoTAdjuster.py b/OoTAdjuster.py
index 38ebe62e2ae1..9519b191e704 100644
--- a/OoTAdjuster.py
+++ b/OoTAdjuster.py
@@ -195,10 +195,10 @@ def set_icon(window):
window.tk.call('wm', 'iconphoto', window._w, logo)
def adjust(args):
- # Create a fake world and OOTWorld to use as a base
- world = MultiWorld(1)
- world.per_slot_randoms = {1: random}
- ootworld = OOTWorld(world, 1)
+ # Create a fake multiworld and OOTWorld to use as a base
+ multiworld = MultiWorld(1)
+ multiworld.per_slot_randoms = {1: random}
+ ootworld = OOTWorld(multiworld, 1)
# Set options in the fake OOTWorld
for name, option in chain(cosmetic_options.items(), sfx_options.items()):
result = getattr(args, name, None)
diff --git a/Options.py b/Options.py
index 2e3927aae3f3..5fe4087132f7 100644
--- a/Options.py
+++ b/Options.py
@@ -1110,6 +1110,11 @@ class PerGameCommonOptions(CommonOptions):
item_links: ItemLinks
+@dataclass
+class DeathLinkMixin:
+ death_link: DeathLink
+
+
def generate_yaml_templates(target_folder: typing.Union[str, "pathlib.Path"], generate_hidden: bool = True):
import os
diff --git a/Patch.py b/Patch.py
index 113d0658c6b7..091545700059 100644
--- a/Patch.py
+++ b/Patch.py
@@ -8,7 +8,7 @@
import ModuleUpdate
ModuleUpdate.update()
-from worlds.Files import AutoPatchRegister, APDeltaPatch
+from worlds.Files import AutoPatchRegister, APPatch
class RomMeta(TypedDict):
@@ -20,7 +20,7 @@ class RomMeta(TypedDict):
def create_rom_file(patch_file: str) -> Tuple[RomMeta, str]:
auto_handler = AutoPatchRegister.get_handler(patch_file)
if auto_handler:
- handler: APDeltaPatch = auto_handler(patch_file)
+ handler: APPatch = auto_handler(patch_file)
target = os.path.splitext(patch_file)[0]+handler.result_file_ending
handler.patch(target)
return {"server": handler.server,
diff --git a/Utils.py b/Utils.py
index f6e4a9ab6052..da2d837ad3a3 100644
--- a/Utils.py
+++ b/Utils.py
@@ -19,14 +19,12 @@
from argparse import Namespace
from settings import Settings, get_settings
from typing import BinaryIO, Coroutine, Optional, Set, Dict, Any, Union
-from yaml import load, load_all, dump, SafeLoader
+from yaml import load, load_all, dump
try:
- from yaml import CLoader as UnsafeLoader
- from yaml import CDumper as Dumper
+ from yaml import CLoader as UnsafeLoader, CSafeLoader as SafeLoader, CDumper as Dumper
except ImportError:
- from yaml import Loader as UnsafeLoader
- from yaml import Dumper
+ from yaml import Loader as UnsafeLoader, SafeLoader, Dumper
if typing.TYPE_CHECKING:
import tkinter
@@ -871,8 +869,8 @@ def visualize_regions(root_region: Region, file_name: str, *,
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")
+ for player in multiworld.player_ids:
+ visualize_regions(multiworld.get_region("Menu", player), f"{multiworld.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
diff --git a/WebHostLib/templates/macros.html b/WebHostLib/templates/macros.html
index 0722ee317466..9cb48009a427 100644
--- a/WebHostLib/templates/macros.html
+++ b/WebHostLib/templates/macros.html
@@ -22,7 +22,7 @@
{% for patch in room.seed.slots|list|sort(attribute="player_id") %}
{{ patch.player_id }} |
- {{ patch.player_name }} |
+ {{ patch.player_name }} |
{{ patch.game }} |
{% if patch.data %}
diff --git a/_speedups.pyx b/_speedups.pyx
index 9bf25cce2984..b4167ec5aa1e 100644
--- a/_speedups.pyx
+++ b/_speedups.pyx
@@ -48,6 +48,7 @@ cdef struct IndexEntry:
size_t count
+@cython.auto_pickle(False)
cdef class LocationStore:
"""Compact store for locations and their items in a MultiServer"""
# The original implementation uses Dict[int, Dict[int, Tuple(int, int, int]]
@@ -78,18 +79,6 @@ cdef class LocationStore:
size += sizeof(self._raw_proxies[0]) * self.sender_index_size
return size
- def __cinit__(self, locations_dict: Dict[int, Dict[int, Sequence[int]]]) -> None:
- self._mem = None
- self._keys = None
- self._items = None
- self._proxies = None
- self._len = 0
- self.entries = NULL
- self.entry_count = 0
- self.sender_index = NULL
- self.sender_index_size = 0
- self._raw_proxies = NULL
-
def __init__(self, locations_dict: Dict[int, Dict[int, Sequence[int]]]) -> None:
self._mem = Pool()
cdef object key
@@ -281,6 +270,7 @@ cdef class LocationStore:
entry.location not in checked])
+@cython.auto_pickle(False)
@cython.internal # unsafe. disable direct import
cdef class PlayerLocationProxy:
cdef LocationStore _store
diff --git a/data/basepatch.bsdiff4 b/data/basepatch.bsdiff4
index a578b248f577..aa8f1375c522 100644
Binary files a/data/basepatch.bsdiff4 and b/data/basepatch.bsdiff4 differ
diff --git a/data/lua/connector_bizhawk_generic.lua b/data/lua/connector_bizhawk_generic.lua
index 47af6e003d8e..00021b241f9a 100644
--- a/data/lua/connector_bizhawk_generic.lua
+++ b/data/lua/connector_bizhawk_generic.lua
@@ -22,6 +22,10 @@ SOFTWARE.
local SCRIPT_VERSION = 1
+-- Set to log incoming requests
+-- Will cause lag due to large console output
+local DEBUG = false
+
--[[
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
@@ -271,10 +275,6 @@ 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_FIRST = 43055
local SOCKET_PORT_RANGE_SIZE = 5
local SOCKET_PORT_LAST = SOCKET_PORT_FIRST + SOCKET_PORT_RANGE_SIZE
@@ -330,18 +330,28 @@ function unlock ()
client_socket:settimeout(0)
end
-function process_request (req)
- local res = {}
+request_handlers = {
+ ["PING"] = function (req)
+ local res = {}
- if req["type"] == "PING" then
res["type"] = "PONG"
- elseif req["type"] == "SYSTEM" then
+ return res
+ end,
+
+ ["SYSTEM"] = function (req)
+ local res = {}
+
res["type"] = "SYSTEM_RESPONSE"
res["value"] = emu.getsystemid()
- elseif req["type"] == "PREFERRED_CORES" then
+ return res
+ end,
+
+ ["PREFERRED_CORES"] = function (req)
+ local res = {}
local preferred_cores = client.getconfig().PreferredCores
+
res["type"] = "PREFERRED_CORES_RESPONSE"
res["value"] = {}
res["value"]["NES"] = preferred_cores.NES
@@ -354,14 +364,21 @@ function process_request (req)
res["value"]["PCECD"] = preferred_cores.PCECD
res["value"]["SGX"] = preferred_cores.SGX
- elseif req["type"] == "HASH" then
+ return res
+ end,
+
+ ["HASH"] = function (req)
+ local res = {}
+
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"])
+ return res
+ end,
+ ["GUARD"] = function (req)
+ local res = {}
+ 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
@@ -372,39 +389,83 @@ function process_request (req)
end
end
+ res["type"] = "GUARD_RESPONSE"
res["value"] = data_is_validated
res["address"] = req["address"]
- elseif req["type"] == "LOCK" then
+ return res
+ end,
+
+ ["LOCK"] = function (req)
+ local res = {}
+
res["type"] = "LOCKED"
lock()
- elseif req["type"] == "UNLOCK" then
+ return res
+ end,
+
+ ["UNLOCK"] = function (req)
+ local res = {}
+
res["type"] = "UNLOCKED"
unlock()
- elseif req["type"] == "READ" then
+ return res
+ end,
+
+ ["READ"] = function (req)
+ local res = {}
+
res["type"] = "READ_RESPONSE"
res["value"] = base64.encode(memory.read_bytes_as_array(req["address"], req["size"], req["domain"]))
- elseif req["type"] == "WRITE" then
+ return res
+ end,
+
+ ["WRITE"] = function (req)
+ local res = {}
+
res["type"] = "WRITE_RESPONSE"
memory.write_bytes_as_array(req["address"], base64.decode(req["value"]), req["domain"])
- elseif req["type"] == "DISPLAY_MESSAGE" then
+ return res
+ end,
+
+ ["DISPLAY_MESSAGE"] = function (req)
+ local res = {}
+
res["type"] = "DISPLAY_MESSAGE_RESPONSE"
message_queue:push(req["message"])
- elseif req["type"] == "SET_MESSAGE_INTERVAL" then
+ return res
+ end,
+
+ ["SET_MESSAGE_INTERVAL"] = function (req)
+ local res = {}
+
res["type"] = "SET_MESSAGE_INTERVAL_RESPONSE"
message_interval = req["value"]
- else
+ return res
+ end,
+
+ ["default"] = function (req)
+ local res = {}
+
res["type"] = "ERROR"
res["err"] = "Unknown command: "..req["type"]
- end
- return res
+ return res
+ end,
+}
+
+function process_request (req)
+ if request_handlers[req["type"]] then
+ return request_handlers[req["type"]](req)
+ else
+ return request_handlers["default"](req)
+ end
end
-- Receive data from AP client and send message back
diff --git a/data/options.yaml b/data/options.yaml
index b9bacaa0d103..30bd328f99a0 100644
--- a/data/options.yaml
+++ b/data/options.yaml
@@ -17,10 +17,10 @@
# A. This is a .yaml file. You are allowed to use most characters.
# To test if your yaml is valid or not, you can use this website:
# http://www.yamllint.com/
-# You can also verify your Archipelago settings are valid at this site:
+# You can also verify that your Archipelago options are valid at this site:
# https://archipelago.gg/check
-# Your name in-game. Spaces will be replaced with underscores and there is a 16-character limit.
+# Your name in-game, limited to 16 characters.
# {player} will be replaced with the player's slot number.
# {PLAYER} will be replaced with the player's slot number, if that slot number is greater than 1.
# {number} will be replaced with the counter value of the name.
diff --git a/docs/network protocol.md b/docs/network protocol.md
index d10e6519a93b..9f2c07883b9d 100644
--- a/docs/network protocol.md
+++ b/docs/network protocol.md
@@ -27,6 +27,8 @@ There are also a number of community-supported libraries available that implemen
| Haxe | [hxArchipelago](https://lib.haxe.org/p/hxArchipelago) | |
| Rust | [ArchipelagoRS](https://github.com/ryanisaacg/archipelago_rs) | |
| Lua | [lua-apclientpp](https://github.com/black-sliver/lua-apclientpp) | |
+| Game Maker + Studio 1.x | [gm-apclientpp](https://github.com/black-sliver/gm-apclientpp) | For GM7, GM8 and GMS1.x, maybe older |
+| GameMaker: Studio 2.x+ | [see Discord](https://discord.com/channels/731205301247803413/1166418532519653396) | |
## Synchronizing Items
When the client receives a [ReceivedItems](#ReceivedItems) packet, if the `index` argument does not match the next index that the client expects then it is expected that the client will re-sync items with the server. This can be accomplished by sending the server a [Sync](#Sync) packet and then a [LocationChecks](#LocationChecks) packet.
@@ -325,7 +327,11 @@ Sent to server to inform it of locations that the client has checked. Used to in
| locations | list\[int\] | The ids of the locations checked by the client. May contain any number of checks, even ones sent before; duplicates do not cause issues with the Archipelago server. |
### LocationScouts
-Sent to the server to inform it of locations the client has seen, but not checked. Useful in cases in which the item may appear in the game world, such as 'ledge items' in A Link to the Past. The server will always respond with a [LocationInfo](#LocationInfo) packet with the items located in the scouted location.
+Sent to the server to retrieve the items that are on a specified list of locations. The server will respond with a [LocationInfo](#LocationInfo) packet containing the items located in the scouted locations.
+Fully remote clients without a patch file may use this to "place" items onto their in-game locations, most commonly to display their names or item classifications before/upon pickup.
+
+LocationScouts can also be used to inform the server of locations the client has seen, but not checked. This creates a hint as if the player had run `!hint_location` on a location, but without deducting hint points.
+This is useful in cases where an item appears in the game world, such as 'ledge items' in _A Link to the Past_. To do this, set the `create_as_hint` parameter to a non-zero value.
#### Arguments
| Name | Type | Notes |
@@ -339,7 +345,7 @@ Sent to the server to update on the sender's status. Examples include readiness
#### Arguments
| Name | Type | Notes |
| ---- | ---- | ----- |
-| status | ClientStatus\[int\] | One of [Client States](#Client-States). Send as int. Follow the link for more information. |
+| status | ClientStatus\[int\] | One of [Client States](#ClientStatus). Send as int. Follow the link for more information. |
### Say
Basic chat command which sends text to the server to be distributed to other clients.
diff --git a/docs/options api.md b/docs/options api.md
index 48a3f763fa92..1141528991df 100644
--- a/docs/options api.md
+++ b/docs/options api.md
@@ -24,17 +24,21 @@ display as `Value1` on the webhost.
(i.e. `alias_value_1 = option_value1`) which will allow users to use either `value_1` or `value1` in their yaml
files, and both will resolve as `value1`. This should be used when changing options around, i.e. changing a Toggle to a
Choice, and defining `alias_true = option_full`.
-- All options support `random` as a generic option. `random` chooses from any of the available values for that option,
-and is reserved by AP. You can set this as your default value, but you cannot define your own `option_random`.
+- All options with a fixed set of possible values (i.e. those which inherit from `Toggle`, `(Text)Choice` or
+`(Named/Special)Range`) support `random` as a generic option. `random` chooses from any of the available values for that
+option, and is reserved by AP. You can set this as your default value, but you cannot define your own `option_random`.
+However, you can override `from_text` and handle `text == "random"` to customize its behavior or
+implement it for additional option types.
-As an example, suppose we want an option that lets the user start their game with a sword in their inventory. Let's
-create our option class (with a docstring), give it a `display_name`, and add it to our game's options dataclass:
+As an example, suppose we want an option that lets the user start their game with a sword in their inventory, an option
+to let the player choose the difficulty, and an option to choose how much health the final boss has. Let's create our
+option classes (with a docstring), give them a `display_name`, and add them to our game's options dataclass:
```python
# options.py
from dataclasses import dataclass
-from Options import Toggle, PerGameCommonOptions
+from Options import Toggle, Range, Choice, PerGameCommonOptions
class StartingSword(Toggle):
@@ -42,13 +46,33 @@ class StartingSword(Toggle):
display_name = "Start With Sword"
+class Difficulty(Choice):
+ """Sets overall game difficulty."""
+ display_name = "Difficulty"
+ option_easy = 0
+ option_normal = 1
+ option_hard = 2
+ alias_beginner = 0 # same as easy but allows the player to use beginner as an alternative for easy in the result in their options
+ alias_expert = 2 # same as hard
+ default = 1 # default to normal
+
+
+class FinalBossHP(Range):
+ """Sets the HP of the final boss"""
+ display_name = "Final Boss HP"
+ range_start = 100
+ range_end = 10000
+ default = 2000
+
+
@dataclass
class ExampleGameOptions(PerGameCommonOptions):
starting_sword: StartingSword
+ difficulty: Difficulty
+ final_boss_health: FinalBossHP
```
-This will create a `Toggle` option, internally called `starting_sword`. To then submit this to the multiworld, we add it
-to our world's `__init__.py`:
+To then submit this to the multiworld, we add it to our world's `__init__.py`:
```python
from worlds.AutoWorld import World
diff --git a/docs/settings api.md b/docs/settings api.md
index f9cbe5e021cc..41023879adf8 100644
--- a/docs/settings api.md
+++ b/docs/settings api.md
@@ -121,6 +121,10 @@ Path to a single file. Automatically resolves as user_path:
Source folder or AP install path on Windows. ~/Archipelago for the AppImage.
Will open a file browser if the file is missing when in GUI mode.
+If the file is used in the world's `generate_output`, make sure to add a `stage_assert_generate` that checks if the
+file is available, otherwise generation may fail at the very end.
+See also [world api.md](https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/world%20api.md#generation).
+
#### class method validate(cls, path: str)
Override this and raise ValueError if validation fails.
diff --git a/docs/style.md b/docs/style.md
index 4cc8111425e3..fbf681f28e97 100644
--- a/docs/style.md
+++ b/docs/style.md
@@ -6,7 +6,6 @@
* 120 character per line for all source files.
* Avoid white space errors like trailing spaces.
-
## Python Code
* We mostly follow [PEP8](https://peps.python.org/pep-0008/). Read below to see the differences.
@@ -18,9 +17,10 @@
* Use type annotations where possible for function signatures and class members.
* Use type annotations where appropriate for local variables (e.g. `var: List[int] = []`, or when the
type is hard or impossible to deduce.) Clear annotations help developers look up and validate API calls.
+* New classes, attributes, and methods in core code should have docstrings that follow
+ [reST style](https://peps.python.org/pep-0287/).
* Worlds that do not follow PEP8 should still have a consistent style across its files to make reading easier.
-
## Markdown
* We almost follow [Google's styleguide](https://google.github.io/styleguide/docguide/style.html).
@@ -30,20 +30,17 @@
* One space between bullet/number and text.
* No lazy numbering.
-
## HTML
* Indent with 2 spaces for new code.
* kebab-case for ids and classes.
-
## CSS
* Indent with 2 spaces for new code.
* `{` on the same line as the selector.
* No space between selector and `{`.
-
## JS
* Indent with 2 spaces.
diff --git a/docs/world api.md b/docs/world api.md
index 0ab06da65603..f82ef40a98f8 100644
--- a/docs/world api.md
+++ b/docs/world api.md
@@ -1,95 +1,95 @@
# Archipelago API
-This document tries to explain some internals required to implement a game for
-Archipelago's generation and server. Once a seed is generated, a client or mod is
-required to send and receive items between the game and server.
+This document tries to explain some aspects of the Archipelago World API used when implementing the generation logic of
+a game.
-Client implementation is out of scope of this document. Please refer to an
-existing game that provides a similar API to yours.
-Refer to the following documents as well:
-- [network protocol.md](https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/network%20protocol.md)
-- [adding games.md](https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/adding%20games.md)
+Client implementation is out of scope of this document. Please refer to an existing game that provides a similar API to
+yours, and the following documents:
-Archipelago will be abbreviated as "AP" from now on.
+* [network protocol.md](https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/network%20protocol.md)
+* [adding games.md](https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/adding%20games.md)
+Archipelago will be abbreviated as "AP" from now on.
## Language
AP worlds are written in python3.
-Clients that connect to the server to sync items can be in any language that
-allows using WebSockets.
-
+Clients that connect to the server to sync items can be in any language that allows using WebSockets.
## Coding style
-AP follows [style.md](https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/style.md).
-When in doubt use an IDE with coding style linter, for example PyCharm Community Edition.
-
+AP follows a [style guide](https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/style.md).
+When in doubt, use an IDE with a code-style linter, for example PyCharm Community Edition.
## Docstrings
-Docstrings are strings attached to an object in Python that describe what the
-object is supposed to be. Certain docstrings will be picked up and used by AP.
-They are assigned by writing a string without any assignment right below a
-definition. The string must be a triple-quoted string.
+Docstrings are strings attached to an object in Python that describe what the object is supposed to be. Certain
+docstrings will be picked up and used by AP. They are assigned by writing a string without any assignment right below a
+definition. The string must be a triple-quoted string, and should
+follow [reST style](https://peps.python.org/pep-0287/).
+
Example:
+
```python
from worlds.AutoWorld import World
+
+
class MyGameWorld(World):
- """This is the description of My Game that will be displayed on the AP
- website."""
+ """This is the description of My Game that will be displayed on the AP website."""
```
-
## Definitions
-This section will cover various classes and objects you can use for your world.
-While some of the attributes and methods are mentioned here, not all of them are,
-but you can find them in `BaseClasses.py`.
+This section covers various classes and objects you can use for your world. While some of the attributes and methods
+are mentioned here, not all of them are, but you can find them in
+[`BaseClasses.py`](https://github.com/ArchipelagoMW/Archipelago/blob/main/BaseClasses.py).
### World Class
-A `World` class is the class with all the specifics of a certain game to be
-included. It will be instantiated for each player that rolls a seed for that
-game.
+A `World` is the class with all the specifics of a certain game that is to be included. A new instance will be created
+for each player of the game for any given generated multiworld.
### WebWorld Class
-A `WebWorld` class contains specific attributes and methods that can be modified
-for your world specifically on the webhost:
+A `WebWorld` class contains specific attributes and methods that can be modified for your world specifically on the
+webhost:
-`settings_page`, which can be changed to a link instead of an AP generated settings page.
+* `options_page` can be changed to a link instead of an AP-generated options page.
-`theme` to be used for your game specific AP pages. Available themes:
+* `theme` to be used for your game-specific AP pages. Available themes:
-| dirt | grass (default) | grassFlowers | ice | jungle | ocean | partyTime | stone |
-|---|---|---|---|---|---|---|---|
-| | | | | | | | |
+ | dirt | grass (default) | grassFlowers | ice | jungle | ocean | partyTime | stone |
+ |--------------------------------------------|---------------------------------------------|----------------------------------------------------|-------------------------------------------|----------------------------------------------|---------------------------------------------|-------------------------------------------------|---------------------------------------------|
+ | | | | | | | | |
-`bug_report_page` (optional) can be a link to a bug reporting page, most likely a GitHub issue page, that will be placed by the site to help direct users to report bugs.
+* `bug_report_page` (optional) can be a link to a bug reporting page, most likely a GitHub issue page, that will be
+ placed by the site to help users report bugs.
-`tutorials` list of `Tutorial` classes where each class represents a guide to be generated on the webhost.
+* `tutorials` list of `Tutorial` classes where each class represents a guide to be generated on the webhost.
-`game_info_languages` (optional) List of strings for defining the existing gameinfo pages your game supports. The documents must be
-prefixed with the same string as defined here. Default already has 'en'.
+* `game_info_languages` (optional) list of strings for defining the existing game info pages your game supports. The
+ documents must be prefixed with the same string as defined here. Default already has 'en'.
-`options_presets` (optional) A `Dict[str, Dict[str, Any]]` where the keys are the names of the presets and the values
-are the options to be set for that preset. The options are defined as a `Dict[str, Any]` where the keys are the names of
-the options and the values are the values to be set for that option. These presets will be available for users to select from on the game's options page.
+* `options_presets` (optional) `Dict[str, Dict[str, Any]]` where the keys are the names of the presets and the values
+ are the options to be set for that preset. The options are defined as a `Dict[str, Any]` where the keys are the names
+ of the options and the values are the values to be set for that option. These presets will be available for users to
+ select from on the game's options page.
Note: The values must be a non-aliased value for the option type and can only include the following option types:
- - If you have a `Range`/`NamedRange` option, the value should be an `int` between the `range_start` and `range_end`
- values.
- - If you have a `NamedRange` option, the value can alternatively be a `str` that is one of the
+* If you have a `Range`/`NamedRange` option, the value should be an `int` between the `range_start` and `range_end`
+ values.
+ * If you have a `NamedRange` option, the value can alternatively be a `str` that is one of the
`special_range_names` keys.
- - If you have a `Choice` option, the value should be a `str` that is one of the `option_` values.
- - If you have a `Toggle`/`DefaultOnToggle` option, the value should be a `bool`.
- - `random` is also a valid value for any of these option types.
+* If you have a `Choice` option, the value should be a `str` that is one of the `option_` values.
+* If you have a `Toggle`/`DefaultOnToggle` option, the value should be a `bool`.
+* `random` is also a valid value for any of these option types.
-`OptionDict`, `OptionList`, `OptionSet`, `FreeText`, or custom `Option`-derived classes are not supported for presets on the webhost at this time.
+`OptionDict`, `OptionList`, `OptionSet`, `FreeText`, or custom `Option`-derived classes are not supported for presets on
+the webhost at this time.
Here is an example of a defined preset:
+
```python
# presets.py
options_presets = {
@@ -114,6 +114,7 @@ options_presets = {
}
}
+
# __init__.py
class RLWeb(WebWorld):
options_presets = options_presets
@@ -122,47 +123,56 @@ class RLWeb(WebWorld):
### MultiWorld Object
-The `MultiWorld` object references the whole multiworld (all items and locations
-for all players) and is accessible through `self.multiworld` inside a `World` object.
+The `MultiWorld` object references the whole multiworld (all items and locations for all players) and is accessible
+through `self.multiworld` from your `World` object.
### Player
-The player is just an integer in AP and is accessible through `self.player`
-inside a `World` object.
+The player is just an `int` in AP and is accessible through `self.player` from your `World` object.
### Player Options
-Players provide customized settings for their World in the form of yamls.
-A `dataclass` of valid options definitions has to be provided in `self.options_dataclass`.
-(It must be a subclass of `PerGameCommonOptions`.)
-Option results are automatically added to the `World` object for easy access.
-Those are accessible through `self.options.`, and you can get a dictionary of the option values via
-`self.options.as_dict()`, passing the desired options as strings.
+Options are provided by the user as part of the generation process, intended to affect how their randomizer experience
+should play out. These can control aspects such as what locations should be shuffled, what items are in the itempool,
+etc. Players provide the customized options for their World in the form of yamls.
+
+By convention, options are defined in `options.py` and will be used when parsing the players' yaml files. Each option
+has its own class, which inherits from a base option type, a docstring to describe it, and a `display_name` property
+shown on the website and in spoiler logs.
+
+The available options are defined by creating a `dataclass`, which must be a subclass of `PerGameCommonOptions`. It has
+defined fields for the option names used in the player yamls and used for options access, with their types matching the
+appropriate Option class. By convention, the strings that define your option names should be in `snake_case`. The
+`dataclass` is then assigned to your `World` by defining its `options_dataclass`. Option results are then automatically
+added to the `World` object for easy access, between `World` creation and `generate_early`. These are accessible through
+`self.options.`, and you can get a dictionary with option values
+via `self.options.as_dict()`,
+passing the desired option names as strings.
+
+Common option types are `Toggle`, `DefaultOnToggle`, `Choice`, and `Range`.
+For more information, see the [options api doc](options%20api.md).
### World Settings
-Any AP installation can provide settings for a world, for example a ROM file, accessible through
-`self.settings.` or `cls.settings.` (new API)
-or `Utils.get_options()["_options"][""]` (deprecated).
+Settings are set by the user outside the generation process. They can be used for those settings that may affect
+generation or client behavior, but should remain static between generations, such as the path to a ROM file.
+These settings are accessible through `self.settings.` or `cls.settings.`.
-Users can set those in their `host.yaml` file. Some settings may automatically open a file browser if a file is missing.
+Users can set these in their `host.yaml` file. Some settings may automatically open a file browser if a file is missing.
-Refer to [settings api.md](https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/settings%20api.md)
-for details.
+Refer to [settings api.md](https://github.com/ArchipelagoMW/Archipelago/blob/main/docs/settings%20api.md) for details.
### Locations
-Locations are places where items can be located in your game. This may be chests
-or boss drops for RPG-like games but could also be progress in a research tree.
+Locations are places where items can be located in your game. This may be chests or boss drops for RPG-like games, but
+could also be progress in a research tree, or even something more abstract like a level up.
-Each location has a `name` and an `id` (a.k.a. "code" or "address"), is placed
-in a Region, has access rules and a classification.
-The name needs to be unique in each game and must not be numeric (has to
-contain least 1 letter or symbol). The ID needs to be unique across all games
-and is best in the same range as the item IDs.
-World-specific IDs are 1 to 253-1, IDs ≤ 0 are global and reserved.
+Each location has a `name` and an `address` (hereafter referred to as an `id`), is placed in a Region, has access rules,
+and has a classification. The name needs to be unique within each game and must not be numeric (must contain least 1
+letter or symbol). The ID needs to be unique across all games, and is best kept in the same range as the item IDs.
+Locations and items can share IDs, so typically a game's locations and items start at the same ID.
-Special locations with ID `None` can hold events.
+World-specific IDs must be in the range 1 to 253-1; IDs ≤ 0 are global and reserved.
Classification is one of `LocationProgressType.DEFAULT`, `PRIORITY` or `EXCLUDED`.
The Fill algorithm will force progression items to be placed at priority locations, giving a higher chance of them being
@@ -170,22 +180,21 @@ required, and will prevent progression and useful items from being placed at exc
#### Documenting Locations
-Worlds can optionally provide a `location_descriptions` map which contains
-human-friendly descriptions of locations or location groups. These descriptions
-will show up in location-selection options in the Weighted Options page. Extra
+Worlds can optionally provide a `location_descriptions` map which contains human-friendly descriptions of locations and
+location groups. These descriptions will show up in location-selection options on the Weighted Options page. Extra
indentation and single newlines will be collapsed into spaces.
```python
-# Locations.py
+# locations.py
location_descriptions = {
"Red Potion #6": "In a secret destructible block under the second stairway",
- "L2 Spaceship": """
- The group of all items in the spaceship in Level 2.
+ "L2 Spaceship":
+ """
+ The group of all items in the spaceship in Level 2.
- This doesn't include the item on the spaceship door, since it can be
- accessed without the Spaeship Key.
- """
+ This doesn't include the item on the spaceship door, since it can be accessed without the Spaceship Key.
+ """
}
```
@@ -193,7 +202,7 @@ location_descriptions = {
# __init__.py
from worlds.AutoWorld import World
-from .Locations import location_descriptions
+from .locations import location_descriptions
class MyGameWorld(World):
@@ -202,47 +211,45 @@ class MyGameWorld(World):
### Items
-Items are all things that can "drop" for your game. This may be RPG items like
-weapons, could as well be technologies you normally research in a research tree.
+Items are all things that can "drop" for your game. This may be RPG items like weapons, or technologies you normally
+research in a research tree.
-Each item has a `name`, an `id` (can be known as "code"), and a classification.
-The most important classification is `progression` (formerly advancement).
-Progression items are items which a player may require to progress in
-their world. Progression items will be assigned to locations with higher
-priority and moved around to meet defined rules and accomplish progression
-balancing.
+Each item has a `name`, a `code` (hereafter referred to as `id`), and a classification.
+The most important classification is `progression`. Progression items are items which a player *may* require to progress
+in their world. If an item can possibly be considered for logic (it's referenced in a location's rules) it *must* be
+progression. Progression items will be assigned to locations with higher priority, and moved around to meet defined rules
+and satisfy progression balancing.
-The name needs to be unique in each game, meaning a duplicate item has the
-same ID. Name must not be numeric (has to contain at least 1 letter or symbol).
+The name needs to be unique within each game, meaning if you need to create multiple items with the same name, they
+will all have the same ID. Name must not be numeric (must contain at least 1 letter or symbol).
-Special items with ID `None` can mark events (read below).
+Other classifications include:
-Other classifications include
* `filler`: a regular item or trash item
-* `useful`: generally quite useful, but not required for anything logical
+* `useful`: generally quite useful, but not required for anything logical. Cannot be placed on excluded locations
* `trap`: negative impact on the player
* `skip_balancing`: denotes that an item should not be moved to an earlier sphere for the purpose of balancing (to be
combined with `progression`; see below)
* `progression_skip_balancing`: the combination of `progression` and `skip_balancing`, i.e., a progression item that
- will not be moved around by progression balancing; used, e.g., for currency or tokens
+ will not be moved around by progression balancing; used, e.g., for currency or tokens, to not flood early spheres
#### Documenting Items
-Worlds can optionally provide an `item_descriptions` map which contains
-human-friendly descriptions of items or item groups. These descriptions will
-show up in item-selection options in the Weighted Options page. Extra
-indentation and single newlines will be collapsed into spaces.
+Worlds can optionally provide an `item_descriptions` map which contains human-friendly descriptions of items and item
+groups. These descriptions will show up in item-selection options on the Weighted Options page. Extra indentation and
+single newlines will be collapsed into spaces.
```python
-# Items.py
+# items.py
item_descriptions = {
"Red Potion": "A standard health potion",
- "Spaceship Key": """
- The key to the spaceship in Level 2.
+ "Spaceship Key":
+ """
+ The key to the spaceship in Level 2.
- This is necessary to get to the Star Realm.
- """
+ This is necessary to get to the Star Realm.
+ """
}
```
@@ -250,7 +257,7 @@ item_descriptions = {
# __init__.py
from worlds.AutoWorld import World
-from .Items import item_descriptions
+from .items import item_descriptions
class MyGameWorld(World):
@@ -259,215 +266,128 @@ class MyGameWorld(World):
### Events
-Events will mark some progress. You define an event location, an
-event item, strap some rules to the location (i.e. hold certain
-items) and manually place the event item at the event location.
+An Event is a special combination of a Location and an Item, with both having an `id` of `None`. These can be used to
+track certain logic interactions, with the Event Item being required for access in other locations or regions, but not
+being "real". Since the item and location have no ID, they get dropped at the end of generation and so the server is
+never made aware of them and these locations can never be checked, nor can the items be received during play.
+They may also be used for making the spoiler log look nicer, i.e. by having a `"Victory"` Event Item, that
+is required to finish the game. This makes it very clear when the player finishes, rather than only seeing their last
+relevant Item. Events function just like any other Location, and can still have their own access rules, etc.
+By convention, the Event "pair" of Location and Item typically have the same name, though this is not a requirement.
+They must not exist in the `name_to_id` lookups, as they have no ID.
+
+The most common way to create an Event pair is to create and place the Item on the Location as soon as it's created:
-Events can be used to either simplify the logic or to get better spoiler logs.
-Events will show up in the spoiler playthrough but they do not represent actual
-items or locations within the game.
+```python
+from worlds.AutoWorld import World
+from BaseClasses import ItemClassification
+from .subclasses import MyGameLocation, MyGameItem
-There is one special case for events: Victory. To get the win condition to show
-up in the spoiler log, you create an event item and place it at an event
-location with the `access_rules` for game completion. Once that's done, the
-world's win condition can be as simple as checking for that item.
-By convention the victory event is called `"Victory"`. It can be placed at one
-or more event locations based on player options.
+class MyGameWorld(World):
+ victory_loc = MyGameLocation(self.player, "Victory", None)
+ victory_loc.place_locked_item(MyGameItem("Victory", ItemClassification.progression, None, self.player))
+```
### Regions
-Regions are logical groups of locations that share some common access rules. If
-location logic is written from scratch, using regions greatly simplifies the
-definition and allows to somewhat easily implement things like entrance
-randomizer in logic.
+Regions are logical containers that typically hold locations that share some common access rules. If location logic is
+written from scratch, using regions greatly simplifies the requirements and can help with implementing things
+like entrance randomization in logic.
-Regions have a list called `exits`, which are `Entrance` objects representing
-transitions to other regions.
+Regions have a list called `exits`, containing `Entrance` objects representing transitions to other regions.
-There has to be one special region "Menu" from which the logic unfolds. AP
-assumes that a player will always be able to return to the "Menu" region by
-resetting the game ("Save and quit").
+There must be one special region, "Menu", from which the logic unfolds. AP assumes that a player will always be able to
+return to the "Menu" region by resetting the game ("Save and quit").
### Entrances
-An `Entrance` connects to a region, is assigned to region's exits and has rules
-to define if it and thus the connected region is accessible.
-They can be static (regular logic) or be defined/connected during generation
-(entrance randomizer).
+An `Entrance` has a `parent_region` and `connected_region`, where it is in the `exits` of its parent, and the
+`entrances` of its connected region. The `Entrance` then has rules assigned to it to determine if it can be passed
+through, making the connected region accessible. They can be static (regular logic) or be defined/connected during
+generation (entrance randomization).
### Access Rules
-An access rule is a function that returns `True` or `False` for a `Location` or
-`Entrance` based on the current `state` (items that can be collected).
+An access rule is a function that returns `True` or `False` for a `Location` or `Entrance` based on the current `state`
+(items that have been collected).
### Item Rules
-An item rule is a function that returns `True` or `False` for a `Location` based
-on a single item. It can be used to reject placement of an item there.
-
+An item rule is a function that returns `True` or `False` for a `Location` based on a single item. It can be used to
+reject the placement of an item there.
## Implementation
### Your World
-All code for your world implementation should be placed in a python package in
-the `/worlds` directory. The starting point for the package is `__init__.py`.
-Conventionally, your world class is placed in that file.
+All code for your world implementation should be placed in a python package in the `/worlds` directory. The starting
+point for the package is `__init__.py`. Conventionally, your `World` class is placed in that file.
-World classes must inherit from the `World` class in `/worlds/AutoWorld.py`,
-which can be imported as `from worlds.AutoWorld import World` from your package.
+World classes must inherit from the `World` class in `/worlds/AutoWorld.py`, which can be imported as
+`from worlds.AutoWorld import World` from your package.
AP will pick up your world automatically due to the `AutoWorld` implementation.
### Requirements
-If your world needs specific python packages, they can be listed in
-`worlds//requirements.txt`. ModuleUpdate.py will automatically
-pick up and install them.
+If your world needs specific python packages, they can be listed in `worlds//requirements.txt`.
+ModuleUpdate.py will automatically pick up and install them.
See [pip documentation](https://pip.pypa.io/en/stable/cli/pip_install/#requirements-file-format).
### Relative Imports
-AP will only import the `__init__.py`. Depending on code size it makes sense to
-use multiple files and use relative imports to access them.
+AP will only import the `__init__.py`. Depending on code size, it may make sense to use multiple files and use relative
+imports to access them.
-e.g. `from .options import MyGameOptions` from your `__init__.py` will load
-`world/[world_name]/options.py` and make its `MyGameOptions` accessible.
+e.g. `from .options import MyGameOptions` from your `__init__.py` will load `world/[world_name]/options.py` and make
+its `MyGameOptions` accessible.
-When imported names pile up it may be easier to use `from . import options`
-and access the variable as `options.MyGameOptions`.
+When imported names pile up, it may be easier to use `from . import options` and access the variable as
+`options.MyGameOptions`.
-Imports from directories outside your world should use absolute imports.
-Correct use of relative / absolute imports is required for zipped worlds to
-function, see [apworld specification.md](apworld%20specification.md).
+Imports from directories outside your world should use absolute imports. Correct use of relative / absolute imports is
+required for zipped worlds to function, see [apworld specification.md](apworld%20specification.md).
### Your Item Type
-Each world uses its own subclass of `BaseClasses.Item`. The constructor can be
-overridden to attach additional data to it, e.g. "price in shop".
-Since the constructor is only ever called from your code, you can add whatever
-arguments you like to the constructor.
+Each world uses its own subclass of `BaseClasses.Item`. The constructor can be overridden to attach additional data to
+it, e.g. "price in shop". Since the constructor is only ever called from your code, you can add whatever arguments you
+like to the constructor.
+
+In its simplest form, we only set the game name and use the default constructor:
-In its simplest form we only set the game name and use the default constructor
```python
from BaseClasses import Item
+
class MyGameItem(Item):
game: str = "My Game"
```
-By convention this class definition will either be placed in your `__init__.py`
-or your `items.py`. For a more elaborate example see `worlds/oot/Items.py`.
-### Your location type
+By convention, this class definition will either be placed in your `__init__.py` or your `items.py`. For a more
+elaborate example see
+[`worlds/oot/Items.py`](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/oot/Items.py).
+
+### Your Location Type
+
+The same thing we did for items above, we will now do for locations:
-The same we have done for items above, we will do for locations
```python
from BaseClasses import Location
+
class MyGameLocation(Location):
game: str = "My Game"
# override constructor to automatically mark event locations as such
- def __init__(self, player: int, name = "", code = None, parent = None) -> None:
+ def __init__(self, player: int, name="", code=None, parent=None) -> None:
super(MyGameLocation, self).__init__(player, name, code, parent)
self.event = code is None
```
-in your `__init__.py` or your `locations.py`.
-
-### Options
-
-By convention options are defined in `options.py` and will be used when parsing
-the players' yaml files.
-
-Each option has its own class, inherits from a base option type, has a docstring
-to describe it and a `display_name` property for display on the website and in
-spoiler logs.
-
-The actual name as used in the yaml is defined via the field names of a `dataclass` that is
-assigned to the world under `self.options_dataclass`. By convention, the strings
-that define your option names should be in `snake_case`.
-
-Common option types are `Toggle`, `DefaultOnToggle`, `Choice`, `Range`.
-For more see `Options.py` in AP's base directory.
-
-#### Toggle, DefaultOnToggle
-
-These don't need any additional properties defined. After parsing the option,
-its `value` will either be True or False.
-
-#### Range
-
-Define properties `range_start`, `range_end` and `default`. Ranges will be
-displayed as sliders on the website and can be set to random in the yaml.
-
-#### Choice
-
-Choices are like toggles, but have more options than just True and False.
-Define a property `option_ = ` per selectable value and
-`default = ` to set the default selection. Aliases can be set by
-defining a property `alias_ = `.
-
-```python
-option_off = 0
-option_on = 1
-option_some = 2
-alias_disabled = 0
-alias_enabled = 1
-default = 0
-```
-
-#### Sample
-```python
-# options.py
-
-from dataclasses import dataclass
-from Options import Toggle, Range, Choice, PerGameCommonOptions
-
-class Difficulty(Choice):
- """Sets overall game difficulty."""
- display_name = "Difficulty"
- option_easy = 0
- option_normal = 1
- option_hard = 2
- alias_beginner = 0 # same as easy
- alias_expert = 2 # same as hard
- default = 1 # default to normal
-
-class FinalBossHP(Range):
- """Sets the HP of the final boss"""
- display_name = "Final Boss HP"
- range_start = 100
- range_end = 10000
- default = 2000
-
-class FixXYZGlitch(Toggle):
- """Fixes ABC when you do XYZ"""
- display_name = "Fix XYZ Glitch"
-
-# By convention, we call the options dataclass `Options`.
-# It has to be derived from 'PerGameCommonOptions'.
-@dataclass
-class MyGameOptions(PerGameCommonOptions):
- difficulty: Difficulty
- final_boss_hp: FinalBossHP
- fix_xyz_glitch: FixXYZGlitch
-```
-
-```python
-# __init__.py
-
-from worlds.AutoWorld import World
-from .options import MyGameOptions # import the options dataclass
-
-class MyGameWorld(World):
- # ...
- options_dataclass = MyGameOptions # assign the options dataclass to the world
- options: MyGameOptions # typing for option results
- # ...
-```
+in your `__init__.py` or your `locations.py`.
### A World Class Skeleton
@@ -483,7 +403,6 @@ from worlds.AutoWorld import World
from BaseClasses import Region, Location, Entrance, Item, RegionType, ItemClassification
-
class MyGameItem(Item): # or from Items import MyGameItem
game = "My Game" # name of the game/world this item is from
@@ -492,7 +411,6 @@ class MyGameLocation(Location): # or from Locations import MyGameLocation
game = "My Game" # name of the game/world this location is in
-
class MyGameSettings(settings.Group):
class RomFile(settings.SNESRomPath):
"""Insert help text for host.yaml here."""
@@ -511,7 +429,7 @@ class MyGameWorld(World):
# ID of first item and location, could be hard-coded but code may be easier
# to read with this as a property.
base_id = 1234
- # Instead of dynamic numbering, IDs could be part of data.
+ # instead of dynamic numbering, IDs could be part of data
# The following two dicts are required for the generation to know which
# items exist. They could be generated from json or something else. They can
@@ -530,74 +448,106 @@ class MyGameWorld(World):
### Generation
-The world has to provide the following things for generation
+The world has to provide the following things for generation:
-* the properties mentioned above
+* the properties mentioned above
* additions to the item pool
* additions to the regions list: at least one called "Menu"
* locations placed inside those regions
* a `def create_item(self, item: str) -> MyGameItem` to create any item on demand
-* applying `self.multiworld.push_precollected` for world defined start inventory
-* `required_client_version: Tuple[int, int, int]`
- Optional client version as tuple of 3 ints to make sure the client is compatible to
- this world (e.g. implements all required features) when connecting.
+* applying `self.multiworld.push_precollected` for world-defined start inventory
-In addition, the following methods can be implemented and are called in this order during generation
+In addition, the following methods can be implemented and are called in this order during generation:
-* `stage_assert_generate(cls, multiworld)` is a class method called at the start of
- generation to check the existence of prerequisite files, usually a ROM for
+* `stage_assert_generate(cls, multiworld: MultiWorld)`
+ a class method called at the start of generation to check for the existence of prerequisite files, usually a ROM for
games which require one.
* `generate_early(self)`
- called per player before any items or locations are created. You can set properties on your world here. Already has
- access to player options and RNG. This is the earliest step where the world should start setting up for the current
- multiworld as any steps before this, the multiworld itself is still getting set up
+ called per player before any items or locations are created. You can set properties on your
+ world here. Already has access to player options and RNG. This is the earliest step where the world should start
+ setting up for the current multiworld, as the multiworld itself is still setting up before this point.
* `create_regions(self)`
- called to place player's regions and their locations into the MultiWorld's regions list. If it's
- hard to separate, this can be done during `generate_early` or `create_items` as well.
+ called to place player's regions and their locations into the MultiWorld's regions list.
+ If it's hard to separate, this can be done during `generate_early` or `create_items` as well.
* `create_items(self)`
- called to place player's items into the MultiWorld's itempool. After this step all regions and items have to be in
- the MultiWorld's regions and itempool, and these lists should not be modified afterwards.
+ called to place player's items into the MultiWorld's itempool. After this step all regions
+ and items have to be in the MultiWorld's regions and itempool, and these lists should not be modified afterward.
* `set_rules(self)`
- called to set access and item rules on locations and entrances.
- Locations have to be defined before this, or rule application can miss them.
+ called to set access and item rules on locations and entrances.
* `generate_basic(self)`
- called after the previous steps. Some placement and player specific
- randomizations can be done here.
-* `pre_fill(self)`, `fill_hook(self)` and `post_fill(self)` are called to modify item placement
- before, during and after the regular fill process, before `generate_output`.
- If items need to be placed during pre_fill, these items can be determined
- and created using `get_prefill_items`
-* `generate_output(self, output_directory: str)` that creates the output
- files if there is output to be generated. When this is
- called, `self.multiworld.get_locations(self.player)` has all locations for the player, with
- attribute `item` pointing to the item.
- `location.item.player` can be used to see if it's a local item.
+ player-specific randomization that does not affect logic can be done here.
+* `pre_fill(self)`, `fill_hook(self)` and `post_fill(self)`
+ called to modify item placement before, during, and after the regular fill process; all finishing before
+ `generate_output`. Any items that need to be placed during `pre_fill` should not exist in the itempool, and if there
+ are any items that need to be filled this way, but need to be in state while you fill other items, they can be
+ returned from `get_prefill_items`.
+* `generate_output(self, output_directory: str)`
+ creates the output files if there is output to be generated. When this is called,
+ `self.multiworld.get_locations(self.player)` has all locations for the player, with attribute `item` pointing to the
+ item. `location.item.player` can be used to see if it's a local item.
* `fill_slot_data(self)` and `modify_multidata(self, multidata: Dict[str, Any])` can be used to modify the data that
will be used by the server to host the MultiWorld.
+All instance methods can, optionally, have a class method defined which will be called after all instance methods are
+finished running, by defining a method with `stage_` in front of the method name. These class methods will have the
+args `(cls, multiworld: MultiWorld)`, followed by any other args that the relevant instance method has.
#### generate_early
```python
def generate_early(self) -> None:
- # read player settings to world instance
+ # read player options to world instance
self.final_boss_hp = self.options.final_boss_hp.value
```
+#### create_regions
+
+```python
+def create_regions(self) -> None:
+ # Add regions to the multiworld. "Menu" is the required starting point.
+ # Arguments to Region() are name, player, multiworld, and optionally hint_text
+ menu_region = Region("Menu", self.player, self.multiworld)
+ self.multiworld.regions.append(menu_region) # or use += [menu_region...]
+
+ main_region = Region("Main Area", self.player, self.multiworld)
+ # add main area's locations to main area (all but final boss)
+ main_region.add_locations(main_region_locations, MyGameLocation)
+ # or
+ # main_region.locations = \
+ # [MyGameLocation(self.player, location_name, self.location_name_to_id[location_name], main_region]
+ self.multiworld.regions.append(main_region)
+
+ boss_region = Region("Boss Room", self.player, self.multiworld)
+ # add event to Boss Room
+ boss_region.locations.append(MyGameLocation(self.player, "Final Boss", None, boss_region))
+
+ # if entrances are not randomized, they should be connected here, otherwise they can also be connected at a later stage
+ # create Entrances and connect the Regions
+ menu_region.connect(main_region) # connects the "Menu" and "Main Area", can also pass a rule
+ # or
+ main_region.add_exits({"Boss Room": "Boss Door"}, {"Boss Room": lambda state: state.has("Sword", self.player)})
+ # connects the "Main Area" and "Boss Room" regions, and places a rule requiring the "Sword" item to traverse
+
+ # if setting location access rules from data is easier here, set_rules can possibly be omitted
+```
+
#### create_item
```python
-# we need a way to know if an item provides progress in the game ("key item")
-# this can be part of the items definition, or depend on recipe randomization
+# we need a way to know if an item provides progress in the game ("key item") this can be part of the items definition,
+# or depend on recipe randomization
from .items import is_progression # this is just a dummy
+
def create_item(self, item: str) -> MyGameItem:
- # This is called when AP wants to create an item by name (for plando) or
- # when you call it from your own code.
- classification = ItemClassification.progression if is_progression(item) else \
- ItemClassification.filler
- return MyGameItem(item, classification, self.item_name_to_id[item],
- self.player)
+ # this is called when AP wants to create an item by name (for plando) or when you call it from your own code
+ classification = ItemClassification.progression if is_progression(item) else
+ ItemClassification.filler
+
+
+return MyGameItem(item, classification, self.item_name_to_id[item],
+ self.player)
+
def create_event(self, event: str) -> MyGameItem:
# while we are at it, we can also add a helper to create events
@@ -610,8 +560,7 @@ def create_event(self, event: str) -> MyGameItem:
def create_items(self) -> None:
# Add items to the Multiworld.
# If there are two of the same item, the item has to be twice in the pool.
- # Which items are added to the pool may depend on player settings,
- # e.g. custom win condition like triforce hunt.
+ # Which items are added to the pool may depend on player options, e.g. custom win condition like triforce hunt.
# Having an item in the start inventory won't remove it from the pool.
# If an item can't have duplicates it has to be excluded manually.
@@ -627,67 +576,10 @@ def create_items(self) -> None:
# itempool and number of locations should match up.
# If this is not the case we want to fill the itempool with junk.
- junk = 0 # calculate this based on player settings
+ junk = 0 # calculate this based on player options
self.multiworld.itempool += [self.create_item("nothing") for _ in range(junk)]
```
-#### create_regions
-
-```python
-def create_regions(self) -> None:
- # Add regions to the multiworld. "Menu" is the required starting point.
- # Arguments to Region() are name, player, world, and optionally hint_text
- menu_region = Region("Menu", self.player, self.multiworld)
- self.multiworld.regions.append(menu_region) # or use += [menu_region...]
-
- main_region = Region("Main Area", self.player, self.multiworld)
- # Add main area's locations to main area (all but final boss)
- main_region.add_locations(main_region_locations, MyGameLocation)
- # or
- # main_region.locations = \
- # [MyGameLocation(self.player, location_name, self.location_name_to_id[location_name], main_region]
- self.multiworld.regions.append(main_region)
-
- boss_region = Region("Boss Room", self.player, self.multiworld)
- # Add event to Boss Room
- boss_region.locations.append(MyGameLocation(self.player, "Final Boss", None, boss_region))
-
- # If entrances are not randomized, they should be connected here,
- # otherwise they can also be connected at a later stage.
- # Create Entrances and connect the Regions
- menu_region.connect(main_region) # connects the "Menu" and "Main Area", can also pass a rule
- # or
- main_region.add_exits({"Boss Room": "Boss Door"}, {"Boss Room": lambda state: state.has("Sword", self.player)})
- # Connects the "Main Area" and "Boss Room" regions, and places a rule requiring the "Sword" item to traverse
-
- # If setting location access rules from data is easier here, set_rules can
- # possibly omitted.
-```
-
-#### generate_basic
-
-```python
-def generate_basic(self) -> None:
- # place "Victory" at "Final Boss" and set collection as win condition
- self.multiworld.get_location("Final Boss", self.player)
- .place_locked_item(self.create_event("Victory"))
- self.multiworld.completion_condition[self.player] =
- lambda state: state.has("Victory", self.player)
-
- # place item Herb into location Chest1 for some reason
- item = self.create_item("Herb")
- self.multiworld.get_location("Chest1", self.player).place_locked_item(item)
- # 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
```python
@@ -703,6 +595,7 @@ def set_rules(self) -> None:
# set a simple rule for an region
set_rule(self.multiworld.get_entrance("Boss Door", self.player),
lambda state: state.has("Boss Key", self.player))
+ # location.access_rule = ... is likely to be a bit faster
# combine rules to require two items
add_rule(self.multiworld.get_location("Chest2", self.player),
lambda state: state.has("Sword", self.player))
@@ -730,59 +623,80 @@ def set_rules(self) -> None:
# get_item_type needs to take player/world into account
# if MyGameItem has a type property, a more direct implementation would be
add_item_rule(self.multiworld.get_location("Chest5", self.player),
- lambda item: item.player != self.player or\
+ lambda item: item.player != self.player or
item.my_type == "weapon")
# location.item_rule = ... is likely to be a bit faster
-```
-### Logic Mixin
+ # place "Victory" at "Final Boss" and set collection as win condition
+ self.multiworld.get_location("Final Boss", self.player).place_locked_item(self.create_event("Victory"))
-While lambdas and events could do pretty much anything, by convention we
-implement more complex logic in logic mixins, even if there is no need to add
-properties to the `BaseClasses.CollectionState` state object.
-
-When importing a file that defines a class that inherits from
-`worlds.AutoWorld.LogicMixin` the state object's class is automatically extended by
-the mixin's members. These members should be prefixed with underscore following
-the name of the implementing world. This is due to sharing a namespace with all
-other logic mixins.
-
-Typical uses are defining methods that are used instead of `state.has`
-in lambdas, e.g.`state.mygame_has(custom, player)` or recurring checks
-like `state.mygame_can_do_something(player)` to simplify lambdas.
-Private members, only accessible from mixins, should start with `_mygame_`,
-public members with `mygame_`.
-
-More advanced uses could be to add additional variables to the state object,
-override `World.collect(self, state, item)` and `remove(self, state, item)`
-to update the state object, and check those added variables in added methods.
-Please do this with caution and only when necessary.
+ self.multiworld.completion_condition[self.player] = lambda state: state.has("Victory", self.player)
+
+# 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")
+```
-#### Sample
+### Custom Logic Rules
+
+Custom methods can be defined for your logic rules. The access rule that ultimately gets assigned to the Location or
+Entrance should be
+a [`CollectionRule`](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/generic/Rules.py#L9).
+Typically, this is done by defining a lambda expression on demand at the relevant bit, typically calling other
+functions, but this can also be achieved by defining a method with the appropriate format and assigning it directly.
+For an example, see [The Messenger](/worlds/messenger/rules.py).
```python
# logic.py
-from worlds.AutoWorld import LogicMixin
+from BaseClasses import CollectionState
+
-class MyGameLogic(LogicMixin):
- def mygame_has_key(self, player: int) -> bool:
- # Arguments above are free to choose
- # MultiWorld can be accessed through self.multiworld, explicitly passing in
- # MyGameWorld instance for easy options access is also a valid approach
- return self.has("key", player) # or whatever
+def mygame_has_key(self, state: CollectionState, player: int) -> bool:
+ # More arguments above are free to choose, since you can expect this is only called in your world
+ # MultiWorld can be accessed through state.multiworld.
+ # Explicitly passing in MyGameWorld instance for easy options access is also a valid approach, but it's generally
+ # better to check options before rule assignment since the individual functions can be called thousands of times
+ return state.has("key", player) # or whatever
```
+
```python
# __init__.py
from worlds.generic.Rules import set_rule
-import .logic # apply the mixin by importing its file
+from . import logic
+
class MyGameWorld(World):
# ...
def set_rules(self) -> None:
set_rule(self.multiworld.get_location("A Door", self.player),
- lambda state: state.mygame_has_key(self.player))
+ lambda state: logic.mygame_has_key(state, self.player))
+```
+
+### Logic Mixin
+
+While lambdas and events can do pretty much anything, more complex logic can be handled in logic mixins.
+
+When importing a file that defines a class that inherits from `worlds.AutoWorld.LogicMixin`, the `CollectionState` class
+is automatically extended by the mixin's members. These members should be prefixed with the name of the implementing
+world since the namespace is shared with all other logic mixins.
+
+Some uses could be to add additional variables to the state object, or to have a custom state machine that gets modified
+with the state.
+Please do this with caution and only when necessary.
+
+#### pre_fill
+
+```python
+def pre_fill(self) -> None:
+ # place item Herb into location Chest1 for some reason
+ item = self.create_item("Herb")
+ self.multiworld.get_location("Chest1", self.player).place_locked_item(item)
+ # in most cases it's better to do this at the same time the itempool is
+ # filled to avoid accidental duplicates, such as manually placed and still in the itempool
```
### Generate Output
@@ -792,9 +706,9 @@ from .mod import generate_mod
def generate_output(self, output_directory: str) -> None:
- # How to generate the mod or ROM highly depends on the game
- # if the mod is written in Lua, Jinja can be used to fill a template
- # if the mod reads a json file, `json.dump()` can be used to generate that
+ # How to generate the mod or ROM highly depends on the game.
+ # If the mod is written in Lua, Jinja can be used to fill a template.
+ # If the mod reads a json file, `json.dump()` can be used to generate that.
# code below is a dummy
data = {
"seed": self.multiworld.seed_name, # to verify the server's multiworld
@@ -804,8 +718,7 @@ def generate_output(self, output_directory: str) -> None:
for location in self.multiworld.get_filled_locations(self.player)},
# store start_inventory from player's .yaml
# make sure to mark as not remote_start_inventory when connecting if stored in rom/mod
- "starter_items": [item.name for item
- in self.multiworld.precollected_items[self.player]],
+ "starter_items": [item.name for item in self.multiworld.precollected_items[self.player]],
}
# add needed option results to the dictionary
@@ -824,20 +737,21 @@ def generate_output(self, output_directory: str) -> None:
### Slot Data
If the game client needs to know information about the generated seed, a preferred method of transferring the data
-is through the slot data. This can be filled from the `fill_slot_data` method of your world by returning a `Dict[str, Any]`,
-but should be limited to data that is absolutely necessary to not waste resources. Slot data is sent to your client once
-it has successfully [connected](network%20protocol.md#connected).
-If you need to know information about locations in your world, instead
-of propagating the slot data, it is preferable to use [LocationScouts](network%20protocol.md#locationscouts) since that
-data already exists on the server. The most common usage of slot data is to send option results that the client needs
-to be aware of.
+is through the slot data. This is filled with the `fill_slot_data` method of your world by returning
+a `dict` with `str` keys that can be serialized with json.
+But, to not waste resources, it should be limited to data that is absolutely necessary. Slot data is sent to your client
+once it has successfully [connected](network%20protocol.md#connected).
+If you need to know information about locations in your world, instead of propagating the slot data, it is preferable
+to use [LocationScouts](network%20protocol.md#locationscouts), since that data already exists on the server. The most
+common usage of slot data is sending option results that the client needs to be aware of.
```python
def fill_slot_data(self) -> Dict[str, Any]:
- # in order for our game client to handle the generated seed correctly we need to know what the user selected
- # for their difficulty and final boss HP
- # a dictionary returned from this method gets set as the slot_data and will be sent to the client after connecting
- # the options dataclass has a method to return a `Dict[str, Any]` of each option name provided and the option's value
+ # In order for our game client to handle the generated seed correctly we need to know what the user selected
+ # for their difficulty and final boss HP.
+ # A dictionary returned from this method gets set as the slot_data and will be sent to the client after connecting.
+ # The options dataclass has a method to return a `Dict[str, Any]` of each option name provided and the relevant
+ # option's value.
return self.options.as_dict("difficulty", "final_boss_hp")
```
@@ -847,15 +761,17 @@ Each world implementation should have a tutorial and a game info page. These are
the `.md` files in your world's `/docs` directory.
#### Game Info
+
The game info page is for a short breakdown of what your game is and how it works in Archipelago. Any additional
information that may be useful to the player when learning your randomizer should also go here. The file name format
is `_.md`. While you can write these docs for multiple languages, currently only the english
version is displayed on the website.
#### Tutorials
+
Your game can have as many tutorials in as many languages as you like, with each one having a relevant `Tutorial`
-defined in the `WebWorld`. The file name you use aren't particularly important, but it should be descriptive of what
-the tutorial is covering, and the name of the file must match the relative URL provided in the `Tutorial`. Currently,
+defined in the `WebWorld`. The file name you use isn't particularly important, but it should be descriptive of what
+the tutorial covers, and the name of the file must match the relative URL provided in the `Tutorial`. Currently,
the JS that determines this ignores the provided file name and will search for `game/document_lang.md`, where
`game/document/lang` is the provided URL.
@@ -874,12 +790,13 @@ from test.bases import WorldTestBase
class MyGameTestBase(WorldTestBase):
- game = "My Game"
+ game = "My Game"
```
-Next using the rules defined in the above `set_rules` we can test that the chests have the correct access rules.
+Next, using the rules defined in the above `set_rules` we can test that the chests have the correct access rules.
Example `test_chest_access.py`
+
```python
from . import MyGameTestBase
@@ -889,15 +806,15 @@ class TestChestAccess(MyGameTestBase):
"""Test locations that require a sword"""
locations = ["Chest1", "Chest2"]
items = [["Sword"]]
- # this will test that each location can't be accessed without the "Sword", but can be accessed once obtained.
+ # this will test that each location can't be accessed without the "Sword", but can be accessed once obtained
self.assertAccessDependency(locations, items)
def test_any_weapon_chests(self) -> None:
"""Test locations that require any weapon"""
locations = [f"Chest{i}" for i in range(3, 6)]
items = [["Sword"], ["Axe"], ["Spear"]]
- # this will test that chests 3-5 can't be accessed without any weapon, but can be with just one of them.
+ # this will test that chests 3-5 can't be accessed without any weapon, but can be with just one of them
self.assertAccessDependency(locations, items)
```
-For more information on tests check the [tests doc](tests.md).
+For more information on tests, check the [tests doc](tests.md).
diff --git a/inno_setup.iss b/inno_setup.iss
index be5de320a1c6..b122cdc00b18 100644
--- a/inno_setup.iss
+++ b/inno_setup.iss
@@ -197,7 +197,7 @@ begin
begin
// Is the installed version at least the packaged one ?
Log('VC Redist x64 Version : found ' + strVersion);
- Result := (CompareStr(strVersion, 'v14.32.31332') < 0);
+ Result := (CompareStr(strVersion, 'v14.38.33130') < 0);
end
else
begin
diff --git a/kvui.py b/kvui.py
index 22e179d5be94..5e1b0fc03048 100644
--- a/kvui.py
+++ b/kvui.py
@@ -38,11 +38,13 @@
from kivy.factory import Factory
from kivy.properties import BooleanProperty, ObjectProperty
from kivy.metrics import dp
+from kivy.effects.scroll import ScrollEffect
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.layout import Layout
from kivy.uix.textinput import TextInput
+from kivy.uix.scrollview import ScrollView
from kivy.uix.recycleview import RecycleView
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelItem
from kivy.uix.boxlayout import BoxLayout
@@ -118,6 +120,17 @@ class ServerToolTip(ToolTip):
pass
+class ScrollBox(ScrollView):
+ layout: BoxLayout
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.layout = BoxLayout(size_hint_y=None)
+ self.layout.bind(minimum_height=self.layout.setter("height"))
+ self.add_widget(self.layout)
+ self.effect_cls = ScrollEffect
+
+
class HovererableLabel(HoverBehavior, Label):
pass
diff --git a/playerSettings.yaml b/playerSettings.yaml
index f9585da246b8..b6b474a9fffa 100644
--- a/playerSettings.yaml
+++ b/playerSettings.yaml
@@ -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.4.3 # Version of Archipelago required for this yaml to work as expected.
+ version: 0.4.4 # 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.
diff --git a/requirements.txt b/requirements.txt
index f604556809f1..e2ccb67c18d4 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,13 +1,13 @@
-colorama>=0.4.5
+colorama>=0.4.6
websockets>=12.0
PyYAML>=6.0.1
jellyfish>=1.0.3
-jinja2>=3.1.2
+jinja2>=3.1.3
schema>=0.7.5
kivy>=2.3.0
bsdiff4>=1.2.4
-platformdirs>=4.0.0
+platformdirs>=4.1.0
certifi>=2023.11.17
-cython>=3.0.6
+cython>=3.0.8
cymem>=2.0.8
-orjson>=3.9.10
\ No newline at end of file
+orjson>=3.9.10
diff --git a/setup.py b/setup.py
index 05e923ed3f01..3f9a7f0ba63f 100644
--- a/setup.py
+++ b/setup.py
@@ -80,7 +80,6 @@
"Super Mario 64",
"VVVVVV",
"Wargroove",
- "Zillion",
}
# LogicMixin is broken before 3.10 import revamp
@@ -349,6 +348,18 @@ def run(self):
for folder in sdl2.dep_bins + glew.dep_bins:
shutil.copytree(folder, self.libfolder, dirs_exist_ok=True)
print(f"copying {folder} -> {self.libfolder}")
+ # windows needs Visual Studio C++ Redistributable
+ # Installer works for x64 and arm64
+ print("Downloading VC Redist")
+ import certifi
+ import ssl
+ context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=certifi.where())
+ with urllib.request.urlopen(r"https://aka.ms/vs/17/release/vc_redist.x64.exe",
+ context=context) as download:
+ vc_redist = download.read()
+ print(f"Download complete, {len(vc_redist) / 1024 / 1024:.2f} MBytes downloaded.", )
+ with open("VC_redist.x64.exe", "wb") as vc_file:
+ vc_file.write(vc_redist)
for data in self.extra_data:
self.installfile(Path(data))
@@ -384,8 +395,6 @@ def run(self):
folders_to_remove.append(file_name)
shutil.rmtree(world_directory)
shutil.copyfile("meta.yaml", self.buildfolder / "Players" / "Templates" / "meta.yaml")
- # TODO: fix LttP options one day
- shutil.copyfile("playerSettings.yaml", self.buildfolder / "Players" / "Templates" / "A Link to the Past.yaml")
try:
from maseya import z3pr
except ImportError:
diff --git a/test/bases.py b/test/bases.py
index 7ce12cc7b787..2d4111d19356 100644
--- a/test/bases.py
+++ b/test/bases.py
@@ -7,7 +7,7 @@
from Generate import get_seed_name
from test.general import gen_steps
from worlds import AutoWorld
-from worlds.AutoWorld import call_all
+from worlds.AutoWorld import World, call_all
from BaseClasses import Location, MultiWorld, CollectionState, ItemClassification, Item
from worlds.alttp.Items import ItemFactory
@@ -105,9 +105,15 @@ def _get_items_partial(self, item_pool, missing_item):
class WorldTestBase(unittest.TestCase):
options: typing.Dict[str, typing.Any] = {}
+ """Define options that should be used when setting up this TestBase."""
multiworld: MultiWorld
+ """The constructed MultiWorld instance after setup."""
+ world: World
+ """The constructed World instance after setup."""
+ player: typing.ClassVar[int] = 1
- game: typing.ClassVar[str] # define game name in subclass, example "Secret of Evermore"
+ game: typing.ClassVar[str]
+ """Define game name in subclass, example "Secret of Evermore"."""
auto_construct: typing.ClassVar[bool] = True
""" automatically set up a world for each test in this class """
memory_leak_tested: typing.ClassVar[bool] = False
@@ -150,8 +156,8 @@ def world_setup(self, seed: typing.Optional[int] = None) -> None:
if not hasattr(self, "game"):
raise NotImplementedError("didn't define game name")
self.multiworld = MultiWorld(1)
- self.multiworld.game[1] = self.game
- self.multiworld.player_name = {1: "Tester"}
+ self.multiworld.game[self.player] = self.game
+ self.multiworld.player_name = {self.player: "Tester"}
self.multiworld.set_seed(seed)
self.multiworld.state = CollectionState(self.multiworld)
random.seed(self.multiworld.seed)
@@ -159,9 +165,10 @@ def world_setup(self, seed: typing.Optional[int] = None) -> None:
args = Namespace()
for name, option in AutoWorld.AutoWorldRegister.world_types[self.game].options_dataclass.type_hints.items():
setattr(args, name, {
- 1: option.from_any(self.options.get(name, getattr(option, "default")))
+ 1: option.from_any(self.options.get(name, option.default))
})
self.multiworld.set_options(args)
+ self.world = self.multiworld.worlds[self.player]
for step in gen_steps:
call_all(self.multiworld, step)
@@ -220,19 +227,19 @@ def remove(self, items: typing.Union[Item, typing.Iterable[Item]]) -> None:
def can_reach_location(self, location: str) -> bool:
"""Determines if the current state can reach the provided location name"""
- return self.multiworld.state.can_reach(location, "Location", 1)
+ return self.multiworld.state.can_reach(location, "Location", self.player)
def can_reach_entrance(self, entrance: str) -> bool:
"""Determines if the current state can reach the provided entrance name"""
- return self.multiworld.state.can_reach(entrance, "Entrance", 1)
+ return self.multiworld.state.can_reach(entrance, "Entrance", self.player)
def can_reach_region(self, region: str) -> bool:
"""Determines if the current state can reach the provided region name"""
- return self.multiworld.state.can_reach(region, "Region", 1)
+ return self.multiworld.state.can_reach(region, "Region", self.player)
def count(self, item_name: str) -> int:
"""Returns the amount of an item currently in state"""
- return self.multiworld.state.count(item_name, 1)
+ return self.multiworld.state.count(item_name, self.player)
def assertAccessDependency(self,
locations: typing.List[str],
@@ -246,10 +253,11 @@ def assertAccessDependency(self,
self.collect_all_but(all_items, state)
if only_check_listed:
for location in locations:
- self.assertFalse(state.can_reach(location, "Location", 1), f"{location} is reachable without {all_items}")
+ self.assertFalse(state.can_reach(location, "Location", self.player),
+ f"{location} is reachable without {all_items}")
else:
for location in self.multiworld.get_locations():
- loc_reachable = state.can_reach(location, "Location", 1)
+ loc_reachable = state.can_reach(location, "Location", self.player)
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}")
@@ -258,7 +266,7 @@ def assertAccessDependency(self,
for item in items:
state.collect(item)
for location in locations:
- self.assertTrue(state.can_reach(location, "Location", 1),
+ self.assertTrue(state.can_reach(location, "Location", self.player),
f"{location} not reachable with {item_names}")
for item in items:
state.remove(item)
@@ -285,7 +293,7 @@ def test_all_state_can_reach_everything(self):
if not (self.run_default_tests and self.constructed):
return
with self.subTest("Game", game=self.game):
- excluded = self.multiworld.worlds[1].options.exclude_locations.value
+ excluded = self.multiworld.worlds[self.player].options.exclude_locations.value
state = self.multiworld.get_all_state(False)
for location in self.multiworld.get_locations():
if location.name not in excluded:
@@ -302,7 +310,7 @@ def test_empty_state_can_reach_something(self):
return
with self.subTest("Game", game=self.game):
state = CollectionState(self.multiworld)
- locations = self.multiworld.get_reachable_locations(state, 1)
+ locations = self.multiworld.get_reachable_locations(state, self.player)
self.assertGreater(len(locations), 0,
"Need to be able to reach at least one location to get started.")
@@ -328,7 +336,7 @@ def fulfills_accessibility() -> bool:
for location in sphere:
if location.item:
state.collect(location.item, True, location)
- return self.multiworld.has_beaten_game(state, 1)
+ return self.multiworld.has_beaten_game(state, self.player)
with self.subTest("Game", game=self.game, seed=self.multiworld.seed):
distribute_items_restrictive(self.multiworld)
diff --git a/test/benchmark/__init__.py b/test/benchmark/__init__.py
index 5f890e85300d..6c80c60b89d7 100644
--- a/test/benchmark/__init__.py
+++ b/test/benchmark/__init__.py
@@ -1,127 +1,7 @@
-import time
-
-
-class TimeIt:
- def __init__(self, name: str, time_logger=None):
- self.name = name
- self.logger = time_logger
- self.timer = None
- self.end_timer = None
-
- def __enter__(self):
- self.timer = time.perf_counter()
- return self
-
- @property
- def dif(self):
- return self.end_timer - self.timer
-
- def __exit__(self, exc_type, exc_val, exc_tb):
- if not self.end_timer:
- self.end_timer = time.perf_counter()
- if self.logger:
- self.logger.info(f"{self.dif:.4f} seconds in {self.name}.")
-
-
if __name__ == "__main__":
- import argparse
- import logging
- import gc
- import collections
- import typing
-
- # makes this module runnable from its folder.
- import sys
- import os
- sys.path.remove(os.path.dirname(__file__))
- new_home = os.path.normpath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
- os.chdir(new_home)
- sys.path.append(new_home)
-
- from Utils import init_logging, local_path
- local_path.cached_path = new_home
- from BaseClasses import MultiWorld, CollectionState, Location
- from worlds import AutoWorld
- from worlds.AutoWorld import call_all
-
- init_logging("Benchmark Runner")
- logger = logging.getLogger("Benchmark")
-
-
- class BenchmarkRunner:
- gen_steps: typing.Tuple[str, ...] = (
- "generate_early", "create_regions", "create_items", "set_rules", "generate_basic", "pre_fill")
- rule_iterations: int = 100_000
-
- if sys.version_info >= (3, 9):
- @staticmethod
- def format_times_from_counter(counter: collections.Counter[str], top: int = 5) -> str:
- return "\n".join(f" {time:.4f} in {name}" for name, time in counter.most_common(top))
- else:
- @staticmethod
- def format_times_from_counter(counter: collections.Counter, top: int = 5) -> str:
- return "\n".join(f" {time:.4f} in {name}" for name, time in counter.most_common(top))
-
- def location_test(self, test_location: Location, state: CollectionState, state_name: str) -> float:
- with TimeIt(f"{test_location.game} {self.rule_iterations} "
- f"runs of {test_location}.access_rule({state_name})", logger) as t:
- for _ in range(self.rule_iterations):
- test_location.access_rule(state)
- # if time is taken to disentangle complex ref chains,
- # this time should be attributed to the rule.
- gc.collect()
- return t.dif
-
- def main(self):
- for game in sorted(AutoWorld.AutoWorldRegister.world_types):
- summary_data: typing.Dict[str, collections.Counter[str]] = {
- "empty_state": collections.Counter(),
- "all_state": collections.Counter(),
- }
- try:
- multiworld = MultiWorld(1)
- multiworld.game[1] = game
- multiworld.player_name = {1: "Tester"}
- multiworld.set_seed(0)
- multiworld.state = CollectionState(multiworld)
- args = argparse.Namespace()
- for name, option in AutoWorld.AutoWorldRegister.world_types[game].options_dataclass.type_hints.items():
- setattr(args, name, {
- 1: option.from_any(getattr(option, "default"))
- })
- multiworld.set_options(args)
-
- gc.collect()
- for step in self.gen_steps:
- with TimeIt(f"{game} step {step}", logger):
- call_all(multiworld, step)
- gc.collect()
-
- locations = sorted(multiworld.get_unfilled_locations())
- if not locations:
- continue
-
- all_state = multiworld.get_all_state(False)
- for location in locations:
- time_taken = self.location_test(location, multiworld.state, "empty_state")
- summary_data["empty_state"][location.name] = time_taken
-
- time_taken = self.location_test(location, all_state, "all_state")
- summary_data["all_state"][location.name] = time_taken
-
- total_empty_state = sum(summary_data["empty_state"].values())
- total_all_state = sum(summary_data["all_state"].values())
-
- logger.info(f"{game} took {total_empty_state/len(locations):.4f} "
- f"seconds per location in empty_state and {total_all_state/len(locations):.4f} "
- f"in all_state. (all times summed for {self.rule_iterations} runs.)")
- logger.info(f"Top times in empty_state:\n"
- f"{self.format_times_from_counter(summary_data['empty_state'])}")
- logger.info(f"Top times in all_state:\n"
- f"{self.format_times_from_counter(summary_data['all_state'])}")
-
- except Exception as e:
- logger.exception(e)
-
- runner = BenchmarkRunner()
- runner.main()
+ import path_change
+ path_change.change_home()
+ import load_worlds
+ load_worlds.run_load_worlds_benchmark()
+ import locations
+ locations.run_locations_benchmark()
diff --git a/test/benchmark/load_worlds.py b/test/benchmark/load_worlds.py
new file mode 100644
index 000000000000..3b001699f4cb
--- /dev/null
+++ b/test/benchmark/load_worlds.py
@@ -0,0 +1,27 @@
+def run_load_worlds_benchmark():
+ """List worlds and their load time.
+ Note that any first-time imports will be attributed to that world, as it is cached afterwards.
+ Likely best used with isolated worlds to measure their time alone."""
+ import logging
+
+ from Utils import init_logging
+
+ # get some general imports cached, to prevent it from being attributed to one world.
+ import orjson
+ orjson.loads("{}") # orjson runs initialization on first use
+
+ import BaseClasses, Launcher, Fill
+
+ from worlds import world_sources
+
+ init_logging("Benchmark Runner")
+ logger = logging.getLogger("Benchmark")
+
+ for module in world_sources:
+ logger.info(f"{module} took {module.time_taken:.4f} seconds.")
+
+
+if __name__ == "__main__":
+ from path_change import change_home
+ change_home()
+ run_load_worlds_benchmark()
diff --git a/test/benchmark/locations.py b/test/benchmark/locations.py
new file mode 100644
index 000000000000..f2209eb689e1
--- /dev/null
+++ b/test/benchmark/locations.py
@@ -0,0 +1,101 @@
+def run_locations_benchmark():
+ import argparse
+ import logging
+ import gc
+ import collections
+ import typing
+ import sys
+
+ from time_it import TimeIt
+
+ from Utils import init_logging
+ from BaseClasses import MultiWorld, CollectionState, Location
+ from worlds import AutoWorld
+ from worlds.AutoWorld import call_all
+
+ init_logging("Benchmark Runner")
+ logger = logging.getLogger("Benchmark")
+
+ class BenchmarkRunner:
+ gen_steps: typing.Tuple[str, ...] = (
+ "generate_early", "create_regions", "create_items", "set_rules", "generate_basic", "pre_fill")
+ rule_iterations: int = 100_000
+
+ if sys.version_info >= (3, 9):
+ @staticmethod
+ def format_times_from_counter(counter: collections.Counter[str], top: int = 5) -> str:
+ return "\n".join(f" {time:.4f} in {name}" for name, time in counter.most_common(top))
+ else:
+ @staticmethod
+ def format_times_from_counter(counter: collections.Counter, top: int = 5) -> str:
+ return "\n".join(f" {time:.4f} in {name}" for name, time in counter.most_common(top))
+
+ def location_test(self, test_location: Location, state: CollectionState, state_name: str) -> float:
+ with TimeIt(f"{test_location.game} {self.rule_iterations} "
+ f"runs of {test_location}.access_rule({state_name})", logger) as t:
+ for _ in range(self.rule_iterations):
+ test_location.access_rule(state)
+ # if time is taken to disentangle complex ref chains,
+ # this time should be attributed to the rule.
+ gc.collect()
+ return t.dif
+
+ def main(self):
+ for game in sorted(AutoWorld.AutoWorldRegister.world_types):
+ summary_data: typing.Dict[str, collections.Counter[str]] = {
+ "empty_state": collections.Counter(),
+ "all_state": collections.Counter(),
+ }
+ try:
+ multiworld = MultiWorld(1)
+ multiworld.game[1] = game
+ multiworld.player_name = {1: "Tester"}
+ multiworld.set_seed(0)
+ multiworld.state = CollectionState(multiworld)
+ args = argparse.Namespace()
+ for name, option in AutoWorld.AutoWorldRegister.world_types[game].options_dataclass.type_hints.items():
+ setattr(args, name, {
+ 1: option.from_any(getattr(option, "default"))
+ })
+ multiworld.set_options(args)
+
+ gc.collect()
+ for step in self.gen_steps:
+ with TimeIt(f"{game} step {step}", logger):
+ call_all(multiworld, step)
+ gc.collect()
+
+ locations = sorted(multiworld.get_unfilled_locations())
+ if not locations:
+ continue
+
+ all_state = multiworld.get_all_state(False)
+ for location in locations:
+ time_taken = self.location_test(location, multiworld.state, "empty_state")
+ summary_data["empty_state"][location.name] = time_taken
+
+ time_taken = self.location_test(location, all_state, "all_state")
+ summary_data["all_state"][location.name] = time_taken
+
+ total_empty_state = sum(summary_data["empty_state"].values())
+ total_all_state = sum(summary_data["all_state"].values())
+
+ logger.info(f"{game} took {total_empty_state/len(locations):.4f} "
+ f"seconds per location in empty_state and {total_all_state/len(locations):.4f} "
+ f"in all_state. (all times summed for {self.rule_iterations} runs.)")
+ logger.info(f"Top times in empty_state:\n"
+ f"{self.format_times_from_counter(summary_data['empty_state'])}")
+ logger.info(f"Top times in all_state:\n"
+ f"{self.format_times_from_counter(summary_data['all_state'])}")
+
+ except Exception as e:
+ logger.exception(e)
+
+ runner = BenchmarkRunner()
+ runner.main()
+
+
+if __name__ == "__main__":
+ from path_change import change_home
+ change_home()
+ run_locations_benchmark()
diff --git a/test/benchmark/path_change.py b/test/benchmark/path_change.py
new file mode 100644
index 000000000000..2baa6273e11e
--- /dev/null
+++ b/test/benchmark/path_change.py
@@ -0,0 +1,16 @@
+import sys
+import os
+
+
+def change_home():
+ """Allow scripts to run from "this" folder."""
+ old_home = os.path.dirname(__file__)
+ sys.path.remove(old_home)
+ new_home = os.path.normpath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
+ os.chdir(new_home)
+ sys.path.append(new_home)
+ # fallback to local import
+ sys.path.append(old_home)
+
+ from Utils import local_path
+ local_path.cached_path = new_home
diff --git a/test/benchmark/time_it.py b/test/benchmark/time_it.py
new file mode 100644
index 000000000000..95c0314682f6
--- /dev/null
+++ b/test/benchmark/time_it.py
@@ -0,0 +1,23 @@
+import time
+
+
+class TimeIt:
+ def __init__(self, name: str, time_logger=None):
+ self.name = name
+ self.logger = time_logger
+ self.timer = None
+ self.end_timer = None
+
+ def __enter__(self):
+ self.timer = time.perf_counter()
+ return self
+
+ @property
+ def dif(self):
+ return self.end_timer - self.timer
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ if not self.end_timer:
+ self.end_timer = time.perf_counter()
+ if self.logger:
+ self.logger.info(f"{self.dif:.4f} seconds in {self.name}.")
diff --git a/test/general/test_fill.py b/test/general/test_fill.py
index e454b3e61d7a..489417771d2a 100644
--- a/test/general/test_fill.py
+++ b/test/general/test_fill.py
@@ -11,30 +11,30 @@
from worlds.generic.Rules import CollectionRule, add_item_rule, locality_rules, set_rule
-def generate_multi_world(players: int = 1) -> MultiWorld:
- multi_world = MultiWorld(players)
- multi_world.player_name = {}
- multi_world.state = CollectionState(multi_world)
+def generate_multiworld(players: int = 1) -> MultiWorld:
+ multiworld = MultiWorld(players)
+ multiworld.player_name = {}
+ multiworld.state = CollectionState(multiworld)
for i in range(players):
player_id = i+1
- world = World(multi_world, player_id)
- multi_world.game[player_id] = f"Game {player_id}"
- multi_world.worlds[player_id] = world
- multi_world.player_name[player_id] = "Test Player " + str(player_id)
- region = Region("Menu", player_id, multi_world, "Menu Region Hint")
- multi_world.regions.append(region)
+ world = World(multiworld, player_id)
+ multiworld.game[player_id] = f"Game {player_id}"
+ multiworld.worlds[player_id] = world
+ multiworld.player_name[player_id] = "Test Player " + str(player_id)
+ region = Region("Menu", player_id, multiworld, "Menu Region Hint")
+ multiworld.regions.append(region)
for option_key, option in Options.PerGameCommonOptions.type_hints.items():
- if hasattr(multi_world, option_key):
- getattr(multi_world, option_key).setdefault(player_id, option.from_any(getattr(option, "default")))
+ if hasattr(multiworld, option_key):
+ getattr(multiworld, option_key).setdefault(player_id, option.from_any(getattr(option, "default")))
else:
- setattr(multi_world, option_key, {player_id: option.from_any(getattr(option, "default"))})
+ setattr(multiworld, option_key, {player_id: option.from_any(getattr(option, "default"))})
# TODO - remove this loop once all worlds use options dataclasses
- world.options = world.options_dataclass(**{option_key: getattr(multi_world, option_key)[player_id]
+ world.options = world.options_dataclass(**{option_key: getattr(multiworld, option_key)[player_id]
for option_key in world.options_dataclass.type_hints})
- multi_world.set_seed(0)
+ multiworld.set_seed(0)
- return multi_world
+ return multiworld
class PlayerDefinition(object):
@@ -46,8 +46,8 @@ class PlayerDefinition(object):
basic_items: List[Item]
regions: List[Region]
- def __init__(self, world: MultiWorld, id: int, menu: Region, locations: List[Location] = [], prog_items: List[Item] = [], basic_items: List[Item] = []):
- self.multiworld = world
+ def __init__(self, multiworld: MultiWorld, id: int, menu: Region, locations: List[Location] = [], prog_items: List[Item] = [], basic_items: List[Item] = []):
+ self.multiworld = multiworld
self.id = id
self.menu = menu
self.locations = locations
@@ -72,7 +72,7 @@ def generate_region(self, parent: Region, size: int, access_rule: CollectionRule
return region
-def fill_region(world: MultiWorld, region: Region, items: List[Item]) -> List[Item]:
+def fill_region(multiworld: MultiWorld, region: Region, items: List[Item]) -> List[Item]:
items = items.copy()
while len(items) > 0:
location = region.locations.pop(0)
@@ -80,7 +80,7 @@ def fill_region(world: MultiWorld, region: Region, items: List[Item]) -> List[It
if location.item:
return items
item = items.pop(0)
- world.push_item(location, item, False)
+ multiworld.push_item(location, item, False)
location.event = item.advancement
return items
@@ -94,15 +94,15 @@ def region_contains(region: Region, item: Item) -> bool:
return False
-def generate_player_data(multi_world: MultiWorld, player_id: int, location_count: int = 0, prog_item_count: int = 0, basic_item_count: int = 0) -> PlayerDefinition:
- menu = multi_world.get_region("Menu", player_id)
+def generate_player_data(multiworld: MultiWorld, player_id: int, location_count: int = 0, prog_item_count: int = 0, basic_item_count: int = 0) -> PlayerDefinition:
+ menu = multiworld.get_region("Menu", player_id)
locations = generate_locations(location_count, player_id, None, menu)
prog_items = generate_items(prog_item_count, player_id, True)
- multi_world.itempool += prog_items
+ multiworld.itempool += prog_items
basic_items = generate_items(basic_item_count, player_id, False)
- multi_world.itempool += basic_items
+ multiworld.itempool += basic_items
- return PlayerDefinition(multi_world, player_id, menu, locations, prog_items, basic_items)
+ return PlayerDefinition(multiworld, player_id, menu, locations, prog_items, basic_items)
def generate_locations(count: int, player_id: int, address: int = None, region: Region = None, tag: str = "") -> List[Location]:
@@ -134,15 +134,15 @@ def names(objs: list) -> Iterable[str]:
class TestFillRestrictive(unittest.TestCase):
def test_basic_fill(self):
"""Tests `fill_restrictive` fills and removes the locations and items from their respective lists"""
- multi_world = generate_multi_world()
- player1 = generate_player_data(multi_world, 1, 2, 2)
+ multiworld = generate_multiworld()
+ player1 = generate_player_data(multiworld, 1, 2, 2)
item0 = player1.prog_items[0]
item1 = player1.prog_items[1]
loc0 = player1.locations[0]
loc1 = player1.locations[1]
- fill_restrictive(multi_world, multi_world.state,
+ fill_restrictive(multiworld, multiworld.state,
player1.locations, player1.prog_items)
self.assertEqual(loc0.item, item1)
@@ -152,16 +152,16 @@ def test_basic_fill(self):
def test_ordered_fill(self):
"""Tests `fill_restrictive` fulfills set rules"""
- multi_world = generate_multi_world()
- player1 = generate_player_data(multi_world, 1, 2, 2)
+ multiworld = generate_multiworld()
+ player1 = generate_player_data(multiworld, 1, 2, 2)
items = player1.prog_items
locations = player1.locations
- multi_world.completion_condition[player1.id] = lambda state: state.has(
+ multiworld.completion_condition[player1.id] = lambda state: state.has(
items[0].name, player1.id) and state.has(items[1].name, player1.id)
set_rule(locations[1], lambda state: state.has(
items[0].name, player1.id))
- fill_restrictive(multi_world, multi_world.state,
+ fill_restrictive(multiworld, multiworld.state,
player1.locations.copy(), player1.prog_items.copy())
self.assertEqual(locations[0].item, items[0])
@@ -169,8 +169,8 @@ def test_ordered_fill(self):
def test_partial_fill(self):
"""Tests that `fill_restrictive` returns unfilled locations"""
- multi_world = generate_multi_world()
- player1 = generate_player_data(multi_world, 1, 3, 2)
+ multiworld = generate_multiworld()
+ player1 = generate_player_data(multiworld, 1, 3, 2)
item0 = player1.prog_items[0]
item1 = player1.prog_items[1]
@@ -178,14 +178,14 @@ def test_partial_fill(self):
loc1 = player1.locations[1]
loc2 = player1.locations[2]
- multi_world.completion_condition[player1.id] = lambda state: state.has(
+ multiworld.completion_condition[player1.id] = lambda state: state.has(
item0.name, player1.id) and state.has(item1.name, player1.id)
set_rule(loc1, lambda state: state.has(
item0.name, player1.id))
# forces a swap
set_rule(loc2, lambda state: state.has(
item0.name, player1.id))
- fill_restrictive(multi_world, multi_world.state,
+ fill_restrictive(multiworld, multiworld.state,
player1.locations, player1.prog_items)
self.assertEqual(loc0.item, item0)
@@ -195,19 +195,19 @@ def test_partial_fill(self):
def test_minimal_fill(self):
"""Test that fill for minimal player can have unreachable items"""
- multi_world = generate_multi_world()
- player1 = generate_player_data(multi_world, 1, 2, 2)
+ multiworld = generate_multiworld()
+ player1 = generate_player_data(multiworld, 1, 2, 2)
items = player1.prog_items
locations = player1.locations
- multi_world.worlds[player1.id].options.accessibility = Accessibility.from_any(Accessibility.option_minimal)
- multi_world.completion_condition[player1.id] = lambda state: state.has(
+ multiworld.worlds[player1.id].options.accessibility = Accessibility.from_any(Accessibility.option_minimal)
+ multiworld.completion_condition[player1.id] = lambda state: state.has(
items[1].name, player1.id)
set_rule(locations[1], lambda state: state.has(
items[0].name, player1.id))
- fill_restrictive(multi_world, multi_world.state,
+ fill_restrictive(multiworld, multiworld.state,
player1.locations.copy(), player1.prog_items.copy())
self.assertEqual(locations[0].item, items[1])
@@ -220,15 +220,15 @@ def test_minimal_mixed_fill(self):
the non-minimal player get all items.
"""
- multi_world = generate_multi_world(2)
- player1 = generate_player_data(multi_world, 1, 3, 3)
- player2 = generate_player_data(multi_world, 2, 3, 3)
+ multiworld = generate_multiworld(2)
+ player1 = generate_player_data(multiworld, 1, 3, 3)
+ player2 = generate_player_data(multiworld, 2, 3, 3)
- multi_world.accessibility[player1.id].value = multi_world.accessibility[player1.id].option_minimal
- multi_world.accessibility[player2.id].value = multi_world.accessibility[player2.id].option_locations
+ multiworld.accessibility[player1.id].value = multiworld.accessibility[player1.id].option_minimal
+ multiworld.accessibility[player2.id].value = multiworld.accessibility[player2.id].option_locations
- multi_world.completion_condition[player1.id] = lambda state: True
- multi_world.completion_condition[player2.id] = lambda state: state.has(player2.prog_items[2].name, player2.id)
+ multiworld.completion_condition[player1.id] = lambda state: True
+ multiworld.completion_condition[player2.id] = lambda state: state.has(player2.prog_items[2].name, player2.id)
set_rule(player1.locations[1], lambda state: state.has(player1.prog_items[0].name, player1.id))
set_rule(player1.locations[2], lambda state: state.has(player1.prog_items[1].name, player1.id))
@@ -241,28 +241,28 @@ def test_minimal_mixed_fill(self):
# fill remaining locations with remaining items
location_pool = player1.locations[1:] + player2.locations
item_pool = player1.prog_items[:-1] + player2.prog_items
- fill_restrictive(multi_world, multi_world.state, location_pool, item_pool)
- multi_world.state.sweep_for_events() # collect everything
+ fill_restrictive(multiworld, multiworld.state, location_pool, item_pool)
+ multiworld.state.sweep_for_events() # collect everything
# all of player2's locations and items should be accessible (not all of player1's)
for item in player2.prog_items:
- self.assertTrue(multi_world.state.has(item.name, player2.id),
+ self.assertTrue(multiworld.state.has(item.name, player2.id),
f'{item} is unreachable in {item.location}')
def test_reversed_fill(self):
"""Test a different set of rules can be satisfied"""
- multi_world = generate_multi_world()
- player1 = generate_player_data(multi_world, 1, 2, 2)
+ multiworld = generate_multiworld()
+ player1 = generate_player_data(multiworld, 1, 2, 2)
item0 = player1.prog_items[0]
item1 = player1.prog_items[1]
loc0 = player1.locations[0]
loc1 = player1.locations[1]
- multi_world.completion_condition[player1.id] = lambda state: state.has(
+ multiworld.completion_condition[player1.id] = lambda state: state.has(
item0.name, player1.id) and state.has(item1.name, player1.id)
set_rule(loc1, lambda state: state.has(item1.name, player1.id))
- fill_restrictive(multi_world, multi_world.state,
+ fill_restrictive(multiworld, multiworld.state,
player1.locations, player1.prog_items)
self.assertEqual(loc0.item, item1)
@@ -270,13 +270,13 @@ def test_reversed_fill(self):
def test_multi_step_fill(self):
"""Test that fill is able to satisfy multiple spheres"""
- multi_world = generate_multi_world()
- player1 = generate_player_data(multi_world, 1, 4, 4)
+ multiworld = generate_multiworld()
+ player1 = generate_player_data(multiworld, 1, 4, 4)
items = player1.prog_items
locations = player1.locations
- multi_world.completion_condition[player1.id] = lambda state: state.has(
+ multiworld.completion_condition[player1.id] = lambda state: state.has(
items[2].name, player1.id) and state.has(items[3].name, player1.id)
set_rule(locations[1], lambda state: state.has(
items[0].name, player1.id))
@@ -285,7 +285,7 @@ def test_multi_step_fill(self):
set_rule(locations[3], lambda state: state.has(
items[1].name, player1.id))
- fill_restrictive(multi_world, multi_world.state,
+ fill_restrictive(multiworld, multiworld.state,
player1.locations.copy(), player1.prog_items.copy())
self.assertEqual(locations[0].item, items[1])
@@ -295,25 +295,25 @@ def test_multi_step_fill(self):
def test_impossible_fill(self):
"""Test that fill raises an error when it can't place any items"""
- multi_world = generate_multi_world()
- player1 = generate_player_data(multi_world, 1, 2, 2)
+ multiworld = generate_multiworld()
+ player1 = generate_player_data(multiworld, 1, 2, 2)
items = player1.prog_items
locations = player1.locations
- multi_world.completion_condition[player1.id] = lambda state: state.has(
+ multiworld.completion_condition[player1.id] = lambda state: state.has(
items[0].name, player1.id) and state.has(items[1].name, player1.id)
set_rule(locations[1], lambda state: state.has(
items[1].name, player1.id))
set_rule(locations[0], lambda state: state.has(
items[0].name, player1.id))
- self.assertRaises(FillError, fill_restrictive, multi_world, multi_world.state,
+ self.assertRaises(FillError, fill_restrictive, multiworld, multiworld.state,
player1.locations.copy(), player1.prog_items.copy())
def test_circular_fill(self):
"""Test that fill raises an error when it can't place all items"""
- multi_world = generate_multi_world()
- player1 = generate_player_data(multi_world, 1, 3, 3)
+ multiworld = generate_multiworld()
+ player1 = generate_player_data(multiworld, 1, 3, 3)
item0 = player1.prog_items[0]
item1 = player1.prog_items[1]
@@ -322,46 +322,46 @@ def test_circular_fill(self):
loc1 = player1.locations[1]
loc2 = player1.locations[2]
- multi_world.completion_condition[player1.id] = lambda state: state.has(
+ multiworld.completion_condition[player1.id] = lambda state: state.has(
item0.name, player1.id) and state.has(item1.name, player1.id) and state.has(item2.name, player1.id)
set_rule(loc1, lambda state: state.has(item0.name, player1.id))
set_rule(loc2, lambda state: state.has(item1.name, player1.id))
set_rule(loc0, lambda state: state.has(item2.name, player1.id))
- self.assertRaises(FillError, fill_restrictive, multi_world, multi_world.state,
+ self.assertRaises(FillError, fill_restrictive, multiworld, multiworld.state,
player1.locations.copy(), player1.prog_items.copy())
def test_competing_fill(self):
"""Test that fill raises an error when it can't place items in a way to satisfy the conditions"""
- multi_world = generate_multi_world()
- player1 = generate_player_data(multi_world, 1, 2, 2)
+ multiworld = generate_multiworld()
+ player1 = generate_player_data(multiworld, 1, 2, 2)
item0 = player1.prog_items[0]
item1 = player1.prog_items[1]
loc1 = player1.locations[1]
- multi_world.completion_condition[player1.id] = lambda state: state.has(
+ multiworld.completion_condition[player1.id] = lambda state: state.has(
item0.name, player1.id) and state.has(item0.name, player1.id) and state.has(item1.name, player1.id)
set_rule(loc1, lambda state: state.has(item0.name, player1.id)
and state.has(item1.name, player1.id))
- self.assertRaises(FillError, fill_restrictive, multi_world, multi_world.state,
+ self.assertRaises(FillError, fill_restrictive, multiworld, multiworld.state,
player1.locations.copy(), player1.prog_items.copy())
def test_multiplayer_fill(self):
"""Test that items can be placed across worlds"""
- multi_world = generate_multi_world(2)
- player1 = generate_player_data(multi_world, 1, 2, 2)
- player2 = generate_player_data(multi_world, 2, 2, 2)
+ multiworld = generate_multiworld(2)
+ player1 = generate_player_data(multiworld, 1, 2, 2)
+ player2 = generate_player_data(multiworld, 2, 2, 2)
- multi_world.completion_condition[player1.id] = lambda state: state.has(
+ multiworld.completion_condition[player1.id] = lambda state: state.has(
player1.prog_items[0].name, player1.id) and state.has(
player1.prog_items[1].name, player1.id)
- multi_world.completion_condition[player2.id] = lambda state: state.has(
+ multiworld.completion_condition[player2.id] = lambda state: state.has(
player2.prog_items[0].name, player2.id) and state.has(
player2.prog_items[1].name, player2.id)
- fill_restrictive(multi_world, multi_world.state, player1.locations +
+ fill_restrictive(multiworld, multiworld.state, player1.locations +
player2.locations, player1.prog_items + player2.prog_items)
self.assertEqual(player1.locations[0].item, player1.prog_items[1])
@@ -371,21 +371,21 @@ def test_multiplayer_fill(self):
def test_multiplayer_rules_fill(self):
"""Test that fill across worlds satisfies the rules"""
- multi_world = generate_multi_world(2)
- player1 = generate_player_data(multi_world, 1, 2, 2)
- player2 = generate_player_data(multi_world, 2, 2, 2)
+ multiworld = generate_multiworld(2)
+ player1 = generate_player_data(multiworld, 1, 2, 2)
+ player2 = generate_player_data(multiworld, 2, 2, 2)
- multi_world.completion_condition[player1.id] = lambda state: state.has(
+ multiworld.completion_condition[player1.id] = lambda state: state.has(
player1.prog_items[0].name, player1.id) and state.has(
player1.prog_items[1].name, player1.id)
- multi_world.completion_condition[player2.id] = lambda state: state.has(
+ multiworld.completion_condition[player2.id] = lambda state: state.has(
player2.prog_items[0].name, player2.id) and state.has(
player2.prog_items[1].name, player2.id)
set_rule(player2.locations[1], lambda state: state.has(
player2.prog_items[0].name, player2.id))
- fill_restrictive(multi_world, multi_world.state, player1.locations +
+ fill_restrictive(multiworld, multiworld.state, player1.locations +
player2.locations, player1.prog_items + player2.prog_items)
self.assertEqual(player1.locations[0].item, player2.prog_items[0])
@@ -395,10 +395,10 @@ def test_multiplayer_rules_fill(self):
def test_restrictive_progress(self):
"""Test that various spheres with different requirements can be filled"""
- multi_world = generate_multi_world()
- player1 = generate_player_data(multi_world, 1, prog_item_count=25)
+ multiworld = generate_multiworld()
+ player1 = generate_player_data(multiworld, 1, prog_item_count=25)
items = player1.prog_items.copy()
- multi_world.completion_condition[player1.id] = lambda state: state.has_all(
+ multiworld.completion_condition[player1.id] = lambda state: state.has_all(
names(player1.prog_items), player1.id)
player1.generate_region(player1.menu, 5)
@@ -411,16 +411,16 @@ def test_restrictive_progress(self):
player1.generate_region(player1.menu, 5, lambda state: state.has_all(
names(items[17:22]), player1.id))
- locations = multi_world.get_unfilled_locations()
+ locations = multiworld.get_unfilled_locations()
- fill_restrictive(multi_world, multi_world.state,
+ fill_restrictive(multiworld, multiworld.state,
locations, player1.prog_items)
def test_swap_to_earlier_location_with_item_rule(self):
"""Test that item swap happens and works as intended"""
# test for PR#1109
- multi_world = generate_multi_world(1)
- player1 = generate_player_data(multi_world, 1, 4, 4)
+ multiworld = generate_multiworld(1)
+ player1 = generate_player_data(multiworld, 1, 4, 4)
locations = player1.locations[:] # copy required
items = player1.prog_items[:] # copy required
# for the test to work, item and location order is relevant: Sphere 1 last, allowed_item not last
@@ -437,15 +437,15 @@ def test_swap_to_earlier_location_with_item_rule(self):
self.assertTrue(sphere1_loc.can_fill(None, allowed_item, False), "Test is flawed")
self.assertFalse(sphere1_loc.can_fill(None, items[2], False), "Test is flawed")
# fill has to place items[1] in locations[0] which will result in a swap because of placement order
- fill_restrictive(multi_world, multi_world.state, player1.locations, player1.prog_items)
+ fill_restrictive(multiworld, multiworld.state, player1.locations, player1.prog_items)
# assert swap happened
self.assertTrue(sphere1_loc.item, "Did not swap required item into Sphere 1")
self.assertEqual(sphere1_loc.item, allowed_item, "Wrong item in Sphere 1")
def test_swap_to_earlier_location_with_item_rule2(self):
"""Test that swap works before all items are placed"""
- multi_world = generate_multi_world(1)
- player1 = generate_player_data(multi_world, 1, 5, 5)
+ multiworld = generate_multiworld(1)
+ player1 = generate_player_data(multiworld, 1, 5, 5)
locations = player1.locations[:] # copy required
items = player1.prog_items[:] # copy required
# Two items provide access to sphere 2.
@@ -477,7 +477,7 @@ def test_swap_to_earlier_location_with_item_rule2(self):
# Now fill should place one_to_two1 in sphere1_loc1 or sphere1_loc2 via swap,
# which it will attempt before two_to_three and three_to_four are placed, testing the behavior.
- fill_restrictive(multi_world, multi_world.state, player1.locations, player1.prog_items)
+ fill_restrictive(multiworld, multiworld.state, player1.locations, player1.prog_items)
# assert swap happened
self.assertTrue(sphere1_loc1.item and sphere1_loc2.item, "Did not swap required item into Sphere 1")
self.assertTrue(sphere1_loc1.item.name == one_to_two1 or
@@ -486,29 +486,29 @@ def test_swap_to_earlier_location_with_item_rule2(self):
def test_double_sweep(self):
"""Test that sweep doesn't duplicate Event items when sweeping"""
# test for PR1114
- multi_world = generate_multi_world(1)
- player1 = generate_player_data(multi_world, 1, 1, 1)
+ multiworld = generate_multiworld(1)
+ player1 = generate_player_data(multiworld, 1, 1, 1)
location = player1.locations[0]
location.address = None
location.event = True
item = player1.prog_items[0]
item.code = None
location.place_locked_item(item)
- multi_world.state.sweep_for_events()
- multi_world.state.sweep_for_events()
- self.assertTrue(multi_world.state.prog_items[item.player][item.name], "Sweep did not collect - Test flawed")
- self.assertEqual(multi_world.state.prog_items[item.player][item.name], 1, "Sweep collected multiple times")
+ multiworld.state.sweep_for_events()
+ multiworld.state.sweep_for_events()
+ self.assertTrue(multiworld.state.prog_items[item.player][item.name], "Sweep did not collect - Test flawed")
+ self.assertEqual(multiworld.state.prog_items[item.player][item.name], 1, "Sweep collected multiple times")
def test_correct_item_instance_removed_from_pool(self):
"""Test that a placed item gets removed from the submitted pool"""
- multi_world = generate_multi_world()
- player1 = generate_player_data(multi_world, 1, 2, 2)
+ multiworld = generate_multiworld()
+ player1 = generate_player_data(multiworld, 1, 2, 2)
player1.prog_items[0].name = "Different_item_instance_but_same_item_name"
player1.prog_items[1].name = "Different_item_instance_but_same_item_name"
loc0 = player1.locations[0]
- fill_restrictive(multi_world, multi_world.state,
+ fill_restrictive(multiworld, multiworld.state,
[loc0], player1.prog_items)
self.assertEqual(1, len(player1.prog_items))
@@ -518,14 +518,14 @@ def test_correct_item_instance_removed_from_pool(self):
class TestDistributeItemsRestrictive(unittest.TestCase):
def test_basic_distribute(self):
"""Test that distribute_items_restrictive is deterministic"""
- multi_world = generate_multi_world()
+ multiworld = generate_multiworld()
player1 = generate_player_data(
- multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
+ multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
locations = player1.locations
prog_items = player1.prog_items
basic_items = player1.basic_items
- distribute_items_restrictive(multi_world)
+ distribute_items_restrictive(multiworld)
self.assertEqual(locations[0].item, basic_items[1])
self.assertFalse(locations[0].event)
@@ -538,52 +538,52 @@ def test_basic_distribute(self):
def test_excluded_distribute(self):
"""Test that distribute_items_restrictive doesn't put advancement items on excluded locations"""
- multi_world = generate_multi_world()
+ multiworld = generate_multiworld()
player1 = generate_player_data(
- multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
+ multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
locations = player1.locations
locations[1].progress_type = LocationProgressType.EXCLUDED
locations[2].progress_type = LocationProgressType.EXCLUDED
- distribute_items_restrictive(multi_world)
+ distribute_items_restrictive(multiworld)
self.assertFalse(locations[1].item.advancement)
self.assertFalse(locations[2].item.advancement)
def test_non_excluded_item_distribute(self):
"""Test that useful items aren't placed on excluded locations"""
- multi_world = generate_multi_world()
+ multiworld = generate_multiworld()
player1 = generate_player_data(
- multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
+ multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
locations = player1.locations
basic_items = player1.basic_items
locations[1].progress_type = LocationProgressType.EXCLUDED
basic_items[1].classification = ItemClassification.useful
- distribute_items_restrictive(multi_world)
+ distribute_items_restrictive(multiworld)
self.assertEqual(locations[1].item, basic_items[0])
def test_too_many_excluded_distribute(self):
"""Test that fill fails if it can't place all progression items due to too many excluded locations"""
- multi_world = generate_multi_world()
+ multiworld = generate_multiworld()
player1 = generate_player_data(
- multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
+ multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
locations = player1.locations
locations[0].progress_type = LocationProgressType.EXCLUDED
locations[1].progress_type = LocationProgressType.EXCLUDED
locations[2].progress_type = LocationProgressType.EXCLUDED
- self.assertRaises(FillError, distribute_items_restrictive, multi_world)
+ self.assertRaises(FillError, distribute_items_restrictive, multiworld)
def test_non_excluded_item_must_distribute(self):
"""Test that fill fails if it can't place useful items due to too many excluded locations"""
- multi_world = generate_multi_world()
+ multiworld = generate_multiworld()
player1 = generate_player_data(
- multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
+ multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
locations = player1.locations
basic_items = player1.basic_items
@@ -592,47 +592,47 @@ def test_non_excluded_item_must_distribute(self):
basic_items[0].classification = ItemClassification.useful
basic_items[1].classification = ItemClassification.useful
- self.assertRaises(FillError, distribute_items_restrictive, multi_world)
+ self.assertRaises(FillError, distribute_items_restrictive, multiworld)
def test_priority_distribute(self):
"""Test that priority locations receive advancement items"""
- multi_world = generate_multi_world()
+ multiworld = generate_multiworld()
player1 = generate_player_data(
- multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
+ multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
locations = player1.locations
locations[0].progress_type = LocationProgressType.PRIORITY
locations[3].progress_type = LocationProgressType.PRIORITY
- distribute_items_restrictive(multi_world)
+ distribute_items_restrictive(multiworld)
self.assertTrue(locations[0].item.advancement)
self.assertTrue(locations[3].item.advancement)
def test_excess_priority_distribute(self):
"""Test that if there's more priority locations than advancement items, they can still fill"""
- multi_world = generate_multi_world()
+ multiworld = generate_multiworld()
player1 = generate_player_data(
- multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
+ multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
locations = player1.locations
locations[0].progress_type = LocationProgressType.PRIORITY
locations[1].progress_type = LocationProgressType.PRIORITY
locations[2].progress_type = LocationProgressType.PRIORITY
- distribute_items_restrictive(multi_world)
+ distribute_items_restrictive(multiworld)
self.assertFalse(locations[3].item.advancement)
def test_multiple_world_priority_distribute(self):
"""Test that priority fill can be satisfied for multiple worlds"""
- multi_world = generate_multi_world(3)
+ multiworld = generate_multiworld(3)
player1 = generate_player_data(
- multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
+ multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
player2 = generate_player_data(
- multi_world, 2, 4, prog_item_count=1, basic_item_count=3)
+ multiworld, 2, 4, prog_item_count=1, basic_item_count=3)
player3 = generate_player_data(
- multi_world, 3, 6, prog_item_count=4, basic_item_count=2)
+ multiworld, 3, 6, prog_item_count=4, basic_item_count=2)
player1.locations[2].progress_type = LocationProgressType.PRIORITY
player1.locations[3].progress_type = LocationProgressType.PRIORITY
@@ -644,7 +644,7 @@ def test_multiple_world_priority_distribute(self):
player3.locations[2].progress_type = LocationProgressType.PRIORITY
player3.locations[3].progress_type = LocationProgressType.PRIORITY
- distribute_items_restrictive(multi_world)
+ distribute_items_restrictive(multiworld)
self.assertTrue(player1.locations[2].item.advancement)
self.assertTrue(player1.locations[3].item.advancement)
@@ -656,9 +656,9 @@ def test_multiple_world_priority_distribute(self):
def test_can_remove_locations_in_fill_hook(self):
"""Test that distribute_items_restrictive calls the fill hook and allows for item and location removal"""
- multi_world = generate_multi_world()
+ multiworld = generate_multiworld()
player1 = generate_player_data(
- multi_world, 1, 4, prog_item_count=2, basic_item_count=2)
+ multiworld, 1, 4, prog_item_count=2, basic_item_count=2)
removed_item: list[Item] = []
removed_location: list[Location] = []
@@ -667,21 +667,21 @@ def fill_hook(progitempool, usefulitempool, filleritempool, fill_locations):
removed_item.append(filleritempool.pop(0))
removed_location.append(fill_locations.pop(0))
- multi_world.worlds[player1.id].fill_hook = fill_hook
+ multiworld.worlds[player1.id].fill_hook = fill_hook
- distribute_items_restrictive(multi_world)
+ distribute_items_restrictive(multiworld)
self.assertIsNone(removed_item[0].location)
self.assertIsNone(removed_location[0].item)
def test_seed_robust_to_item_order(self):
"""Test deterministic fill"""
- mw1 = generate_multi_world()
+ mw1 = generate_multiworld()
gen1 = generate_player_data(
mw1, 1, 4, prog_item_count=2, basic_item_count=2)
distribute_items_restrictive(mw1)
- mw2 = generate_multi_world()
+ mw2 = generate_multiworld()
gen2 = generate_player_data(
mw2, 1, 4, prog_item_count=2, basic_item_count=2)
mw2.itempool.append(mw2.itempool.pop(0))
@@ -694,12 +694,12 @@ def test_seed_robust_to_item_order(self):
def test_seed_robust_to_location_order(self):
"""Test deterministic fill even if locations in a region are reordered"""
- mw1 = generate_multi_world()
+ mw1 = generate_multiworld()
gen1 = generate_player_data(
mw1, 1, 4, prog_item_count=2, basic_item_count=2)
distribute_items_restrictive(mw1)
- mw2 = generate_multi_world()
+ mw2 = generate_multiworld()
gen2 = generate_player_data(
mw2, 1, 4, prog_item_count=2, basic_item_count=2)
reg = mw2.get_region("Menu", gen2.id)
@@ -713,45 +713,45 @@ def test_seed_robust_to_location_order(self):
def test_can_reserve_advancement_items_for_general_fill(self):
"""Test that priority locations fill still satisfies item rules"""
- multi_world = generate_multi_world()
+ multiworld = generate_multiworld()
player1 = generate_player_data(
- multi_world, 1, location_count=5, prog_item_count=5)
+ multiworld, 1, location_count=5, prog_item_count=5)
items = player1.prog_items
- multi_world.completion_condition[player1.id] = lambda state: state.has_all(
+ multiworld.completion_condition[player1.id] = lambda state: state.has_all(
names(items), player1.id)
location = player1.locations[0]
location.progress_type = LocationProgressType.PRIORITY
location.item_rule = lambda item: item not in items[:4]
- distribute_items_restrictive(multi_world)
+ distribute_items_restrictive(multiworld)
self.assertEqual(location.item, items[4])
def test_non_excluded_local_items(self):
"""Test that local items get placed locally in a multiworld"""
- multi_world = generate_multi_world(2)
+ multiworld = generate_multiworld(2)
player1 = generate_player_data(
- multi_world, 1, location_count=5, basic_item_count=5)
+ multiworld, 1, location_count=5, basic_item_count=5)
player2 = generate_player_data(
- multi_world, 2, location_count=5, basic_item_count=5)
+ multiworld, 2, location_count=5, basic_item_count=5)
- for item in multi_world.get_items():
+ for item in multiworld.get_items():
item.classification = ItemClassification.useful
- multi_world.local_items[player1.id].value = set(names(player1.basic_items))
- multi_world.local_items[player2.id].value = set(names(player2.basic_items))
- locality_rules(multi_world)
+ multiworld.local_items[player1.id].value = set(names(player1.basic_items))
+ multiworld.local_items[player2.id].value = set(names(player2.basic_items))
+ locality_rules(multiworld)
- distribute_items_restrictive(multi_world)
+ distribute_items_restrictive(multiworld)
- for item in multi_world.get_items():
+ for item in multiworld.get_items():
self.assertEqual(item.player, item.location.player)
self.assertFalse(item.location.event, False)
def test_early_items(self) -> None:
"""Test that the early items API successfully places items early"""
- mw = generate_multi_world(2)
+ mw = generate_multiworld(2)
player1 = generate_player_data(mw, 1, location_count=5, basic_item_count=5)
player2 = generate_player_data(mw, 2, location_count=5, basic_item_count=5)
mw.early_items[1][player1.basic_items[0].name] = 1
@@ -810,19 +810,19 @@ def assertRegionContains(self, region: Region, item: Item) -> bool:
"\n Contains" + str(list(map(lambda location: location.item, region.locations))))
def setUp(self) -> None:
- multi_world = generate_multi_world(2)
- self.multi_world = multi_world
+ multiworld = generate_multiworld(2)
+ self.multiworld = multiworld
player1 = generate_player_data(
- multi_world, 1, prog_item_count=2, basic_item_count=40)
+ multiworld, 1, prog_item_count=2, basic_item_count=40)
self.player1 = player1
player2 = generate_player_data(
- multi_world, 2, prog_item_count=2, basic_item_count=40)
+ multiworld, 2, prog_item_count=2, basic_item_count=40)
self.player2 = player2
- multi_world.completion_condition[player1.id] = lambda state: state.has(
+ multiworld.completion_condition[player1.id] = lambda state: state.has(
player1.prog_items[0].name, player1.id) and state.has(
player1.prog_items[1].name, player1.id)
- multi_world.completion_condition[player2.id] = lambda state: state.has(
+ multiworld.completion_condition[player2.id] = lambda state: state.has(
player2.prog_items[0].name, player2.id) and state.has(
player2.prog_items[1].name, player2.id)
@@ -830,42 +830,42 @@ def setUp(self) -> None:
# Sphere 1
region = player1.generate_region(player1.menu, 20)
- items = fill_region(multi_world, region, [
+ items = fill_region(multiworld, region, [
player1.prog_items[0]] + items)
# Sphere 2
region = player1.generate_region(
player1.regions[1], 20, lambda state: state.has(player1.prog_items[0].name, player1.id))
items = fill_region(
- multi_world, region, [player1.prog_items[1], player2.prog_items[0]] + items)
+ multiworld, region, [player1.prog_items[1], player2.prog_items[0]] + items)
# Sphere 3
region = player2.generate_region(
player2.menu, 20, lambda state: state.has(player2.prog_items[0].name, player2.id))
- fill_region(multi_world, region, [player2.prog_items[1]] + items)
+ fill_region(multiworld, region, [player2.prog_items[1]] + items)
def test_balances_progression(self) -> None:
"""Tests that progression balancing moves progression items earlier"""
- self.multi_world.progression_balancing[self.player1.id].value = 50
- self.multi_world.progression_balancing[self.player2.id].value = 50
+ self.multiworld.progression_balancing[self.player1.id].value = 50
+ self.multiworld.progression_balancing[self.player2.id].value = 50
self.assertRegionContains(
self.player1.regions[2], self.player2.prog_items[0])
- balance_multiworld_progression(self.multi_world)
+ balance_multiworld_progression(self.multiworld)
self.assertRegionContains(
self.player1.regions[1], self.player2.prog_items[0])
def test_balances_progression_light(self) -> None:
"""Test that progression balancing still moves items earlier on minimum value"""
- self.multi_world.progression_balancing[self.player1.id].value = 1
- self.multi_world.progression_balancing[self.player2.id].value = 1
+ self.multiworld.progression_balancing[self.player1.id].value = 1
+ self.multiworld.progression_balancing[self.player2.id].value = 1
self.assertRegionContains(
self.player1.regions[2], self.player2.prog_items[0])
- balance_multiworld_progression(self.multi_world)
+ balance_multiworld_progression(self.multiworld)
# TODO: arrange for a result that's different from the default
self.assertRegionContains(
@@ -873,13 +873,13 @@ def test_balances_progression_light(self) -> None:
def test_balances_progression_heavy(self) -> None:
"""Test that progression balancing moves items earlier on maximum value"""
- self.multi_world.progression_balancing[self.player1.id].value = 99
- self.multi_world.progression_balancing[self.player2.id].value = 99
+ self.multiworld.progression_balancing[self.player1.id].value = 99
+ self.multiworld.progression_balancing[self.player2.id].value = 99
self.assertRegionContains(
self.player1.regions[2], self.player2.prog_items[0])
- balance_multiworld_progression(self.multi_world)
+ balance_multiworld_progression(self.multiworld)
# TODO: arrange for a result that's different from the default
self.assertRegionContains(
@@ -887,25 +887,25 @@ def test_balances_progression_heavy(self) -> None:
def test_skips_balancing_progression(self) -> None:
"""Test that progression balancing is skipped when players have it disabled"""
- self.multi_world.progression_balancing[self.player1.id].value = 0
- self.multi_world.progression_balancing[self.player2.id].value = 0
+ self.multiworld.progression_balancing[self.player1.id].value = 0
+ self.multiworld.progression_balancing[self.player2.id].value = 0
self.assertRegionContains(
self.player1.regions[2], self.player2.prog_items[0])
- balance_multiworld_progression(self.multi_world)
+ balance_multiworld_progression(self.multiworld)
self.assertRegionContains(
self.player1.regions[2], self.player2.prog_items[0])
def test_ignores_priority_locations(self) -> None:
"""Test that progression items on priority locations don't get moved by balancing"""
- self.multi_world.progression_balancing[self.player1.id].value = 50
- self.multi_world.progression_balancing[self.player2.id].value = 50
+ self.multiworld.progression_balancing[self.player1.id].value = 50
+ self.multiworld.progression_balancing[self.player2.id].value = 50
self.player2.prog_items[0].location.progress_type = LocationProgressType.PRIORITY
- balance_multiworld_progression(self.multi_world)
+ balance_multiworld_progression(self.multiworld)
self.assertRegionContains(
self.player1.regions[2], self.player2.prog_items[0])
diff --git a/test/general/test_groups.py b/test/general/test_groups.py
new file mode 100644
index 000000000000..486d3311fa6b
--- /dev/null
+++ b/test/general/test_groups.py
@@ -0,0 +1,27 @@
+from unittest import TestCase
+
+from worlds.AutoWorld import AutoWorldRegister
+
+
+class TestNameGroups(TestCase):
+ def test_item_name_groups_not_empty(self) -> None:
+ """
+ Test that there are no empty item name groups, which is likely a bug.
+ """
+ for game_name, world_type in AutoWorldRegister.world_types.items():
+ if not world_type.item_id_to_name:
+ continue # ignore worlds without items
+ with self.subTest(game=game_name):
+ for name, group in world_type.item_name_groups.items():
+ self.assertTrue(group, f"Item name group \"{name}\" of \"{game_name}\" is empty")
+
+ def test_location_name_groups_not_empty(self) -> None:
+ """
+ Test that there are no empty location name groups, which is likely a bug.
+ """
+ for game_name, world_type in AutoWorldRegister.world_types.items():
+ if not world_type.location_id_to_name:
+ continue # ignore worlds without locations
+ with self.subTest(game=game_name):
+ for name, group in world_type.location_name_groups.items():
+ self.assertTrue(group, f"Location name group \"{name}\" of \"{game_name}\" is empty")
diff --git a/test/general/test_helpers.py b/test/general/test_helpers.py
index 83b56b34386b..be8473975638 100644
--- a/test/general/test_helpers.py
+++ b/test/general/test_helpers.py
@@ -29,8 +29,8 @@ def test_region_helpers(self) -> None:
"event_loc": None,
},
"TestRegion2": {
- "loc_1": 321,
- "loc_2": 654,
+ "loc_3": 321,
+ "loc_4": 654,
}
}
diff --git a/test/general/test_items.py b/test/general/test_items.py
index bd6c3fd85305..1612937225f2 100644
--- a/test/general/test_items.py
+++ b/test/general/test_items.py
@@ -43,15 +43,15 @@ def test_item_name_group_conflict(self):
with self.subTest(group_name, group_name=group_name):
self.assertNotIn(group_name, world_type.item_name_to_id)
- def test_item_count_greater_equal_locations(self):
- """Test that by the pre_fill step under default settings, each game submits items >= locations"""
+ def test_item_count_equal_locations(self):
+ """Test that by the pre_fill step under default settings, each game submits items == locations"""
for game_name, world_type in AutoWorldRegister.world_types.items():
with self.subTest("Game", game=game_name):
multiworld = setup_solo_multiworld(world_type)
- self.assertGreaterEqual(
+ self.assertEqual(
len(multiworld.itempool),
len(multiworld.get_unfilled_locations()),
- f"{game_name} Item count MUST meet or exceed the number of locations",
+ f"{game_name} Item count MUST match the number of locations",
)
def test_items_in_datapackage(self):
diff --git a/test/general/test_locations.py b/test/general/test_locations.py
index 725b48e62f72..2ac059312c17 100644
--- a/test/general/test_locations.py
+++ b/test/general/test_locations.py
@@ -11,14 +11,14 @@ def test_create_duplicate_locations(self):
multiworld = setup_solo_multiworld(world_type)
locations = Counter(location.name for location in multiworld.get_locations())
if locations:
- self.assertLessEqual(locations.most_common(1)[0][1], 1,
- f"{world_type.game} has duplicate of location name {locations.most_common(1)}")
+ self.assertEqual(locations.most_common(1)[0][1], 1,
+ f"{world_type.game} has duplicate of location name {locations.most_common(1)}")
locations = Counter(location.address for location in multiworld.get_locations()
if type(location.address) is int)
if locations:
- self.assertLessEqual(locations.most_common(1)[0][1], 1,
- f"{world_type.game} has duplicate of location ID {locations.most_common(1)}")
+ self.assertEqual(locations.most_common(1)[0][1], 1,
+ f"{world_type.game} has duplicate of location ID {locations.most_common(1)}")
def test_locations_in_datapackage(self):
"""Tests that created locations not filled before fill starts exist in the datapackage."""
diff --git a/test/general/test_reachability.py b/test/general/test_reachability.py
index cfd83c940463..e57c398b7beb 100644
--- a/test/general/test_reachability.py
+++ b/test/general/test_reachability.py
@@ -36,15 +36,15 @@ def test_default_all_state_can_reach_everything(self):
for game_name, world_type in AutoWorldRegister.world_types.items():
unreachable_regions = self.default_settings_unreachable_regions.get(game_name, set())
with self.subTest("Game", game=game_name):
- world = setup_solo_multiworld(world_type)
- excluded = world.worlds[1].options.exclude_locations.value
- state = world.get_all_state(False)
- for location in world.get_locations():
+ multiworld = setup_solo_multiworld(world_type)
+ excluded = multiworld.worlds[1].options.exclude_locations.value
+ state = multiworld.get_all_state(False)
+ for location in multiworld.get_locations():
if location.name not in excluded:
with self.subTest("Location should be reached", location=location):
self.assertTrue(location.can_reach(state), f"{location.name} unreachable")
- for region in world.get_regions():
+ for region in multiworld.get_regions():
if region.name in unreachable_regions:
with self.subTest("Region should be unreachable", region=region):
self.assertFalse(region.can_reach(state))
@@ -53,15 +53,15 @@ def test_default_all_state_can_reach_everything(self):
self.assertTrue(region.can_reach(state))
with self.subTest("Completion Condition"):
- self.assertTrue(world.can_beat_game(state))
+ self.assertTrue(multiworld.can_beat_game(state))
def test_default_empty_state_can_reach_something(self):
"""Ensure empty state can reach at least one location with the defined options"""
for game_name, world_type in AutoWorldRegister.world_types.items():
with self.subTest("Game", game=game_name):
- world = setup_solo_multiworld(world_type)
- state = CollectionState(world)
- all_locations = world.get_locations()
+ multiworld = setup_solo_multiworld(world_type)
+ state = CollectionState(multiworld)
+ all_locations = multiworld.get_locations()
if all_locations:
locations = set()
for location in all_locations:
diff --git a/test/utils/test_yaml.py b/test/utils/test_yaml.py
new file mode 100644
index 000000000000..4e23857eb07f
--- /dev/null
+++ b/test/utils/test_yaml.py
@@ -0,0 +1,68 @@
+# Tests that yaml wrappers in Utils.py do what they should
+
+import unittest
+from typing import cast, Any, ClassVar, Dict
+
+from Utils import dump, Dumper # type: ignore[attr-defined]
+from Utils import parse_yaml, parse_yamls, unsafe_parse_yaml
+
+
+class AClass:
+ def __eq__(self, other: Any) -> bool:
+ return isinstance(other, self.__class__)
+
+
+class TestYaml(unittest.TestCase):
+ safe_data: ClassVar[Dict[str, Any]] = {
+ "a": [1, 2, 3],
+ "b": None,
+ "c": True,
+ }
+ unsafe_data: ClassVar[Dict[str, Any]] = {
+ "a": AClass()
+ }
+
+ @property
+ def safe_str(self) -> str:
+ return cast(str, dump(self.safe_data, Dumper=Dumper))
+
+ @property
+ def unsafe_str(self) -> str:
+ return cast(str, dump(self.unsafe_data, Dumper=Dumper))
+
+ def assertIsNonEmptyString(self, string: str) -> None:
+ self.assertTrue(string)
+ self.assertIsInstance(string, str)
+
+ def test_dump(self) -> None:
+ self.assertIsNonEmptyString(self.safe_str)
+ self.assertIsNonEmptyString(self.unsafe_str)
+
+ def test_safe_parse(self) -> None:
+ self.assertEqual(self.safe_data, parse_yaml(self.safe_str))
+ with self.assertRaises(Exception):
+ parse_yaml(self.unsafe_str)
+ with self.assertRaises(Exception):
+ parse_yaml("1\n---\n2\n")
+
+ def test_unsafe_parse(self) -> None:
+ self.assertEqual(self.safe_data, unsafe_parse_yaml(self.safe_str))
+ self.assertEqual(self.unsafe_data, unsafe_parse_yaml(self.unsafe_str))
+ with self.assertRaises(Exception):
+ unsafe_parse_yaml("1\n---\n2\n")
+
+ def test_multi_parse(self) -> None:
+ self.assertEqual(self.safe_data, next(parse_yamls(self.safe_str)))
+ with self.assertRaises(Exception):
+ next(parse_yamls(self.unsafe_str))
+ self.assertEqual(2, len(list(parse_yamls("1\n---\n2\n"))))
+
+ def test_unique_key(self) -> None:
+ s = """
+ a: 1
+ a: 2
+ """
+ with self.assertRaises(Exception):
+ parse_yaml(s)
+ with self.assertRaises(Exception):
+ next(parse_yamls(s))
diff --git a/typings/kivy/graphics/texture.pyi b/typings/kivy/graphics/texture.pyi
index 19e03aad69dd..ca643b1cada5 100644
--- a/typings/kivy/graphics/texture.pyi
+++ b/typings/kivy/graphics/texture.pyi
@@ -10,4 +10,4 @@ class FillType_Drawable:
class Texture:
- pass
+ size: FillType_Vec
diff --git a/worlds/AutoWorld.py b/worlds/AutoWorld.py
index fdc50acc5581..dd0f46f6a6d1 100644
--- a/worlds/AutoWorld.py
+++ b/worlds/AutoWorld.py
@@ -7,15 +7,15 @@
import sys
import time
from dataclasses import make_dataclass
-from typing import Any, Callable, ClassVar, Dict, Set, Tuple, FrozenSet, List, Optional, TYPE_CHECKING, TextIO, Type, \
- Union
+from typing import (Any, Callable, ClassVar, Dict, FrozenSet, List, Mapping,
+ Optional, Set, TextIO, Tuple, TYPE_CHECKING, Type, Union)
from Options import PerGameCommonOptions
from BaseClasses import CollectionState
if TYPE_CHECKING:
import random
- from BaseClasses import MultiWorld, Item, Location, Tutorial
+ from BaseClasses import MultiWorld, Item, Location, Tutorial, Region, Entrance
from . import GamesPackage
from settings import Group
@@ -365,13 +365,19 @@ def generate_output(self, output_directory: str) -> None:
If you need any last-second randomization, use self.random instead."""
pass
- def fill_slot_data(self) -> Dict[str, Any]: # json of WebHostLib.models.Slot
- """Fill in the `slot_data` field in the `Connected` network package.
+ def fill_slot_data(self) -> Mapping[str, Any]: # json of WebHostLib.models.Slot
+ """What is returned from this function will be in the `slot_data` field
+ in the `Connected` network package.
+ It should be a `dict` with `str` keys, and should be serializable with json.
+
This is a way the generator can give custom data to the client.
The client will receive this as JSON in the `Connected` response.
The generation does not wait for `generate_output` to complete before calling this.
`threading.Event` can be used if you need to wait for something from `generate_output`."""
+ # The reason for the `Mapping` type annotation, rather than `dict`
+ # is so that type checkers won't worry about the mutability of `dict`,
+ # so you can have more specific typing in your world implementation.
return {}
def extend_hint_information(self, hint_data: Dict[int, Dict[int, str]]):
@@ -438,7 +444,7 @@ def collect_item(self, state: "CollectionState", item: "Item", remove: bool = Fa
def get_pre_fill_items(self) -> List["Item"]:
return []
- # following methods should not need to be overridden.
+ # these two methods can be extended for pseudo-items on state
def collect(self, state: "CollectionState", item: "Item") -> bool:
name = self.collect_item(state, item)
if name:
@@ -458,6 +464,16 @@ def remove(self, state: "CollectionState", item: "Item") -> bool:
def create_filler(self) -> "Item":
return self.create_item(self.get_filler_item_name())
+ # convenience methods
+ def get_location(self, location_name: str) -> "Location":
+ return self.multiworld.get_location(location_name, self.player)
+
+ def get_entrance(self, entrance_name: str) -> "Entrance":
+ return self.multiworld.get_entrance(entrance_name, self.player)
+
+ def get_region(self, region_name: str) -> "Region":
+ return self.multiworld.get_region(region_name, self.player)
+
@classmethod
def get_data_package_data(cls) -> "GamesPackage":
sorted_item_name_groups = {
diff --git a/worlds/Files.py b/worlds/Files.py
index 52d3c7da1d35..336a3090937b 100644
--- a/worlds/Files.py
+++ b/worlds/Files.py
@@ -1,5 +1,6 @@
from __future__ import annotations
+import abc
import json
import zipfile
import os
@@ -15,7 +16,7 @@
del os
-class AutoPatchRegister(type):
+class AutoPatchRegister(abc.ABCMeta):
patch_types: ClassVar[Dict[str, AutoPatchRegister]] = {}
file_endings: ClassVar[Dict[str, AutoPatchRegister]] = {}
@@ -112,14 +113,25 @@ def get_manifest(self) -> Dict[str, Any]:
}
-class APDeltaPatch(APContainer, metaclass=AutoPatchRegister):
- """An APContainer that additionally has delta.bsdiff4
+class APPatch(APContainer, abc.ABC, metaclass=AutoPatchRegister):
+ """
+ An abstract `APContainer` that defines the requirements for an object
+ to be used by the `Patch.create_rom_file` function.
+ """
+ result_file_ending: str = ".sfc"
+
+ @abc.abstractmethod
+ def patch(self, target: str) -> None:
+ """ create the output file with the file name `target` """
+
+
+class APDeltaPatch(APPatch):
+ """An APPatch that additionally has delta.bsdiff4
containing a delta patch to get the desired file, often a rom."""
hash: Optional[str] # base checksum of source file
patch_file_ending: str = ""
delta: Optional[bytes] = None
- result_file_ending: str = ".sfc"
source_data: bytes
def __init__(self, *args: Any, patched_path: str = "", **kwargs: Any) -> None:
diff --git a/worlds/__init__.py b/worlds/__init__.py
index 66c91639b9f3..168bba7abf41 100644
--- a/worlds/__init__.py
+++ b/worlds/__init__.py
@@ -3,7 +3,9 @@
import sys
import warnings
import zipimport
-from typing import Dict, List, NamedTuple, TypedDict
+import time
+import dataclasses
+from typing import Dict, List, TypedDict, Optional
from Utils import local_path, user_path
@@ -34,10 +36,12 @@ class DataPackage(TypedDict):
games: Dict[str, GamesPackage]
-class WorldSource(NamedTuple):
+@dataclasses.dataclass(order=True)
+class WorldSource:
path: str # typically relative path from this module
is_zip: bool = False
relative: bool = True # relative to regular world import folder
+ time_taken: Optional[float] = None
def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.path}, is_zip={self.is_zip}, relative={self.relative})"
@@ -50,6 +54,7 @@ def resolved_path(self) -> str:
def load(self) -> bool:
try:
+ start = time.perf_counter()
if self.is_zip:
importer = zipimport.zipimporter(self.resolved_path)
if hasattr(importer, "find_spec"): # new in Python 3.10
@@ -69,6 +74,7 @@ def load(self) -> bool:
importer.exec_module(mod)
else:
importlib.import_module(f".{self.path}", "worlds")
+ self.time_taken = time.perf_counter()-start
return True
except Exception:
diff --git a/worlds/adventure/__init__.py b/worlds/adventure/__init__.py
index 105725bd053c..9b9b0d77d800 100644
--- a/worlds/adventure/__init__.py
+++ b/worlds/adventure/__init__.py
@@ -271,7 +271,7 @@ def pre_fill(self):
overworld_locations_copy = overworld.locations.copy()
all_locations = self.multiworld.get_locations(self.player)
- locations_copy = all_locations.copy()
+ locations_copy = list(all_locations)
for loc in all_locations:
if loc.item is not None or loc.progress_type != LocationProgressType.DEFAULT:
locations_copy.remove(loc)
diff --git a/worlds/alttp/Bosses.py b/worlds/alttp/Bosses.py
index 90ffe9dcf4b1..965a86db008a 100644
--- a/worlds/alttp/Bosses.py
+++ b/worlds/alttp/Bosses.py
@@ -6,7 +6,7 @@
from Fill import FillError
from .Options import LTTPBosses as Bosses
from .StateHelpers import can_shoot_arrows, can_extend_magic, can_get_good_bee, has_sword, has_beam_sword, \
- has_melee_weapon, has_fire_source
+ has_melee_weapon, has_fire_source, can_use_bombs
if TYPE_CHECKING:
from . import ALTTPWorld
@@ -62,7 +62,8 @@ def MoldormDefeatRule(state, player: int) -> bool:
def HelmasaurKingDefeatRule(state, player: int) -> bool:
# TODO: technically possible with the hammer
- return has_sword(state, player) or can_shoot_arrows(state, player)
+ return (can_use_bombs(state, player, 5) or state.has("Hammer", player)) and (has_sword(state, player)
+ or can_shoot_arrows(state, player))
def ArrghusDefeatRule(state, player: int) -> bool:
@@ -143,7 +144,7 @@ def GanonDefeatRule(state, player: int) -> bool:
can_hurt = has_beam_sword(state, player)
common = can_hurt and has_fire_source(state, player)
# silverless ganon may be needed in anything higher than no glitches
- if state.multiworld.logic[player] != 'noglitches':
+ if state.multiworld.glitches_required[player] != 'no_glitches':
# need to light torch a sufficient amount of times
return common and (state.has('Tempered Sword', player) or state.has('Golden Sword', player) or (
state.has('Silver Bow', player) and can_shoot_arrows(state, player)) or
diff --git a/worlds/alttp/Dungeons.py b/worlds/alttp/Dungeons.py
index b456174f39b7..c886fce92079 100644
--- a/worlds/alttp/Dungeons.py
+++ b/worlds/alttp/Dungeons.py
@@ -9,7 +9,7 @@
from .Bosses import BossFactory, Boss
from .Items import ItemFactory
from .Regions import lookup_boss_drops, key_drop_data
-from .Options import smallkey_shuffle
+from .Options import small_key_shuffle
if typing.TYPE_CHECKING:
from .SubClasses import ALttPLocation, ALttPItem
@@ -66,7 +66,7 @@ def create_dungeons(world: "ALTTPWorld"):
def make_dungeon(name, default_boss, dungeon_regions, big_key, small_keys, dungeon_items):
dungeon = Dungeon(name, dungeon_regions, big_key,
- [] if multiworld.smallkey_shuffle[player] == smallkey_shuffle.option_universal else small_keys,
+ [] if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal else small_keys,
dungeon_items, player)
for item in dungeon.all_items:
item.dungeon = dungeon
diff --git a/worlds/alttp/EntranceRandomizer.py b/worlds/alttp/EntranceRandomizer.py
index 47c36b6cde33..37486a9cde07 100644
--- a/worlds/alttp/EntranceRandomizer.py
+++ b/worlds/alttp/EntranceRandomizer.py
@@ -23,7 +23,7 @@ def defval(value):
multiargs, _ = parser.parse_known_args(argv)
parser = argparse.ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
- parser.add_argument('--logic', default=defval('noglitches'), const='noglitches', nargs='?', choices=['noglitches', 'minorglitches', 'owglitches', 'hybridglitches', 'nologic'],
+ parser.add_argument('--logic', default=defval('no_glitches'), const='no_glitches', nargs='?', choices=['no_glitches', 'minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'],
help='''\
Select Enforcement of Item Requirements. (default: %(default)s)
No Glitches:
@@ -49,7 +49,7 @@ def defval(value):
instead of a bunny.
''')
parser.add_argument('--goal', default=defval('ganon'), const='ganon', nargs='?',
- choices=['ganon', 'pedestal', 'bosses', 'triforcehunt', 'localtriforcehunt', 'ganontriforcehunt', 'localganontriforcehunt', 'crystals', 'ganonpedestal'],
+ choices=['ganon', 'pedestal', 'bosses', 'triforce_hunt', 'local_triforce_hunt', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'crystals', 'ganon_pedestal'],
help='''\
Select completion goal. (default: %(default)s)
Ganon: Collect all crystals, beat Agahnim 2 then
@@ -92,7 +92,7 @@ def defval(value):
Hard: Reduced functionality.
Expert: Greatly reduced functionality.
''')
- parser.add_argument('--timer', default=defval('none'), const='normal', nargs='?', choices=['none', 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'],
+ parser.add_argument('--timer', default=defval('none'), const='normal', nargs='?', choices=['none', 'display', 'timed', 'timed_ohko', 'ohko', 'timed_countdown'],
help='''\
Select game timer setting. Affects available itempool. (default: %(default)s)
None: No timer.
@@ -151,7 +151,7 @@ def defval(value):
slightly biased to placing progression items with
less restrictions.
''')
- parser.add_argument('--shuffle', default=defval('vanilla'), const='vanilla', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', 'dungeonsfull', 'dungeonssimple', 'dungeonscrossed'],
+ parser.add_argument('--shuffle', default=defval('vanilla'), const='vanilla', nargs='?', choices=['vanilla', 'simple', 'restricted', 'full', 'crossed', 'insanity', 'restricted_legacy', 'full_legacy', 'madness_legacy', 'insanity_legacy', 'dungeons_full', 'dungeons_simple', 'dungeons_crossed'],
help='''\
Select Entrance Shuffling Algorithm. (default: %(default)s)
Full: Mix cave and dungeon entrances freely while limiting
@@ -178,9 +178,9 @@ def defval(value):
parser.add_argument('--open_pyramid', default=defval('auto'), help='''\
Pre-opens the pyramid hole, this removes the Agahnim 2 requirement for it.
Depending on goal, you might still need to beat Agahnim 2 in order to beat ganon.
- fast ganon goals are crystals, ganontriforcehunt, localganontriforcehunt, pedestalganon
+ fast ganon goals are crystals, ganon_triforce_hunt, local_ganon_triforce_hunt, pedestalganon
auto - Only opens pyramid hole if the goal specifies a fast ganon, and entrance shuffle
- is vanilla, dungeonssimple or dungeonsfull.
+ is vanilla, dungeons_simple or dungeons_full.
goal - Opens pyramid hole if the goal specifies a fast ganon.
yes - Always opens the pyramid hole.
no - Never opens the pyramid hole.
diff --git a/worlds/alttp/EntranceShuffle.py b/worlds/alttp/EntranceShuffle.py
index 07bb587eebe3..fceba86a739e 100644
--- a/worlds/alttp/EntranceShuffle.py
+++ b/worlds/alttp/EntranceShuffle.py
@@ -21,17 +21,17 @@ def link_entrances(world, player):
connect_simple(world, exitname, regionname, player)
# if we do not shuffle, set default connections
- if world.shuffle[player] == 'vanilla':
+ if world.entrance_shuffle[player] == 'vanilla':
for exitname, regionname in default_connections:
connect_simple(world, exitname, regionname, player)
for exitname, regionname in default_dungeon_connections:
connect_simple(world, exitname, regionname, player)
- elif world.shuffle[player] == 'dungeonssimple':
+ elif world.entrance_shuffle[player] == 'dungeons_simple':
for exitname, regionname in default_connections:
connect_simple(world, exitname, regionname, player)
simple_shuffle_dungeons(world, player)
- elif world.shuffle[player] == 'dungeonsfull':
+ elif world.entrance_shuffle[player] == 'dungeons_full':
for exitname, regionname in default_connections:
connect_simple(world, exitname, regionname, player)
@@ -63,9 +63,9 @@ def link_entrances(world, player):
connect_mandatory_exits(world, lw_entrances, dungeon_exits, list(LW_Dungeon_Entrances_Must_Exit), player)
connect_mandatory_exits(world, dw_entrances, dungeon_exits, list(DW_Dungeon_Entrances_Must_Exit), player)
connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player)
- elif world.shuffle[player] == 'dungeonscrossed':
+ elif world.entrance_shuffle[player] == 'dungeons_crossed':
crossed_shuffle_dungeons(world, player)
- elif world.shuffle[player] == 'simple':
+ elif world.entrance_shuffle[player] == 'simple':
simple_shuffle_dungeons(world, player)
old_man_entrances = list(Old_Man_Entrances)
@@ -136,7 +136,7 @@ def link_entrances(world, player):
# place remaining doors
connect_doors(world, single_doors, door_targets, player)
- elif world.shuffle[player] == 'restricted':
+ elif world.entrance_shuffle[player] == 'restricted':
simple_shuffle_dungeons(world, player)
lw_entrances = list(LW_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances)
@@ -207,62 +207,8 @@ def link_entrances(world, player):
# place remaining doors
connect_doors(world, doors, door_targets, player)
- elif world.shuffle[player] == 'restricted_legacy':
- simple_shuffle_dungeons(world, player)
-
- lw_entrances = list(LW_Entrances)
- dw_entrances = list(DW_Entrances)
- dw_must_exits = list(DW_Entrances_Must_Exit)
- old_man_entrances = list(Old_Man_Entrances)
- caves = list(Cave_Exits)
- three_exit_caves = list(Cave_Three_Exits)
- single_doors = list(Single_Cave_Doors)
- bomb_shop_doors = list(Bomb_Shop_Single_Cave_Doors)
- blacksmith_doors = list(Blacksmith_Single_Cave_Doors)
- door_targets = list(Single_Cave_Targets)
-
- # only use two exit caves to do mandatory dw connections
- connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player)
- # add three exit doors to pool for remainder
- caves.extend(three_exit_caves)
-
- # place old man, has limited options
- # exit has to come from specific set of doors, the entrance is free to move about
- world.random.shuffle(old_man_entrances)
- old_man_exit = old_man_entrances.pop()
- lw_entrances.extend(old_man_entrances)
- world.random.shuffle(lw_entrances)
- old_man_entrance = lw_entrances.pop()
- connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)', player)
- connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player)
-
- # place Old Man House in Light World
- connect_caves(world, lw_entrances, [], Old_Man_House, player)
- # connect rest. There's 2 dw entrances remaining, so we will not run into parity issue placing caves
- connect_caves(world, lw_entrances, dw_entrances, caves, player)
-
- # scramble holes
- scramble_holes(world, player)
-
- # place blacksmith, has limited options
- world.random.shuffle(blacksmith_doors)
- blacksmith_hut = blacksmith_doors.pop()
- connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player)
- bomb_shop_doors.extend(blacksmith_doors)
-
- # place dam and pyramid fairy, have limited options
- world.random.shuffle(bomb_shop_doors)
- bomb_shop = bomb_shop_doors.pop()
- connect_entrance(world, bomb_shop, 'Big Bomb Shop', player)
- single_doors.extend(bomb_shop_doors)
-
- # tavern back door cannot be shuffled yet
- connect_doors(world, ['Tavern North'], ['Tavern'], player)
-
- # place remaining doors
- connect_doors(world, single_doors, door_targets, player)
- elif world.shuffle[player] == 'full':
+ elif world.entrance_shuffle[player] == 'full':
skull_woods_shuffle(world, player)
lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances)
@@ -368,7 +314,7 @@ def link_entrances(world, player):
# place remaining doors
connect_doors(world, doors, door_targets, player)
- elif world.shuffle[player] == 'crossed':
+ elif world.entrance_shuffle[player] == 'crossed':
skull_woods_shuffle(world, player)
entrances = list(LW_Entrances + LW_Dungeon_Entrances + LW_Single_Cave_Doors + Old_Man_Entrances + DW_Entrances + DW_Dungeon_Entrances + DW_Single_Cave_Doors)
@@ -445,337 +391,8 @@ def link_entrances(world, player):
# place remaining doors
connect_doors(world, entrances, door_targets, player)
- elif world.shuffle[player] == 'full_legacy':
- skull_woods_shuffle(world, player)
-
- lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + Old_Man_Entrances)
- dw_entrances = list(DW_Entrances + DW_Dungeon_Entrances)
- dw_must_exits = list(DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit)
- lw_must_exits = list(LW_Dungeon_Entrances_Must_Exit)
- old_man_entrances = list(Old_Man_Entrances + ['Tower of Hera'])
- caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits) # don't need to consider three exit caves, have one exit caves to avoid parity issues
- single_doors = list(Single_Cave_Doors)
- bomb_shop_doors = list(Bomb_Shop_Single_Cave_Doors)
- blacksmith_doors = list(Blacksmith_Single_Cave_Doors)
- door_targets = list(Single_Cave_Targets)
-
- if world.mode[player] == 'standard':
- # must connect front of hyrule castle to do escape
- connect_two_way(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
- else:
- caves.append(tuple(world.random.sample(
- ['Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'], 3)))
- lw_entrances.append('Hyrule Castle Entrance (South)')
-
- if not world.shuffle_ganon:
- connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player)
- else:
- dw_entrances.append('Ganons Tower')
- caves.append('Ganons Tower Exit')
-
- # we randomize which world requirements we fulfill first so we get better dungeon distribution
- if world.random.randint(0, 1) == 0:
- connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player)
- connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player)
- else:
- connect_mandatory_exits(world, dw_entrances, caves, dw_must_exits, player)
- connect_mandatory_exits(world, lw_entrances, caves, lw_must_exits, player)
- if world.mode[player] == 'standard':
- # rest of hyrule castle must be in light world
- connect_caves(world, lw_entrances, [], [('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)')], player)
-
- # place old man, has limited options
- # exit has to come from specific set of doors, the entrance is free to move about
- old_man_entrances = [door for door in old_man_entrances if door in lw_entrances]
- world.random.shuffle(old_man_entrances)
- old_man_exit = old_man_entrances.pop()
- lw_entrances.remove(old_man_exit)
-
- world.random.shuffle(lw_entrances)
- old_man_entrance = lw_entrances.pop()
- connect_two_way(world, old_man_entrance, 'Old Man Cave Exit (West)', player)
- connect_two_way(world, old_man_exit, 'Old Man Cave Exit (East)', player)
-
- # place Old Man House in Light World
- connect_caves(world, lw_entrances, [], list(Old_Man_House), player) #need this to avoid badness with multiple seeds
-
- # now scramble the rest
- connect_caves(world, lw_entrances, dw_entrances, caves, player)
-
- # scramble holes
- scramble_holes(world, player)
-
- # place blacksmith, has limited options
- world.random.shuffle(blacksmith_doors)
- blacksmith_hut = blacksmith_doors.pop()
- connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player)
- bomb_shop_doors.extend(blacksmith_doors)
-
- # place bomb shop, has limited options
- world.random.shuffle(bomb_shop_doors)
- bomb_shop = bomb_shop_doors.pop()
- connect_entrance(world, bomb_shop, 'Big Bomb Shop', player)
- single_doors.extend(bomb_shop_doors)
-
- # tavern back door cannot be shuffled yet
- connect_doors(world, ['Tavern North'], ['Tavern'], player)
-
- # place remaining doors
- connect_doors(world, single_doors, door_targets, player)
- elif world.shuffle[player] == 'madness_legacy':
- # here lie dragons, connections are no longer two way
- lw_entrances = list(LW_Entrances + LW_Dungeon_Entrances + Old_Man_Entrances)
- dw_entrances = list(DW_Entrances + DW_Dungeon_Entrances)
- dw_entrances_must_exits = list(DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit)
-
- lw_doors = list(LW_Entrances + LW_Dungeon_Entrances + LW_Dungeon_Entrances_Must_Exit) + ['Kakariko Well Cave',
- 'Bat Cave Cave',
- 'North Fairy Cave',
- 'Sanctuary',
- 'Lost Woods Hideout Stump',
- 'Lumberjack Tree Cave'] + list(
- Old_Man_Entrances)
- dw_doors = list(
- DW_Entrances + DW_Dungeon_Entrances + DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit) + [
- 'Skull Woods First Section Door', 'Skull Woods Second Section Door (East)',
- 'Skull Woods Second Section Door (West)']
-
- world.random.shuffle(lw_doors)
- world.random.shuffle(dw_doors)
-
- dw_entrances_must_exits.append('Skull Woods Second Section Door (West)')
- dw_entrances.append('Skull Woods Second Section Door (East)')
- dw_entrances.append('Skull Woods First Section Door')
-
- lw_entrances.extend(
- ['Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump',
- 'Lumberjack Tree Cave'])
-
- lw_entrances_must_exits = list(LW_Dungeon_Entrances_Must_Exit)
-
- old_man_entrances = list(Old_Man_Entrances) + ['Tower of Hera']
-
- mandatory_light_world = ['Old Man House Exit (Bottom)', 'Old Man House Exit (Top)']
- mandatory_dark_world = []
- caves = list(Cave_Exits + Dungeon_Exits + Cave_Three_Exits)
-
- # shuffle up holes
-
- lw_hole_entrances = ['Kakariko Well Drop', 'Bat Cave Drop', 'North Fairy Cave Drop', 'Lost Woods Hideout Drop', 'Lumberjack Tree Tree', 'Sanctuary Grave']
- dw_hole_entrances = ['Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole']
-
- hole_targets = [('Kakariko Well Exit', 'Kakariko Well (top)'),
- ('Bat Cave Exit', 'Bat Cave (right)'),
- ('North Fairy Cave Exit', 'North Fairy Cave'),
- ('Lost Woods Hideout Exit', 'Lost Woods Hideout (top)'),
- ('Lumberjack Tree Exit', 'Lumberjack Tree (top)'),
- (('Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)'), 'Skull Woods Second Section (Drop)')]
-
- if world.mode[player] == 'standard':
- # cannot move uncle cave
- connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player)
- connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player)
- connect_entrance(world, 'Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Exit', player)
- else:
- lw_hole_entrances.append('Hyrule Castle Secret Entrance Drop')
- hole_targets.append(('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance'))
- lw_doors.append('Hyrule Castle Secret Entrance Stairs')
- lw_entrances.append('Hyrule Castle Secret Entrance Stairs')
-
- if not world.shuffle_ganon:
- connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player)
- connect_two_way(world, 'Pyramid Entrance', 'Pyramid Exit', player)
- connect_entrance(world, 'Pyramid Hole', 'Pyramid', player)
- else:
- dw_entrances.append('Ganons Tower')
- caves.append('Ganons Tower Exit')
- dw_hole_entrances.append('Pyramid Hole')
- hole_targets.append(('Pyramid Exit', 'Pyramid'))
- dw_entrances_must_exits.append('Pyramid Entrance')
- dw_doors.extend(['Ganons Tower', 'Pyramid Entrance'])
-
- world.random.shuffle(lw_hole_entrances)
- world.random.shuffle(dw_hole_entrances)
- world.random.shuffle(hole_targets)
-
- # decide if skull woods first section should be in light or dark world
- sw_light = world.random.randint(0, 1) == 0
- if sw_light:
- sw_hole_pool = lw_hole_entrances
- mandatory_light_world.append('Skull Woods First Section Exit')
- else:
- sw_hole_pool = dw_hole_entrances
- mandatory_dark_world.append('Skull Woods First Section Exit')
- for target in ['Skull Woods First Section (Left)', 'Skull Woods First Section (Right)',
- 'Skull Woods First Section (Top)']:
- connect_entrance(world, sw_hole_pool.pop(), target, player)
-
- # sanctuary has to be in light world
- connect_entrance(world, lw_hole_entrances.pop(), 'Sewer Drop', player)
- mandatory_light_world.append('Sanctuary Exit')
-
- # fill up remaining holes
- for hole in dw_hole_entrances:
- exits, target = hole_targets.pop()
- mandatory_dark_world.append(exits)
- connect_entrance(world, hole, target, player)
-
- for hole in lw_hole_entrances:
- exits, target = hole_targets.pop()
- mandatory_light_world.append(exits)
- connect_entrance(world, hole, target, player)
-
- # hyrule castle handling
- if world.mode[player] == 'standard':
- # must connect front of hyrule castle to do escape
- connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
- connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player)
- mandatory_light_world.append(('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'))
- else:
- lw_doors.append('Hyrule Castle Entrance (South)')
- lw_entrances.append('Hyrule Castle Entrance (South)')
- caves.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'))
-
- # now let's deal with mandatory reachable stuff
- def extract_reachable_exit(cavelist):
- world.random.shuffle(cavelist)
- candidate = None
- for cave in cavelist:
- if isinstance(cave, tuple) and len(cave) > 1:
- # special handling: TRock and Spectracle Rock cave have two entries that we should consider entrance only
- # ToDo this should be handled in a more sensible manner
- if cave[0] in ['Turtle Rock Exit (Front)', 'Spectacle Rock Cave Exit (Peak)'] and len(cave) == 2:
- continue
- candidate = cave
- break
- if candidate is None:
- raise KeyError('No suitable cave.')
- cavelist.remove(candidate)
- return candidate
-
- def connect_reachable_exit(entrance, general, worldspecific, worldoors):
- # select which one is the primary option
- if world.random.randint(0, 1) == 0:
- primary = general
- secondary = worldspecific
- else:
- primary = worldspecific
- secondary = general
-
- try:
- cave = extract_reachable_exit(primary)
- except KeyError:
- cave = extract_reachable_exit(secondary)
-
- exit = cave[-1]
- cave = cave[:-1]
- connect_exit(world, exit, entrance, player)
- connect_entrance(world, worldoors.pop(), exit, player)
- # rest of cave now is forced to be in this world
- worldspecific.append(cave)
-
- # we randomize which world requirements we fulfill first so we get better dungeon distribution
- if world.random.randint(0, 1) == 0:
- for entrance in lw_entrances_must_exits:
- connect_reachable_exit(entrance, caves, mandatory_light_world, lw_doors)
- for entrance in dw_entrances_must_exits:
- connect_reachable_exit(entrance, caves, mandatory_dark_world, dw_doors)
- else:
- for entrance in dw_entrances_must_exits:
- connect_reachable_exit(entrance, caves, mandatory_dark_world, dw_doors)
- for entrance in lw_entrances_must_exits:
- connect_reachable_exit(entrance, caves, mandatory_light_world, lw_doors)
-
- # place old man, has limited options
- # exit has to come from specific set of doors, the entrance is free to move about
- old_man_entrances = [entrance for entrance in old_man_entrances if entrance in lw_entrances]
- world.random.shuffle(old_man_entrances)
- old_man_exit = old_man_entrances.pop()
- lw_entrances.remove(old_man_exit)
-
- connect_exit(world, 'Old Man Cave Exit (East)', old_man_exit, player)
- connect_entrance(world, lw_doors.pop(), 'Old Man Cave Exit (East)', player)
- mandatory_light_world.append('Old Man Cave Exit (West)')
-
- # we connect up the mandatory associations we have found
- for mandatory in mandatory_light_world:
- if not isinstance(mandatory, tuple):
- mandatory = (mandatory,)
- for exit in mandatory:
- # point out somewhere
- connect_exit(world, exit, lw_entrances.pop(), player)
- # point in from somewhere
- connect_entrance(world, lw_doors.pop(), exit, player)
-
- for mandatory in mandatory_dark_world:
- if not isinstance(mandatory, tuple):
- mandatory = (mandatory,)
- for exit in mandatory:
- # point out somewhere
- connect_exit(world, exit, dw_entrances.pop(), player)
- # point in from somewhere
- connect_entrance(world, dw_doors.pop(), exit, player)
- # handle remaining caves
- while caves:
- # connect highest exit count caves first, prevent issue where we have 2 or 3 exits accross worlds left to fill
- cave_candidate = (None, 0)
- for i, cave in enumerate(caves):
- if isinstance(cave, str):
- cave = (cave,)
- if len(cave) > cave_candidate[1]:
- cave_candidate = (i, len(cave))
- cave = caves.pop(cave_candidate[0])
-
- place_lightworld = world.random.randint(0, 1) == 0
- if place_lightworld:
- target_doors = lw_doors
- target_entrances = lw_entrances
- else:
- target_doors = dw_doors
- target_entrances = dw_entrances
-
- if isinstance(cave, str):
- cave = (cave,)
-
- # check if we can still fit the cave into our target group
- if len(target_doors) < len(cave):
- if not place_lightworld:
- target_doors = lw_doors
- target_entrances = lw_entrances
- else:
- target_doors = dw_doors
- target_entrances = dw_entrances
-
- for exit in cave:
- connect_exit(world, exit, target_entrances.pop(), player)
- connect_entrance(world, target_doors.pop(), exit, player)
-
- # handle simple doors
-
- single_doors = list(Single_Cave_Doors)
- bomb_shop_doors = list(Bomb_Shop_Single_Cave_Doors)
- blacksmith_doors = list(Blacksmith_Single_Cave_Doors)
- door_targets = list(Single_Cave_Targets)
-
- # place blacksmith, has limited options
- world.random.shuffle(blacksmith_doors)
- blacksmith_hut = blacksmith_doors.pop()
- connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player)
- bomb_shop_doors.extend(blacksmith_doors)
-
- # place dam and pyramid fairy, have limited options
- world.random.shuffle(bomb_shop_doors)
- bomb_shop = bomb_shop_doors.pop()
- connect_entrance(world, bomb_shop, 'Big Bomb Shop', player)
- single_doors.extend(bomb_shop_doors)
-
- # tavern back door cannot be shuffled yet
- connect_doors(world, ['Tavern North'], ['Tavern'], player)
-
- # place remaining doors
- connect_doors(world, single_doors, door_targets, player)
- elif world.shuffle[player] == 'insanity':
+ elif world.entrance_shuffle[player] == 'insanity':
# beware ye who enter here
entrances = LW_Entrances + LW_Dungeon_Entrances + DW_Entrances + DW_Dungeon_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave']
@@ -922,157 +539,15 @@ def connect_reachable_exit(entrance, caves, doors):
# place remaining doors
connect_doors(world, doors, door_targets, player)
- elif world.shuffle[player] == 'insanity_legacy':
- world.fix_fake_world[player] = False
- # beware ye who enter here
-
- entrances = LW_Entrances + LW_Dungeon_Entrances + DW_Entrances + DW_Dungeon_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave']
- entrances_must_exits = DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit + LW_Dungeon_Entrances_Must_Exit + ['Skull Woods Second Section Door (West)']
-
- doors = LW_Entrances + LW_Dungeon_Entrances + LW_Dungeon_Entrances_Must_Exit + ['Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave'] + Old_Man_Entrances +\
- DW_Entrances + DW_Dungeon_Entrances + DW_Entrances_Must_Exit + DW_Dungeon_Entrances_Must_Exit + ['Skull Woods First Section Door', 'Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)']
-
- world.random.shuffle(doors)
-
- old_man_entrances = list(Old_Man_Entrances) + ['Tower of Hera']
-
- caves = Cave_Exits + Dungeon_Exits + Cave_Three_Exits + ['Old Man House Exit (Bottom)', 'Old Man House Exit (Top)', 'Skull Woods First Section Exit', 'Skull Woods Second Section Exit (East)', 'Skull Woods Second Section Exit (West)',
- 'Kakariko Well Exit', 'Bat Cave Exit', 'North Fairy Cave Exit', 'Lost Woods Hideout Exit', 'Lumberjack Tree Exit', 'Sanctuary Exit']
-
- # shuffle up holes
-
- hole_entrances = ['Kakariko Well Drop', 'Bat Cave Drop', 'North Fairy Cave Drop', 'Lost Woods Hideout Drop', 'Lumberjack Tree Tree', 'Sanctuary Grave',
- 'Skull Woods First Section Hole (East)', 'Skull Woods First Section Hole (West)', 'Skull Woods First Section Hole (North)', 'Skull Woods Second Section Hole']
-
- hole_targets = ['Kakariko Well (top)', 'Bat Cave (right)', 'North Fairy Cave', 'Lost Woods Hideout (top)', 'Lumberjack Tree (top)', 'Sewer Drop', 'Skull Woods Second Section (Drop)',
- 'Skull Woods First Section (Left)', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Top)']
-
- if world.mode[player] == 'standard':
- # cannot move uncle cave
- connect_entrance(world, 'Hyrule Castle Secret Entrance Drop', 'Hyrule Castle Secret Entrance', player)
- connect_exit(world, 'Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance Stairs', player)
- connect_entrance(world, 'Hyrule Castle Secret Entrance Stairs', 'Hyrule Castle Secret Entrance Exit', player)
- else:
- hole_entrances.append('Hyrule Castle Secret Entrance Drop')
- hole_targets.append('Hyrule Castle Secret Entrance')
- doors.append('Hyrule Castle Secret Entrance Stairs')
- entrances.append('Hyrule Castle Secret Entrance Stairs')
- caves.append('Hyrule Castle Secret Entrance Exit')
-
- if not world.shuffle_ganon:
- connect_two_way(world, 'Ganons Tower', 'Ganons Tower Exit', player)
- connect_two_way(world, 'Pyramid Entrance', 'Pyramid Exit', player)
- connect_entrance(world, 'Pyramid Hole', 'Pyramid', player)
- else:
- entrances.append('Ganons Tower')
- caves.extend(['Ganons Tower Exit', 'Pyramid Exit'])
- hole_entrances.append('Pyramid Hole')
- hole_targets.append('Pyramid')
- entrances_must_exits.append('Pyramid Entrance')
- doors.extend(['Ganons Tower', 'Pyramid Entrance'])
-
- world.random.shuffle(hole_entrances)
- world.random.shuffle(hole_targets)
- world.random.shuffle(entrances)
-
- # fill up holes
- for hole in hole_entrances:
- connect_entrance(world, hole, hole_targets.pop(), player)
-
- # hyrule castle handling
- if world.mode[player] == 'standard':
- # must connect front of hyrule castle to do escape
- connect_entrance(world, 'Hyrule Castle Entrance (South)', 'Hyrule Castle Exit (South)', player)
- connect_exit(world, 'Hyrule Castle Exit (South)', 'Hyrule Castle Entrance (South)', player)
- caves.append(('Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'))
- else:
- doors.append('Hyrule Castle Entrance (South)')
- entrances.append('Hyrule Castle Entrance (South)')
- caves.append(('Hyrule Castle Exit (South)', 'Hyrule Castle Exit (West)', 'Hyrule Castle Exit (East)'))
-
- # now let's deal with mandatory reachable stuff
- def extract_reachable_exit(cavelist):
- world.random.shuffle(cavelist)
- candidate = None
- for cave in cavelist:
- if isinstance(cave, tuple) and len(cave) > 1:
- # special handling: TRock has two entries that we should consider entrance only
- # ToDo this should be handled in a more sensible manner
- if cave[0] in ['Turtle Rock Exit (Front)', 'Spectacle Rock Cave Exit (Peak)'] and len(cave) == 2:
- continue
- candidate = cave
- break
- if candidate is None:
- raise KeyError('No suitable cave.')
- cavelist.remove(candidate)
- return candidate
-
- def connect_reachable_exit(entrance, caves, doors):
- cave = extract_reachable_exit(caves)
-
- exit = cave[-1]
- cave = cave[:-1]
- connect_exit(world, exit, entrance, player)
- connect_entrance(world, doors.pop(), exit, player)
- # rest of cave now is forced to be in this world
- caves.append(cave)
-
- # connect mandatory exits
- for entrance in entrances_must_exits:
- connect_reachable_exit(entrance, caves, doors)
-
- # place old man, has limited options
- # exit has to come from specific set of doors, the entrance is free to move about
- old_man_entrances = [entrance for entrance in old_man_entrances if entrance in entrances]
- world.random.shuffle(old_man_entrances)
- old_man_exit = old_man_entrances.pop()
- entrances.remove(old_man_exit)
-
- connect_exit(world, 'Old Man Cave Exit (East)', old_man_exit, player)
- connect_entrance(world, doors.pop(), 'Old Man Cave Exit (East)', player)
- caves.append('Old Man Cave Exit (West)')
-
- # handle remaining caves
- for cave in caves:
- if isinstance(cave, str):
- cave = (cave,)
-
- for exit in cave:
- connect_exit(world, exit, entrances.pop(), player)
- connect_entrance(world, doors.pop(), exit, player)
-
- # handle simple doors
- single_doors = list(Single_Cave_Doors)
- bomb_shop_doors = list(Bomb_Shop_Single_Cave_Doors)
- blacksmith_doors = list(Blacksmith_Single_Cave_Doors)
- door_targets = list(Single_Cave_Targets)
-
- # place blacksmith, has limited options
- world.random.shuffle(blacksmith_doors)
- blacksmith_hut = blacksmith_doors.pop()
- connect_entrance(world, blacksmith_hut, 'Blacksmiths Hut', player)
- bomb_shop_doors.extend(blacksmith_doors)
-
- # place dam and pyramid fairy, have limited options
- world.random.shuffle(bomb_shop_doors)
- bomb_shop = bomb_shop_doors.pop()
- connect_entrance(world, bomb_shop, 'Big Bomb Shop', player)
- single_doors.extend(bomb_shop_doors)
-
- # tavern back door cannot be shuffled yet
- connect_doors(world, ['Tavern North'], ['Tavern'], player)
-
- # place remaining doors
- connect_doors(world, single_doors, door_targets, player)
else:
raise NotImplementedError(
- f'{world.shuffle[player]} Shuffling not supported yet. Player {world.get_player_name(player)}')
+ f'{world.entrance_shuffle[player]} Shuffling not supported yet. Player {world.get_player_name(player)}')
- if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']:
+ if world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']:
overworld_glitch_connections(world, player)
# mandatory hybrid major glitches connections
- if world.logic[player] in ['hybridglitches', 'nologic']:
+ if world.glitches_required[player] in ['hybrid_major_glitches', 'no_logic']:
underworld_glitch_connections(world, player)
# check for swamp palace fix
@@ -1106,17 +581,17 @@ def link_inverted_entrances(world, player):
connect_simple(world, exitname, regionname, player)
# if we do not shuffle, set default connections
- if world.shuffle[player] == 'vanilla':
+ if world.entrance_shuffle[player] == 'vanilla':
for exitname, regionname in inverted_default_connections:
connect_simple(world, exitname, regionname, player)
for exitname, regionname in inverted_default_dungeon_connections:
connect_simple(world, exitname, regionname, player)
- elif world.shuffle[player] == 'dungeonssimple':
+ elif world.entrance_shuffle[player] == 'dungeons_simple':
for exitname, regionname in inverted_default_connections:
connect_simple(world, exitname, regionname, player)
simple_shuffle_dungeons(world, player)
- elif world.shuffle[player] == 'dungeonsfull':
+ elif world.entrance_shuffle[player] == 'dungeons_full':
for exitname, regionname in inverted_default_connections:
connect_simple(world, exitname, regionname, player)
@@ -1171,9 +646,9 @@ def link_inverted_entrances(world, player):
connect_mandatory_exits(world, lw_entrances, dungeon_exits, lw_dungeon_entrances_must_exit, player)
connect_caves(world, lw_entrances, dw_entrances, dungeon_exits, player)
- elif world.shuffle[player] == 'dungeonscrossed':
+ elif world.entrance_shuffle[player] == 'dungeons_crossed':
inverted_crossed_shuffle_dungeons(world, player)
- elif world.shuffle[player] == 'simple':
+ elif world.entrance_shuffle[player] == 'simple':
simple_shuffle_dungeons(world, player)
old_man_entrances = list(Inverted_Old_Man_Entrances)
@@ -1270,7 +745,7 @@ def link_inverted_entrances(world, player):
# place remaining doors
connect_doors(world, single_doors, door_targets, player)
- elif world.shuffle[player] == 'restricted':
+ elif world.entrance_shuffle[player] == 'restricted':
simple_shuffle_dungeons(world, player)
lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Single_Cave_Doors)
@@ -1355,7 +830,7 @@ def link_inverted_entrances(world, player):
doors = lw_entrances + dw_entrances
# place remaining doors
connect_doors(world, doors, door_targets, player)
- elif world.shuffle[player] == 'full':
+ elif world.entrance_shuffle[player] == 'full':
skull_woods_shuffle(world, player)
lw_entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors)
@@ -1506,7 +981,7 @@ def link_inverted_entrances(world, player):
# place remaining doors
connect_doors(world, doors, door_targets, player)
- elif world.shuffle[player] == 'crossed':
+ elif world.entrance_shuffle[player] == 'crossed':
skull_woods_shuffle(world, player)
entrances = list(Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_LW_Single_Cave_Doors + Inverted_Old_Man_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_DW_Single_Cave_Doors)
@@ -1617,7 +1092,7 @@ def link_inverted_entrances(world, player):
# place remaining doors
connect_doors(world, entrances, door_targets, player)
- elif world.shuffle[player] == 'insanity':
+ elif world.entrance_shuffle[player] == 'insanity':
# beware ye who enter here
entrances = Inverted_LW_Entrances + Inverted_LW_Dungeon_Entrances + Inverted_DW_Entrances + Inverted_DW_Dungeon_Entrances + Inverted_Old_Man_Entrances + Old_Man_Entrances + ['Skull Woods Second Section Door (East)', 'Skull Woods Second Section Door (West)', 'Skull Woods First Section Door', 'Kakariko Well Cave', 'Bat Cave Cave', 'North Fairy Cave', 'Sanctuary', 'Lost Woods Hideout Stump', 'Lumberjack Tree Cave', 'Hyrule Castle Entrance (South)']
@@ -1776,10 +1251,10 @@ def connect_reachable_exit(entrance, caves, doors):
else:
raise NotImplementedError('Shuffling not supported yet')
- if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']:
+ if world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']:
overworld_glitch_connections(world, player)
# mandatory hybrid major glitches connections
- if world.logic[player] in ['hybridglitches', 'nologic']:
+ if world.glitches_required[player] in ['hybrid_major_glitches', 'no_logic']:
underworld_glitch_connections(world, player)
# patch swamp drain
@@ -1880,14 +1355,14 @@ def scramble_holes(world, player):
hole_targets.append(('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance'))
# do not shuffle sanctuary into pyramid hole unless shuffle is crossed
- if world.shuffle[player] == 'crossed':
+ if world.entrance_shuffle[player] == 'crossed':
hole_targets.append(('Sanctuary Exit', 'Sewer Drop'))
if world.shuffle_ganon:
world.random.shuffle(hole_targets)
exit, target = hole_targets.pop()
connect_two_way(world, 'Pyramid Entrance', exit, player)
connect_entrance(world, 'Pyramid Hole', target, player)
- if world.shuffle[player] != 'crossed':
+ if world.entrance_shuffle[player] != 'crossed':
hole_targets.append(('Sanctuary Exit', 'Sewer Drop'))
world.random.shuffle(hole_targets)
@@ -1922,14 +1397,14 @@ def scramble_inverted_holes(world, player):
hole_targets.append(('Hyrule Castle Secret Entrance Exit', 'Hyrule Castle Secret Entrance'))
# do not shuffle sanctuary into pyramid hole unless shuffle is crossed
- if world.shuffle[player] == 'crossed':
+ if world.entrance_shuffle[player] == 'crossed':
hole_targets.append(('Sanctuary Exit', 'Sewer Drop'))
if world.shuffle_ganon:
world.random.shuffle(hole_targets)
exit, target = hole_targets.pop()
connect_two_way(world, 'Inverted Pyramid Entrance', exit, player)
connect_entrance(world, 'Inverted Pyramid Hole', target, player)
- if world.shuffle[player] != 'crossed':
+ if world.entrance_shuffle[player] != 'crossed':
hole_targets.append(('Sanctuary Exit', 'Sewer Drop'))
world.random.shuffle(hole_targets)
@@ -1958,7 +1433,7 @@ def connect_mandatory_exits(world, entrances, caves, must_be_exits, player):
invalid_connections = Must_Exit_Invalid_Connections.copy()
invalid_cave_connections = defaultdict(set)
- if world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']:
+ if world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']:
from worlds.alttp import OverworldGlitchRules
for entrance in OverworldGlitchRules.get_non_mandatory_exits(world.mode[player] == 'inverted'):
invalid_connections[entrance] = set()
@@ -3038,6 +2513,7 @@ def plando_connect(world, player: int):
('Sanctuary Push Door', 'Sanctuary'),
('Sewer Drop', 'Sewers'),
('Sewers Back Door', 'Sewers (Dark)'),
+ ('Sewers Secret Room', 'Sewers Secret Room'),
('Agahnim 1', 'Agahnim 1'),
('Flute Spot 1', 'Death Mountain'),
('Death Mountain Entrance Rock', 'Death Mountain Entrance'),
@@ -3053,6 +2529,8 @@ def plando_connect(world, player: int):
('Spiral Cave Ledge Access', 'Spiral Cave Ledge'),
('Spiral Cave Ledge Drop', 'East Death Mountain (Bottom)'),
('Spiral Cave (top to bottom)', 'Spiral Cave (Bottom)'),
+ ('Hookshot Cave Bomb Wall (South)', 'Hookshot Cave (Upper)'),
+ ('Hookshot Cave Bomb Wall (North)', 'Hookshot Cave'),
('East Death Mountain (Top)', 'East Death Mountain (Top)'),
('Death Mountain (Top)', 'Death Mountain (Top)'),
('Death Mountain Drop', 'Death Mountain'),
@@ -3227,6 +2705,7 @@ def plando_connect(world, player: int):
('Sanctuary Push Door', 'Sanctuary'),
('Sewer Drop', 'Sewers'),
('Sewers Back Door', 'Sewers (Dark)'),
+ ('Sewers Secret Room', 'Sewers Secret Room'),
('Agahnim 1', 'Agahnim 1'),
('Death Mountain Entrance Rock', 'Death Mountain Entrance'),
('Death Mountain Entrance Drop', 'Light World'),
@@ -3241,6 +2720,8 @@ def plando_connect(world, player: int):
('Spiral Cave Ledge Access', 'Spiral Cave Ledge'),
('Spiral Cave Ledge Drop', 'East Death Mountain (Bottom)'),
('Spiral Cave (top to bottom)', 'Spiral Cave (Bottom)'),
+ ('Hookshot Cave Bomb Wall (South)', 'Hookshot Cave (Upper)'),
+ ('Hookshot Cave Bomb Wall (North)', 'Hookshot Cave'),
('East Death Mountain (Top)', 'East Death Mountain (Top)'),
('Death Mountain (Top)', 'Death Mountain (Top)'),
('Death Mountain Drop', 'Death Mountain'),
@@ -3572,7 +3053,7 @@ def plando_connect(world, player: int):
('Superbunny Cave Exit (Bottom)', 'Dark Death Mountain (East Bottom)'),
('Hookshot Cave Exit (South)', 'Dark Death Mountain (Top)'),
('Hookshot Cave Exit (North)', 'Death Mountain Floating Island (Dark World)'),
- ('Hookshot Cave Back Entrance', 'Hookshot Cave'),
+ ('Hookshot Cave Back Entrance', 'Hookshot Cave (Upper)'),
('Mimic Cave', 'Mimic Cave'),
('Pyramid Hole', 'Pyramid'),
@@ -3703,7 +3184,7 @@ def plando_connect(world, player: int):
('Superbunny Cave (Bottom)', 'Superbunny Cave (Bottom)'),
('Superbunny Cave Exit (Bottom)', 'Dark Death Mountain (East Bottom)'),
('Hookshot Cave Exit (North)', 'Death Mountain Floating Island (Dark World)'),
- ('Hookshot Cave Back Entrance', 'Hookshot Cave'),
+ ('Hookshot Cave Back Entrance', 'Hookshot Cave (Upper)'),
('Mimic Cave', 'Mimic Cave'),
('Inverted Pyramid Hole', 'Pyramid'),
('Inverted Links House', 'Inverted Links House'),
diff --git a/worlds/alttp/InvertedRegions.py b/worlds/alttp/InvertedRegions.py
index f89eebec3339..2e30fde8cc85 100644
--- a/worlds/alttp/InvertedRegions.py
+++ b/worlds/alttp/InvertedRegions.py
@@ -133,7 +133,7 @@ def create_inverted_regions(world, player):
create_cave_region(world, player, 'Kakariko Gamble Game', 'a game of chance'),
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, 'Capacity Upgrade', 'the queen of fairies', ['Capacity Upgrade Shop']),
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'],
@@ -176,8 +176,9 @@ def create_inverted_regions(world, player):
'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 - 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, 'Sewers', 'a drop\'s exit', None, ['Sanctuary Push Door', 'Sewers Back Door', 'Sewers Secret Room']),
+ create_dungeon_region(world, player, 'Sewers Secret Room', 'a drop\'s exit', ['Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle',
+ 'Sewers - Secret Room - Right']),
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', '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),
@@ -346,7 +347,9 @@ def create_inverted_regions(world, player):
create_cave_region(world, player, 'Hookshot Cave', 'a connector',
['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right',
'Hookshot Cave - Bottom Left'],
- ['Hookshot Cave Exit (South)', 'Hookshot Cave Exit (North)']),
+ ['Hookshot Cave Exit (South)', 'Hookshot Cave Bomb Wall (South)']),
+ create_cave_region(world, player, 'Hookshot Cave (Upper)', 'a connector', None, ['Hookshot Cave Exit (North)',
+ 'Hookshot Cave Bomb Wall (North)']),
create_dw_region(world, player, 'Death Mountain Floating Island (Dark World)', None,
['Floating Island Drop', 'Hookshot Cave Back Entrance']),
create_cave_region(world, player, 'Mimic Cave', 'Mimic Cave', ['Mimic Cave']),
@@ -380,8 +383,8 @@ def create_inverted_regions(world, player):
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 (Entrance)', 'Ice Palace', ['Ice Palace - Jelly Key Drop', 'Ice Palace - Compass Chest'], ['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 (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)']),
diff --git a/worlds/alttp/ItemPool.py b/worlds/alttp/ItemPool.py
index 1c3f3e44f72c..bb5bbaa61a02 100644
--- a/worlds/alttp/ItemPool.py
+++ b/worlds/alttp/ItemPool.py
@@ -5,12 +5,12 @@
from Fill import FillError
from .SubClasses import ALttPLocation, LTTPRegion, LTTPRegionType
-from .Shops import TakeAny, total_shop_slots, set_up_shops, shuffle_shops, create_dynamic_shop_locations
+from .Shops import TakeAny, total_shop_slots, set_up_shops, shop_table_by_location, ShopType
from .Bosses import place_bosses
from .Dungeons import get_dungeon_item_pool_player
from .EntranceShuffle import connect_entrance
-from .Items import ItemFactory, GetBeemizerItem
-from .Options import smallkey_shuffle, compass_shuffle, bigkey_shuffle, map_shuffle, LTTPBosses
+from .Items import ItemFactory, GetBeemizerItem, trap_replaceable, item_name_groups
+from .Options import small_key_shuffle, compass_shuffle, big_key_shuffle, map_shuffle, TriforcePiecesMode
from .StateHelpers import has_triforce_pieces, has_melee_weapon
from .Regions import key_drop_data
@@ -189,104 +189,62 @@
),
}
-ice_rod_hunt_difficulties = dict()
-for diff in {'easy', 'normal', 'hard', 'expert'}:
- ice_rod_hunt_difficulties[diff] = Difficulty(
- baseitems=['Nothing'] * 41,
- bottles=['Nothing'] * 4,
- bottle_count=difficulties[diff].bottle_count,
- same_bottle=difficulties[diff].same_bottle,
- progressiveshield=['Nothing'] * 3,
- basicshield=['Nothing'] * 3,
- progressivearmor=['Nothing'] * 2,
- basicarmor=['Nothing'] * 2,
- swordless=['Nothing'] * 4,
- progressivemagic=['Nothing'] * 2,
- basicmagic=['Nothing'] * 2,
- progressivesword=['Nothing'] * 4,
- basicsword=['Nothing'] * 4,
- progressivebow=['Nothing'] * 2,
- basicbow=['Nothing'] * 2,
- timedohko=difficulties[diff].timedohko,
- timedother=difficulties[diff].timedother,
- progressiveglove=['Nothing'] * 2,
- basicglove=['Nothing'] * 2,
- alwaysitems=['Ice Rod'] + ['Nothing'] * 19,
- legacyinsanity=['Nothing'] * 2,
- 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,
- progressive_armor_limit=difficulties[diff].progressive_armor_limit,
- progressive_bow_limit=difficulties[diff].progressive_bow_limit,
- progressive_bottle_limit=difficulties[diff].progressive_bottle_limit,
- boss_heart_container_limit=difficulties[diff].boss_heart_container_limit,
- heart_piece_limit=difficulties[diff].heart_piece_limit,
- )
+
+items_reduction_table = (
+ ("Piece of Heart", "Boss Heart Container", 4, 1),
+ # the order of the upgrades is important
+ ("Arrow Upgrade (+5)", "Arrow Upgrade (+10)", 8, 4),
+ ("Arrow Upgrade (+5)", "Arrow Upgrade (+10)", 7, 4),
+ ("Arrow Upgrade (+5)", "Arrow Upgrade (+10)", 6, 3),
+ ("Arrow Upgrade (+10)", "Arrow Upgrade (70)", 4, 1),
+ ("Bomb Upgrade (+5)", "Bomb Upgrade (+10)", 8, 4),
+ ("Bomb Upgrade (+5)", "Bomb Upgrade (+10)", 7, 4),
+ ("Bomb Upgrade (+5)", "Bomb Upgrade (+10)", 6, 3),
+ ("Bomb Upgrade (+10)", "Bomb Upgrade (50)", 5, 1),
+ ("Bomb Upgrade (+10)", "Bomb Upgrade (50)", 4, 1),
+ ("Progressive Sword", 4),
+ ("Fighter Sword", 1),
+ ("Master Sword", 1),
+ ("Tempered Sword", 1),
+ ("Golden Sword", 1),
+ ("Progressive Shield", 3),
+ ("Blue Shield", 1),
+ ("Red Shield", 1),
+ ("Mirror Shield", 1),
+ ("Progressive Mail", 2),
+ ("Blue Mail", 1),
+ ("Red Mail", 1),
+ ("Progressive Bow", 2),
+ ("Bow", 1),
+ ("Silver Bow", 1),
+ ("Lamp", 1),
+ ("Bottles",)
+)
def generate_itempool(world):
player = world.player
multiworld = world.multiworld
- if multiworld.difficulty[player] not in difficulties:
- raise NotImplementedError(f"Diffulty {multiworld.difficulty[player]}")
- if multiworld.goal[player] not in {'ganon', 'pedestal', 'bosses', 'triforcehunt', 'localtriforcehunt', 'icerodhunt',
- 'ganontriforcehunt', 'localganontriforcehunt', 'crystals', 'ganonpedestal'}:
+ if multiworld.item_pool[player].current_key not in difficulties:
+ raise NotImplementedError(f"Diffulty {multiworld.item_pool[player]}")
+ if multiworld.goal[player] not in ('ganon', 'pedestal', 'bosses', 'triforce_hunt', 'local_triforce_hunt',
+ 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'crystals',
+ 'ganon_pedestal'):
raise NotImplementedError(f"Goal {multiworld.goal[player]} for player {player}")
- if multiworld.mode[player] not in {'open', 'standard', 'inverted'}:
+ if multiworld.mode[player] not in ('open', 'standard', 'inverted'):
raise NotImplementedError(f"Mode {multiworld.mode[player]} for player {player}")
- if multiworld.timer[player] not in {False, 'display', 'timed', 'timed-ohko', 'ohko', 'timed-countdown'}:
+ if multiworld.timer[player] not in {False, 'display', 'timed', 'timed_ohko', 'ohko', 'timed_countdown'}:
raise NotImplementedError(f"Timer {multiworld.mode[player]} for player {player}")
- if multiworld.timer[player] in ['ohko', 'timed-ohko']:
+ if multiworld.timer[player] in ['ohko', 'timed_ohko']:
multiworld.can_take_damage[player] = False
- if multiworld.goal[player] in ['pedestal', 'triforcehunt', 'localtriforcehunt', 'icerodhunt']:
+ if multiworld.goal[player] in ['pedestal', 'triforce_hunt', 'local_triforce_hunt']:
multiworld.push_item(multiworld.get_location('Ganon', player), ItemFactory('Nothing', player), False)
else:
multiworld.push_item(multiworld.get_location('Ganon', player), ItemFactory('Triforce', player), False)
- if multiworld.goal[player] == 'icerodhunt':
- multiworld.progression_balancing[player].value = 0
- loc = multiworld.get_location('Turtle Rock - Boss', player)
- multiworld.push_item(loc, ItemFactory('Triforce Piece', player), False)
- multiworld.treasure_hunt_count[player] = 1
- if multiworld.boss_shuffle[player] != 'none':
- if isinstance(multiworld.boss_shuffle[player].value, str) and 'turtle rock-' not in multiworld.boss_shuffle[player].value:
- multiworld.boss_shuffle[player] = LTTPBosses.from_text(f'Turtle Rock-Trinexx;{multiworld.boss_shuffle[player].current_key}')
- elif isinstance(multiworld.boss_shuffle[player].value, int):
- multiworld.boss_shuffle[player] = LTTPBosses.from_text(f'Turtle Rock-Trinexx;{multiworld.boss_shuffle[player].current_key}')
- else:
- logging.warning(f'Cannot guarantee that Trinexx is the boss of Turtle Rock for player {player}')
- loc.event = True
- loc.locked = True
- itemdiff = difficulties[multiworld.difficulty[player]]
- itempool = []
- itempool.extend(itemdiff.alwaysitems)
- itempool.remove('Ice Rod')
-
- itempool.extend(['Single Arrow', 'Sanctuary Heart Container'])
- itempool.extend(['Boss Heart Container'] * itemdiff.boss_heart_container_limit)
- itempool.extend(['Piece of Heart'] * itemdiff.heart_piece_limit)
- itempool.extend(itemdiff.bottles)
- itempool.extend(itemdiff.basicbow)
- itempool.extend(itemdiff.basicarmor)
- if not multiworld.swordless[player]:
- itempool.extend(itemdiff.basicsword)
- itempool.extend(itemdiff.basicmagic)
- itempool.extend(itemdiff.basicglove)
- itempool.extend(itemdiff.basicshield)
- itempool.extend(itemdiff.legacyinsanity)
- itempool.extend(['Rupees (300)'] * 34)
- itempool.extend(['Bombs (10)'] * 5)
- itempool.extend(['Arrows (10)'] * 7)
- if multiworld.smallkey_shuffle[player] == smallkey_shuffle.option_universal:
- itempool.extend(itemdiff.universal_keys)
-
- for item in itempool:
- multiworld.push_precollected(ItemFactory(item, player))
-
- if multiworld.goal[player] in ['triforcehunt', 'localtriforcehunt', 'icerodhunt']:
+ if multiworld.goal[player] in ['triforce_hunt', 'local_triforce_hunt']:
region = multiworld.get_region('Light World', player)
loc = ALttPLocation(player, "Murahdahla", parent=region)
@@ -308,7 +266,8 @@ def generate_itempool(world):
('Missing Smith', 'Return Smith'),
('Floodgate', 'Open Floodgate'),
('Agahnim 1', 'Beat Agahnim 1'),
- ('Flute Activation Spot', 'Activated Flute')
+ ('Flute Activation Spot', 'Activated Flute'),
+ ('Capacity Upgrade Shop', 'Capacity Upgrade Shop')
]
for location_name, event_name in event_pairs:
location = multiworld.get_location(location_name, player)
@@ -340,17 +299,31 @@ def generate_itempool(world):
if not found_sword:
found_sword = True
possible_weapons.append(item)
- if item in ['Progressive Bow', 'Bow'] and not found_bow:
+ elif item in ['Progressive Bow', 'Bow'] and not found_bow:
found_bow = True
possible_weapons.append(item)
- if item in ['Hammer', 'Bombs (10)', 'Fire Rod', 'Cane of Somaria', 'Cane of Byrna']:
+ elif item in ['Hammer', 'Fire Rod', 'Cane of Somaria', 'Cane of Byrna']:
if item not in possible_weapons:
possible_weapons.append(item)
+ elif (item == 'Bombs (10)' and (not multiworld.bombless_start[player]) and item not in
+ possible_weapons):
+ possible_weapons.append(item)
+ elif (item in ['Bomb Upgrade (+10)', 'Bomb Upgrade (50)'] and multiworld.bombless_start[player] and item
+ not in possible_weapons):
+ possible_weapons.append(item)
+
starting_weapon = multiworld.random.choice(possible_weapons)
placed_items["Link's Uncle"] = starting_weapon
pool.remove(starting_weapon)
- if placed_items["Link's Uncle"] in ['Bow', 'Progressive Bow', 'Bombs (10)', 'Cane of Somaria', 'Cane of Byrna'] and multiworld.enemy_health[player] not in ['default', 'easy']:
- multiworld.escape_assist[player].append('bombs')
+ if (placed_items["Link's Uncle"] in ['Bow', 'Progressive Bow', 'Bombs (10)', 'Bomb Upgrade (+10)',
+ 'Bomb Upgrade (50)', 'Cane of Somaria', 'Cane of Byrna'] and multiworld.enemy_health[player] not in ['default', 'easy']):
+ if multiworld.bombless_start[player] and "Bomb Upgrade" not in placed_items["Link's Uncle"]:
+ if 'Bow' in placed_items["Link's Uncle"]:
+ multiworld.escape_assist[player].append('arrows')
+ elif 'Cane' in placed_items["Link's Uncle"]:
+ multiworld.escape_assist[player].append('magic')
+ else:
+ multiworld.escape_assist[player].append('bombs')
for (location, item) in placed_items.items():
multiworld.get_location(location, player).place_locked_item(ItemFactory(item, player))
@@ -377,7 +350,7 @@ def generate_itempool(world):
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 not multiworld.key_drop_shuffle[player]:
if drop_item in dungeon_items:
dungeon_items.remove(drop_item)
else:
@@ -391,88 +364,151 @@ def generate_itempool(world):
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:
+ elif "Small" in key_data[3] and multiworld.small_key_shuffle[player] == small_key_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))
+ multiworld.itempool.append(ItemFactory(GetBeemizerItem(multiworld, 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:
- multiworld.itempool.append(ItemFactory(GetBeemizerItem(multiworld, player, 'Nothing'), player))
+
+ for x in range(len(dungeon_items)-1, -1, -1):
+ item = dungeon_items[x]
+ if ((multiworld.small_key_shuffle[player] == small_key_shuffle.option_start_with and item.type == 'SmallKey')
+ or (multiworld.big_key_shuffle[player] == big_key_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.pop(x)
multiworld.push_precollected(item)
+ multiworld.itempool.append(ItemFactory(dungeon_item_replacements.pop(), player))
+ multiworld.itempool.extend([item for item in dungeon_items])
+
+ set_up_shops(multiworld, player)
+
+ if multiworld.retro_bow[player]:
+ shop_items = 0
+ shop_locations = [location for shop_locations in (shop.region.locations for shop in multiworld.shops if
+ shop.type == ShopType.Shop and shop.region.player == player) for location in shop_locations if
+ location.shop_slot is not None]
+ for location in shop_locations:
+ if location.shop.inventory[location.shop_slot]["item"] == "Single Arrow":
+ location.place_locked_item(ItemFactory("Single Arrow", player))
+ else:
+ shop_items += 1
else:
- for x in range(len(dungeon_items)-1, -1, -1):
- item = dungeon_items[x]
- if ((multiworld.smallkey_shuffle[player] == smallkey_shuffle.option_start_with and item.type == 'SmallKey')
- 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.pop(x)
- multiworld.push_precollected(item)
- multiworld.itempool.append(ItemFactory(dungeon_item_replacements.pop(), player))
- multiworld.itempool.extend([item for item in dungeon_items])
- # logic has some branches where having 4 hearts is one possible requirement (of several alternatives)
- # rather than making all hearts/heart pieces progression items (which slows down generation considerably)
- # We mark one random heart container as an advancement item (or 4 heart pieces in expert mode)
- if multiworld.goal[player] != 'icerodhunt' and multiworld.difficulty[player] in ['easy', 'normal', 'hard'] and not (multiworld.custom and multiworld.customitemarray[30] == 0):
- next(item for item in items if item.name == 'Boss Heart Container').classification = ItemClassification.progression
- elif multiworld.goal[player] != 'icerodhunt' and multiworld.difficulty[player] in ['expert'] and not (multiworld.custom and multiworld.customitemarray[29] < 4):
- adv_heart_pieces = (item for item in items if item.name == 'Piece of Heart')
- for i in range(4):
- next(adv_heart_pieces).classification = ItemClassification.progression
-
-
- progressionitems = []
- nonprogressionitems = []
- for item in items:
- if item.advancement or item.type:
- progressionitems.append(item)
+ shop_items = min(multiworld.shop_item_slots[player], 30 if multiworld.include_witch_hut[player] else 27)
+
+ if multiworld.shuffle_capacity_upgrades[player]:
+ shop_items += 2
+ chance_100 = int(multiworld.retro_bow[player]) * 0.25 + int(
+ multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal) * 0.5
+ for _ in range(shop_items):
+ if multiworld.random.random() < chance_100:
+ items.append(ItemFactory(GetBeemizerItem(multiworld, player, "Rupees (100)"), player))
else:
- nonprogressionitems.append(GetBeemizerItem(multiworld, item.player, item))
- multiworld.random.shuffle(nonprogressionitems)
-
- if additional_triforce_pieces:
- if additional_triforce_pieces > len(nonprogressionitems):
- raise FillError(f"Not enough non-progression items to replace with Triforce pieces found for player "
- f"{multiworld.get_player_name(player)}.")
- progressionitems += [ItemFactory("Triforce Piece", player) for _ in range(additional_triforce_pieces)]
- nonprogressionitems.sort(key=lambda item: int("Heart" in item.name)) # try to keep hearts in the pool
- nonprogressionitems = nonprogressionitems[additional_triforce_pieces:]
- multiworld.random.shuffle(nonprogressionitems)
-
- # shuffle medallions
- if multiworld.required_medallions[player][0] == "random":
- mm_medallion = multiworld.random.choice(['Ether', 'Quake', 'Bombos'])
- else:
- mm_medallion = multiworld.required_medallions[player][0]
- if multiworld.required_medallions[player][1] == "random":
- tr_medallion = multiworld.random.choice(['Ether', 'Quake', 'Bombos'])
+ items.append(ItemFactory(GetBeemizerItem(multiworld, player, "Rupees (50)"), player))
+
+ multiworld.random.shuffle(items)
+ pool_count = len(items)
+ new_items = ["Triforce Piece" for _ in range(additional_triforce_pieces)]
+ if multiworld.shuffle_capacity_upgrades[player] or multiworld.bombless_start[player]:
+ progressive = multiworld.progressive[player]
+ progressive = multiworld.random.choice([True, False]) if progressive == 'grouped_random' else progressive == 'on'
+ if multiworld.shuffle_capacity_upgrades[player] == "on_combined":
+ new_items.append("Bomb Upgrade (50)")
+ elif multiworld.shuffle_capacity_upgrades[player] == "on":
+ new_items += ["Bomb Upgrade (+5)"] * 6
+ new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)")
+ if multiworld.shuffle_capacity_upgrades[player] != "on_combined" and multiworld.bombless_start[player]:
+ new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)")
+
+ if multiworld.shuffle_capacity_upgrades[player] and not multiworld.retro_bow[player]:
+ if multiworld.shuffle_capacity_upgrades[player] == "on_combined":
+ new_items += ["Arrow Upgrade (70)"]
+ else:
+ new_items += ["Arrow Upgrade (+5)"] * 6
+ new_items.append("Arrow Upgrade (+5)" if progressive else "Arrow Upgrade (+10)")
+
+ items += [ItemFactory(item, player) for item in new_items]
+ removed_filler = []
+
+ multiworld.random.shuffle(items) # Decide what gets tossed randomly.
+
+ while len(items) > pool_count:
+ for i, item in enumerate(items):
+ if item.classification in (ItemClassification.filler, ItemClassification.trap):
+ removed_filler.append(items.pop(i))
+ break
+ else:
+ # no more junk to remove, condense progressive items
+ def condense_items(items, small_item, big_item, rem, add):
+ small_item = ItemFactory(small_item, player)
+ # while (len(items) >= pool_count + rem - 1 # minus 1 to account for the replacement item
+ # and items.count(small_item) >= rem):
+ if items.count(small_item) >= rem:
+ for _ in range(rem):
+ items.remove(small_item)
+ removed_filler.append(ItemFactory(small_item.name, player))
+ items += [ItemFactory(big_item, player) for _ in range(add)]
+ return True
+ return False
+
+ def cut_item(items, item_to_cut, minimum_items):
+ item_to_cut = ItemFactory(item_to_cut, player)
+ if items.count(item_to_cut) > minimum_items:
+ items.remove(item_to_cut)
+ removed_filler.append(ItemFactory(item_to_cut.name, player))
+ return True
+ return False
+
+ while len(items) > pool_count:
+ items_were_cut = False
+ for reduce_item in items_reduction_table:
+ if len(items) <= pool_count:
+ break
+ if len(reduce_item) == 2:
+ items_were_cut = items_were_cut or cut_item(items, *reduce_item)
+ elif len(reduce_item) == 4:
+ items_were_cut = items_were_cut or condense_items(items, *reduce_item)
+ elif len(reduce_item) == 1: # Bottles
+ bottles = [item for item in items if item.name in item_name_groups["Bottles"]]
+ if len(bottles) > 4:
+ bottle = multiworld.random.choice(bottles)
+ items.remove(bottle)
+ removed_filler.append(bottle)
+ items_were_cut = True
+ assert items_were_cut, f"Failed to limit item pool size for player {player}"
+ if len(items) < pool_count:
+ items += removed_filler[len(items) - pool_count:]
+
+ if multiworld.randomize_cost_types[player]:
+ # Heart and Arrow costs require all Heart Container/Pieces and Arrow Upgrades to be advancement items for logic
+ for item in items:
+ if (item.name in ("Boss Heart Container", "Sanctuary Heart Container", "Piece of Heart")
+ or "Arrow Upgrade" in item.name):
+ item.classification = ItemClassification.progression
else:
- tr_medallion = multiworld.required_medallions[player][1]
- multiworld.required_medallions[player] = (mm_medallion, tr_medallion)
+ # Otherwise, logic has some branches where having 4 hearts is one possible requirement (of several alternatives)
+ # rather than making all hearts/heart pieces progression items (which slows down generation considerably)
+ # We mark one random heart container as an advancement item (or 4 heart pieces in expert mode)
+ if multiworld.item_pool[player] in ['easy', 'normal', 'hard'] and not (multiworld.custom and multiworld.customitemarray[30] == 0):
+ next(item for item in items if item.name == 'Boss Heart Container').classification = ItemClassification.progression
+ elif multiworld.item_pool[player] in ['expert'] and not (multiworld.custom and multiworld.customitemarray[29] < 4):
+ adv_heart_pieces = (item for item in items if item.name == 'Piece of Heart')
+ for i in range(4):
+ next(adv_heart_pieces).classification = ItemClassification.progression
+
+ multiworld.required_medallions[player] = (multiworld.misery_mire_medallion[player].current_key.title(),
+ multiworld.turtle_rock_medallion[player].current_key.title())
place_bosses(world)
- set_up_shops(multiworld, player)
-
- if multiworld.shop_shuffle[player]:
- shuffle_shops(multiworld, nonprogressionitems, player)
- multiworld.itempool += progressionitems + nonprogressionitems
+ multiworld.itempool += items
if multiworld.retro_caves[player]:
set_up_take_anys(multiworld, player) # depends on world.itempool to be set
- # set_up_take_anys needs to run first
- create_dynamic_shop_locations(multiworld, player)
take_any_locations = {
@@ -516,9 +552,14 @@ def set_up_take_anys(world, player):
sword = world.random.choice(swords)
world.itempool.remove(sword)
world.itempool.append(ItemFactory('Rupees (20)', player))
- old_man_take_any.shop.add_inventory(0, sword.name, 0, 0, create_location=True)
+ old_man_take_any.shop.add_inventory(0, sword.name, 0, 0)
+ loc_name = "Old Man Sword Cave"
+ location = ALttPLocation(player, loc_name, shop_table_by_location[loc_name], parent=old_man_take_any)
+ location.shop_slot = 0
+ old_man_take_any.locations.append(location)
+ location.place_locked_item(sword)
else:
- old_man_take_any.shop.add_inventory(0, 'Rupees (300)', 0, 0, create_location=True)
+ old_man_take_any.shop.add_inventory(0, 'Rupees (300)', 0, 0)
for num in range(4):
take_any = LTTPRegion("Take-Any #{}".format(num+1), LTTPRegionType.Cave, 'a cave of choice', player, world)
@@ -532,18 +573,22 @@ def set_up_take_anys(world, player):
take_any.shop = TakeAny(take_any, room_id, 0xE3, True, True, total_shop_slots + num + 1)
world.shops.append(take_any.shop)
take_any.shop.add_inventory(0, 'Blue Potion', 0, 0)
- take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 0, create_location=True)
+ take_any.shop.add_inventory(1, 'Boss Heart Container', 0, 0)
+ location = ALttPLocation(player, take_any.name, shop_table_by_location[take_any.name], parent=take_any)
+ location.shop_slot = 1
+ take_any.locations.append(location)
+ location.place_locked_item(ItemFactory("Boss Heart Container", player))
def get_pool_core(world, player: int):
- shuffle = world.shuffle[player]
- difficulty = world.difficulty[player]
- timer = world.timer[player]
- goal = world.goal[player]
- mode = world.mode[player]
+ shuffle = world.entrance_shuffle[player].current_key
+ difficulty = world.item_pool[player].current_key
+ timer = world.timer[player].current_key
+ goal = world.goal[player].current_key
+ mode = world.mode[player].current_key
swordless = world.swordless[player]
retro_bow = world.retro_bow[player]
- logic = world.logic[player]
+ logic = world.glitches_required[player]
pool = []
placed_items = {}
@@ -552,7 +597,7 @@ def get_pool_core(world, player: int):
treasure_hunt_count = None
treasure_hunt_icon = None
- diff = ice_rod_hunt_difficulties[difficulty] if goal == 'icerodhunt' else difficulties[difficulty]
+ diff = difficulties[difficulty]
pool.extend(diff.alwaysitems)
def place_item(loc, item):
@@ -560,7 +605,7 @@ def place_item(loc, item):
placed_items[loc] = item
# provide boots to major glitch dependent seeds
- if logic in {'owglitches', 'hybridglitches', 'nologic'} and world.glitch_boots[player] and goal != 'icerodhunt':
+ if logic in {'overworld_glitches', 'hybrid_major_glitches', 'no_logic'} and world.glitch_boots[player]:
precollected_items.append('Pegasus Boots')
pool.remove('Pegasus Boots')
pool.append('Rupees (20)')
@@ -611,7 +656,7 @@ def place_item(loc, item):
if want_progressives(world.random):
pool.extend(diff.progressivebow)
world.worlds[player].has_progressive_bows = True
- elif (swordless or logic == 'noglitches') and goal != 'icerodhunt':
+ elif (swordless or logic == 'no_glitches'):
swordless_bows = ['Bow', 'Silver Bow']
if difficulty == "easy":
swordless_bows *= 2
@@ -627,21 +672,32 @@ def place_item(loc, item):
extraitems = total_items_to_place - len(pool) - len(placed_items)
- if timer in ['timed', 'timed-countdown']:
+ if timer in ['timed', 'timed_countdown']:
pool.extend(diff.timedother)
extraitems -= len(diff.timedother)
clock_mode = 'stopwatch' if timer == 'timed' else 'countdown'
- elif timer == 'timed-ohko':
+ elif timer == 'timed_ohko':
pool.extend(diff.timedohko)
extraitems -= len(diff.timedohko)
clock_mode = 'countdown-ohko'
additional_pieces_to_place = 0
- if 'triforcehunt' in goal:
- pieces_in_core = min(extraitems, world.triforce_pieces_available[player])
- additional_pieces_to_place = world.triforce_pieces_available[player] - pieces_in_core
+ if 'triforce_hunt' in goal:
+
+ if world.triforce_pieces_mode[player].value == TriforcePiecesMode.option_extra:
+ triforce_pieces = world.triforce_pieces_available[player].value + world.triforce_pieces_extra[player].value
+ elif world.triforce_pieces_mode[player].value == TriforcePiecesMode.option_percentage:
+ percentage = float(max(100, world.triforce_pieces_percentage[player].value)) / 100
+ triforce_pieces = int(round(world.triforce_pieces_required[player].value * percentage, 0))
+ else: # available
+ triforce_pieces = world.triforce_pieces_available[player].value
+
+ triforce_pieces = max(triforce_pieces, world.triforce_pieces_required[player].value)
+
+ pieces_in_core = min(extraitems, triforce_pieces)
+ additional_pieces_to_place = triforce_pieces - pieces_in_core
pool.extend(["Triforce Piece"] * pieces_in_core)
extraitems -= pieces_in_core
- treasure_hunt_count = world.triforce_pieces_required[player]
+ treasure_hunt_count = world.triforce_pieces_required[player].value
treasure_hunt_icon = 'Triforce Piece'
for extra in diff.extras:
@@ -659,12 +715,12 @@ def place_item(loc, item):
pool.remove("Rupees (20)")
if retro_bow:
- replace = {'Single Arrow', 'Arrows (10)', 'Arrow Upgrade (+5)', 'Arrow Upgrade (+10)'}
+ replace = {'Single Arrow', 'Arrows (10)', 'Arrow Upgrade (+5)', 'Arrow Upgrade (+10)', 'Arrow Upgrade (50)'}
pool = ['Rupees (5)' if item in replace else item for item in pool]
- if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal:
+ if world.small_key_shuffle[player] == small_key_shuffle.option_universal:
pool.extend(diff.universal_keys)
if mode == 'standard':
- if world.key_drop_shuffle[player] and world.goal[player] != 'icerodhunt':
+ if world.key_drop_shuffle[player]:
key_locations = ['Secret Passage', 'Hyrule Castle - Map Guard Key Drop']
key_location = world.random.choice(key_locations)
key_locations.remove(key_location)
@@ -688,8 +744,8 @@ def place_item(loc, item):
def make_custom_item_pool(world, player):
- shuffle = world.shuffle[player]
- difficulty = world.difficulty[player]
+ shuffle = world.entrance_shuffle[player]
+ difficulty = world.item_pool[player]
timer = world.timer[player]
goal = world.goal[player]
mode = world.mode[player]
@@ -798,9 +854,9 @@ def place_item(loc, item):
treasure_hunt_count = world.triforce_pieces_required[player]
treasure_hunt_icon = 'Triforce Piece'
- if timer in ['display', 'timed', 'timed-countdown']:
- clock_mode = 'countdown' if timer == 'timed-countdown' else 'stopwatch'
- elif timer == 'timed-ohko':
+ if timer in ['display', 'timed', 'timed_countdown']:
+ clock_mode = 'countdown' if timer == 'timed_countdown' else 'stopwatch'
+ elif timer == 'timed_ohko':
clock_mode = 'countdown-ohko'
elif timer == 'ohko':
clock_mode = 'ohko'
@@ -810,7 +866,7 @@ def place_item(loc, item):
itemtotal = itemtotal + 1
if mode == 'standard':
- if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal:
+ if world.small_key_shuffle[player] == small_key_shuffle.option_universal:
key_location = world.random.choice(
['Secret Passage', 'Hyrule Castle - Boomerang Chest', 'Hyrule Castle - Map Chest',
'Hyrule Castle - Zelda\'s Chest', 'Sewers - Dark Cross'])
@@ -833,7 +889,7 @@ def place_item(loc, item):
pool.extend(['Magic Mirror'] * customitemarray[22])
pool.extend(['Moon Pearl'] * customitemarray[28])
- if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal:
+ if world.small_key_shuffle[player] == small_key_shuffle.option_universal:
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)
diff --git a/worlds/alttp/Items.py b/worlds/alttp/Items.py
index 18f96b2ddb81..8e513552ad10 100644
--- a/worlds/alttp/Items.py
+++ b/worlds/alttp/Items.py
@@ -112,13 +112,15 @@ def as_init_dict(self) -> typing.Dict[str, typing.Any]:
'Crystal 7': ItemData(IC.progression, 'Crystal', (0x08, 0x34, 0x64, 0x40, 0x7C, 0x06), None, None, None, None, None, None, "a blue crystal"),
'Single Arrow': ItemData(IC.filler, None, 0x43, 'a lonely arrow\nsits here.', 'and the arrow', 'stick-collecting kid', 'sewing needle for sale', 'fungus for arrow', 'archer boy sews again', 'an arrow'),
'Arrows (10)': ItemData(IC.filler, None, 0x44, 'This will give\nyou ten shots\nwith your bow!', 'and the arrow pack','stick-collecting kid', 'sewing kit for sale', 'fungus for arrows', 'archer boy sews again','ten arrows'),
- 'Arrow Upgrade (+10)': ItemData(IC.filler, None, 0x54, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
- 'Arrow Upgrade (+5)': ItemData(IC.filler, None, 0x53, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
+ 'Arrow Upgrade (+10)': ItemData(IC.useful, None, 0x54, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
+ 'Arrow Upgrade (+5)': ItemData(IC.useful, None, 0x53, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
+ 'Arrow Upgrade (70)': ItemData(IC.useful, None, 0x4D, 'increase arrow\nstorage, low\nlow price', 'and the quiver', 'quiver-enlarging kid', 'arrow boost for sale', 'witch and more skewers', 'upgrade boy sews more again', 'arrow capacity'),
'Single Bomb': ItemData(IC.filler, None, 0x27, 'I make things\ngo BOOM! But\njust once.', 'and the explosion', 'the bomb-holding kid', 'firecracker for sale', 'blend fungus into bomb', '\'splosion boy explodes again', 'a bomb'),
'Bombs (3)': ItemData(IC.filler, None, 0x28, 'I make things\ngo triple\nBOOM!!!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'three bombs'),
'Bombs (10)': ItemData(IC.filler, None, 0x31, 'I make things\ngo BOOM! Ten\ntimes!', 'and the explosions', 'the bomb-holding kid', 'firecrackers for sale', 'blend fungus into bombs', '\'splosion boy explodes again', 'ten bombs'),
- 'Bomb Upgrade (+10)': ItemData(IC.filler, None, 0x52, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'),
- 'Bomb Upgrade (+5)': ItemData(IC.filler, None, 0x51, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'),
+ 'Bomb Upgrade (+10)': ItemData(IC.progression, None, 0x52, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'),
+ 'Bomb Upgrade (+5)': ItemData(IC.progression, None, 0x51, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'),
+ 'Bomb Upgrade (50)': ItemData(IC.progression, None, 0x4C, 'increase bomb\nstorage, low\nlow price', 'and the bomb bag', 'boom-enlarging kid', 'bomb boost for sale', 'the shroom goes boom', 'upgrade boy explodes more again', 'bomb capacity'),
'Blue Mail': ItemData(IC.useful, None, 0x22, 'Now you\'re a\nblue elf!', 'and the banana hat', 'the protected kid', 'banana hat for sale', 'the clothing store', 'tailor boy banana hatted again', 'the Blue Mail'),
'Red Mail': ItemData(IC.useful, None, 0x23, 'Now you\'re a\nred elf!', 'and the eggplant hat', 'well-protected kid', 'purple hat for sale', 'the nice clothing store', 'tailor boy fears nothing again', 'the Red Mail'),
'Progressive Mail': ItemData(IC.useful, None, 0x60, 'time for a\nchange of\nclothes?', 'and the unknown hat', 'the protected kid', 'new hat for sale', 'the clothing store', 'tailor boy has threads again', 'some armor'),
@@ -222,6 +224,7 @@ def as_init_dict(self) -> typing.Dict[str, typing.Any]:
'Return Smith': ItemData(IC.progression, 'Event', None, None, None, None, None, None, None, None),
'Pick Up Purple Chest': ItemData(IC.progression, 'Event', None, None, None, None, None, None, None, None),
'Open Floodgate': ItemData(IC.progression, 'Event', None, None, None, None, None, None, None, None),
+ 'Capacity Upgrade Shop': ItemData(IC.progression, 'Event', None, None, None, None, None, None, None, None),
}
item_init_table = {name: data.as_init_dict() for name, data in item_table.items()}
@@ -287,5 +290,5 @@ def as_init_dict(self) -> typing.Dict[str, typing.Any]:
item_table[name].classification in {IC.progression, IC.progression_skip_balancing}}
item_name_groups['Progression Items'] = progression_items
item_name_groups['Non Progression Items'] = everything - progression_items
-
+item_name_groups['Upgrades'] = {name for name in everything if 'Upgrade' in name}
trap_replaceable = item_name_groups['Rupees'] | {'Arrows (10)', 'Single Bomb', 'Bombs (3)', 'Bombs (10)', 'Nothing'}
diff --git a/worlds/alttp/Options.py b/worlds/alttp/Options.py
index a89a9adb83a7..8cc5d32608d9 100644
--- a/worlds/alttp/Options.py
+++ b/worlds/alttp/Options.py
@@ -1,10 +1,18 @@
import typing
from BaseClasses import MultiWorld
-from Options import Choice, Range, Option, Toggle, DefaultOnToggle, DeathLink, StartInventoryPool, PlandoBosses
-
-
-class Logic(Choice):
+from Options import Choice, Range, Option, Toggle, DefaultOnToggle, DeathLink, StartInventoryPool, PlandoBosses,\
+ FreeText
+
+
+class GlitchesRequired(Choice):
+ """Determine the logic required to complete the seed
+ None: No glitches required
+ Minor Glitches: Puts fake flipper, waterwalk, super bunny shenanigans, and etc into logic
+ Overworld Glitches: Assumes the player has knowledge of both overworld major glitches (boots clips, mirror clips) and minor glitches
+ Hybrid Major Glitches: In addition to overworld glitches, also requires underworld clips between dungeons.
+ No Logic: Your own items are placed with no regard to any logic; such as your Fire Rod can be on your Trinexx."""
+ display_name = "Glitches Required"
option_no_glitches = 0
option_minor_glitches = 1
option_overworld_glitches = 2
@@ -12,20 +20,121 @@ class Logic(Choice):
option_no_logic = 4
alias_owg = 2
alias_hmg = 3
+ alias_none = 0
-class Objective(Choice):
- option_crystals = 0
- # option_pendants = 1
- option_triforce_pieces = 2
- option_pedestal = 3
- option_bingo = 4
+class DarkRoomLogic(Choice):
+ """Logic for unlit dark rooms. Lamp: require the Lamp for these rooms to be considered accessible.
+ Torches: in addition to lamp, allow the fire rod and presence of easily accessible torches for access.
+ None: all dark rooms are always considered doable, meaning this may force completion of rooms in complete darkness."""
+ display_name = "Dark Room Logic"
+ option_lamp = 0
+ option_torches = 1
+ option_none = 2
+ default = 0
class Goal(Choice):
- option_kill_ganon = 0
- option_kill_ganon_and_gt_agahnim = 1
- option_hand_in = 2
+ """Ganon: Climb GT, defeat Agahnim 2, and then kill Ganon
+ Crystals: Only killing Ganon is required. However, items may still be placed in GT
+ Bosses: Defeat the boss of all dungeons, including Agahnim's tower and GT (Aga 2)
+ Pedestal: Pull the Triforce from the Master Sword pedestal
+ Ganon Pedestal: Pull the Master Sword pedestal, then kill Ganon
+ Triforce Hunt: Collect Triforce pieces spread throughout the worlds, then turn them in to Murahadala in front of Hyrule Castle
+ Local Triforce Hunt: Collect Triforce pieces spread throughout your world, then turn them in to Murahadala in front of Hyrule Castle
+ Ganon Triforce Hunt: Collect Triforce pieces spread throughout the worlds, then kill Ganon
+ Local Ganon Triforce Hunt: Collect Triforce pieces spread throughout your world, then kill Ganon"""
+ display_name = "Goal"
+ default = 0
+ option_ganon = 0
+ option_crystals = 1
+ option_bosses = 2
+ option_pedestal = 3
+ option_ganon_pedestal = 4
+ option_triforce_hunt = 5
+ option_local_triforce_hunt = 6
+ option_ganon_triforce_hunt = 7
+ option_local_ganon_triforce_hunt = 8
+
+
+class EntranceShuffle(Choice):
+ """Dungeons Simple: Shuffle just dungeons amongst each other, swapping dungeons entirely, so Hyrule Castle is always 1 dungeon.
+ Dungeons Full: Shuffle any dungeon entrance with any dungeon interior, so Hyrule Castle can be 4 different dungeons, but keep dungeons to a specific world.
+ Dungeons Crossed: like dungeons_full, but allow cross-world traversal through a dungeon. Warning: May force repeated dungeon traversal.
+ Simple: Entrances are grouped together before being randomized. Interiors with two entrances are grouped shuffled together with each other,
+ and Death Mountain entrances are shuffled only on Death Mountain. Dungeons are swapped entirely.
+ Restricted: Like Simple, but single entrance interiors, multi entrance interiors, and Death Mountain interior entrances are all shuffled with each other.
+ Full: Like Restricted, but all Dungeon entrances are shuffled with all non-Dungeon entrances.
+ Crossed: Like Full, but interiors with multiple entrances are no longer confined to the same world, which may allow crossing worlds.
+ Insanity: Like Crossed, but entrances and exits may be decoupled from each other, so that leaving through an exit may not return you to the entrance you entered from."""
+ display_name = "Entrance Shuffle"
+ default = 0
+ alias_none = 0
+ option_vanilla = 0
+ option_dungeons_simple = 1
+ option_dungeons_full = 2
+ option_dungeons_crossed = 3
+ option_simple = 4
+ option_restricted = 5
+ option_full = 6
+ option_crossed = 7
+ option_insanity = 8
+ alias_dungeonssimple = 1
+ alias_dungeonsfull = 2
+ alias_dungeonscrossed = 3
+
+
+class EntranceShuffleSeed(FreeText):
+ """You can specify a number to use as an entrance shuffle seed, or a group name. Everyone with the same group name
+ will get the same entrance shuffle result as long as their Entrance Shuffle, Mode, Retro Caves, and Glitches
+ Required options are the same."""
+ default = "random"
+ display_name = "Entrance Shuffle Seed"
+
+
+class TriforcePiecesMode(Choice):
+ """Determine how to calculate the extra available triforce pieces.
+ Extra: available = triforce_pieces_extra + triforce_pieces_required
+ Percentage: available = (triforce_pieces_percentage /100) * triforce_pieces_required
+ Available: available = triforce_pieces_available"""
+ display_name = "Triforce Pieces Mode"
+ default = 2
+ option_extra = 0
+ option_percentage = 1
+ option_available = 2
+
+
+class TriforcePiecesPercentage(Range):
+ """Set to how many triforce pieces according to a percentage of the required ones, are available to collect in the world."""
+ display_name = "Triforce Pieces Percentage"
+ range_start = 100
+ range_end = 1000
+ default = 150
+
+
+class TriforcePiecesAvailable(Range):
+ """Set to how many triforces pieces are available to collect in the world. Default is 30. Max is 90, Min is 1"""
+ display_name = "Triforce Pieces Available"
+ range_start = 1
+ range_end = 90
+ default = 30
+
+
+class TriforcePiecesRequired(Range):
+ """Set to how many out of X triforce pieces you need to win the game in a triforce hunt.
+ Default is 20. Max is 90, Min is 1."""
+ display_name = "Triforce Pieces Required"
+ range_start = 1
+ range_end = 90
+ default = 20
+
+
+class TriforcePiecesExtra(Range):
+ """Set to how many extra triforces pieces are available to collect in the world."""
+ display_name = "Triforce Pieces Extra"
+ range_start = 0
+ range_end = 89
+ default = 10
class OpenPyramid(Choice):
@@ -44,10 +153,10 @@ class OpenPyramid(Choice):
def to_bool(self, world: MultiWorld, player: int) -> bool:
if self.value == self.option_goal:
- return world.goal[player] in {'crystals', 'ganontriforcehunt', 'localganontriforcehunt', 'ganonpedestal'}
+ return world.goal[player] in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'}
elif self.value == self.option_auto:
- return world.goal[player] in {'crystals', 'ganontriforcehunt', 'localganontriforcehunt', 'ganonpedestal'} \
- and (world.shuffle[player] in {'vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed'} or not
+ return world.goal[player] in {'crystals', 'ganon_triforce_hunt', 'local_ganon_triforce_hunt', 'ganon_pedestal'} \
+ and (world.entrance_shuffle[player] in {'vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed'} or not
world.shuffle_ganon)
elif self.value == self.option_open:
return True
@@ -76,13 +185,13 @@ def hints_useful(self):
return self.value in {1, 2, 3, 4}
-class bigkey_shuffle(DungeonItem):
+class big_key_shuffle(DungeonItem):
"""Big Key Placement"""
item_name_group = "Big Keys"
display_name = "Big Key Shuffle"
-class smallkey_shuffle(DungeonItem):
+class small_key_shuffle(DungeonItem):
"""Small Key Placement"""
option_universal = 5
item_name_group = "Small Keys"
@@ -101,12 +210,154 @@ class map_shuffle(DungeonItem):
display_name = "Map Shuffle"
-class key_drop_shuffle(Toggle):
+class key_drop_shuffle(DefaultOnToggle):
"""Shuffle keys found in pots and dropped from killed enemies,
respects the small key and big key shuffle options."""
display_name = "Key Drop Shuffle"
+class DungeonCounters(Choice):
+ """On: Always display amount of items checked in a dungeon. Pickup: Show when compass is picked up.
+ Default: Show when compass is picked up if the compass itself is shuffled. Off: Never show item count in dungeons."""
+ display_name = "Dungeon Counters"
+ default = 1
+ option_on = 0
+ option_pickup = 1
+ option_default = 2
+ option_off = 4
+
+
+class Mode(Choice):
+ """Standard: Begin the game by rescuing Zelda from her cell and escorting her to the Sanctuary
+ Open: Begin the game from your choice of Link's House or the Sanctuary
+ Inverted: Begin in the Dark World. The Moon Pearl is required to avoid bunny-state in Light World, and the Light World game map is altered"""
+ option_standard = 0
+ option_open = 1
+ option_inverted = 2
+ default = 1
+ display_name = "Mode"
+
+
+class ItemPool(Choice):
+ """Easy: Doubled upgrades, progressives, and etc. Normal: Item availability remains unchanged from vanilla game.
+ Hard: Reduced upgrade availability (max: 14 hearts, blue mail, tempered sword, fire shield, no silvers unless swordless).
+ Expert: Minimum upgrade availability (max: 8 hearts, green mail, master sword, fighter shield, no silvers unless swordless)."""
+ display_name = "Item Pool"
+ default = 1
+ option_easy = 0
+ option_normal = 1
+ option_hard = 2
+ option_expert = 3
+
+
+class ItemFunctionality(Choice):
+ """Easy: Allow Hammer to damage ganon, Allow Hammer tablet collection, Allow swordless medallion use everywhere.
+ Normal: Vanilla item functionality
+ Hard: Reduced helpfulness of items (potions less effective, can't catch faeries, cape uses double magic, byrna does not grant invulnerability, boomerangs do not stun, silvers disabled outside ganon)
+ Expert: Vastly reduces the helpfulness of items (potions barely effective, can't catch faeries, cape uses double magic, byrna does not grant invulnerability, boomerangs and hookshot do not stun, silvers disabled outside ganon)"""
+ display_name = "Item Functionality"
+ default = 1
+ option_easy = 0
+ option_normal = 1
+ option_hard = 2
+ option_expert = 3
+
+
+class EnemyHealth(Choice):
+ """Default: Vanilla enemy HP. Easy: Enemies have reduced health. Hard: Enemies have increased health.
+ Expert: Enemies have greatly increased health."""
+ display_name = "Enemy Health"
+ default = 1
+ option_easy = 0
+ option_default = 1
+ option_hard = 2
+ option_expert = 3
+
+
+class EnemyDamage(Choice):
+ """Default: Vanilla enemy damage. Shuffled: 0 # Enemies deal 0 to 4 hearts and armor helps.
+ Chaos: Enemies deal 0 to 8 hearts and armor just reshuffles the damage."""
+ display_name = "Enemy Damage"
+ default = 0
+ option_default = 0
+ option_shuffled = 2
+ option_chaos = 3
+
+
+class ShufflePrizes(Choice):
+ """Shuffle "general" prize packs, as in enemy, tree pull, dig etc.; "bonk" prizes; or both."""
+ display_name = "Shuffle Prizes"
+ default = 1
+ option_off = 0
+ option_general = 1
+ option_bonk = 2
+ option_both = 3
+
+
+class Medallion(Choice):
+ default = "random"
+ option_ether = 0
+ option_bombos = 1
+ option_quake = 2
+
+
+class MiseryMireMedallion(Medallion):
+ """Required medallion to open Misery Mire front entrance."""
+ display_name = "Misery Mire Medallion"
+
+
+class TurtleRockMedallion(Medallion):
+ """Required medallion to open Turtle Rock front entrance."""
+ display_name = "Turtle Rock Medallion"
+
+
+class Timer(Choice):
+ """None: No timer will be displayed. OHKO: Timer always at zero. Permanent OHKO.
+ Timed: Starts with clock at zero. Green clocks subtract 4 minutes (total 20). Blue clocks subtract 2 minutes (total 10). Red clocks add two minutes (total 10). Winner is the player with the lowest time at the end.
+ Timed OHKO: Starts the clock at ten minutes. Green clocks add five minutes (total 25). As long as the clock as at zero, Link will die in one hit.
+ Timed Countdown: Starts the clock with forty minutes. Same clocks as timed mode, but if the clock hits zero you lose. You can still keep playing, though.
+ Display: Displays a timer, but otherwise does not affect gameplay or the item pool."""
+ display_name = "Timer"
+ option_none = 0
+ option_timed = 1
+ option_timed_ohko = 2
+ option_ohko = 3
+ option_timed_countdown = 4
+ option_display = 5
+ default = 0
+
+
+class CountdownStartTime(Range):
+ """For Timed OHKO and Timed Countdown timer modes, the amount of time in minutes to start with."""
+ display_name = "Countdown Start Time"
+ range_start = 0
+ range_end = 480
+ default = 10
+
+
+class ClockTime(Range):
+ range_start = -60
+ range_end = 60
+
+
+class RedClockTime(ClockTime):
+ """For all timer modes, the amount of time in minutes to gain or lose when picking up a red clock."""
+ display_name = "Red Clock Time"
+ default = -2
+
+
+class BlueClockTime(ClockTime):
+ """For all timer modes, the amount of time in minutes to gain or lose when picking up a blue clock."""
+ display_name = "Blue Clock Time"
+ default = 2
+
+
+class GreenClockTime(ClockTime):
+ """For all timer modes, the amount of time in minutes to gain or lose when picking up a green clock."""
+ display_name = "Green Clock Time"
+ default = 4
+
+
class Crystals(Range):
range_start = 0
range_end = 7
@@ -137,18 +388,52 @@ class ShopItemSlots(Range):
range_end = 30
+class RandomizeShopInventories(Choice):
+ """Generate new default inventories for overworld/underworld shops, and unique shops; or each shop independently"""
+ display_name = "Randomize Shop Inventories"
+ default = 0
+ option_default = 0
+ option_randomize_by_shop_type = 1
+ option_randomize_each = 2
+
+
+class ShuffleShopInventories(Toggle):
+ """Shuffle default inventories of the shops around"""
+ display_name = "Shuffle Shop Inventories"
+
+
+class RandomizeShopPrices(Toggle):
+ """Randomize the prices of the items in shop inventories"""
+ display_name = "Randomize Shop Prices"
+
+
+class RandomizeCostTypes(Toggle):
+ """Prices of the items in shop inventories may cost hearts, arrow, or bombs instead of rupees"""
+ display_name = "Randomize Cost Types"
+
+
class ShopPriceModifier(Range):
"""Percentage modifier for shuffled item prices in shops"""
- display_name = "Shop Price Cost Percent"
+ display_name = "Shop Price Modifier"
range_start = 0
default = 100
range_end = 400
-class WorldState(Choice):
- option_standard = 1
- option_open = 0
- option_inverted = 2
+class IncludeWitchHut(Toggle):
+ """Consider witch's hut like any other shop and shuffle/randomize it too"""
+ display_name = "Include Witch's Hut"
+
+
+class ShuffleCapacityUpgrades(Choice):
+ """Shuffle capacity upgrades into the item pool (and allow them to traverse the multiworld).
+ On Combined will shuffle only a single bomb upgrade and arrow upgrade each which bring you to the maximum capacity."""
+ display_name = "Shuffle Capacity Upgrades"
+ option_off = 0
+ option_on = 1
+ option_on_combined = 2
+ alias_false = 0
+ alias_true = 1
class LTTPBosses(PlandoBosses):
@@ -236,6 +521,11 @@ class Swordless(Toggle):
display_name = "Swordless"
+class BomblessStart(Toggle):
+ """Start with a max of 0 bombs available, requiring Bomb Upgrade items in order to use bombs"""
+ display_name = "Bombless Start"
+
+
# Might be a decent idea to split "Bow" into its own option with choices of
# Defer to Progressive Option (default), Progressive, Non-Progressive, Bow + Silvers, Retro
class RetroBow(Toggle):
@@ -433,29 +723,66 @@ class AllowCollect(Toggle):
alttp_options: typing.Dict[str, type(Option)] = {
+ "start_inventory_from_pool": StartInventoryPool,
+ "goal": Goal,
+ "mode": Mode,
+ "glitches_required": GlitchesRequired,
+ "dark_room_logic": DarkRoomLogic,
+ "open_pyramid": OpenPyramid,
"crystals_needed_for_gt": CrystalsTower,
"crystals_needed_for_ganon": CrystalsGanon,
- "open_pyramid": OpenPyramid,
- "bigkey_shuffle": bigkey_shuffle,
- "smallkey_shuffle": smallkey_shuffle,
+ "triforce_pieces_mode": TriforcePiecesMode,
+ "triforce_pieces_percentage": TriforcePiecesPercentage,
+ "triforce_pieces_required": TriforcePiecesRequired,
+ "triforce_pieces_available": TriforcePiecesAvailable,
+ "triforce_pieces_extra": TriforcePiecesExtra,
+ "entrance_shuffle": EntranceShuffle,
+ "entrance_shuffle_seed": EntranceShuffleSeed,
+ "big_key_shuffle": big_key_shuffle,
+ "small_key_shuffle": small_key_shuffle,
"key_drop_shuffle": key_drop_shuffle,
"compass_shuffle": compass_shuffle,
"map_shuffle": map_shuffle,
+ "restrict_dungeon_item_on_boss": RestrictBossItem,
+ "item_pool": ItemPool,
+ "item_functionality": ItemFunctionality,
+ "enemy_health": EnemyHealth,
+ "enemy_damage": EnemyDamage,
"progressive": Progressive,
"swordless": Swordless,
+ "dungeon_counters": DungeonCounters,
"retro_bow": RetroBow,
"retro_caves": RetroCaves,
"hints": Hints,
"scams": Scams,
- "restrict_dungeon_item_on_boss": RestrictBossItem,
"boss_shuffle": LTTPBosses,
"pot_shuffle": PotShuffle,
"enemy_shuffle": EnemyShuffle,
"killable_thieves": KillableThieves,
"bush_shuffle": BushShuffle,
"shop_item_slots": ShopItemSlots,
+ "randomize_shop_inventories": RandomizeShopInventories,
+ "shuffle_shop_inventories": ShuffleShopInventories,
+ "include_witch_hut": IncludeWitchHut,
+ "randomize_shop_prices": RandomizeShopPrices,
+ "randomize_cost_types": RandomizeCostTypes,
"shop_price_modifier": ShopPriceModifier,
+ "shuffle_capacity_upgrades": ShuffleCapacityUpgrades,
+ "bombless_start": BomblessStart,
+ "shuffle_prizes": ShufflePrizes,
"tile_shuffle": TileShuffle,
+ "misery_mire_medallion": MiseryMireMedallion,
+ "turtle_rock_medallion": TurtleRockMedallion,
+ "glitch_boots": GlitchBoots,
+ "beemizer_total_chance": BeemizerTotalChance,
+ "beemizer_trap_chance": BeemizerTrapChance,
+ "timer": Timer,
+ "countdown_start_time": CountdownStartTime,
+ "red_clock_time": RedClockTime,
+ "blue_clock_time": BlueClockTime,
+ "green_clock_time": GreenClockTime,
+ "death_link": DeathLink,
+ "allow_collect": AllowCollect,
"ow_palettes": OWPalette,
"uw_palettes": UWPalette,
"hud_palettes": HUDPalette,
@@ -469,10 +796,4 @@ class AllowCollect(Toggle):
"music": Music,
"reduceflashing": ReduceFlashing,
"triforcehud": TriforceHud,
- "glitch_boots": GlitchBoots,
- "beemizer_total_chance": BeemizerTotalChance,
- "beemizer_trap_chance": BeemizerTrapChance,
- "death_link": DeathLink,
- "allow_collect": AllowCollect,
- "start_inventory_from_pool": StartInventoryPool,
}
diff --git a/worlds/alttp/Regions.py b/worlds/alttp/Regions.py
index 0cc8a3d6a71f..dc3adb108af1 100644
--- a/worlds/alttp/Regions.py
+++ b/worlds/alttp/Regions.py
@@ -94,7 +94,7 @@ def create_regions(world, player):
create_cave_region(world, player, 'Kakariko Gamble Game', 'a game of chance'),
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, 'Capacity Upgrade', 'the queen of fairies', ['Capacity Upgrade Shop']),
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'),
@@ -121,8 +121,9 @@ def create_regions(world, player):
['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 - 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, 'Sewers', 'a drop\'s exit', None, ['Sanctuary Push Door', 'Sewers Back Door', 'Sewers Secret Room']),
+ create_dungeon_region(world, player, 'Sewers Secret Room', 'a drop\'s exit', ['Sewers - Secret Room - Left', 'Sewers - Secret Room - Middle',
+ 'Sewers - Secret Room - Right']),
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', '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),
@@ -275,7 +276,9 @@ def create_regions(world, player):
create_cave_region(world, player, 'Hookshot Cave', 'a connector',
['Hookshot Cave - Top Right', 'Hookshot Cave - Top Left', 'Hookshot Cave - Bottom Right',
'Hookshot Cave - Bottom Left'],
- ['Hookshot Cave Exit (South)', 'Hookshot Cave Exit (North)']),
+ ['Hookshot Cave Exit (South)', 'Hookshot Cave Bomb Wall (South)']),
+ create_cave_region(world, player, 'Hookshot Cave (Upper)', 'a connector', None, ['Hookshot Cave Exit (North)',
+ 'Hookshot Cave Bomb Wall (North)']),
create_dw_region(world, player, 'Death Mountain Floating Island (Dark World)', None,
['Floating Island Drop', 'Hookshot Cave Back Entrance', 'Floating Island Mirror Spot']),
create_lw_region(world, player, 'Death Mountain Floating Island (Light World)', ['Floating Island']),
@@ -311,8 +314,8 @@ def create_regions(world, player):
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 (Entrance)', 'Ice Palace', ['Ice Palace - Jelly Key Drop', 'Ice Palace - Compass Chest'], ['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 (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)']),
@@ -735,6 +738,7 @@ def mark_light_world_regions(world, player: int):
'Missing Smith': (None, None, False, None),
'Dark Blacksmith Ruins': (None, None, False, None),
'Flute Activation Spot': (None, None, False, None),
+ 'Capacity Upgrade Shop': (None, None, False, None),
'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], None, True, 'Eastern Palace'),
'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], None, True, 'Desert Palace'),
'Tower of Hera - Prize': (
diff --git a/worlds/alttp/Rom.py b/worlds/alttp/Rom.py
index b80cec578a97..ff4947bb0198 100644
--- a/worlds/alttp/Rom.py
+++ b/worlds/alttp/Rom.py
@@ -4,7 +4,7 @@
import worlds.Files
LTTPJPN10HASH: str = "03a63945398191337e896e5771f77173"
-RANDOMIZERBASEHASH: str = "9952c2a3ec1b421e408df0d20c8f0c7f"
+RANDOMIZERBASEHASH: str = "35d010bc148e0ea0ee68e81e330223f1"
ROM_PLAYER_LIMIT: int = 255
import io
@@ -36,7 +36,7 @@
SickKid_texts, FluteBoy_texts, Zora_texts, MagicShop_texts, Sahasrahla_names
from .Items import ItemFactory, item_table, item_name_groups, progression_items
from .EntranceShuffle import door_addresses
-from .Options import smallkey_shuffle
+from .Options import small_key_shuffle
try:
from maseya import z3pr
@@ -294,7 +294,7 @@ def patch_enemizer(world, rom: LocalRom, enemizercli, output_directory):
'RandomizeBushEnemyChance': multiworld.bush_shuffle[player].value,
'RandomizeEnemyHealthRange': multiworld.enemy_health[player] != 'default',
'RandomizeEnemyHealthType': {'default': 0, 'easy': 0, 'normal': 1, 'hard': 2, 'expert': 3}[
- multiworld.enemy_health[player]],
+ multiworld.enemy_health[player].current_key],
'OHKO': False,
'RandomizeEnemyDamage': multiworld.enemy_damage[player] != 'default',
'AllowEnemyZeroDamage': True,
@@ -858,13 +858,13 @@ def patch_rom(world: MultiWorld, rom: LocalRom, player: int, enemized: bool):
# Thanks to Zarby89 for originally finding these values
# todo fix screen scrolling
- if world.shuffle[player] not in {'insanity', 'insanity_legacy', 'madness_legacy'} and \
+ if world.entrance_shuffle[player] != 'insanity' and \
exit.name in {'Eastern Palace Exit', 'Tower of Hera Exit', 'Thieves Town Exit',
'Skull Woods Final Section Exit', 'Ice Palace Exit', 'Misery Mire Exit',
'Palace of Darkness Exit', 'Swamp Palace Exit', 'Ganons Tower Exit',
'Desert Palace Exit (North)', 'Agahnims Tower Exit', 'Spiral Cave Exit (Top)',
'Superbunny Cave Exit (Bottom)', 'Turtle Rock Ledge Exit (East)'} and \
- (world.logic[player] not in ['hybridglitches', 'nologic'] or
+ (world.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic'] or
exit.name not in {'Palace of Darkness Exit', 'Tower of Hera Exit', 'Swamp Palace Exit'}):
# For exits that connot be reached from another, no need to apply offset fixes.
rom.write_int16(0x15DB5 + 2 * offset, link_y) # same as final else
@@ -907,7 +907,9 @@ def credits_digit(num):
if world.retro_caves[player]: # Old man cave and Take any caves will count towards collection rate.
credits_total += 5
if world.shop_item_slots[player]: # Potion shop only counts towards collection rate if included in the shuffle.
- credits_total += 30 if 'w' in world.shop_shuffle[player] else 27
+ credits_total += 30 if world.include_witch_hut[player] else 27
+ if world.shuffle_capacity_upgrades[player]:
+ credits_total += 2
rom.write_byte(0x187010, credits_total) # dynamic credits
@@ -1059,7 +1061,7 @@ def credits_digit(num):
# Set stun items
rom.write_byte(0x180180, 0x03) # All standard items
# Set overflow items for progressive equipment
- if world.timer[player] in ['timed', 'timed-countdown', 'timed-ohko']:
+ if world.timer[player] in ['timed', 'timed_countdown', 'timed_ohko']:
overflow_replacement = GREEN_CLOCK
else:
overflow_replacement = GREEN_TWENTY_RUPEES
@@ -1079,7 +1081,7 @@ def credits_digit(num):
difficulty.progressive_bow_limit, item_table[difficulty.basicbow[-1]].item_code])
if difficulty.progressive_bow_limit < 2 and (
- world.swordless[player] or world.logic[player] == 'noglitches'):
+ world.swordless[player] or world.glitches_required[player] == 'no_glitches'):
rom.write_bytes(0x180098, [2, item_table["Silver Bow"].item_code])
rom.write_byte(0x180181, 0x01) # Make silver arrows work only on ganon
rom.write_byte(0x180182, 0x00) # Don't auto equip silvers on pickup
@@ -1095,7 +1097,7 @@ def credits_digit(num):
prize_replacements[0xE1] = 0xDA # 5 Arrows -> Blue Rupee
prize_replacements[0xE2] = 0xDB # 10 Arrows -> Red Rupee
- if "g" in world.shuffle_prizes[player]:
+ if world.shuffle_prizes[player] in ("general", "both"):
# shuffle prize packs
prizes = [0xD8, 0xD8, 0xD8, 0xD8, 0xD9, 0xD8, 0xD8, 0xD9, 0xDA, 0xD9, 0xDA, 0xDB, 0xDA, 0xD9, 0xDA, 0xDA, 0xE0,
0xDF, 0xDF, 0xDA, 0xE0, 0xDF, 0xD8, 0xDF,
@@ -1157,7 +1159,7 @@ def chunk(l, n):
byte = int(rom.read_byte(address))
rom.write_byte(address, prize_replacements.get(byte, byte))
- if "b" in world.shuffle_prizes[player]:
+ if world.shuffle_prizes[player] in ("bonk", "both"):
# set bonk prizes
bonk_prizes = [0x79, 0xE3, 0x79, 0xAC, 0xAC, 0xE0, 0xDC, 0xAC, 0xE3, 0xE3, 0xDA, 0xE3, 0xDA, 0xD8, 0xAC,
0xAC, 0xE3, 0xD8, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xE3, 0xDC, 0xDB, 0xE3, 0xDA, 0x79, 0x79,
@@ -1274,7 +1276,7 @@ def chunk(l, n):
rom.write_bytes(0x180213, [0x00, 0x01]) # Not a Tournament Seed
gametype = 0x04 # item
- if world.shuffle[player] != 'vanilla':
+ if world.entrance_shuffle[player] != 'vanilla':
gametype |= 0x02 # entrance
if enemized:
gametype |= 0x01 # enemizer
@@ -1312,7 +1314,7 @@ def chunk(l, n):
equip[0x36C] = 0x18
equip[0x36D] = 0x18
equip[0x379] = 0x68
- starting_max_bombs = 10
+ starting_max_bombs = 0 if world.bombless_start[player] else 10
starting_max_arrows = 30
startingstate = CollectionState(world)
@@ -1430,8 +1432,8 @@ def chunk(l, n):
'Bottle (Fairy)': 6, 'Bottle (Bee)': 7, 'Bottle (Good Bee)': 8}
rupees = {'Rupee (1)': 1, 'Rupees (5)': 5, 'Rupees (20)': 20, 'Rupees (50)': 50, 'Rupees (100)': 100,
'Rupees (300)': 300}
- bomb_caps = {'Bomb Upgrade (+5)': 5, 'Bomb Upgrade (+10)': 10}
- arrow_caps = {'Arrow Upgrade (+5)': 5, 'Arrow Upgrade (+10)': 10}
+ bomb_caps = {'Bomb Upgrade (+5)': 5, 'Bomb Upgrade (+10)': 10, 'Bomb Upgrade (50)': 50}
+ arrow_caps = {'Arrow Upgrade (+5)': 5, 'Arrow Upgrade (+10)': 10, 'Arrow Upgrade (70)': 70}
bombs = {'Single Bomb': 1, 'Bombs (3)': 3, 'Bombs (10)': 10}
arrows = {'Single Arrow': 1, 'Arrows (10)': 10}
@@ -1498,7 +1500,7 @@ def chunk(l, n):
rom.write_byte(0x3A96D, 0xF0 if world.mode[
player] != 'inverted' else 0xD0) # Residual Portal: Normal (F0= Light Side, D0=Dark Side, 42 = both (Darth Vader))
rom.write_byte(0x3A9A7, 0xD0) # Residual Portal: Normal (D0= Light Side, F0=Dark Side, 42 = both (Darth Vader))
- if 'u' in world.shop_shuffle[player]:
+ if world.shuffle_capacity_upgrades[player]:
rom.write_bytes(0x180080,
[5, 10, 5, 10]) # values to fill for Capacity Upgrades (Bomb5, Bomb10, Arrow5, Arrow10)
else:
@@ -1509,11 +1511,11 @@ def chunk(l, n):
(0x02 if 'bombs' in world.escape_assist[player] else 0x00) |
(0x04 if 'magic' in world.escape_assist[player] else 0x00))) # Escape assist
- if world.goal[player] in ['pedestal', 'triforcehunt', 'localtriforcehunt', 'icerodhunt']:
+ if world.goal[player] in ['pedestal', 'triforce_hunt', 'local_triforce_hunt']:
rom.write_byte(0x18003E, 0x01) # make ganon invincible
- elif world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']:
+ elif world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']:
rom.write_byte(0x18003E, 0x05) # make ganon invincible until enough triforce pieces are collected
- elif world.goal[player] in ['ganonpedestal']:
+ elif world.goal[player] in ['ganon_pedestal']:
rom.write_byte(0x18003E, 0x06)
elif world.goal[player] in ['bosses']:
rom.write_byte(0x18003E, 0x02) # make ganon invincible until all bosses are beat
@@ -1534,12 +1536,12 @@ def chunk(l, n):
# c - enabled for inside compasses
# s - enabled for inside small keys
# block HC upstairs doors in rain state in standard mode
- rom.write_byte(0x18008A, 0x01 if world.mode[player] == "standard" and world.shuffle[player] != 'vanilla' else 0x00)
+ rom.write_byte(0x18008A, 0x01 if world.mode[player] == "standard" and world.entrance_shuffle[player] != 'vanilla' else 0x00)
- rom.write_byte(0x18016A, 0x10 | ((0x01 if world.smallkey_shuffle[player] else 0x00)
+ rom.write_byte(0x18016A, 0x10 | ((0x01 if world.small_key_shuffle[player] else 0x00)
| (0x02 if world.compass_shuffle[player] else 0x00)
| (0x04 if world.map_shuffle[player] else 0x00)
- | (0x08 if world.bigkey_shuffle[
+ | (0x08 if world.big_key_shuffle[
player] else 0x00))) # free roaming item text boxes
rom.write_byte(0x18003B, 0x01 if world.map_shuffle[player] else 0x00) # maps showing crystals on overworld
@@ -1561,9 +1563,9 @@ def chunk(l, n):
# b - Big Key
# a - Small Key
#
- rom.write_byte(0x180045, ((0x00 if (world.smallkey_shuffle[player] == smallkey_shuffle.option_original_dungeon or
- world.smallkey_shuffle[player] == smallkey_shuffle.option_universal) else 0x01)
- | (0x02 if world.bigkey_shuffle[player] else 0x00)
+ rom.write_byte(0x180045, ((0x00 if (world.small_key_shuffle[player] == small_key_shuffle.option_original_dungeon or
+ world.small_key_shuffle[player] == small_key_shuffle.option_universal) else 0x01)
+ | (0x02 if world.big_key_shuffle[player] else 0x00)
| (0x04 if world.map_shuffle[player] else 0x00)
| (0x08 if world.compass_shuffle[player] else 0x00))) # free roaming items in menu
@@ -1595,8 +1597,8 @@ def get_reveal_bytes(itemName):
rom.write_int16(0x18017C, get_reveal_bytes('Crystal 5') | get_reveal_bytes('Crystal 6') if world.map_shuffle[
player] else 0x0000) # Bomb Shop Reveal
- rom.write_byte(0x180172, 0x01 if world.smallkey_shuffle[
- player] == smallkey_shuffle.option_universal else 0x00) # universal keys
+ rom.write_byte(0x180172, 0x01 if world.small_key_shuffle[
+ player] == small_key_shuffle.option_universal else 0x00) # universal keys
rom.write_byte(0x18637E, 0x01 if world.retro_bow[player] else 0x00) # Skip quiver in item shops once bought
rom.write_byte(0x180175, 0x01 if world.retro_bow[player] else 0x00) # rupee bow
rom.write_byte(0x180176, 0x0A if world.retro_bow[player] else 0x00) # wood arrow cost
@@ -1613,9 +1615,9 @@ def get_reveal_bytes(itemName):
rom.write_byte(0x180020, digging_game_rng)
rom.write_byte(0xEFD95, digging_game_rng)
rom.write_byte(0x1800A3, 0x01) # enable correct world setting behaviour after agahnim kills
- rom.write_byte(0x1800A4, 0x01 if world.logic[player] != 'nologic' else 0x00) # enable POD EG fix
- rom.write_byte(0x186383, 0x01 if world.glitch_triforce or world.logic[
- player] == 'nologic' else 0x00) # disable glitching to Triforce from Ganons Room
+ rom.write_byte(0x1800A4, 0x01 if world.glitches_required[player] != 'no_logic' else 0x00) # enable POD EG fix
+ rom.write_byte(0x186383, 0x01 if world.glitch_triforce or world.glitches_required[
+ player] == 'no_logic' else 0x00) # disable glitching to Triforce from Ganons Room
rom.write_byte(0x180042, 0x01 if world.save_and_quit_from_boss else 0x00) # Allow Save and Quit after boss kill
# remove shield from uncle
@@ -1660,8 +1662,8 @@ def get_reveal_bytes(itemName):
0x4F])
# allow smith into multi-entrance caves in appropriate shuffles
- if world.shuffle[player] in ['restricted', 'full', 'crossed', 'insanity', 'madness'] or (
- world.shuffle[player] == 'simple' and world.mode[player] == 'inverted'):
+ if world.entrance_shuffle[player] in ['restricted', 'full', 'crossed', 'insanity'] or (
+ world.entrance_shuffle[player] == 'simple' and world.mode[player] == 'inverted'):
rom.write_byte(0x18004C, 0x01)
# set correct flag for hera basement item
@@ -1758,8 +1760,8 @@ def write_custom_shops(rom, world, player):
if item is None:
break
if world.shop_item_slots[player] or shop.type == ShopType.TakeAny:
- count_shop = (shop.region.name != 'Potion Shop' or 'w' in world.shop_shuffle[player]) and \
- shop.region.name != 'Capacity Upgrade'
+ count_shop = (shop.region.name != 'Potion Shop' or world.include_witch_hut[player]) and \
+ (shop.region.name != 'Capacity Upgrade' or world.shuffle_capacity_upgrades[player])
rom.write_byte(0x186560 + shop.sram_offset + slot, 1 if count_shop else 0)
if item['item'] == 'Single Arrow' and item['player'] == 0:
arrow_mask |= 1 << index
@@ -2201,7 +2203,7 @@ def write_strings(rom, world, player):
tt.removeUnwantedText()
# Let's keep this guy's text accurate to the shuffle setting.
- if world.shuffle[player] in ['vanilla', 'dungeonsfull', 'dungeonssimple', 'dungeonscrossed']:
+ if world.entrance_shuffle[player] in ['vanilla', 'dungeons_full', 'dungeons_simple', 'dungeons_crossed']:
tt['kakariko_flophouse_man_no_flippers'] = 'I really hate mowing my yard.\n{PAGEBREAK}\nI should move.'
tt['kakariko_flophouse_man'] = 'I really hate mowing my yard.\n{PAGEBREAK}\nI should move.'
@@ -2255,7 +2257,7 @@ def hint_text(dest, ped_hint=False):
entrances_to_hint.update({'Inverted Ganons Tower': 'The sealed castle door'})
else:
entrances_to_hint.update({'Ganons Tower': 'Ganon\'s Tower'})
- if world.shuffle[player] in ['simple', 'restricted', 'restricted_legacy']:
+ if world.entrance_shuffle[player] in ['simple', 'restricted']:
for entrance in all_entrances:
if entrance.name in entrances_to_hint:
this_hint = entrances_to_hint[entrance.name] + ' leads to ' + hint_text(
@@ -2265,9 +2267,9 @@ def hint_text(dest, ped_hint=False):
break
# Now we write inconvenient locations for most shuffles and finish taking care of the less chaotic ones.
entrances_to_hint.update(InconvenientOtherEntrances)
- if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']:
+ if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']:
hint_count = 0
- elif world.shuffle[player] in ['simple', 'restricted', 'restricted_legacy']:
+ elif world.entrance_shuffle[player] in ['simple', 'restricted']:
hint_count = 2
else:
hint_count = 4
@@ -2284,14 +2286,14 @@ def hint_text(dest, ped_hint=False):
# Next we handle hints for randomly selected other entrances,
# curating the selection intelligently based on shuffle.
- if world.shuffle[player] not in ['simple', 'restricted', 'restricted_legacy']:
+ if world.entrance_shuffle[player] not in ['simple', 'restricted']:
entrances_to_hint.update(ConnectorEntrances)
entrances_to_hint.update(DungeonEntrances)
if world.mode[player] == 'inverted':
entrances_to_hint.update({'Inverted Agahnims Tower': 'The dark mountain tower'})
else:
entrances_to_hint.update({'Agahnims Tower': 'The sealed castle door'})
- elif world.shuffle[player] == 'restricted':
+ elif world.entrance_shuffle[player] == 'restricted':
entrances_to_hint.update(ConnectorEntrances)
entrances_to_hint.update(OtherEntrances)
if world.mode[player] == 'inverted':
@@ -2301,15 +2303,15 @@ def hint_text(dest, ped_hint=False):
else:
entrances_to_hint.update({'Dark Sanctuary Hint': 'The dark sanctuary cave'})
entrances_to_hint.update({'Big Bomb Shop': 'The old bomb shop'})
- if world.shuffle[player] in ['insanity', 'madness_legacy', 'insanity_legacy']:
+ if world.entrance_shuffle[player] != 'insanity':
entrances_to_hint.update(InsanityEntrances)
if world.shuffle_ganon:
if world.mode[player] == 'inverted':
entrances_to_hint.update({'Inverted Pyramid Entrance': 'The extra castle passage'})
else:
entrances_to_hint.update({'Pyramid Ledge': 'The pyramid ledge'})
- hint_count = 4 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull',
- 'dungeonscrossed'] else 0
+ hint_count = 4 if world.entrance_shuffle[player] not in ['vanilla', 'dungeons_simple', 'dungeons_full',
+ 'dungeons_crossed'] else 0
for entrance in all_entrances:
if entrance.name in entrances_to_hint:
if hint_count:
@@ -2323,11 +2325,11 @@ def hint_text(dest, ped_hint=False):
# Next we write a few hints for specific inconvenient locations. We don't make many because in entrance this is highly unpredictable.
locations_to_hint = InconvenientLocations.copy()
- if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']:
+ if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']:
locations_to_hint.extend(InconvenientVanillaLocations)
local_random.shuffle(locations_to_hint)
- hint_count = 3 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull',
- 'dungeonscrossed'] else 5
+ hint_count = 3 if world.entrance_shuffle[player] not in ['vanilla', 'dungeons_simple', 'dungeons_full',
+ 'dungeons_crossed'] else 5
for location in locations_to_hint[:hint_count]:
if location == 'Swamp Left':
if local_random.randint(0, 1):
@@ -2381,16 +2383,16 @@ def hint_text(dest, ped_hint=False):
# Lastly we write hints to show where certain interesting items are.
items_to_hint = RelevantItems.copy()
- if world.smallkey_shuffle[player].hints_useful:
+ if world.small_key_shuffle[player].hints_useful:
items_to_hint |= item_name_groups["Small Keys"]
- if world.bigkey_shuffle[player].hints_useful:
+ if world.big_key_shuffle[player].hints_useful:
items_to_hint |= item_name_groups["Big Keys"]
if world.hints[player] == "full":
hint_count = len(hint_locations) # fill all remaining hint locations with Item hints.
else:
- hint_count = 5 if world.shuffle[player] not in ['vanilla', 'dungeonssimple', 'dungeonsfull',
- 'dungeonscrossed'] else 8
+ hint_count = 5 if world.entrance_shuffle[player] not in ['vanilla', 'dungeons_simple', 'dungeons_full',
+ 'dungeons_crossed'] else 8
hint_count = min(hint_count, len(items_to_hint), len(hint_locations))
if hint_count:
locations = world.find_items_in_locations(items_to_hint, player, True)
@@ -2417,7 +2419,7 @@ def hint_text(dest, ped_hint=False):
tt['ganon_phase_3_no_silvers'] = 'Did you find the silver arrows%s' % silverarrow_hint
tt['ganon_phase_3_no_silvers_alt'] = 'Did you find the silver arrows%s' % silverarrow_hint
if world.worlds[player].has_progressive_bows and (world.difficulty_requirements[player].progressive_bow_limit >= 2 or (
- world.swordless[player] or world.logic[player] == 'noglitches')):
+ world.swordless[player] or world.glitches_required[player] == 'no_glitches')):
prog_bow_locs = world.find_item_locations('Progressive Bow', player, True)
world.per_slot_randoms[player].shuffle(prog_bow_locs)
found_bow = False
@@ -2448,7 +2450,7 @@ def hint_text(dest, ped_hint=False):
if world.goal[player] == 'bosses':
tt['sign_ganon'] = 'You need to kill all bosses, Ganon last.'
- elif world.goal[player] == 'ganonpedestal':
+ elif world.goal[player] == 'ganon_pedestal':
tt['sign_ganon'] = 'You need to pull the pedestal to defeat Ganon.'
elif world.goal[player] == "ganon":
if world.crystals_needed_for_ganon[player] == 1:
@@ -2456,14 +2458,6 @@ def hint_text(dest, ped_hint=False):
else:
tt['sign_ganon'] = f'You need {world.crystals_needed_for_ganon[player]} crystals to beat Ganon and ' \
f'have beaten Agahnim atop Ganons Tower'
- elif world.goal[player] == "icerodhunt":
- tt['sign_ganon'] = 'Go find the Ice Rod and Kill Trinexx, then talk to Murahdahla... Ganon is invincible!'
- tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Go kill Trinexx instead.'
- tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.'
- tt['murahdahla'] = "Hello @. I\nam Murahdahla, brother of\nSahasrahla and Aginah. Behold the power of\n" \
- "invisibility.\n\n\n\n… … …\n\nWait! you can see me? I knew I should have\n" \
- "hidden in a hollow tree. " \
- "If you bring me the Triforce piece from Turtle Rock, I can reassemble it."
else:
if world.crystals_needed_for_ganon[player] == 1:
tt['sign_ganon'] = 'You need a crystal to beat Ganon.'
@@ -2478,10 +2472,10 @@ def hint_text(dest, ped_hint=False):
tt['sahasrahla_quest_have_master_sword'] = Sahasrahla2_texts[local_random.randint(0, len(Sahasrahla2_texts) - 1)]
tt['blind_by_the_light'] = Blind_texts[local_random.randint(0, len(Blind_texts) - 1)]
- if world.goal[player] in ['triforcehunt', 'localtriforcehunt', 'icerodhunt']:
+ if world.goal[player] in ['triforce_hunt', 'local_triforce_hunt']:
tt['ganon_fall_in_alt'] = 'Why are you even here?\n You can\'t even hurt me! Get the Triforce Pieces.'
tt['ganon_phase_3_alt'] = 'Seriously? Go Away, I will not Die.'
- if world.goal[player] == 'triforcehunt' and world.players > 1:
+ if world.goal[player] == 'triforce_hunt' and world.players > 1:
tt['sign_ganon'] = 'Go find the Triforce pieces with your friends... Ganon is invincible!'
else:
tt['sign_ganon'] = 'Go find the Triforce pieces... Ganon is invincible!'
@@ -2504,17 +2498,17 @@ def hint_text(dest, ped_hint=False):
tt['ganon_fall_in_alt'] = 'You cannot defeat me until you finish your goal!'
tt['ganon_phase_3_alt'] = 'Got wax in\nyour ears?\nI can not die!'
if world.treasure_hunt_count[player] > 1:
- if world.goal[player] == 'ganontriforcehunt' and world.players > 1:
+ if world.goal[player] == 'ganon_triforce_hunt' and world.players > 1:
tt['sign_ganon'] = 'You need to find %d Triforce pieces out of %d with your friends to defeat Ganon.' % \
(world.treasure_hunt_count[player], world.triforce_pieces_available[player])
- elif world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']:
+ elif world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']:
tt['sign_ganon'] = 'You need to find %d Triforce pieces out of %d to defeat Ganon.' % \
(world.treasure_hunt_count[player], world.triforce_pieces_available[player])
else:
- if world.goal[player] == 'ganontriforcehunt' and world.players > 1:
+ if world.goal[player] == 'ganon_triforce_hunt' and world.players > 1:
tt['sign_ganon'] = 'You need to find %d Triforce piece out of %d with your friends to defeat Ganon.' % \
(world.treasure_hunt_count[player], world.triforce_pieces_available[player])
- elif world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']:
+ elif world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']:
tt['sign_ganon'] = 'You need to find %d Triforce piece out of %d to defeat Ganon.' % \
(world.treasure_hunt_count[player], world.triforce_pieces_available[player])
@@ -2614,12 +2608,12 @@ def set_inverted_mode(world, player, rom):
rom.write_byte(snes_to_pc(0x08D40C), 0xD0) # morph proof
# the following bytes should only be written in vanilla
# or they'll overwrite the randomizer's shuffles
- if world.shuffle[player] == 'vanilla':
+ if world.entrance_shuffle[player] == 'vanilla':
rom.write_byte(0xDBB73 + 0x23, 0x37) # switch AT and GT
rom.write_byte(0xDBB73 + 0x36, 0x24)
rom.write_int16(0x15AEE + 2 * 0x38, 0x00E0)
rom.write_int16(0x15AEE + 2 * 0x25, 0x000C)
- if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']:
+ if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']:
rom.write_byte(0x15B8C, 0x6C)
rom.write_byte(0xDBB73 + 0x00, 0x53) # switch bomb shop and links house
rom.write_byte(0xDBB73 + 0x52, 0x01)
@@ -2677,7 +2671,7 @@ def set_inverted_mode(world, player, rom):
rom.write_int16(snes_to_pc(0x02D9A6), 0x005A)
rom.write_byte(snes_to_pc(0x02D9B3), 0x12)
# keep the old man spawn point at old man house unless shuffle is vanilla
- if world.shuffle[player] in ['vanilla', 'dungeonsfull', 'dungeonssimple', 'dungeonscrossed']:
+ if world.entrance_shuffle[player] in ['vanilla', 'dungeons_full', 'dungeons_simple', 'dungeons_crossed']:
rom.write_bytes(snes_to_pc(0x308350), [0x00, 0x00, 0x01])
rom.write_int16(snes_to_pc(0x02D8DE), 0x00F1)
rom.write_bytes(snes_to_pc(0x02D910), [0x1F, 0x1E, 0x1F, 0x1F, 0x03, 0x02, 0x03, 0x03])
@@ -2740,7 +2734,7 @@ def set_inverted_mode(world, player, rom):
rom.write_int16s(snes_to_pc(0x1bb836), [0x001B, 0x001B, 0x001B])
rom.write_int16(snes_to_pc(0x308300), 0x0140) # new pyramid hole entrance
rom.write_int16(snes_to_pc(0x308320), 0x001B)
- if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']:
+ if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']:
rom.write_byte(snes_to_pc(0x308340), 0x7B)
rom.write_int16(snes_to_pc(0x1af504), 0x148B)
rom.write_int16(snes_to_pc(0x1af50c), 0x149B)
@@ -2777,10 +2771,10 @@ def set_inverted_mode(world, player, rom):
rom.write_bytes(snes_to_pc(0x1BC85A), [0x50, 0x0F, 0x82])
rom.write_int16(0xDB96F + 2 * 0x35, 0x001B) # move pyramid exit door
rom.write_int16(0xDBA71 + 2 * 0x35, 0x06A4)
- if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']:
+ if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']:
rom.write_byte(0xDBB73 + 0x35, 0x36)
rom.write_byte(snes_to_pc(0x09D436), 0xF3) # remove castle gate warp
- if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']:
+ if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']:
rom.write_int16(0x15AEE + 2 * 0x37, 0x0010) # pyramid exit to new hc area
rom.write_byte(0x15B8C + 0x37, 0x1B)
rom.write_int16(0x15BDB + 2 * 0x37, 0x0418)
diff --git a/worlds/alttp/Rules.py b/worlds/alttp/Rules.py
index 8a04f87afa02..b86a793fb937 100644
--- a/worlds/alttp/Rules.py
+++ b/worlds/alttp/Rules.py
@@ -9,25 +9,25 @@
from . import OverworldGlitchRules
from .Bosses import GanonDefeatRule
from .Items import ItemFactory, item_name_groups, item_table, progression_items
-from .Options import smallkey_shuffle
+from .Options import small_key_shuffle
from .OverworldGlitchRules import no_logic_rules, overworld_glitches_rules
from .Regions import LTTPRegionType, location_table
from .StateHelpers import (can_extend_magic, can_kill_most_things,
can_lift_heavy_rocks, can_lift_rocks,
can_melt_things, can_retrieve_tablet,
can_shoot_arrows, has_beam_sword, has_crystals,
- has_fire_source, has_hearts,
+ has_fire_source, has_hearts, has_melee_weapon,
has_misery_mire_medallion, has_sword, has_turtle_rock_medallion,
- has_triforce_pieces)
+ has_triforce_pieces, can_use_bombs, can_bomb_or_bonk)
from .UnderworldGlitchRules import underworld_glitches_rules
def set_rules(world):
player = world.player
world = world.multiworld
- if world.logic[player] == 'nologic':
+ if world.glitches_required[player] == 'no_logic':
if player == next(player_id for player_id in world.get_game_players("A Link to the Past")
- if world.logic[player_id] == 'nologic'): # only warn one time
+ if world.glitches_required[player_id] == 'no_logic'): # only warn one time
logging.info(
'WARNING! Seeds generated under this logic often require major glitches and may be impossible!')
@@ -45,8 +45,8 @@ def set_rules(world):
else:
world.completion_condition[player] = lambda state: state.has('Triforce', player)
- global_rules(world, player)
dungeon_boss_rules(world, player)
+ global_rules(world, player)
if world.mode[player] != 'inverted':
default_rules(world, player)
@@ -61,24 +61,24 @@ def set_rules(world):
else:
raise NotImplementedError(f'World state {world.mode[player]} is not implemented yet')
- if world.logic[player] == 'noglitches':
+ if world.glitches_required[player] == 'no_glitches':
no_glitches_rules(world, player)
- elif world.logic[player] == 'owglitches':
+ elif world.glitches_required[player] == 'overworld_glitches':
# Initially setting no_glitches_rules to set the baseline rules for some
# entrances. The overworld_glitches_rules set is primarily additive.
no_glitches_rules(world, player)
fake_flipper_rules(world, player)
overworld_glitches_rules(world, player)
- elif world.logic[player] in ['hybridglitches', 'nologic']:
+ elif world.glitches_required[player] in ['hybrid_major_glitches', 'no_logic']:
no_glitches_rules(world, player)
fake_flipper_rules(world, player)
overworld_glitches_rules(world, player)
underworld_glitches_rules(world, player)
- elif world.logic[player] == 'minorglitches':
+ elif world.glitches_required[player] == 'minor_glitches':
no_glitches_rules(world, player)
fake_flipper_rules(world, player)
else:
- raise NotImplementedError(f'Not implemented yet: Logic - {world.logic[player]}')
+ raise NotImplementedError(f'Not implemented yet: Logic - {world.glitches_required[player]}')
if world.goal[player] == 'bosses':
# require all bosses to beat ganon
@@ -89,7 +89,7 @@ def set_rules(world):
if world.mode[player] != 'inverted':
set_big_bomb_rules(world, player)
- if world.logic[player] in {'owglitches', 'hybridglitches', 'nologic'} and world.shuffle[player] not in {'insanity', 'insanity_legacy', 'madness'}:
+ if world.glitches_required[player] in {'overworld_glitches', 'hybrid_major_glitches', 'no_logic'} and world.entrance_shuffle[player] not in {'insanity', 'insanity_legacy', 'madness'}:
path_to_courtyard = mirrorless_path_to_castle_courtyard(world, player)
add_rule(world.get_entrance('Pyramid Fairy', player), lambda state: state.multiworld.get_entrance('Dark Death Mountain Offset Mirror', player).can_reach(state) and all(rule(state) for rule in path_to_courtyard), 'or')
else:
@@ -97,18 +97,18 @@ def set_rules(world):
# if swamp and dam have not been moved we require mirror for swamp palace
# however there is mirrorless swamp in hybrid MG, so we don't necessarily want this. HMG handles this requirement itself.
- if not world.swamp_patch_required[player] and world.logic[player] not in ['hybridglitches', 'nologic']:
+ if not world.swamp_patch_required[player] and world.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic']:
add_rule(world.get_entrance('Swamp Palace Moat', player), lambda state: state.has('Magic Mirror', player))
# GT Entrance may be required for Turtle Rock for OWG and < 7 required
ganons_tower = world.get_entrance('Inverted Ganons Tower' if world.mode[player] == 'inverted' else 'Ganons Tower', player)
- if world.crystals_needed_for_gt[player] == 7 and not (world.logic[player] in ['owglitches', 'hybridglitches', 'nologic'] and world.mode[player] != 'inverted'):
+ if world.crystals_needed_for_gt[player] == 7 and not (world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic'] and world.mode[player] != 'inverted'):
set_rule(ganons_tower, lambda state: False)
set_trock_key_rules(world, player)
set_rule(ganons_tower, lambda state: has_crystals(state, state.multiworld.crystals_needed_for_gt[player], player))
- if world.mode[player] != 'inverted' and world.logic[player] in ['owglitches', 'hybridglitches', 'nologic']:
+ if world.mode[player] != 'inverted' and world.glitches_required[player] in ['overworld_glitches', 'hybrid_major_glitches', 'no_logic']:
add_rule(world.get_entrance('Ganons Tower', player), lambda state: state.multiworld.get_entrance('Ganons Tower Ascent', player).can_reach(state), 'or')
set_bunny_rules(world, player, world.mode[player] == 'inverted')
@@ -139,6 +139,7 @@ def set_defeat_dungeon_boss_rule(location):
add_rule(location, lambda state: location.parent_region.dungeon.boss.can_defeat(state))
+
def set_always_allow(spot, rule):
spot.always_allow = rule
@@ -184,6 +185,7 @@ def dungeon_boss_rules(world, player):
for location in boss_locations:
set_defeat_dungeon_boss_rule(world.get_location(location, player))
+
def global_rules(world, player):
# ganon can only carry triforce
add_item_rule(world.get_location('Ganon', player), lambda item: item.name == 'Triforce' and item.player == player)
@@ -213,14 +215,61 @@ def global_rules(world, player):
set_rule(world.get_location('Ether Tablet', player), lambda state: can_retrieve_tablet(state, player))
set_rule(world.get_location('Master Sword Pedestal', player), lambda state: state.has('Red Pendant', player) and state.has('Blue Pendant', player) and state.has('Green Pendant', player))
- set_rule(world.get_location('Missing Smith', player), lambda state: state.has('Get Frog', player) and state.can_reach('Blacksmiths Hut', 'Region', player)) # Can't S&Q with smith
+ set_rule(world.get_location('Missing Smith', player), lambda state: state.has('Get Frog', player) and state.can_reach('Blacksmiths Hut', 'Region', player)) # Can't S&Q with smith
set_rule(world.get_location('Blacksmith', player), lambda state: state.has('Return Smith', player))
set_rule(world.get_location('Magic Bat', player), lambda state: state.has('Magic Powder', player))
set_rule(world.get_location('Sick Kid', player), lambda state: state.has_group("Bottles", player))
set_rule(world.get_location('Library', player), lambda state: state.has('Pegasus Boots', player))
- set_rule(world.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player))
+
+ if world.enemy_shuffle[player]:
+ set_rule(world.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player) and
+ can_kill_most_things(state, player, 4))
+ else:
+ set_rule(world.get_location('Mimic Cave', player), lambda state: state.has('Hammer', player)
+ and ((state.multiworld.enemy_health[player] in ("easy", "default") and can_use_bombs(state, player, 4))
+ or can_shoot_arrows(state, player) or state.has("Cane of Somaria", player)
+ or has_beam_sword(state, player)))
+
set_rule(world.get_location('Sahasrahla', player), lambda state: state.has('Green Pendant', player))
+ set_rule(world.get_location('Aginah\'s Cave', player), lambda state: can_use_bombs(state, player))
+ set_rule(world.get_location('Blind\'s Hideout - Top', player), lambda state: can_use_bombs(state, player))
+ set_rule(world.get_location('Chicken House', player), lambda state: can_use_bombs(state, player))
+ set_rule(world.get_location('Kakariko Well - Top', player), lambda state: can_use_bombs(state, player))
+ set_rule(world.get_location('Graveyard Cave', player), lambda state: can_use_bombs(state, player))
+ set_rule(world.get_location('Sahasrahla\'s Hut - Left', player), lambda state: can_bomb_or_bonk(state, player))
+ set_rule(world.get_location('Sahasrahla\'s Hut - Middle', player), lambda state: can_bomb_or_bonk(state, player))
+ set_rule(world.get_location('Sahasrahla\'s Hut - Right', player), lambda state: can_bomb_or_bonk(state, player))
+ set_rule(world.get_location('Paradox Cave Lower - Left', player), lambda state: can_use_bombs(state, player)
+ or has_beam_sword(state, player) or can_shoot_arrows(state, player)
+ or state.has_any(["Fire Rod", "Cane of Somaria"], player))
+ set_rule(world.get_location('Paradox Cave Lower - Right', player), lambda state: can_use_bombs(state, player)
+ or has_beam_sword(state, player) or can_shoot_arrows(state, player)
+ or state.has_any(["Fire Rod", "Cane of Somaria"], player))
+ set_rule(world.get_location('Paradox Cave Lower - Far Right', player), lambda state: can_use_bombs(state, player)
+ or has_beam_sword(state, player) or can_shoot_arrows(state, player)
+ or state.has_any(["Fire Rod", "Cane of Somaria"], player))
+ set_rule(world.get_location('Paradox Cave Lower - Middle', player), lambda state: can_use_bombs(state, player)
+ or has_beam_sword(state, player) or can_shoot_arrows(state, player)
+ or state.has_any(["Fire Rod", "Cane of Somaria"], player))
+ set_rule(world.get_location('Paradox Cave Lower - Far Left', player), lambda state: can_use_bombs(state, player)
+ or has_beam_sword(state, player) or can_shoot_arrows(state, player)
+ or state.has_any(["Fire Rod", "Cane of Somaria"], player))
+ set_rule(world.get_location('Paradox Cave Upper - Left', player), lambda state: can_use_bombs(state, player))
+ set_rule(world.get_location('Paradox Cave Upper - Right', player), lambda state: can_use_bombs(state, player))
+ set_rule(world.get_location('Mini Moldorm Cave - Far Left', player), lambda state: can_kill_most_things(state, player, 4))
+ set_rule(world.get_location('Mini Moldorm Cave - Left', player), lambda state: can_kill_most_things(state, player, 4))
+ set_rule(world.get_location('Mini Moldorm Cave - Far Right', player), lambda state: can_kill_most_things(state, player, 4))
+ set_rule(world.get_location('Mini Moldorm Cave - Right', player), lambda state: can_kill_most_things(state, player, 4))
+ set_rule(world.get_location('Mini Moldorm Cave - Generous Guy', player), lambda state: can_kill_most_things(state, player, 4))
+ set_rule(world.get_location('Hype Cave - Bottom', player), lambda state: can_use_bombs(state, player))
+ set_rule(world.get_location('Hype Cave - Middle Left', player), lambda state: can_use_bombs(state, player))
+ set_rule(world.get_location('Hype Cave - Middle Right', player), lambda state: can_use_bombs(state, player))
+ set_rule(world.get_location('Hype Cave - Top', player), lambda state: can_use_bombs(state, player))
+ set_rule(world.get_entrance('Light World Death Mountain Shop', player), lambda state: can_use_bombs(state, player))
+
+ set_rule(world.get_entrance('Two Brothers House Exit (West)', player), lambda state: can_bomb_or_bonk(state, player))
+ set_rule(world.get_entrance('Two Brothers House Exit (East)', player), lambda state: can_bomb_or_bonk(state, player))
set_rule(world.get_location('Spike Cave', player), lambda state:
state.has('Hammer', player) and can_lift_rocks(state, player) and
@@ -238,61 +287,81 @@ def global_rules(world, player):
set_rule(world.get_entrance('Sewers Door', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) or (
- world.smallkey_shuffle[player] == smallkey_shuffle.option_universal and world.mode[
+ world.small_key_shuffle[player] == small_key_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, 4))
+ set_rule(world.get_entrance('Sewers Secret Room', player), lambda state: can_bomb_or_bonk(state, player))
+
set_rule(world.get_entrance('Agahnim 1', player),
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 - Room 03', player), lambda state: can_kill_most_things(state, player, 4))
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)',
+ lambda state: can_kill_most_things(state, player, 4) 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)',
+ lambda state: can_kill_most_things(state, player, 4) 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)',
+ lambda state: can_kill_most_things(state, player, 4) 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))))
+ lambda state: can_kill_most_things(state, player, 5) and (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))
+ lambda state: state.has('Big Key (Eastern Palace)', player) and can_kill_most_things(state, player, 1))
set_rule(world.get_location('Eastern Palace - Big Chest', player),
lambda state: state.has('Big Key (Eastern Palace)', player))
+ # not bothering to check for can_kill_most_things in the rooms leading to boss, as if you can kill a boss you should
+ # be able to get through these rooms
ep_boss = world.get_location('Eastern Palace - Boss', player)
- set_rule(ep_boss, lambda state: state.has('Big Key (Eastern Palace)', player) and
+ add_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
+ add_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))
add_rule(ep_prize, lambda state: can_shoot_arrows(state, player))
+ # You can always kill the Stalfos' with the pots on easy/normal
+ if world.enemy_health[player] in ("hard", "expert") or world.enemy_shuffle[player]:
+ stalfos_rule = lambda state: can_kill_most_things(state, player, 4)
+ for location in ['Eastern Palace - Compass Chest', 'Eastern Palace - Big Chest',
+ 'Eastern Palace - Dark Square Pot Key', 'Eastern Palace - Dark Eyegore Key Drop',
+ 'Eastern Palace - Big Key Chest', 'Eastern Palace - Boss', 'Eastern Palace - Prize']:
+ add_rule(world.get_location(location, player), stalfos_rule)
+
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, 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))
+ set_rule(world.get_location('Desert Palace - Big Key Chest', player), lambda state: can_kill_most_things(state, player, 3))
+ 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, 4))
+ 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, 4))
+ add_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))
+ add_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]):
+ if not (world.small_key_shuffle[player] and world.big_key_shuffle[player]):
add_rule(world.get_location('Desert Palace - Prize', player), lambda state: state.multiworld.get_region('Desert Palace Main (Outer)', player).can_reach(state))
set_rule(world.get_entrance('Tower of Hera Small Key Door', player), lambda state: state._lttp_has_key('Small Key (Tower of Hera)', player) or location_item_name(state, 'Tower of Hera - Big Key Chest', player) == ('Small Key (Tower of Hera)', player))
set_rule(world.get_entrance('Tower of Hera Big Key Door', player), lambda state: state.has('Big Key (Tower of Hera)', player))
+ if world.enemy_shuffle[player]:
+ add_rule(world.get_entrance('Tower of Hera Big Key Door', player), lambda state: can_kill_most_things(state, player, 3))
+ else:
+ add_rule(world.get_entrance('Tower of Hera Big Key Door', player),
+ lambda state: (has_melee_weapon(state, player) or (state.has('Silver Bow', player)
+ and can_shoot_arrows(state, player)) or state.has("Cane of Byrna", player)
+ or state.has("Cane of Somaria", player)))
set_rule(world.get_location('Tower of Hera - Big Chest', player), lambda state: state.has('Big Key (Tower of Hera)', player))
set_rule(world.get_location('Tower of Hera - Big Key Chest', player), lambda state: has_fire_source(state, player))
if world.accessibility[player] != 'locations':
@@ -300,9 +369,13 @@ 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_location('Swamp Palace - Map Chest', player), lambda state: can_use_bombs(state, 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))
+ if world.pot_shuffle[player]:
+ # it could move the key to the top right platform which can only be reached with bombs
+ add_rule(world.get_location('Swamp Palace - Hookshot Pot Key', player), lambda state: can_use_bombs(state, 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))
@@ -310,20 +383,23 @@ def global_rules(world, 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) 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']:
+ if not world.small_key_shuffle[player] and world.glitches_required[player] not in ['hybrid_major_glitches', 'no_logic']:
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))
+ if world.pot_shuffle[player]:
+ # key can (and probably will) be moved behind bombable wall
+ set_rule(world.get_location('Swamp Palace - Waterway Pot Key', player), lambda state: can_use_bombs(state, player))
set_rule(world.get_entrance('Thieves Town Big Key Door', player), lambda state: state.has('Big Key (Thieves Town)', 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_entrance('Blind Fight', player), lambda state: state._lttp_has_key('Small Key (Thieves Town)', player, 3) and can_use_bombs(state, player))
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))
+ lambda state: ((state._lttp_has_key('Small Key (Thieves Town)', player, 3)) or (location_item_name(state, 'Thieves\' Town - Big Chest', player) == ("Small Key (Thieves Town)", player)) and state._lttp_has_key('Small Key (Thieves Town)', player, 2)) 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_always_allow(world.get_location('Thieves\' Town - Big Chest', player), lambda state, item: item.name == 'Small Key (Thieves Town)' and item.player == player)
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),
@@ -334,7 +410,7 @@ def global_rules(world, 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, 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))
+ set_rule(world.get_location('Skull Woods - Big Chest', player), lambda state: state.has('Big Key (Skull Woods)', player) and can_use_bombs(state, 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, 4) and state.has('Fire Rod', player) and has_sword(state, player)) # sword required for curtain
@@ -342,7 +418,9 @@ def global_rules(world, player):
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_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_location('Ice Palace - Compass Chest', 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 (Second Section)', player), lambda state: can_melt_things(state, player) and state._lttp_has_key('Small Key (Ice Palace)', player) and can_use_bombs(state, 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, 6) or (state.has('Cane of Somaria', player) and state._lttp_has_key('Small Key (Ice Palace)', player, 5))))
@@ -387,16 +465,21 @@ def global_rules(world, 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('Misery Mire (Vitreous)', player), lambda state: state.has('Cane of Somaria', player) and can_use_bombs(state, 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 - Pokey 1 Key Drop', player), lambda state: can_kill_most_things(state, player, 5))
+ set_rule(world.get_location('Turtle Rock - Pokey 2 Key Drop', player), lambda state: can_kill_most_things(state, player, 5))
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)))
set_rule(world.get_entrance('Turtle Rock (Big Chest) (North)', player), lambda state: state.has('Cane of Somaria', player) or state.has('Hookshot', player))
- set_rule(world.get_entrance('Turtle Rock Big Key Door', player), lambda state: state.has('Big Key (Turtle Rock)', player))
+ set_rule(world.get_entrance('Turtle Rock Big Key Door', player), lambda state: state.has('Big Key (Turtle Rock)', player) and can_kill_most_things(state, player, 10))
+ set_rule(world.get_entrance('Turtle Rock Ledge Exit (West)', player), lambda state: can_use_bombs(state, player) and can_kill_most_things(state, player, 10))
+ set_rule(world.get_location('Turtle Rock - Chain Chomps', player), lambda state: can_use_bombs(state, player) or can_shoot_arrows(state, player)
+ or has_beam_sword(state, player) or state.has_any(["Blue Boomerang", "Red Boomerang", "Hookshot", "Cane of Somaria", "Fire Rod", "Ice Rod"], player))
set_rule(world.get_entrance('Turtle Rock (Dark Room) (North)', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_entrance('Turtle Rock (Dark Room) (South)', player), lambda state: state.has('Cane of Somaria', player))
set_rule(world.get_location('Turtle Rock - Eye Bridge - Bottom Left', player), lambda state: state.has('Cane of Byrna', player) or state.has('Cape', player) or state.has('Mirror Shield', player))
@@ -405,16 +488,22 @@ def global_rules(world, 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, 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))
+ if world.enemy_shuffle[player]:
+ set_rule(world.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: can_bomb_or_bonk(state, player) and can_kill_most_things(state, player, 3))
+ else:
+ set_rule(world.get_entrance('Palace of Darkness Bonk Wall', player), lambda state: can_bomb_or_bonk(state, player) and can_shoot_arrows(state, player))
set_rule(world.get_entrance('Palace of Darkness Hammer Peg Drop', player), lambda state: state.has('Hammer', player))
set_rule(world.get_entrance('Palace of Darkness Bridge Room', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 1)) # If we can reach any other small key door, we already have back door access to this area
set_rule(world.get_entrance('Palace of Darkness Big Key Door', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) and state.has('Big Key (Palace of Darkness)', player) and can_shoot_arrows(state, player) and state.has('Hammer', player))
set_rule(world.get_entrance('Palace of Darkness (North)', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 4))
- set_rule(world.get_location('Palace of Darkness - Big Chest', player), lambda state: state.has('Big Key (Palace of Darkness)', player))
+ set_rule(world.get_location('Palace of Darkness - Big Chest', player), lambda state: can_use_bombs(state, player) and state.has('Big Key (Palace of Darkness)', player))
+ set_rule(world.get_location('Palace of Darkness - The Arena - Ledge', player), lambda state: can_use_bombs(state, player))
+ if world.pot_shuffle[player]:
+ # chest switch may be up on ledge where bombs are required
+ set_rule(world.get_location('Palace of Darkness - Stalfos Basement', player), lambda state: can_use_bombs(state, player))
- set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase', player), lambda state: state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) or (
- location_item_name(state, 'Palace of Darkness - Big Key Chest', player) in [('Small Key (Palace of Darkness)', player)] and state._lttp_has_key('Small Key (Palace of Darkness)', player, 3)))
+ set_rule(world.get_entrance('Palace of Darkness Big Key Chest Staircase', player), lambda state: can_use_bombs(state, player) and (state._lttp_has_key('Small Key (Palace of Darkness)', player, 6) or (
+ location_item_name(state, 'Palace of Darkness - Big Key Chest', player) in [('Small Key (Palace of Darkness)', player)] and state._lttp_has_key('Small Key (Palace of Darkness)', player, 3))))
if world.accessibility[player] != 'locations':
set_always_allow(world.get_location('Palace of Darkness - Big Key Chest', player), lambda state, item: item.name == 'Small Key (Palace of Darkness)' and item.player == player and state._lttp_has_key('Small Key (Palace of Darkness)', player, 5))
@@ -430,13 +519,9 @@ def global_rules(world, player):
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)))
- 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)))
@@ -465,17 +550,17 @@ def global_rules(world, player):
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, 7) or (
+ set_rule(world.get_location(location, player), lambda state: (can_use_bombs(state, player) or state.has("Cane of Somaria", player)) and 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))
set_rule(world.get_location('Ganons Tower - Big Key Room - Left', player),
- lambda state: state.multiworld.get_location('Ganons Tower - Big Key Room - Left', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
+ lambda state: can_use_bombs(state, player) and state.multiworld.get_location('Ganons Tower - Big Key Room - Left', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
set_rule(world.get_location('Ganons Tower - Big Key Chest', player),
- lambda state: state.multiworld.get_location('Ganons Tower - Big Key Chest', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
+ lambda state: can_use_bombs(state, player) and state.multiworld.get_location('Ganons Tower - Big Key Chest', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
set_rule(world.get_location('Ganons Tower - Big Key Room - Right', player),
- lambda state: state.multiworld.get_location('Ganons Tower - Big Key Room - Right', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
+ lambda state: can_use_bombs(state, player) and state.multiworld.get_location('Ganons Tower - Big Key Room - Right', player).parent_region.dungeon.bosses['bottom'].can_defeat(state))
if world.enemy_shuffle[player]:
set_rule(world.get_entrance('Ganons Tower Big Key Door', player),
lambda state: state.has('Big Key (Ganons Tower)', player))
@@ -483,7 +568,8 @@ def global_rules(world, player):
set_rule(world.get_entrance('Ganons Tower Big Key Door', player),
lambda state: state.has('Big Key (Ganons Tower)', player) and can_shoot_arrows(state, 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))
+ lambda state: can_kill_most_things(state, player, 8) and 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 - Mini Helmasaur Key Drop', player), lambda state: can_kill_most_things(state, player, 1))
set_rule(world.get_location('Ganons Tower - Pre-Moldorm Chest', player),
lambda state: state._lttp_has_key('Small Key (Ganons Tower)', player, 7))
set_rule(world.get_entrance('Ganons Tower Moldorm Door', player),
@@ -493,9 +579,9 @@ def global_rules(world, player):
set_defeat_dungeon_boss_rule(world.get_location('Agahnim 2', player))
ganon = world.get_location('Ganon', player)
set_rule(ganon, lambda state: GanonDefeatRule(state, player))
- if world.goal[player] in ['ganontriforcehunt', 'localganontriforcehunt']:
+ if world.goal[player] in ['ganon_triforce_hunt', 'local_ganon_triforce_hunt']:
add_rule(ganon, lambda state: has_triforce_pieces(state, player))
- elif world.goal[player] == 'ganonpedestal':
+ elif world.goal[player] == 'ganon_pedestal':
add_rule(world.get_location('Ganon', player), lambda state: state.can_reach('Master Sword Pedestal', 'Location', player))
else:
add_rule(ganon, lambda state: has_crystals(state, state.multiworld.crystals_needed_for_ganon[player], player))
@@ -507,6 +593,12 @@ def global_rules(world, player):
def default_rules(world, player):
"""Default world rules when world state is not inverted."""
# overworld requirements
+
+ set_rule(world.get_entrance('Light World Bomb Hut', player), lambda state: can_use_bombs(state, player))
+ set_rule(world.get_entrance('Light Hype Fairy', player), lambda state: can_use_bombs(state, player))
+ set_rule(world.get_entrance('Mini Moldorm Cave', player), lambda state: can_use_bombs(state, player))
+ set_rule(world.get_entrance('Ice Rod Cave', player), lambda state: can_use_bombs(state, player))
+
set_rule(world.get_entrance('Kings Grave', player), lambda state: state.has('Pegasus Boots', player))
set_rule(world.get_entrance('Kings Grave Outer Rocks', player), lambda state: can_lift_heavy_rocks(state, player))
set_rule(world.get_entrance('Kings Grave Inner Rocks', player), lambda state: can_lift_heavy_rocks(state, player))
@@ -562,12 +654,12 @@ def default_rules(world, player):
set_rule(world.get_entrance('Dark Lake Hylia Drop (East)', player), lambda state: (state.has('Moon Pearl', player) and state.has('Flippers', player) or state.has('Magic Mirror', player))) # Overworld Bunny Revival
set_rule(world.get_location('Bombos Tablet', player), lambda state: can_retrieve_tablet(state, player))
set_rule(world.get_entrance('Dark Lake Hylia Drop (South)', player), lambda state: state.has('Moon Pearl', player) and state.has('Flippers', player)) # ToDo any fake flipper set up?
- set_rule(world.get_entrance('Dark Lake Hylia Ledge Fairy', player), lambda state: state.has('Moon Pearl', player)) # bomb required
+ set_rule(world.get_entrance('Dark Lake Hylia Ledge Fairy', player), lambda state: state.has('Moon Pearl', player) and can_use_bombs(state, player))
set_rule(world.get_entrance('Dark Lake Hylia Ledge Spike Cave', player), lambda state: can_lift_rocks(state, player) and state.has('Moon Pearl', player))
set_rule(world.get_entrance('Dark Lake Hylia Teleporter', player), lambda state: state.has('Moon Pearl', player))
set_rule(world.get_entrance('Village of Outcasts Heavy Rock', player), lambda state: state.has('Moon Pearl', player) and can_lift_heavy_rocks(state, player))
- set_rule(world.get_entrance('Hype Cave', player), lambda state: state.has('Moon Pearl', player)) # bomb required
- set_rule(world.get_entrance('Brewery', player), lambda state: state.has('Moon Pearl', player)) # bomb required
+ set_rule(world.get_entrance('Hype Cave', player), lambda state: state.has('Moon Pearl', player) and can_use_bombs(state, player))
+ set_rule(world.get_entrance('Brewery', player), lambda state: state.has('Moon Pearl', player) and can_use_bombs(state, player))
set_rule(world.get_entrance('Thieves Town', player), lambda state: state.has('Moon Pearl', player)) # bunny cannot pull
set_rule(world.get_entrance('Skull Woods First Section Hole (North)', player), lambda state: state.has('Moon Pearl', player)) # bunny cannot lift bush
set_rule(world.get_entrance('Skull Woods Second Section Hole', player), lambda state: state.has('Moon Pearl', player)) # bunny cannot lift bush
@@ -621,9 +713,9 @@ def inverted_rules(world, player):
# overworld requirements
set_rule(world.get_location('Maze Race', player), lambda state: state.has('Moon Pearl', player))
- set_rule(world.get_entrance('Mini Moldorm Cave', player), lambda state: state.has('Moon Pearl', player))
- set_rule(world.get_entrance('Ice Rod Cave', player), lambda state: state.has('Moon Pearl', player))
- set_rule(world.get_entrance('Light Hype Fairy', player), lambda state: state.has('Moon Pearl', player))
+ set_rule(world.get_entrance('Mini Moldorm Cave', player), lambda state: state.has('Moon Pearl', player) and can_use_bombs(state, player))
+ set_rule(world.get_entrance('Ice Rod Cave', player), lambda state: state.has('Moon Pearl', player) and can_use_bombs(state, player))
+ set_rule(world.get_entrance('Light Hype Fairy', player), lambda state: state.has('Moon Pearl', player) and can_use_bombs(state, player))
set_rule(world.get_entrance('Potion Shop Pier', player), lambda state: state.has('Flippers', player) and state.has('Moon Pearl', player))
set_rule(world.get_entrance('Light World Pier', player), lambda state: state.has('Flippers', player) and state.has('Moon Pearl', player))
set_rule(world.get_entrance('Kings Grave', player), lambda state: state.has('Pegasus Boots', player) and state.has('Moon Pearl', player))
@@ -669,7 +761,7 @@ def inverted_rules(world, player):
set_rule(world.get_entrance('Bush Covered Lawn Outer Bushes', player), lambda state: state.has('Moon Pearl', player))
set_rule(world.get_entrance('Bomb Hut Inner Bushes', player), lambda state: state.has('Moon Pearl', player))
set_rule(world.get_entrance('Bomb Hut Outer Bushes', player), lambda state: state.has('Moon Pearl', player))
- set_rule(world.get_entrance('Light World Bomb Hut', player), lambda state: state.has('Moon Pearl', player)) # need bomb
+ set_rule(world.get_entrance('Light World Bomb Hut', player), lambda state: state.has('Moon Pearl', player) and can_use_bombs(state, player))
set_rule(world.get_entrance('North Fairy Cave Drop', player), lambda state: state.has('Moon Pearl', player))
set_rule(world.get_entrance('Lost Woods Hideout Drop', player), lambda state: state.has('Moon Pearl', player))
set_rule(world.get_location('Potion Shop', player), lambda state: state.has('Mushroom', player) and (state.can_reach('Potion Shop Area', 'Region', player))) # new inverted region, need pearl for bushes or access to potion shop door/waterfall fairy
@@ -715,6 +807,11 @@ def inverted_rules(world, player):
set_rule(world.get_entrance('Bumper Cave Exit (Top)', player), lambda state: state.has('Cape', player))
set_rule(world.get_entrance('Bumper Cave Exit (Bottom)', player), lambda state: state.has('Cape', player) or state.has('Hookshot', player))
+ set_rule(world.get_entrance('Hype Cave', player), lambda state: can_use_bombs(state, player))
+ set_rule(world.get_entrance('Brewery', player), lambda state: can_use_bombs(state, player))
+ set_rule(world.get_entrance('Dark Lake Hylia Ledge Fairy', player), lambda state: can_use_bombs(state, player))
+
+
set_rule(world.get_entrance('Skull Woods Final Section', player), lambda state: state.has('Fire Rod', player))
set_rule(world.get_entrance('Misery Mire', player), lambda state: has_sword(state, player) and has_misery_mire_medallion(state, player)) # sword required to cast magic (!)
@@ -900,20 +997,25 @@ def add_conditional_lamp(spot, region, spottype='Location', accessible_torch=Fal
def open_rules(world, player):
+
+ set_rule(world.get_location('Hyrule Castle - Map Guard Key Drop', player),
+ lambda state: can_kill_most_things(state, player, 1))
+
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 Guard Key Drop', player),
+ lambda state: basement_key_rule(state) and can_kill_most_things(state, player, 2))
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))
+ lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 3) and can_kill_most_things(state, player, 1))
set_rule(world.get_location('Hyrule Castle - Big Key Drop', player),
- lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4))
+ lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) and can_kill_most_things(state, player, 1))
set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 4) and
state.has('Big Key (Hyrule Castle)', player))
@@ -924,6 +1026,7 @@ def swordless_rules(world, player):
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_location('Ice Palace - Jelly Key Drop', player), lambda state: state.has('Fire Rod', player) or state.has('Bombos', player))
+ set_rule(world.get_location('Ice Palace - Compass Chest', 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('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
@@ -954,7 +1057,7 @@ def standard_rules(world, 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:
+ if world.small_key_shuffle[player] != small_key_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),
@@ -968,6 +1071,9 @@ def standard_rules(world, player):
set_rule(world.get_location('Sewers - Key Rat Key Drop', player),
lambda state: state._lttp_has_key('Small Key (Hyrule Castle)', player, 3))
+ else:
+ set_rule(world.get_location('Hyrule Castle - Zelda\'s Chest', player),
+ lambda state: state.has('Big Key (Hyrule Castle)', player))
def toss_junk_item(world, player):
items = ['Rupees (20)', 'Bombs (3)', 'Arrows (10)', 'Rupees (5)', 'Rupee (1)', 'Bombs (10)',
@@ -1059,15 +1165,15 @@ def tr_big_key_chest_keys_needed(state):
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]:
+ if not can_reach_front and not world.small_key_shuffle[player]:
# Must not go in the Big Key Chest - only 1 other chest available and 2+ keys required for all other chests
forbid_item(world.get_location('Turtle Rock - Big Key Chest', player), 'Big Key (Turtle Rock)', 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:
+ if world.accessibility[player] == 'locations':
+ if world.big_key_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',
@@ -1512,8 +1618,10 @@ def set_bunny_rules(world: MultiWorld, player: int, inverted: bool):
# regions for the exits of multi-entrance caves/drops that bunny cannot pass
# Note spiral cave and two brothers house are passable in superbunny state for glitch logic with extra requirements.
- bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave', 'Skull Woods First Section (Right)', 'Skull Woods First Section (Left)', 'Skull Woods First Section (Top)', 'Turtle Rock (Entrance)', 'Turtle Rock (Second Section)', 'Turtle Rock (Big Chest)', 'Skull Woods Second Section (Drop)',
- 'Turtle Rock (Eye Bridge)', 'Sewers', 'Pyramid', 'Spiral Cave (Top)', 'Desert Palace Main (Inner)', 'Fairy Ascension Cave (Drop)']
+ bunny_impassable_caves = ['Bumper Cave', 'Two Brothers House', 'Hookshot Cave', 'Skull Woods First Section (Right)',
+ 'Skull Woods First Section (Left)', 'Skull Woods First Section (Top)', 'Turtle Rock (Entrance)', 'Turtle Rock (Second Section)',
+ 'Turtle Rock (Big Chest)', 'Skull Woods Second Section (Drop)', 'Turtle Rock (Eye Bridge)', 'Sewers', 'Pyramid',
+ 'Spiral Cave (Top)', 'Desert Palace Main (Inner)', 'Fairy Ascension Cave (Drop)']
bunny_accessible_locations = ['Link\'s Uncle', 'Sahasrahla', 'Sick Kid', 'Lost Woods Hideout', 'Lumberjack Tree',
'Checkerboard Cave', 'Potion Shop', 'Spectacle Rock Cave', 'Pyramid',
@@ -1546,7 +1654,7 @@ def is_link(region):
def get_rule_to_add(region, location = None, connecting_entrance = None):
# In OWG, a location can potentially be superbunny-mirror accessible or
# bunny revival accessible.
- if world.logic[player] in ['minorglitches', 'owglitches', 'hybridglitches', 'nologic']:
+ if world.glitches_required[player] in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic']:
if region.name == 'Swamp Palace (Entrance)': # Need to 0hp revive - not in logic
return lambda state: state.has('Moon Pearl', player)
if region.name == 'Tower of Hera (Bottom)': # Need to hit the crystal switch
@@ -1586,7 +1694,7 @@ def get_rule_to_add(region, location = None, connecting_entrance = None):
seen.add(new_region)
if not is_link(new_region):
# For glitch rulesets, establish superbunny and revival rules.
- if world.logic[player] in ['minorglitches', 'owglitches', 'hybridglitches', 'nologic'] and entrance.name not in OverworldGlitchRules.get_invalid_bunny_revival_dungeons():
+ if world.glitches_required[player] in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'] and entrance.name not in OverworldGlitchRules.get_invalid_bunny_revival_dungeons():
if region.name in OverworldGlitchRules.get_sword_required_superbunny_mirror_regions():
possible_options.append(lambda state: path_to_access_rule(new_path, entrance) and state.has('Magic Mirror', player) and has_sword(state, player))
elif (region.name in OverworldGlitchRules.get_boots_required_superbunny_mirror_regions()
@@ -1623,7 +1731,7 @@ def get_rule_to_add(region, location = None, connecting_entrance = None):
# Add requirements for all locations that are actually in the dark world, except those available to the bunny, including dungeon revival
for entrance in world.get_entrances(player):
if is_bunny(entrance.connected_region):
- if world.logic[player] in ['minorglitches', 'owglitches', 'hybridglitches', 'nologic'] :
+ if world.glitches_required[player] in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'] :
if entrance.connected_region.type == LTTPRegionType.Dungeon:
if entrance.parent_region.type != LTTPRegionType.Dungeon and entrance.connected_region.name in OverworldGlitchRules.get_invalid_bunny_revival_dungeons():
add_rule(entrance, get_rule_to_add(entrance.connected_region, None, entrance))
@@ -1631,7 +1739,7 @@ def get_rule_to_add(region, location = None, connecting_entrance = None):
if entrance.connected_region.name == 'Turtle Rock (Entrance)':
add_rule(world.get_entrance('Turtle Rock Entrance Gap', player), get_rule_to_add(entrance.connected_region, None, entrance))
for location in entrance.connected_region.locations:
- if world.logic[player] in ['minorglitches', 'owglitches', 'hybridglitches', 'nologic'] and entrance.name in OverworldGlitchRules.get_invalid_mirror_bunny_entrances():
+ if world.glitches_required[player] in ['minor_glitches', 'overworld_glitches', 'hybrid_major_glitches', 'no_logic'] and entrance.name in OverworldGlitchRules.get_invalid_mirror_bunny_entrances():
continue
if location.name in bunny_accessible_locations:
continue
diff --git a/worlds/alttp/Shops.py b/worlds/alttp/Shops.py
index c0f2e2236e69..64a385a18587 100644
--- a/worlds/alttp/Shops.py
+++ b/worlds/alttp/Shops.py
@@ -5,11 +5,14 @@
from Utils import int16_as_bytes
+from worlds.generic.Rules import add_rule
+
+from BaseClasses import CollectionState
from .SubClasses import ALttPLocation
from .EntranceShuffle import door_addresses
-from .Items import item_name_groups, item_table, ItemFactory, trap_replaceable, GetBeemizerItem
-from .Options import smallkey_shuffle
-
+from .Items import item_name_groups
+from .Options import small_key_shuffle, RandomizeShopInventories
+from .StateHelpers import has_hearts, can_use_bombs, can_hold_arrows
logger = logging.getLogger("Shops")
@@ -36,9 +39,9 @@ class ShopPriceType(IntEnum):
Item = 10
-class Shop():
+class Shop:
slots: int = 3 # slot count is not dynamic in asm, however inventory can have None as empty slots
- blacklist: Set[str] = set() # items that don't work, todo: actually check against this
+ blacklist: Set[str] = set() # items that don't work
type = ShopType.Shop
slot_names: Dict[int, str] = {
0: " Left",
@@ -103,7 +106,7 @@ def clear_inventory(self):
self.inventory = [None] * self.slots
def add_inventory(self, slot: int, item: str, price: int, max: int = 0,
- replacement: Optional[str] = None, replacement_price: int = 0, create_location: bool = False,
+ replacement: Optional[str] = None, replacement_price: int = 0,
player: int = 0, price_type: int = ShopPriceType.Rupees,
replacement_price_type: int = ShopPriceType.Rupees):
self.inventory[slot] = {
@@ -114,33 +117,23 @@ def add_inventory(self, slot: int, item: str, price: int, max: int = 0,
'replacement': replacement,
'replacement_price': replacement_price,
'replacement_price_type': replacement_price_type,
- 'create_location': create_location,
'player': player
}
def push_inventory(self, slot: int, item: str, price: int, max: int = 1, player: int = 0,
price_type: int = ShopPriceType.Rupees):
- if not self.inventory[slot]:
- raise ValueError("Inventory can't be pushed back if it doesn't exist")
-
- if not self.can_push_inventory(slot):
- logging.warning(f'Warning, there is already an item pushed into this slot.')
self.inventory[slot] = {
'item': item,
'price': price,
'price_type': price_type,
'max': max,
- 'replacement': self.inventory[slot]["item"],
- 'replacement_price': self.inventory[slot]["price"],
- 'replacement_price_type': self.inventory[slot]["price_type"],
- 'create_location': self.inventory[slot]["create_location"],
+ 'replacement': self.inventory[slot]["item"] if self.inventory[slot] else None,
+ 'replacement_price': self.inventory[slot]["price"] if self.inventory[slot] else 0,
+ 'replacement_price_type': self.inventory[slot]["price_type"] if self.inventory[slot] else ShopPriceType.Rupees,
'player': player
}
- def can_push_inventory(self, slot: int):
- return self.inventory[slot] and not self.inventory[slot]["replacement"]
-
class TakeAny(Shop):
type = ShopType.TakeAny
@@ -156,6 +149,10 @@ class UpgradeShop(Shop):
# Potions break due to VRAM flags set in UpgradeShop.
# Didn't check for more things breaking as not much else can be shuffled here currently
blacklist = item_name_groups["Potions"]
+ slot_names: Dict[int, str] = {
+ 0: " Left",
+ 1: " Right"
+ }
shop_class_mapping = {ShopType.UpgradeShop: UpgradeShop,
@@ -163,191 +160,84 @@ class UpgradeShop(Shop):
ShopType.TakeAny: TakeAny}
-def FillDisabledShopSlots(world):
- shop_slots: Set[ALttPLocation] = {location for shop_locations in (shop.region.locations for shop in world.shops)
- for location in shop_locations
- if location.shop_slot is not None and location.shop_slot_disabled}
- for location in shop_slots:
- location.shop_slot_disabled = True
- shop: Shop = location.parent_region.shop
- location.item = ItemFactory(shop.inventory[location.shop_slot]['item'], location.player)
- location.item_rule = lambda item: item.name == location.item.name and item.player == location.player
- location.locked = True
+def push_shop_inventories(multiworld):
+ shop_slots = [location for shop_locations in (shop.region.locations for shop in multiworld.shops if shop.type
+ != ShopType.TakeAny) for location in shop_locations if location.shop_slot is not None]
+ for location in shop_slots:
+ item_name = location.item.name
+ # Retro Bow arrows will already have been pushed
+ if (not multiworld.retro_bow[location.player]) or ((item_name, location.item.player)
+ != ("Single Arrow", location.player)):
+ location.shop.push_inventory(location.shop_slot, item_name, location.shop_price,
+ 1, location.item.player if location.item.player != location.player else 0,
+ location.shop_price_type)
+ location.shop_price = location.shop.inventory[location.shop_slot]["price"] = min(location.shop_price,
+ get_price(multiworld, location.shop.inventory[location.shop_slot], location.player,
+ location.shop_price_type)[1])
-def ShopSlotFill(multiworld):
- shop_slots: Set[ALttPLocation] = {location for shop_locations in
- (shop.region.locations for shop in multiworld.shops if shop.type != ShopType.TakeAny)
- for location in shop_locations if location.shop_slot is not None}
- removed = set()
- for location in shop_slots:
- shop: Shop = location.parent_region.shop
- if not shop.can_push_inventory(location.shop_slot) or location.shop_slot_disabled:
- location.shop_slot_disabled = True
- removed.add(location)
-
- if removed:
- shop_slots -= removed
-
- if shop_slots:
- logger.info("Filling LttP Shop Slots")
- del shop_slots
-
- from Fill import swap_location_item
- # TODO: allow each game to register a blacklist to be used here?
- blacklist_words = {"Rupee"}
- blacklist_words = {item_name for item_name in item_table if any(
- blacklist_word in item_name for blacklist_word in blacklist_words)}
- blacklist_words.add("Bee")
-
- locations_per_sphere = [sorted(sphere, key=lambda location: (location.name, location.player))
- for sphere in multiworld.get_spheres()]
-
- # currently special care needs to be taken so that Shop.region.locations.item is identical to Shop.inventory
- # Potentially create Locations as needed and make inventory the only source, to prevent divergence
- cumu_weights = []
- shops_per_sphere = []
- candidates_per_sphere = []
-
- # sort spheres into piles of valid candidates and shops
- for sphere in locations_per_sphere:
- current_shops_slots = []
- current_candidates = []
- shops_per_sphere.append(current_shops_slots)
- candidates_per_sphere.append(current_candidates)
- for location in sphere:
- if isinstance(location, ALttPLocation) and location.shop_slot is not None:
- if not location.shop_slot_disabled:
- current_shops_slots.append(location)
- elif not location.locked and location.item.name not in blacklist_words:
- current_candidates.append(location)
- if cumu_weights:
- x = cumu_weights[-1]
- else:
- x = 0
- cumu_weights.append(len(current_candidates) + x)
-
- multiworld.random.shuffle(current_candidates)
-
- del locations_per_sphere
-
- for i, current_shop_slots in enumerate(shops_per_sphere):
- if current_shop_slots:
- # getting all candidates and shuffling them feels cpu expensive, there may be a better method
- candidates = [(location, i) for i, candidates in enumerate(candidates_per_sphere[i:], start=i)
- for location in candidates]
- multiworld.random.shuffle(candidates)
- for location in current_shop_slots:
- shop: Shop = location.parent_region.shop
- for index, (c, swapping_sphere_id) in enumerate(candidates): # chosen item locations
- if c.item_rule(location.item) and location.item_rule(c.item):
- swap_location_item(c, location, check_locked=False)
- logger.debug(f"Swapping {c} into {location}:: {location.item}")
- # remove candidate
- candidates_per_sphere[swapping_sphere_id].remove(c)
- candidates.pop(index)
- break
-
- else:
- # This *should* never happen. But let's fail safely just in case.
- logger.warning("Ran out of ShopShuffle Item candidate locations.")
- location.shop_slot_disabled = True
- continue
-
- item_name = location.item.name
- if location.item.game != "A Link to the Past":
- if location.item.advancement:
- price = multiworld.random.randrange(8, 56)
- elif location.item.useful:
- price = multiworld.random.randrange(4, 28)
- else:
- price = multiworld.random.randrange(2, 14)
- elif any(x in item_name for x in
- ['Compass', 'Map', 'Single Bomb', 'Single Arrow', 'Piece of Heart']):
- price = multiworld.random.randrange(1, 7)
- elif any(x in item_name for x in ['Arrow', 'Bomb', 'Clock']):
- price = multiworld.random.randrange(2, 14)
- elif any(x in item_name for x in ['Small Key', 'Heart']):
- price = multiworld.random.randrange(4, 28)
- else:
- price = multiworld.random.randrange(8, 56)
-
- shop.push_inventory(location.shop_slot, item_name,
- min(int(price * multiworld.shop_price_modifier[location.player] / 100) * 5, 9999), 1,
- location.item.player if location.item.player != location.player else 0)
- if 'P' in multiworld.shop_shuffle[location.player]:
- price_to_funny_price(multiworld, shop.inventory[location.shop_slot], location.player)
-
- FillDisabledShopSlots(multiworld)
-
-
-def create_shops(world, player: int):
- option = world.shop_shuffle[player]
+def create_shops(multiworld, player: int):
player_shop_table = shop_table.copy()
- if "w" in option:
+ if multiworld.include_witch_hut[player]:
player_shop_table["Potion Shop"] = player_shop_table["Potion Shop"]._replace(locked=False)
dynamic_shop_slots = total_dynamic_shop_slots + 3
else:
dynamic_shop_slots = total_dynamic_shop_slots
+ if multiworld.shuffle_capacity_upgrades[player]:
+ player_shop_table["Capacity Upgrade"] = player_shop_table["Capacity Upgrade"]._replace(locked=False)
- num_slots = min(dynamic_shop_slots, world.shop_item_slots[player])
+ num_slots = min(dynamic_shop_slots, multiworld.shop_item_slots[player])
single_purchase_slots: List[bool] = [True] * num_slots + [False] * (dynamic_shop_slots - num_slots)
- world.random.shuffle(single_purchase_slots)
+ multiworld.random.shuffle(single_purchase_slots)
- if 'g' in option or 'f' in option:
+ if multiworld.randomize_shop_inventories[player]:
default_shop_table = [i for l in
[shop_generation_types[x] for x in ['arrows', 'bombs', 'potions', 'shields', 'bottle'] if
- not world.retro_bow[player] or x != 'arrows'] for i in l]
- new_basic_shop = world.random.sample(default_shop_table, k=3)
- new_dark_shop = world.random.sample(default_shop_table, k=3)
+ not multiworld.retro_bow[player] or x != 'arrows'] for i in l]
+ new_basic_shop = multiworld.random.sample(default_shop_table, k=3)
+ new_dark_shop = multiworld.random.sample(default_shop_table, k=3)
for name, shop in player_shop_table.items():
typ, shop_id, keeper, custom, locked, items, sram_offset = shop
if not locked:
- new_items = world.random.sample(default_shop_table, k=3)
- if 'f' not in option:
+ new_items = multiworld.random.sample(default_shop_table, k=len(items))
+ if multiworld.randomize_shop_inventories[player] == RandomizeShopInventories.option_randomize_by_shop_type:
if items == _basic_shop_defaults:
new_items = new_basic_shop
elif items == _dark_world_shop_defaults:
new_items = new_dark_shop
- keeper = world.random.choice([0xA0, 0xC1, 0xFF])
+ keeper = multiworld.random.choice([0xA0, 0xC1, 0xFF])
player_shop_table[name] = ShopData(typ, shop_id, keeper, custom, locked, new_items, sram_offset)
- if world.mode[player] == "inverted":
+ if multiworld.mode[player] == "inverted":
# make sure that blue potion is available in inverted, special case locked = None; lock when done.
player_shop_table["Dark Lake Hylia Shop"] = \
player_shop_table["Dark Lake Hylia Shop"]._replace(items=_inverted_hylia_shop_defaults, locked=None)
- chance_100 = int(world.retro_bow[player]) * 0.25 + int(
- world.smallkey_shuffle[player] == smallkey_shuffle.option_universal) * 0.5
for region_name, (room_id, type, shopkeeper, custom, locked, inventory, sram_offset) in player_shop_table.items():
- region = world.get_region(region_name, player)
+ region = multiworld.get_region(region_name, player)
shop: Shop = shop_class_mapping[type](region, room_id, shopkeeper, custom, locked, sram_offset)
# special case: allow shop slots, but do not allow overwriting of base inventory behind them
if locked is None:
shop.locked = True
region.shop = shop
- world.shops.append(shop)
+ multiworld.shops.append(shop)
for index, item in enumerate(inventory):
shop.add_inventory(index, *item)
- if not locked and num_slots:
+ if not locked and (num_slots or type == ShopType.UpgradeShop):
slot_name = f"{region.name}{shop.slot_names[index]}"
loc = ALttPLocation(player, slot_name, address=shop_table_by_location[slot_name],
parent=region, hint_text="for sale")
+ loc.shop_price_type, loc.shop_price = get_price(multiworld, None, player)
+ loc.item_rule = lambda item, spot=loc: not any(i for i in price_blacklist[spot.shop_price_type] if i in item.name)
+ add_rule(loc, lambda state, spot=loc: shop_price_rules(state, player, spot))
+ loc.shop = shop
loc.shop_slot = index
- loc.locked = True
- if single_purchase_slots.pop():
- if world.goal[player] != 'icerodhunt':
- if world.random.random() < chance_100:
- additional_item = 'Rupees (100)'
- else:
- additional_item = 'Rupees (50)'
- else:
- additional_item = GetBeemizerItem(world, player, 'Nothing')
- loc.item = ItemFactory(additional_item, player)
- else:
- loc.item = ItemFactory(GetBeemizerItem(world, player, 'Nothing'), player)
+ if ((not (multiworld.shuffle_capacity_upgrades[player] and type == ShopType.UpgradeShop))
+ and not single_purchase_slots.pop()):
loc.shop_slot_disabled = True
- shop.region.locations.append(loc)
+ loc.locked = True
+ else:
+ shop.region.locations.append(loc)
class ShopData(NamedTuple):
@@ -387,9 +277,10 @@ class ShopData(NamedTuple):
SHOP_ID_START = 0x400000
shop_table_by_location_id = dict(enumerate(
- (f"{name}{Shop.slot_names[num]}" for name, shop_data in
- sorted(shop_table.items(), key=lambda item: item[1].sram_offset)
- for num in range(3)), start=SHOP_ID_START))
+ (f"{name}{UpgradeShop.slot_names[num]}" if shop_data.type == ShopType.UpgradeShop else
+ f"{name}{Shop.slot_names[num]}" for name, shop_data in sorted(shop_table.items(),
+ key=lambda item: item[1].sram_offset)
+ for num in range(2 if shop_data.type == ShopType.UpgradeShop else 3)), start=SHOP_ID_START))
shop_table_by_location_id[(SHOP_ID_START + total_shop_slots)] = "Old Man Sword Cave"
shop_table_by_location_id[(SHOP_ID_START + total_shop_slots + 1)] = "Take-Any #1"
@@ -409,114 +300,54 @@ class ShopData(NamedTuple):
}
-def set_up_shops(world, player: int):
+def set_up_shops(multiworld, player: int):
# TODO: move hard+ mode changes for shields here, utilizing the new shops
- if world.retro_bow[player]:
- rss = world.get_region('Red Shield Shop', player).shop
+ if multiworld.retro_bow[player]:
+ rss = multiworld.get_region('Red Shield Shop', player).shop
replacement_items = [['Red Potion', 150], ['Green Potion', 75], ['Blue Potion', 200], ['Bombs (10)', 50],
['Blue Shield', 50], ['Small Heart',
10]] # Can't just replace the single arrow with 10 arrows as retro doesn't need them.
- if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal:
+ if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal:
replacement_items.append(['Small Key (Universal)', 100])
- replacement_item = world.random.choice(replacement_items)
+ replacement_item = multiworld.random.choice(replacement_items)
rss.add_inventory(2, 'Single Arrow', 80, 1, replacement_item[0], replacement_item[1])
rss.locked = True
- if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal or world.retro_bow[player]:
- for shop in world.random.sample([s for s in world.shops if
- s.custom and not s.locked and s.type == ShopType.Shop and s.region.player == player],
- 5):
+ if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal or multiworld.retro_bow[player]:
+ for shop in multiworld.random.sample([s for s in multiworld.shops if
+ s.custom and not s.locked and s.type == ShopType.Shop
+ and s.region.player == player], 5):
shop.locked = True
slots = [0, 1, 2]
- world.random.shuffle(slots)
+ multiworld.random.shuffle(slots)
slots = iter(slots)
- if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal:
+ if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal:
shop.add_inventory(next(slots), 'Small Key (Universal)', 100)
- if world.retro_bow[player]:
+ if multiworld.retro_bow[player]:
shop.push_inventory(next(slots), 'Single Arrow', 80)
-
-def shuffle_shops(world, items, player: int):
- option = world.shop_shuffle[player]
- if 'u' in option:
- progressive = world.progressive[player]
- progressive = world.random.choice([True, False]) if progressive == 'grouped_random' else progressive == 'on'
- progressive &= world.goal == 'icerodhunt'
- new_items = ["Bomb Upgrade (+5)"] * 6
- new_items.append("Bomb Upgrade (+5)" if progressive else "Bomb Upgrade (+10)")
-
- if not world.retro_bow[player]:
- new_items += ["Arrow Upgrade (+5)"] * 6
- new_items.append("Arrow Upgrade (+5)" if progressive else "Arrow Upgrade (+10)")
-
- world.random.shuffle(new_items) # Decide what gets tossed randomly if it can't insert everything.
-
- capacityshop: Optional[Shop] = None
- for shop in world.shops:
+ if multiworld.shuffle_capacity_upgrades[player]:
+ for shop in multiworld.shops:
if shop.type == ShopType.UpgradeShop and shop.region.player == player and \
shop.region.name == "Capacity Upgrade":
shop.clear_inventory()
- capacityshop = shop
-
- if world.goal[player] != 'icerodhunt':
- for i, item in enumerate(items):
- if item.name in trap_replaceable:
- items[i] = ItemFactory(new_items.pop(), player)
- if not new_items:
- break
- else:
- logging.warning(
- f"Not all upgrades put into Player{player}' item pool. Putting remaining items in Capacity Upgrade shop instead.")
- bombupgrades = sum(1 for item in new_items if 'Bomb Upgrade' in item)
- arrowupgrades = sum(1 for item in new_items if 'Arrow Upgrade' in item)
- slots = iter(range(2))
- if bombupgrades:
- capacityshop.add_inventory(next(slots), 'Bomb Upgrade (+5)', 100, bombupgrades)
- if arrowupgrades:
- capacityshop.add_inventory(next(slots), 'Arrow Upgrade (+5)', 100, arrowupgrades)
- else:
- for item in new_items:
- world.push_precollected(ItemFactory(item, player))
- if any(setting in option for setting in 'ipP'):
+ if (multiworld.shuffle_shop_inventories[player] or multiworld.randomize_shop_prices[player]
+ or multiworld.randomize_cost_types[player]):
shops = []
- upgrade_shops = []
total_inventory = []
- for shop in world.shops:
+ for shop in multiworld.shops:
if shop.region.player == player:
- if shop.type == ShopType.UpgradeShop:
- upgrade_shops.append(shop)
- elif shop.type == ShopType.Shop and not shop.locked:
+ if shop.type == ShopType.Shop and not shop.locked:
shops.append(shop)
total_inventory.extend(shop.inventory)
- if 'p' in option:
- def price_adjust(price: int) -> int:
- # it is important that a base price of 0 always returns 0 as new price!
- adjust = 2 if price < 100 else 5
- return int((price / adjust) * (0.5 + world.random.random() * 1.5)) * adjust
-
- def adjust_item(item):
- if item:
- item["price"] = price_adjust(item["price"])
- item['replacement_price'] = price_adjust(item["price"])
-
- for item in total_inventory:
- adjust_item(item)
- for shop in upgrade_shops:
- for item in shop.inventory:
- adjust_item(item)
-
- if 'P' in option:
- for item in total_inventory:
- price_to_funny_price(world, item, player)
- # Don't apply to upgrade shops
- # Upgrade shop is only one place, and will generally be too easy to
- # replenish hearts and bombs
-
- if 'i' in option:
- world.random.shuffle(total_inventory)
+ for item in total_inventory:
+ item["price_type"], item["price"] = get_price(multiworld, item, player)
+
+ if multiworld.shuffle_shop_inventories[player]:
+ multiworld.random.shuffle(total_inventory)
i = 0
for shop in shops:
@@ -539,16 +370,18 @@ def adjust_item(item):
}
price_chart = {
- ShopPriceType.Rupees: lambda p: p,
- ShopPriceType.Hearts: lambda p: min(5, p // 5) * 8, # Each heart is 0x8 in memory, Max of 5 hearts (20 total??)
- ShopPriceType.Magic: lambda p: min(15, p // 5) * 8, # Each pip is 0x8 in memory, Max of 15 pips (16 total...)
- ShopPriceType.Bombs: lambda p: max(1, min(10, p // 5)), # 10 Bombs max
- ShopPriceType.Arrows: lambda p: max(1, min(30, p // 5)), # 30 Arrows Max
- ShopPriceType.HeartContainer: lambda p: 0x8,
- ShopPriceType.BombUpgrade: lambda p: 0x1,
- ShopPriceType.ArrowUpgrade: lambda p: 0x1,
- ShopPriceType.Keys: lambda p: min(3, (p // 100) + 1), # Max of 3 keys for a price
- ShopPriceType.Potion: lambda p: (p // 5) % 5,
+ ShopPriceType.Rupees: lambda p, d: p,
+ # Each heart is 0x8 in memory, Max of 19 hearts on easy/normal, 9 on hard, 7 on expert
+ ShopPriceType.Hearts: lambda p, d: max(8, min([19, 19, 9, 7][d], p // 14) * 8),
+ # Each pip is 0x8 in memory, Max of 15 pips (16 total)
+ ShopPriceType.Magic: lambda p, d: max(8, min(15, p // 18) * 8),
+ ShopPriceType.Bombs: lambda p, d: max(1, min(50, p // 5)), # 50 Bombs max
+ ShopPriceType.Arrows: lambda p, d: max(1, min(70, p // 4)), # 70 Arrows Max
+ ShopPriceType.HeartContainer: lambda p, d: 0x8,
+ ShopPriceType.BombUpgrade: lambda p, d: 0x1,
+ ShopPriceType.ArrowUpgrade: lambda p, d: 0x1,
+ ShopPriceType.Keys: lambda p, d: max(1, min(3, (p // 90) + 1)), # Max of 3 keys for a price
+ ShopPriceType.Potion: lambda p, d: (p // 5) % 5,
}
price_type_display_name = {
@@ -557,6 +390,8 @@ def adjust_item(item):
ShopPriceType.Bombs: "Bombs",
ShopPriceType.Arrows: "Arrows",
ShopPriceType.Keys: "Keys",
+ ShopPriceType.Item: "Item",
+ ShopPriceType.Magic: "Magic"
}
# price division
@@ -565,57 +400,74 @@ def adjust_item(item):
ShopPriceType.Magic: 8,
}
-# prices with no? logic requirements
-simple_price_types = [
- ShopPriceType.Rupees,
- ShopPriceType.Hearts,
- ShopPriceType.Bombs,
- ShopPriceType.Arrows,
- ShopPriceType.Keys
-]
+
+def get_price_modifier(item):
+ if item.game == "A Link to the Past":
+ if any(x in item.name for x in
+ ['Compass', 'Map', 'Single Bomb', 'Single Arrow', 'Piece of Heart']):
+ return 0.125
+ elif any(x in item.name for x in
+ ['Arrow', 'Bomb', 'Clock']) and item.name != "Bombos" and "(50)" not in item.name:
+ return 0.25
+ elif any(x in item.name for x in ['Small Key', 'Heart']):
+ return 0.5
+ else:
+ return 1
+ if item.advancement:
+ return 1
+ elif item.useful:
+ return 0.5
+ else:
+ return 0.25
-def price_to_funny_price(world, item: dict, player: int):
- """
- Converts a raw Rupee price into a special price type
- """
+def get_price(multiworld, item, player: int, price_type=None):
+ """Converts a raw Rupee price into a special price type"""
+
+ if price_type:
+ price_types = [price_type]
+ else:
+ price_types = [ShopPriceType.Rupees] # included as a chance to not change price
+ if multiworld.randomize_cost_types[player]:
+ price_types += [
+ ShopPriceType.Hearts,
+ ShopPriceType.Bombs,
+ ShopPriceType.Magic,
+ ]
+ if multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal:
+ if item and item["item"] == "Small Key (Universal)":
+ price_types = [ShopPriceType.Rupees, ShopPriceType.Magic] # no logical requirements for repeatable keys
+ else:
+ price_types.append(ShopPriceType.Keys)
+ if multiworld.retro_bow[player]:
+ if item and item["item"] == "Single Arrow":
+ price_types = [ShopPriceType.Rupees, ShopPriceType.Magic] # no logical requirements for arrows
+ else:
+ price_types.append(ShopPriceType.Arrows)
+ diff = multiworld.item_pool[player].value
if item:
- price_types = [
- ShopPriceType.Rupees, # included as a chance to not change price type
- ShopPriceType.Hearts,
- ShopPriceType.Bombs,
- ]
- # don't pay in universal keys to get access to universal keys
- if world.smallkey_shuffle[player] == smallkey_shuffle.option_universal \
- and not "Small Key (Universal)" == item['replacement']:
- price_types.append(ShopPriceType.Keys)
- if not world.retro_bow[player]:
- price_types.append(ShopPriceType.Arrows)
- world.random.shuffle(price_types)
+ # This is for a shop's regular inventory, the item is already determined, and we will decide the price here
+ price = item["price"]
+ if multiworld.randomize_shop_prices[player]:
+ adjust = 2 if price < 100 else 5
+ price = int((price / adjust) * (0.5 + multiworld.random.random() * 1.5)) * adjust
+ multiworld.random.shuffle(price_types)
for p_type in price_types:
- # Ignore rupee prices
- if p_type == ShopPriceType.Rupees:
- return
if any(x in item['item'] for x in price_blacklist[p_type]):
continue
- else:
- item['price'] = min(price_chart[p_type](item['price']), 255)
- item['price_type'] = p_type
- break
-
-
-def create_dynamic_shop_locations(world, player):
- for shop in world.shops:
- if shop.region.player == player:
- for i, item in enumerate(shop.inventory):
- if item is None:
- continue
- if item['create_location']:
- slot_name = f"{shop.region.name}{shop.slot_names[i]}"
- loc = ALttPLocation(player, slot_name,
- address=shop_table_by_location[slot_name], parent=shop.region)
- loc.place_locked_item(ItemFactory(item['item'], player))
- if shop.type == ShopType.TakeAny:
- loc.shop_slot_disabled = True
- shop.region.locations.append(loc)
- loc.shop_slot = i
+ return p_type, price_chart[p_type](price, diff)
+ else:
+ # This is an AP location and the price will be adjusted after an item is shuffled into it
+ p_type = multiworld.random.choice(price_types)
+ return p_type, price_chart[p_type](min(int(multiworld.random.randint(8, 56)
+ * multiworld.shop_price_modifier[player] / 100) * 5, 9999), diff)
+
+
+def shop_price_rules(state: CollectionState, player: int, location: ALttPLocation):
+ if location.shop_price_type == ShopPriceType.Hearts:
+ return has_hearts(state, player, (location.shop_price / 8) + 1)
+ elif location.shop_price_type == ShopPriceType.Bombs:
+ return can_use_bombs(state, player, location.shop_price)
+ elif location.shop_price_type == ShopPriceType.Arrows:
+ return can_hold_arrows(state, player, location.shop_price)
+ return True
diff --git a/worlds/alttp/StateHelpers.py b/worlds/alttp/StateHelpers.py
index 38ce00ef4537..4ed1b1caf205 100644
--- a/worlds/alttp/StateHelpers.py
+++ b/worlds/alttp/StateHelpers.py
@@ -10,7 +10,7 @@ def is_not_bunny(state: CollectionState, region: LTTPRegion, player: int) -> boo
def can_bomb_clip(state: CollectionState, region: LTTPRegion, player: int) -> bool:
- return is_not_bunny(state, region, player) and state.has('Pegasus Boots', player)
+ return can_use_bombs(state, player) and is_not_bunny(state, region, player) and state.has('Pegasus Boots', player)
def can_buy_unlimited(state: CollectionState, item: str, player: int) -> bool:
@@ -83,13 +83,47 @@ def can_extend_magic(state: CollectionState, player: int, smallmagic: int = 16,
return basemagic >= smallmagic
+def can_hold_arrows(state: CollectionState, player: int, quantity: int):
+ arrows = 30 + ((state.count("Arrow Upgrade (+5)", player) * 5) + (state.count("Arrow Upgrade (+10)", player) * 10)
+ + (state.count("Bomb Upgrade (50)", player) * 50))
+ # Arrow Upgrade (+5) beyond the 6th gives +10
+ arrows += max(0, ((state.count("Arrow Upgrade (+5)", player) - 6) * 10))
+ return min(70, arrows) >= quantity
+
+
+def can_use_bombs(state: CollectionState, player: int, quantity: int = 1) -> bool:
+ bombs = 0 if state.multiworld.bombless_start[player] else 10
+ bombs += ((state.count("Bomb Upgrade (+5)", player) * 5) + (state.count("Bomb Upgrade (+10)", player) * 10)
+ + (state.count("Bomb Upgrade (50)", player) * 50))
+ # Bomb Upgrade (+5) beyond the 6th gives +10
+ bombs += max(0, ((state.count("Bomb Upgrade (+5)", player) - 6) * 10))
+ if (not state.multiworld.shuffle_capacity_upgrades[player]) and state.has("Capacity Upgrade Shop", player):
+ bombs += 40
+ return bombs >= min(quantity, 50)
+
+
+def can_bomb_or_bonk(state: CollectionState, player: int) -> bool:
+ return state.has("Pegasus Boots", player) or can_use_bombs(state, player)
+
+
def can_kill_most_things(state: CollectionState, player: int, enemies: int = 5) -> bool:
- return (has_melee_weapon(state, player)
- or state.has('Cane of Somaria', player)
- or (state.has('Cane of Byrna', player) and (enemies < 6 or can_extend_magic(state, player)))
- or can_shoot_arrows(state, player)
- or state.has('Fire Rod', player)
- or (state.has('Bombs (10)', player) and enemies < 6))
+ if state.multiworld.enemy_shuffle[player]:
+ # I don't fully understand Enemizer's logic for placing enemies in spots where they need to be killable, if any.
+ # Just go with maximal requirements for now.
+ return (has_melee_weapon(state, player)
+ and state.has('Cane of Somaria', player)
+ and state.has('Cane of Byrna', player) and can_extend_magic(state, player)
+ and can_shoot_arrows(state, player)
+ and state.has('Fire Rod', player)
+ and can_use_bombs(state, player, enemies * 4))
+ else:
+ return (has_melee_weapon(state, player)
+ or state.has('Cane of Somaria', player)
+ or (state.has('Cane of Byrna', player) and (enemies < 6 or can_extend_magic(state, player)))
+ or can_shoot_arrows(state, player)
+ or state.has('Fire Rod', player)
+ or (state.multiworld.enemy_health[player] in ("easy", "default")
+ and can_use_bombs(state, player, enemies * 4)))
def can_get_good_bee(state: CollectionState, player: int) -> bool:
@@ -159,4 +193,4 @@ def can_get_glitched_speed_dw(state: CollectionState, player: int) -> bool:
rules = [state.has('Pegasus Boots', player), any([state.has('Hookshot', player), has_sword(state, player)])]
if state.multiworld.mode[player] != 'inverted':
rules.append(state.has('Moon Pearl', player))
- return all(rules)
\ No newline at end of file
+ return all(rules)
diff --git a/worlds/alttp/SubClasses.py b/worlds/alttp/SubClasses.py
index 22eeebe181e5..769dcc199852 100644
--- a/worlds/alttp/SubClasses.py
+++ b/worlds/alttp/SubClasses.py
@@ -14,9 +14,12 @@ class ALttPLocation(Location):
crystal: bool
player_address: Optional[int]
_hint_text: Optional[str]
+ shop: None
shop_slot: Optional[int] = None
"""If given as integer, shop_slot is the shop's inventory index."""
shop_slot_disabled: bool = False
+ shop_price = 0
+ shop_price_type = None
parent_region: "LTTPRegion"
def __init__(self, player: int, name: str, address: Optional[int] = None, crystal: bool = False,
diff --git a/worlds/alttp/UnderworldGlitchRules.py b/worlds/alttp/UnderworldGlitchRules.py
index a6aefc74129a..497d5de496c3 100644
--- a/worlds/alttp/UnderworldGlitchRules.py
+++ b/worlds/alttp/UnderworldGlitchRules.py
@@ -42,7 +42,7 @@ def dungeon_reentry_rules(world, player, clip: Entrance, dungeon_region: str, du
fix_fake_worlds = world.fix_fake_world[player]
dungeon_entrance = [r for r in world.get_region(dungeon_region, player).entrances if r.name != clip.name][0]
- if not fix_dungeon_exits: # vanilla, simple, restricted, dungeonssimple; should never have fake worlds fix
+ if not fix_dungeon_exits: # vanilla, simple, restricted, dungeons_simple; should never have fake worlds fix
# Dungeons are only shuffled among themselves. We need to check SW, MM, and AT because they can't be reentered trivially.
if dungeon_entrance.name == 'Skull Woods Final Section':
set_rule(clip, lambda state: False) # entrance doesn't exist until you fire rod it from the other side
@@ -52,12 +52,12 @@ def dungeon_reentry_rules(world, player, clip: Entrance, dungeon_region: str, du
add_rule(clip, lambda state: state.has('Cape', player) or has_beam_sword(state, player) or state.has('Beat Agahnim 1', player)) # kill/bypass barrier
# Then we set a restriction on exiting the dungeon, so you can't leave unless you got in normally.
add_rule(world.get_entrance(dungeon_exit, player), lambda state: dungeon_entrance.can_reach(state))
- elif not fix_fake_worlds: # full, dungeonsfull; fixed dungeon exits, but no fake worlds fix
+ elif not fix_fake_worlds: # full, dungeons_full; fixed dungeon exits, but no fake worlds fix
# Entry requires the entrance's requirements plus a fake pearl, but you don't gain logical access to the surrounding region.
add_rule(clip, lambda state: dungeon_entrance.access_rule(fake_pearl_state(state, player)))
# exiting restriction
add_rule(world.get_entrance(dungeon_exit, player), lambda state: dungeon_entrance.can_reach(state))
- # Otherwise, the shuffle type is crossed, dungeonscrossed, or insanity; all of these do not need additional rules on where we can go,
+ # Otherwise, the shuffle type is crossed, dungeons_crossed, or insanity; all of these do not need additional rules on where we can go,
# since the clip links directly to the exterior region.
@@ -93,7 +93,7 @@ def underworld_glitches_rules(world, player):
# We need to be able to s+q to old man, then go to either Mire or Hera at either Hera or GT.
# First we require a certain type of entrance shuffle, then build the rule from its pieces.
if not world.swamp_patch_required[player]:
- if world.shuffle[player] in ['vanilla', 'dungeonssimple', 'dungeonsfull', 'dungeonscrossed']:
+ if world.entrance_shuffle[player] in ['vanilla', 'dungeons_simple', 'dungeons_full', 'dungeons_crossed']:
rule_map = {
'Misery Mire (Entrance)': (lambda state: True),
'Tower of Hera (Bottom)': (lambda state: state.can_reach('Tower of Hera Big Key Door', 'Entrance', player))
diff --git a/worlds/alttp/__init__.py b/worlds/alttp/__init__.py
index 3f380d0037a2..7a2664b3f4bc 100644
--- a/worlds/alttp/__init__.py
+++ b/worlds/alttp/__init__.py
@@ -13,14 +13,14 @@
from .InvertedRegions import create_inverted_regions, mark_dark_world_regions
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 .Options import alttp_options, small_key_shuffle
from .Regions import lookup_name_to_id, create_regions, mark_light_world_regions, lookup_vanilla_location_to_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
from .Rules import set_rules
-from .Shops import create_shops, Shop, ShopSlotFill, ShopType, price_rate_display, price_type_display_name
+from .Shops import create_shops, Shop, push_shop_inventories, ShopType, price_rate_display, price_type_display_name
from .SubClasses import ALttPItem, LTTPRegionType
from worlds.AutoWorld import World, WebWorld, LogicMixin
from .StateHelpers import can_buy_unlimited
@@ -42,7 +42,7 @@ class RomFile(settings.SNESRomPath):
class ALTTPWeb(WebWorld):
setup_en = Tutorial(
- "Multiworld Setup Tutorial",
+ "Multiworld Setup Guide",
"A guide to setting up the Archipelago ALttP Software on your computer. This guide covers single-player, multiworld, and related software.",
"English",
"multiworld_en.md",
@@ -78,7 +78,7 @@ class ALTTPWeb(WebWorld):
)
msu = Tutorial(
- "MSU-1 Setup Tutorial",
+ "MSU-1 Setup Guide",
"A guide to setting up MSU-1, which allows for custom in-game music.",
"English",
"msu1_en.md",
@@ -105,7 +105,7 @@ class ALTTPWeb(WebWorld):
)
plando = Tutorial(
- "Plando Tutorial",
+ "Plando Guide",
"A guide to creating Multiworld Plandos with LTTP",
"English",
"plando_en.md",
@@ -213,7 +213,7 @@ class ALTTPWorld(World):
item_name_to_id = {name: data.item_code for name, data in item_table.items() if type(data.item_code) == int}
location_name_to_id = lookup_name_to_id
- data_version = 8
+ data_version = 9
required_client_version = (0, 4, 1)
web = ALTTPWeb()
@@ -290,33 +290,34 @@ def generate_early(self):
self.pyramid_fairy_bottle_fill = self.random.choice(bottle_options)
if multiworld.mode[player] == 'standard':
- if multiworld.smallkey_shuffle[player]:
- if (multiworld.smallkey_shuffle[player] not in
- (smallkey_shuffle.option_universal, smallkey_shuffle.option_own_dungeons,
- smallkey_shuffle.option_start_with)):
+ if multiworld.small_key_shuffle[player]:
+ if (multiworld.small_key_shuffle[player] not in
+ (small_key_shuffle.option_universal, small_key_shuffle.option_own_dungeons,
+ small_key_shuffle.option_start_with)):
self.multiworld.local_early_items[self.player]["Small Key (Hyrule Castle)"] = 1
self.multiworld.local_items[self.player].value.add("Small Key (Hyrule Castle)")
self.multiworld.non_local_items[self.player].value.discard("Small Key (Hyrule Castle)")
- if multiworld.bigkey_shuffle[player]:
+ if multiworld.big_key_shuffle[player]:
self.multiworld.local_items[self.player].value.add("Big Key (Hyrule Castle)")
self.multiworld.non_local_items[self.player].value.discard("Big Key (Hyrule Castle)")
# system for sharing ER layouts
self.er_seed = str(multiworld.random.randint(0, 2 ** 64))
- if "-" in multiworld.shuffle[player]:
- shuffle, seed = multiworld.shuffle[player].split("-", 1)
- multiworld.shuffle[player] = shuffle
+ if multiworld.entrance_shuffle[player] != "vanilla" and multiworld.entrance_shuffle_seed[player] != "random":
+ shuffle = multiworld.entrance_shuffle[player].current_key
if shuffle == "vanilla":
self.er_seed = "vanilla"
- elif seed.startswith("group-") or multiworld.is_race:
+ elif (not multiworld.entrance_shuffle_seed[player].value.isdigit()) or multiworld.is_race:
self.er_seed = get_same_seed(multiworld, (
- shuffle, seed, multiworld.retro_caves[player], multiworld.mode[player], multiworld.logic[player]))
+ shuffle, multiworld.entrance_shuffle_seed[player].value, multiworld.retro_caves[player], multiworld.mode[player],
+ multiworld.glitches_required[player]))
else: # not a race or group seed, use set seed as is.
- self.er_seed = seed
- elif multiworld.shuffle[player] == "vanilla":
+ self.er_seed = int(multiworld.entrance_shuffle_seed[player].value)
+ elif multiworld.entrance_shuffle[player] == "vanilla":
self.er_seed = "vanilla"
- for dungeon_item in ["smallkey_shuffle", "bigkey_shuffle", "compass_shuffle", "map_shuffle"]:
+
+ for dungeon_item in ["small_key_shuffle", "big_key_shuffle", "compass_shuffle", "map_shuffle"]:
option = getattr(multiworld, dungeon_item)[player]
if option == "own_world":
multiworld.local_items[player].value |= self.item_name_groups[option.item_name_group]
@@ -329,10 +330,10 @@ def generate_early(self):
if option == "original_dungeon":
self.dungeon_specific_item_names |= self.item_name_groups[option.item_name_group]
- multiworld.difficulty_requirements[player] = difficulties[multiworld.difficulty[player]]
+ multiworld.difficulty_requirements[player] = difficulties[multiworld.item_pool[player].current_key]
# enforce pre-defined local items.
- if multiworld.goal[player] in ["localtriforcehunt", "localganontriforcehunt"]:
+ if multiworld.goal[player] in ["local_triforce_hunt", "local_ganon_triforce_hunt"]:
multiworld.local_items[player].value.add('Triforce Piece')
# Not possible to place crystals outside boss prizes yet (might as well make it consistent with pendants too).
@@ -345,9 +346,6 @@ def create_regions(self):
player = self.player
world = self.multiworld
- world.triforce_pieces_available[player] = max(world.triforce_pieces_available[player],
- world.triforce_pieces_required[player])
-
if world.mode[player] != 'inverted':
create_regions(world, player)
else:
@@ -355,8 +353,8 @@ def create_regions(self):
create_shops(world, player)
self.create_dungeons()
- if world.logic[player] not in ["noglitches", "minorglitches"] and world.shuffle[player] in \
- {"vanilla", "dungeonssimple", "dungeonsfull", "simple", "restricted", "full"}:
+ if world.glitches_required[player] not in ["no_glitches", "minor_glitches"] and world.entrance_shuffle[player] in \
+ {"vanilla", "dungeons_simple", "dungeons_full", "simple", "restricted", "full"}:
world.fix_fake_world[player] = False
# seeded entrance shuffle
@@ -455,7 +453,7 @@ def collect_item(self, state: CollectionState, item: Item, remove=False):
if state.has('Silver Bow', item.player):
return
elif state.has('Bow', item.player) and (self.multiworld.difficulty_requirements[item.player].progressive_bow_limit >= 2
- or self.multiworld.logic[item.player] == 'noglitches'
+ or self.multiworld.glitches_required[item.player] == 'no_glitches'
or self.multiworld.swordless[item.player]): # modes where silver bow is always required for ganon
return 'Silver Bow'
elif self.multiworld.difficulty_requirements[item.player].progressive_bow_limit >= 1:
@@ -499,9 +497,9 @@ def pre_fill(self):
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:
+ if world.mode[player] == 'standard' and world.small_key_shuffle[player] \
+ and world.small_key_shuffle[player] != small_key_shuffle.option_universal and \
+ world.small_key_shuffle[player] != small_key_shuffle.option_own_dungeons:
world.local_early_items[player]["Small Key (Hyrule Castle)"] = 1
@classmethod
@@ -509,10 +507,9 @@ 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)
+ push_shop_inventories(world)
@property
def use_enemizer(self) -> bool:
@@ -579,7 +576,7 @@ def generate_output(self, output_directory: str):
@classmethod
def stage_extend_hint_information(cls, world, hint_data: typing.Dict[int, typing.Dict[int, str]]):
er_hint_data = {player: {} for player in world.get_game_players("A Link to the Past") if
- world.shuffle[player] != "vanilla" or world.retro_caves[player]}
+ world.entrance_shuffle[player] != "vanilla" or world.retro_caves[player]}
for region in world.regions:
if region.player in er_hint_data and region.locations:
@@ -645,9 +642,9 @@ def stage_fill_hook(cls, world, progitempool, usefulitempool, filleritempool, fi
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"}:
+ world.glitches_required[player] in {'overworld_glitches', 'hybrid_major_glitches', "no_logic"}:
pass
- elif 'triforcehunt' in world.goal[player] and ('local' in world.goal[player] or world.players == 1):
+ elif 'triforce_hunt' in world.goal[player].current_key and ('local' in world.goal[player].current_key or world.players == 1):
trash_counts[player] = world.random.randint(world.crystals_needed_for_gt[player] * 2,
world.crystals_needed_for_gt[player] * 4)
else:
@@ -681,35 +678,6 @@ def bool_to_text(variable: typing.Union[bool, str]) -> str:
return variable
return "Yes" if variable else "No"
- spoiler_handle.write('Logic: %s\n' % self.multiworld.logic[self.player])
- spoiler_handle.write('Dark Room Logic: %s\n' % self.multiworld.dark_room_logic[self.player])
- spoiler_handle.write('Mode: %s\n' % self.multiworld.mode[self.player])
- spoiler_handle.write('Goal: %s\n' % self.multiworld.goal[self.player])
- if "triforce" in self.multiworld.goal[self.player]: # triforce hunt
- spoiler_handle.write("Pieces available for Triforce: %s\n" %
- self.multiworld.triforce_pieces_available[self.player])
- spoiler_handle.write("Pieces required for Triforce: %s\n" %
- self.multiworld.triforce_pieces_required[self.player])
- spoiler_handle.write('Difficulty: %s\n' % self.multiworld.difficulty[self.player])
- spoiler_handle.write('Item Functionality: %s\n' % self.multiworld.item_functionality[self.player])
- spoiler_handle.write('Entrance Shuffle: %s\n' % self.multiworld.shuffle[self.player])
- if self.multiworld.shuffle[self.player] != "vanilla":
- spoiler_handle.write('Entrance Shuffle Seed %s\n' % self.er_seed)
- spoiler_handle.write('Shop inventory shuffle: %s\n' %
- bool_to_text("i" in self.multiworld.shop_shuffle[self.player]))
- spoiler_handle.write('Shop price shuffle: %s\n' %
- bool_to_text("p" in self.multiworld.shop_shuffle[self.player]))
- spoiler_handle.write('Shop upgrade shuffle: %s\n' %
- bool_to_text("u" in self.multiworld.shop_shuffle[self.player]))
- spoiler_handle.write('New Shop inventory: %s\n' %
- bool_to_text("g" in self.multiworld.shop_shuffle[self.player] or
- "f" in self.multiworld.shop_shuffle[self.player]))
- spoiler_handle.write('Custom Potion Shop: %s\n' %
- bool_to_text("w" in self.multiworld.shop_shuffle[self.player]))
- spoiler_handle.write('Enemy health: %s\n' % self.multiworld.enemy_health[self.player])
- spoiler_handle.write('Enemy damage: %s\n' % self.multiworld.enemy_damage[self.player])
- spoiler_handle.write('Prize shuffle %s\n' % self.multiworld.shuffle_prizes[self.player])
-
def write_spoiler(self, spoiler_handle: typing.TextIO) -> None:
player_name = self.multiworld.get_player_name(self.player)
spoiler_handle.write("\n\nMedallions:\n")
@@ -783,7 +751,7 @@ def build_shop_info(shop: Shop) -> typing.Dict[str, str]:
if item["replacement"] is None:
continue
shop_data["item_{}".format(index)] +=\
- f", {item['replacement']} - {item['replacement_price']}" \
+ f", {item['replacement']} - {item['replacement_price'] // price_rate_display.get(item['replacement_price_type'], 1)}" \
f" {price_type_display_name[item['replacement_price_type']]}"
return shop_data
@@ -796,10 +764,7 @@ def build_shop_info(shop: Shop) -> typing.Dict[str, str]:
item)))
def get_filler_item_name(self) -> str:
- if self.multiworld.goal[self.player] == "icerodhunt":
- item = "Nothing"
- else:
- item = self.multiworld.random.choice(extras_list)
+ item = self.multiworld.random.choice(extras_list)
return GetBeemizerItem(self.multiworld, self.player, item)
def get_pre_fill_items(self):
@@ -819,20 +784,20 @@ def fill_slot_data(self):
# for convenient auto-tracking of the generated settings and adjusting the tracker accordingly
slot_options = ["crystals_needed_for_gt", "crystals_needed_for_ganon", "open_pyramid",
- "bigkey_shuffle", "smallkey_shuffle", "compass_shuffle", "map_shuffle",
+ "big_key_shuffle", "small_key_shuffle", "compass_shuffle", "map_shuffle",
"progressive", "swordless", "retro_bow", "retro_caves", "shop_item_slots",
- "boss_shuffle", "pot_shuffle", "enemy_shuffle", "key_drop_shuffle"]
+ "boss_shuffle", "pot_shuffle", "enemy_shuffle", "key_drop_shuffle", "bombless_start",
+ "randomize_shop_inventories", "shuffle_shop_inventories", "shuffle_capacity_upgrades",
+ "entrance_shuffle", "dark_room_logic", "goal", "mode",
+ "triforce_pieces_mode", "triforce_pieces_percentage", "triforce_pieces_required",
+ "triforce_pieces_available", "triforce_pieces_extra",
+ ]
slot_data = {option_name: getattr(self.multiworld, option_name)[self.player].value for option_name in slot_options}
slot_data.update({
- 'mode': self.multiworld.mode[self.player],
- 'goal': self.multiworld.goal[self.player],
- 'dark_room_logic': self.multiworld.dark_room_logic[self.player],
'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],
}
)
return slot_data
@@ -849,8 +814,8 @@ def get_same_seed(world, seed_def: tuple) -> str:
class ALttPLogic(LogicMixin):
def _lttp_has_key(self, item, player, count: int = 1):
- if self.multiworld.logic[player] == 'nologic':
+ if self.multiworld.glitches_required[player] == 'no_logic':
return True
- if self.multiworld.smallkey_shuffle[player] == smallkey_shuffle.option_universal:
+ if self.multiworld.small_key_shuffle[player] == small_key_shuffle.option_universal:
return can_buy_unlimited(self, 'Small Key (Universal)', player)
return self.prog_items[player][item] >= count
diff --git a/worlds/alttp/test/dungeons/TestAgahnimsTower.py b/worlds/alttp/test/dungeons/TestAgahnimsTower.py
index 94e785485882..93c3f60463d0 100644
--- a/worlds/alttp/test/dungeons/TestAgahnimsTower.py
+++ b/worlds/alttp/test/dungeons/TestAgahnimsTower.py
@@ -7,30 +7,30 @@ def testTower(self):
self.starting_regions = ['Agahnims Tower']
self.run_tests([
["Castle Tower - Room 03", False, []],
- ["Castle Tower - Room 03", False, [], ['Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']],
+ ["Castle Tower - Room 03", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']],
["Castle Tower - Room 03", True, ['Progressive Sword']],
["Castle Tower - Dark Maze", False, []],
["Castle Tower - Dark Maze", False, [], ['Small Key (Agahnims Tower)']],
["Castle Tower - Dark Maze", False, [], ['Lamp']],
- ["Castle Tower - Dark Maze", False, [], ['Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']],
+ ["Castle Tower - Dark Maze", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', '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", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', '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, ['Small Key (Agahnims Tower)', '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']],
+ ["Castle Tower - Circle of Pots Key Drop", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', '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)', 'Small Key (Agahnims Tower)', 'Lamp']],
["Agahnim 1", False, []],
- ["Agahnim 1", False, ['Small Key (Agahnims Tower)'], ['Small Key (Agahnims Tower)']],
+ ["Agahnim 1", False, ['Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)']],
["Agahnim 1", False, [], ['Progressive Sword']],
["Agahnim 1", False, [], ['Lamp']],
- ["Agahnim 1", True, ['Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Lamp', 'Progressive Sword']],
+ ["Agahnim 1", True, ['Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Small Key (Agahnims Tower)', 'Lamp', 'Progressive Sword']],
])
\ No newline at end of file
diff --git a/worlds/alttp/test/dungeons/TestDarkPalace.py b/worlds/alttp/test/dungeons/TestDarkPalace.py
index e3974e777da3..3912fbd282d9 100644
--- a/worlds/alttp/test/dungeons/TestDarkPalace.py
+++ b/worlds/alttp/test/dungeons/TestDarkPalace.py
@@ -11,29 +11,37 @@ def testDarkPalace(self):
["Palace of Darkness - The Arena - Ledge", False, []],
["Palace of Darkness - The Arena - Ledge", False, [], ['Progressive Bow']],
- ["Palace of Darkness - The Arena - Ledge", True, ['Progressive Bow']],
+ ["Palace of Darkness - The Arena - Ledge", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Palace of Darkness - The Arena - Ledge", True, ['Progressive Bow', 'Bomb Upgrade (+5)']],
["Palace of Darkness - Map Chest", False, []],
["Palace of Darkness - Map Chest", False, [], ['Progressive Bow']],
- ["Palace of Darkness - Map Chest", True, ['Progressive Bow']],
+ ["Palace of Darkness - Map Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']],
+ ["Palace of Darkness - Map Chest", True, ['Progressive Bow', 'Bomb Upgrade (+5)']],
+ ["Palace of Darkness - Map Chest", True, ['Progressive Bow', 'Pegasus Boots']],
#Lower requirement for self-locking key
#No lower requirement when bow/hammer is out of logic
["Palace of Darkness - Big Key Chest", False, []],
["Palace of Darkness - Big Key Chest", False, [key]*5, [key]],
- ["Palace of Darkness - Big Key Chest", True, [key]*6],
+ ["Palace of Darkness - Big Key Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Palace of Darkness - Big Key Chest", True, [key]*6 + ['Bomb Upgrade (+5)']],
["Palace of Darkness - The Arena - Bridge", False, []],
["Palace of Darkness - The Arena - Bridge", False, [], [key, 'Progressive Bow']],
["Palace of Darkness - The Arena - Bridge", False, [], [key, 'Hammer']],
+ ["Palace of Darkness - The Arena - Bridge", False, [], [key, 'Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']],
["Palace of Darkness - The Arena - Bridge", True, [key]],
- ["Palace of Darkness - The Arena - Bridge", True, ['Progressive Bow', 'Hammer']],
+ ["Palace of Darkness - The Arena - Bridge", True, ['Progressive Bow', 'Hammer', 'Bomb Upgrade (+5)']],
+ ["Palace of Darkness - The Arena - Bridge", True, ['Progressive Bow', 'Hammer', 'Pegasus Boots']],
["Palace of Darkness - Stalfos Basement", False, []],
["Palace of Darkness - Stalfos Basement", False, [], [key, 'Progressive Bow']],
["Palace of Darkness - Stalfos Basement", False, [], [key, 'Hammer']],
+ ["Palace of Darkness - Stalfos Basement", False, [], [key, 'Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']],
["Palace of Darkness - Stalfos Basement", True, [key]],
- ["Palace of Darkness - Stalfos Basement", True, ['Progressive Bow', 'Hammer']],
+ ["Palace of Darkness - Stalfos Basement", True, ['Progressive Bow', 'Hammer', 'Bomb Upgrade (+5)']],
+ ["Palace of Darkness - Stalfos Basement", True, ['Progressive Bow', 'Hammer', 'Pegasus Boots']],
["Palace of Darkness - Compass Chest", False, []],
["Palace of Darkness - Compass Chest", False, [key]*3, [key]],
@@ -67,8 +75,9 @@ def testDarkPalace(self):
["Palace of Darkness - Big Chest", False, []],
["Palace of Darkness - Big Chest", False, [], ['Lamp']],
["Palace of Darkness - Big Chest", False, [], ['Big Key (Palace of Darkness)']],
+ ["Palace of Darkness - Big Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
["Palace of Darkness - Big Chest", False, [key]*5, [key]],
- ["Palace of Darkness - Big Chest", True, ['Lamp', 'Big Key (Palace of Darkness)'] + [key]*6],
+ ["Palace of Darkness - Big Chest", True, ['Bomb Upgrade (+5)', 'Lamp', 'Big Key (Palace of Darkness)'] + [key]*6],
["Palace of Darkness - Boss", False, []],
["Palace of Darkness - Boss", False, [], ['Lamp']],
diff --git a/worlds/alttp/test/dungeons/TestDesertPalace.py b/worlds/alttp/test/dungeons/TestDesertPalace.py
index 2d1951391177..58e441f94585 100644
--- a/worlds/alttp/test/dungeons/TestDesertPalace.py
+++ b/worlds/alttp/test/dungeons/TestDesertPalace.py
@@ -19,35 +19,35 @@ def testDesertPalace(self):
["Desert Palace - Compass Chest", False, []],
["Desert Palace - Compass Chest", False, [], ['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)']],
+ ["Desert Palace - Compass Chest", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)']],
+ ["Desert Palace - Compass Chest", True, ['Progressive Sword', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)']],
["Desert Palace - Big Key Chest", False, []],
["Desert Palace - Big Key Chest", False, [], ['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 - Big Key Chest", False, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)']],
+ ["Desert Palace - Big Key Chest", True, ['Progressive Sword', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', '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, ['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 - Beamos Hall Pot Key", True, ['Small Key (Desert Palace)', '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, ['Small Key (Desert Palace)', '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 - Desert Tiles 2 Pot Key", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Progressive Sword']],
["Desert Palace - Boss", False, []],
- ["Desert Palace - Boss", False, [], ['Small Key (Desert Palace)']],
+ ["Desert Palace - Boss", False, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)']],
["Desert Palace - Boss", False, [], ['Big Key (Desert Palace)']],
["Desert Palace - Boss", False, [], ['Lamp', 'Fire Rod']],
["Desert Palace - Boss", False, [], ['Progressive Sword', 'Hammer', 'Fire Rod', 'Ice Rod', 'Progressive Bow', 'Cane of Somaria', 'Cane of Byrna']],
- ["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', 'Cane of Somaria']],
- ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Cane of Byrna']],
+ ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Fire Rod']],
+ ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Progressive Sword']],
+ ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Hammer']],
+ ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Cane of Somaria']],
+ ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Lamp', 'Cane of Byrna']],
])
\ No newline at end of file
diff --git a/worlds/alttp/test/dungeons/TestDungeon.py b/worlds/alttp/test/dungeons/TestDungeon.py
index 8ca2791dcfe4..1f8288ace07f 100644
--- a/worlds/alttp/test/dungeons/TestDungeon.py
+++ b/worlds/alttp/test/dungeons/TestDungeon.py
@@ -14,6 +14,8 @@ def setUp(self):
self.starting_regions = [] # Where to start exploring
self.remove_exits = [] # Block dungeon exits
self.multiworld.difficulty_requirements[1] = difficulties['normal']
+ self.multiworld.bombless_start[1].value = True
+ self.multiworld.shuffle_capacity_upgrades[1].value = True
create_regions(self.multiworld, 1)
self.multiworld.worlds[1].create_dungeons()
create_shops(self.multiworld, 1)
diff --git a/worlds/alttp/test/dungeons/TestEasternPalace.py b/worlds/alttp/test/dungeons/TestEasternPalace.py
index 35c1b9928394..ee8b7a16246f 100644
--- a/worlds/alttp/test/dungeons/TestEasternPalace.py
+++ b/worlds/alttp/test/dungeons/TestEasternPalace.py
@@ -18,13 +18,13 @@ def testEastern(self):
["Eastern Palace - Big Key Chest", False, []],
["Eastern Palace - Big Key Chest", False, [], ['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)']],
+ ["Eastern Palace - Big Key Chest", True, ['Lamp', 'Small Key (Eastern Palace)', 'Small Key (Eastern Palace)', 'Progressive Sword']],
#@todo: Advanced?
["Eastern Palace - Boss", False, []],
["Eastern Palace - Boss", False, [], ['Lamp']],
["Eastern Palace - Boss", False, [], ['Progressive Bow']],
["Eastern Palace - Boss", False, [], ['Big Key (Eastern Palace)']],
- ["Eastern Palace - Boss", True, ['Lamp', 'Progressive Bow', 'Big Key (Eastern Palace)']]
+ ["Eastern Palace - Boss", False, ['Small Key (Eastern Palace)', 'Small Key (Eastern Palace)']],
+ ["Eastern Palace - Boss", True, ['Lamp', 'Small Key (Eastern Palace)', 'Small Key (Eastern Palace)', 'Progressive Bow', 'Big Key (Eastern Palace)']]
])
diff --git a/worlds/alttp/test/dungeons/TestGanonsTower.py b/worlds/alttp/test/dungeons/TestGanonsTower.py
index d22dc92b366f..1e70f580de4e 100644
--- a/worlds/alttp/test/dungeons/TestGanonsTower.py
+++ b/worlds/alttp/test/dungeons/TestGanonsTower.py
@@ -33,50 +33,46 @@ def testGanonsTower(self):
["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)', '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 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)', '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)', '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 - 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)', '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)', '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 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)', '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)', '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 - 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)', '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)', '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)', '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)', '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 - 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)', '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)', '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)', '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 - 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)', '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)', '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)', '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 - 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)', '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)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
["Ganons Tower - Tile Room", False, []],
["Ganons Tower - Tile Room", False, [], ['Cane of Somaria']],
@@ -85,34 +81,34 @@ def testGanonsTower(self):
["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)', '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)', '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)', '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)', '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)', '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)', '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)', '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)', '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)', '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 Chest", True, ['Bomb Upgrade (+5)', '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)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']],
+ ["Ganons Tower - Big Key Chest", True, ['Bomb Upgrade (+5)', '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)', '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)', '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 - Left", True, ['Bomb Upgrade (+5)', '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)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']],
+ ["Ganons Tower - Big Key Room - Left", True, ['Bomb Upgrade (+5)', '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)', '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)', '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 - Big Key Room - Right", True, ['Bomb Upgrade (+5)', '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)', 'Small Key (Ganons Tower)', 'Cane of Somaria', 'Fire Rod']],
+ ["Ganons Tower - Big Key Room - Right", True, ['Bomb Upgrade (+5)', '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)', 'Small Key (Ganons Tower)', 'Hookshot', 'Hammer']],
["Ganons Tower - Mini Helmasaur Room - Left", False, []],
["Ganons Tower - Mini Helmasaur Room - Left", False, [], ['Progressive Bow']],
@@ -132,8 +128,8 @@ def testGanonsTower(self):
["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)', '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 - 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)', '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)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod']],
["Ganons Tower - Validation Chest", False, []],
["Ganons Tower - Validation Chest", False, [], ['Hookshot']],
@@ -141,8 +137,8 @@ def testGanonsTower(self):
["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)', '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']],
+ ["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)', '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)', '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)', '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)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Small Key (Ganons Tower)', 'Fire Rod', 'Hookshot', 'Hammer']],
])
\ No newline at end of file
diff --git a/worlds/alttp/test/dungeons/TestIcePalace.py b/worlds/alttp/test/dungeons/TestIcePalace.py
index edc9f1fbae9e..868631587375 100644
--- a/worlds/alttp/test/dungeons/TestIcePalace.py
+++ b/worlds/alttp/test/dungeons/TestIcePalace.py
@@ -11,8 +11,9 @@ def testIcePalace(self):
["Ice Palace - Big Key Chest", False, [], ['Progressive Glove']],
["Ice Palace - Big Key Chest", False, [], ['Fire Rod', 'Bombos']],
["Ice Palace - Big Key Chest", False, [], ['Fire Rod', 'Progressive Sword']],
- ["Ice Palace - Big Key Chest", True, ['Progressive Glove', 'Fire Rod', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']],
- ["Ice Palace - Big Key Chest", True, ['Progressive Glove', 'Bombos', 'Progressive Sword', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']],
+ ["Ice Palace - Big Key Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Ice Palace - Big Key Chest", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Fire Rod', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']],
+ ["Ice Palace - Big Key Chest", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Bombos', 'Progressive Sword', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']],
#@todo: Change from item randomizer - Right side key door is only in logic if big key is in there
#["Ice Palace - Big Key Chest", True, ['Progressive Glove', 'Cane of Byrna', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']],
#["Ice Palace - Big Key Chest", True, ['Progressive Glove', 'Cane of Byrna', 'Bombos', 'Progressive Sword', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']],
@@ -22,16 +23,17 @@ def testIcePalace(self):
["Ice Palace - Compass Chest", False, []],
["Ice Palace - Compass Chest", False, [], ['Fire Rod', 'Bombos']],
["Ice Palace - Compass Chest", False, [], ['Fire Rod', 'Progressive Sword']],
- ["Ice Palace - Compass Chest", True, ['Fire Rod']],
- ["Ice Palace - Compass Chest", True, ['Bombos', 'Progressive Sword']],
+ ["Ice Palace - Compass Chest", True, ['Small Key (Ice Palace)', 'Fire Rod']],
+ ["Ice Palace - Compass Chest", True, ['Small Key (Ice Palace)', 'Bombos', 'Progressive Sword']],
["Ice Palace - Map Chest", False, []],
["Ice Palace - Map Chest", False, [], ['Hammer']],
["Ice Palace - Map Chest", False, [], ['Progressive Glove']],
["Ice Palace - Map Chest", False, [], ['Fire Rod', 'Bombos']],
["Ice Palace - Map Chest", False, [], ['Fire Rod', 'Progressive Sword']],
- ["Ice Palace - Map Chest", True, ['Progressive Glove', 'Fire Rod', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']],
- ["Ice Palace - Map Chest", True, ['Progressive Glove', 'Bombos', 'Progressive Sword', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']],
+ ["Ice Palace - Map Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Ice Palace - Map Chest", True, ['Small Key (Ice Palace)', 'Bomb Upgrade (+5)', 'Progressive Glove', 'Fire Rod', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']],
+ ["Ice Palace - Map Chest", True, ['Small Key (Ice Palace)', 'Bomb Upgrade (+5)', 'Progressive Glove', 'Bombos', 'Progressive Sword', 'Hammer', 'Hookshot', 'Small Key (Ice Palace)']],
#["Ice Palace - Map Chest", True, ['Progressive Glove', 'Cane of Byrna', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']],
#["Ice Palace - Map Chest", True, ['Progressive Glove', 'Cane of Byrna', 'Bombos', 'Progressive Sword', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']],
#["Ice Palace - Map Chest", True, ['Progressive Glove', 'Cape', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']],
@@ -40,8 +42,9 @@ def testIcePalace(self):
["Ice Palace - Spike Room", False, []],
["Ice Palace - Spike Room", False, [], ['Fire Rod', 'Bombos']],
["Ice Palace - Spike Room", False, [], ['Fire Rod', 'Progressive Sword']],
- ["Ice Palace - Spike Room", True, ['Fire Rod', 'Hookshot', 'Small Key (Ice Palace)']],
- ["Ice Palace - Spike Room", True, ['Bombos', 'Progressive Sword', 'Hookshot', 'Small Key (Ice Palace)']],
+ ["Ice Palace - Spike Room", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Ice Palace - Spike Room", True, ['Bomb Upgrade (+5)', 'Fire Rod', 'Hookshot', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']],
+ ["Ice Palace - Spike Room", True, ['Bomb Upgrade (+5)', 'Bombos', 'Progressive Sword', 'Hookshot', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']],
#["Ice Palace - Spike Room", True, ['Cape', 'Fire Rod', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']],
#["Ice Palace - Spike Room", True, ['Cape', 'Bombos', 'Progressive Sword', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']],
#["Ice Palace - Spike Room", True, ['Cane of Byrna', 'Fire Rod', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']],
@@ -50,21 +53,24 @@ def testIcePalace(self):
["Ice Palace - Freezor Chest", False, []],
["Ice Palace - Freezor Chest", False, [], ['Fire Rod', 'Bombos']],
["Ice Palace - Freezor Chest", False, [], ['Fire Rod', 'Progressive Sword']],
- ["Ice Palace - Freezor Chest", True, ['Fire Rod']],
- ["Ice Palace - Freezor Chest", True, ['Bombos', 'Progressive Sword']],
+ ["Ice Palace - Freezor Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Ice Palace - Freezor Chest", True, ['Bomb Upgrade (+5)', 'Fire Rod', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']],
+ ["Ice Palace - Freezor Chest", True, ['Bomb Upgrade (+5)', 'Bombos', 'Progressive Sword', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']],
["Ice Palace - Iced T Room", False, []],
["Ice Palace - Iced T Room", False, [], ['Fire Rod', 'Bombos']],
["Ice Palace - Iced T Room", False, [], ['Fire Rod', 'Progressive Sword']],
- ["Ice Palace - Iced T Room", True, ['Fire Rod']],
- ["Ice Palace - Iced T Room", True, ['Bombos', 'Progressive Sword']],
+ ["Ice Palace - Iced T Room", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Ice Palace - Iced T Room", True, ['Bomb Upgrade (+5)', 'Fire Rod', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']],
+ ["Ice Palace - Iced T Room", True, ['Bomb Upgrade (+5)', 'Bombos', 'Progressive Sword', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)']],
["Ice Palace - Big Chest", False, []],
["Ice Palace - Big Chest", False, [], ['Big Key (Ice Palace)']],
["Ice Palace - Big Chest", False, [], ['Fire Rod', 'Bombos']],
["Ice Palace - Big Chest", False, [], ['Fire Rod', 'Progressive Sword']],
- ["Ice Palace - Big Chest", True, ['Big Key (Ice Palace)', 'Fire Rod']],
- ["Ice Palace - Big Chest", True, ['Big Key (Ice Palace)', 'Bombos', 'Progressive Sword']],
+ ["Ice Palace - Big Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Ice Palace - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Fire Rod']],
+ ["Ice Palace - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Bombos', 'Progressive Sword']],
["Ice Palace - Boss", False, []],
["Ice Palace - Boss", False, [], ['Hammer']],
@@ -72,9 +78,10 @@ def testIcePalace(self):
["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", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
# 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']],
+ ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Fire Rod', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']],
+ ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Fire Rod', 'Hammer', 'Cane of Somaria', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']],
+ ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword', 'Hammer', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']],
+ ["Ice Palace - Boss", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Big Key (Ice Palace)', 'Bombos', 'Progressive Sword', 'Hammer', 'Cane of Somaria', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Small Key (Ice Palace)', 'Hookshot']],
])
\ No newline at end of file
diff --git a/worlds/alttp/test/dungeons/TestMiseryMire.py b/worlds/alttp/test/dungeons/TestMiseryMire.py
index ea5fb288450d..ca74e9365ee6 100644
--- a/worlds/alttp/test/dungeons/TestMiseryMire.py
+++ b/worlds/alttp/test/dungeons/TestMiseryMire.py
@@ -32,36 +32,32 @@ def testMiseryMire(self):
["Misery Mire - Main Lobby", False, []],
["Misery Mire - Main Lobby", False, [], ['Pegasus Boots', 'Hookshot']],
["Misery Mire - Main Lobby", False, [], ['Small Key (Misery Mire)', 'Big Key (Misery Mire)']],
- ["Misery Mire - Main Lobby", True, ['Small Key (Misery Mire)', 'Hookshot', 'Progressive Sword']],
- ["Misery Mire - Main Lobby", True, ['Small Key (Misery Mire)', 'Pegasus Boots', 'Progressive Sword']],
- ["Misery Mire - Main Lobby", True, ['Big Key (Misery Mire)', 'Hookshot', 'Progressive Sword']],
- ["Misery Mire - Main Lobby", True, ['Big Key (Misery Mire)', 'Pegasus Boots', 'Progressive Sword']],
+ ["Misery Mire - Main Lobby", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Hookshot', 'Progressive Sword']],
+ ["Misery Mire - Main Lobby", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Pegasus Boots', 'Progressive Sword']],
["Misery Mire - Big Key Chest", False, []],
["Misery Mire - Big Key Chest", False, [], ['Fire Rod', 'Lamp']],
["Misery Mire - Big Key Chest", False, [], ['Pegasus Boots', 'Hookshot']],
- ["Misery Mire - Big Key Chest", False, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)'], ['Small Key (Misery Mire)']],
- ["Misery Mire - Big Key Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Lamp', 'Progressive Sword', 'Pegasus Boots']],
- ["Misery Mire - Big Key Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Lamp', 'Progressive Sword', 'Hookshot']],
- ["Misery Mire - Big Key Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Fire Rod', 'Progressive Sword', 'Pegasus Boots']],
- ["Misery Mire - Big Key Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Fire Rod', 'Progressive Sword', 'Hookshot']],
+ ["Misery Mire - Big Key Chest", False, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)']],
+ ["Misery Mire - Big Key Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Lamp', 'Progressive Sword', 'Pegasus Boots']],
+ ["Misery Mire - Big Key Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Lamp', 'Progressive Sword', 'Hookshot']],
+ ["Misery Mire - Big Key Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Fire Rod', 'Progressive Sword', 'Pegasus Boots']],
+ ["Misery Mire - Big Key Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Fire Rod', 'Progressive Sword', 'Hookshot']],
["Misery Mire - Compass Chest", False, []],
["Misery Mire - Compass Chest", False, [], ['Fire Rod', 'Lamp']],
["Misery Mire - Compass Chest", False, [], ['Pegasus Boots', 'Hookshot']],
- ["Misery Mire - Compass Chest", False, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)'], ['Small Key (Misery Mire)']],
- ["Misery Mire - Compass Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Lamp', 'Progressive Sword', 'Pegasus Boots']],
- ["Misery Mire - Compass Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Lamp', 'Progressive Sword', 'Hookshot']],
- ["Misery Mire - Compass Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Fire Rod', 'Progressive Sword', 'Pegasus Boots']],
- ["Misery Mire - Compass Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Fire Rod', 'Progressive Sword', 'Hookshot']],
+ ["Misery Mire - Compass Chest", False, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)']],
+ ["Misery Mire - Compass Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Lamp', 'Progressive Sword', 'Pegasus Boots']],
+ ["Misery Mire - Compass Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Lamp', 'Progressive Sword', 'Hookshot']],
+ ["Misery Mire - Compass Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Fire Rod', 'Progressive Sword', 'Pegasus Boots']],
+ ["Misery Mire - Compass Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Fire Rod', 'Progressive Sword', 'Hookshot']],
["Misery Mire - Map Chest", False, []],
- ["Misery Mire - Map Chest", False, [], ['Small Key (Misery Mire)', 'Big Key (Misery Mire)']],
+ ["Misery Mire - Map Chest", False, [], ['Small Key (Misery Mire)']],
["Misery Mire - Map Chest", False, [], ['Pegasus Boots', 'Hookshot']],
- ["Misery Mire - Map Chest", True, ['Small Key (Misery Mire)', 'Progressive Sword', 'Pegasus Boots']],
- ["Misery Mire - Map Chest", True, ['Small Key (Misery Mire)', 'Progressive Sword', 'Hookshot']],
- ["Misery Mire - Map Chest", True, ['Big Key (Misery Mire)', 'Progressive Sword', 'Pegasus Boots']],
- ["Misery Mire - Map Chest", True, ['Big Key (Misery Mire)', 'Progressive Sword', 'Hookshot']],
+ ["Misery Mire - Map Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Progressive Sword', 'Pegasus Boots']],
+ ["Misery Mire - Map Chest", True, ['Small Key (Misery Mire)', 'Small Key (Misery Mire)', 'Progressive Sword', 'Hookshot']],
["Misery Mire - Spike Chest", False, []],
["Misery Mire - Spike Chest", False, [], ['Pegasus Boots', 'Hookshot']],
@@ -78,7 +74,8 @@ def testMiseryMire(self):
["Misery Mire - Boss", False, [], ['Progressive Sword', 'Hammer', 'Progressive Bow']],
["Misery Mire - Boss", False, [], ['Big Key (Misery Mire)']],
["Misery Mire - Boss", False, [], ['Pegasus Boots', 'Hookshot']],
- ["Misery Mire - Boss", True, ['Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Progressive Sword', 'Pegasus Boots']],
- ["Misery Mire - Boss", True, ['Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Hammer', 'Pegasus Boots']],
- ["Misery Mire - Boss", True, ['Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Progressive Bow', 'Pegasus Boots']],
+ ["Misery Mire - Boss", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Misery Mire - Boss", True, ['Bomb Upgrade (+5)', 'Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Progressive Sword', 'Pegasus Boots']],
+ ["Misery Mire - Boss", True, ['Bomb Upgrade (+5)', 'Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Hammer', 'Pegasus Boots']],
+ ["Misery Mire - Boss", True, ['Bomb Upgrade (+5)', 'Big Key (Misery Mire)', 'Lamp', 'Cane of Somaria', 'Progressive Bow', 'Pegasus Boots']],
])
\ No newline at end of file
diff --git a/worlds/alttp/test/dungeons/TestSkullWoods.py b/worlds/alttp/test/dungeons/TestSkullWoods.py
index 7f97c4d2f823..7650e785c871 100644
--- a/worlds/alttp/test/dungeons/TestSkullWoods.py
+++ b/worlds/alttp/test/dungeons/TestSkullWoods.py
@@ -8,7 +8,8 @@ def testSkullWoodsFrontAllEntrances(self):
self.run_tests([
["Skull Woods - Big Chest", False, []],
["Skull Woods - Big Chest", False, [], ['Big Key (Skull Woods)']],
- ["Skull Woods - Big Chest", True, ['Big Key (Skull Woods)']],
+ ["Skull Woods - Big Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Skull Woods - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Skull Woods)']],
["Skull Woods - Compass Chest", True, []],
@@ -64,7 +65,8 @@ def testSkullWoodsBackOnly(self):
self.run_tests([
["Skull Woods - Big Chest", False, []],
["Skull Woods - Big Chest", False, [], ['Big Key (Skull Woods)']],
- ["Skull Woods - Big Chest", True, ['Big Key (Skull Woods)']],
+ ["Skull Woods - Big Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Skull Woods - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Skull Woods)']],
["Skull Woods - Compass Chest", False, []],
["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)']],
@@ -94,6 +96,6 @@ def testSkullWoodsBack(self):
["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)'], ['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']],
+ ["Skull Woods - Boss", False, ['Small Key (Skull Woods)', '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)', 'Small Key (Skull Woods)', 'Fire Rod', 'Progressive Sword']],
])
\ No newline at end of file
diff --git a/worlds/alttp/test/dungeons/TestSwampPalace.py b/worlds/alttp/test/dungeons/TestSwampPalace.py
index 51440f6ccc4d..fb0672a5a9cc 100644
--- a/worlds/alttp/test/dungeons/TestSwampPalace.py
+++ b/worlds/alttp/test/dungeons/TestSwampPalace.py
@@ -16,35 +16,36 @@ def testSwampPalace(self):
["Swamp Palace - Big Chest", False, [], ['Open Floodgate']],
["Swamp Palace - Big Chest", False, [], ['Hammer']],
["Swamp Palace - Big Chest", False, [], ['Big Key (Swamp Palace)']],
- ["Swamp Palace - Big Chest", False, [], ['Small Key (Swamp Palace)']],
- ["Swamp Palace - Big Chest", True, ['Open Floodgate', 'Big Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']],
+ ["Swamp Palace - Big Chest", False, [], ['Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)']],
+ ["Swamp Palace - Big Chest", True, ['Open Floodgate', 'Big Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']],
["Swamp Palace - Big Key Chest", False, []],
["Swamp Palace - Big Key Chest", False, [], ['Flippers']],
["Swamp Palace - Big Key Chest", False, [], ['Open Floodgate']],
["Swamp Palace - Big Key Chest", False, [], ['Hammer']],
["Swamp Palace - Big Key Chest", False, [], ['Small Key (Swamp Palace)']],
- ["Swamp Palace - Big Key Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']],
+ ["Swamp Palace - Big Key Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']],
["Swamp Palace - Map Chest", False, []],
["Swamp Palace - Map Chest", False, [], ['Flippers']],
["Swamp Palace - Map Chest", False, [], ['Open Floodgate']],
["Swamp Palace - Map Chest", False, [], ['Small Key (Swamp Palace)']],
- ["Swamp Palace - Map Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers']],
+ ["Swamp Palace - Map Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Swamp Palace - Map Chest", True, ['Bomb Upgrade (+5)', 'Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers']],
["Swamp Palace - West Chest", False, []],
["Swamp Palace - West Chest", False, [], ['Flippers']],
["Swamp Palace - West Chest", False, [], ['Open Floodgate']],
["Swamp Palace - West Chest", False, [], ['Hammer']],
["Swamp Palace - West Chest", False, [], ['Small Key (Swamp Palace)']],
- ["Swamp Palace - West Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']],
+ ["Swamp Palace - West Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']],
["Swamp Palace - Compass Chest", False, []],
["Swamp Palace - Compass Chest", False, [], ['Flippers']],
["Swamp Palace - Compass Chest", False, [], ['Open Floodgate']],
["Swamp Palace - Compass Chest", False, [], ['Hammer']],
["Swamp Palace - Compass Chest", False, [], ['Small Key (Swamp Palace)']],
- ["Swamp Palace - Compass Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']],
+ ["Swamp Palace - Compass Chest", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer']],
["Swamp Palace - Flooded Room - Left", False, []],
["Swamp Palace - Flooded Room - Left", False, [], ['Flippers']],
@@ -52,7 +53,7 @@ def testSwampPalace(self):
["Swamp Palace - Flooded Room - Left", False, [], ['Hammer']],
["Swamp Palace - Flooded Room - Left", False, [], ['Hookshot']],
["Swamp Palace - Flooded Room - Left", False, [], ['Small Key (Swamp Palace)']],
- ["Swamp Palace - Flooded Room - Left", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']],
+ ["Swamp Palace - Flooded Room - Left", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']],
["Swamp Palace - Flooded Room - Right", False, []],
["Swamp Palace - Flooded Room - Right", False, [], ['Flippers']],
@@ -60,7 +61,7 @@ def testSwampPalace(self):
["Swamp Palace - Flooded Room - Right", False, [], ['Hammer']],
["Swamp Palace - Flooded Room - Right", False, [], ['Hookshot']],
["Swamp Palace - Flooded Room - Right", False, [], ['Small Key (Swamp Palace)']],
- ["Swamp Palace - Flooded Room - Right", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']],
+ ["Swamp Palace - Flooded Room - Right", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']],
["Swamp Palace - Waterfall Room", False, []],
["Swamp Palace - Waterfall Room", False, [], ['Flippers']],
@@ -68,7 +69,7 @@ def testSwampPalace(self):
["Swamp Palace - Waterfall Room", False, [], ['Hammer']],
["Swamp Palace - Waterfall Room", False, [], ['Hookshot']],
["Swamp Palace - Waterfall Room", False, [], ['Small Key (Swamp Palace)']],
- ["Swamp Palace - Waterfall Room", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']],
+ ["Swamp Palace - Waterfall Room", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']],
["Swamp Palace - Boss", False, []],
["Swamp Palace - Boss", False, [], ['Flippers']],
@@ -76,5 +77,5 @@ def testSwampPalace(self):
["Swamp Palace - Boss", False, [], ['Hammer']],
["Swamp Palace - Boss", False, [], ['Hookshot']],
["Swamp Palace - Boss", False, [], ['Small Key (Swamp Palace)']],
- ["Swamp Palace - Boss", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']],
+ ["Swamp Palace - Boss", True, ['Open Floodgate', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Small Key (Swamp Palace)', 'Flippers', 'Hammer', 'Hookshot']],
])
\ No newline at end of file
diff --git a/worlds/alttp/test/dungeons/TestThievesTown.py b/worlds/alttp/test/dungeons/TestThievesTown.py
index 01f1570a2581..342823c91007 100644
--- a/worlds/alttp/test/dungeons/TestThievesTown.py
+++ b/worlds/alttp/test/dungeons/TestThievesTown.py
@@ -21,18 +21,19 @@ def testThievesTown(self):
["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 - Spike Switch Pot Key", False, [], ['Small Key (Thieves Town)']],
+ ["Thieves' Town - Spike Switch Pot Key", True, ['Big Key (Thieves Town)', 'Small 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 - Attic", True, ['Big Key (Thieves Town)', 'Small Key (Thieves Town)', 'Small 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)']],
["Thieves' Town - Big Chest", False, [], ['Hammer']],
- ["Thieves' Town - Big Chest", True, ['Hammer', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)']],
+ ["Thieves' Town - Big Chest", True, ['Hammer', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)']],
["Thieves' Town - Blind's Cell", False, []],
["Thieves' Town - Blind's Cell", False, [], ['Big Key (Thieves Town)']],
@@ -41,8 +42,9 @@ def testThievesTown(self):
["Thieves' Town - Boss", False, []],
["Thieves' Town - Boss", False, [], ['Big 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']],
- ["Thieves' Town - Boss", True, ['Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Cane of Somaria']],
- ["Thieves' Town - Boss", True, ['Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Cane of Byrna']],
+ ["Thieves' Town - Boss", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Hammer']],
+ ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Progressive Sword']],
+ ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Cane of Somaria']],
+ ["Thieves' Town - Boss", True, ['Bomb Upgrade (+5)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Small Key (Thieves Town)', 'Big Key (Thieves Town)', 'Cane of Byrna']],
])
\ No newline at end of file
diff --git a/worlds/alttp/test/dungeons/TestTowerOfHera.py b/worlds/alttp/test/dungeons/TestTowerOfHera.py
index 04685a66a876..3299e20291b0 100644
--- a/worlds/alttp/test/dungeons/TestTowerOfHera.py
+++ b/worlds/alttp/test/dungeons/TestTowerOfHera.py
@@ -18,11 +18,11 @@ def testTowerOfHera(self):
["Tower of Hera - Compass Chest", False, []],
["Tower of Hera - Compass Chest", False, [], ['Big Key (Tower of Hera)']],
- ["Tower of Hera - Compass Chest", True, ['Big Key (Tower of Hera)']],
+ ["Tower of Hera - Compass Chest", True, ['Big Key (Tower of Hera)', 'Progressive Sword']],
["Tower of Hera - Big Chest", False, []],
["Tower of Hera - Big Chest", False, [], ['Big Key (Tower of Hera)']],
- ["Tower of Hera - Big Chest", True, ['Big Key (Tower of Hera)']],
+ ["Tower of Hera - Big Chest", True, ['Big Key (Tower of Hera)', 'Progressive Sword']],
["Tower of Hera - Boss", False, []],
["Tower of Hera - Boss", False, [], ['Big Key (Tower of Hera)']],
diff --git a/worlds/alttp/test/inverted/TestInverted.py b/worlds/alttp/test/inverted/TestInverted.py
index f5608ba07b2d..f2c585e46500 100644
--- a/worlds/alttp/test/inverted/TestInverted.py
+++ b/worlds/alttp/test/inverted/TestInverted.py
@@ -14,7 +14,9 @@ class TestInverted(TestBase, LTTPTestBase):
def setUp(self):
self.world_setup()
self.multiworld.difficulty_requirements[1] = difficulties['normal']
- self.multiworld.mode[1] = "inverted"
+ self.multiworld.mode[1].value = 2
+ self.multiworld.bombless_start[1].value = True
+ self.multiworld.shuffle_capacity_upgrades[1].value = True
create_inverted_regions(self.multiworld, 1)
self.multiworld.worlds[1].create_dungeons()
create_shops(self.multiworld, 1)
diff --git a/worlds/alttp/test/inverted/TestInvertedBombRules.py b/worlds/alttp/test/inverted/TestInvertedBombRules.py
index d9eacb5ad98b..83a25812c9b6 100644
--- a/worlds/alttp/test/inverted/TestInvertedBombRules.py
+++ b/worlds/alttp/test/inverted/TestInvertedBombRules.py
@@ -11,8 +11,8 @@ class TestInvertedBombRules(LTTPTestBase):
def setUp(self):
self.world_setup()
- self.multiworld.mode[1] = "inverted"
self.multiworld.difficulty_requirements[1] = difficulties['normal']
+ self.multiworld.mode[1].value = 2
create_inverted_regions(self.multiworld, 1)
self.multiworld.worlds[1].create_dungeons()
diff --git a/worlds/alttp/test/inverted/TestInvertedDarkWorld.py b/worlds/alttp/test/inverted/TestInvertedDarkWorld.py
index 710ee07f2b6d..16b837ee6556 100644
--- a/worlds/alttp/test/inverted/TestInvertedDarkWorld.py
+++ b/worlds/alttp/test/inverted/TestInvertedDarkWorld.py
@@ -5,7 +5,8 @@ class TestInvertedDarkWorld(TestInverted):
def testNorthWest(self):
self.run_location_tests([
- ["Brewery", True, []],
+ ["Brewery", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Brewery", True, ['Bomb Upgrade (+5)']],
["C-Shaped House", True, []],
@@ -77,15 +78,16 @@ def testNorthEast(self):
def testSouth(self):
self.run_location_tests([
- ["Hype Cave - Top", True, []],
-
- ["Hype Cave - Middle Right", True, []],
-
- ["Hype Cave - Middle Left", True, []],
-
- ["Hype Cave - Bottom", True, []],
-
- ["Hype Cave - Generous Guy", True, []],
+ ["Hype Cave - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Top", True, ['Bomb Upgrade (+5)']],
+ ["Hype Cave - Middle Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)']],
+ ["Hype Cave - Middle Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)']],
+ ["Hype Cave - Bottom", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)']],
+ ["Hype Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)']],
["Stumpy", True, []],
diff --git a/worlds/alttp/test/inverted/TestInvertedDeathMountain.py b/worlds/alttp/test/inverted/TestInvertedDeathMountain.py
index aedec2a1daa6..605a9dc3f3a9 100644
--- a/worlds/alttp/test/inverted/TestInvertedDeathMountain.py
+++ b/worlds/alttp/test/inverted/TestInvertedDeathMountain.py
@@ -40,10 +40,12 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Far Left", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']],
["Paradox Cave Lower - Far Left", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']],
["Paradox Cave Lower - Far Left", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']],
- ["Paradox Cave Lower - Far Left", True, ['Flute', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
- ["Paradox Cave Lower - Far Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
+ ["Paradox Cave Lower - Far Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Cane of Somaria', 'Fire Rod']],
+ ["Paradox Cave Lower - Far Left", True, ['Flute', 'Hookshot', 'Moon Pearl', 'Bomb Upgrade (+5)']],
+ ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl', 'Cane of Somaria']],
+ ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Progressive Sword', 'Progressive Sword']],
+ ["Paradox Cave Lower - Far Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Fire Rod']],
+ ["Paradox Cave Lower - Far Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Progressive Bow']],
["Paradox Cave Lower - Left", False, []],
["Paradox Cave Lower - Left", False, [], ['Moon Pearl']],
@@ -52,10 +54,12 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Left", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']],
["Paradox Cave Lower - Left", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']],
["Paradox Cave Lower - Left", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']],
- ["Paradox Cave Lower - Left", True, ['Flute', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
- ["Paradox Cave Lower - Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
+ ["Paradox Cave Lower - Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Cane of Somaria', 'Fire Rod']],
+ ["Paradox Cave Lower - Left", True, ['Flute', 'Hookshot', 'Moon Pearl', 'Bomb Upgrade (+5)']],
+ ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl', 'Cane of Somaria']],
+ ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Progressive Sword', 'Progressive Sword']],
+ ["Paradox Cave Lower - Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Fire Rod']],
+ ["Paradox Cave Lower - Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Progressive Bow']],
["Paradox Cave Lower - Middle", False, []],
["Paradox Cave Lower - Middle", False, [], ['Moon Pearl']],
@@ -64,10 +68,12 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Middle", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']],
["Paradox Cave Lower - Middle", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']],
["Paradox Cave Lower - Middle", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']],
- ["Paradox Cave Lower - Middle", True, ['Flute', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
- ["Paradox Cave Lower - Middle", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
+ ["Paradox Cave Lower - Middle", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Cane of Somaria', 'Fire Rod']],
+ ["Paradox Cave Lower - Middle", True, ['Flute', 'Hookshot', 'Moon Pearl', 'Bomb Upgrade (+5)']],
+ ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl', 'Cane of Somaria']],
+ ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Progressive Sword', 'Progressive Sword']],
+ ["Paradox Cave Lower - Middle", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Fire Rod']],
+ ["Paradox Cave Lower - Middle", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Progressive Bow']],
["Paradox Cave Lower - Right", False, []],
["Paradox Cave Lower - Right", False, [], ['Moon Pearl']],
@@ -76,10 +82,12 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Right", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']],
["Paradox Cave Lower - Right", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']],
["Paradox Cave Lower - Right", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']],
- ["Paradox Cave Lower - Right", True, ['Flute', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
- ["Paradox Cave Lower - Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
+ ["Paradox Cave Lower - Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Cane of Somaria', 'Fire Rod']],
+ ["Paradox Cave Lower - Right", True, ['Flute', 'Hookshot', 'Moon Pearl', 'Bomb Upgrade (+5)']],
+ ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl', 'Cane of Somaria']],
+ ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Progressive Sword', 'Progressive Sword']],
+ ["Paradox Cave Lower - Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Fire Rod']],
+ ["Paradox Cave Lower - Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Progressive Bow']],
["Paradox Cave Lower - Far Right", False, []],
["Paradox Cave Lower - Far Right", False, [], ['Moon Pearl']],
@@ -88,10 +96,12 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Far Right", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']],
["Paradox Cave Lower - Far Right", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']],
["Paradox Cave Lower - Far Right", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']],
- ["Paradox Cave Lower - Far Right", True, ['Flute', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
- ["Paradox Cave Lower - Far Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
+ ["Paradox Cave Lower - Far Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Cane of Somaria', 'Fire Rod']],
+ ["Paradox Cave Lower - Far Right", True, ['Flute', 'Hookshot', 'Moon Pearl', 'Bomb Upgrade (+5)']],
+ ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl', 'Cane of Somaria']],
+ ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Progressive Sword', 'Progressive Sword']],
+ ["Paradox Cave Lower - Far Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Fire Rod']],
+ ["Paradox Cave Lower - Far Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl', 'Progressive Bow']],
["Paradox Cave Upper - Left", False, []],
["Paradox Cave Upper - Left", False, [], ['Moon Pearl']],
@@ -100,10 +110,11 @@ def testEastDeathMountain(self):
["Paradox Cave Upper - Left", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']],
["Paradox Cave Upper - Left", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']],
["Paradox Cave Upper - Left", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']],
- ["Paradox Cave Upper - Left", True, ['Flute', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
- ["Paradox Cave Upper - Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
+ ["Paradox Cave Upper - Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot', 'Moon Pearl']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
["Paradox Cave Upper - Right", False, []],
["Paradox Cave Upper - Right", False, [], ['Moon Pearl']],
@@ -112,20 +123,22 @@ def testEastDeathMountain(self):
["Paradox Cave Upper - Right", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']],
["Paradox Cave Upper - Right", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']],
["Paradox Cave Upper - Right", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']],
- ["Paradox Cave Upper - Right", True, ['Flute', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
- ["Paradox Cave Upper - Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
+ ["Paradox Cave Upper - Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot', 'Moon Pearl']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
["Mimic Cave", False, []],
["Mimic Cave", False, [], ['Moon Pearl']],
["Mimic Cave", False, [], ['Hammer']],
["Mimic Cave", False, [], ['Progressive Glove', 'Flute']],
["Mimic Cave", False, [], ['Lamp', 'Flute']],
- ["Mimic Cave", True, ['Flute', 'Moon Pearl', 'Hammer', 'Hookshot']],
- ["Mimic Cave", True, ['Flute', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hammer']],
- ["Mimic Cave", True, ['Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer', 'Hookshot']],
- ["Mimic Cave", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer']],
+ ["Mimic Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Bow', 'Cane of Somaria', 'Progressive Sword']],
+ ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Flute', 'Moon Pearl', 'Hammer', 'Hookshot']],
+ ["Mimic Cave", True, ['Progressive Bow', 'Flute', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hammer']],
+ ["Mimic Cave", True, ['Cane of Somaria', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer', 'Hookshot']],
+ ["Mimic Cave", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer']],
["Ether Tablet", False, []],
["Ether Tablet", False, [], ['Moon Pearl']],
diff --git a/worlds/alttp/test/inverted/TestInvertedLightWorld.py b/worlds/alttp/test/inverted/TestInvertedLightWorld.py
index 9d4b9099daae..77af09317241 100644
--- a/worlds/alttp/test/inverted/TestInvertedLightWorld.py
+++ b/worlds/alttp/test/inverted/TestInvertedLightWorld.py
@@ -44,15 +44,17 @@ def testKakariko(self):
["Chicken House", False, []],
["Chicken House", False, [], ['Moon Pearl']],
- ["Chicken House", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Chicken House", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Chicken House", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Chicken House", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Chicken House", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Chicken House", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Chicken House", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
["Kakariko Well - Top", False, []],
["Kakariko Well - Top", False, [], ['Moon Pearl']],
- ["Kakariko Well - Top", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Kakariko Well - Top", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Kakariko Well - Top", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Kakariko Well - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Kakariko Well - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Kakariko Well - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Kakariko Well - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
["Kakariko Well - Left", False, []],
["Kakariko Well - Left", False, [], ['Moon Pearl']],
@@ -80,9 +82,10 @@ def testKakariko(self):
["Blind's Hideout - Top", False, []],
["Blind's Hideout - Top", False, [], ['Moon Pearl']],
- ["Blind's Hideout - Top", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Blind's Hideout - Top", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Blind's Hideout - Top", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Blind's Hideout - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Blind's Hideout - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Blind's Hideout - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Blind's Hideout - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
["Blind's Hideout - Left", False, []],
["Blind's Hideout - Left", False, [], ['Moon Pearl']],
@@ -161,9 +164,10 @@ def testKakariko(self):
["Maze Race", False, []],
["Maze Race", False, [], ['Moon Pearl']],
- ["Maze Race", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Maze Race", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Maze Race", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Maze Race", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']],
+ ["Maze Race", True, ['Pegasus Boots', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Maze Race", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Maze Race", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
])
def testSouthLightWorld(self):
@@ -184,9 +188,10 @@ def testSouthLightWorld(self):
["Aginah's Cave", False, []],
["Aginah's Cave", False, [], ['Moon Pearl']],
- ["Aginah's Cave", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Aginah's Cave", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Aginah's Cave", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Aginah's Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Aginah's Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Aginah's Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Aginah's Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
["Bombos Tablet", False, []],
["Bombos Tablet", False, ['Progressive Sword'], ['Progressive Sword']],
@@ -212,39 +217,45 @@ def testSouthLightWorld(self):
["Mini Moldorm Cave - Far Left", False, []],
["Mini Moldorm Cave - Far Left", False, [], ['Moon Pearl']],
- ["Mini Moldorm Cave - Far Left", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Mini Moldorm Cave - Far Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Mini Moldorm Cave - Far Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Mini Moldorm Cave - Far Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Far Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Sword']],
+ ["Mini Moldorm Cave - Far Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Mini Moldorm Cave - Far Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Progressive Sword']],
["Mini Moldorm Cave - Left", False, []],
["Mini Moldorm Cave - Left", False, [], ['Moon Pearl']],
- ["Mini Moldorm Cave - Left", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Mini Moldorm Cave - Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Mini Moldorm Cave - Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Mini Moldorm Cave - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Sword']],
+ ["Mini Moldorm Cave - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Mini Moldorm Cave - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Progressive Sword']],
["Mini Moldorm Cave - Generous Guy", False, []],
["Mini Moldorm Cave - Generous Guy", False, [], ['Moon Pearl']],
- ["Mini Moldorm Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Mini Moldorm Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Mini Moldorm Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Mini Moldorm Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Sword']],
+ ["Mini Moldorm Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Mini Moldorm Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Progressive Sword']],
["Mini Moldorm Cave - Right", False, []],
["Mini Moldorm Cave - Right", False, [], ['Moon Pearl']],
- ["Mini Moldorm Cave - Right", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Mini Moldorm Cave - Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Mini Moldorm Cave - Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Mini Moldorm Cave - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Sword']],
+ ["Mini Moldorm Cave - Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Mini Moldorm Cave - Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Progressive Sword']],
["Mini Moldorm Cave - Far Right", False, []],
["Mini Moldorm Cave - Far Right", False, [], ['Moon Pearl']],
- ["Mini Moldorm Cave - Far Right", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Mini Moldorm Cave - Far Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Mini Moldorm Cave - Far Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Mini Moldorm Cave - Far Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Far Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Sword']],
+ ["Mini Moldorm Cave - Far Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Mini Moldorm Cave - Far Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Progressive Sword']],
["Ice Rod Cave", False, []],
["Ice Rod Cave", False, [], ['Moon Pearl']],
- ["Ice Rod Cave", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Ice Rod Cave", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Ice Rod Cave", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Ice Rod Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Ice Rod Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Ice Rod Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Ice Rod Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
])
def testZoraArea(self):
@@ -302,21 +313,24 @@ def testLightWorld(self):
["Sahasrahla's Hut - Left", False, []],
["Sahasrahla's Hut - Left", False, [], ['Moon Pearl']],
- ["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Sahasrahla's Hut - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']],
+ ["Sahasrahla's Hut - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Sahasrahla's Hut - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Sahasrahla's Hut - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
["Sahasrahla's Hut - Middle", False, []],
["Sahasrahla's Hut - Middle", False, [], ['Moon Pearl']],
- ["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Sahasrahla's Hut - Middle", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']],
+ ["Sahasrahla's Hut - Middle", True, ['Pegasus Boots', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Sahasrahla's Hut - Middle", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Sahasrahla's Hut - Middle", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
["Sahasrahla's Hut - Right", False, []],
["Sahasrahla's Hut - Right", False, [], ['Moon Pearl']],
- ["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Sahasrahla's Hut - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']],
+ ["Sahasrahla's Hut - Right", True, ['Pegasus Boots', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Sahasrahla's Hut - Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Sahasrahla's Hut - Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
["Sahasrahla", False, []],
["Sahasrahla", False, [], ['Green Pendant']],
@@ -346,9 +360,10 @@ def testLightWorld(self):
["Graveyard Cave", False, []],
["Graveyard Cave", False, [], ['Moon Pearl']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Beat Agahnim 1']],
+ ["Graveyard Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']],
["Potion Shop", False, []],
["Potion Shop", False, [], ['Mushroom']],
diff --git a/worlds/alttp/test/inverted/TestInvertedTurtleRock.py b/worlds/alttp/test/inverted/TestInvertedTurtleRock.py
index fe8979c1ef02..db3084b02a5b 100644
--- a/worlds/alttp/test/inverted/TestInvertedTurtleRock.py
+++ b/worlds/alttp/test/inverted/TestInvertedTurtleRock.py
@@ -11,20 +11,20 @@ def testTurtleRock(self):
["Turtle Rock - Compass Chest", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Quake', 'Small Key (Turtle Rock)']],
["Turtle Rock - Compass Chest", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria']],
["Turtle Rock - Compass Chest", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria']],
- ["Turtle Rock - Compass Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
- ["Turtle Rock - Compass Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
- ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
- ["Turtle Rock - Compass Chest", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
+ ["Turtle Rock - Compass Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', '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 - Compass Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', '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 - Compass Chest", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', '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 - Compass Chest", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', '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", False, []],
["Turtle Rock - Chain Chomps", False, [], ['Magic Mirror', 'Cane of Somaria']],
["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)', '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', '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)', 'Small Key (Turtle Rock)']],
+ ["Turtle Rock - Chain Chomps", True, ['Bomb Upgrade (+5)', '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']],
- ["Turtle Rock - Chain Chomps", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror']],
+ ["Turtle Rock - Chain Chomps", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Fire Rod']],
["Turtle Rock - Roller Room - Left", False, []],
["Turtle Rock - Roller Room - Left", False, [], ['Cane of Somaria']],
@@ -33,10 +33,10 @@ def testTurtleRock(self):
["Turtle Rock - Roller Room - Left", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Quake', 'Small Key (Turtle Rock)']],
["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria']],
["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria']],
- ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
- ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
- ["Turtle Rock - Roller Room - Left", True, ['Moon Pearl', 'Fire Rod', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
- ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
+ ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', '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 - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', '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 - Roller Room - Left", True, ['Moon Pearl', 'Fire Rod', 'Flute', 'Magic Mirror', 'Hookshot', '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 - Roller Room - Left", True, ['Fire Rod', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', '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 - Roller Room - Right", False, []],
["Turtle Rock - Roller Room - Right", False, [], ['Cane of Somaria']],
@@ -45,17 +45,17 @@ def testTurtleRock(self):
["Turtle Rock - Roller Room - Right", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Quake', 'Small Key (Turtle Rock)']],
["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria']],
["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria']],
- ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
- ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
- ["Turtle Rock - Roller Room - Right", True, ['Moon Pearl', 'Fire Rod', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
- ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
+ ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', '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 - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', '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 - Roller Room - Right", True, ['Moon Pearl', 'Fire Rod', 'Flute', 'Magic Mirror', 'Hookshot', '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 - Roller Room - Right", True, ['Fire Rod', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', '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", False, []],
["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)', '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, ['Bomb Upgrade (+5)', '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, ['Bomb Upgrade (+5)', '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)', '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,12 +66,12 @@ def testTurtleRock(self):
["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)', '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)']],
+ ["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)', '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)']],
- ["Turtle Rock - Big Key Chest", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
- ["Turtle Rock - Big Key Chest", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', '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', 'Progressive Glove', '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', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', '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, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', '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, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', '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", False, []],
["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)', 'Magic Mirror']],
@@ -80,7 +80,7 @@ def testTurtleRock(self):
["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)', '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', '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)', '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,12 +98,15 @@ def testTurtleRock(self):
["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)', '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)']]
+ ["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)', '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)', '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)', '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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']]
+
])
+
+
def testEyeBridge(self):
for location in ["Turtle Rock - Eye Bridge - Top Right", "Turtle Rock - Eye Bridge - Top Left",
"Turtle Rock - Eye Bridge - Bottom Right", "Turtle Rock - Eye Bridge - Bottom Left"]:
@@ -115,11 +118,11 @@ def testEyeBridge(self):
[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)', '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)', '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)', '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)', '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)', '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']],
+ [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)', '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']],
diff --git a/worlds/alttp/test/inverted_minor_glitches/TestInvertedDarkWorld.py b/worlds/alttp/test/inverted_minor_glitches/TestInvertedDarkWorld.py
index 69f564489700..dd4a74b6c4d2 100644
--- a/worlds/alttp/test/inverted_minor_glitches/TestInvertedDarkWorld.py
+++ b/worlds/alttp/test/inverted_minor_glitches/TestInvertedDarkWorld.py
@@ -5,7 +5,8 @@ class TestInvertedDarkWorld(TestInvertedMinor):
def testNorthWest(self):
self.run_location_tests([
- ["Brewery", True, []],
+ ["Brewery", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Brewery", True, ['Bomb Upgrade (+5)']],
["C-Shaped House", True, []],
@@ -67,15 +68,16 @@ def testNorthEast(self):
def testSouth(self):
self.run_location_tests([
- ["Hype Cave - Top", True, []],
-
- ["Hype Cave - Middle Right", True, []],
-
- ["Hype Cave - Middle Left", True, []],
-
- ["Hype Cave - Bottom", True, []],
-
- ["Hype Cave - Generous Guy", True, []],
+ ["Hype Cave - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Top", True, ['Bomb Upgrade (+5)']],
+ ["Hype Cave - Middle Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)']],
+ ["Hype Cave - Middle Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)']],
+ ["Hype Cave - Bottom", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)']],
+ ["Hype Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)']],
["Stumpy", True, []],
diff --git a/worlds/alttp/test/inverted_minor_glitches/TestInvertedDeathMountain.py b/worlds/alttp/test/inverted_minor_glitches/TestInvertedDeathMountain.py
index c68a8e5f0c89..c189d107d976 100644
--- a/worlds/alttp/test/inverted_minor_glitches/TestInvertedDeathMountain.py
+++ b/worlds/alttp/test/inverted_minor_glitches/TestInvertedDeathMountain.py
@@ -40,10 +40,11 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Far Left", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']],
["Paradox Cave Lower - Far Left", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']],
["Paradox Cave Lower - Far Left", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']],
- ["Paradox Cave Lower - Far Left", True, ['Flute', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
- ["Paradox Cave Lower - Far Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
+ ["Paradox Cave Lower - Far Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod']],
+ ["Paradox Cave Lower - Far Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot', 'Moon Pearl']],
+ ["Paradox Cave Lower - Far Left", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
+ ["Paradox Cave Lower - Far Left", True, ['Progressive Bow', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
+ ["Paradox Cave Lower - Far Left", True, ['Cane of Somaria', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
["Paradox Cave Lower - Left", False, []],
["Paradox Cave Lower - Left", False, [], ['Moon Pearl']],
@@ -52,10 +53,11 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Left", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']],
["Paradox Cave Lower - Left", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']],
["Paradox Cave Lower - Left", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']],
- ["Paradox Cave Lower - Left", True, ['Flute', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
- ["Paradox Cave Lower - Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
+ ["Paradox Cave Lower - Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod']],
+ ["Paradox Cave Lower - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot', 'Moon Pearl']],
+ ["Paradox Cave Lower - Left", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
+ ["Paradox Cave Lower - Left", True, ['Progressive Bow', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
+ ["Paradox Cave Lower - Left", True, ['Cane of Somaria', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
["Paradox Cave Lower - Middle", False, []],
["Paradox Cave Lower - Middle", False, [], ['Moon Pearl']],
@@ -64,10 +66,11 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Middle", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']],
["Paradox Cave Lower - Middle", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']],
["Paradox Cave Lower - Middle", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']],
- ["Paradox Cave Lower - Middle", True, ['Flute', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
- ["Paradox Cave Lower - Middle", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
+ ["Paradox Cave Lower - Middle", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod']],
+ ["Paradox Cave Lower - Middle", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot', 'Moon Pearl']],
+ ["Paradox Cave Lower - Middle", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
+ ["Paradox Cave Lower - Middle", True, ['Progressive Bow', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
+ ["Paradox Cave Lower - Middle", True, ['Cane of Somaria', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
["Paradox Cave Lower - Right", False, []],
["Paradox Cave Lower - Right", False, [], ['Moon Pearl']],
@@ -76,10 +79,11 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Right", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']],
["Paradox Cave Lower - Right", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']],
["Paradox Cave Lower - Right", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']],
- ["Paradox Cave Lower - Right", True, ['Flute', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
- ["Paradox Cave Lower - Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
+ ["Paradox Cave Lower - Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod']],
+ ["Paradox Cave Lower - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot', 'Moon Pearl']],
+ ["Paradox Cave Lower - Right", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
+ ["Paradox Cave Lower - Right", True, ['Progressive Bow', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
+ ["Paradox Cave Lower - Right", True, ['Cane of Somaria', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
["Paradox Cave Lower - Far Right", False, []],
["Paradox Cave Lower - Far Right", False, [], ['Moon Pearl']],
@@ -88,10 +92,11 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Far Right", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']],
["Paradox Cave Lower - Far Right", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']],
["Paradox Cave Lower - Far Right", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']],
- ["Paradox Cave Lower - Far Right", True, ['Flute', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
- ["Paradox Cave Lower - Far Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
+ ["Paradox Cave Lower - Far Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod']],
+ ["Paradox Cave Lower - Far Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot', 'Moon Pearl']],
+ ["Paradox Cave Lower - Far Right", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
+ ["Paradox Cave Lower - Far Right", True, ['Progressive Bow', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
+ ["Paradox Cave Lower - Far Right", True, ['Cane of Somaria', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
["Paradox Cave Upper - Left", False, []],
["Paradox Cave Upper - Left", False, [], ['Moon Pearl']],
@@ -100,10 +105,11 @@ def testEastDeathMountain(self):
["Paradox Cave Upper - Left", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']],
["Paradox Cave Upper - Left", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']],
["Paradox Cave Upper - Left", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']],
- ["Paradox Cave Upper - Left", True, ['Flute', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
- ["Paradox Cave Upper - Left", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
+ ["Paradox Cave Upper - Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)','Bomb Upgrade (50)']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot', 'Moon Pearl']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
["Paradox Cave Upper - Right", False, []],
["Paradox Cave Upper - Right", False, [], ['Moon Pearl']],
@@ -112,20 +118,21 @@ def testEastDeathMountain(self):
["Paradox Cave Upper - Right", False, ['Progressive Glove'], ['Progressive Glove', 'Hookshot']],
["Paradox Cave Upper - Right", False, ['Progressive Glove', 'Hookshot', 'Moon Pearl']],
["Paradox Cave Upper - Right", False, ['Flute', 'Progressive Glove', 'Hammer', 'Moon Pearl']],
- ["Paradox Cave Upper - Right", True, ['Flute', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
- ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
- ["Paradox Cave Upper - Right", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
+ ["Paradox Cave Upper - Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)','Bomb Upgrade (50)']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot', 'Moon Pearl']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Hookshot', 'Moon Pearl']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
["Mimic Cave", False, []],
["Mimic Cave", False, [], ['Moon Pearl']],
["Mimic Cave", False, [], ['Hammer']],
["Mimic Cave", False, [], ['Progressive Glove', 'Flute']],
["Mimic Cave", False, [], ['Lamp', 'Flute']],
- ["Mimic Cave", True, ['Flute', 'Moon Pearl', 'Hammer', 'Hookshot']],
- ["Mimic Cave", True, ['Flute', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hammer']],
- ["Mimic Cave", True, ['Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer', 'Hookshot']],
- ["Mimic Cave", True, ['Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer']],
+ ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Flute', 'Moon Pearl', 'Hammer', 'Hookshot']],
+ ["Mimic Cave", True, ['Progressive Sword', 'Progressive Sword', 'Flute', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove', 'Hammer']],
+ ["Mimic Cave", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer', 'Hookshot']],
+ ["Mimic Cave", True, ['Cane of Somaria', 'Progressive Glove', 'Progressive Glove', 'Lamp', 'Moon Pearl', 'Hammer']],
["Ether Tablet", False, []],
["Ether Tablet", False, [], ['Moon Pearl']],
diff --git a/worlds/alttp/test/inverted_minor_glitches/TestInvertedLightWorld.py b/worlds/alttp/test/inverted_minor_glitches/TestInvertedLightWorld.py
index 376e7b4bec49..086c1c92b5dd 100644
--- a/worlds/alttp/test/inverted_minor_glitches/TestInvertedLightWorld.py
+++ b/worlds/alttp/test/inverted_minor_glitches/TestInvertedLightWorld.py
@@ -43,16 +43,18 @@ def testKakariko(self):
["Chicken House", False, []],
["Chicken House", False, [], ['Moon Pearl']],
- ["Chicken House", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Chicken House", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Chicken House", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Chicken House", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)','Bomb Upgrade (50)']],
+ ["Chicken House", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Chicken House", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Chicken House", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
# top can't be bombed as super bunny and needs Moon Pearl
["Kakariko Well - Top", False, []],
["Kakariko Well - Top", False, [], ['Moon Pearl']],
- ["Kakariko Well - Top", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Kakariko Well - Top", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Kakariko Well - Top", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Kakariko Well - Top", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)','Bomb Upgrade (50)']],
+ ["Kakariko Well - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Kakariko Well - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Kakariko Well - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
["Kakariko Well - Left", False, []],
["Kakariko Well - Left", True, ['Beat Agahnim 1']],
@@ -76,9 +78,10 @@ def testKakariko(self):
["Blind's Hideout - Top", False, []],
["Blind's Hideout - Top", False, [], ['Moon Pearl', 'Magic Mirror']],
- ["Blind's Hideout - Top", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Blind's Hideout - Top", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Blind's Hideout - Top", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Blind's Hideout - Top", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)','Bomb Upgrade (50)']],
+ ["Blind's Hideout - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Blind's Hideout - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Blind's Hideout - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
["Blind's Hideout - Left", False, []],
["Blind's Hideout - Left", False, [], ['Moon Pearl', 'Magic Mirror']],
@@ -157,9 +160,10 @@ def testKakariko(self):
["Maze Race", False, []],
["Maze Race", False, [], ['Moon Pearl']],
- ["Maze Race", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Maze Race", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Maze Race", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Maze Race", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)','Bomb Upgrade (50)', 'Pegasus Boots']],
+ ["Maze Race", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Maze Race", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Maze Race", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
])
def testSouthLightWorld(self):
@@ -179,9 +183,10 @@ def testSouthLightWorld(self):
["Aginah's Cave", False, []],
["Aginah's Cave", False, [], ['Moon Pearl']],
- ["Aginah's Cave", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Aginah's Cave", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Aginah's Cave", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Aginah's Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Aginah's Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Aginah's Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Aginah's Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
["Bombos Tablet", False, []],
["Bombos Tablet", False, ['Progressive Sword'], ['Progressive Sword']],
@@ -209,39 +214,45 @@ def testSouthLightWorld(self):
["Mini Moldorm Cave - Far Left", False, []],
["Mini Moldorm Cave - Far Left", False, [], ['Moon Pearl']],
- ["Mini Moldorm Cave - Far Left", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Mini Moldorm Cave - Far Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Mini Moldorm Cave - Far Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Mini Moldorm Cave - Far Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Far Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Mini Moldorm Cave - Far Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Mini Moldorm Cave - Far Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
["Mini Moldorm Cave - Left", False, []],
["Mini Moldorm Cave - Left", False, [], ['Moon Pearl']],
- ["Mini Moldorm Cave - Left", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Mini Moldorm Cave - Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Mini Moldorm Cave - Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Mini Moldorm Cave - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Mini Moldorm Cave - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Mini Moldorm Cave - Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
["Mini Moldorm Cave - Generous Guy", False, []],
["Mini Moldorm Cave - Generous Guy", False, [], ['Moon Pearl']],
- ["Mini Moldorm Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Mini Moldorm Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Mini Moldorm Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Mini Moldorm Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Mini Moldorm Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Mini Moldorm Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
["Mini Moldorm Cave - Right", False, []],
["Mini Moldorm Cave - Right", False, [], ['Moon Pearl']],
- ["Mini Moldorm Cave - Right", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Mini Moldorm Cave - Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Mini Moldorm Cave - Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Mini Moldorm Cave - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Mini Moldorm Cave - Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Mini Moldorm Cave - Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
["Mini Moldorm Cave - Far Right", False, []],
["Mini Moldorm Cave - Far Right", False, [], ['Moon Pearl']],
- ["Mini Moldorm Cave - Far Right", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Mini Moldorm Cave - Far Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Mini Moldorm Cave - Far Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Mini Moldorm Cave - Far Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Far Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Mini Moldorm Cave - Far Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Mini Moldorm Cave - Far Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
["Ice Rod Cave", False, []],
["Ice Rod Cave", False, [], ['Moon Pearl']],
- ["Ice Rod Cave", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Ice Rod Cave", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Ice Rod Cave", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Ice Rod Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Ice Rod Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Ice Rod Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Ice Rod Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
])
def testZoraArea(self):
@@ -297,25 +308,28 @@ def testLightWorld(self):
["Sahasrahla's Hut - Left", False, []],
["Sahasrahla's Hut - Left", False, [], ['Moon Pearl', 'Magic Mirror']],
- ["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Sahasrahla's Hut - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']],
+ ["Sahasrahla's Hut - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Sahasrahla's Hut - Left", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Sahasrahla's Hut - Left", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
# super bunny bonk
["Sahasrahla's Hut - Left", True, ['Magic Mirror', 'Beat Agahnim 1', 'Pegasus Boots']],
["Sahasrahla's Hut - Middle", False, []],
["Sahasrahla's Hut - Middle", False, [], ['Moon Pearl', 'Magic Mirror']],
- ["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Sahasrahla's Hut - Middle", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']],
+ ["Sahasrahla's Hut - Middle", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Sahasrahla's Hut - Middle", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Sahasrahla's Hut - Middle", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
# super bunny bonk
["Sahasrahla's Hut - Middle", True, ['Magic Mirror', 'Beat Agahnim 1', 'Pegasus Boots']],
["Sahasrahla's Hut - Right", False, []],
["Sahasrahla's Hut - Right", False, [], ['Moon Pearl', 'Magic Mirror']],
- ["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Beat Agahnim 1']],
- ["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Sahasrahla's Hut - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']],
+ ["Sahasrahla's Hut - Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']],
+ ["Sahasrahla's Hut - Right", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Sahasrahla's Hut - Right", True, ['Pegasus Boots', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
# super bunny bonk
["Sahasrahla's Hut - Right", True, ['Magic Mirror', 'Beat Agahnim 1', 'Pegasus Boots']],
@@ -347,9 +361,10 @@ def testLightWorld(self):
["Graveyard Cave", False, []],
["Graveyard Cave", False, [], ['Moon Pearl']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Beat Agahnim 1']],
+ ["Graveyard Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']],
["Potion Shop", False, []],
["Potion Shop", False, [], ['Mushroom']],
diff --git a/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py b/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py
index 33e582298185..0219332e0748 100644
--- a/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py
+++ b/worlds/alttp/test/inverted_minor_glitches/TestInvertedMinor.py
@@ -13,8 +13,10 @@
class TestInvertedMinor(TestBase, LTTPTestBase):
def setUp(self):
self.world_setup()
- self.multiworld.mode[1] = "inverted"
- self.multiworld.logic[1] = "minorglitches"
+ self.multiworld.mode[1].value = 2
+ self.multiworld.glitches_required[1] = "minor_glitches"
+ self.multiworld.bombless_start[1].value = True
+ self.multiworld.shuffle_capacity_upgrades[1].value = True
self.multiworld.difficulty_requirements[1] = difficulties['normal']
create_inverted_regions(self.multiworld, 1)
self.multiworld.worlds[1].create_dungeons()
diff --git a/worlds/alttp/test/inverted_minor_glitches/TestInvertedTurtleRock.py b/worlds/alttp/test/inverted_minor_glitches/TestInvertedTurtleRock.py
index d7b5c9f79788..a416e1b35d33 100644
--- a/worlds/alttp/test/inverted_minor_glitches/TestInvertedTurtleRock.py
+++ b/worlds/alttp/test/inverted_minor_glitches/TestInvertedTurtleRock.py
@@ -11,21 +11,21 @@ def testTurtleRock(self):
["Turtle Rock - Compass Chest", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Quake', 'Small Key (Turtle Rock)']],
["Turtle Rock - Compass Chest", True, ['Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria']],
["Turtle Rock - Compass Chest", True, ['Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria']],
- ["Turtle Rock - Compass Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
- ["Turtle Rock - Compass Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
- ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
- ["Turtle Rock - Compass Chest", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
+ ["Turtle Rock - Compass Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', '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 - Compass Chest", True, ['Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', '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 - Compass Chest", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', '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 - Compass Chest", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', '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", 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)', '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', '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)', 'Small Key (Turtle Rock)']],
+ ["Turtle Rock - Chain Chomps", True, ['Bomb Upgrade (+5)', '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']],
- ["Turtle Rock - Chain Chomps", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror']],
+ ["Turtle Rock - Chain Chomps", True, ['Fire Rod', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror']],
["Turtle Rock - Roller Room - Left", False, []],
["Turtle Rock - Roller Room - Left", False, [], ['Cane of Somaria']],
@@ -34,10 +34,10 @@ def testTurtleRock(self):
["Turtle Rock - Roller Room - Left", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Quake', 'Small Key (Turtle Rock)']],
["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria']],
["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria']],
- ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
- ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
- ["Turtle Rock - Roller Room - Left", True, ['Moon Pearl', 'Fire Rod', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
- ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
+ ["Turtle Rock - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', '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 - Roller Room - Left", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', '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 - Roller Room - Left", True, ['Moon Pearl', 'Fire Rod', 'Flute', 'Magic Mirror', 'Hookshot', '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 - Roller Room - Left", True, ['Fire Rod', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', '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 - Roller Room - Right", False, []],
["Turtle Rock - Roller Room - Right", False, [], ['Cane of Somaria']],
@@ -46,17 +46,17 @@ def testTurtleRock(self):
["Turtle Rock - Roller Room - Right", False, ['Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)'], ['Quake', 'Small Key (Turtle Rock)']],
["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Flute', 'Quake', 'Progressive Sword', 'Cane of Somaria']],
["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Progressive Glove', 'Quake', 'Progressive Sword', 'Cane of Somaria']],
- ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
- ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
- ["Turtle Rock - Roller Room - Right", True, ['Moon Pearl', 'Fire Rod', 'Flute', 'Magic Mirror', 'Hookshot', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
- ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
+ ["Turtle Rock - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove', '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 - Roller Room - Right", True, ['Fire Rod', 'Lamp', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', '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 - Roller Room - Right", True, ['Moon Pearl', 'Fire Rod', 'Flute', 'Magic Mirror', 'Hookshot', '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 - Roller Room - Right", True, ['Fire Rod', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', '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", False, []],
["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)', '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, ['Bomb Upgrade (+5)', '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, ['Bomb Upgrade (+5)', '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)', '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']],
@@ -67,12 +67,12 @@ def testTurtleRock(self):
["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)', '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)']],
+ ["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)', '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)']],
- ["Turtle Rock - Big Key Chest", True, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
- ["Turtle Rock - Big Key Chest", True, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', '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', 'Progressive Glove', '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', 'Magic Mirror', 'Progressive Glove', 'Moon Pearl', 'Hookshot', '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, ['Moon Pearl', 'Flute', 'Magic Mirror', 'Hookshot', '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, ['Flute', 'Progressive Glove', 'Progressive Glove', 'Magic Mirror', '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", False, []],
["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)', 'Magic Mirror']],
@@ -81,7 +81,7 @@ def testTurtleRock(self):
["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)', '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', '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)', '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,11 +98,11 @@ def testTurtleRock(self):
["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)', '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)']]
+ ["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)', '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)', '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)', '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)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Big Key (Turtle Rock)']]
])
@@ -117,11 +117,11 @@ def testEyeBridge(self):
[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)', '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)', '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)', '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)', '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)', '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']],
+ [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)', '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']],
diff --git a/worlds/alttp/test/inverted_owg/TestDarkWorld.py b/worlds/alttp/test/inverted_owg/TestDarkWorld.py
index e7e720d2b853..8fb234f0b50a 100644
--- a/worlds/alttp/test/inverted_owg/TestDarkWorld.py
+++ b/worlds/alttp/test/inverted_owg/TestDarkWorld.py
@@ -5,15 +5,16 @@ class TestDarkWorld(TestInvertedOWG):
def testSouthDarkWorld(self):
self.run_location_tests([
- ["Hype Cave - Top", True, []],
-
- ["Hype Cave - Middle Right", True, []],
-
- ["Hype Cave - Middle Left", True, []],
-
- ["Hype Cave - Bottom", True, []],
-
- ["Hype Cave - Generous Guy", True, []],
+ ["Hype Cave - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Middle Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Middle Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Bottom", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Top", True, ['Bomb Upgrade (+5)']],
+ ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)']],
+ ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)']],
+ ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)']],
+ ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)']],
["Stumpy", True, []],
@@ -22,7 +23,8 @@ def testSouthDarkWorld(self):
def testWestDarkWorld(self):
self.run_location_tests([
- ["Brewery", True, []],
+ ["Brewery", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Brewery", True, ['Bomb Upgrade (+5)']],
["C-Shaped House", True, []],
diff --git a/worlds/alttp/test/inverted_owg/TestDeathMountain.py b/worlds/alttp/test/inverted_owg/TestDeathMountain.py
index 79796a7aeb1e..b509643d0c5e 100644
--- a/worlds/alttp/test/inverted_owg/TestDeathMountain.py
+++ b/worlds/alttp/test/inverted_owg/TestDeathMountain.py
@@ -24,36 +24,38 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Far Left", False, []],
["Paradox Cave Lower - Far Left", False, [], ['Moon Pearl']],
- ["Paradox Cave Lower - Far Left", True, ['Moon Pearl', 'Pegasus Boots']],
+ ["Paradox Cave Lower - Far Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']],
["Paradox Cave Lower - Left", False, []],
["Paradox Cave Lower - Left", False, [], ['Moon Pearl']],
- ["Paradox Cave Lower - Left", True, ['Moon Pearl', 'Pegasus Boots']],
+ ["Paradox Cave Lower - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']],
["Paradox Cave Lower - Middle", False, []],
["Paradox Cave Lower - Middle", False, [], ['Moon Pearl']],
- ["Paradox Cave Lower - Middle", True, ['Moon Pearl', 'Pegasus Boots']],
+ ["Paradox Cave Lower - Middle", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']],
["Paradox Cave Lower - Right", False, []],
["Paradox Cave Lower - Right", False, [], ['Moon Pearl']],
- ["Paradox Cave Lower - Right", True, ['Moon Pearl', 'Pegasus Boots']],
+ ["Paradox Cave Lower - Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']],
["Paradox Cave Lower - Far Right", False, []],
["Paradox Cave Lower - Far Right", False, [], ['Moon Pearl']],
- ["Paradox Cave Lower - Far Right", True, ['Moon Pearl', 'Pegasus Boots']],
+ ["Paradox Cave Lower - Far Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']],
["Paradox Cave Upper - Left", False, []],
["Paradox Cave Upper - Left", False, [], ['Moon Pearl']],
- ["Paradox Cave Upper - Left", True, ['Moon Pearl', 'Pegasus Boots']],
+ ["Paradox Cave Upper - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']],
["Paradox Cave Upper - Right", False, []],
["Paradox Cave Upper - Right", False, [], ['Moon Pearl']],
- ["Paradox Cave Upper - Right", True, ['Moon Pearl', 'Pegasus Boots']],
+ ["Paradox Cave Upper - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']],
["Mimic Cave", False, []],
["Mimic Cave", False, [], ['Moon Pearl']],
["Mimic Cave", False, [], ['Hammer']],
- ["Mimic Cave", True, ['Moon Pearl', 'Hammer', 'Pegasus Boots']],
+ ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Hammer', 'Pegasus Boots']],
["Ether Tablet", False, []],
["Ether Tablet", False, ['Progressive Sword'], ['Progressive Sword']],
diff --git a/worlds/alttp/test/inverted_owg/TestDungeons.py b/worlds/alttp/test/inverted_owg/TestDungeons.py
index f5d07544aaea..53b12bdf89d1 100644
--- a/worlds/alttp/test/inverted_owg/TestDungeons.py
+++ b/worlds/alttp/test/inverted_owg/TestDungeons.py
@@ -13,16 +13,15 @@ def testFirstDungeonChests(self):
["Sanctuary", False, []],
["Sanctuary", False, ['Beat Agahnim 1']],
["Sanctuary", True, ['Magic Mirror', 'Beat Agahnim 1']],
- ["Sanctuary", True, ['Lamp', 'Beat Agahnim 1', 'Small Key (Hyrule Castle)']],
+ ["Sanctuary", True, ['Lamp', 'Beat Agahnim 1', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)']],
["Sanctuary", True, ['Moon Pearl', 'Pegasus Boots']],
["Sanctuary", True, ['Magic Mirror', 'Pegasus Boots']],
["Sewers - Secret Room - Left", False, []],
["Sewers - Secret Room - Left", True, ['Moon Pearl', 'Progressive Glove', 'Pegasus Boots']],
- ["Sewers - Secret Room - Left", True, ['Moon Pearl', 'Pegasus Boots', 'Lamp', 'Small Key (Hyrule Castle)']],
- ["Sewers - Secret Room - Left", True,
- ['Magic Mirror', 'Pegasus Boots', 'Lamp', 'Small Key (Hyrule Castle)']],
- ["Sewers - Secret Room - Left", True, ['Beat Agahnim 1', 'Lamp', 'Small Key (Hyrule Castle)']],
+ ["Sewers - Secret Room - Left", True, ['Moon Pearl', 'Pegasus Boots', 'Lamp', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)']],
+ ["Sewers - Secret Room - Left", True, ['Magic Mirror', 'Pegasus Boots', 'Lamp', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)']],
+ ["Sewers - Secret Room - Left", True, ['Bomb Upgrade (+5)', 'Beat Agahnim 1', 'Lamp', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)']],
["Eastern Palace - Compass Chest", False, []],
["Eastern Palace - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots']],
@@ -37,15 +36,15 @@ def testFirstDungeonChests(self):
["Desert Palace - Boss", False, [], ['Small Key (Desert Palace)']],
["Desert Palace - Boss", False, [], ['Big Key (Desert Palace)']],
["Desert Palace - Boss", False, [], ['Lamp', 'Fire Rod']],
- ["Desert Palace - Boss", True, ['Progressive Sword', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Moon Pearl', 'Pegasus Boots', 'Lamp']],
- ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Moon Pearl', 'Pegasus Boots', 'Fire Rod']],
+ ["Desert Palace - Boss", True, ['Progressive Sword', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Moon Pearl', 'Pegasus Boots', 'Lamp']],
+ ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Big Key (Desert Palace)', 'Moon Pearl', 'Pegasus Boots', 'Fire Rod']],
["Tower of Hera - Basement Cage", False, []],
["Tower of Hera - Basement Cage", False, [], ['Moon Pearl']],
["Tower of Hera - Basement Cage", True, ['Pegasus Boots', 'Moon Pearl']],
["Castle Tower - Room 03", False, []],
- ["Castle Tower - Room 03", False, [], ['Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']],
+ ["Castle Tower - Room 03", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']],
["Castle Tower - Room 03", True, ['Pegasus Boots', 'Progressive Sword']],
["Castle Tower - Room 03", True, ['Pegasus Boots', 'Progressive Bow']],
@@ -62,7 +61,8 @@ def testFirstDungeonChests(self):
["Skull Woods - Big Chest", False, []],
["Skull Woods - Big Chest", False, [], ['Big Key (Skull Woods)']],
- ["Skull Woods - Big Chest", True, ['Big Key (Skull Woods)']],
+ ["Skull Woods - Big Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Skull Woods - Big Chest", True, ['Bomb Upgrade (+5)', 'Big Key (Skull Woods)']],
["Skull Woods - Big Key Chest", True, []],
@@ -75,8 +75,8 @@ def testFirstDungeonChests(self):
["Ice Palace - Compass Chest", False, []],
["Ice Palace - Compass Chest", False, [], ['Fire Rod', 'Bombos', 'Progressive Sword']],
# Qirn Jump
- ["Ice Palace - Compass Chest", True, ['Fire Rod']],
- ["Ice Palace - Compass Chest", True, ['Bombos', 'Progressive Sword']],
+ ["Ice Palace - Compass Chest", True, ['Fire Rod', 'Small Key (Ice Palace)']],
+ ["Ice Palace - Compass Chest", True, ['Bombos', 'Progressive Sword', 'Small Key (Ice Palace)']],
["Misery Mire - Bridge Chest", False, []],
["Misery Mire - Bridge Chest", False, [], ['Ether']],
@@ -85,11 +85,20 @@ def testFirstDungeonChests(self):
["Turtle Rock - Compass Chest", False, []],
["Turtle Rock - Compass Chest", False, [], ['Cane of Somaria']],
- ["Turtle Rock - Compass Chest", True, ['Pegasus Boots', 'Magic Mirror', 'Moon Pearl', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
+ ["Turtle Rock - Compass Chest", True, ['Pegasus Boots', 'Magic Mirror', 'Moon Pearl', '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 - Compass Chest", True, ['Pegasus Boots', 'Quake', 'Progressive Sword', 'Cane of Somaria']],
["Turtle Rock - Chain Chomps", False, []],
- ["Turtle Rock - Chain Chomps", True, ['Pegasus Boots', 'Magic Mirror', 'Moon Pearl']],
+ ["Turtle Rock - Chain Chomps", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Hookshot', 'Progressive Sword', 'Progressive Bow', 'Blue Boomerang', 'Red Boomerang', 'Cane of Somaria', 'Fire Rod', 'Ice Rod']],
+ ["Turtle Rock - Chain Chomps", True, ['Bomb Upgrade (+5)', 'Pegasus Boots', 'Magic Mirror', 'Moon Pearl']],
+ ["Turtle Rock - Chain Chomps", True, ['Hookshot', 'Pegasus Boots']],
+ ["Turtle Rock - Chain Chomps", True, ['Progressive Bow', 'Pegasus Boots', 'Magic Mirror', 'Moon Pearl']],
+ ["Turtle Rock - Chain Chomps", True, ['Blue Boomerang', 'Pegasus Boots', 'Magic Mirror', 'Moon Pearl']],
+ ["Turtle Rock - Chain Chomps", True, ['Red Boomerang', 'Pegasus Boots', 'Magic Mirror', 'Moon Pearl']],
+ ["Turtle Rock - Chain Chomps", True, ['Cane of Somaria', 'Pegasus Boots', 'Magic Mirror', 'Moon Pearl']],
+ ["Turtle Rock - Chain Chomps", True, ['Fire Rod', 'Pegasus Boots', 'Magic Mirror', 'Moon Pearl']],
+ ["Turtle Rock - Chain Chomps", True, ['Ice Rod', 'Pegasus Boots', 'Magic Mirror', 'Moon Pearl']],
+ ["Turtle Rock - Chain Chomps", True, ['Progressive Sword', 'Progressive Sword', 'Pegasus Boots']],
["Turtle Rock - Crystaroller Room", False, []],
["Turtle Rock - Crystaroller Room", True, ['Pegasus Boots', 'Magic Mirror', 'Moon Pearl', 'Big Key (Turtle Rock)']],
diff --git a/worlds/alttp/test/inverted_owg/TestInvertedOWG.py b/worlds/alttp/test/inverted_owg/TestInvertedOWG.py
index a4e84fce9b62..849f06098a44 100644
--- a/worlds/alttp/test/inverted_owg/TestInvertedOWG.py
+++ b/worlds/alttp/test/inverted_owg/TestInvertedOWG.py
@@ -13,8 +13,10 @@
class TestInvertedOWG(TestBase, LTTPTestBase):
def setUp(self):
self.world_setup()
- self.multiworld.logic[1] = "owglitches"
- self.multiworld.mode[1] = "inverted"
+ self.multiworld.glitches_required[1] = "overworld_glitches"
+ self.multiworld.mode[1].value = 2
+ self.multiworld.bombless_start[1].value = True
+ self.multiworld.shuffle_capacity_upgrades[1].value = True
self.multiworld.difficulty_requirements[1] = difficulties['normal']
create_inverted_regions(self.multiworld, 1)
self.multiworld.worlds[1].create_dungeons()
diff --git a/worlds/alttp/test/inverted_owg/TestLightWorld.py b/worlds/alttp/test/inverted_owg/TestLightWorld.py
index de92b4ef854d..bd18259bec3f 100644
--- a/worlds/alttp/test/inverted_owg/TestLightWorld.py
+++ b/worlds/alttp/test/inverted_owg/TestLightWorld.py
@@ -40,40 +40,46 @@ def testLightWorld(self):
["Chicken House", False, []],
["Chicken House", False, [], ['Moon Pearl']],
- ["Chicken House", True, ['Moon Pearl', 'Pegasus Boots']],
+ ["Chicken House", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Chicken House", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']],
["Aginah's Cave", False, []],
["Aginah's Cave", False, [], ['Moon Pearl']],
- ["Aginah's Cave", True, ['Moon Pearl', 'Pegasus Boots']],
+ ["Aginah's Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Aginah's Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']],
["Sahasrahla's Hut - Left", False, []],
["Sahasrahla's Hut - Left", False, [], ['Moon Pearl', 'Magic Mirror']],
["Sahasrahla's Hut - Left", False, [], ['Moon Pearl', 'Pegasus Boots']],
+ ["Sahasrahla's Hut - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']],
["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Pegasus Boots']],
["Sahasrahla's Hut - Left", True, ['Magic Mirror', 'Pegasus Boots']],
##todo: Damage boost superbunny not in logic
#["Sahasrahla's Hut - Left", True, ['Beat Agahnim 1', 'Pegasus Boots']],
- ["Sahasrahla's Hut - Left", True, ['Moon Pearl', 'Beat Agahnim 1']],
+ ["Sahasrahla's Hut - Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']],
["Sahasrahla's Hut - Middle", False, []],
["Sahasrahla's Hut - Middle", False, [], ['Moon Pearl', 'Magic Mirror']],
["Sahasrahla's Hut - Middle", False, [], ['Moon Pearl', 'Pegasus Boots']],
+ ["Sahasrahla's Hut - Middle", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']],
["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Pegasus Boots']],
["Sahasrahla's Hut - Middle", True, ['Magic Mirror', 'Pegasus Boots']],
#["Sahasrahla's Hut - Middle", True, ['Beat Agahnim 1', 'Pegasus Boots']],
- ["Sahasrahla's Hut - Middle", True, ['Moon Pearl', 'Beat Agahnim 1']],
+ ["Sahasrahla's Hut - Middle", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']],
["Sahasrahla's Hut - Right", False, []],
["Sahasrahla's Hut - Right", False, [], ['Moon Pearl', 'Magic Mirror']],
["Sahasrahla's Hut - Right", False, [], ['Moon Pearl', 'Pegasus Boots']],
+ ["Sahasrahla's Hut - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']],
["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Pegasus Boots']],
["Sahasrahla's Hut - Right", True, ['Magic Mirror', 'Pegasus Boots']],
#["Sahasrahla's Hut - Right", True, ['Beat Agahnim 1', 'Pegasus Boots']],
- ["Sahasrahla's Hut - Right", True, ['Moon Pearl', 'Beat Agahnim 1']],
+ ["Sahasrahla's Hut - Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1']],
["Kakariko Well - Top", False, []],
["Kakariko Well - Top", False, [], ['Moon Pearl']],
- ["Kakariko Well - Top", True, ['Moon Pearl', 'Pegasus Boots']],
+ ["Kakariko Well - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Kakariko Well - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']],
["Kakariko Well - Left", False, []],
["Kakariko Well - Left", True, ['Moon Pearl', 'Pegasus Boots']],
@@ -101,7 +107,8 @@ def testLightWorld(self):
["Blind's Hideout - Top", False, []],
["Blind's Hideout - Top", False, [], ['Moon Pearl']],
- ["Blind's Hideout - Top", True, ['Moon Pearl', 'Pegasus Boots']],
+ ["Blind's Hideout - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Blind's Hideout - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']],
["Blind's Hideout - Left", False, []],
["Blind's Hideout - Left", False, [], ['Moon Pearl', 'Magic Mirror']],
@@ -134,27 +141,33 @@ def testLightWorld(self):
["Mini Moldorm Cave - Far Left", False, []],
["Mini Moldorm Cave - Far Left", False, [], ['Moon Pearl']],
- ["Mini Moldorm Cave - Far Left", True, ['Moon Pearl', 'Pegasus Boots']],
+ ["Mini Moldorm Cave - Far Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Far Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Pegasus Boots']],
["Mini Moldorm Cave - Left", False, []],
["Mini Moldorm Cave - Left", False, [], ['Moon Pearl']],
- ["Mini Moldorm Cave - Left", True, ['Moon Pearl', 'Pegasus Boots']],
+ ["Mini Moldorm Cave - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Pegasus Boots']],
["Mini Moldorm Cave - Right", False, []],
["Mini Moldorm Cave - Right", False, [], ['Moon Pearl']],
- ["Mini Moldorm Cave - Right", True, ['Moon Pearl', 'Pegasus Boots']],
+ ["Mini Moldorm Cave - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Pegasus Boots']],
["Mini Moldorm Cave - Far Right", False, []],
["Mini Moldorm Cave - Far Right", False, [], ['Moon Pearl']],
- ["Mini Moldorm Cave - Far Right", True, ['Moon Pearl', 'Pegasus Boots']],
+ ["Mini Moldorm Cave - Far Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Far Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Pegasus Boots']],
["Mini Moldorm Cave - Generous Guy", False, []],
["Mini Moldorm Cave - Generous Guy", False, [], ['Moon Pearl']],
- ["Mini Moldorm Cave - Generous Guy", True, ['Moon Pearl', 'Pegasus Boots']],
+ ["Mini Moldorm Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Progressive Sword', 'Moon Pearl', 'Pegasus Boots']],
["Ice Rod Cave", False, []],
["Ice Rod Cave", False, [], ['Moon Pearl']],
- ["Ice Rod Cave", True, ['Moon Pearl', 'Pegasus Boots']],
+ ["Ice Rod Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Ice Rod Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']],
#I don't think so
#["Ice Rod Cave", True, ['Magic Mirror', 'Pegasus Boots', 'BigRedBomb']],
#["Ice Rod Cave", True, ['Magic Mirror', 'Beat Agahnim 1', 'BigRedBomb']],
@@ -236,7 +249,8 @@ def testLightWorld(self):
["Graveyard Cave", False, []],
["Graveyard Cave", False, [], ['Moon Pearl']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Pegasus Boots']],
+ ["Graveyard Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']],
["Checkerboard Cave", False, []],
["Checkerboard Cave", False, [], ['Progressive Glove']],
diff --git a/worlds/alttp/test/minor_glitches/TestDarkWorld.py b/worlds/alttp/test/minor_glitches/TestDarkWorld.py
index 3a6f97254c95..9b0e43ea9494 100644
--- a/worlds/alttp/test/minor_glitches/TestDarkWorld.py
+++ b/worlds/alttp/test/minor_glitches/TestDarkWorld.py
@@ -7,43 +7,48 @@ def testSouthDarkWorld(self):
self.run_location_tests([
["Hype Cave - Top", False, []],
["Hype Cave - Top", False, [], ['Moon Pearl']],
- ["Hype Cave - Top", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
- ["Hype Cave - Top", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
- ["Hype Cave - Top", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Hype Cave - Top", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
- ["Hype Cave - Top", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
+ ["Hype Cave - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
+ ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
+ ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
["Hype Cave - Middle Right", False, []],
["Hype Cave - Middle Right", False, [], ['Moon Pearl']],
- ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
- ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
- ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
- ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
+ ["Hype Cave - Middle Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
+ ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
+ ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
["Hype Cave - Middle Left", False, []],
["Hype Cave - Middle Left", False, [], ['Moon Pearl']],
- ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
- ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
- ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
- ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
+ ["Hype Cave - Middle Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
+ ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
+ ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
["Hype Cave - Bottom", False, []],
["Hype Cave - Bottom", False, [], ['Moon Pearl']],
- ["Hype Cave - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
- ["Hype Cave - Bottom", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
- ["Hype Cave - Bottom", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Hype Cave - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
- ["Hype Cave - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
+ ["Hype Cave - Bottom", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
+ ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
+ ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
["Hype Cave - Generous Guy", False, []],
["Hype Cave - Generous Guy", False, [], ['Moon Pearl']],
- ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
- ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
- ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
- ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
+ ["Hype Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
+ ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
+ ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
["Stumpy", False, []],
["Stumpy", False, [], ['Moon Pearl']],
@@ -66,10 +71,11 @@ def testWestDarkWorld(self):
self.run_location_tests([
["Brewery", False, []],
["Brewery", False, [], ['Moon Pearl']],
- ["Brewery", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
- ["Brewery", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Brewery", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
- ["Brewery", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
+ ["Brewery", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
+ ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
["C-Shaped House", False, []],
["C-Shaped House", False, [], ['Moon Pearl']],
diff --git a/worlds/alttp/test/minor_glitches/TestDeathMountain.py b/worlds/alttp/test/minor_glitches/TestDeathMountain.py
index 2603aaeb7b9e..7d7589d2f7fe 100644
--- a/worlds/alttp/test/minor_glitches/TestDeathMountain.py
+++ b/worlds/alttp/test/minor_glitches/TestDeathMountain.py
@@ -48,7 +48,8 @@ def testEastDeathMountain(self):
["Mimic Cave", False, [], ['Moon Pearl']],
["Mimic Cave", False, [], ['Cane of Somaria']],
["Mimic Cave", False, ['Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']],
- ["Mimic Cave", True, ['Quake', 'Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Moon Pearl', 'Cane of Somaria', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
+ ["Mimic Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Quake', 'Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Moon Pearl', 'Cane of Somaria', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
["Spiral Cave", False, []],
["Spiral Cave", False, [], ['Progressive Glove', 'Flute']],
@@ -73,10 +74,11 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Far Left", False, ['Progressive Glove', 'Hookshot']],
["Paradox Cave Lower - Far Left", False, ['Flute', 'Magic Mirror']],
["Paradox Cave Lower - Far Left", False, ['Flute', 'Hammer']],
- ["Paradox Cave Lower - Far Left", True, ['Flute', 'Hookshot']],
- ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']],
- ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
- ["Paradox Cave Lower - Far Left", True, ['Flute', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Lower - Far Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod', 'Cane of Somaria']],
+ ["Paradox Cave Lower - Far Left", True, ['Cane of Somaria', 'Flute', 'Hookshot']],
+ ["Paradox Cave Lower - Far Left", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot']],
+ ["Paradox Cave Lower - Far Left", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Lower - Far Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror', 'Hammer']],
["Paradox Cave Lower - Left", False, []],
["Paradox Cave Lower - Left", False, [], ['Progressive Glove', 'Flute']],
@@ -87,10 +89,11 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Left", False, ['Progressive Glove', 'Hookshot']],
["Paradox Cave Lower - Left", False, ['Flute', 'Magic Mirror']],
["Paradox Cave Lower - Left", False, ['Flute', 'Hammer']],
- ["Paradox Cave Lower - Left", True, ['Flute', 'Hookshot']],
- ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']],
- ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
- ["Paradox Cave Lower - Left", True, ['Flute', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Lower - Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod', 'Cane of Somaria']],
+ ["Paradox Cave Lower - Left", True, ['Cane of Somaria', 'Flute', 'Hookshot']],
+ ["Paradox Cave Lower - Left", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot']],
+ ["Paradox Cave Lower - Left", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Lower - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror', 'Hammer']],
["Paradox Cave Lower - Middle", False, []],
["Paradox Cave Lower - Middle", False, [], ['Progressive Glove', 'Flute']],
@@ -101,10 +104,11 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Middle", False, ['Progressive Glove', 'Hookshot']],
["Paradox Cave Lower - Middle", False, ['Flute', 'Magic Mirror']],
["Paradox Cave Lower - Middle", False, ['Flute', 'Hammer']],
- ["Paradox Cave Lower - Middle", True, ['Flute', 'Hookshot']],
- ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Hookshot']],
- ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
- ["Paradox Cave Lower - Middle", True, ['Flute', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Lower - Middle", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod', 'Cane of Somaria']],
+ ["Paradox Cave Lower - Middle", True, ['Cane of Somaria', 'Flute', 'Hookshot']],
+ ["Paradox Cave Lower - Middle", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot']],
+ ["Paradox Cave Lower - Middle", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Lower - Middle", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror', 'Hammer']],
["Paradox Cave Lower - Right", False, []],
["Paradox Cave Lower - Right", False, [], ['Progressive Glove', 'Flute']],
@@ -115,10 +119,11 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Right", False, ['Progressive Glove', 'Hookshot']],
["Paradox Cave Lower - Right", False, ['Flute', 'Magic Mirror']],
["Paradox Cave Lower - Right", False, ['Flute', 'Hammer']],
- ["Paradox Cave Lower - Right", True, ['Flute', 'Hookshot']],
- ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']],
- ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
- ["Paradox Cave Lower - Right", True, ['Flute', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Lower - Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod', 'Cane of Somaria']],
+ ["Paradox Cave Lower - Right", True, ['Cane of Somaria', 'Flute', 'Hookshot']],
+ ["Paradox Cave Lower - Right", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot']],
+ ["Paradox Cave Lower - Right", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Lower - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror', 'Hammer']],
["Paradox Cave Lower - Far Right", False, []],
["Paradox Cave Lower - Far Right", False, [], ['Progressive Glove', 'Flute']],
@@ -129,10 +134,11 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Far Right", False, ['Progressive Glove', 'Hookshot']],
["Paradox Cave Lower - Far Right", False, ['Flute', 'Magic Mirror']],
["Paradox Cave Lower - Far Right", False, ['Flute', 'Hammer']],
- ["Paradox Cave Lower - Far Right", True, ['Flute', 'Hookshot']],
- ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']],
- ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
- ["Paradox Cave Lower - Far Right", True, ['Flute', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Lower - Far Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod', 'Cane of Somaria']],
+ ["Paradox Cave Lower - Far Right", True, ['Cane of Somaria', 'Flute', 'Hookshot']],
+ ["Paradox Cave Lower - Far Right", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot']],
+ ["Paradox Cave Lower - Far Right", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Lower - Far Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror', 'Hammer']],
["Paradox Cave Upper - Left", False, []],
["Paradox Cave Upper - Left", False, [], ['Progressive Glove', 'Flute']],
@@ -143,10 +149,11 @@ def testEastDeathMountain(self):
["Paradox Cave Upper - Left", False, ['Progressive Glove', 'Hookshot']],
["Paradox Cave Upper - Left", False, ['Flute', 'Magic Mirror']],
["Paradox Cave Upper - Left", False, ['Flute', 'Hammer']],
- ["Paradox Cave Upper - Left", True, ['Flute', 'Hookshot']],
- ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']],
- ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
- ["Paradox Cave Upper - Left", True, ['Flute', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Upper - Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Hookshot']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror', 'Hammer']],
["Paradox Cave Upper - Right", False, []],
["Paradox Cave Upper - Right", False, [], ['Progressive Glove', 'Flute']],
@@ -157,10 +164,11 @@ def testEastDeathMountain(self):
["Paradox Cave Upper - Right", False, ['Progressive Glove', 'Hookshot']],
["Paradox Cave Upper - Right", False, ['Flute', 'Magic Mirror']],
["Paradox Cave Upper - Right", False, ['Flute', 'Hammer']],
- ["Paradox Cave Upper - Right", True, ['Flute', 'Hookshot']],
- ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']],
- ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
- ["Paradox Cave Upper - Right", True, ['Flute', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Upper - Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Hookshot']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror', 'Hammer']],
])
def testWestDarkWorldDeathMountain(self):
diff --git a/worlds/alttp/test/minor_glitches/TestLightWorld.py b/worlds/alttp/test/minor_glitches/TestLightWorld.py
index bdfdc2349691..017f2d64a8f8 100644
--- a/worlds/alttp/test/minor_glitches/TestLightWorld.py
+++ b/worlds/alttp/test/minor_glitches/TestLightWorld.py
@@ -29,17 +29,21 @@ def testLightWorld(self):
["Kakariko Tavern", True, []],
- ["Chicken House", True, []],
+ ["Chicken House", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Chicken House", True, ['Bomb Upgrade (+5)']],
- ["Aginah's Cave", True, []],
+ ["Aginah's Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Aginah's Cave", True, ['Bomb Upgrade (+5)']],
- ["Sahasrahla's Hut - Left", True, []],
+ ["Sahasrahla's Hut - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']],
+ ["Sahasrahla's Hut - Left", True, ['Bomb Upgrade (+5)']],
+ ["Sahasrahla's Hut - Middle", True, ['Pegasus Boots']],
+ ["Sahasrahla's Hut - Middle", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']],
+ ["Sahasrahla's Hut - Right", True, ['Bomb Upgrade (+5)']],
+ ["Sahasrahla's Hut - Right", True, ['Pegasus Boots']],
- ["Sahasrahla's Hut - Middle", True, []],
-
- ["Sahasrahla's Hut - Right", True, []],
-
- ["Kakariko Well - Top", True, []],
+ ["Kakariko Well - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Kakariko Well - Top", True, ['Bomb Upgrade (+5)']],
["Kakariko Well - Left", True, []],
@@ -49,7 +53,8 @@ def testLightWorld(self):
["Kakariko Well - Bottom", True, []],
- ["Blind's Hideout - Top", True, []],
+ ["Blind's Hideout - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Blind's Hideout - Top", True, ['Bomb Upgrade (+5)']],
["Blind's Hideout - Left", True, []],
@@ -63,15 +68,19 @@ def testLightWorld(self):
["Bonk Rock Cave", False, [], ['Pegasus Boots']],
["Bonk Rock Cave", True, ['Pegasus Boots']],
- ["Mini Moldorm Cave - Far Left", True, []],
-
- ["Mini Moldorm Cave - Left", True, []],
+ ["Mini Moldorm Cave - Far Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Far Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword']],
+ ["Mini Moldorm Cave - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword']],
+ ["Mini Moldorm Cave - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword']],
+ ["Mini Moldorm Cave - Far Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Far Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword']],
+ ["Mini Moldorm Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Progressive Sword']],
- ["Mini Moldorm Cave - Right", True, []],
-
- ["Mini Moldorm Cave - Far Right", True, []],
-
- ["Ice Rod Cave", True, []],
+ ["Ice Rod Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Ice Rod Cave", True, ['Bomb Upgrade (+5)']],
["Bottle Merchant", True, []],
@@ -131,11 +140,12 @@ def testLightWorld(self):
["Graveyard Cave", False, []],
["Graveyard Cave", False, [], ['Magic Mirror']],
["Graveyard Cave", False, [], ['Moon Pearl']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Hammer']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Hammer', 'Hookshot']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
+ ["Graveyard Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Hammer']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Hammer', 'Hookshot']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
["Checkerboard Cave", False, []],
["Checkerboard Cave", False, [], ['Progressive Glove']],
@@ -143,8 +153,6 @@ def testLightWorld(self):
["Checkerboard Cave", False, [], ['Magic Mirror']],
["Checkerboard Cave", True, ['Flute', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']],
- ["Mini Moldorm Cave - Generous Guy", True, []],
-
["Library", False, []],
["Library", False, [], ['Pegasus Boots']],
["Library", True, ['Pegasus Boots']],
@@ -155,7 +163,10 @@ def testLightWorld(self):
["Potion Shop", False, [], ['Mushroom']],
["Potion Shop", True, ['Mushroom']],
- ["Maze Race", True, []],
+ ["Maze Race", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Magic Mirror', 'Pegasus Boots']],
+ ["Maze Race", True, ['Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
+ ["Maze Race", True, ['Bomb Upgrade (+5)']],
+ ["Maze Race", True, ['Pegasus Boots']],
["Desert Ledge", False, []],
["Desert Ledge", False, [], ['Book of Mudora', 'Flute']],
diff --git a/worlds/alttp/test/minor_glitches/TestMinor.py b/worlds/alttp/test/minor_glitches/TestMinor.py
index d5cfd3095b9c..c7de74d3a6c3 100644
--- a/worlds/alttp/test/minor_glitches/TestMinor.py
+++ b/worlds/alttp/test/minor_glitches/TestMinor.py
@@ -10,7 +10,9 @@
class TestMinor(TestBase, LTTPTestBase):
def setUp(self):
self.world_setup()
- self.multiworld.logic[1] = "minorglitches"
+ self.multiworld.glitches_required[1] = "minor_glitches"
+ self.multiworld.bombless_start[1].value = True
+ self.multiworld.shuffle_capacity_upgrades[1].value = True
self.multiworld.difficulty_requirements[1] = difficulties['normal']
self.multiworld.worlds[1].er_seed = 0
self.multiworld.worlds[1].create_regions()
diff --git a/worlds/alttp/test/owg/TestDarkWorld.py b/worlds/alttp/test/owg/TestDarkWorld.py
index 93324656bd93..c671f6485c92 100644
--- a/worlds/alttp/test/owg/TestDarkWorld.py
+++ b/worlds/alttp/test/owg/TestDarkWorld.py
@@ -7,48 +7,53 @@ def testSouthDarkWorld(self):
self.run_location_tests([
["Hype Cave - Top", False, []],
["Hype Cave - Top", False, [], ['Moon Pearl']],
- ["Hype Cave - Top", True, ['Moon Pearl', 'Pegasus Boots']],
- ["Hype Cave - Top", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
- ["Hype Cave - Top", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
- ["Hype Cave - Top", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Hype Cave - Top", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
- ["Hype Cave - Top", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
+ ["Hype Cave - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']],
+ ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
+ ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
+ ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
["Hype Cave - Middle Right", False, []],
["Hype Cave - Middle Right", False, [], ['Moon Pearl']],
- ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Pegasus Boots']],
- ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
- ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
- ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
- ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
+ ["Hype Cave - Middle Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']],
+ ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
+ ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
+ ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
["Hype Cave - Middle Left", False, []],
["Hype Cave - Middle Left", False, [], ['Moon Pearl']],
- ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Pegasus Boots']],
- ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
- ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
- ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
- ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
+ ["Hype Cave - Middle Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']],
+ ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
+ ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
+ ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
["Hype Cave - Bottom", False, []],
["Hype Cave - Bottom", False, [], ['Moon Pearl']],
- ["Hype Cave - Bottom", True, ['Moon Pearl', 'Pegasus Boots']],
- ["Hype Cave - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
- ["Hype Cave - Bottom", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
- ["Hype Cave - Bottom", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Hype Cave - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
- ["Hype Cave - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
+ ["Hype Cave - Bottom", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']],
+ ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
+ ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
+ ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
["Hype Cave - Generous Guy", False, []],
["Hype Cave - Generous Guy", False, [], ['Moon Pearl']],
- ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Pegasus Boots']],
- ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
- ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
- ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
- ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
+ ["Hype Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']],
+ ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
+ ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
+ ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
["Stumpy", False, []],
["Stumpy", False, [], ['Moon Pearl']],
@@ -129,13 +134,14 @@ def testWestDarkWorld(self):
self.run_location_tests([
["Brewery", False, []],
["Brewery", False, [], ['Moon Pearl']],
+ ["Brewery", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
["Brewery", False, [], ['Pegasus Boots', 'Magic Mirror', 'Hookshot', 'Progressive Glove']],
- ["Brewery", True, ['Moon Pearl', 'Pegasus Boots']],
- ["Brewery", True, ['Moon Pearl', 'Flute', 'Magic Mirror']],
- ["Brewery", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
- ["Brewery", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Brewery", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
- ["Brewery", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
+ ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots']],
+ ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Flute', 'Magic Mirror']],
+ ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
+ ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
["C-Shaped House", False, []],
["C-Shaped House", False, [], ['Moon Pearl', 'Magic Mirror']],
diff --git a/worlds/alttp/test/owg/TestDeathMountain.py b/worlds/alttp/test/owg/TestDeathMountain.py
index 41031c65c593..0933b2881e2d 100644
--- a/worlds/alttp/test/owg/TestDeathMountain.py
+++ b/worlds/alttp/test/owg/TestDeathMountain.py
@@ -48,9 +48,10 @@ def testEastDeathMountain(self):
["Mimic Cave", False, [], ['Hammer']],
["Mimic Cave", False, [], ['Pegasus Boots', 'Flute', 'Lamp']],
["Mimic Cave", False, [], ['Pegasus Boots', 'Flute', 'Progressive Glove']],
- ["Mimic Cave", True, ['Magic Mirror', 'Hammer', 'Pegasus Boots']],
- ["Mimic Cave", True, ['Magic Mirror', 'Hammer', 'Progressive Glove', 'Lamp']],
- ["Mimic Cave", True, ['Magic Mirror', 'Hammer', 'Flute']],
+ ["Mimic Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Hookshot', 'Hammer']],
+ ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Magic Mirror', 'Hammer', 'Pegasus Boots']],
+ ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Magic Mirror', 'Hammer', 'Progressive Glove', 'Lamp']],
+ ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Magic Mirror', 'Hammer', 'Flute']],
["Spiral Cave", False, []],
["Spiral Cave", False, [], ['Pegasus Boots', 'Progressive Glove', 'Flute']],
@@ -64,65 +65,72 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Far Left", False, []],
["Paradox Cave Lower - Far Left", False, [], ['Pegasus Boots', 'Progressive Glove', 'Flute']],
["Paradox Cave Lower - Far Left", False, [], ['Pegasus Boots', 'Magic Mirror', 'Hookshot']],
- ["Paradox Cave Lower - Far Left", True, ['Pegasus Boots']],
- ["Paradox Cave Lower - Far Left", True, ['Flute', 'Hookshot']],
- ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']],
- ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror']],
- ["Paradox Cave Lower - Far Left", True, ['Flute', 'Magic Mirror']],
+ ["Paradox Cave Lower - Far Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod', 'Cane of Somaria']],
+ ["Paradox Cave Lower - Far Left", True, ['Fire Rod', 'Pegasus Boots']],
+ ["Paradox Cave Lower - Far Left", True, ['Cane of Somaria', 'Flute', 'Hookshot']],
+ ["Paradox Cave Lower - Far Left", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot']],
+ ["Paradox Cave Lower - Far Left", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Magic Mirror']],
+ ["Paradox Cave Lower - Far Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror']],
["Paradox Cave Lower - Left", False, []],
["Paradox Cave Lower - Left", False, [], ['Pegasus Boots', 'Progressive Glove', 'Flute']],
["Paradox Cave Lower - Left", False, [], ['Pegasus Boots', 'Magic Mirror', 'Hookshot']],
- ["Paradox Cave Lower - Left", True, ['Pegasus Boots']],
- ["Paradox Cave Lower - Left", True, ['Flute', 'Hookshot']],
- ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']],
- ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror']],
- ["Paradox Cave Lower - Left", True, ['Flute', 'Magic Mirror']],
+ ["Paradox Cave Lower - Left", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod', 'Cane of Somaria']],
+ ["Paradox Cave Lower - Left", True, ['Fire Rod', 'Pegasus Boots']],
+ ["Paradox Cave Lower - Left", True, ['Cane of Somaria', 'Flute', 'Hookshot']],
+ ["Paradox Cave Lower - Left", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot']],
+ ["Paradox Cave Lower - Left", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Magic Mirror']],
+ ["Paradox Cave Lower - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror']],
["Paradox Cave Lower - Middle", False, []],
["Paradox Cave Lower - Middle", False, [], ['Pegasus Boots', 'Progressive Glove', 'Flute']],
["Paradox Cave Lower - Middle", False, [], ['Pegasus Boots', 'Magic Mirror', 'Hookshot']],
- ["Paradox Cave Lower - Middle", True, ['Pegasus Boots']],
- ["Paradox Cave Lower - Middle", True, ['Flute', 'Hookshot']],
- ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Hookshot']],
- ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Magic Mirror']],
- ["Paradox Cave Lower - Middle", True, ['Flute', 'Magic Mirror']],
+ ["Paradox Cave Lower - Middle", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod', 'Cane of Somaria']],
+ ["Paradox Cave Lower - Middle", True, ['Fire Rod', 'Pegasus Boots']],
+ ["Paradox Cave Lower - Middle", True, ['Cane of Somaria', 'Flute', 'Hookshot']],
+ ["Paradox Cave Lower - Middle", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot']],
+ ["Paradox Cave Lower - Middle", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Magic Mirror']],
+ ["Paradox Cave Lower - Middle", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror']],
["Paradox Cave Lower - Right", False, []],
["Paradox Cave Lower - Right", False, [], ['Pegasus Boots', 'Progressive Glove', 'Flute']],
["Paradox Cave Lower - Right", False, [], ['Pegasus Boots', 'Magic Mirror', 'Hookshot']],
- ["Paradox Cave Lower - Right", True, ['Pegasus Boots']],
- ["Paradox Cave Lower - Right", True, ['Flute', 'Hookshot']],
- ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']],
- ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror']],
- ["Paradox Cave Lower - Right", True, ['Flute', 'Magic Mirror']],
+ ["Paradox Cave Lower - Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod', 'Cane of Somaria']],
+ ["Paradox Cave Lower - Right", True, ['Fire Rod', 'Pegasus Boots']],
+ ["Paradox Cave Lower - Right", True, ['Cane of Somaria', 'Flute', 'Hookshot']],
+ ["Paradox Cave Lower - Right", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot']],
+ ["Paradox Cave Lower - Right", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Magic Mirror']],
+ ["Paradox Cave Lower - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror']],
["Paradox Cave Lower - Far Right", False, []],
["Paradox Cave Lower - Far Right", False, [], ['Pegasus Boots', 'Progressive Glove', 'Flute']],
["Paradox Cave Lower - Far Right", False, [], ['Pegasus Boots', 'Magic Mirror', 'Hookshot']],
- ["Paradox Cave Lower - Far Right", True, ['Pegasus Boots']],
- ["Paradox Cave Lower - Far Right", True, ['Flute', 'Hookshot']],
- ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']],
- ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror']],
- ["Paradox Cave Lower - Far Right", True, ['Flute', 'Magic Mirror']],
+ ["Paradox Cave Lower - Far Right", False, ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Progressive Sword', 'Progressive Bow', 'Fire Rod', 'Cane of Somaria']],
+ ["Paradox Cave Lower - Far Right", True, ['Fire Rod', 'Pegasus Boots']],
+ ["Paradox Cave Lower - Far Right", True, ['Cane of Somaria', 'Flute', 'Hookshot']],
+ ["Paradox Cave Lower - Far Right", True, ['Progressive Sword', 'Progressive Sword', 'Progressive Glove', 'Lamp', 'Hookshot']],
+ ["Paradox Cave Lower - Far Right", True, ['Progressive Bow', 'Progressive Glove', 'Lamp', 'Magic Mirror']],
+ ["Paradox Cave Lower - Far Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror']],
["Paradox Cave Upper - Left", False, []],
["Paradox Cave Upper - Left", False, [], ['Pegasus Boots', 'Progressive Glove', 'Flute']],
["Paradox Cave Upper - Left", False, [], ['Pegasus Boots', 'Magic Mirror', 'Hookshot']],
- ["Paradox Cave Upper - Left", True, ['Pegasus Boots']],
- ["Paradox Cave Upper - Left", True, ['Flute', 'Hookshot']],
- ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']],
- ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror']],
- ["Paradox Cave Upper - Left", True, ['Flute', 'Magic Mirror']],
+ ["Paradox Cave Upper - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Pegasus Boots']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Hookshot']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Magic Mirror']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror']],
["Paradox Cave Upper - Right", False, []],
["Paradox Cave Upper - Right", False, [], ['Pegasus Boots', 'Progressive Glove', 'Flute']],
["Paradox Cave Upper - Right", False, [], ['Pegasus Boots', 'Magic Mirror', 'Hookshot']],
- ["Paradox Cave Upper - Right", True, ['Pegasus Boots']],
- ["Paradox Cave Upper - Right", True, ['Flute', 'Hookshot']],
- ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']],
- ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror']],
- ["Paradox Cave Upper - Right", True, ['Flute', 'Magic Mirror']],
+ ["Paradox Cave Upper - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Pegasus Boots']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Hookshot']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Magic Mirror']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror']],
])
def testWestDarkWorldDeathMountain(self):
diff --git a/worlds/alttp/test/owg/TestDungeons.py b/worlds/alttp/test/owg/TestDungeons.py
index 4f878969679a..e43e18d16cf2 100644
--- a/worlds/alttp/test/owg/TestDungeons.py
+++ b/worlds/alttp/test/owg/TestDungeons.py
@@ -6,13 +6,14 @@ class TestDungeons(TestVanillaOWG):
def testFirstDungeonChests(self):
self.run_location_tests([
["Hyrule Castle - Map Chest", True, []],
- ["Hyrule Castle - Map Guard Key Drop", True, []],
+ ["Hyrule Castle - Map Guard Key Drop", False, []],
+ ["Hyrule Castle - Map Guard Key Drop", True, ['Progressive Sword']],
["Sanctuary", True, []],
["Sewers - Secret Room - Left", False, []],
- ["Sewers - Secret Room - Left", True, ['Progressive Glove']],
- ["Sewers - Secret Room - Left", True, ['Lamp', 'Small Key (Hyrule Castle)']],
+ ["Sewers - Secret Room - Left", True, ['Pegasus Boots', 'Progressive Glove']],
+ ["Sewers - Secret Room - Left", True, ['Bomb Upgrade (+5)', 'Lamp', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)', 'Small Key (Hyrule Castle)']],
["Eastern Palace - Compass Chest", True, []],
@@ -25,8 +26,8 @@ def testFirstDungeonChests(self):
["Desert Palace - Boss", False, [], ['Small Key (Desert Palace)']],
["Desert Palace - Boss", False, [], ['Big Key (Desert Palace)']],
["Desert Palace - Boss", False, [], ['Lamp', 'Fire Rod']],
- ["Desert Palace - Boss", True, ['Progressive Sword', 'Small Key (Desert Palace)', 'Pegasus Boots', 'Lamp', 'Big Key (Desert Palace)']],
- ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Pegasus Boots', 'Fire Rod', 'Big Key (Desert Palace)']],
+ ["Desert Palace - Boss", True, ['Progressive Sword', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Pegasus Boots', 'Lamp', 'Big Key (Desert Palace)']],
+ ["Desert Palace - Boss", True, ['Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Small Key (Desert Palace)', 'Pegasus Boots', 'Fire Rod', 'Big Key (Desert Palace)']],
["Tower of Hera - Basement Cage", False, []],
["Tower of Hera - Basement Cage", False, [], ['Pegasus Boots', "Flute", "Progressive Glove"]],
@@ -41,10 +42,9 @@ def testFirstDungeonChests(self):
["Castle Tower - Room 03", False, []],
["Castle Tower - Room 03", False, ['Progressive Sword'], ['Progressive Sword', 'Cape', 'Beat Agahnim 1']],
- ["Castle Tower - Room 03", False, [], ['Progressive Sword', 'Hammer', 'Progressive Bow', 'Fire Rod', 'Ice Rod', 'Cane of Somaria', 'Cane of Byrna']],
["Castle Tower - Room 03", True, ['Progressive Sword', 'Progressive Sword']],
- ["Castle Tower - Room 03", True, ['Cape', 'Progressive Bow']],
- ["Castle Tower - Room 03", True, ['Beat Agahnim 1', 'Fire Rod']],
+ ["Castle Tower - Room 03", True, ['Progressive Sword', 'Cape']],
+ ["Castle Tower - Room 03", True, ['Progressive Sword', 'Beat Agahnim 1']],
["Palace of Darkness - Shooter Room", False, []],
["Palace of Darkness - Shooter Room", False, [], ['Moon Pearl']],
@@ -69,9 +69,10 @@ def testFirstDungeonChests(self):
["Skull Woods - Big Chest", False, []],
["Skull Woods - Big Chest", False, [], ['Big Key (Skull Woods)']],
+ ["Skull Woods - Big Chest", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
#todo: Bomb Jump in logic?
#["Skull Woods - Big Chest", True, ['Magic Mirror', 'Pegasus Boots', 'Big Key (Skull Woods)']],
- ["Skull Woods - Big Chest", True, ['Moon Pearl', 'Pegasus Boots', 'Big Key (Skull Woods)']],
+ ["Skull Woods - Big Chest", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Pegasus Boots', 'Big Key (Skull Woods)']],
["Skull Woods - Big Key Chest", False, []],
["Skull Woods - Big Key Chest", True, ['Magic Mirror', 'Pegasus Boots']],
@@ -89,10 +90,10 @@ def testFirstDungeonChests(self):
["Ice Palace - Compass Chest", False, []],
["Ice Palace - Compass Chest", False, [], ['Fire Rod', 'Bombos']],
["Ice Palace - Compass Chest", False, [], ['Fire Rod', 'Progressive Sword']],
- ["Ice Palace - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots', 'Flippers', 'Fire Rod']],
- ["Ice Palace - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots', 'Flippers', 'Bombos', 'Progressive Sword']],
- ["Ice Palace - Compass Chest", True, ['Progressive Glove', 'Progressive Glove', 'Fire Rod']],
- ["Ice Palace - Compass Chest", True, ['Progressive Glove', 'Progressive Glove', 'Bombos', 'Progressive Sword']],
+ ["Ice Palace - Compass Chest", True, ['Small Key (Ice Palace)', 'Moon Pearl', 'Pegasus Boots', 'Flippers', 'Fire Rod']],
+ ["Ice Palace - Compass Chest", True, ['Small Key (Ice Palace)', 'Moon Pearl', 'Pegasus Boots', 'Flippers', 'Bombos', 'Progressive Sword']],
+ ["Ice Palace - Compass Chest", True, ['Small Key (Ice Palace)', 'Progressive Glove', 'Progressive Glove', 'Fire Rod']],
+ ["Ice Palace - Compass Chest", True, ['Small Key (Ice Palace)', 'Progressive Glove', 'Progressive Glove', 'Bombos', 'Progressive Sword']],
["Misery Mire - Bridge Chest", False, []],
["Misery Mire - Bridge Chest", False, [], ['Moon Pearl']],
@@ -104,15 +105,15 @@ def testFirstDungeonChests(self):
["Turtle Rock - Compass Chest", False, [], ['Cane of Somaria']],
#todo: does clip require sword?
#["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
- ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Progressive Sword']],
+ ["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots', '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)', 'Progressive Sword']],
["Turtle Rock - Compass Chest", True, ['Moon Pearl', 'Pegasus Boots', 'Cane of Somaria', 'Progressive Sword', 'Quake']],
- ["Turtle Rock - Compass Chest", True, ['Pegasus Boots', 'Magic Mirror', 'Cane of Somaria', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
+ ["Turtle Rock - Compass Chest", True, ['Pegasus Boots', 'Magic Mirror', '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", False, []],
#todo: does clip require sword?
#["Turtle Rock - Chain Chomps", True, ['Moon Pearl', 'Pegasus Boots']],
- ["Turtle Rock - Chain Chomps", True, ['Moon Pearl', 'Pegasus Boots', 'Progressive Sword']],
- ["Turtle Rock - Chain Chomps", True, ['Pegasus Boots', 'Magic Mirror']],
+ ["Turtle Rock - Chain Chomps", True, ['Moon Pearl', 'Pegasus Boots', 'Progressive Sword', 'Progressive Sword']],
+ ["Turtle Rock - Chain Chomps", True, ['Pegasus Boots', 'Magic Mirror', 'Progressive Bow']],
["Turtle Rock - Crystaroller Room", False, []],
["Turtle Rock - Crystaroller Room", False, [], ['Big Key (Turtle Rock)']],
diff --git a/worlds/alttp/test/owg/TestLightWorld.py b/worlds/alttp/test/owg/TestLightWorld.py
index f3f1ba0c2703..84342a33c856 100644
--- a/worlds/alttp/test/owg/TestLightWorld.py
+++ b/worlds/alttp/test/owg/TestLightWorld.py
@@ -25,17 +25,21 @@ def testLightWorld(self):
["Kakariko Tavern", True, []],
- ["Chicken House", True, []],
+ ["Chicken House", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Chicken House", True, ['Bomb Upgrade (+5)']],
- ["Aginah's Cave", True, []],
+ ["Aginah's Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Aginah's Cave", True, ['Bomb Upgrade (+5)']],
- ["Sahasrahla's Hut - Left", True, []],
+ ["Sahasrahla's Hut - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']],
+ ["Sahasrahla's Hut - Left", True, ['Bomb Upgrade (+5)']],
+ ["Sahasrahla's Hut - Middle", True, ['Pegasus Boots']],
+ ["Sahasrahla's Hut - Middle", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']],
+ ["Sahasrahla's Hut - Right", True, ['Bomb Upgrade (+5)']],
+ ["Sahasrahla's Hut - Right", True, ['Pegasus Boots']],
- ["Sahasrahla's Hut - Middle", True, []],
-
- ["Sahasrahla's Hut - Right", True, []],
-
- ["Kakariko Well - Top", True, []],
+ ["Kakariko Well - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Kakariko Well - Top", True, ['Bomb Upgrade (+5)']],
["Kakariko Well - Left", True, []],
@@ -45,7 +49,8 @@ def testLightWorld(self):
["Kakariko Well - Bottom", True, []],
- ["Blind's Hideout - Top", True, []],
+ ["Blind's Hideout - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Blind's Hideout - Top", True, ['Bomb Upgrade (+5)']],
["Blind's Hideout - Left", True, []],
@@ -59,15 +64,19 @@ def testLightWorld(self):
["Bonk Rock Cave", False, [], ['Pegasus Boots']],
["Bonk Rock Cave", True, ['Pegasus Boots']],
- ["Mini Moldorm Cave - Far Left", True, []],
-
- ["Mini Moldorm Cave - Left", True, []],
+ ["Mini Moldorm Cave - Far Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Far Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword']],
+ ["Mini Moldorm Cave - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword']],
+ ["Mini Moldorm Cave - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword']],
+ ["Mini Moldorm Cave - Far Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Far Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword']],
+ ["Mini Moldorm Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Progressive Sword']],
- ["Mini Moldorm Cave - Right", True, []],
-
- ["Mini Moldorm Cave - Far Right", True, []],
-
- ["Ice Rod Cave", True, []],
+ ["Ice Rod Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Ice Rod Cave", True, ['Bomb Upgrade (+5)']],
["Bottle Merchant", True, []],
@@ -126,12 +135,13 @@ def testLightWorld(self):
["Graveyard Cave", False, []],
["Graveyard Cave", False, [], ['Pegasus Boots', 'Magic Mirror']],
["Graveyard Cave", False, [], ['Pegasus Boots', 'Moon Pearl']],
- ["Graveyard Cave", True, ['Pegasus Boots']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Hammer']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Hammer', 'Hookshot']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
+ ["Graveyard Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Pegasus Boots']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Hammer']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Hammer', 'Hookshot']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
["Checkerboard Cave", False, []],
["Checkerboard Cave", False, [], ['Progressive Glove']],
@@ -140,8 +150,6 @@ def testLightWorld(self):
["Checkerboard Cave", True, ['Pegasus Boots', 'Progressive Glove']],
["Checkerboard Cave", True, ['Flute', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']],
- ["Mini Moldorm Cave - Generous Guy", True, []],
-
["Library", False, []],
["Library", False, [], ['Pegasus Boots']],
["Library", True, ['Pegasus Boots']],
@@ -152,7 +160,10 @@ def testLightWorld(self):
["Potion Shop", False, [], ['Mushroom']],
["Potion Shop", True, ['Mushroom']],
- ["Maze Race", True, []],
+ ["Maze Race", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Magic Mirror', 'Pegasus Boots']],
+ ["Maze Race", True, ['Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
+ ["Maze Race", True, ['Bomb Upgrade (+5)']],
+ ["Maze Race", True, ['Pegasus Boots']],
["Desert Ledge", False, []],
["Desert Ledge", False, [], ['Pegasus Boots', 'Book of Mudora', 'Flute']],
diff --git a/worlds/alttp/test/owg/TestVanillaOWG.py b/worlds/alttp/test/owg/TestVanillaOWG.py
index 37b0b6ccb868..1f8f2707edaa 100644
--- a/worlds/alttp/test/owg/TestVanillaOWG.py
+++ b/worlds/alttp/test/owg/TestVanillaOWG.py
@@ -11,7 +11,9 @@ class TestVanillaOWG(TestBase, LTTPTestBase):
def setUp(self):
self.world_setup()
self.multiworld.difficulty_requirements[1] = difficulties['normal']
- self.multiworld.logic[1] = "owglitches"
+ self.multiworld.glitches_required[1] = "overworld_glitches"
+ self.multiworld.bombless_start[1].value = True
+ self.multiworld.shuffle_capacity_upgrades[1].value = True
self.multiworld.worlds[1].er_seed = 0
self.multiworld.worlds[1].create_regions()
self.multiworld.worlds[1].create_items()
diff --git a/worlds/alttp/test/vanilla/TestDarkWorld.py b/worlds/alttp/test/vanilla/TestDarkWorld.py
index ecb3e5583098..8ff09c527de8 100644
--- a/worlds/alttp/test/vanilla/TestDarkWorld.py
+++ b/worlds/alttp/test/vanilla/TestDarkWorld.py
@@ -7,43 +7,48 @@ def testSouthDarkWorld(self):
self.run_location_tests([
["Hype Cave - Top", False, []],
["Hype Cave - Top", False, [], ['Moon Pearl']],
- ["Hype Cave - Top", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
- ["Hype Cave - Top", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
- ["Hype Cave - Top", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Hype Cave - Top", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
- ["Hype Cave - Top", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
+ ["Hype Cave - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
+ ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
+ ["Hype Cave - Top", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
["Hype Cave - Middle Right", False, []],
["Hype Cave - Middle Right", False, [], ['Moon Pearl']],
- ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
- ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
- ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
- ["Hype Cave - Middle Right", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
+ ["Hype Cave - Middle Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
+ ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
+ ["Hype Cave - Middle Right", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
["Hype Cave - Middle Left", False, []],
["Hype Cave - Middle Left", False, [], ['Moon Pearl']],
- ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
- ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
- ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
- ["Hype Cave - Middle Left", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
+ ["Hype Cave - Middle Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
+ ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
+ ["Hype Cave - Middle Left", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
["Hype Cave - Bottom", False, []],
["Hype Cave - Bottom", False, [], ['Moon Pearl']],
- ["Hype Cave - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
- ["Hype Cave - Bottom", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
- ["Hype Cave - Bottom", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Hype Cave - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
- ["Hype Cave - Bottom", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
+ ["Hype Cave - Bottom", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
+ ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
+ ["Hype Cave - Bottom", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
["Hype Cave - Generous Guy", False, []],
["Hype Cave - Generous Guy", False, [], ['Moon Pearl']],
- ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
- ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
- ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
- ["Hype Cave - Generous Guy", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
+ ["Hype Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Hammer']],
+ ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
+ ["Hype Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
["Stumpy", False, []],
["Stumpy", False, [], ['Moon Pearl']],
@@ -66,10 +71,11 @@ def testWestDarkWorld(self):
self.run_location_tests([
["Brewery", False, []],
["Brewery", False, [], ['Moon Pearl']],
- ["Brewery", True, ['Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
- ["Brewery", True, ['Moon Pearl', 'Progressive Glove', 'Hammer']],
- ["Brewery", True, ['Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
- ["Brewery", True, ['Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
+ ["Brewery", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Progressive Glove']],
+ ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Progressive Glove', 'Hammer']],
+ ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
+ ["Brewery", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
["C-Shaped House", False, []],
["C-Shaped House", False, [], ['Moon Pearl']],
diff --git a/worlds/alttp/test/vanilla/TestDeathMountain.py b/worlds/alttp/test/vanilla/TestDeathMountain.py
index ecb3831f6ad1..a559d8869c2f 100644
--- a/worlds/alttp/test/vanilla/TestDeathMountain.py
+++ b/worlds/alttp/test/vanilla/TestDeathMountain.py
@@ -48,7 +48,8 @@ def testEastDeathMountain(self):
["Mimic Cave", False, [], ['Moon Pearl']],
["Mimic Cave", False, [], ['Cane of Somaria']],
["Mimic Cave", False, ['Small Key (Turtle Rock)'], ['Small Key (Turtle Rock)']],
- ["Mimic Cave", True, ['Quake', 'Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Moon Pearl', 'Cane of Somaria', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
+ ["Mimic Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mimic Cave", True, ['Bomb Upgrade (+5)', 'Quake', 'Progressive Sword', 'Flute', 'Progressive Glove', 'Progressive Glove', 'Hammer', 'Moon Pearl', 'Cane of Somaria', 'Magic Mirror', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)', 'Small Key (Turtle Rock)']],
["Spiral Cave", False, []],
["Spiral Cave", False, [], ['Progressive Glove', 'Flute']],
@@ -73,10 +74,10 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Far Left", False, ['Progressive Glove', 'Hookshot']],
["Paradox Cave Lower - Far Left", False, ['Flute', 'Magic Mirror']],
["Paradox Cave Lower - Far Left", False, ['Flute', 'Hammer']],
- ["Paradox Cave Lower - Far Left", True, ['Flute', 'Hookshot']],
- ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']],
- ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
- ["Paradox Cave Lower - Far Left", True, ['Flute', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Lower - Far Left", True, ['Flute', 'Hookshot', 'Cane of Somaria']],
+ ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Bomb Upgrade (+5)']],
+ ["Paradox Cave Lower - Far Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer', 'Progressive Sword', 'Progressive Sword']],
+ ["Paradox Cave Lower - Far Left", True, ['Flute', 'Magic Mirror', 'Hammer', 'Fire Rod']],
["Paradox Cave Lower - Left", False, []],
["Paradox Cave Lower - Left", False, [], ['Progressive Glove', 'Flute']],
@@ -87,10 +88,10 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Left", False, ['Progressive Glove', 'Hookshot']],
["Paradox Cave Lower - Left", False, ['Flute', 'Magic Mirror']],
["Paradox Cave Lower - Left", False, ['Flute', 'Hammer']],
- ["Paradox Cave Lower - Left", True, ['Flute', 'Hookshot']],
- ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']],
- ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
- ["Paradox Cave Lower - Left", True, ['Flute', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Lower - Left", True, ['Flute', 'Hookshot', 'Cane of Somaria']],
+ ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Bomb Upgrade (+5)']],
+ ["Paradox Cave Lower - Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer', 'Progressive Sword', 'Progressive Sword']],
+ ["Paradox Cave Lower - Left", True, ['Flute', 'Magic Mirror', 'Hammer', 'Fire Rod']],
["Paradox Cave Lower - Middle", False, []],
["Paradox Cave Lower - Middle", False, [], ['Progressive Glove', 'Flute']],
@@ -101,10 +102,10 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Middle", False, ['Progressive Glove', 'Hookshot']],
["Paradox Cave Lower - Middle", False, ['Flute', 'Magic Mirror']],
["Paradox Cave Lower - Middle", False, ['Flute', 'Hammer']],
- ["Paradox Cave Lower - Middle", True, ['Flute', 'Hookshot']],
- ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Hookshot']],
- ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
- ["Paradox Cave Lower - Middle", True, ['Flute', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Lower - Middle", True, ['Flute', 'Hookshot', 'Cane of Somaria']],
+ ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Bomb Upgrade (+5)']],
+ ["Paradox Cave Lower - Middle", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer', 'Progressive Sword', 'Progressive Sword']],
+ ["Paradox Cave Lower - Middle", True, ['Flute', 'Magic Mirror', 'Hammer', 'Fire Rod']],
["Paradox Cave Lower - Right", False, []],
["Paradox Cave Lower - Right", False, [], ['Progressive Glove', 'Flute']],
@@ -115,10 +116,10 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Right", False, ['Progressive Glove', 'Hookshot']],
["Paradox Cave Lower - Right", False, ['Flute', 'Magic Mirror']],
["Paradox Cave Lower - Right", False, ['Flute', 'Hammer']],
- ["Paradox Cave Lower - Right", True, ['Flute', 'Hookshot']],
- ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']],
- ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
- ["Paradox Cave Lower - Right", True, ['Flute', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Lower - Right", True, ['Flute', 'Hookshot', 'Cane of Somaria']],
+ ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Bomb Upgrade (+5)']],
+ ["Paradox Cave Lower - Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer', 'Progressive Sword', 'Progressive Sword']],
+ ["Paradox Cave Lower - Right", True, ['Flute', 'Magic Mirror', 'Hammer', 'Fire Rod']],
["Paradox Cave Lower - Far Right", False, []],
["Paradox Cave Lower - Far Right", False, [], ['Progressive Glove', 'Flute']],
@@ -129,10 +130,10 @@ def testEastDeathMountain(self):
["Paradox Cave Lower - Far Right", False, ['Progressive Glove', 'Hookshot']],
["Paradox Cave Lower - Far Right", False, ['Flute', 'Magic Mirror']],
["Paradox Cave Lower - Far Right", False, ['Flute', 'Hammer']],
- ["Paradox Cave Lower - Far Right", True, ['Flute', 'Hookshot']],
- ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']],
- ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
- ["Paradox Cave Lower - Far Right", True, ['Flute', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Lower - Far Right", True, ['Flute', 'Hookshot', 'Cane of Somaria']],
+ ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Hookshot', 'Bomb Upgrade (+5)']],
+ ["Paradox Cave Lower - Far Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer', 'Progressive Sword', 'Progressive Sword']],
+ ["Paradox Cave Lower - Far Right", True, ['Flute', 'Magic Mirror', 'Hammer', 'Fire Rod']],
["Paradox Cave Upper - Left", False, []],
["Paradox Cave Upper - Left", False, [], ['Progressive Glove', 'Flute']],
@@ -143,10 +144,11 @@ def testEastDeathMountain(self):
["Paradox Cave Upper - Left", False, ['Progressive Glove', 'Hookshot']],
["Paradox Cave Upper - Left", False, ['Flute', 'Magic Mirror']],
["Paradox Cave Upper - Left", False, ['Flute', 'Hammer']],
- ["Paradox Cave Upper - Left", True, ['Flute', 'Hookshot']],
- ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Lamp', 'Hookshot']],
- ["Paradox Cave Upper - Left", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
- ["Paradox Cave Upper - Left", True, ['Flute', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Upper - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Hookshot']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Upper - Left", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror', 'Hammer']],
["Paradox Cave Upper - Right", False, []],
["Paradox Cave Upper - Right", False, [], ['Progressive Glove', 'Flute']],
@@ -157,10 +159,11 @@ def testEastDeathMountain(self):
["Paradox Cave Upper - Right", False, ['Progressive Glove', 'Hookshot']],
["Paradox Cave Upper - Right", False, ['Flute', 'Magic Mirror']],
["Paradox Cave Upper - Right", False, ['Flute', 'Hammer']],
- ["Paradox Cave Upper - Right", True, ['Flute', 'Hookshot']],
- ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Lamp', 'Hookshot']],
- ["Paradox Cave Upper - Right", True, ['Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
- ["Paradox Cave Upper - Right", True, ['Flute', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Upper - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Hookshot']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Hookshot']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Progressive Glove', 'Lamp', 'Magic Mirror', 'Hammer']],
+ ["Paradox Cave Upper - Right", True, ['Bomb Upgrade (+5)', 'Flute', 'Magic Mirror', 'Hammer']],
])
def testWestDarkWorldDeathMountain(self):
diff --git a/worlds/alttp/test/vanilla/TestLightWorld.py b/worlds/alttp/test/vanilla/TestLightWorld.py
index 977e807290d1..6d9284aba0d3 100644
--- a/worlds/alttp/test/vanilla/TestLightWorld.py
+++ b/worlds/alttp/test/vanilla/TestLightWorld.py
@@ -29,17 +29,21 @@ def testLightWorld(self):
["Kakariko Tavern", True, []],
- ["Chicken House", True, []],
+ ["Chicken House", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Chicken House", True, ['Bomb Upgrade (+5)']],
- ["Aginah's Cave", True, []],
+ ["Aginah's Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Aginah's Cave", True, ['Bomb Upgrade (+5)']],
- ["Sahasrahla's Hut - Left", True, []],
+ ["Sahasrahla's Hut - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']],
+ ["Sahasrahla's Hut - Left", True, ['Bomb Upgrade (+5)']],
+ ["Sahasrahla's Hut - Middle", True, ['Pegasus Boots']],
+ ["Sahasrahla's Hut - Middle", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Pegasus Boots']],
+ ["Sahasrahla's Hut - Right", True, ['Bomb Upgrade (+5)']],
+ ["Sahasrahla's Hut - Right", True, ['Pegasus Boots']],
- ["Sahasrahla's Hut - Middle", True, []],
-
- ["Sahasrahla's Hut - Right", True, []],
-
- ["Kakariko Well - Top", True, []],
+ ["Kakariko Well - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Kakariko Well - Top", True, ['Bomb Upgrade (+5)']],
["Kakariko Well - Left", True, []],
@@ -49,7 +53,8 @@ def testLightWorld(self):
["Kakariko Well - Bottom", True, []],
- ["Blind's Hideout - Top", True, []],
+ ["Blind's Hideout - Top", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Blind's Hideout - Top", True, ['Bomb Upgrade (+5)']],
["Blind's Hideout - Left", True, []],
@@ -63,15 +68,19 @@ def testLightWorld(self):
["Bonk Rock Cave", False, [], ['Pegasus Boots']],
["Bonk Rock Cave", True, ['Pegasus Boots']],
- ["Mini Moldorm Cave - Far Left", True, []],
-
- ["Mini Moldorm Cave - Left", True, []],
-
- ["Mini Moldorm Cave - Right", True, []],
-
- ["Mini Moldorm Cave - Far Right", True, []],
+ ["Mini Moldorm Cave - Far Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Far Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword']],
+ ["Mini Moldorm Cave - Left", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Left", True, ['Bomb Upgrade (+5)', 'Progressive Sword']],
+ ["Mini Moldorm Cave - Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword']],
+ ["Mini Moldorm Cave - Far Right", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Far Right", True, ['Bomb Upgrade (+5)', 'Progressive Sword']],
+ ["Mini Moldorm Cave - Generous Guy", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Mini Moldorm Cave - Generous Guy", True, ['Bomb Upgrade (+5)', 'Progressive Sword']],
- ["Ice Rod Cave", True, []],
+ ["Ice Rod Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Ice Rod Cave", True, ['Bomb Upgrade (+5)']],
["Bottle Merchant", True, []],
@@ -136,11 +145,12 @@ def testLightWorld(self):
["Graveyard Cave", False, []],
["Graveyard Cave", False, [], ['Magic Mirror']],
["Graveyard Cave", False, [], ['Moon Pearl']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Hammer']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Hammer', 'Hookshot']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
- ["Graveyard Cave", True, ['Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
+ ["Graveyard Cave", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Progressive Glove', 'Hammer']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Hammer', 'Hookshot']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Progressive Glove', 'Hookshot']],
+ ["Graveyard Cave", True, ['Bomb Upgrade (+5)', 'Moon Pearl', 'Magic Mirror', 'Beat Agahnim 1', 'Flippers', 'Hookshot']],
["Checkerboard Cave", False, []],
["Checkerboard Cave", False, [], ['Progressive Glove']],
@@ -148,7 +158,6 @@ def testLightWorld(self):
["Checkerboard Cave", False, [], ['Magic Mirror']],
["Checkerboard Cave", True, ['Flute', 'Magic Mirror', 'Progressive Glove', 'Progressive Glove']],
- ["Mini Moldorm Cave - Generous Guy", True, []],
["Library", False, []],
["Library", False, [], ['Pegasus Boots']],
@@ -160,7 +169,10 @@ def testLightWorld(self):
["Potion Shop", False, [], ['Mushroom']],
["Potion Shop", True, ['Mushroom']],
- ["Maze Race", True, []],
+ ["Maze Race", False, [], ['Bomb Upgrade (+5)', 'Bomb Upgrade (+10)', 'Bomb Upgrade (50)', 'Magic Mirror', 'Pegasus Boots']],
+ ["Maze Race", True, ['Magic Mirror', 'Progressive Glove', 'Progressive Glove', 'Moon Pearl']],
+ ["Maze Race", True, ['Bomb Upgrade (+5)']],
+ ["Maze Race", True, ['Pegasus Boots']],
["Desert Ledge", False, []],
["Desert Ledge", False, [], ['Book of Mudora', 'Flute']],
diff --git a/worlds/alttp/test/vanilla/TestVanilla.py b/worlds/alttp/test/vanilla/TestVanilla.py
index 3c983e98504c..3f4fbad8c2b6 100644
--- a/worlds/alttp/test/vanilla/TestVanilla.py
+++ b/worlds/alttp/test/vanilla/TestVanilla.py
@@ -9,8 +9,10 @@
class TestVanilla(TestBase, LTTPTestBase):
def setUp(self):
self.world_setup()
- self.multiworld.logic[1] = "noglitches"
+ self.multiworld.glitches_required[1] = "no_glitches"
self.multiworld.difficulty_requirements[1] = difficulties['normal']
+ self.multiworld.bombless_start[1].value = True
+ self.multiworld.shuffle_capacity_upgrades[1].value = True
self.multiworld.worlds[1].er_seed = 0
self.multiworld.worlds[1].create_regions()
self.multiworld.worlds[1].create_items()
diff --git a/worlds/blasphemous/docs/setup_en.md b/worlds/blasphemous/docs/setup_en.md
index cc238a492eb3..070d1ca4964b 100644
--- a/worlds/blasphemous/docs/setup_en.md
+++ b/worlds/blasphemous/docs/setup_en.md
@@ -15,7 +15,6 @@ Optional:
- Quick Prie Dieu warp mod: [GitHub](https://github.com/BadMagic100/Blasphemous-PrieWarp)
- Boots of Pleading mod: [GitHub](https://github.com/BrandenEK/Blasphemous-Boots-of-Pleading)
- Double Jump mod: [GitHub](https://github.com/BrandenEK/Blasphemous-Double-Jump)
-- PopTracker pack: [GitHub](https://github.com/sassyvania/Blasphemous-Randomizer-Maptracker)
## Mod Installer (Recommended)
diff --git a/worlds/bumpstik/__init__.py b/worlds/bumpstik/__init__.py
index c4e65d07b6a9..d93b25cda5e7 100644
--- a/worlds/bumpstik/__init__.py
+++ b/worlds/bumpstik/__init__.py
@@ -14,7 +14,7 @@
class BumpStikWeb(WebWorld):
tutorials = [Tutorial(
- "Bumper Stickers Setup Tutorial",
+ "Bumper Stickers Setup Guide",
"A guide to setting up the Archipelago Bumper Stickers software on your computer.",
"English",
"setup_en.md",
diff --git a/worlds/checksfinder/__init__.py b/worlds/checksfinder/__init__.py
index 621e8f5c37b2..b70c65bb08f5 100644
--- a/worlds/checksfinder/__init__.py
+++ b/worlds/checksfinder/__init__.py
@@ -10,7 +10,7 @@
class ChecksFinderWeb(WebWorld):
tutorials = [Tutorial(
- "Multiworld Setup Tutorial",
+ "Multiworld Setup Guide",
"A guide to setting up the Archipelago ChecksFinder software on your computer. This guide covers "
"single-player, multiworld, and related software.",
"English",
diff --git a/worlds/dark_souls_3/__init__.py b/worlds/dark_souls_3/__init__.py
index 7ee6c2a6411b..b4c231cdea1b 100644
--- a/worlds/dark_souls_3/__init__.py
+++ b/worlds/dark_souls_3/__init__.py
@@ -14,8 +14,9 @@
class DarkSouls3Web(WebWorld):
bug_report_page = "https://github.com/Marechal-L/Dark-Souls-III-Archipelago-client/issues"
+ theme = "stone"
setup_en = Tutorial(
- "Multiworld Setup Tutorial",
+ "Multiworld Setup Guide",
"A guide to setting up the Archipelago Dark Souls III randomizer on your computer.",
"English",
"setup_en.md",
diff --git a/worlds/dark_souls_3/docs/setup_en.md b/worlds/dark_souls_3/docs/setup_en.md
index 7a3ca4e9bd86..72c665af9507 100644
--- a/worlds/dark_souls_3/docs/setup_en.md
+++ b/worlds/dark_souls_3/docs/setup_en.md
@@ -11,7 +11,7 @@
## General Concept
-
+
**This mod can ban you permanently from the FromSoftware servers if used online.**
The Dark Souls III AP Client is a dinput8.dll triggered when launching Dark Souls III. This .dll file will launch a command
diff --git a/worlds/dark_souls_3/docs/setup_fr.md b/worlds/dark_souls_3/docs/setup_fr.md
index 6ad86c4aff13..769d331bb98d 100644
--- a/worlds/dark_souls_3/docs/setup_fr.md
+++ b/worlds/dark_souls_3/docs/setup_fr.md
@@ -12,7 +12,7 @@ permettant de lire des informations de la partie et écrire des commandes pour i
## Procédures d'installation
-
+
**Il y a des risques de bannissement permanent des serveurs FromSoftware si ce mod est utilisé en ligne.**
Ce client a été testé sur la version Steam officielle du jeu (v1.15/1.35), peu importe les DLCs actuellement installés.
diff --git a/worlds/dkc3/Locations.py b/worlds/dkc3/Locations.py
index e8d5409b1563..6d8833872b03 100644
--- a/worlds/dkc3/Locations.py
+++ b/worlds/dkc3/Locations.py
@@ -2,6 +2,7 @@
from BaseClasses import Location
from .Names import LocationName
+from worlds.AutoWorld import World
class DKC3Location(Location):
@@ -321,13 +322,13 @@ def __init__(self, player: int, name: str = '', address: int = None, parent=None
location_table = {}
-def setup_locations(world, player: int):
+def setup_locations(world: World):
location_table = {**level_location_table, **boss_location_table, **secret_cave_location_table}
- if False:#world.include_trade_sequence[player].value:
+ if False:#world.options.include_trade_sequence:
location_table.update({**brothers_bear_location_table})
- if world.kongsanity[player].value:
+ if world.options.kongsanity:
location_table.update({**kong_location_table})
return location_table
diff --git a/worlds/dkc3/Options.py b/worlds/dkc3/Options.py
index 7c0f532cfc76..06be30cf15ae 100644
--- a/worlds/dkc3/Options.py
+++ b/worlds/dkc3/Options.py
@@ -1,6 +1,7 @@
+from dataclasses import dataclass
import typing
-from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionList
+from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionList, PerGameCommonOptions
class Goal(Choice):
@@ -158,21 +159,21 @@ class StartingLifeCount(Range):
default = 5
-dkc3_options: typing.Dict[str, type(Option)] = {
- #"death_link": DeathLink, # Disabled
- "goal": Goal,
- #"include_trade_sequence": IncludeTradeSequence, # Disabled
- "dk_coins_for_gyrocopter": DKCoinsForGyrocopter,
- "krematoa_bonus_coin_cost": KrematoaBonusCoinCost,
- "percentage_of_extra_bonus_coins": PercentageOfExtraBonusCoins,
- "number_of_banana_birds": NumberOfBananaBirds,
- "percentage_of_banana_birds": PercentageOfBananaBirds,
- "kongsanity": KONGsanity,
- "level_shuffle": LevelShuffle,
- "difficulty": Difficulty,
- "autosave": Autosave,
- "merry": MERRY,
- "music_shuffle": MusicShuffle,
- "kong_palette_swap": KongPaletteSwap,
- "starting_life_count": StartingLifeCount,
-}
+@dataclass
+class DKC3Options(PerGameCommonOptions):
+ #death_link: DeathLink # Disabled
+ goal: Goal
+ #include_trade_sequence: IncludeTradeSequence # Disabled
+ dk_coins_for_gyrocopter: DKCoinsForGyrocopter
+ krematoa_bonus_coin_cost: KrematoaBonusCoinCost
+ percentage_of_extra_bonus_coins: PercentageOfExtraBonusCoins
+ number_of_banana_birds: NumberOfBananaBirds
+ percentage_of_banana_birds: PercentageOfBananaBirds
+ kongsanity: KONGsanity
+ level_shuffle: LevelShuffle
+ difficulty: Difficulty
+ autosave: Autosave
+ merry: MERRY
+ music_shuffle: MusicShuffle
+ kong_palette_swap: KongPaletteSwap
+ starting_life_count: StartingLifeCount
diff --git a/worlds/dkc3/Regions.py b/worlds/dkc3/Regions.py
index ca6545ca14cc..ae505b78d84b 100644
--- a/worlds/dkc3/Regions.py
+++ b/worlds/dkc3/Regions.py
@@ -4,38 +4,39 @@
from .Items import DKC3Item
from .Locations import DKC3Location
from .Names import LocationName, ItemName
+from worlds.AutoWorld import World
-def create_regions(world, player: int, active_locations):
- menu_region = create_region(world, player, active_locations, 'Menu', None)
+def create_regions(world: World, active_locations):
+ menu_region = create_region(world, active_locations, 'Menu', None)
overworld_1_region_locations = {}
- if world.goal[player] != "knautilus":
+ if world.options.goal != "knautilus":
overworld_1_region_locations.update({LocationName.banana_bird_mother: []})
- overworld_1_region = create_region(world, player, active_locations, LocationName.overworld_1_region,
+ overworld_1_region = create_region(world, active_locations, LocationName.overworld_1_region,
overworld_1_region_locations)
overworld_2_region_locations = {}
- overworld_2_region = create_region(world, player, active_locations, LocationName.overworld_2_region,
+ overworld_2_region = create_region(world, active_locations, LocationName.overworld_2_region,
overworld_2_region_locations)
overworld_3_region_locations = {}
- overworld_3_region = create_region(world, player, active_locations, LocationName.overworld_3_region,
+ overworld_3_region = create_region(world, active_locations, LocationName.overworld_3_region,
overworld_3_region_locations)
overworld_4_region_locations = {}
- overworld_4_region = create_region(world, player, active_locations, LocationName.overworld_4_region,
+ overworld_4_region = create_region(world, active_locations, LocationName.overworld_4_region,
overworld_4_region_locations)
- lake_orangatanga_region = create_region(world, player, active_locations, LocationName.lake_orangatanga_region, None)
- kremwood_forest_region = create_region(world, player, active_locations, LocationName.kremwood_forest_region, None)
- cotton_top_cove_region = create_region(world, player, active_locations, LocationName.cotton_top_cove_region, None)
- mekanos_region = create_region(world, player, active_locations, LocationName.mekanos_region, None)
- k3_region = create_region(world, player, active_locations, LocationName.k3_region, None)
- razor_ridge_region = create_region(world, player, active_locations, LocationName.razor_ridge_region, None)
- kaos_kore_region = create_region(world, player, active_locations, LocationName.kaos_kore_region, None)
- krematoa_region = create_region(world, player, active_locations, LocationName.krematoa_region, None)
+ lake_orangatanga_region = create_region(world, active_locations, LocationName.lake_orangatanga_region, None)
+ kremwood_forest_region = create_region(world, active_locations, LocationName.kremwood_forest_region, None)
+ cotton_top_cove_region = create_region(world, active_locations, LocationName.cotton_top_cove_region, None)
+ mekanos_region = create_region(world, active_locations, LocationName.mekanos_region, None)
+ k3_region = create_region(world, active_locations, LocationName.k3_region, None)
+ razor_ridge_region = create_region(world, active_locations, LocationName.razor_ridge_region, None)
+ kaos_kore_region = create_region(world, active_locations, LocationName.kaos_kore_region, None)
+ krematoa_region = create_region(world, active_locations, LocationName.krematoa_region, None)
lakeside_limbo_region_locations = {
@@ -44,9 +45,9 @@ def create_regions(world, player: int, active_locations):
LocationName.lakeside_limbo_bonus_2 : [0x657, 3],
LocationName.lakeside_limbo_dk : [0x657, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
lakeside_limbo_region_locations[LocationName.lakeside_limbo_kong] = []
- lakeside_limbo_region = create_region(world, player, active_locations, LocationName.lakeside_limbo_region,
+ lakeside_limbo_region = create_region(world, active_locations, LocationName.lakeside_limbo_region,
lakeside_limbo_region_locations)
doorstop_dash_region_locations = {
@@ -55,9 +56,9 @@ def create_regions(world, player: int, active_locations):
LocationName.doorstop_dash_bonus_2 : [0x65A, 3],
LocationName.doorstop_dash_dk : [0x65A, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
doorstop_dash_region_locations[LocationName.doorstop_dash_kong] = []
- doorstop_dash_region = create_region(world, player, active_locations, LocationName.doorstop_dash_region,
+ doorstop_dash_region = create_region(world, active_locations, LocationName.doorstop_dash_region,
doorstop_dash_region_locations)
tidal_trouble_region_locations = {
@@ -66,9 +67,9 @@ def create_regions(world, player: int, active_locations):
LocationName.tidal_trouble_bonus_2 : [0x659, 3],
LocationName.tidal_trouble_dk : [0x659, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
tidal_trouble_region_locations[LocationName.tidal_trouble_kong] = []
- tidal_trouble_region = create_region(world, player, active_locations, LocationName.tidal_trouble_region,
+ tidal_trouble_region = create_region(world, active_locations, LocationName.tidal_trouble_region,
tidal_trouble_region_locations)
skiddas_row_region_locations = {
@@ -77,9 +78,9 @@ def create_regions(world, player: int, active_locations):
LocationName.skiddas_row_bonus_2 : [0x65D, 3],
LocationName.skiddas_row_dk : [0x65D, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
skiddas_row_region_locations[LocationName.skiddas_row_kong] = []
- skiddas_row_region = create_region(world, player, active_locations, LocationName.skiddas_row_region,
+ skiddas_row_region = create_region(world, active_locations, LocationName.skiddas_row_region,
skiddas_row_region_locations)
murky_mill_region_locations = {
@@ -88,9 +89,9 @@ def create_regions(world, player: int, active_locations):
LocationName.murky_mill_bonus_2 : [0x65C, 3],
LocationName.murky_mill_dk : [0x65C, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
murky_mill_region_locations[LocationName.murky_mill_kong] = []
- murky_mill_region = create_region(world, player, active_locations, LocationName.murky_mill_region,
+ murky_mill_region = create_region(world, active_locations, LocationName.murky_mill_region,
murky_mill_region_locations)
barrel_shield_bust_up_region_locations = {
@@ -99,9 +100,9 @@ def create_regions(world, player: int, active_locations):
LocationName.barrel_shield_bust_up_bonus_2 : [0x662, 3],
LocationName.barrel_shield_bust_up_dk : [0x662, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
barrel_shield_bust_up_region_locations[LocationName.barrel_shield_bust_up_kong] = []
- barrel_shield_bust_up_region = create_region(world, player, active_locations,
+ barrel_shield_bust_up_region = create_region(world, active_locations,
LocationName.barrel_shield_bust_up_region,
barrel_shield_bust_up_region_locations)
@@ -111,9 +112,9 @@ def create_regions(world, player: int, active_locations):
LocationName.riverside_race_bonus_2 : [0x664, 3],
LocationName.riverside_race_dk : [0x664, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
riverside_race_region_locations[LocationName.riverside_race_kong] = []
- riverside_race_region = create_region(world, player, active_locations, LocationName.riverside_race_region,
+ riverside_race_region = create_region(world, active_locations, LocationName.riverside_race_region,
riverside_race_region_locations)
squeals_on_wheels_region_locations = {
@@ -122,9 +123,9 @@ def create_regions(world, player: int, active_locations):
LocationName.squeals_on_wheels_bonus_2 : [0x65B, 3],
LocationName.squeals_on_wheels_dk : [0x65B, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
squeals_on_wheels_region_locations[LocationName.squeals_on_wheels_kong] = []
- squeals_on_wheels_region = create_region(world, player, active_locations, LocationName.squeals_on_wheels_region,
+ squeals_on_wheels_region = create_region(world, active_locations, LocationName.squeals_on_wheels_region,
squeals_on_wheels_region_locations)
springin_spiders_region_locations = {
@@ -133,9 +134,9 @@ def create_regions(world, player: int, active_locations):
LocationName.springin_spiders_bonus_2 : [0x661, 3],
LocationName.springin_spiders_dk : [0x661, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
springin_spiders_region_locations[LocationName.springin_spiders_kong] = []
- springin_spiders_region = create_region(world, player, active_locations, LocationName.springin_spiders_region,
+ springin_spiders_region = create_region(world, active_locations, LocationName.springin_spiders_region,
springin_spiders_region_locations)
bobbing_barrel_brawl_region_locations = {
@@ -144,9 +145,9 @@ def create_regions(world, player: int, active_locations):
LocationName.bobbing_barrel_brawl_bonus_2 : [0x666, 3],
LocationName.bobbing_barrel_brawl_dk : [0x666, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
bobbing_barrel_brawl_region_locations[LocationName.bobbing_barrel_brawl_kong] = []
- bobbing_barrel_brawl_region = create_region(world, player, active_locations,
+ bobbing_barrel_brawl_region = create_region(world, active_locations,
LocationName.bobbing_barrel_brawl_region,
bobbing_barrel_brawl_region_locations)
@@ -156,9 +157,9 @@ def create_regions(world, player: int, active_locations):
LocationName.bazzas_blockade_bonus_2 : [0x667, 3],
LocationName.bazzas_blockade_dk : [0x667, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
bazzas_blockade_region_locations[LocationName.bazzas_blockade_kong] = []
- bazzas_blockade_region = create_region(world, player, active_locations, LocationName.bazzas_blockade_region,
+ bazzas_blockade_region = create_region(world, active_locations, LocationName.bazzas_blockade_region,
bazzas_blockade_region_locations)
rocket_barrel_ride_region_locations = {
@@ -167,9 +168,9 @@ def create_regions(world, player: int, active_locations):
LocationName.rocket_barrel_ride_bonus_2 : [0x66A, 3],
LocationName.rocket_barrel_ride_dk : [0x66A, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
rocket_barrel_ride_region_locations[LocationName.rocket_barrel_ride_kong] = []
- rocket_barrel_ride_region = create_region(world, player, active_locations, LocationName.rocket_barrel_ride_region,
+ rocket_barrel_ride_region = create_region(world, active_locations, LocationName.rocket_barrel_ride_region,
rocket_barrel_ride_region_locations)
kreeping_klasps_region_locations = {
@@ -178,9 +179,9 @@ def create_regions(world, player: int, active_locations):
LocationName.kreeping_klasps_bonus_2 : [0x658, 3],
LocationName.kreeping_klasps_dk : [0x658, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
kreeping_klasps_region_locations[LocationName.kreeping_klasps_kong] = []
- kreeping_klasps_region = create_region(world, player, active_locations, LocationName.kreeping_klasps_region,
+ kreeping_klasps_region = create_region(world, active_locations, LocationName.kreeping_klasps_region,
kreeping_klasps_region_locations)
tracker_barrel_trek_region_locations = {
@@ -189,9 +190,9 @@ def create_regions(world, player: int, active_locations):
LocationName.tracker_barrel_trek_bonus_2 : [0x66B, 3],
LocationName.tracker_barrel_trek_dk : [0x66B, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
tracker_barrel_trek_region_locations[LocationName.tracker_barrel_trek_kong] = []
- tracker_barrel_trek_region = create_region(world, player, active_locations, LocationName.tracker_barrel_trek_region,
+ tracker_barrel_trek_region = create_region(world, active_locations, LocationName.tracker_barrel_trek_region,
tracker_barrel_trek_region_locations)
fish_food_frenzy_region_locations = {
@@ -200,9 +201,9 @@ def create_regions(world, player: int, active_locations):
LocationName.fish_food_frenzy_bonus_2 : [0x668, 3],
LocationName.fish_food_frenzy_dk : [0x668, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
fish_food_frenzy_region_locations[LocationName.fish_food_frenzy_kong] = []
- fish_food_frenzy_region = create_region(world, player, active_locations, LocationName.fish_food_frenzy_region,
+ fish_food_frenzy_region = create_region(world, active_locations, LocationName.fish_food_frenzy_region,
fish_food_frenzy_region_locations)
fire_ball_frenzy_region_locations = {
@@ -211,9 +212,9 @@ def create_regions(world, player: int, active_locations):
LocationName.fire_ball_frenzy_bonus_2 : [0x66D, 3],
LocationName.fire_ball_frenzy_dk : [0x66D, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
fire_ball_frenzy_region_locations[LocationName.fire_ball_frenzy_kong] = []
- fire_ball_frenzy_region = create_region(world, player, active_locations, LocationName.fire_ball_frenzy_region,
+ fire_ball_frenzy_region = create_region(world, active_locations, LocationName.fire_ball_frenzy_region,
fire_ball_frenzy_region_locations)
demolition_drain_pipe_region_locations = {
@@ -222,9 +223,9 @@ def create_regions(world, player: int, active_locations):
LocationName.demolition_drain_pipe_bonus_2 : [0x672, 3],
LocationName.demolition_drain_pipe_dk : [0x672, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
demolition_drain_pipe_region_locations[LocationName.demolition_drain_pipe_kong] = []
- demolition_drain_pipe_region = create_region(world, player, active_locations,
+ demolition_drain_pipe_region = create_region(world, active_locations,
LocationName.demolition_drain_pipe_region,
demolition_drain_pipe_region_locations)
@@ -234,9 +235,9 @@ def create_regions(world, player: int, active_locations):
LocationName.ripsaw_rage_bonus_2 : [0x660, 3],
LocationName.ripsaw_rage_dk : [0x660, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
ripsaw_rage_region_locations[LocationName.ripsaw_rage_kong] = []
- ripsaw_rage_region = create_region(world, player, active_locations, LocationName.ripsaw_rage_region,
+ ripsaw_rage_region = create_region(world, active_locations, LocationName.ripsaw_rage_region,
ripsaw_rage_region_locations)
blazing_bazookas_region_locations = {
@@ -245,9 +246,9 @@ def create_regions(world, player: int, active_locations):
LocationName.blazing_bazookas_bonus_2 : [0x66E, 3],
LocationName.blazing_bazookas_dk : [0x66E, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
blazing_bazookas_region_locations[LocationName.blazing_bazookas_kong] = []
- blazing_bazookas_region = create_region(world, player, active_locations, LocationName.blazing_bazookas_region,
+ blazing_bazookas_region = create_region(world, active_locations, LocationName.blazing_bazookas_region,
blazing_bazookas_region_locations)
low_g_labyrinth_region_locations = {
@@ -256,9 +257,9 @@ def create_regions(world, player: int, active_locations):
LocationName.low_g_labyrinth_bonus_2 : [0x670, 3],
LocationName.low_g_labyrinth_dk : [0x670, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
low_g_labyrinth_region_locations[LocationName.low_g_labyrinth_kong] = []
- low_g_labyrinth_region = create_region(world, player, active_locations, LocationName.low_g_labyrinth_region,
+ low_g_labyrinth_region = create_region(world, active_locations, LocationName.low_g_labyrinth_region,
low_g_labyrinth_region_locations)
krevice_kreepers_region_locations = {
@@ -267,9 +268,9 @@ def create_regions(world, player: int, active_locations):
LocationName.krevice_kreepers_bonus_2 : [0x673, 3],
LocationName.krevice_kreepers_dk : [0x673, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
krevice_kreepers_region_locations[LocationName.krevice_kreepers_kong] = []
- krevice_kreepers_region = create_region(world, player, active_locations, LocationName.krevice_kreepers_region,
+ krevice_kreepers_region = create_region(world, active_locations, LocationName.krevice_kreepers_region,
krevice_kreepers_region_locations)
tearaway_toboggan_region_locations = {
@@ -278,9 +279,9 @@ def create_regions(world, player: int, active_locations):
LocationName.tearaway_toboggan_bonus_2 : [0x65F, 3],
LocationName.tearaway_toboggan_dk : [0x65F, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
tearaway_toboggan_region_locations[LocationName.tearaway_toboggan_kong] = []
- tearaway_toboggan_region = create_region(world, player, active_locations, LocationName.tearaway_toboggan_region,
+ tearaway_toboggan_region = create_region(world, active_locations, LocationName.tearaway_toboggan_region,
tearaway_toboggan_region_locations)
barrel_drop_bounce_region_locations = {
@@ -289,9 +290,9 @@ def create_regions(world, player: int, active_locations):
LocationName.barrel_drop_bounce_bonus_2 : [0x66C, 3],
LocationName.barrel_drop_bounce_dk : [0x66C, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
barrel_drop_bounce_region_locations[LocationName.barrel_drop_bounce_kong] = []
- barrel_drop_bounce_region = create_region(world, player, active_locations, LocationName.barrel_drop_bounce_region,
+ barrel_drop_bounce_region = create_region(world, active_locations, LocationName.barrel_drop_bounce_region,
barrel_drop_bounce_region_locations)
krack_shot_kroc_region_locations = {
@@ -300,9 +301,9 @@ def create_regions(world, player: int, active_locations):
LocationName.krack_shot_kroc_bonus_2 : [0x66F, 3],
LocationName.krack_shot_kroc_dk : [0x66F, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
krack_shot_kroc_region_locations[LocationName.krack_shot_kroc_kong] = []
- krack_shot_kroc_region = create_region(world, player, active_locations, LocationName.krack_shot_kroc_region,
+ krack_shot_kroc_region = create_region(world, active_locations, LocationName.krack_shot_kroc_region,
krack_shot_kroc_region_locations)
lemguin_lunge_region_locations = {
@@ -311,9 +312,9 @@ def create_regions(world, player: int, active_locations):
LocationName.lemguin_lunge_bonus_2 : [0x65E, 3],
LocationName.lemguin_lunge_dk : [0x65E, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
lemguin_lunge_region_locations[LocationName.lemguin_lunge_kong] = []
- lemguin_lunge_region = create_region(world, player, active_locations, LocationName.lemguin_lunge_region,
+ lemguin_lunge_region = create_region(world, active_locations, LocationName.lemguin_lunge_region,
lemguin_lunge_region_locations)
buzzer_barrage_region_locations = {
@@ -322,9 +323,9 @@ def create_regions(world, player: int, active_locations):
LocationName.buzzer_barrage_bonus_2 : [0x676, 3],
LocationName.buzzer_barrage_dk : [0x676, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
buzzer_barrage_region_locations[LocationName.buzzer_barrage_kong] = []
- buzzer_barrage_region = create_region(world, player, active_locations, LocationName.buzzer_barrage_region,
+ buzzer_barrage_region = create_region(world, active_locations, LocationName.buzzer_barrage_region,
buzzer_barrage_region_locations)
kong_fused_cliffs_region_locations = {
@@ -333,9 +334,9 @@ def create_regions(world, player: int, active_locations):
LocationName.kong_fused_cliffs_bonus_2 : [0x674, 3],
LocationName.kong_fused_cliffs_dk : [0x674, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
kong_fused_cliffs_region_locations[LocationName.kong_fused_cliffs_kong] = []
- kong_fused_cliffs_region = create_region(world, player, active_locations, LocationName.kong_fused_cliffs_region,
+ kong_fused_cliffs_region = create_region(world, active_locations, LocationName.kong_fused_cliffs_region,
kong_fused_cliffs_region_locations)
floodlit_fish_region_locations = {
@@ -344,9 +345,9 @@ def create_regions(world, player: int, active_locations):
LocationName.floodlit_fish_bonus_2 : [0x669, 3],
LocationName.floodlit_fish_dk : [0x669, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
floodlit_fish_region_locations[LocationName.floodlit_fish_kong] = []
- floodlit_fish_region = create_region(world, player, active_locations, LocationName.floodlit_fish_region,
+ floodlit_fish_region = create_region(world, active_locations, LocationName.floodlit_fish_region,
floodlit_fish_region_locations)
pothole_panic_region_locations = {
@@ -355,9 +356,9 @@ def create_regions(world, player: int, active_locations):
LocationName.pothole_panic_bonus_2 : [0x677, 3],
LocationName.pothole_panic_dk : [0x677, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
pothole_panic_region_locations[LocationName.pothole_panic_kong] = []
- pothole_panic_region = create_region(world, player, active_locations, LocationName.pothole_panic_region,
+ pothole_panic_region = create_region(world, active_locations, LocationName.pothole_panic_region,
pothole_panic_region_locations)
ropey_rumpus_region_locations = {
@@ -366,9 +367,9 @@ def create_regions(world, player: int, active_locations):
LocationName.ropey_rumpus_bonus_2 : [0x675, 3],
LocationName.ropey_rumpus_dk : [0x675, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
ropey_rumpus_region_locations[LocationName.ropey_rumpus_kong] = []
- ropey_rumpus_region = create_region(world, player, active_locations, LocationName.ropey_rumpus_region,
+ ropey_rumpus_region = create_region(world, active_locations, LocationName.ropey_rumpus_region,
ropey_rumpus_region_locations)
konveyor_rope_clash_region_locations = {
@@ -377,9 +378,9 @@ def create_regions(world, player: int, active_locations):
LocationName.konveyor_rope_clash_bonus_2 : [0x657, 3],
LocationName.konveyor_rope_clash_dk : [0x657, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
konveyor_rope_clash_region_locations[LocationName.konveyor_rope_clash_kong] = []
- konveyor_rope_clash_region = create_region(world, player, active_locations, LocationName.konveyor_rope_clash_region,
+ konveyor_rope_clash_region = create_region(world, active_locations, LocationName.konveyor_rope_clash_region,
konveyor_rope_clash_region_locations)
creepy_caverns_region_locations = {
@@ -388,9 +389,9 @@ def create_regions(world, player: int, active_locations):
LocationName.creepy_caverns_bonus_2 : [0x678, 3],
LocationName.creepy_caverns_dk : [0x678, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
creepy_caverns_region_locations[LocationName.creepy_caverns_kong] = []
- creepy_caverns_region = create_region(world, player, active_locations, LocationName.creepy_caverns_region,
+ creepy_caverns_region = create_region(world, active_locations, LocationName.creepy_caverns_region,
creepy_caverns_region_locations)
lightning_lookout_region_locations = {
@@ -399,9 +400,9 @@ def create_regions(world, player: int, active_locations):
LocationName.lightning_lookout_bonus_2 : [0x665, 3],
LocationName.lightning_lookout_dk : [0x665, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
lightning_lookout_region_locations[LocationName.lightning_lookout_kong] = []
- lightning_lookout_region = create_region(world, player, active_locations, LocationName.lightning_lookout_region,
+ lightning_lookout_region = create_region(world, active_locations, LocationName.lightning_lookout_region,
lightning_lookout_region_locations)
koindozer_klamber_region_locations = {
@@ -410,9 +411,9 @@ def create_regions(world, player: int, active_locations):
LocationName.koindozer_klamber_bonus_2 : [0x679, 3],
LocationName.koindozer_klamber_dk : [0x679, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
koindozer_klamber_region_locations[LocationName.koindozer_klamber_kong] = []
- koindozer_klamber_region = create_region(world, player, active_locations, LocationName.koindozer_klamber_region,
+ koindozer_klamber_region = create_region(world, active_locations, LocationName.koindozer_klamber_region,
koindozer_klamber_region_locations)
poisonous_pipeline_region_locations = {
@@ -421,9 +422,9 @@ def create_regions(world, player: int, active_locations):
LocationName.poisonous_pipeline_bonus_2 : [0x671, 3],
LocationName.poisonous_pipeline_dk : [0x671, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
poisonous_pipeline_region_locations[LocationName.poisonous_pipeline_kong] = []
- poisonous_pipeline_region = create_region(world, player, active_locations, LocationName.poisonous_pipeline_region,
+ poisonous_pipeline_region = create_region(world, active_locations, LocationName.poisonous_pipeline_region,
poisonous_pipeline_region_locations)
stampede_sprint_region_locations = {
@@ -433,9 +434,9 @@ def create_regions(world, player: int, active_locations):
LocationName.stampede_sprint_bonus_3 : [0x67B, 4],
LocationName.stampede_sprint_dk : [0x67B, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
stampede_sprint_region_locations[LocationName.stampede_sprint_kong] = []
- stampede_sprint_region = create_region(world, player, active_locations, LocationName.stampede_sprint_region,
+ stampede_sprint_region = create_region(world, active_locations, LocationName.stampede_sprint_region,
stampede_sprint_region_locations)
criss_cross_cliffs_region_locations = {
@@ -444,9 +445,9 @@ def create_regions(world, player: int, active_locations):
LocationName.criss_cross_cliffs_bonus_2 : [0x67C, 3],
LocationName.criss_cross_cliffs_dk : [0x67C, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
criss_cross_cliffs_region_locations[LocationName.criss_cross_cliffs_kong] = []
- criss_cross_cliffs_region = create_region(world, player, active_locations, LocationName.criss_cross_cliffs_region,
+ criss_cross_cliffs_region = create_region(world, active_locations, LocationName.criss_cross_cliffs_region,
criss_cross_cliffs_region_locations)
tyrant_twin_tussle_region_locations = {
@@ -456,9 +457,9 @@ def create_regions(world, player: int, active_locations):
LocationName.tyrant_twin_tussle_bonus_3 : [0x67D, 4],
LocationName.tyrant_twin_tussle_dk : [0x67D, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
tyrant_twin_tussle_region_locations[LocationName.tyrant_twin_tussle_kong] = []
- tyrant_twin_tussle_region = create_region(world, player, active_locations, LocationName.tyrant_twin_tussle_region,
+ tyrant_twin_tussle_region = create_region(world, active_locations, LocationName.tyrant_twin_tussle_region,
tyrant_twin_tussle_region_locations)
swoopy_salvo_region_locations = {
@@ -468,147 +469,147 @@ def create_regions(world, player: int, active_locations):
LocationName.swoopy_salvo_bonus_3 : [0x663, 4],
LocationName.swoopy_salvo_dk : [0x663, 5],
}
- if world.kongsanity[player]:
+ if world.options.kongsanity:
swoopy_salvo_region_locations[LocationName.swoopy_salvo_kong] = []
- swoopy_salvo_region = create_region(world, player, active_locations, LocationName.swoopy_salvo_region,
+ swoopy_salvo_region = create_region(world, active_locations, LocationName.swoopy_salvo_region,
swoopy_salvo_region_locations)
rocket_rush_region_locations = {
LocationName.rocket_rush_flag : [0x67E, 1],
LocationName.rocket_rush_dk : [0x67E, 5],
}
- rocket_rush_region = create_region(world, player, active_locations, LocationName.rocket_rush_region,
+ rocket_rush_region = create_region(world, active_locations, LocationName.rocket_rush_region,
rocket_rush_region_locations)
belchas_barn_region_locations = {
LocationName.belchas_barn: [0x64F, 1],
}
- belchas_barn_region = create_region(world, player, active_locations, LocationName.belchas_barn_region,
+ belchas_barn_region = create_region(world, active_locations, LocationName.belchas_barn_region,
belchas_barn_region_locations)
arichs_ambush_region_locations = {
LocationName.arichs_ambush: [0x650, 1],
}
- arichs_ambush_region = create_region(world, player, active_locations, LocationName.arichs_ambush_region,
+ arichs_ambush_region = create_region(world, active_locations, LocationName.arichs_ambush_region,
arichs_ambush_region_locations)
squirts_showdown_region_locations = {
LocationName.squirts_showdown: [0x651, 1],
}
- squirts_showdown_region = create_region(world, player, active_locations, LocationName.squirts_showdown_region,
+ squirts_showdown_region = create_region(world, active_locations, LocationName.squirts_showdown_region,
squirts_showdown_region_locations)
kaos_karnage_region_locations = {
LocationName.kaos_karnage: [0x652, 1],
}
- kaos_karnage_region = create_region(world, player, active_locations, LocationName.kaos_karnage_region,
+ kaos_karnage_region = create_region(world, active_locations, LocationName.kaos_karnage_region,
kaos_karnage_region_locations)
bleaks_house_region_locations = {
LocationName.bleaks_house: [0x653, 1],
}
- bleaks_house_region = create_region(world, player, active_locations, LocationName.bleaks_house_region,
+ bleaks_house_region = create_region(world, active_locations, LocationName.bleaks_house_region,
bleaks_house_region_locations)
barboss_barrier_region_locations = {
LocationName.barboss_barrier: [0x654, 1],
}
- barboss_barrier_region = create_region(world, player, active_locations, LocationName.barboss_barrier_region,
+ barboss_barrier_region = create_region(world, active_locations, LocationName.barboss_barrier_region,
barboss_barrier_region_locations)
kastle_kaos_region_locations = {
LocationName.kastle_kaos: [0x655, 1],
}
- kastle_kaos_region = create_region(world, player, active_locations, LocationName.kastle_kaos_region,
+ kastle_kaos_region = create_region(world, active_locations, LocationName.kastle_kaos_region,
kastle_kaos_region_locations)
knautilus_region_locations = {
LocationName.knautilus: [0x656, 1],
}
- knautilus_region = create_region(world, player, active_locations, LocationName.knautilus_region,
+ knautilus_region = create_region(world, active_locations, LocationName.knautilus_region,
knautilus_region_locations)
belchas_burrow_region_locations = {
LocationName.belchas_burrow: [0x647, 1],
}
- belchas_burrow_region = create_region(world, player, active_locations, LocationName.belchas_burrow_region,
+ belchas_burrow_region = create_region(world, active_locations, LocationName.belchas_burrow_region,
belchas_burrow_region_locations)
kong_cave_region_locations = {
LocationName.kong_cave: [0x645, 1],
}
- kong_cave_region = create_region(world, player, active_locations, LocationName.kong_cave_region,
+ kong_cave_region = create_region(world, active_locations, LocationName.kong_cave_region,
kong_cave_region_locations)
undercover_cove_region_locations = {
LocationName.undercover_cove: [0x644, 1],
}
- undercover_cove_region = create_region(world, player, active_locations, LocationName.undercover_cove_region,
+ undercover_cove_region = create_region(world, active_locations, LocationName.undercover_cove_region,
undercover_cove_region_locations)
ks_cache_region_locations = {
LocationName.ks_cache: [0x642, 1],
}
- ks_cache_region = create_region(world, player, active_locations, LocationName.ks_cache_region,
+ ks_cache_region = create_region(world, active_locations, LocationName.ks_cache_region,
ks_cache_region_locations)
hill_top_hoard_region_locations = {
LocationName.hill_top_hoard: [0x643, 1],
}
- hill_top_hoard_region = create_region(world, player, active_locations, LocationName.hill_top_hoard_region,
+ hill_top_hoard_region = create_region(world, active_locations, LocationName.hill_top_hoard_region,
hill_top_hoard_region_locations)
bounty_beach_region_locations = {
LocationName.bounty_beach: [0x646, 1],
}
- bounty_beach_region = create_region(world, player, active_locations, LocationName.bounty_beach_region,
+ bounty_beach_region = create_region(world, active_locations, LocationName.bounty_beach_region,
bounty_beach_region_locations)
smugglers_cove_region_locations = {
LocationName.smugglers_cove: [0x648, 1],
}
- smugglers_cove_region = create_region(world, player, active_locations, LocationName.smugglers_cove_region,
+ smugglers_cove_region = create_region(world, active_locations, LocationName.smugglers_cove_region,
smugglers_cove_region_locations)
arichs_hoard_region_locations = {
LocationName.arichs_hoard: [0x649, 1],
}
- arichs_hoard_region = create_region(world, player, active_locations, LocationName.arichs_hoard_region,
+ arichs_hoard_region = create_region(world, active_locations, LocationName.arichs_hoard_region,
arichs_hoard_region_locations)
bounty_bay_region_locations = {
LocationName.bounty_bay: [0x64A, 1],
}
- bounty_bay_region = create_region(world, player, active_locations, LocationName.bounty_bay_region,
+ bounty_bay_region = create_region(world, active_locations, LocationName.bounty_bay_region,
bounty_bay_region_locations)
sky_high_secret_region_locations = {}
- if False:#world.include_trade_sequence[player]:
+ if False:#world.options.include_trade_sequence:
sky_high_secret_region_locations[LocationName.sky_high_secret] = [0x64B, 1]
- sky_high_secret_region = create_region(world, player, active_locations, LocationName.sky_high_secret_region,
+ sky_high_secret_region = create_region(world, active_locations, LocationName.sky_high_secret_region,
sky_high_secret_region_locations)
glacial_grotto_region_locations = {
LocationName.glacial_grotto: [0x64C, 1],
}
- glacial_grotto_region = create_region(world, player, active_locations, LocationName.glacial_grotto_region,
+ glacial_grotto_region = create_region(world, active_locations, LocationName.glacial_grotto_region,
glacial_grotto_region_locations)
cifftop_cache_region_locations = {}
- if False:#world.include_trade_sequence[player]:
+ if False:#world.options.include_trade_sequence:
cifftop_cache_region_locations[LocationName.cifftop_cache] = [0x64D, 1]
- cifftop_cache_region = create_region(world, player, active_locations, LocationName.cifftop_cache_region,
+ cifftop_cache_region = create_region(world, active_locations, LocationName.cifftop_cache_region,
cifftop_cache_region_locations)
sewer_stockpile_region_locations = {
LocationName.sewer_stockpile: [0x64E, 1],
}
- sewer_stockpile_region = create_region(world, player, active_locations, LocationName.sewer_stockpile_region,
+ sewer_stockpile_region = create_region(world, active_locations, LocationName.sewer_stockpile_region,
sewer_stockpile_region_locations)
# Set up the regions correctly.
- world.regions += [
+ world.multiworld.regions += [
menu_region,
overworld_1_region,
overworld_2_region,
@@ -693,7 +694,7 @@ def create_regions(world, player: int, active_locations):
blue_region_locations = {}
blizzard_region_locations = {}
- if False:#world.include_trade_sequence[player]:
+ if False:#world.options.include_trade_sequence:
bazaar_region_locations.update({
LocationName.bazaars_general_store_1: [0x615, 2, True],
LocationName.bazaars_general_store_2: [0x615, 3, True],
@@ -713,19 +714,19 @@ def create_regions(world, player: int, active_locations):
blizzard_region_locations[LocationName.blizzards_basecamp] = [0x625, 4, True]
- bazaar_region = create_region(world, player, active_locations, LocationName.bazaar_region, bazaar_region_locations)
- bramble_region = create_region(world, player, active_locations, LocationName.bramble_region,
+ bazaar_region = create_region(world, active_locations, LocationName.bazaar_region, bazaar_region_locations)
+ bramble_region = create_region(world, active_locations, LocationName.bramble_region,
bramble_region_locations)
- flower_spot_region = create_region(world, player, active_locations, LocationName.flower_spot_region,
+ flower_spot_region = create_region(world, active_locations, LocationName.flower_spot_region,
flower_spot_region_locations)
- barter_region = create_region(world, player, active_locations, LocationName.barter_region, barter_region_locations)
- barnacle_region = create_region(world, player, active_locations, LocationName.barnacle_region,
+ barter_region = create_region(world, active_locations, LocationName.barter_region, barter_region_locations)
+ barnacle_region = create_region(world, active_locations, LocationName.barnacle_region,
barnacle_region_locations)
- blue_region = create_region(world, player, active_locations, LocationName.blue_region, blue_region_locations)
- blizzard_region = create_region(world, player, active_locations, LocationName.blizzard_region,
+ blue_region = create_region(world, active_locations, LocationName.blue_region, blue_region_locations)
+ blizzard_region = create_region(world, active_locations, LocationName.blizzard_region,
blizzard_region_locations)
- world.regions += [
+ world.multiworld.regions += [
bazaar_region,
bramble_region,
flower_spot_region,
@@ -736,41 +737,41 @@ def create_regions(world, player: int, active_locations):
]
-def connect_regions(world, player, level_list):
+def connect_regions(world: World, level_list):
names: typing.Dict[str, int] = {}
# Overworld
- connect(world, player, names, 'Menu', LocationName.overworld_1_region)
- connect(world, player, names, LocationName.overworld_1_region, LocationName.overworld_2_region,
- lambda state: (state.has(ItemName.progressive_boat, player, 1)))
- connect(world, player, names, LocationName.overworld_2_region, LocationName.overworld_3_region,
- lambda state: (state.has(ItemName.progressive_boat, player, 3)))
- connect(world, player, names, LocationName.overworld_1_region, LocationName.overworld_4_region,
- lambda state: (state.has(ItemName.dk_coin, player, world.dk_coins_for_gyrocopter[player].value) and
- state.has(ItemName.progressive_boat, player, 3)))
+ connect(world, world.player, names, 'Menu', LocationName.overworld_1_region)
+ connect(world, world.player, names, LocationName.overworld_1_region, LocationName.overworld_2_region,
+ lambda state: (state.has(ItemName.progressive_boat, world.player, 1)))
+ connect(world, world.player, names, LocationName.overworld_2_region, LocationName.overworld_3_region,
+ lambda state: (state.has(ItemName.progressive_boat, world.player, 3)))
+ connect(world, world.player, names, LocationName.overworld_1_region, LocationName.overworld_4_region,
+ lambda state: (state.has(ItemName.dk_coin, world.player, world.options.dk_coins_for_gyrocopter.value) and
+ state.has(ItemName.progressive_boat, world.player, 3)))
# World Connections
- connect(world, player, names, LocationName.overworld_1_region, LocationName.lake_orangatanga_region)
- connect(world, player, names, LocationName.overworld_1_region, LocationName.kremwood_forest_region)
- connect(world, player, names, LocationName.overworld_1_region, LocationName.bounty_beach_region)
- connect(world, player, names, LocationName.overworld_1_region, LocationName.bazaar_region)
+ connect(world, world.player, names, LocationName.overworld_1_region, LocationName.lake_orangatanga_region)
+ connect(world, world.player, names, LocationName.overworld_1_region, LocationName.kremwood_forest_region)
+ connect(world, world.player, names, LocationName.overworld_1_region, LocationName.bounty_beach_region)
+ connect(world, world.player, names, LocationName.overworld_1_region, LocationName.bazaar_region)
- connect(world, player, names, LocationName.overworld_2_region, LocationName.cotton_top_cove_region)
- connect(world, player, names, LocationName.overworld_2_region, LocationName.mekanos_region)
- connect(world, player, names, LocationName.overworld_2_region, LocationName.kong_cave_region)
- connect(world, player, names, LocationName.overworld_2_region, LocationName.bramble_region)
+ connect(world, world.player, names, LocationName.overworld_2_region, LocationName.cotton_top_cove_region)
+ connect(world, world.player, names, LocationName.overworld_2_region, LocationName.mekanos_region)
+ connect(world, world.player, names, LocationName.overworld_2_region, LocationName.kong_cave_region)
+ connect(world, world.player, names, LocationName.overworld_2_region, LocationName.bramble_region)
- connect(world, player, names, LocationName.overworld_3_region, LocationName.k3_region)
- connect(world, player, names, LocationName.overworld_3_region, LocationName.razor_ridge_region)
- connect(world, player, names, LocationName.overworld_3_region, LocationName.kaos_kore_region)
- connect(world, player, names, LocationName.overworld_3_region, LocationName.krematoa_region)
- connect(world, player, names, LocationName.overworld_3_region, LocationName.undercover_cove_region)
- connect(world, player, names, LocationName.overworld_3_region, LocationName.flower_spot_region)
- connect(world, player, names, LocationName.overworld_3_region, LocationName.barter_region)
+ connect(world, world.player, names, LocationName.overworld_3_region, LocationName.k3_region)
+ connect(world, world.player, names, LocationName.overworld_3_region, LocationName.razor_ridge_region)
+ connect(world, world.player, names, LocationName.overworld_3_region, LocationName.kaos_kore_region)
+ connect(world, world.player, names, LocationName.overworld_3_region, LocationName.krematoa_region)
+ connect(world, world.player, names, LocationName.overworld_3_region, LocationName.undercover_cove_region)
+ connect(world, world.player, names, LocationName.overworld_3_region, LocationName.flower_spot_region)
+ connect(world, world.player, names, LocationName.overworld_3_region, LocationName.barter_region)
- connect(world, player, names, LocationName.overworld_4_region, LocationName.belchas_burrow_region)
- connect(world, player, names, LocationName.overworld_4_region, LocationName.ks_cache_region)
- connect(world, player, names, LocationName.overworld_4_region, LocationName.hill_top_hoard_region)
+ connect(world, world.player, names, LocationName.overworld_4_region, LocationName.belchas_burrow_region)
+ connect(world, world.player, names, LocationName.overworld_4_region, LocationName.ks_cache_region)
+ connect(world, world.player, names, LocationName.overworld_4_region, LocationName.hill_top_hoard_region)
# Lake Orangatanga Connections
@@ -786,7 +787,7 @@ def connect_regions(world, player, level_list):
]
for i in range(0, len(lake_orangatanga_levels)):
- connect(world, player, names, LocationName.lake_orangatanga_region, lake_orangatanga_levels[i])
+ connect(world, world.player, names, LocationName.lake_orangatanga_region, lake_orangatanga_levels[i])
# Kremwood Forest Connections
kremwood_forest_levels = [
@@ -800,10 +801,10 @@ def connect_regions(world, player, level_list):
]
for i in range(0, len(kremwood_forest_levels) - 1):
- connect(world, player, names, LocationName.kremwood_forest_region, kremwood_forest_levels[i])
+ connect(world, world.player, names, LocationName.kremwood_forest_region, kremwood_forest_levels[i])
- connect(world, player, names, LocationName.kremwood_forest_region, kremwood_forest_levels[-1],
- lambda state: (state.can_reach(LocationName.riverside_race_flag, "Location", player)))
+ connect(world, world.player, names, LocationName.kremwood_forest_region, kremwood_forest_levels[-1],
+ lambda state: (state.can_reach(LocationName.riverside_race_flag, "Location", world.player)))
# Cotton-Top Cove Connections
cotton_top_cove_levels = [
@@ -818,7 +819,7 @@ def connect_regions(world, player, level_list):
]
for i in range(0, len(cotton_top_cove_levels)):
- connect(world, player, names, LocationName.cotton_top_cove_region, cotton_top_cove_levels[i])
+ connect(world, world.player, names, LocationName.cotton_top_cove_region, cotton_top_cove_levels[i])
# Mekanos Connections
mekanos_levels = [
@@ -831,14 +832,14 @@ def connect_regions(world, player, level_list):
]
for i in range(0, len(mekanos_levels)):
- connect(world, player, names, LocationName.mekanos_region, mekanos_levels[i])
+ connect(world, world.player, names, LocationName.mekanos_region, mekanos_levels[i])
- if False:#world.include_trade_sequence[player]:
- connect(world, player, names, LocationName.mekanos_region, LocationName.sky_high_secret_region,
- lambda state: (state.has(ItemName.bowling_ball, player, 1)))
+ if False:#world.options.include_trade_sequence:
+ connect(world, world.player, names, LocationName.mekanos_region, LocationName.sky_high_secret_region,
+ lambda state: (state.has(ItemName.bowling_ball, world.player, 1)))
else:
- connect(world, player, names, LocationName.mekanos_region, LocationName.sky_high_secret_region,
- lambda state: (state.can_reach(LocationName.bleaks_house, "Location", player)))
+ connect(world, world.player, names, LocationName.mekanos_region, LocationName.sky_high_secret_region,
+ lambda state: (state.can_reach(LocationName.bleaks_house, "Location", world.player)))
# K3 Connections
k3_levels = [
@@ -853,7 +854,7 @@ def connect_regions(world, player, level_list):
]
for i in range(0, len(k3_levels)):
- connect(world, player, names, LocationName.k3_region, k3_levels[i])
+ connect(world, world.player, names, LocationName.k3_region, k3_levels[i])
# Razor Ridge Connections
razor_ridge_levels = [
@@ -866,13 +867,13 @@ def connect_regions(world, player, level_list):
]
for i in range(0, len(razor_ridge_levels)):
- connect(world, player, names, LocationName.razor_ridge_region, razor_ridge_levels[i])
+ connect(world, world.player, names, LocationName.razor_ridge_region, razor_ridge_levels[i])
- if False:#world.include_trade_sequence[player]:
- connect(world, player, names, LocationName.razor_ridge_region, LocationName.cifftop_cache_region,
- lambda state: (state.has(ItemName.wrench, player, 1)))
+ if False:#world.options.include_trade_sequence:
+ connect(world, world.player, names, LocationName.razor_ridge_region, LocationName.cifftop_cache_region,
+ lambda state: (state.has(ItemName.wrench, world.player, 1)))
else:
- connect(world, player, names, LocationName.razor_ridge_region, LocationName.cifftop_cache_region)
+ connect(world, world.player, names, LocationName.razor_ridge_region, LocationName.cifftop_cache_region)
# KAOS Kore Connections
kaos_kore_levels = [
@@ -885,7 +886,7 @@ def connect_regions(world, player, level_list):
]
for i in range(0, len(kaos_kore_levels)):
- connect(world, player, names, LocationName.kaos_kore_region, kaos_kore_levels[i])
+ connect(world, world.player, names, LocationName.kaos_kore_region, kaos_kore_levels[i])
# Krematoa Connections
krematoa_levels = [
@@ -897,22 +898,22 @@ def connect_regions(world, player, level_list):
]
for i in range(0, len(krematoa_levels)):
- connect(world, player, names, LocationName.krematoa_region, krematoa_levels[i],
- lambda state, i=i: (state.has(ItemName.bonus_coin, player, world.krematoa_bonus_coin_cost[player].value * (i+1))))
+ connect(world, world.player, names, LocationName.krematoa_region, krematoa_levels[i],
+ lambda state, i=i: (state.has(ItemName.bonus_coin, world.player, world.options.krematoa_bonus_coin_cost.value * (i+1))))
- if world.goal[player] == "knautilus":
- connect(world, player, names, LocationName.kaos_kore_region, LocationName.knautilus_region)
- connect(world, player, names, LocationName.krematoa_region, LocationName.kastle_kaos_region,
- lambda state: (state.has(ItemName.krematoa_cog, player, 5)))
+ if world.options.goal == "knautilus":
+ connect(world, world.player, names, LocationName.kaos_kore_region, LocationName.knautilus_region)
+ connect(world, world.player, names, LocationName.krematoa_region, LocationName.kastle_kaos_region,
+ lambda state: (state.has(ItemName.krematoa_cog, world.player, 5)))
else:
- connect(world, player, names, LocationName.kaos_kore_region, LocationName.kastle_kaos_region)
- connect(world, player, names, LocationName.krematoa_region, LocationName.knautilus_region,
- lambda state: (state.has(ItemName.krematoa_cog, player, 5)))
+ connect(world, world.player, names, LocationName.kaos_kore_region, LocationName.kastle_kaos_region)
+ connect(world, world.player, names, LocationName.krematoa_region, LocationName.knautilus_region,
+ lambda state: (state.has(ItemName.krematoa_cog, world.player, 5)))
-def create_region(world: MultiWorld, player: int, active_locations, name: str, locations=None):
+def create_region(world: World, active_locations, name: str, locations=None):
# Shamelessly stolen from the ROR2 definition
- ret = Region(name, player, world)
+ ret = Region(name, world.player, world.multiworld)
if locations:
for locationName, locationData in locations.items():
loc_id = active_locations.get(locationName, 0)
@@ -921,16 +922,16 @@ def create_region(world: MultiWorld, player: int, active_locations, name: str, l
loc_bit = locationData[1] if (len(locationData) > 1) else 0
loc_invert = locationData[2] if (len(locationData) > 2) else False
- location = DKC3Location(player, locationName, loc_id, ret, loc_byte, loc_bit, loc_invert)
+ location = DKC3Location(world.player, locationName, loc_id, ret, loc_byte, loc_bit, loc_invert)
ret.locations.append(location)
return ret
-def connect(world: MultiWorld, player: int, used_names: typing.Dict[str, int], source: str, target: str,
+def connect(world: World, player: int, used_names: typing.Dict[str, int], source: str, target: str,
rule: typing.Optional[typing.Callable] = None):
- source_region = world.get_region(source, player)
- target_region = world.get_region(target, player)
+ source_region = world.multiworld.get_region(source, player)
+ target_region = world.multiworld.get_region(target, player)
if target not in used_names:
used_names[target] = 1
diff --git a/worlds/dkc3/Rom.py b/worlds/dkc3/Rom.py
index 4255a0a38280..efe8033d0fa5 100644
--- a/worlds/dkc3/Rom.py
+++ b/worlds/dkc3/Rom.py
@@ -1,5 +1,6 @@
import Utils
from Utils import read_snes_rom
+from worlds.AutoWorld import World
from worlds.Files import APDeltaPatch
from .Locations import lookup_id_to_name, all_locations
from .Levels import level_list, level_dict
@@ -475,11 +476,10 @@ def read_from_file(self, file):
-def patch_rom(world, rom, player, active_level_list):
- local_random = world.per_slot_randoms[player]
+def patch_rom(world: World, rom: LocalRom, active_level_list):
# Boomer Costs
- bonus_coin_cost = world.krematoa_bonus_coin_cost[player]
+ bonus_coin_cost = world.options.krematoa_bonus_coin_cost
inverted_bonus_coin_cost = 0x100 - bonus_coin_cost
rom.write_byte(0x3498B9, inverted_bonus_coin_cost)
rom.write_byte(0x3498BA, inverted_bonus_coin_cost)
@@ -491,7 +491,7 @@ def patch_rom(world, rom, player, active_level_list):
rom.write_byte(0x349862, bonus_coin_cost)
# Gyrocopter Costs
- dk_coin_cost = world.dk_coins_for_gyrocopter[player]
+ dk_coin_cost = world.options.dk_coins_for_gyrocopter
rom.write_byte(0x3484A6, dk_coin_cost)
rom.write_byte(0x3484D5, dk_coin_cost)
rom.write_byte(0x3484D7, 0x90)
@@ -508,8 +508,8 @@ def patch_rom(world, rom, player, active_level_list):
rom.write_bytes(0x34ACD0, bytearray([0xEA, 0xEA]))
# Banana Bird Costs
- if world.goal[player] == "banana_bird_hunt":
- banana_bird_cost = math.floor(world.number_of_banana_birds[player] * world.percentage_of_banana_birds[player] / 100.0)
+ if world.options.goal == "banana_bird_hunt":
+ banana_bird_cost = math.floor(world.options.number_of_banana_birds * world.options.percentage_of_banana_birds / 100.0)
rom.write_byte(0x34AB85, banana_bird_cost)
rom.write_byte(0x329FD8, banana_bird_cost)
rom.write_byte(0x32A025, banana_bird_cost)
@@ -528,65 +528,65 @@ def patch_rom(world, rom, player, active_level_list):
# Palette Swap
rom.write_byte(0x3B96A5, 0xD0)
- if world.kong_palette_swap[player] == "default":
+ if world.options.kong_palette_swap == "default":
rom.write_byte(0x3B96A9, 0x00)
rom.write_byte(0x3B96A8, 0x00)
- elif world.kong_palette_swap[player] == "purple":
+ elif world.options.kong_palette_swap == "purple":
rom.write_byte(0x3B96A9, 0x00)
rom.write_byte(0x3B96A8, 0x3C)
- elif world.kong_palette_swap[player] == "spooky":
+ elif world.options.kong_palette_swap == "spooky":
rom.write_byte(0x3B96A9, 0x00)
rom.write_byte(0x3B96A8, 0xA0)
- elif world.kong_palette_swap[player] == "dark":
+ elif world.options.kong_palette_swap == "dark":
rom.write_byte(0x3B96A9, 0x05)
rom.write_byte(0x3B96A8, 0xA0)
- elif world.kong_palette_swap[player] == "chocolate":
+ elif world.options.kong_palette_swap == "chocolate":
rom.write_byte(0x3B96A9, 0x1D)
rom.write_byte(0x3B96A8, 0xA0)
- elif world.kong_palette_swap[player] == "shadow":
+ elif world.options.kong_palette_swap == "shadow":
rom.write_byte(0x3B96A9, 0x45)
rom.write_byte(0x3B96A8, 0xA0)
- elif world.kong_palette_swap[player] == "red_gold":
+ elif world.options.kong_palette_swap == "red_gold":
rom.write_byte(0x3B96A9, 0x5D)
rom.write_byte(0x3B96A8, 0xA0)
- elif world.kong_palette_swap[player] == "gbc":
+ elif world.options.kong_palette_swap == "gbc":
rom.write_byte(0x3B96A9, 0x20)
rom.write_byte(0x3B96A8, 0x3C)
- elif world.kong_palette_swap[player] == "halloween":
+ elif world.options.kong_palette_swap == "halloween":
rom.write_byte(0x3B96A9, 0x70)
rom.write_byte(0x3B96A8, 0x3C)
- if world.music_shuffle[player]:
+ if world.options.music_shuffle:
for address in music_rom_data:
- rand_song = local_random.choice(level_music_ids)
+ rand_song = world.random.choice(level_music_ids)
rom.write_byte(address, rand_song)
# Starting Lives
- rom.write_byte(0x9130, world.starting_life_count[player].value)
- rom.write_byte(0x913B, world.starting_life_count[player].value)
+ rom.write_byte(0x9130, world.options.starting_life_count.value)
+ rom.write_byte(0x913B, world.options.starting_life_count.value)
# Cheat options
cheat_bytes = [0x00, 0x00]
- if world.merry[player]:
+ if world.options.merry:
cheat_bytes[0] |= 0x01
- if world.autosave[player]:
+ if world.options.autosave:
cheat_bytes[0] |= 0x02
- if world.difficulty[player] == "tufst":
+ if world.options.difficulty == "tufst":
cheat_bytes[0] |= 0x80
cheat_bytes[1] |= 0x80
- elif world.difficulty[player] == "hardr":
+ elif world.options.difficulty == "hardr":
cheat_bytes[0] |= 0x00
cheat_bytes[1] |= 0x00
- elif world.difficulty[player] == "norml":
+ elif world.options.difficulty == "norml":
cheat_bytes[1] |= 0x40
rom.write_bytes(0x8303, bytearray(cheat_bytes))
# Handle Level Shuffle Here
- if world.level_shuffle[player]:
+ if world.options.level_shuffle:
for i in range(len(active_level_list)):
rom.write_byte(level_dict[level_list[i]].nameIDAddress, level_dict[active_level_list[i]].nameID)
rom.write_byte(level_dict[level_list[i]].levelIDAddress, level_dict[active_level_list[i]].levelID)
@@ -611,7 +611,7 @@ def patch_rom(world, rom, player, active_level_list):
rom.write_byte(0x34C213, (0x32 + level_dict[active_level_list[25]].levelID))
rom.write_byte(0x34C21B, (0x32 + level_dict[active_level_list[26]].levelID))
- if world.goal[player] == "knautilus":
+ if world.options.goal == "knautilus":
# Swap Kastle KAOS and Knautilus
rom.write_byte(0x34D4E1, 0xC2)
rom.write_byte(0x34D4E2, 0x24)
@@ -621,7 +621,7 @@ def patch_rom(world, rom, player, active_level_list):
rom.write_byte(0x32F339, 0x55)
# Handle KONGsanity Here
- if world.kongsanity[player]:
+ if world.options.kongsanity:
# Arich's Hoard KONGsanity fix
rom.write_bytes(0x34BA8C, bytearray([0xEA, 0xEA]))
@@ -668,7 +668,7 @@ def patch_rom(world, rom, player, active_level_list):
rom.write_bytes(0x32A5EE, bytearray([0x00, 0x03, 0x50, 0x4F, 0x52, 0x59, 0x47, 0x4F, 0x4E, 0xC5])) # "PORYGONE"
from Utils import __version__
- rom.name = bytearray(f'D3{__version__.replace(".", "")[0:3]}_{player}_{world.seed:11}\0', 'utf8')[:21]
+ rom.name = bytearray(f'D3{__version__.replace(".", "")[0:3]}_{world.player}_{world.multiworld.seed:11}\0', 'utf8')[:21]
rom.name.extend([0] * (21 - len(rom.name)))
rom.write_bytes(0x7FC0, rom.name)
diff --git a/worlds/dkc3/Rules.py b/worlds/dkc3/Rules.py
index dc90eefd1367..cc45e4ef3ad5 100644
--- a/worlds/dkc3/Rules.py
+++ b/worlds/dkc3/Rules.py
@@ -1,32 +1,31 @@
import math
-from BaseClasses import MultiWorld
from .Names import LocationName, ItemName
-from worlds.AutoWorld import LogicMixin
+from worlds.AutoWorld import LogicMixin, World
from worlds.generic.Rules import add_rule, set_rule
-def set_rules(world: MultiWorld, player: int):
+def set_rules(world: World):
- if False:#world.include_trade_sequence[player]:
- add_rule(world.get_location(LocationName.barnacles_island, player),
- lambda state: state.has(ItemName.shell, player))
+ if False:#world.options.include_trade_sequence:
+ add_rule(world.multiworld.get_location(LocationName.barnacles_island, world.player),
+ lambda state: state.has(ItemName.shell, world.player))
- add_rule(world.get_location(LocationName.blues_beach_hut, player),
- lambda state: state.has(ItemName.present, player))
+ add_rule(world.multiworld.get_location(LocationName.blues_beach_hut, world.player),
+ lambda state: state.has(ItemName.present, world.player))
- add_rule(world.get_location(LocationName.brambles_bungalow, player),
- lambda state: state.has(ItemName.flower, player))
+ add_rule(world.multiworld.get_location(LocationName.brambles_bungalow, world.player),
+ lambda state: state.has(ItemName.flower, world.player))
- add_rule(world.get_location(LocationName.barters_swap_shop, player),
- lambda state: state.has(ItemName.mirror, player))
+ add_rule(world.multiworld.get_location(LocationName.barters_swap_shop, world.player),
+ lambda state: state.has(ItemName.mirror, world.player))
- if world.goal[player] != "knautilus":
+ if world.options.goal != "knautilus":
required_banana_birds = math.floor(
- world.number_of_banana_birds[player].value * (world.percentage_of_banana_birds[player].value / 100.0))
+ world.options.number_of_banana_birds.value * (world.options.percentage_of_banana_birds.value / 100.0))
- add_rule(world.get_location(LocationName.banana_bird_mother, player),
- lambda state: state.has(ItemName.banana_bird, player, required_banana_birds))
+ add_rule(world.multiworld.get_location(LocationName.banana_bird_mother, world.player),
+ lambda state: state.has(ItemName.banana_bird, world.player, required_banana_birds))
- world.completion_condition[player] = lambda state: state.has(ItemName.victory, player)
+ world.multiworld.completion_condition[world.player] = lambda state: state.has(ItemName.victory, world.player)
diff --git a/worlds/dkc3/__init__.py b/worlds/dkc3/__init__.py
index 462e1416d9e6..dfb42bd04ca8 100644
--- a/worlds/dkc3/__init__.py
+++ b/worlds/dkc3/__init__.py
@@ -1,3 +1,4 @@
+import dataclasses
import os
import typing
import math
@@ -5,9 +6,10 @@
import settings
from BaseClasses import Item, MultiWorld, Tutorial, ItemClassification
+from Options import PerGameCommonOptions
from .Items import DKC3Item, ItemData, item_table, inventory_table, junk_table
from .Locations import DKC3Location, all_locations, setup_locations
-from .Options import dkc3_options
+from .Options import DKC3Options
from .Regions import create_regions, connect_regions
from .Levels import level_list
from .Rules import set_rules
@@ -50,8 +52,11 @@ class DKC3World(World):
mystery of why Donkey Kong and Diddy disappeared while on vacation.
"""
game: str = "Donkey Kong Country 3"
- option_definitions = dkc3_options
settings: typing.ClassVar[DK3Settings]
+
+ options_dataclass = DKC3Options
+ options: DKC3Options
+
topology_present = False
data_version = 2
#hint_blacklist = {LocationName.rocket_rush_flag}
@@ -74,24 +79,25 @@ def stage_assert_generate(cls, multiworld: MultiWorld):
def _get_slot_data(self):
return {
- #"death_link": self.world.death_link[self.player].value,
+ #"death_link": self.options.death_link.value,
"active_levels": self.active_level_list,
}
def fill_slot_data(self) -> dict:
slot_data = self._get_slot_data()
- for option_name in dkc3_options:
- option = getattr(self.multiworld, option_name)[self.player]
+ for option_name in (attr.name for attr in dataclasses.fields(DKC3Options)
+ if attr not in dataclasses.fields(PerGameCommonOptions)):
+ option = getattr(self.options, option_name)
slot_data[option_name] = option.value
return slot_data
def create_regions(self):
- location_table = setup_locations(self.multiworld, self.player)
- create_regions(self.multiworld, self.player, location_table)
+ location_table = setup_locations(self)
+ create_regions(self, location_table)
# Not generate basic
- self.topology_present = self.multiworld.level_shuffle[self.player].value
+ self.topology_present = self.options.level_shuffle.value
itempool: typing.List[DKC3Item] = []
# Levels
@@ -103,12 +109,12 @@ def create_regions(self):
number_of_cogs = 4
self.multiworld.get_location(LocationName.rocket_rush_flag, self.player).place_locked_item(self.create_item(ItemName.krematoa_cog))
number_of_bosses = 8
- if self.multiworld.goal[self.player] == "knautilus":
+ if self.options.goal == "knautilus":
self.multiworld.get_location(LocationName.kastle_kaos, self.player).place_locked_item(self.create_item(ItemName.victory))
number_of_bosses = 7
else:
self.multiworld.get_location(LocationName.banana_bird_mother, self.player).place_locked_item(self.create_item(ItemName.victory))
- number_of_banana_birds = self.multiworld.number_of_banana_birds[self.player]
+ number_of_banana_birds = self.options.number_of_banana_birds
# Bosses
total_required_locations += number_of_bosses
@@ -116,15 +122,15 @@ def create_regions(self):
# Secret Caves
total_required_locations += 13
- if self.multiworld.kongsanity[self.player]:
+ if self.options.kongsanity:
total_required_locations += 39
## Brothers Bear
- if False:#self.world.include_trade_sequence[self.player]:
+ if False:#self.options.include_trade_sequence:
total_required_locations += 10
- number_of_bonus_coins = (self.multiworld.krematoa_bonus_coin_cost[self.player] * 5)
- number_of_bonus_coins += math.ceil((85 - number_of_bonus_coins) * self.multiworld.percentage_of_extra_bonus_coins[self.player] / 100)
+ number_of_bonus_coins = (self.options.krematoa_bonus_coin_cost * 5)
+ number_of_bonus_coins += math.ceil((85 - number_of_bonus_coins) * self.options.percentage_of_extra_bonus_coins / 100)
itempool += [self.create_item(ItemName.bonus_coin) for _ in range(number_of_bonus_coins)]
itempool += [self.create_item(ItemName.dk_coin) for _ in range(41)]
@@ -142,20 +148,17 @@ def create_regions(self):
self.active_level_list = level_list.copy()
- if self.multiworld.level_shuffle[self.player]:
- self.multiworld.random.shuffle(self.active_level_list)
+ if self.options.level_shuffle:
+ self.random.shuffle(self.active_level_list)
- connect_regions(self.multiworld, self.player, self.active_level_list)
+ connect_regions(self, self.active_level_list)
self.multiworld.itempool += itempool
def generate_output(self, output_directory: str):
try:
- world = self.multiworld
- player = self.player
-
rom = LocalRom(get_base_rom_path())
- patch_rom(self.multiworld, rom, self.player, self.active_level_list)
+ patch_rom(self, rom, self.active_level_list)
self.active_level_list.append(LocationName.rocket_rush_region)
@@ -163,15 +166,15 @@ def generate_output(self, output_directory: str):
rom.write_to_file(rompath)
self.rom_name = rom.name
- patch = DKC3DeltaPatch(os.path.splitext(rompath)[0]+DKC3DeltaPatch.patch_file_ending, player=player,
- player_name=world.player_name[player], patched_path=rompath)
+ patch = DKC3DeltaPatch(os.path.splitext(rompath)[0]+DKC3DeltaPatch.patch_file_ending, player=self.player,
+ player_name=self.multiworld.player_name[self.player], patched_path=rompath)
patch.write()
except:
raise
finally:
+ self.rom_name_available_event.set() # make sure threading continues and errors are collected
if os.path.exists(rompath):
os.unlink(rompath)
- self.rom_name_available_event.set() # make sure threading continues and errors are collected
def modify_multidata(self, multidata: dict):
import base64
@@ -183,6 +186,7 @@ def modify_multidata(self, multidata: dict):
new_name = base64.b64encode(bytes(self.rom_name)).decode()
multidata["connect_names"][new_name] = multidata["connect_names"][self.multiworld.player_name[self.player]]
+ def extend_hint_information(self, hint_data: typing.Dict[int, typing.Dict[int, str]]):
if self.topology_present:
world_names = [
LocationName.lake_orangatanga_region,
@@ -200,7 +204,8 @@ def modify_multidata(self, multidata: dict):
level_region = self.multiworld.get_region(self.active_level_list[world_index * 5 + level_index], self.player)
for location in level_region.locations:
er_hint_data[location.address] = world_names[world_index]
- multidata['er_hint_data'][self.player] = er_hint_data
+
+ hint_data[self.player] = er_hint_data
def create_item(self, name: str, force_non_progression=False) -> Item:
data = item_table[name]
@@ -220,4 +225,4 @@ def get_filler_item_name(self) -> str:
return self.multiworld.random.choice(list(junk_table.keys()))
def set_rules(self):
- set_rules(self.multiworld, self.player)
+ set_rules(self)
diff --git a/worlds/dlcquest/__init__.py b/worlds/dlcquest/__init__.py
index ca7a0157cb5c..db55b1903b61 100644
--- a/worlds/dlcquest/__init__.py
+++ b/worlds/dlcquest/__init__.py
@@ -14,7 +14,7 @@
class DLCqwebworld(WebWorld):
setup_en = Tutorial(
- "Multiworld Setup Tutorial",
+ "Multiworld Setup Guide",
"A guide to setting up the Archipelago DLCQuest game on your computer.",
"English",
"setup_en.md",
diff --git a/worlds/factorio/__init__.py b/worlds/factorio/__init__.py
index 17f3163e9026..3b7475738489 100644
--- a/worlds/factorio/__init__.py
+++ b/worlds/factorio/__init__.py
@@ -53,7 +53,7 @@ class BridgeChatOut(settings.Bool):
class FactorioWeb(WebWorld):
tutorials = [Tutorial(
- "Multiworld Setup Tutorial",
+ "Multiworld Setup Guide",
"A guide to setting up the Archipelago Factorio software on your computer.",
"English",
"setup_en.md",
diff --git a/worlds/ff1/data/locations.json b/worlds/ff1/data/locations.json
index 9771d51de088..2f465a78970e 100644
--- a/worlds/ff1/data/locations.json
+++ b/worlds/ff1/data/locations.json
@@ -1,253 +1,253 @@
{
- "Coneria1": 257,
- "Coneria2": 258,
- "ConeriaMajor": 259,
- "Coneria4": 260,
- "Coneria5": 261,
- "Coneria6": 262,
- "MatoyasCave1": 299,
- "MatoyasCave3": 301,
- "MatoyasCave2": 300,
- "NorthwestCastle1": 273,
- "NorthwestCastle3": 275,
- "NorthwestCastle2": 274,
- "ToFTopLeft1": 263,
- "ToFBottomLeft": 265,
- "ToFTopLeft2": 264,
- "ToFRevisited6": 509,
- "ToFRevisited4": 507,
- "ToFRMasmune": 504,
- "ToFRevisited5": 508,
- "ToFRevisited3": 506,
- "ToFRevisited2": 505,
- "ToFRevisited7": 510,
- "ToFTopRight1": 267,
- "ToFTopRight2": 268,
- "ToFBottomRight": 266,
- "IceCave15": 377,
- "IceCave16": 378,
- "IceCave9": 371,
- "IceCave11": 373,
- "IceCave10": 372,
- "IceCave12": 374,
- "IceCave13": 375,
- "IceCave14": 376,
- "IceCave1": 363,
- "IceCave2": 364,
- "IceCave3": 365,
- "IceCave4": 366,
- "IceCave5": 367,
- "IceCaveMajor": 370,
- "IceCave7": 369,
- "IceCave6": 368,
- "Elfland1": 269,
- "Elfland2": 270,
- "Elfland3": 271,
- "Elfland4": 272,
- "Ordeals5": 383,
- "Ordeals6": 384,
- "Ordeals7": 385,
- "Ordeals1": 379,
- "Ordeals2": 380,
- "Ordeals3": 381,
- "Ordeals4": 382,
- "OrdealsMajor": 387,
- "Ordeals8": 386,
- "SeaShrine7": 411,
- "SeaShrine8": 412,
- "SeaShrine9": 413,
- "SeaShrine10": 414,
- "SeaShrine1": 405,
- "SeaShrine2": 406,
- "SeaShrine3": 407,
- "SeaShrine4": 408,
- "SeaShrine5": 409,
- "SeaShrine6": 410,
- "SeaShrine13": 417,
- "SeaShrine14": 418,
- "SeaShrine11": 415,
- "SeaShrine15": 419,
- "SeaShrine16": 420,
- "SeaShrineLocked": 421,
- "SeaShrine18": 422,
- "SeaShrine19": 423,
- "SeaShrine20": 424,
- "SeaShrine23": 427,
- "SeaShrine21": 425,
- "SeaShrine22": 426,
- "SeaShrine24": 428,
- "SeaShrine26": 430,
- "SeaShrine28": 432,
- "SeaShrine25": 429,
- "SeaShrine30": 434,
- "SeaShrine31": 435,
- "SeaShrine27": 431,
- "SeaShrine29": 433,
- "SeaShrineMajor": 436,
- "SeaShrine12": 416,
- "DwarfCave3": 291,
- "DwarfCave4": 292,
- "DwarfCave6": 294,
- "DwarfCave7": 295,
- "DwarfCave5": 293,
- "DwarfCave8": 296,
- "DwarfCave9": 297,
- "DwarfCave10": 298,
- "DwarfCave1": 289,
- "DwarfCave2": 290,
- "Waterfall1": 437,
- "Waterfall2": 438,
- "Waterfall3": 439,
- "Waterfall4": 440,
- "Waterfall5": 441,
- "Waterfall6": 442,
- "MirageTower5": 456,
- "MirageTower16": 467,
- "MirageTower17": 468,
- "MirageTower15": 466,
- "MirageTower18": 469,
- "MirageTower14": 465,
- "SkyPalace1": 470,
- "SkyPalace2": 471,
- "SkyPalace3": 472,
- "SkyPalace4": 473,
- "SkyPalace18": 487,
- "SkyPalace19": 488,
- "SkyPalace16": 485,
- "SkyPalaceMajor": 489,
- "SkyPalace17": 486,
- "SkyPalace22": 491,
- "SkyPalace21": 490,
- "SkyPalace23": 492,
- "SkyPalace24": 493,
- "SkyPalace31": 500,
- "SkyPalace32": 501,
- "SkyPalace33": 502,
- "SkyPalace34": 503,
- "SkyPalace29": 498,
- "SkyPalace26": 495,
- "SkyPalace25": 494,
- "SkyPalace28": 497,
- "SkyPalace27": 496,
- "SkyPalace30": 499,
- "SkyPalace14": 483,
- "SkyPalace11": 480,
- "SkyPalace12": 481,
- "SkyPalace13": 482,
- "SkyPalace15": 484,
- "SkyPalace10": 479,
- "SkyPalace5": 474,
- "SkyPalace6": 475,
- "SkyPalace7": 476,
- "SkyPalace8": 477,
- "SkyPalace9": 478,
- "MirageTower9": 460,
- "MirageTower13": 464,
- "MirageTower10": 461,
- "MirageTower12": 463,
- "MirageTower11": 462,
- "MirageTower1": 452,
- "MirageTower2": 453,
- "MirageTower4": 455,
- "MirageTower3": 454,
- "MirageTower8": 459,
- "MirageTower7": 458,
- "MirageTower6": 457,
- "Volcano30": 359,
- "Volcano32": 361,
- "Volcano31": 360,
- "Volcano28": 357,
- "Volcano29": 358,
- "Volcano21": 350,
- "Volcano20": 349,
- "Volcano24": 353,
- "Volcano19": 348,
- "Volcano25": 354,
- "VolcanoMajor": 362,
- "Volcano26": 355,
- "Volcano27": 356,
- "Volcano22": 351,
- "Volcano23": 352,
- "Volcano1": 330,
- "Volcano9": 338,
- "Volcano2": 331,
- "Volcano10": 339,
- "Volcano3": 332,
- "Volcano8": 337,
- "Volcano4": 333,
- "Volcano13": 342,
- "Volcano11": 340,
- "Volcano7": 336,
- "Volcano6": 335,
- "Volcano5": 334,
- "Volcano14": 343,
- "Volcano12": 341,
- "Volcano15": 344,
- "Volcano18": 347,
- "Volcano17": 346,
- "Volcano16": 345,
- "MarshCave6": 281,
- "MarshCave5": 280,
- "MarshCave7": 282,
- "MarshCave8": 283,
- "MarshCave10": 285,
- "MarshCave2": 277,
- "MarshCave11": 286,
- "MarshCave3": 278,
- "MarshCaveMajor": 284,
- "MarshCave12": 287,
- "MarshCave4": 279,
- "MarshCave1": 276,
- "MarshCave13": 288,
- "TitansTunnel1": 326,
- "TitansTunnel2": 327,
- "TitansTunnel3": 328,
- "TitansTunnel4": 329,
- "EarthCave1": 302,
- "EarthCave2": 303,
- "EarthCave5": 306,
- "EarthCave3": 304,
- "EarthCave4": 305,
- "EarthCave9": 310,
- "EarthCave10": 311,
- "EarthCave11": 312,
- "EarthCave6": 307,
- "EarthCave7": 308,
- "EarthCave12": 313,
- "EarthCaveMajor": 317,
- "EarthCave19": 320,
- "EarthCave17": 318,
- "EarthCave18": 319,
- "EarthCave20": 321,
- "EarthCave24": 325,
- "EarthCave21": 322,
- "EarthCave22": 323,
- "EarthCave23": 324,
- "EarthCave13": 314,
- "EarthCave15": 316,
- "EarthCave14": 315,
- "EarthCave8": 309,
- "Cardia11": 398,
- "Cardia9": 396,
- "Cardia10": 397,
- "Cardia6": 393,
- "Cardia8": 395,
- "Cardia7": 394,
- "Cardia13": 400,
- "Cardia12": 399,
- "Cardia4": 391,
- "Cardia5": 392,
- "Cardia3": 390,
- "Cardia1": 388,
- "Cardia2": 389,
- "CaravanShop": 767,
+ "Matoya's Cave - Chest 1": 299,
+ "Matoya's Cave - Chest 2": 301,
+ "Matoya's Cave - Chest 3": 300,
+ "Dwarf Cave - Entrance 1": 289,
+ "Dwarf Cave - Entrance 2": 290,
+ "Dwarf Cave - Treasury 1": 291,
+ "Dwarf Cave - Treasury 2": 292,
+ "Dwarf Cave - Treasury 3": 295,
+ "Dwarf Cave - Treasury 4": 293,
+ "Dwarf Cave - Treasury 5": 294,
+ "Dwarf Cave - Treasury 6": 296,
+ "Dwarf Cave - Treasury 7": 297,
+ "Dwarf Cave - Treasury 8": 298,
+ "Coneria Castle - Treasury 1": 257,
+ "Coneria Castle - Treasury 2": 258,
+ "Coneria Castle - Treasury 3": 260,
+ "Coneria Castle - Treasury 4": 261,
+ "Coneria Castle - Treasury 5": 262,
+ "Coneria Castle - Treasury Major": 259,
+ "Elf Castle - Treasury 1": 269,
+ "Elf Castle - Treasury 2": 270,
+ "Elf Castle - Treasury 3": 271,
+ "Elf Castle - Treasury 4": 272,
+ "Northwest Castle - Treasury 1": 273,
+ "Northwest Castle - Treasury 2": 275,
+ "Northwest Castle - Treasury 3": 274,
+ "Titan's Tunnel - Chest 1": 327,
+ "Titan's Tunnel - Chest 2": 328,
+ "Titan's Tunnel - Chest 3": 329,
+ "Titan's Tunnel - Major": 326,
+ "Cardia Grass Island - Entrance": 398,
+ "Cardia Grass Island - Duo Room 1": 396,
+ "Cardia Grass Island - Duo Rooom 2": 397,
+ "Cardia Swamp Island - Chest 1": 393,
+ "Cardia Swamp Island - Chest 2": 395,
+ "Cardia Swamp Island - Chest 3": 394,
+ "Cardia Forest Island - Entrance 1": 389,
+ "Cardia Forest Island - Entrance 2": 388,
+ "Cardia Forest Island - Entrance 3": 390,
+ "Cardia Forest Island - Incentive 1": 400,
+ "Cardia Forest Island - Incentive 2": 399,
+ "Cardia Forest Island - Incentive 3": 392,
+ "Cardia Forest Island - Incentive Major": 391,
+ "Temple of Fiends - Unlocked Single": 265,
+ "Temple of Fiends - Unlocked Duo 1": 263,
+ "Temple of Fiends - Unlocked Duo 2": 264,
+ "Temple of Fiends - Locked Single": 266,
+ "Temple of Fiends - Locked Duo 1": 267,
+ "Temple of Fiends - Locked Duo 2": 268,
+ "Marsh Cave Top (B1) - Single": 283,
+ "Marsh Cave Top (B1) - Corner": 282,
+ "Marsh Cave Top (B1) - Duo 1": 281,
+ "Marsh Cave Top (B1) - Duo 2": 280,
+ "Marsh Cave Bottom (B2) - Distant": 276,
+ "Marsh Cave Bottom (B2) - Tetris-Z First": 277,
+ "Marsh Cave Bottom (B2) - Tetris-Z Middle 1": 278,
+ "Marsh Cave Bottom (B2) - Tetris-Z Middle 2": 285,
+ "Marsh Cave Bottom (B2) - Tetris-Z Incentive": 284,
+ "Marsh Cave Bottom (B2) - Tetris-Z Last": 279,
+ "Marsh Cave Bottom (B2) - Locked Corner": 286,
+ "Marsh Cave Bottom (B2) - Locked Middle": 287,
+ "Marsh Cave Bottom (B2) - Locked Incentive": 288,
+ "Earth Cave Giant's Floor (B1) - Single": 306,
+ "Earth Cave Giant's Floor (B1) - Appendix 1": 302,
+ "Earth Cave Giant's Floor (B1) - Appendix 2": 303,
+ "Earth Cave Giant's Floor (B1) - Side Path 1": 304,
+ "Earth Cave Giant's Floor (B1) - Side Path 2": 305,
+ "Earth Cave (B2) - Side Room 1": 307,
+ "Earth Cave (B2) - Side Room 2": 308,
+ "Earth Cave (B2) - Side Room 3": 309,
+ "Earth Cave (B2) - Guarded 1": 310,
+ "Earth Cave (B2) - Guarded 2": 311,
+ "Earth Cave (B2) - Guarded 3": 312,
+ "Earth Cave Vampire Floor (B3) - Side Room": 315,
+ "Earth Cave Vampire Floor (B3) - TFC": 316,
+ "Earth Cave Vampire Floor (B3) - Asher Trunk": 314,
+ "Earth Cave Vampire Floor (B3) - Vampire's Closet": 313,
+ "Earth Cave Vampire Floor (B3) - Incentive": 317,
+ "Earth Cave Rod Locked Floor (B4) - Armory 1": 321,
+ "Earth Cave Rod Locked Floor (B4) - Armory 2": 322,
+ "Earth Cave Rod Locked Floor (B4) - Armory 3": 325,
+ "Earth Cave Rod Locked Floor (B4) - Armory 4": 323,
+ "Earth Cave Rod Locked Floor (B4) - Armory 5": 324,
+ "Earth Cave Rod Locked Floor (B4) - Lich's Closet 1": 318,
+ "Earth Cave Rod Locked Floor (B4) - Lich's Closet 2": 319,
+ "Earth Cave Rod Locked Floor (B4) - Lich's Closet 3": 320,
+ "Gurgu Volcano Armory Floor (B2) - Guarded": 346,
+ "Gurgu Volcano Armory Floor (B2) - Center": 347,
+ "Gurgu Volcano Armory Floor (B2) - Hairpins": 344,
+ "Gurgu Volcano Armory Floor (B2) - Shortpins": 345,
+ "Gurgu Volcano Armory Floor (B2) - Vertpins 1": 342,
+ "Gurgu Volcano Armory Floor (B2) - Vertpins 2": 343,
+ "Gurgu Volcano Armory Floor (B2) - Armory 1": 338,
+ "Gurgu Volcano Armory Floor (B2) - Armory 2": 330,
+ "Gurgu Volcano Armory Floor (B2) - Armory 3": 331,
+ "Gurgu Volcano Armory Floor (B2) - Armory 4": 337,
+ "Gurgu Volcano Armory Floor (B2) - Armory 5": 335,
+ "Gurgu Volcano Armory Floor (B2) - Armory 6": 332,
+ "Gurgu Volcano Armory Floor (B2) - Armory 7": 333,
+ "Gurgu Volcano Armory Floor (B2) - Armory 8": 334,
+ "Gurgu Volcano Armory Floor (B2) - Armory 9": 341,
+ "Gurgu Volcano Armory Floor (B2) - Armory 10": 336,
+ "Gurgu Volcano Armory Floor (B2) - Armory 11": 340,
+ "Gurgu Volcano Armory Floor (B2) - Armory 12": 339,
+ "Gurgu Volcano Agama Floor (B4) - Entrance 1": 349,
+ "Gurgu Volcano Agama Floor (B4) - Entrance 2": 348,
+ "Gurgu Volcano Agama Floor (B4) - First Greed": 350,
+ "Gurgu Volcano Agama Floor (B4) - Worm Room 1": 361,
+ "Gurgu Volcano Agama Floor (B4) - Worm Room 2": 359,
+ "Gurgu Volcano Agama Floor (B4) - Worm Room 3": 360,
+ "Gurgu Volcano Agama Floor (B4) - Worm Room 4": 357,
+ "Gurgu Volcano Agama Floor (B4) - Worm Room 5": 358,
+ "Gurgu Volcano Agama Floor (B4) - Second Greed 1": 353,
+ "Gurgu Volcano Agama Floor (B4) - Second Greed 2": 354,
+ "Gurgu Volcano Agama Floor (B4) - Side Room 1": 355,
+ "Gurgu Volcano Agama Floor (B4) - Side Room 2": 356,
+ "Gurgu Volcano Agama Floor (B4) - Grind Room 1": 351,
+ "Gurgu Volcano Agama Floor (B4) - Grind Room 2": 352,
+ "Gurgu Volcano Kary Floor (B5) - Incentive": 362,
+ "Ice Cave Incentive Floor (B2) - Chest 1": 368,
+ "Ice Cave Incentive Floor (B2) - Chest 2": 369,
+ "Ice Cave Incentive Floor (B2) - Major": 370,
+ "Ice Cave Bottom (B3) - IceD Room 1": 377,
+ "Ice Cave Bottom (B3) - IceD Room 2": 378,
+ "Ice Cave Bottom (B3) - Six-Pack 1": 371,
+ "Ice Cave Bottom (B3) - Six-Pack 2": 372,
+ "Ice Cave Bottom (B3) - Six-Pack 3": 375,
+ "Ice Cave Bottom (B3) - Six-Pack 4": 373,
+ "Ice Cave Bottom (B3) - Six-Pack 5": 374,
+ "Ice Cave Bottom (B3) - Six-Pack 6": 376,
+ "Ice Cave Exit Floor (B1) - Greeds Checks 1": 363,
+ "Ice Cave Exit Floor (B1) - Greeds Checks 2": 364,
+ "Ice Cave Exit Floor (B1) - Drop Room 1": 365,
+ "Ice Cave Exit Floor (B1) - Drop Room 2": 366,
+ "Ice Cave Exit Floor (B1) - Drop Room 3": 367,
+ "Castle of Ordeals Top Floor (3F) - Single": 386,
+ "Castle of Ordeals Top Floor (3F) - Three-Pack 1": 383,
+ "Castle of Ordeals Top Floor (3F) - Three-Pack 2": 384,
+ "Castle of Ordeals Top Floor (3F) - Three-Pack 3": 385,
+ "Castle of Ordeals Top Floor (3F) - Four-Pack 1": 379,
+ "Castle of Ordeals Top Floor (3F) - Four-Pack 2": 380,
+ "Castle of Ordeals Top Floor (3F) - Four-Pack 3": 381,
+ "Castle of Ordeals Top Floor (3F) - Four-Pack 4": 382,
+ "Castle of Ordeals Top Floor (3F) - Incentive": 387,
+ "Sea Shrine Split Floor (B3) - Kraken Side": 415,
+ "Sea Shrine Split Floor (B3) - Mermaid Side": 416,
+ "Sea Shrine TFC Floor (B2) - TFC": 421,
+ "Sea Shrine TFC Floor (B2) - TFC North": 420,
+ "Sea Shrine TFC Floor (B2) - Side Corner": 419,
+ "Sea Shrine TFC Floor (B2) - First Greed": 422,
+ "Sea Shrine TFC Floor (B2) - Second Greed": 423,
+ "Sea Shrine Mermaids (B1) - Passby": 427,
+ "Sea Shrine Mermaids (B1) - Bubbles 1": 428,
+ "Sea Shrine Mermaids (B1) - Bubbles 2": 429,
+ "Sea Shrine Mermaids (B1) - Incentive 1": 434,
+ "Sea Shrine Mermaids (B1) - Incentive 2": 435,
+ "Sea Shrine Mermaids (B1) - Incentive Major": 436,
+ "Sea Shrine Mermaids (B1) - Entrance 1": 424,
+ "Sea Shrine Mermaids (B1) - Entrance 2": 425,
+ "Sea Shrine Mermaids (B1) - Entrance 3": 426,
+ "Sea Shrine Mermaids (B1) - Four-Corner First": 430,
+ "Sea Shrine Mermaids (B1) - Four-Corner Second": 431,
+ "Sea Shrine Mermaids (B1) - Four-Corner Third": 432,
+ "Sea Shrine Mermaids (B1) - Four-Corner Fourth": 433,
+ "Sea Shrine Greed Floor (B3) - Chest 1": 418,
+ "Sea Shrine Greed Floor (B3) - Chest 2": 417,
+ "Sea Shrine Sharknado Floor (B4) - Dengbait 1": 409,
+ "Sea Shrine Sharknado Floor (B4) - Dengbait 2": 410,
+ "Sea Shrine Sharknado Floor (B4) - Side Corner 1": 411,
+ "Sea Shrine Sharknado Floor (B4) - Side Corner 2": 412,
+ "Sea Shrine Sharknado Floor (B4) - Side Corner 3": 413,
+ "Sea Shrine Sharknado Floor (B4) - Exit": 414,
+ "Sea Shrine Sharknado Floor (B4) - Greed Room 1": 405,
+ "Sea Shrine Sharknado Floor (B4) - Greed Room 2": 406,
+ "Sea Shrine Sharknado Floor (B4) - Greed Room 3": 407,
+ "Sea Shrine Sharknado Floor (B4) - Greed Room 4": 408,
+ "Waterfall Cave - Chest 1": 437,
+ "Waterfall Cave - Chest 2": 438,
+ "Waterfall Cave - Chest 3": 439,
+ "Waterfall Cave - Chest 4": 440,
+ "Waterfall Cave - Chest 5": 441,
+ "Waterfall Cave - Chest 6": 442,
+ "Mirage Tower (1F) - Chest 1": 456,
+ "Mirage Tower (1F) - Chest 2": 452,
+ "Mirage Tower (1F) - Chest 3": 453,
+ "Mirage Tower (1F) - Chest 4": 455,
+ "Mirage Tower (1F) - Chest 5": 454,
+ "Mirage Tower (1F) - Chest 6": 459,
+ "Mirage Tower (1F) - Chest 7": 457,
+ "Mirage Tower (1F) - Chest 8": 458,
+ "Mirage Tower (2F) - Lesser 1": 469,
+ "Mirage Tower (2F) - Lesser 2": 468,
+ "Mirage Tower (2F) - Lesser 3": 467,
+ "Mirage Tower (2F) - Lesser 4": 466,
+ "Mirage Tower (2F) - Lesser 5": 465,
+ "Mirage Tower (2F) - Greater 1": 460,
+ "Mirage Tower (2F) - Greater 2": 461,
+ "Mirage Tower (2F) - Greater 3": 462,
+ "Mirage Tower (2F) - Greater 4": 463,
+ "Mirage Tower (2F) - Greater 5": 464,
+ "Sky Fortress Plus (1F) - Solo": 479,
+ "Sky Fortress Plus (1F) - Five-Pack 1": 474,
+ "Sky Fortress Plus (1F) - Five-Pack 2": 475,
+ "Sky Fortress Plus (1F) - Five-Pack 3": 476,
+ "Sky Fortress Plus (1F) - Five-Pack 4": 477,
+ "Sky Fortress Plus (1F) - Five-Pack 5": 478,
+ "Sky Fortress Plus (1F) - Four-Pack 1": 470,
+ "Sky Fortress Plus (1F) - Four-Pack 2": 471,
+ "Sky Fortress Plus (1F) - Four-Pack 3": 472,
+ "Sky Fortress Plus (1F) - Four-Pack 4": 473,
+ "Sky Fortress Spider (2F) - Cheap Room 1": 485,
+ "Sky Fortress Spider (2F) - Cheap Room 2": 486,
+ "Sky Fortress Spider (2F) - Vault 1": 487,
+ "Sky Fortress Spider (2F) - Vault 2": 488,
+ "Sky Fortress Spider (2F) - Incentive": 489,
+ "Sky Fortress Spider (2F) - Gauntlet Room": 483,
+ "Sky Fortress Spider (2F) - Ribbon Room 1": 482,
+ "Sky Fortress Spider (2F) - Ribbon Room 2": 484,
+ "Sky Fortress Spider (2F) - Wardrobe 1": 480,
+ "Sky Fortress Spider (2F) - Wardrobe 2": 481,
+ "Sky Fortress Provides (3F) - Six-Pack 1": 498,
+ "Sky Fortress Provides (3F) - Six-Pack 2": 495,
+ "Sky Fortress Provides (3F) - Six-Pack 3": 494,
+ "Sky Fortress Provides (3F) - Six-Pack 4": 497,
+ "Sky Fortress Provides (3F) - Six-Pack 5": 496,
+ "Sky Fortress Provides (3F) - Six-Pack 6": 499,
+ "Sky Fortress Provides (3F) - CC's Gambit 1": 500,
+ "Sky Fortress Provides (3F) - CC's Gambit 2": 501,
+ "Sky Fortress Provides (3F) - CC's Gambit 3": 502,
+ "Sky Fortress Provides (3F) - CC's Gambit 4": 503,
+ "Sky Fortress Provides (3F) - Greed 1": 491,
+ "Sky Fortress Provides (3F) - Greed 2": 490,
+ "Sky Fortress Provides (3F) - Greed 3": 492,
+ "Sky Fortress Provides (3F) - Greed 4": 493,
+ "Temple of Fiends Revisited (3F) - Validation 1": 509,
+ "Temple of Fiends Revisited (3F) - Validation 2": 510,
+ "Temple of Fiends Revisited Kary Floor (6F) - Greed Checks 1": 507,
+ "Temple of Fiends Revisited Kary Floor (6F) - Greed Checks 2": 508,
+ "Temple of Fiends Revisited Kary Floor (6F) - Katana Chest": 506,
+ "Temple of Fiends Revisited Kary Floor (6F) - Vault": 505,
+ "Temple of Fiends Revisited Tiamat Floor (8F) - Masamune Chest": 504,
+ "Shop Item": 767,
"King": 513,
- "Princess2": 530,
+ "Princess": 530,
"Matoya": 522,
"Astos": 519,
"Bikke": 516,
- "CanoeSage": 533,
- "ElfPrince": 518,
+ "Canoe Sage": 533,
+ "Elf Prince": 518,
"Nerrick": 520,
"Smith": 521,
"CubeBot": 529,
diff --git a/worlds/ffmq/Output.py b/worlds/ffmq/Output.py
index 98ecd28986df..9daee9f643a9 100644
--- a/worlds/ffmq/Output.py
+++ b/worlds/ffmq/Output.py
@@ -3,7 +3,7 @@
import zipfile
from copy import deepcopy
from .Regions import object_id_table
-from Main import __version__
+from Utils import __version__
from worlds.Files import APContainer
import pkgutil
diff --git a/worlds/ffmq/Regions.py b/worlds/ffmq/Regions.py
index 61f70864c0b4..8b83c88e72c9 100644
--- a/worlds/ffmq/Regions.py
+++ b/worlds/ffmq/Regions.py
@@ -220,15 +220,12 @@ def stage_set_rules(multiworld):
for player in no_enemies_players:
for location in vendor_locations:
if multiworld.accessibility[player] == "locations":
- print("exclude")
multiworld.get_location(location, player).progress_type = LocationProgressType.EXCLUDED
else:
- print("unreachable")
multiworld.get_location(location, player).access_rule = lambda state: False
else:
# There are not enough junk items to fill non-minimal players' vendors. Just set an item rule not allowing
- # advancement items so that useful items can be placed.
- print("no advancement")
+ # advancement items so that useful items can be placed
for player in no_enemies_players:
for location in vendor_locations:
multiworld.get_location(location, player).item_rule = lambda item: not item.advancement
diff --git a/worlds/generic/docs/plando_en.md b/worlds/generic/docs/plando_en.md
index 2d40f45195ba..9d8e6befe87f 100644
--- a/worlds/generic/docs/plando_en.md
+++ b/worlds/generic/docs/plando_en.md
@@ -171,16 +171,16 @@ relevant guide: [A Link to the Past Plando Guide](/tutorial/A%20Link%20to%20the%
## Connections Plando
-This is currently only supported by Minecraft and A Link to the Past. As the way that these games interact with their
-connections is different, I will only explain the basics here, while more specifics for A Link to the Past connection
-plando can be found in its plando guide.
+This is currently only supported by a few games, including A Link to the Past, Minecraft, and Ocarina of Time. As the way that these games interact with their
+connections is different, only the basics are explained here. More specific information for connection plando in A Link to the Past can be found in
+its [plando guide](/tutorial/A%20Link%20to%20the%20Past/plando/en#connections).
* The options for connections are `percentage`, `entrance`, `exit`, and `direction`. Each of these options supports
subweights.
* `percentage` is the percentage chance for this connection from 0 to 100 and defaults to 100.
* Every connection has an `entrance` and an `exit`. These can be unlinked like in A Link to the Past insanity entrance
shuffle.
-* `direction` can be `both`, `entrance`, or `exit` and determines in which direction this connection will operate.
+* `direction` can be `both`, `entrance`, or `exit` and determines in which direction this connection will operate. `direction` defaults to `both`.
[A Link to the Past connections](https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/alttp/EntranceShuffle.py#L3852)
diff --git a/worlds/kh2/Locations.py b/worlds/kh2/Locations.py
index 61fafe909412..100e971e7dff 100644
--- a/worlds/kh2/Locations.py
+++ b/worlds/kh2/Locations.py
@@ -1356,5 +1356,5 @@
location_groups: typing.Dict[str, list]
location_groups = {
Region_Name: [loc for loc in Region_Locs if "Event" not in loc]
- for Region_Name, Region_Locs in KH2REGIONS.items() if Region_Locs
+ for Region_Name, Region_Locs in KH2REGIONS.items() if Region_Locs and "Event" not in Region_Locs[0]
}
diff --git a/worlds/kh2/Logic.py b/worlds/kh2/Logic.py
index 1f13aa5f029c..1e6c403106d1 100644
--- a/worlds/kh2/Logic.py
+++ b/worlds/kh2/Logic.py
@@ -606,11 +606,11 @@
ItemName.LimitForm: 1,
}
final_leveling_access = {
- LocationName.MemorysSkyscaperMythrilCrystal,
+ LocationName.RoxasEventLocation,
LocationName.GrimReaper2,
LocationName.Xaldin,
LocationName.StormRider,
- LocationName.SunsetTerraceAbilityRing
+ LocationName.UndergroundConcourseMythrilGem
}
multi_form_region_access = {
diff --git a/worlds/ladx/LADXR/utils.py b/worlds/ladx/LADXR/utils.py
index fcf1d2bb56e7..5f8b2685550d 100644
--- a/worlds/ladx/LADXR/utils.py
+++ b/worlds/ladx/LADXR/utils.py
@@ -146,7 +146,7 @@ def setReplacementName(key: str, value: str) -> None:
def formatText(instr: str, *, center: bool = False, ask: Optional[str] = None) -> bytes:
instr = instr.format(**_NAMES)
- s = instr.encode("ascii")
+ s = instr.encode("ascii", errors="replace")
s = s.replace(b"'", b"^")
def padLine(line: bytes) -> bytes:
@@ -169,7 +169,7 @@ def padLine(line: bytes) -> bytes:
if result_line:
result += padLine(result_line)
if ask is not None:
- askbytes = ask.encode("ascii")
+ askbytes = ask.encode("ascii", errors="replace")
result = result.rstrip()
while len(result) % 32 != 16:
result += b' '
diff --git a/worlds/ladx/Options.py b/worlds/ladx/Options.py
index 691891c0b350..117242208be2 100644
--- a/worlds/ladx/Options.py
+++ b/worlds/ladx/Options.py
@@ -399,6 +399,26 @@ class Palette(Choice):
option_pink = 4
option_inverted = 5
+class Music(Choice, LADXROption):
+ """
+ [Vanilla] Regular Music
+ [Shuffled] Shuffled Music
+ [Off] No music
+ """
+ ladxr_name = "music"
+ option_vanilla = 0
+ option_shuffled = 1
+ option_off = 2
+
+
+ def to_ladxr_option(self, all_options):
+ s = ""
+ if self.value == self.option_shuffled:
+ s = "random"
+ elif self.value == self.option_off:
+ s = "off"
+ return self.ladxr_name, s
+
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.
@@ -444,6 +464,7 @@ class AdditionalWarpPoints(DefaultOffToggle):
'shuffle_maps': ShuffleMaps,
'shuffle_compasses': ShuffleCompasses,
'shuffle_stone_beaks': ShuffleStoneBeaks,
+ 'music': Music,
'music_change_condition': MusicChangeCondition,
'nag_messages': NagMessages,
'ap_title_screen': APTitleScreen,
diff --git a/worlds/ladx/__init__.py b/worlds/ladx/__init__.py
index 181cc053222d..6742dffd30c3 100644
--- a/worlds/ladx/__init__.py
+++ b/worlds/ladx/__init__.py
@@ -276,6 +276,11 @@ def create_items(self) -> None:
# Properly fill locations within dungeon
location.dungeon = r.dungeon_index
+ # For now, special case first item
+ FORCE_START_ITEM = True
+ if FORCE_START_ITEM:
+ self.force_start_item()
+
def force_start_item(self):
start_loc = self.multiworld.get_location("Tarin's Gift (Mabe Village)", self.player)
if not start_loc.item:
@@ -287,17 +292,12 @@ def force_start_item(self):
start_item = self.multiworld.itempool.pop(index)
start_loc.place_locked_item(start_item)
-
def get_pre_fill_items(self):
return self.pre_fill_items
def pre_fill(self) -> None:
allowed_locations_by_item = {}
- # For now, special case first item
- FORCE_START_ITEM = True
- if FORCE_START_ITEM:
- self.force_start_item()
# Set up filter rules
diff --git a/worlds/lingo/__init__.py b/worlds/lingo/__init__.py
index 088967445007..2f9354193250 100644
--- a/worlds/lingo/__init__.py
+++ b/worlds/lingo/__init__.py
@@ -82,9 +82,8 @@ def create_items(self):
skips = int(non_traps * skip_percentage / 100.0)
non_skips = non_traps - skips
- filler_list = [":)", "The Feeling of Being Lost", "Wanderlust", "Empty White Hallways"]
for i in range(0, non_skips):
- pool.append(self.create_item(filler_list[i % len(filler_list)]))
+ pool.append(self.create_item(self.get_filler_item_name()))
for i in range(0, skips):
pool.append(self.create_item("Puzzle Skip"))
@@ -130,3 +129,7 @@ def fill_slot_data(self):
slot_data["painting_entrance_to_exit"] = self.player_logic.painting_mapping
return slot_data
+
+ def get_filler_item_name(self) -> str:
+ filler_list = [":)", "The Feeling of Being Lost", "Wanderlust", "Empty White Hallways"]
+ return self.random.choice(filler_list)
diff --git a/worlds/lingo/data/LL1.yaml b/worlds/lingo/data/LL1.yaml
index 32a7659b826e..f72e63c1427e 100644
--- a/worlds/lingo/data/LL1.yaml
+++ b/worlds/lingo/data/LL1.yaml
@@ -112,6 +112,7 @@
HI:
id: Entry Room/Panel_hi_hi
tag: midwhite
+ check: True
HIDDEN:
id: Entry Room/Panel_hidden_hidden
tag: midwhite
@@ -278,10 +279,10 @@
tag: forbid
check: True
achievement: The Seeker
- BEAR:
+ BEAR (1):
id: Heteronym Room/Panel_bear_bear
tag: midwhite
- MINE:
+ MINE (1):
id: Heteronym Room/Panel_mine_mine
tag: double midwhite
subtag: left
@@ -297,7 +298,7 @@
DOES:
id: Heteronym Room/Panel_does_does
tag: midwhite
- MOBILE:
+ MOBILE (1):
id: Heteronym Room/Panel_mobile_mobile
tag: double midwhite
subtag: left
@@ -399,8 +400,7 @@
door: Crossroads Entrance
The Tenacious:
door: Tenacious Entrance
- Warts Straw Area:
- door: Symmetry Door
+ Near Far Area: True
Hedge Maze:
door: Shortcut to Hedge Maze
Orange Tower First Floor:
@@ -427,14 +427,6 @@
id: Palindrome Room/Panel_slaughter_laughter
colors: red
tag: midred
- NEAR:
- id: Symmetry Room/Panel_near_far
- colors: black
- tag: botblack
- FAR:
- id: Symmetry Room/Panel_far_near
- colors: black
- tag: botblack
TRACE:
id: Maze Room/Panel_trace_trace
tag: midwhite
@@ -477,14 +469,6 @@
group: Entrances to The Tenacious
panels:
- SLAUGHTER
- Symmetry Door:
- id:
- - Symmetry Room Area Doors/Door_near_far
- - Symmetry Room Area Doors/Door_far_near
- group: Symmetry Doors
- panels:
- - NEAR
- - FAR
Shortcut to Hedge Maze:
id: Maze Area Doors/Door_trace_trace
group: Hedge Maze Doors
@@ -548,7 +532,7 @@
id: Lingo Room/Panel_shortcut
colors: yellow
tag: midyellow
- PILGRIMAGE:
+ PILGRIM:
id: Lingo Room/Panel_pilgrim
colors: blue
tag: midblue
@@ -569,7 +553,7 @@
Exit:
event: True
panels:
- - PILGRIMAGE
+ - PILGRIM
Pilgrim Room:
entrances:
The Seeker:
@@ -755,7 +739,7 @@
panels:
- TURN
- room: Orange Tower Fourth Floor
- panel: RUNT
+ panel: RUNT (1)
Words Sword Door:
id:
- Shuffle Room Area Doors/Door_words_shuffle_3
@@ -962,11 +946,36 @@
- LEVEL (White)
- RACECAR (White)
- SOLOS (White)
+ Near Far Area:
+ entrances:
+ Hub Room: True
+ Warts Straw Area:
+ door: Door
+ panels:
+ NEAR:
+ id: Symmetry Room/Panel_near_far
+ colors: black
+ tag: botblack
+ FAR:
+ id: Symmetry Room/Panel_far_near
+ colors: black
+ tag: botblack
+ doors:
+ Door:
+ id:
+ - Symmetry Room Area Doors/Door_near_far
+ - Symmetry Room Area Doors/Door_far_near
+ group: Symmetry Doors
+ item_name: Symmetry Room - Near Far Door
+ location_name: Symmetry Room - NEAR, FAR
+ panels:
+ - NEAR
+ - FAR
Warts Straw Area:
entrances:
- Hub Room:
- room: Hub Room
- door: Symmetry Door
+ Near Far Area:
+ room: Near Far Area
+ door: Door
Leaf Feel Area:
door: Door
panels:
@@ -984,6 +993,8 @@
- Symmetry Room Area Doors/Door_warts_straw
- Symmetry Room Area Doors/Door_straw_warts
group: Symmetry Doors
+ item_name: Symmetry Room - Warts Straw Door
+ location_name: Symmetry Room - WARTS, STRAW
panels:
- WARTS
- STRAW
@@ -1009,6 +1020,8 @@
- Symmetry Room Area Doors/Door_leaf_feel
- Symmetry Room Area Doors/Door_feel_leaf
group: Symmetry Doors
+ item_name: Symmetry Room - Leaf Feel Door
+ location_name: Symmetry Room - LEAF, FEEL
panels:
- LEAF
- FEEL
@@ -1118,7 +1131,13 @@
id: Cross Room/Panel_north_missing
colors: green
tag: forbid
- required_room: Outside The Bold
+ required_panel:
+ - room: Outside The Bold
+ panel: SOUND
+ - room: Outside The Bold
+ panel: YEAST
+ - room: Outside The Bold
+ panel: WET
DIAMONDS:
id: Cross Room/Panel_diamonds_missing
colors: green
@@ -1160,7 +1179,7 @@
group: Color Hunt Barriers
skip_location: True
panels:
- - room: Champion's Rest
+ - room: Color Hunt
panel: PURPLE
Hallway Door:
id: Red Blue Purple Room Area Doors/Door_room_2
@@ -1430,7 +1449,7 @@
entrances:
The Perceptive: True
panels:
- NAPS:
+ SPAN:
id: Naps Room/Panel_naps_span
colors: black
tag: midblack
@@ -1456,7 +1475,7 @@
location_name: The Fearless - First Floor Puzzles
group: Fearless Doors
panels:
- - NAPS
+ - SPAN
- TEAM
- TEEM
- IMPATIENT
@@ -1558,11 +1577,11 @@
required_door:
door: Stairs
achievement: The Observant
- BACK:
+ FOUR (1):
id: Look Room/Panel_four_back
colors: green
tag: forbid
- SIDE:
+ FOUR (2):
id: Look Room/Panel_four_side
colors: green
tag: forbid
@@ -1572,87 +1591,87 @@
hunt: True
required_door:
door: Backside Door
- STAIRS:
+ SIX:
id: Look Room/Panel_six_stairs
colors: green
tag: forbid
- WAYS:
+ FOUR (3):
id: Look Room/Panel_four_ways
colors: green
tag: forbid
- "ON":
+ TWO (1):
id: Look Room/Panel_two_on
colors: green
tag: forbid
- UP:
+ TWO (2):
id: Look Room/Panel_two_up
colors: green
tag: forbid
- SWIMS:
+ FIVE:
id: Look Room/Panel_five_swims
colors: green
tag: forbid
- UPSTAIRS:
+ BELOW (1):
id: Look Room/Panel_eight_upstairs
colors: green
tag: forbid
required_door:
door: Stairs
- TOIL:
+ BLUE:
id: Look Room/Panel_blue_toil
colors: green
tag: forbid
required_door:
door: Stairs
- STOP:
+ BELOW (2):
id: Look Room/Panel_four_stop
colors: green
tag: forbid
required_door:
door: Stairs
- TOP:
+ MINT (1):
id: Look Room/Panel_aqua_top
colors: green
tag: forbid
required_door:
door: Stairs
- HI:
+ ESACREWOL:
id: Look Room/Panel_blue_hi
colors: green
tag: forbid
required_door:
door: Stairs
- HI (2):
+ EULB:
id: Look Room/Panel_blue_hi2
colors: green
tag: forbid
required_door:
door: Stairs
- "31":
+ NUMBERS (1):
id: Look Room/Panel_numbers_31
colors: green
tag: forbid
required_door:
door: Stairs
- "52":
+ NUMBERS (2):
id: Look Room/Panel_numbers_52
colors: green
tag: forbid
required_door:
door: Stairs
- OIL:
+ MINT (2):
id: Look Room/Panel_aqua_oil
colors: green
tag: forbid
required_door:
door: Stairs
- BACKSIDE (GREEN):
+ GREEN (1):
id: Look Room/Panel_eight_backside
colors: green
tag: forbid
required_door:
door: Stairs
- SIDEWAYS:
+ GREEN (2):
id: Look Room/Panel_eight_sideways
colors: green
tag: forbid
@@ -1663,13 +1682,13 @@
id: Maze Area Doors/Door_backside
group: Backside Doors
panels:
- - BACK
- - SIDE
+ - FOUR (1)
+ - FOUR (2)
Stairs:
id: Maze Area Doors/Door_stairs
group: Observant Doors
panels:
- - STAIRS
+ - SIX
The Incomparable:
entrances:
The Observant: True # Assuming that access to The Observant includes access to the right entrance
@@ -1951,7 +1970,7 @@
group: Color Hunt Barriers
skip_location: True
panels:
- - room: Champion's Rest
+ - room: Color Hunt
panel: RED
Rhyme Room Entrance:
id: Double Room Area Doors/Door_room_entry_stairs2
@@ -1969,9 +1988,9 @@
- Color Arrow Room Doors/Door_orange_hider_1
- Color Arrow Room Doors/Door_orange_hider_2
- Color Arrow Room Doors/Door_orange_hider_3
- location_name: Color Hunt - RED and YELLOW
- group: Champion's Rest - Color Barriers
- item_name: Champion's Rest - Orange Barrier
+ location_name: Color Barriers - RED and YELLOW
+ group: Color Hunt Barriers
+ item_name: Color Hunt - Orange Barrier
panels:
- RED
- room: Directional Gallery
@@ -1999,7 +2018,7 @@
Courtyard: True
Roof: True # through the sunwarp
panels:
- RUNT:
+ RUNT (1):
id: Shuffle Room/Panel_turn_runt2
colors: yellow
tag: midyellow
@@ -2213,6 +2232,7 @@
- Master Room Doors/Door_master_down
- Master Room Doors/Door_master_down2
skip_location: True
+ item_name: Mastery
panels:
- THE MASTER
Mastery Panels:
@@ -2229,25 +2249,25 @@
panel: MASTERY
- room: Hedge Maze
panel: MASTERY (1)
- - room: Roof
- panel: MASTERY (1)
- - room: Roof
- panel: MASTERY (2)
+ - room: Behind A Smile
+ panel: MASTERY
+ - room: Sixteen Colorful Squares
+ panel: MASTERY
- MASTERY
- room: Hedge Maze
panel: MASTERY (2)
- - room: Roof
- panel: MASTERY (3)
- - room: Roof
- panel: MASTERY (4)
- - room: Roof
- panel: MASTERY (5)
+ - room: Among Treetops
+ panel: MASTERY
+ - room: Horizon's Edge
+ panel: MASTERY
+ - room: Beneath The Lookout
+ panel: MASTERY
- room: Elements Area
panel: MASTERY
- room: Pilgrim Antechamber
panel: MASTERY
- - room: Roof
- panel: MASTERY (6)
+ - room: Rooftop Staircase
+ panel: MASTERY
paintings:
- id: map_painting2
orientation: north
@@ -2259,52 +2279,75 @@
Crossroads:
room: Crossroads
door: Roof Access
+ Behind A Smile:
+ entrances:
+ Roof: True
panels:
- MASTERY (1):
+ MASTERY:
id: Master Room/Panel_mastery_mastery6
tag: midwhite
hunt: True
required_door:
room: Orange Tower Seventh Floor
door: Mastery
- MASTERY (2):
+ STAIRCASE:
+ id: Open Areas/Panel_staircase
+ tag: midwhite
+ Sixteen Colorful Squares:
+ entrances:
+ Roof: True
+ panels:
+ MASTERY:
id: Master Room/Panel_mastery_mastery7
tag: midwhite
hunt: True
required_door:
room: Orange Tower Seventh Floor
door: Mastery
- MASTERY (3):
+ Among Treetops:
+ entrances:
+ Roof: True
+ panels:
+ MASTERY:
id: Master Room/Panel_mastery_mastery10
tag: midwhite
hunt: True
required_door:
room: Orange Tower Seventh Floor
door: Mastery
- MASTERY (4):
+ Horizon's Edge:
+ entrances:
+ Roof: True
+ panels:
+ MASTERY:
id: Master Room/Panel_mastery_mastery11
tag: midwhite
hunt: True
required_door:
room: Orange Tower Seventh Floor
door: Mastery
- MASTERY (5):
+ Beneath The Lookout:
+ entrances:
+ Roof: True
+ panels:
+ MASTERY:
id: Master Room/Panel_mastery_mastery12
tag: midwhite
hunt: True
required_door:
room: Orange Tower Seventh Floor
door: Mastery
- MASTERY (6):
+ Rooftop Staircase:
+ entrances:
+ Roof: True
+ panels:
+ MASTERY:
id: Master Room/Panel_mastery_mastery15
tag: midwhite
hunt: True
required_door:
room: Orange Tower Seventh Floor
door: Mastery
- STAIRCASE:
- id: Open Areas/Panel_staircase
- tag: midwhite
Orange Tower Basement:
entrances:
Orange Tower Sixth Floor:
@@ -2376,7 +2419,7 @@
group: Color Hunt Barriers
skip_location: True
panels:
- - room: Champion's Rest
+ - room: Color Hunt
panel: GREEN
paintings:
- id: flower_painting_7
@@ -2626,9 +2669,7 @@
tag: forbid
doors:
Progress Door:
- id:
- - Doorway Room Doors/Door_gray
- - Doorway Room Doors/Door_gray2 # See comment below
+ id: Doorway Room Doors/Door_gray
item_name: The Colorful - Gray Door
location_name: The Colorful - Gray
group: Colorful Doors
@@ -2778,6 +2819,7 @@
door: Exit
Eight Alcove:
door: Eight Door
+ The Optimistic: True
panels:
SEVEN (1):
id: Backside Room/Panel_seven_seven_5
@@ -2827,21 +2869,11 @@
id: Rhyme Room/Panel_locked_knocked
colors: purple
tag: midpurp
- BACKSIDE:
- id: Backside Room/Panel_backside_1
- tag: midwhite
- The Optimistic:
- id: Countdown Panels/Panel_optimistic_optimistic
- check: True
- tag: forbid
- required_door:
- door: Backsides
- achievement: The Optimistic
- PAST:
+ PAST (1):
id: Shuffle Room/Panel_past_present
colors: brown
tag: botbrown
- FUTURE:
+ FUTURE (1):
id: Shuffle Room/Panel_future_present
colors:
- brown
@@ -2887,14 +2919,14 @@
group: Color Hunt Barriers
skip_location: True
panels:
- - room: Champion's Rest
+ - room: Color Hunt
panel: BLUE
Orange Barrier:
id: Color Arrow Room Doors/Door_orange_3
group: Color Hunt Barriers
skip_location: True
panels:
- - room: Champion's Rest
+ - room: Color Hunt
panel: ORANGE
Initiated Entrance:
id: Red Blue Purple Room Area Doors/Door_locked_knocked
@@ -2906,9 +2938,9 @@
# containing region.
Green Barrier:
id: Color Arrow Room Doors/Door_green_hider_1
- location_name: Color Hunt - BLUE and YELLOW
- item_name: Champion's Rest - Green Barrier
- group: Champion's Rest - Color Barriers
+ location_name: Color Barriers - BLUE and YELLOW
+ item_name: Color Hunt - Green Barrier
+ group: Color Hunt Barriers
panels:
- BLUE
- room: Directional Gallery
@@ -2918,9 +2950,9 @@
- Color Arrow Room Doors/Door_purple_hider_1
- Color Arrow Room Doors/Door_purple_hider_2
- Color Arrow Room Doors/Door_purple_hider_3
- location_name: Color Hunt - RED and BLUE
- item_name: Champion's Rest - Purple Barrier
- group: Champion's Rest - Color Barriers
+ location_name: Color Barriers - RED and BLUE
+ item_name: Color Hunt - Purple Barrier
+ group: Color Hunt Barriers
panels:
- BLUE
- room: Orange Tower Third Floor
@@ -2930,7 +2962,7 @@
- Color Arrow Room Doors/Door_all_hider_1
- Color Arrow Room Doors/Door_all_hider_2
- Color Arrow Room Doors/Door_all_hider_3
- location_name: Color Hunt - GREEN, ORANGE and PURPLE
+ location_name: Color Barriers - GREEN, ORANGE and PURPLE
item_name: Champion's Rest - Entrance
panels:
- ORANGE
@@ -2938,17 +2970,6 @@
panel: GREEN
- room: Outside The Agreeable
panel: PURPLE
- Backsides:
- event: True
- panels:
- - room: The Observant
- panel: BACKSIDE
- - room: Yellow Backside Area
- panel: BACKSIDE
- - room: Directional Gallery
- panel: BACKSIDE
- - room: The Bearer
- panel: BACKSIDE
Eight Door:
id: Red Blue Purple Room Area Doors/Door_a_strands2
skip_location: True
@@ -3058,6 +3079,28 @@
id: Rhyme Room/Panel_bed_dead
colors: purple
tag: toppurp
+ The Optimistic:
+ entrances:
+ Outside The Initiated: True
+ panels:
+ BACKSIDE:
+ id: Backside Room/Panel_backside_1
+ tag: midwhite
+ Achievement:
+ id: Countdown Panels/Panel_optimistic_optimistic
+ check: True
+ tag: forbid
+ required_panel:
+ - panel: BACKSIDE
+ - room: The Observant
+ panel: BACKSIDE
+ - room: Yellow Backside Area
+ panel: BACKSIDE
+ - room: Directional Gallery
+ panel: BACKSIDE
+ - room: The Bearer
+ panel: BACKSIDE
+ achievement: The Optimistic
The Traveled:
entrances:
Hub Room:
@@ -3158,7 +3201,7 @@
Outside The Undeterred: True
Crossroads: True
Hedge Maze: True
- Outside The Initiated: True # backside
+ The Optimistic: True # backside
Directional Gallery: True # backside
Yellow Backside Area: True
The Bearer:
@@ -3170,12 +3213,12 @@
Outside The Bold:
entrances:
Color Hallways: True
- Champion's Rest:
- room: Champion's Rest
+ Color Hunt:
+ room: Color Hunt
door: Shortcut to The Steady
The Bearer:
room: The Bearer
- door: Shortcut to The Bold
+ door: Entrance
Directional Gallery:
# There is a painting warp here from the Directional Gallery, but it
# only appears when the sixes are revealed. It could be its own item if
@@ -3246,7 +3289,7 @@
tag: midwhite
required_door:
door: Stargazer Door
- MOUTH:
+ SOUND:
id: Cross Room/Panel_mouth_south
colors: purple
tag: midpurp
@@ -3590,7 +3633,7 @@
id: Blue Room/Panel_bone_skeleton
colors: blue
tag: botblue
- EYE:
+ EYE (1):
id: Blue Room/Panel_mouth_face
colors: blue
tag: double botblue
@@ -3996,7 +4039,7 @@
group: Color Hunt Barriers
skip_location: True
panels:
- - room: Champion's Rest
+ - room: Color Hunt
panel: YELLOW
paintings:
- id: smile_painting_7
@@ -4014,12 +4057,15 @@
orientation: south
- id: cherry_painting
orientation: east
- Champion's Rest:
+ Color Hunt:
entrances:
Outside The Bold:
door: Shortcut to The Steady
Orange Tower Fourth Floor: True # sunwarp
Roof: True # through ceiling of sunwarp
+ Champion's Rest:
+ room: Outside The Initiated
+ door: Entrance
panels:
EXIT:
id: Rock Room/Panel_red_red
@@ -4060,11 +4106,28 @@
required_door:
room: Orange Tower Third Floor
door: Orange Barrier
- YOU:
- id: Color Arrow Room/Panel_you
+ doors:
+ Shortcut to The Steady:
+ id: Rock Room Doors/Door_hint
+ panels:
+ - EXIT
+ paintings:
+ - id: arrows_painting_7
+ orientation: east
+ - id: fruitbowl_painting3
+ orientation: west
+ enter_only: True
required_door:
room: Outside The Initiated
door: Entrance
+ Champion's Rest:
+ entrances:
+ Color Hunt:
+ room: Outside The Initiated
+ door: Entrance
+ panels:
+ YOU:
+ id: Color Arrow Room/Panel_you
check: True
colors: gray
tag: forbid
@@ -4072,53 +4135,24 @@
id: Color Arrow Room/Panel_me
colors: gray
tag: forbid
- required_door:
- room: Outside The Initiated
- door: Entrance
SECRET BLUE:
# Pretend this and the other two are white, because they are snipes.
# TODO: Extract them and randomize them?
id: Color Arrow Room/Panel_secret_blue
tag: forbid
- required_door:
- room: Outside The Initiated
- door: Entrance
SECRET YELLOW:
id: Color Arrow Room/Panel_secret_yellow
tag: forbid
- required_door:
- room: Outside The Initiated
- door: Entrance
SECRET RED:
id: Color Arrow Room/Panel_secret_red
tag: forbid
- required_door:
- room: Outside The Initiated
- door: Entrance
- doors:
- Shortcut to The Steady:
- id: Rock Room Doors/Door_hint
- panels:
- - EXIT
paintings:
- - id: arrows_painting_7
- orientation: east
- - id: fruitbowl_painting3
- orientation: west
- enter_only: True
- required_door:
- room: Outside The Initiated
- door: Entrance
- id: colors_painting
orientation: south
- enter_only: True
- required_door:
- room: Outside The Initiated
- door: Entrance
The Bearer:
entrances:
Outside The Bold:
- door: Shortcut to The Bold
+ door: Entrance
Orange Tower Fifth Floor:
room: Art Gallery
door: Exit
@@ -4195,7 +4229,7 @@
- yellow
tag: mid red yellow
doors:
- Shortcut to The Bold:
+ Entrance:
id: Red Blue Purple Room Area Doors/Door_middle_middle
panels:
- MIDDLE
@@ -4324,21 +4358,21 @@
door: Side Area Shortcut
Roof: True
panels:
- SNOW:
+ SMILE:
id: Cross Room/Panel_smile_lime
colors:
- red
- yellow
tag: mid yellow red
- SMILE:
+ required_panel:
+ room: The Bearer (North)
+ panel: WARTS
+ SNOW:
id: Cross Room/Panel_snow_won
colors:
- red
- yellow
tag: mid red yellow
- required_panel:
- room: The Bearer (North)
- panel: WARTS
doors:
Side Area Shortcut:
event: True
@@ -4414,9 +4448,14 @@
colors: blue
tag: forbid
required_panel:
- room: The Bearer (West)
- panel: SMILE
- required_room: Outside The Bold
+ - room: The Bearer (West)
+ panel: SMILE
+ - room: Outside The Bold
+ panel: SOUND
+ - room: Outside The Bold
+ panel: YEAST
+ - room: Outside The Bold
+ panel: WET
Cross Tower (South):
entrances: # No roof access
The Bearer (North):
@@ -6127,7 +6166,7 @@
id: Painting Room/Panel_our_four
colors: blue
tag: midblue
- ONE ROAD MANY TURNS:
+ ORDER:
id: Painting Room/Panel_order_onepathmanyturns
tag: forbid
colors:
@@ -6182,8 +6221,9 @@
Exit:
id: Tower Room Area Doors/Door_painting_exit
include_reduce: True
+ item_name: Orange Tower Fifth Floor - Quadruple Intersection
panels:
- - ONE ROAD MANY TURNS
+ - ORDER
paintings:
- id: smile_painting_3
orientation: west
@@ -6201,7 +6241,6 @@
- Third Floor
- Fourth Floor
- Fifth Floor
- - Exit
Art Gallery (Second Floor):
entrances:
Art Gallery:
@@ -6676,7 +6715,7 @@
- room: Rhyme Room (Target)
panel: PISTOL
- room: Rhyme Room (Target)
- panel: QUARTZ
+ panel: GEM
Rhyme Room (Target):
entrances:
Rhyme Room (Smiley): # one-way
@@ -6704,7 +6743,7 @@
tag: syn rhyme
subtag: top
link: rhyme CRYSTAL
- QUARTZ:
+ GEM:
id: Double Room/Panel_crystal_syn
colors: purple
tag: syn rhyme
@@ -6729,7 +6768,7 @@
group: Rhyme Room Doors
panels:
- PISTOL
- - QUARTZ
+ - GEM
- INNOVATIVE (Top)
- INNOVATIVE (Bottom)
paintings:
@@ -6787,22 +6826,18 @@
id: Panel Room/Panel_room_floor_5
colors: gray
tag: forbid
- FLOOR (7):
+ FLOOR (6):
id: Panel Room/Panel_room_floor_7
colors: gray
tag: forbid
- FLOOR (8):
+ FLOOR (7):
id: Panel Room/Panel_room_floor_8
colors: gray
tag: forbid
- FLOOR (9):
+ FLOOR (8):
id: Panel Room/Panel_room_floor_9
colors: gray
tag: forbid
- FLOOR (10):
- id: Panel Room/Panel_room_floor_10
- colors: gray
- tag: forbid
CEILING (1):
id: Panel Room/Panel_room_ceiling_1
colors: gray
@@ -6823,6 +6858,10 @@
id: Panel Room/Panel_room_ceiling_5
colors: gray
tag: forbid
+ CEILING (6):
+ id: Panel Room/Panel_room_floor_10
+ colors: gray
+ tag: forbid
WALL (1):
id: Panel Room/Panel_room_wall_1
colors: gray
@@ -6928,10 +6967,12 @@
MASTERY:
id: Master Room/Panel_mastery_mastery
tag: midwhite
- colors: gray
required_door:
room: Orange Tower Seventh Floor
door: Mastery
+ required_panel:
+ room: Room Room
+ panel: WALL (2)
doors:
Excavation:
event: True
@@ -7081,7 +7122,7 @@
id: Hangry Room/Panel_red_top_3
colors: red
tag: topred
- FLUMMOXED:
+ FLUSTERED:
id: Hangry Room/Panel_red_top_4
colors: red
tag: topred
@@ -7584,7 +7625,7 @@
- black
- blue
tag: chain mid black blue
- BREAD:
+ CHEESE:
id: Challenge Room/Panel_bread_mold
colors: brown
tag: double botbrown
@@ -7631,7 +7672,7 @@
id: Challenge Room/Panel_double_anagram_5
colors: yellow
tag: midyellow
- FACTS:
+ FACTS (Chain):
id: Challenge Room/Panel_facts
colors:
- red
@@ -7641,18 +7682,18 @@
id: Challenge Room/Panel_facts2
colors: red
tag: forbid
- FACTS (3):
+ FACTS (2):
id: Challenge Room/Panel_facts3
tag: forbid
- FACTS (4):
+ FACTS (3):
id: Challenge Room/Panel_facts4
colors: blue
tag: forbid
- FACTS (5):
+ FACTS (4):
id: Challenge Room/Panel_facts5
colors: blue
tag: forbid
- FACTS (6):
+ FACTS (5):
id: Challenge Room/Panel_facts6
colors: blue
tag: forbid
diff --git a/worlds/lingo/data/ids.yaml b/worlds/lingo/data/ids.yaml
index 2b9e7f3d8ca0..4cad94855512 100644
--- a/worlds/lingo/data/ids.yaml
+++ b/worlds/lingo/data/ids.yaml
@@ -31,12 +31,12 @@ panels:
LIES: 444408
The Seeker:
Achievement: 444409
- BEAR: 444410
- MINE: 444411
+ BEAR (1): 444410
+ MINE (1): 444411
MINE (2): 444412
BOW: 444413
DOES: 444414
- MOBILE: 444415
+ MOBILE (1): 444415
MOBILE (2): 444416
DESERT: 444417
DESSERT: 444418
@@ -57,8 +57,6 @@ panels:
Hub Room:
ORDER: 444432
SLAUGHTER: 444433
- NEAR: 444434
- FAR: 444435
TRACE: 444436
RAT: 444437
OPEN: 444438
@@ -72,7 +70,7 @@ panels:
EIGHT: 444445
Pilgrim Antechamber:
HOT CRUST: 444446
- PILGRIMAGE: 444447
+ PILGRIM: 444447
MASTERY: 444448
Pilgrim Room:
THIS: 444449
@@ -123,6 +121,9 @@ panels:
RACECAR (White): 444489
SOLOS (White): 444490
Achievement: 444491
+ Near Far Area:
+ NEAR: 444434
+ FAR: 444435
Warts Straw Area:
WARTS: 444492
STRAW: 444493
@@ -187,7 +188,7 @@ panels:
Achievement: 444546
GAZE: 444547
The Fearless (First Floor):
- NAPS: 444548
+ SPAN: 444548
TEAM: 444549
TEEM: 444550
IMPATIENT: 444551
@@ -208,25 +209,25 @@ panels:
EVEN: 444564
The Observant:
Achievement: 444565
- BACK: 444566
- SIDE: 444567
+ FOUR (1): 444566
+ FOUR (2): 444567
BACKSIDE: 444568
- STAIRS: 444569
- WAYS: 444570
- 'ON': 444571
- UP: 444572
- SWIMS: 444573
- UPSTAIRS: 444574
- TOIL: 444575
- STOP: 444576
- TOP: 444577
- HI: 444578
- HI (2): 444579
- '31': 444580
- '52': 444581
- OIL: 444582
- BACKSIDE (GREEN): 444583
- SIDEWAYS: 444584
+ SIX: 444569
+ FOUR (3): 444570
+ TWO (1): 444571
+ TWO (2): 444572
+ FIVE: 444573
+ BELOW (1): 444574
+ BLUE: 444575
+ BELOW (2): 444576
+ MINT (1): 444577
+ ESACREWOL: 444578
+ EULB: 444579
+ NUMBERS (1): 444580
+ NUMBERS (2): 444581
+ MINT (2): 444582
+ GREEN (1): 444583
+ GREEN (2): 444584
The Incomparable:
Achievement: 444585
A (One): 444586
@@ -254,7 +255,7 @@ panels:
RED: 444605
DEER + WREN: 444606
Orange Tower Fourth Floor:
- RUNT: 444607
+ RUNT (1): 444607
RUNT (2): 444608
LEARNS + UNSEW: 444609
HOT CRUSTS: 444610
@@ -279,14 +280,19 @@ panels:
THE END: 444620
THE MASTER: 444621
MASTERY: 444622
- Roof:
- MASTERY (1): 444623
- MASTERY (2): 444624
- MASTERY (3): 444625
- MASTERY (4): 444626
- MASTERY (5): 444627
- MASTERY (6): 444628
+ Behind A Smile:
+ MASTERY: 444623
STAIRCASE: 444629
+ Sixteen Colorful Squares:
+ MASTERY: 444624
+ Among Treetops:
+ MASTERY: 444625
+ Horizon's Edge:
+ MASTERY: 444626
+ Beneath The Lookout:
+ MASTERY: 444627
+ Rooftop Staircase:
+ MASTERY: 444628
Orange Tower Basement:
MASTERY: 444630
THE LIBRARY: 444631
@@ -341,16 +347,17 @@ panels:
ORANGE: 444663
UNCOVER: 444664
OXEN: 444665
- BACKSIDE: 444666
- The Optimistic: 444667
- PAST: 444668
- FUTURE: 444669
+ PAST (1): 444668
+ FUTURE (1): 444669
FUTURE (2): 444670
PAST (2): 444671
PRESENT: 444672
SMILE: 444673
ANGERED: 444674
VOTE: 444675
+ The Optimistic:
+ BACKSIDE: 444666
+ Achievement: 444667
The Initiated:
Achievement: 444676
DAUGHTER: 444677
@@ -400,7 +407,7 @@ panels:
ZEN: 444719
SON: 444720
STARGAZER: 444721
- MOUTH: 444722
+ SOUND: 444722
YEAST: 444723
WET: 444724
The Bold:
@@ -442,7 +449,7 @@ panels:
The Undeterred:
Achievement: 444759
BONE: 444760
- EYE: 444761
+ EYE (1): 444761
MOUTH: 444762
IRIS: 444763
EYE (2): 444764
@@ -489,7 +496,7 @@ panels:
WINDWARD: 444803
LIGHT: 444804
REWIND: 444805
- Champion's Rest:
+ Color Hunt:
EXIT: 444806
HUES: 444807
RED: 444808
@@ -498,6 +505,7 @@ panels:
GREEN: 444811
PURPLE: 444812
ORANGE: 444813
+ Champion's Rest:
YOU: 444814
ME: 444815
SECRET BLUE: 444816
@@ -523,8 +531,8 @@ panels:
TENT: 444832
BOWL: 444833
The Bearer (West):
- SNOW: 444834
- SMILE: 444835
+ SMILE: 444834
+ SNOW: 444835
Bearer Side Area:
SHORTCUT: 444836
POTS: 444837
@@ -719,7 +727,7 @@ panels:
TRUSTWORTHY: 444978
FREE: 444979
OUR: 444980
- ONE ROAD MANY TURNS: 444981
+ ORDER: 444981
Art Gallery (Second Floor):
HOUSE: 444982
PATH: 444983
@@ -777,7 +785,7 @@ panels:
WILD: 445028
KID: 445029
PISTOL: 445030
- QUARTZ: 445031
+ GEM: 445031
INNOVATIVE (Top): 445032
INNOVATIVE (Bottom): 445033
Room Room:
@@ -791,15 +799,15 @@ panels:
FLOOR (3): 445041
FLOOR (4): 445042
FLOOR (5): 445043
- FLOOR (7): 445044
- FLOOR (8): 445045
- FLOOR (9): 445046
- FLOOR (10): 445047
+ FLOOR (6): 445044
+ FLOOR (7): 445045
+ FLOOR (8): 445046
CEILING (1): 445048
CEILING (2): 445049
CEILING (3): 445050
CEILING (4): 445051
CEILING (5): 445052
+ CEILING (6): 445047
WALL (1): 445053
WALL (2): 445054
WALL (3): 445055
@@ -847,7 +855,7 @@ panels:
PANDEMIC (1): 445100
TRINITY: 445101
CHEMISTRY: 445102
- FLUMMOXED: 445103
+ FLUSTERED: 445103
PANDEMIC (2): 445104
COUNTERCLOCKWISE: 445105
FEARLESS: 445106
@@ -933,7 +941,7 @@ panels:
CORNER: 445182
STRAWBERRIES: 445183
GRUB: 445184
- BREAD: 445185
+ CHEESE: 445185
COLOR: 445186
WRITER: 445187
'02759': 445188
@@ -944,12 +952,12 @@ panels:
DUCK LOGO: 445193
AVIAN GREEN: 445194
FEVER TEAR: 445195
- FACTS: 445196
+ FACTS (Chain): 445196
FACTS (1): 445197
- FACTS (3): 445198
- FACTS (4): 445199
- FACTS (5): 445200
- FACTS (6): 445201
+ FACTS (2): 445198
+ FACTS (3): 445199
+ FACTS (4): 445200
+ FACTS (5): 445201
LAPEL SHEEP: 445202
doors:
Starting Room:
@@ -979,9 +987,6 @@ doors:
Tenacious Entrance:
item: 444426
location: 444433
- Symmetry Door:
- item: 444428
- location: 445204
Shortcut to Hedge Maze:
item: 444430
location: 444436
@@ -1038,6 +1043,10 @@ doors:
location: 445210
White Palindromes:
location: 445211
+ Near Far Area:
+ Door:
+ item: 444428
+ location: 445204
Warts Straw Area:
Door:
item: 444451
@@ -1286,12 +1295,12 @@ doors:
location: 445246
Yellow Barrier:
item: 444538
- Champion's Rest:
+ Color Hunt:
Shortcut to The Steady:
item: 444539
location: 444806
The Bearer:
- Shortcut to The Bold:
+ Entrance:
item: 444540
location: 444820
Backside Door:
@@ -1442,7 +1451,6 @@ door_groups:
Fearless Doors: 444469
Backside Doors: 444473
Orange Tower First Floor - Shortcuts: 444484
- Champion's Rest - Color Barriers: 444489
Welcome Back Doors: 444492
Colorful Doors: 444498
Directional Gallery Doors: 444531
diff --git a/worlds/lingo/items.py b/worlds/lingo/items.py
index 7b1a65056178..9f8bf5615592 100644
--- a/worlds/lingo/items.py
+++ b/worlds/lingo/items.py
@@ -24,14 +24,6 @@ def should_include(self, world: "LingoWorld") -> bool:
return world.options.shuffle_colors > 0
elif self.mode == "doors":
return world.options.shuffle_doors != ShuffleDoors.option_none
- elif self.mode == "orange tower":
- # door shuffle is on and tower isn't progressive
- return world.options.shuffle_doors != ShuffleDoors.option_none \
- and not world.options.progressive_orange_tower
- elif self.mode == "the colorful":
- # complex door shuffle is on and colorful isn't progressive
- return world.options.shuffle_doors == ShuffleDoors.option_complex \
- and not world.options.progressive_colorful
elif self.mode == "complex door":
return world.options.shuffle_doors == ShuffleDoors.option_complex
elif self.mode == "door group":
@@ -72,12 +64,7 @@ def load_item_data():
door_groups.setdefault(door.group, []).extend(door.door_ids)
if room_name in PROGRESSION_BY_ROOM and door_name in PROGRESSION_BY_ROOM[room_name]:
- if room_name == "Orange Tower":
- door_mode = "orange tower"
- elif room_name == "The Colorful":
- door_mode = "the colorful"
- else:
- door_mode = "special"
+ door_mode = "special"
ALL_ITEM_TABLE[door.item_name] = \
ItemData(get_door_item_id(room_name, door_name),
diff --git a/worlds/lingo/options.py b/worlds/lingo/options.py
index ec6158fab5ae..ed1426450eb7 100644
--- a/worlds/lingo/options.py
+++ b/worlds/lingo/options.py
@@ -1,6 +1,6 @@
from dataclasses import dataclass
-from Options import Toggle, Choice, DefaultOnToggle, Range, PerGameCommonOptions
+from Options import Toggle, Choice, DefaultOnToggle, Range, PerGameCommonOptions, StartInventoryPool
class ShuffleDoors(Choice):
@@ -136,3 +136,4 @@ class LingoOptions(PerGameCommonOptions):
trap_percentage: TrapPercentage
puzzle_skip_percentage: PuzzleSkipPercentage
death_link: DeathLink
+ start_inventory_from_pool: StartInventoryPool
diff --git a/worlds/lingo/player_logic.py b/worlds/lingo/player_logic.py
index f3efc2914c3d..3a6eedfe0ae1 100644
--- a/worlds/lingo/player_logic.py
+++ b/worlds/lingo/player_logic.py
@@ -1,3 +1,4 @@
+from enum import Enum
from typing import Dict, List, NamedTuple, Optional, Set, Tuple, TYPE_CHECKING
from .items import ALL_ITEM_TABLE
@@ -36,6 +37,27 @@ class PlayerLocation(NamedTuple):
access: AccessRequirements
+class ProgressiveItemBehavior(Enum):
+ DISABLE = 1
+ SPLIT = 2
+ PROGRESSIVE = 3
+
+
+def should_split_progression(progression_name: str, world: "LingoWorld") -> ProgressiveItemBehavior:
+ if progression_name == "Progressive Orange Tower":
+ if world.options.progressive_orange_tower:
+ return ProgressiveItemBehavior.PROGRESSIVE
+ else:
+ return ProgressiveItemBehavior.SPLIT
+ elif progression_name == "Progressive Colorful":
+ if world.options.progressive_colorful:
+ return ProgressiveItemBehavior.PROGRESSIVE
+ else:
+ return ProgressiveItemBehavior.SPLIT
+
+ return ProgressiveItemBehavior.PROGRESSIVE
+
+
class LingoPlayerLogic:
"""
Defines logic after a player's options have been applied
@@ -83,10 +105,13 @@ def set_door_item(self, room: str, door: str, item: str):
def handle_non_grouped_door(self, room_name: str, door_data: Door, world: "LingoWorld"):
if room_name in PROGRESSION_BY_ROOM and door_data.name in PROGRESSION_BY_ROOM[room_name]:
- if (room_name == "Orange Tower" and not world.options.progressive_orange_tower)\
- or (room_name == "The Colorful" and not world.options.progressive_colorful):
+ progression_name = PROGRESSION_BY_ROOM[room_name][door_data.name].item_name
+ progression_handling = should_split_progression(progression_name, world)
+
+ if progression_handling == ProgressiveItemBehavior.SPLIT:
self.set_door_item(room_name, door_data.name, door_data.item_name)
- else:
+ self.real_items.append(door_data.item_name)
+ elif progression_handling == ProgressiveItemBehavior.PROGRESSIVE:
progressive_item_name = PROGRESSION_BY_ROOM[room_name][door_data.name].item_name
self.set_door_item(room_name, door_data.name, progressive_item_name)
self.real_items.append(progressive_item_name)
@@ -196,9 +221,8 @@ def __init__(self, world: "LingoWorld"):
["Orange Tower Fourth Floor", "Hot Crusts Door"], ["Outside The Initiated", "Shortcut to Hub Room"],
["Orange Tower First Floor", "Shortcut to Hub Room"], ["Directional Gallery", "Shortcut to The Undeterred"],
["Orange Tower First Floor", "Salt Pepper Door"], ["Hub Room", "Crossroads Entrance"],
- ["Champion's Rest", "Shortcut to The Steady"], ["The Bearer", "Shortcut to The Bold"],
- ["Art Gallery", "Exit"], ["The Tenacious", "Shortcut to Hub Room"],
- ["Outside The Agreeable", "Tenacious Entrance"]
+ ["Color Hunt", "Shortcut to The Steady"], ["The Bearer", "Entrance"], ["Art Gallery", "Exit"],
+ ["The Tenacious", "Shortcut to Hub Room"], ["Outside The Agreeable", "Tenacious Entrance"]
]
pilgrimage_reqs = AccessRequirements()
for door in fake_pilgrimage:
@@ -224,30 +248,44 @@ def __init__(self, world: "LingoWorld"):
"kind of logic error.")
if door_shuffle != ShuffleDoors.option_none and location_classification != LocationClassification.insanity \
- and not early_color_hallways:
- # If shuffle doors is on, force a useful item onto the HI panel. This may not necessarily get you out of BK,
- # but the goal is to allow you to reach at least one more check. The non-painting ones are hardcoded right
- # now. We only allow the entrance to the Pilgrim Room if color shuffle is off, because otherwise there are
- # no extra checks in there. We only include the entrance to the Rhyme Room when color shuffle is off and
- # door shuffle is on simple, because otherwise there are no extra checks in there.
+ and not early_color_hallways and world.multiworld.players > 1:
+ # Under the combination of door shuffle, normal location checks, and no early color hallways, sphere 1 is
+ # only three checks. In a multiplayer situation, this can be frustrating for the player because they are
+ # more likely to be stuck in the starting room for a long time. To remedy this, we will force a useful item
+ # onto the GOOD LUCK check under these circumstances. The goal is to expand sphere 1 to at least four
+ # checks (and likely more than that).
+ #
+ # Note: A very low LEVEL 2 requirement would naturally expand sphere 1 to four checks, but this is a very
+ # uncommon configuration, so we will ignore it and force a good item anyway.
+
+ # Starting Room - Back Right Door gives access to OPEN and DEAD END.
+ # Starting Room - Exit Door gives access to OPEN and TRACE.
good_item_options: List[str] = ["Starting Room - Back Right Door", "Second Room - Exit Door"]
if not color_shuffle:
+ # HOT CRUST and THIS.
good_item_options.append("Pilgrim Room - Sun Painting")
- if door_shuffle == ShuffleDoors.option_simple:
- good_item_options += ["Welcome Back Doors"]
+ if door_shuffle == ShuffleDoors.option_simple:
+ # WELCOME BACK, CLOCKWISE, and DRAWL + RUNS.
+ good_item_options.append("Welcome Back Doors")
+ else:
+ # WELCOME BACK and CLOCKWISE.
+ good_item_options.append("Welcome Back Area - Shortcut to Starting Room")
- if not color_shuffle:
- good_item_options.append("Rhyme Room Doors")
- else:
- good_item_options += ["Welcome Back Area - Shortcut to Starting Room"]
+ if door_shuffle == ShuffleDoors.option_simple:
+ # Color hallways access (NOTE: reconsider when sunwarp shuffling exists).
+ good_item_options.append("Rhyme Room Doors")
+ # When painting shuffle is off, most Starting Room paintings give color hallways access. The Wondrous's
+ # painting does not, but it gives access to SHRINK and WELCOME BACK.
for painting_obj in PAINTINGS_BY_ROOM["Starting Room"]:
if not painting_obj.enter_only or painting_obj.required_door is None:
continue
# If painting shuffle is on, we only want to consider paintings that actually go somewhere.
+ #
+ # NOTE: This does not guarantee that there will be any checks on the other side.
if painting_shuffle and painting_obj.id not in self.painting_mapping.keys():
continue
diff --git a/worlds/lingo/test/TestDoors.py b/worlds/lingo/test/TestDoors.py
index 49a0f9c49010..f496c5f5785a 100644
--- a/worlds/lingo/test/TestDoors.py
+++ b/worlds/lingo/test/TestDoors.py
@@ -8,8 +8,6 @@ class TestRequiredRoomLogic(LingoTestBase):
}
def test_pilgrim_first(self) -> None:
- self.remove_forced_good_item()
-
self.assertFalse(self.multiworld.state.can_reach("The Seeker", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Pilgrim Antechamber", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Pilgrim Room", "Region", self.player))
@@ -30,8 +28,6 @@ def test_pilgrim_first(self) -> None:
self.assertTrue(self.can_reach_location("The Seeker - Achievement"))
def test_hidden_first(self) -> None:
- self.remove_forced_good_item()
-
self.assertFalse(self.multiworld.state.can_reach("The Seeker", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Pilgrim Room", "Region", self.player))
self.assertFalse(self.can_reach_location("The Seeker - Achievement"))
@@ -59,8 +55,6 @@ class TestRequiredDoorLogic(LingoTestBase):
}
def test_through_rhyme(self) -> None:
- self.remove_forced_good_item()
-
self.assertFalse(self.can_reach_location("Rhyme Room - Circle/Looped Square Wall"))
self.collect_by_name("Starting Room - Rhyme Room Entrance")
@@ -70,8 +64,6 @@ def test_through_rhyme(self) -> None:
self.assertTrue(self.can_reach_location("Rhyme Room - Circle/Looped Square Wall"))
def test_through_hidden(self) -> None:
- self.remove_forced_good_item()
-
self.assertFalse(self.can_reach_location("Rhyme Room - Circle/Looped Square Wall"))
self.collect_by_name("Starting Room - Rhyme Room Entrance")
@@ -91,8 +83,6 @@ class TestSimpleDoors(LingoTestBase):
}
def test_requirement(self):
- self.remove_forced_good_item()
-
self.assertFalse(self.multiworld.state.can_reach("Outside The Wanderer", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
diff --git a/worlds/lingo/test/TestOrangeTower.py b/worlds/lingo/test/TestOrangeTower.py
index 9170de108ad0..7b0c3bb52518 100644
--- a/worlds/lingo/test/TestOrangeTower.py
+++ b/worlds/lingo/test/TestOrangeTower.py
@@ -8,8 +8,6 @@ class TestProgressiveOrangeTower(LingoTestBase):
}
def test_from_welcome_back(self) -> None:
- self.remove_forced_good_item()
-
self.assertFalse(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
@@ -85,8 +83,6 @@ def test_from_welcome_back(self) -> None:
self.assertTrue(self.multiworld.state.can_reach("Orange Tower Seventh Floor", "Region", self.player))
def test_from_hub_room(self) -> None:
- self.remove_forced_good_item()
-
self.assertFalse(self.multiworld.state.can_reach("Orange Tower First Floor", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Second Floor", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Third Floor", "Region", self.player))
diff --git a/worlds/lingo/test/TestProgressive.py b/worlds/lingo/test/TestProgressive.py
index 8edc7ce6ccef..e79fd6bc9087 100644
--- a/worlds/lingo/test/TestProgressive.py
+++ b/worlds/lingo/test/TestProgressive.py
@@ -7,8 +7,6 @@ class TestComplexProgressiveHallwayRoom(LingoTestBase):
}
def test_item(self):
- self.remove_forced_good_item()
-
self.assertFalse(self.multiworld.state.can_reach("Outside The Agreeable", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Hallway Room (2)", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Hallway Room (3)", "Region", self.player))
@@ -60,8 +58,6 @@ class TestSimpleHallwayRoom(LingoTestBase):
}
def test_item(self):
- self.remove_forced_good_item()
-
self.assertFalse(self.multiworld.state.can_reach("Outside The Agreeable", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Hallway Room (2)", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Hallway Room (3)", "Region", self.player))
@@ -90,13 +86,11 @@ class TestProgressiveArtGallery(LingoTestBase):
}
def test_item(self):
- self.remove_forced_good_item()
-
self.assertFalse(self.multiworld.state.can_reach("Art Gallery", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player))
- self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS"))
+ self.assertFalse(self.can_reach_location("Art Gallery - ORDER"))
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player))
self.collect_by_name(["Second Room - Exit Door", "Crossroads - Tower Entrance",
@@ -105,7 +99,7 @@ def test_item(self):
self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player))
- self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS"))
+ self.assertFalse(self.can_reach_location("Art Gallery - ORDER"))
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player))
progressive_gallery_room = self.get_items_by_name("Progressive Art Gallery")
@@ -115,7 +109,7 @@ def test_item(self):
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player))
- self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS"))
+ self.assertFalse(self.can_reach_location("Art Gallery - ORDER"))
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player))
self.collect(progressive_gallery_room[1])
@@ -123,7 +117,7 @@ def test_item(self):
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player))
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player))
- self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS"))
+ self.assertFalse(self.can_reach_location("Art Gallery - ORDER"))
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player))
self.collect(progressive_gallery_room[2])
@@ -131,7 +125,7 @@ def test_item(self):
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player))
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player))
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player))
- self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS"))
+ self.assertFalse(self.can_reach_location("Art Gallery - ORDER"))
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player))
self.collect(progressive_gallery_room[3])
@@ -139,15 +133,15 @@ def test_item(self):
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player))
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player))
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player))
- self.assertTrue(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS"))
+ self.assertTrue(self.can_reach_location("Art Gallery - ORDER"))
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player))
- self.collect(progressive_gallery_room[4])
+ self.collect_by_name("Orange Tower Fifth Floor - Quadruple Intersection")
self.assertTrue(self.multiworld.state.can_reach("Art Gallery", "Region", self.player))
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player))
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player))
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player))
- self.assertTrue(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS"))
+ self.assertTrue(self.can_reach_location("Art Gallery - ORDER"))
self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player))
@@ -162,7 +156,7 @@ def test_item(self):
self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player))
- self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS"))
+ self.assertFalse(self.can_reach_location("Art Gallery - ORDER"))
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player))
self.collect_by_name("Yellow")
@@ -170,7 +164,7 @@ def test_item(self):
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player))
- self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS"))
+ self.assertFalse(self.can_reach_location("Art Gallery - ORDER"))
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player))
self.collect_by_name("Brown")
@@ -178,7 +172,7 @@ def test_item(self):
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player))
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player))
self.assertFalse(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player))
- self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS"))
+ self.assertFalse(self.can_reach_location("Art Gallery - ORDER"))
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player))
self.collect_by_name("Blue")
@@ -186,7 +180,7 @@ def test_item(self):
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player))
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player))
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player))
- self.assertFalse(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS"))
+ self.assertFalse(self.can_reach_location("Art Gallery - ORDER"))
self.assertFalse(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player))
self.collect_by_name(["Orange", "Gray"])
@@ -194,5 +188,5 @@ def test_item(self):
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Second Floor)", "Region", self.player))
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Third Floor)", "Region", self.player))
self.assertTrue(self.multiworld.state.can_reach("Art Gallery (Fourth Floor)", "Region", self.player))
- self.assertTrue(self.can_reach_location("Art Gallery - ONE ROAD MANY TURNS"))
+ self.assertTrue(self.can_reach_location("Art Gallery - ORDER"))
self.assertTrue(self.multiworld.state.can_reach("Orange Tower Fifth Floor", "Region", self.player))
diff --git a/worlds/lingo/test/__init__.py b/worlds/lingo/test/__init__.py
index 7ff456d8fcc3..a4196de110db 100644
--- a/worlds/lingo/test/__init__.py
+++ b/worlds/lingo/test/__init__.py
@@ -6,12 +6,3 @@
class LingoTestBase(WorldTestBase):
game = "Lingo"
player: ClassVar[int] = 1
-
- def world_setup(self, *args, **kwargs):
- super().world_setup(*args, **kwargs)
-
- def remove_forced_good_item(self):
- location = self.multiworld.get_location("Second Room - Good Luck", self.player)
- self.remove(location.item)
- self.multiworld.itempool.append(location.item)
- self.multiworld.state.events.add(location)
diff --git a/worlds/lingo/utils/validate_config.rb b/worlds/lingo/utils/validate_config.rb
index 3ac49dc220ce..96ed9fcd66b9 100644
--- a/worlds/lingo/utils/validate_config.rb
+++ b/worlds/lingo/utils/validate_config.rb
@@ -8,7 +8,8 @@
require 'yaml'
configpath = ARGV[0]
-mappath = ARGV[1]
+idspath = ARGV[1]
+mappath = ARGV[2]
panels = Set["Countdown Panels/Panel_1234567890_wanderlust"]
doors = Set["Naps Room Doors/Door_hider_new1", "Tower Room Area Doors/Door_wanderer_entrance"]
@@ -46,6 +47,8 @@
non_counting = 0
+ids = YAML.load_file(idspath)
+
config = YAML.load_file(configpath)
config.each do |room_name, room|
configured_rooms.add(room_name)
@@ -162,6 +165,10 @@
unless bad_subdirectives.empty? then
puts "#{room_name} - #{panel_name} :::: Panel has the following invalid subdirectives: #{bad_subdirectives.join(", ")}"
end
+
+ unless ids.include?("panels") and ids["panels"].include?(room_name) and ids["panels"][room_name].include?(panel_name)
+ puts "#{room_name} - #{panel_name} :::: Panel is missing a location ID"
+ end
end
(room["doors"] || {}).each do |door_name, door|
@@ -229,6 +236,18 @@
unless bad_subdirectives.empty? then
puts "#{room_name} - #{door_name} :::: Door has the following invalid subdirectives: #{bad_subdirectives.join(", ")}"
end
+
+ unless door["skip_item"] or door["event"]
+ unless ids.include?("doors") and ids["doors"].include?(room_name) and ids["doors"][room_name].include?(door_name) and ids["doors"][room_name][door_name].include?("item")
+ puts "#{room_name} - #{door_name} :::: Door is missing an item ID"
+ end
+ end
+
+ unless door["skip_location"] or door["event"]
+ unless ids.include?("doors") and ids["doors"].include?(room_name) and ids["doors"][room_name].include?(door_name) and ids["doors"][room_name][door_name].include?("location")
+ puts "#{room_name} - #{door_name} :::: Door is missing a location ID"
+ end
+ end
end
(room["paintings"] || []).each do |painting|
@@ -281,6 +300,10 @@
mentioned_doors.add("#{room_name} - #{door}")
end
end
+
+ unless ids.include?("progression") and ids["progression"].include?(progression_name)
+ puts "#{room_name} - #{progression_name} :::: Progression is missing an item ID"
+ end
end
end
@@ -303,6 +326,10 @@
if num == 1 then
puts "Door group \"#{group}\" only has one door in it"
end
+
+ unless ids.include?("door_groups") and ids["door_groups"].include?(group)
+ puts "#{group} :::: Door group is missing an item ID"
+ end
end
slashed_rooms = configured_rooms.select do |room|
diff --git a/worlds/meritous/__init__.py b/worlds/meritous/__init__.py
index 1bf1bfc0f2e6..fd12734be9db 100644
--- a/worlds/meritous/__init__.py
+++ b/worlds/meritous/__init__.py
@@ -17,7 +17,7 @@
class MeritousWeb(WebWorld):
tutorials = [Tutorial(
- "Meritous Setup Tutorial",
+ "Meritous Setup Guide",
"A guide to setting up the Archipelago Meritous software on your computer.",
"English",
"setup_en.md",
diff --git a/worlds/messenger/__init__.py b/worlds/messenger/__init__.py
index b0d031905c92..f4a28729f1ed 100644
--- a/worlds/messenger/__init__.py
+++ b/worlds/messenger/__init__.py
@@ -17,7 +17,7 @@ class MessengerWeb(WebWorld):
bug_report_page = "https://github.com/alwaysintreble/TheMessengerRandomizerModAP/issues"
tut_en = Tutorial(
- "Multiworld Setup Tutorial",
+ "Multiworld Setup Guide",
"A guide to setting up The Messenger randomizer on your computer.",
"English",
"setup_en.md",
diff --git a/worlds/messenger/options.py b/worlds/messenger/options.py
index 1da544bee70c..6984e215472a 100644
--- a/worlds/messenger/options.py
+++ b/worlds/messenger/options.py
@@ -3,7 +3,7 @@
from schema import And, Optional, Or, Schema
-from Options import Accessibility, Choice, DeathLink, DefaultOnToggle, OptionDict, PerGameCommonOptions, Range, \
+from Options import Accessibility, Choice, DeathLinkMixin, DefaultOnToggle, OptionDict, PerGameCommonOptions, Range, \
StartInventoryPool, Toggle
@@ -133,7 +133,7 @@ class PlannedShopPrices(OptionDict):
@dataclass
-class MessengerOptions(PerGameCommonOptions):
+class MessengerOptions(DeathLinkMixin, PerGameCommonOptions):
accessibility: MessengerAccessibility
start_inventory: StartInventoryPool
logic_level: Logic
@@ -146,5 +146,3 @@ class MessengerOptions(PerGameCommonOptions):
percent_seals_required: RequiredSeals
shop_price: ShopPrices
shop_price_plan: PlannedShopPrices
- death_link: DeathLink
-
diff --git a/worlds/messenger/test/__init__.py b/worlds/messenger/test/__init__.py
index 7ab1e11781da..f3fcd4ae2d60 100644
--- a/worlds/messenger/test/__init__.py
+++ b/worlds/messenger/test/__init__.py
@@ -1,6 +1,7 @@
from test.TestBase import WorldTestBase
+from .. import MessengerWorld
class MessengerTestBase(WorldTestBase):
game = "The Messenger"
- player: int = 1
+ world: MessengerWorld
diff --git a/worlds/messenger/test/test_locations.py b/worlds/messenger/test/test_locations.py
index 0c330be4bd3a..627d58c29061 100644
--- a/worlds/messenger/test/test_locations.py
+++ b/worlds/messenger/test/test_locations.py
@@ -12,5 +12,5 @@ def run_default_tests(self) -> bool:
return False
def test_locations_exist(self) -> None:
- for location in self.multiworld.worlds[1].location_name_to_id:
+ for location in self.world.location_name_to_id:
self.assertIsInstance(self.multiworld.get_location(location, self.player), MessengerLocation)
diff --git a/worlds/messenger/test/test_shop.py b/worlds/messenger/test/test_shop.py
index afb1b32b88e3..ee7e82d6cdbe 100644
--- a/worlds/messenger/test/test_shop.py
+++ b/worlds/messenger/test/test_shop.py
@@ -17,7 +17,7 @@ def test_shop_rules(self) -> None:
self.assertFalse(self.can_reach_location(loc))
def test_shop_prices(self) -> None:
- prices: Dict[str, int] = self.multiworld.worlds[self.player].shop_prices
+ prices: Dict[str, int] = self.world.shop_prices
for loc, price in prices.items():
with self.subTest("prices", loc=loc):
self.assertLessEqual(price, self.multiworld.get_location(f"The Shop - {loc}", self.player).cost)
@@ -51,7 +51,7 @@ class ShopCostMinTest(ShopCostTest):
}
def test_shop_rules(self) -> None:
- if self.multiworld.worlds[self.player].total_shards:
+ if self.world.total_shards:
super().test_shop_rules()
else:
for loc in SHOP_ITEMS:
@@ -85,7 +85,7 @@ def test_costs(self) -> None:
with self.subTest("has cost", loc=loc):
self.assertFalse(self.can_reach_location(loc))
- prices = self.multiworld.worlds[self.player].shop_prices
+ prices = self.world.shop_prices
for loc, price in prices.items():
with self.subTest("prices", loc=loc):
if loc == "Karuta Plates":
@@ -98,7 +98,7 @@ def test_costs(self) -> None:
self.assertTrue(loc.replace("The Shop - ", "") in SHOP_ITEMS)
self.assertEqual(len(prices), len(SHOP_ITEMS))
- figures = self.multiworld.worlds[self.player].figurine_prices
+ figures = self.world.figurine_prices
for loc, price in figures.items():
with self.subTest("figure prices", loc=loc):
if loc == "Barmath'azel Figurine":
diff --git a/worlds/messenger/test/test_shop_chest.py b/worlds/messenger/test/test_shop_chest.py
index a34fa0fb96c0..f2030c63de99 100644
--- a/worlds/messenger/test/test_shop_chest.py
+++ b/worlds/messenger/test/test_shop_chest.py
@@ -41,8 +41,8 @@ class HalfSealsRequired(MessengerTestBase):
def test_seals_amount(self) -> None:
"""Should have 45 power seals in the item pool and half that required"""
self.assertEqual(self.multiworld.total_seals[self.player], 45)
- self.assertEqual(self.multiworld.worlds[self.player].total_seals, 45)
- self.assertEqual(self.multiworld.worlds[self.player].required_seals, 22)
+ self.assertEqual(self.world.total_seals, 45)
+ self.assertEqual(self.world.required_seals, 22)
total_seals = [seal for seal in self.multiworld.itempool if seal.name == "Power Seal"]
required_seals = [seal for seal in total_seals
if seal.classification == ItemClassification.progression_skip_balancing]
@@ -60,8 +60,8 @@ class ThirtyThirtySeals(MessengerTestBase):
def test_seals_amount(self) -> None:
"""Should have 30 power seals in the pool and 33 percent of that required."""
self.assertEqual(self.multiworld.total_seals[self.player], 30)
- self.assertEqual(self.multiworld.worlds[self.player].total_seals, 30)
- self.assertEqual(self.multiworld.worlds[self.player].required_seals, 10)
+ self.assertEqual(self.world.total_seals, 30)
+ self.assertEqual(self.world.required_seals, 10)
total_seals = [seal for seal in self.multiworld.itempool if seal.name == "Power Seal"]
required_seals = [seal for seal in total_seals
if seal.classification == ItemClassification.progression_skip_balancing]
@@ -78,7 +78,7 @@ class MaxSealsNoShards(MessengerTestBase):
def test_seals_amount(self) -> None:
"""Should set total seals to 70 since shards aren't shuffled."""
self.assertEqual(self.multiworld.total_seals[self.player], 85)
- self.assertEqual(self.multiworld.worlds[self.player].total_seals, 70)
+ self.assertEqual(self.world.total_seals, 70)
class MaxSealsWithShards(MessengerTestBase):
@@ -91,8 +91,8 @@ class MaxSealsWithShards(MessengerTestBase):
def test_seals_amount(self) -> None:
"""Should have 85 seals in the pool with all required and be a valid seed."""
self.assertEqual(self.multiworld.total_seals[self.player], 85)
- self.assertEqual(self.multiworld.worlds[self.player].total_seals, 85)
- self.assertEqual(self.multiworld.worlds[self.player].required_seals, 85)
+ self.assertEqual(self.world.total_seals, 85)
+ self.assertEqual(self.world.required_seals, 85)
total_seals = [seal for seal in self.multiworld.itempool if seal.name == "Power Seal"]
required_seals = [seal for seal in total_seals
if seal.classification == ItemClassification.progression_skip_balancing]
diff --git a/worlds/minecraft/__init__.py b/worlds/minecraft/__init__.py
index 187f1fdf196a..343b9bad19a9 100644
--- a/worlds/minecraft/__init__.py
+++ b/worlds/minecraft/__init__.py
@@ -37,7 +37,7 @@ class MinecraftWebWorld(WebWorld):
bug_report_page = "https://github.com/KonoTyran/Minecraft_AP_Randomizer/issues/new?assignees=&labels=bug&template=bug_report.yaml&title=%5BBug%5D%3A+Brief+Description+of+bug+here"
setup = Tutorial(
- "Multiworld Setup Tutorial",
+ "Multiworld Setup Guide",
"A guide to setting up the Archipelago Minecraft software on your computer. This guide covers"
"single-player, multiworld, and related software.",
"English",
diff --git a/worlds/mmbn3/__init__.py b/worlds/mmbn3/__init__.py
index acf258a730c6..762bfd11ae4a 100644
--- a/worlds/mmbn3/__init__.py
+++ b/worlds/mmbn3/__init__.py
@@ -100,9 +100,7 @@ def create_regions(self) -> None:
for region_info in regions:
region = name_to_region[region_info.name]
for connection in region_info.connections:
- connection_region = name_to_region[connection]
- entrance = Entrance(self.player, connection, region)
- entrance.connect(connection_region)
+ entrance = region.connect(name_to_region[connection])
# ACDC Pending with Start Randomizer
# if connection == RegionName.ACDC_Overworld:
@@ -141,7 +139,6 @@ def create_regions(self) -> None:
if connection == RegionName.WWW_Island:
entrance.access_rule = lambda state:\
state.has(ItemName.Progressive_Undernet_Rank, self.player, 8)
- region.exits.append(entrance)
def create_items(self) -> None:
# First add in all progression and useful items
diff --git a/worlds/musedash/MuseDashCollection.py b/worlds/musedash/MuseDashCollection.py
index 6cd27c696c93..cc4cc71ce33f 100644
--- a/worlds/musedash/MuseDashCollection.py
+++ b/worlds/musedash/MuseDashCollection.py
@@ -26,7 +26,8 @@ class MuseDashCollections:
# MUSE_PLUS_DLC, # To be included when OptionSets are rendered as part of basic settings.
# "maimai DX Limited-time Suite", # Part of Muse Plus. Goes away 31st Jan 2026.
"Miku in Museland", # Paid DLC not included in Muse Plus
- "MSR Anthology", # Part of Muse Plus. Goes away 20th Jan 2024.
+ "Rin Len's Mirrorland", # Paid DLC not included in Muse Plus
+ "MSR Anthology", # Now no longer available.
]
DIFF_OVERRIDES: List[str] = [
diff --git a/worlds/musedash/MuseDashData.txt b/worlds/musedash/MuseDashData.txt
index fe3574f31b67..ce5929bfd00d 100644
--- a/worlds/musedash/MuseDashData.txt
+++ b/worlds/musedash/MuseDashData.txt
@@ -484,7 +484,7 @@ Hand in Hand|66-1|Miku in Museland|False|1|3|6|
Cynical Night Plan|66-2|Miku in Museland|False|4|6|8|
God-ish|66-3|Miku in Museland|False|4|7|10|
Darling Dance|66-4|Miku in Museland|False|4|7|9|
-Hatsune Creation Myth|66-5|Miku in Museland|False|6|8|10|
+Hatsune Creation Myth|66-5|Miku in Museland|False|6|8|10|11
The Vampire|66-6|Miku in Museland|False|4|6|9|
Future Eve|66-7|Miku in Museland|False|4|8|11|
Unknown Mother Goose|66-8|Miku in Museland|False|4|8|10|
@@ -509,4 +509,24 @@ INTERNET SURVIVOR|69-1|Touhou Mugakudan -3-|False|5|8|10|
Shuki*RaiRai|69-2|Touhou Mugakudan -3-|False|5|7|9|
HELLOHELL|69-3|Touhou Mugakudan -3-|False|4|7|10|
Calamity Fortune|69-4|Touhou Mugakudan -3-|True|6|8|10|11
-Tsurupettan|69-5|Touhou Mugakudan -3-|True|2|5|8|
\ No newline at end of file
+Tsurupettan|69-5|Touhou Mugakudan -3-|True|2|5|8|
+Twilight Poems|43-44|MD Plus Project|True|3|6|8|
+All My Friends feat. RANASOL|43-45|MD Plus Project|True|4|7|9|
+Heartache|43-46|MD Plus Project|True|5|7|10|
+Blue Lemonade|43-47|MD Plus Project|True|3|6|8|
+Haunted Dance|43-48|MD Plus Project|False|6|9|11|
+Hey Vincent.|43-49|MD Plus Project|True|6|8|10|
+Meteor feat. TEA|43-50|MD Plus Project|True|3|6|9|
+Narcissism Angel|43-51|MD Plus Project|True|1|3|6|
+AlterLuna|43-52|MD Plus Project|True|6|8|11|
+Niki Tousen|43-53|MD Plus Project|True|6|8|10|11
+Rettou Joutou|70-0|Rin Len's Mirrorland|False|4|7|9|
+Telecaster B-Boy|70-1|Rin Len's Mirrorland|False|5|7|10|
+Iya Iya Iya|70-2|Rin Len's Mirrorland|False|2|4|7|
+Nee Nee Nee|70-3|Rin Len's Mirrorland|False|4|6|8|
+Chaotic Love Revolution|70-4|Rin Len's Mirrorland|False|4|6|8|
+Dance of the Corpses|70-5|Rin Len's Mirrorland|False|2|5|8|
+Bitter Choco Decoration|70-6|Rin Len's Mirrorland|False|3|6|9|
+Dance Robot Dance|70-7|Rin Len's Mirrorland|False|4|7|10|
+Sweet Devil|70-8|Rin Len's Mirrorland|False|5|7|9|
+Someday'z Coming|70-9|Rin Len's Mirrorland|False|5|7|9|
\ No newline at end of file
diff --git a/worlds/musedash/Options.py b/worlds/musedash/Options.py
index d5ce313f8f03..26ad5ff5d967 100644
--- a/worlds/musedash/Options.py
+++ b/worlds/musedash/Options.py
@@ -36,7 +36,7 @@ class AdditionalSongs(Range):
- The final song count may be lower due to other settings.
"""
range_start = 15
- range_end = 508 # Note will probably not reach this high if any other settings are done.
+ range_end = 528 # Note will probably not reach this high if any other settings are done.
default = 40
display_name = "Additional Song Count"
diff --git a/worlds/noita/Events.py b/worlds/noita/Events.py
deleted file mode 100644
index e759d38c6c7a..000000000000
--- a/worlds/noita/Events.py
+++ /dev/null
@@ -1,42 +0,0 @@
-from typing import Dict
-
-from BaseClasses import Item, ItemClassification, Location, MultiWorld, Region
-from . import Items, Locations
-
-
-def create_event(player: int, name: str) -> Item:
- return Items.NoitaItem(name, ItemClassification.progression, None, player)
-
-
-def create_location(player: int, name: str, region: Region) -> Location:
- return Locations.NoitaLocation(player, name, None, region)
-
-
-def create_locked_location_event(multiworld: MultiWorld, player: int, region_name: str, item: str) -> Location:
- region = multiworld.get_region(region_name, player)
-
- new_location = create_location(player, item, region)
- new_location.place_locked_item(create_event(player, item))
-
- region.locations.append(new_location)
- return new_location
-
-
-def create_all_events(multiworld: MultiWorld, player: int) -> None:
- for region, event in event_locks.items():
- create_locked_location_event(multiworld, player, region, event)
-
- multiworld.completion_condition[player] = lambda state: state.has("Victory", player)
-
-
-# Maps region names to event names
-event_locks: Dict[str, str] = {
- "The Work": "Victory",
- "Mines": "Portal to Holy Mountain 1",
- "Coal Pits": "Portal to Holy Mountain 2",
- "Snowy Depths": "Portal to Holy Mountain 3",
- "Hiisi Base": "Portal to Holy Mountain 4",
- "Underground Jungle": "Portal to Holy Mountain 5",
- "The Vault": "Portal to Holy Mountain 6",
- "Temple of the Art": "Portal to Holy Mountain 7",
-}
diff --git a/worlds/noita/Rules.py b/worlds/noita/Rules.py
deleted file mode 100644
index 8190b80dc710..000000000000
--- a/worlds/noita/Rules.py
+++ /dev/null
@@ -1,166 +0,0 @@
-from typing import List, NamedTuple, Set
-
-from BaseClasses import CollectionState, MultiWorld
-from . import Items, Locations
-from .Options import BossesAsChecks, VictoryCondition
-from worlds.generic import Rules as GenericRules
-
-
-class EntranceLock(NamedTuple):
- source: str
- destination: str
- event: str
- items_needed: int
-
-
-entrance_locks: List[EntranceLock] = [
- EntranceLock("Mines", "Coal Pits Holy Mountain", "Portal to Holy Mountain 1", 1),
- EntranceLock("Coal Pits", "Snowy Depths Holy Mountain", "Portal to Holy Mountain 2", 2),
- EntranceLock("Snowy Depths", "Hiisi Base Holy Mountain", "Portal to Holy Mountain 3", 3),
- EntranceLock("Hiisi Base", "Underground Jungle Holy Mountain", "Portal to Holy Mountain 4", 4),
- EntranceLock("Underground Jungle", "Vault Holy Mountain", "Portal to Holy Mountain 5", 5),
- EntranceLock("The Vault", "Temple of the Art Holy Mountain", "Portal to Holy Mountain 6", 6),
- EntranceLock("Temple of the Art", "Laboratory Holy Mountain", "Portal to Holy Mountain 7", 7),
-]
-
-
-holy_mountain_regions: List[str] = [
- "Coal Pits Holy Mountain",
- "Snowy Depths Holy Mountain",
- "Hiisi Base Holy Mountain",
- "Underground Jungle Holy Mountain",
- "Vault Holy Mountain",
- "Temple of the Art Holy Mountain",
- "Laboratory Holy Mountain",
-]
-
-
-wand_tiers: List[str] = [
- "Wand (Tier 1)", # Coal Pits
- "Wand (Tier 2)", # Snowy Depths
- "Wand (Tier 3)", # Hiisi Base
- "Wand (Tier 4)", # Underground Jungle
- "Wand (Tier 5)", # The Vault
- "Wand (Tier 6)", # Temple of the Art
-]
-
-items_hidden_from_shops: List[str] = ["Gold (200)", "Gold (1000)", "Potion", "Random Potion", "Secret Potion",
- "Chaos Die", "Greed Die", "Kammi", "Refreshing Gourd", "Sädekivi", "Broken Wand",
- "Powder Pouch"]
-
-perk_list: List[str] = list(filter(Items.item_is_perk, Items.item_table.keys()))
-
-
-# ----------------
-# Helper Functions
-# ----------------
-
-
-def has_perk_count(state: CollectionState, player: int, amount: int) -> bool:
- return sum(state.count(perk, player) for perk in perk_list) >= amount
-
-
-def has_orb_count(state: CollectionState, player: int, amount: int) -> bool:
- return state.count("Orb", player) >= amount
-
-
-def forbid_items_at_location(multiworld: MultiWorld, location_name: str, items: Set[str], player: int):
- location = multiworld.get_location(location_name, player)
- GenericRules.forbid_items_for_player(location, items, player)
-
-
-# ----------------
-# Rule Functions
-# ----------------
-
-
-# Prevent gold and potions from appearing as purchasable items in shops (because physics will destroy them)
-def ban_items_from_shops(multiworld: MultiWorld, player: int) -> None:
- for location_name in Locations.location_name_to_id.keys():
- if "Shop Item" in location_name:
- forbid_items_at_location(multiworld, location_name, items_hidden_from_shops, player)
-
-
-# Prevent high tier wands from appearing in early Holy Mountain shops
-def ban_early_high_tier_wands(multiworld: MultiWorld, player: int) -> None:
- for i, region_name in enumerate(holy_mountain_regions):
- wands_to_forbid = wand_tiers[i+1:]
-
- locations_in_region = Locations.location_region_mapping[region_name].keys()
- for location_name in locations_in_region:
- forbid_items_at_location(multiworld, location_name, wands_to_forbid, player)
-
- # Prevent high tier wands from appearing in the Secret shop
- wands_to_forbid = wand_tiers[3:]
- locations_in_region = Locations.location_region_mapping["Secret Shop"].keys()
- for location_name in locations_in_region:
- forbid_items_at_location(multiworld, location_name, wands_to_forbid, player)
-
-
-def lock_holy_mountains_into_spheres(multiworld: MultiWorld, player: int) -> None:
- for lock in entrance_locks:
- location = multiworld.get_entrance(f"From {lock.source} To {lock.destination}", player)
- GenericRules.set_rule(location, lambda state, evt=lock.event: state.has(evt, player))
-
-
-def holy_mountain_unlock_conditions(multiworld: MultiWorld, player: int) -> None:
- victory_condition = multiworld.victory_condition[player].value
- for lock in entrance_locks:
- location = multiworld.get_location(lock.event, player)
-
- if victory_condition == VictoryCondition.option_greed_ending:
- location.access_rule = lambda state, items_needed=lock.items_needed: (
- has_perk_count(state, player, items_needed//2)
- )
- elif victory_condition == VictoryCondition.option_pure_ending:
- location.access_rule = lambda state, items_needed=lock.items_needed: (
- has_perk_count(state, player, items_needed//2) and
- has_orb_count(state, player, items_needed)
- )
- elif victory_condition == VictoryCondition.option_peaceful_ending:
- location.access_rule = lambda state, items_needed=lock.items_needed: (
- has_perk_count(state, player, items_needed//2) and
- has_orb_count(state, player, items_needed * 3)
- )
-
-
-def biome_unlock_conditions(multiworld: MultiWorld, player: int):
- lukki_entrances = multiworld.get_region("Lukki Lair", player).entrances
- magical_entrances = multiworld.get_region("Magical Temple", player).entrances
- wizard_entrances = multiworld.get_region("Wizards' Den", player).entrances
- for entrance in lukki_entrances:
- entrance.access_rule = lambda state: state.has("Melee Immunity Perk", player) and\
- state.has("All-Seeing Eye Perk", player)
- for entrance in magical_entrances:
- entrance.access_rule = lambda state: state.has("All-Seeing Eye Perk", player)
- for entrance in wizard_entrances:
- entrance.access_rule = lambda state: state.has("All-Seeing Eye Perk", player)
-
-
-def victory_unlock_conditions(multiworld: MultiWorld, player: int) -> None:
- victory_condition = multiworld.victory_condition[player].value
- victory_location = multiworld.get_location("Victory", player)
-
- if victory_condition == VictoryCondition.option_pure_ending:
- victory_location.access_rule = lambda state: has_orb_count(state, player, 11)
- elif victory_condition == VictoryCondition.option_peaceful_ending:
- victory_location.access_rule = lambda state: has_orb_count(state, player, 33)
-
-
-# ----------------
-# Main Function
-# ----------------
-
-
-def create_all_rules(multiworld: MultiWorld, player: int) -> None:
- if multiworld.players > 1:
- ban_items_from_shops(multiworld, player)
- ban_early_high_tier_wands(multiworld, player)
- lock_holy_mountains_into_spheres(multiworld, player)
- holy_mountain_unlock_conditions(multiworld, player)
- biome_unlock_conditions(multiworld, player)
- victory_unlock_conditions(multiworld, player)
-
- # Prevent the Map perk (used to find Toveri) from being on Toveri (boss)
- if multiworld.bosses_as_checks[player].value >= BossesAsChecks.option_all_bosses:
- forbid_items_at_location(multiworld, "Toveri", {"Spatial Awareness Perk"}, player)
diff --git a/worlds/noita/__init__.py b/worlds/noita/__init__.py
index 792b90e3f551..b8f8e4ae8346 100644
--- a/worlds/noita/__init__.py
+++ b/worlds/noita/__init__.py
@@ -1,6 +1,8 @@
from BaseClasses import Item, Tutorial
from worlds.AutoWorld import WebWorld, World
-from . import Events, Items, Locations, Options, Regions, Rules
+from typing import Dict, Any
+from . import events, items, locations, regions, rules
+from .options import NoitaOptions
class NoitaWeb(WebWorld):
@@ -24,13 +26,14 @@ class NoitaWorld(World):
"""
game = "Noita"
- option_definitions = Options.noita_options
+ options: NoitaOptions
+ options_dataclass = NoitaOptions
- item_name_to_id = Items.item_name_to_id
- location_name_to_id = Locations.location_name_to_id
+ item_name_to_id = items.item_name_to_id
+ location_name_to_id = locations.location_name_to_id
- item_name_groups = Items.item_name_groups
- location_name_groups = Locations.location_name_groups
+ item_name_groups = items.item_name_groups
+ location_name_groups = locations.location_name_groups
data_version = 2
web = NoitaWeb()
@@ -40,21 +43,21 @@ def generate_early(self):
raise Exception("Noita yaml's slot name has invalid character(s).")
# Returned items will be sent over to the client
- def fill_slot_data(self):
- return {name: getattr(self.multiworld, name)[self.player].value for name in self.option_definitions}
+ def fill_slot_data(self) -> Dict[str, Any]:
+ return self.options.as_dict("death_link", "victory_condition", "path_option", "hidden_chests",
+ "pedestal_checks", "orbs_as_checks", "bosses_as_checks", "extra_orbs", "shop_price")
def create_regions(self) -> None:
- Regions.create_all_regions_and_connections(self.multiworld, self.player)
- Events.create_all_events(self.multiworld, self.player)
+ regions.create_all_regions_and_connections(self)
def create_item(self, name: str) -> Item:
- return Items.create_item(self.player, name)
+ return items.create_item(self.player, name)
def create_items(self) -> None:
- Items.create_all_items(self.multiworld, self.player)
+ items.create_all_items(self)
def set_rules(self) -> None:
- Rules.create_all_rules(self.multiworld, self.player)
+ rules.create_all_rules(self)
def get_filler_item_name(self) -> str:
- return self.multiworld.random.choice(Items.filler_items)
+ return self.random.choice(items.filler_items)
diff --git a/worlds/noita/events.py b/worlds/noita/events.py
new file mode 100644
index 000000000000..4ec04e98b457
--- /dev/null
+++ b/worlds/noita/events.py
@@ -0,0 +1,43 @@
+from typing import Dict, TYPE_CHECKING
+from BaseClasses import Item, ItemClassification, Location, Region
+from . import items, locations
+
+if TYPE_CHECKING:
+ from . import NoitaWorld
+
+
+def create_event(player: int, name: str) -> Item:
+ return items.NoitaItem(name, ItemClassification.progression, None, player)
+
+
+def create_location(player: int, name: str, region: Region) -> Location:
+ return locations.NoitaLocation(player, name, None, region)
+
+
+def create_locked_location_event(player: int, region: Region, item: str) -> Location:
+ new_location = create_location(player, item, region)
+ new_location.place_locked_item(create_event(player, item))
+
+ region.locations.append(new_location)
+ return new_location
+
+
+def create_all_events(world: "NoitaWorld", created_regions: Dict[str, Region]) -> None:
+ for region_name, event in event_locks.items():
+ region = created_regions[region_name]
+ create_locked_location_event(world.player, region, event)
+
+ world.multiworld.completion_condition[world.player] = lambda state: state.has("Victory", world.player)
+
+
+# Maps region names to event names
+event_locks: Dict[str, str] = {
+ "The Work": "Victory",
+ "Mines": "Portal to Holy Mountain 1",
+ "Coal Pits": "Portal to Holy Mountain 2",
+ "Snowy Depths": "Portal to Holy Mountain 3",
+ "Hiisi Base": "Portal to Holy Mountain 4",
+ "Underground Jungle": "Portal to Holy Mountain 5",
+ "The Vault": "Portal to Holy Mountain 6",
+ "Temple of the Art": "Portal to Holy Mountain 7",
+}
diff --git a/worlds/noita/Items.py b/worlds/noita/items.py
similarity index 82%
rename from worlds/noita/Items.py
rename to worlds/noita/items.py
index c859a8039494..6b662fbee692 100644
--- a/worlds/noita/Items.py
+++ b/worlds/noita/items.py
@@ -1,9 +1,14 @@
import itertools
from collections import Counter
-from typing import Dict, List, NamedTuple, Set
+from typing import Dict, List, NamedTuple, Set, TYPE_CHECKING
-from BaseClasses import Item, ItemClassification, MultiWorld
-from .Options import BossesAsChecks, VictoryCondition, ExtraOrbs
+from BaseClasses import Item, ItemClassification
+from .options import BossesAsChecks, VictoryCondition, ExtraOrbs
+
+if TYPE_CHECKING:
+ from . import NoitaWorld
+else:
+ NoitaWorld = object
class ItemData(NamedTuple):
@@ -44,39 +49,40 @@ def create_kantele(victory_condition: VictoryCondition) -> List[str]:
return ["Kantele"] if victory_condition.value >= VictoryCondition.option_pure_ending else []
-def create_random_items(multiworld: MultiWorld, player: int, weights: Dict[str, int], count: int) -> List[str]:
+def create_random_items(world: NoitaWorld, weights: Dict[str, int], count: int) -> List[str]:
filler_pool = weights.copy()
- if multiworld.bad_effects[player].value == 0:
+ if not world.options.bad_effects:
del filler_pool["Trap"]
- return multiworld.random.choices(population=list(filler_pool.keys()),
- weights=list(filler_pool.values()),
- k=count)
+ return world.random.choices(population=list(filler_pool.keys()),
+ weights=list(filler_pool.values()),
+ k=count)
-def create_all_items(multiworld: MultiWorld, player: int) -> None:
- locations_to_fill = len(multiworld.get_unfilled_locations(player))
+def create_all_items(world: NoitaWorld) -> None:
+ player = world.player
+ locations_to_fill = len(world.multiworld.get_unfilled_locations(player))
itempool = (
create_fixed_item_pool()
- + create_orb_items(multiworld.victory_condition[player], multiworld.extra_orbs[player])
- + create_spatial_awareness_item(multiworld.bosses_as_checks[player])
- + create_kantele(multiworld.victory_condition[player])
+ + create_orb_items(world.options.victory_condition, world.options.extra_orbs)
+ + create_spatial_awareness_item(world.options.bosses_as_checks)
+ + create_kantele(world.options.victory_condition)
)
# if there's not enough shop-allowed items in the pool, we can encounter gen issues
# 39 is the number of shop-valid items we need to guarantee
if len(itempool) < 39:
- itempool += create_random_items(multiworld, player, shop_only_filler_weights, 39 - len(itempool))
+ itempool += create_random_items(world, shop_only_filler_weights, 39 - len(itempool))
# this is so that it passes tests and gens if you have minimal locations and only one player
- if multiworld.players == 1:
- for location in multiworld.get_unfilled_locations(player):
+ if world.multiworld.players == 1:
+ for location in world.multiworld.get_unfilled_locations(player):
if "Shop Item" in location.name:
location.item = create_item(player, itempool.pop())
- locations_to_fill = len(multiworld.get_unfilled_locations(player))
+ locations_to_fill = len(world.multiworld.get_unfilled_locations(player))
- itempool += create_random_items(multiworld, player, filler_weights, locations_to_fill - len(itempool))
- multiworld.itempool += [create_item(player, name) for name in itempool]
+ itempool += create_random_items(world, filler_weights, locations_to_fill - len(itempool))
+ world.multiworld.itempool += [create_item(player, name) for name in itempool]
# 110000 - 110032
diff --git a/worlds/noita/Locations.py b/worlds/noita/locations.py
similarity index 94%
rename from worlds/noita/Locations.py
rename to worlds/noita/locations.py
index 7c27d699ccba..afe16c54e4b2 100644
--- a/worlds/noita/Locations.py
+++ b/worlds/noita/locations.py
@@ -201,11 +201,10 @@ class LocationFlag(IntEnum):
}
-# Iterating the hidden chest and pedestal locations here to avoid clutter above
-def generate_location_entries(locname: str, locinfo: LocationData) -> Dict[str, int]:
- if locinfo.ltype in ["chest", "pedestal"]:
- return {f"{locname} {i + 1}": locinfo.id + i for i in range(20)}
- return {locname: locinfo.id}
+def make_location_range(location_name: str, base_id: int, amt: int) -> Dict[str, int]:
+ if amt == 1:
+ return {location_name: base_id}
+ return {f"{location_name} {i+1}": base_id + i for i in range(amt)}
location_name_groups: Dict[str, Set[str]] = {"shop": set(), "orb": set(), "boss": set(), "chest": set(),
@@ -215,9 +214,11 @@ def generate_location_entries(locname: str, locinfo: LocationData) -> Dict[str,
for location_group in location_region_mapping.values():
for locname, locinfo in location_group.items():
- location_name_to_id.update(generate_location_entries(locname, locinfo))
- if locinfo.ltype in ["chest", "pedestal"]:
- for i in range(20):
- location_name_groups[locinfo.ltype].add(f"{locname} {i + 1}")
- else:
- location_name_groups[locinfo.ltype].add(locname)
+ # Iterating the hidden chest and pedestal locations here to avoid clutter above
+ amount = 20 if locinfo.ltype in ["chest", "pedestal"] else 1
+ entries = make_location_range(locname, locinfo.id, amount)
+
+ location_name_to_id.update(entries)
+ location_name_groups[locinfo.ltype].update(entries.keys())
+
+shop_locations = {name for name in location_name_to_id.keys() if "Shop Item" in name}
diff --git a/worlds/noita/Options.py b/worlds/noita/options.py
similarity index 87%
rename from worlds/noita/Options.py
rename to worlds/noita/options.py
index 0b54597f364d..7d987571a589 100644
--- a/worlds/noita/Options.py
+++ b/worlds/noita/options.py
@@ -1,5 +1,5 @@
-from typing import Dict
-from Options import AssembleOptions, Choice, DeathLink, DefaultOnToggle, Range, StartInventoryPool
+from Options import Choice, DeathLink, DefaultOnToggle, Range, StartInventoryPool, PerGameCommonOptions
+from dataclasses import dataclass
class PathOption(Choice):
@@ -99,16 +99,16 @@ class ShopPrice(Choice):
default = 100
-noita_options: Dict[str, AssembleOptions] = {
- "start_inventory_from_pool": StartInventoryPool,
- "death_link": DeathLink,
- "bad_effects": Traps,
- "victory_condition": VictoryCondition,
- "path_option": PathOption,
- "hidden_chests": HiddenChests,
- "pedestal_checks": PedestalChecks,
- "orbs_as_checks": OrbsAsChecks,
- "bosses_as_checks": BossesAsChecks,
- "extra_orbs": ExtraOrbs,
- "shop_price": ShopPrice,
-}
+@dataclass
+class NoitaOptions(PerGameCommonOptions):
+ start_inventory_from_pool: StartInventoryPool
+ death_link: DeathLink
+ bad_effects: Traps
+ victory_condition: VictoryCondition
+ path_option: PathOption
+ hidden_chests: HiddenChests
+ pedestal_checks: PedestalChecks
+ orbs_as_checks: OrbsAsChecks
+ bosses_as_checks: BossesAsChecks
+ extra_orbs: ExtraOrbs
+ shop_price: ShopPrice
diff --git a/worlds/noita/Regions.py b/worlds/noita/regions.py
similarity index 61%
rename from worlds/noita/Regions.py
rename to worlds/noita/regions.py
index 561d483b4865..6a9c86772381 100644
--- a/worlds/noita/Regions.py
+++ b/worlds/noita/regions.py
@@ -1,48 +1,43 @@
# Regions are areas in your game that you travel to.
-from typing import Dict, Set, List
+from typing import Dict, List, TYPE_CHECKING
-from BaseClasses import Entrance, MultiWorld, Region
-from . import Locations
+from BaseClasses import Entrance, Region
+from . import locations
+from .events import create_all_events
+if TYPE_CHECKING:
+ from . import NoitaWorld
-def add_location(player: int, loc_name: str, id: int, region: Region) -> None:
- location = Locations.NoitaLocation(player, loc_name, id, region)
- region.locations.append(location)
-
-def add_locations(multiworld: MultiWorld, player: int, region: Region) -> None:
- locations = Locations.location_region_mapping.get(region.name, {})
- for location_name, location_data in locations.items():
+def create_locations(world: "NoitaWorld", region: Region) -> None:
+ locs = locations.location_region_mapping.get(region.name, {})
+ for location_name, location_data in locs.items():
location_type = location_data.ltype
flag = location_data.flag
- opt_orbs = multiworld.orbs_as_checks[player].value
- opt_bosses = multiworld.bosses_as_checks[player].value
- opt_paths = multiworld.path_option[player].value
- opt_num_chests = multiworld.hidden_chests[player].value
- opt_num_pedestals = multiworld.pedestal_checks[player].value
+ is_orb_allowed = location_type == "orb" and flag <= world.options.orbs_as_checks
+ is_boss_allowed = location_type == "boss" and flag <= world.options.bosses_as_checks
+ amount = 0
+ if flag == locations.LocationFlag.none or is_orb_allowed or is_boss_allowed:
+ amount = 1
+ elif location_type == "chest" and flag <= world.options.path_option:
+ amount = world.options.hidden_chests.value
+ elif location_type == "pedestal" and flag <= world.options.path_option:
+ amount = world.options.pedestal_checks.value
- is_orb_allowed = location_type == "orb" and flag <= opt_orbs
- is_boss_allowed = location_type == "boss" and flag <= opt_bosses
- if flag == Locations.LocationFlag.none or is_orb_allowed or is_boss_allowed:
- add_location(player, location_name, location_data.id, region)
- elif location_type == "chest" and flag <= opt_paths:
- for i in range(opt_num_chests):
- add_location(player, f"{location_name} {i+1}", location_data.id + i, region)
- elif location_type == "pedestal" and flag <= opt_paths:
- for i in range(opt_num_pedestals):
- add_location(player, f"{location_name} {i+1}", location_data.id + i, region)
+ region.add_locations(locations.make_location_range(location_name, location_data.id, amount),
+ locations.NoitaLocation)
# Creates a new Region with the locations found in `location_region_mapping` and adds them to the world.
-def create_region(multiworld: MultiWorld, player: int, region_name: str) -> Region:
- new_region = Region(region_name, player, multiworld)
- add_locations(multiworld, player, new_region)
+def create_region(world: "NoitaWorld", region_name: str) -> Region:
+ new_region = Region(region_name, world.player, world.multiworld)
+ create_locations(world, new_region)
return new_region
-def create_regions(multiworld: MultiWorld, player: int) -> Dict[str, Region]:
- return {name: create_region(multiworld, player, name) for name in noita_regions}
+def create_regions(world: "NoitaWorld") -> Dict[str, Region]:
+ return {name: create_region(world, name) for name in noita_regions}
# An "Entrance" is really just a connection between two regions
@@ -60,11 +55,12 @@ def create_connections(player: int, regions: Dict[str, Region]) -> None:
# Creates all regions and connections. Called from NoitaWorld.
-def create_all_regions_and_connections(multiworld: MultiWorld, player: int) -> None:
- created_regions = create_regions(multiworld, player)
- create_connections(player, created_regions)
+def create_all_regions_and_connections(world: "NoitaWorld") -> None:
+ created_regions = create_regions(world)
+ create_connections(world.player, created_regions)
+ create_all_events(world, created_regions)
- multiworld.regions += created_regions.values()
+ world.multiworld.regions += created_regions.values()
# Oh, what a tangled web we weave
diff --git a/worlds/noita/rules.py b/worlds/noita/rules.py
new file mode 100644
index 000000000000..95039bee4635
--- /dev/null
+++ b/worlds/noita/rules.py
@@ -0,0 +1,172 @@
+from typing import List, NamedTuple, Set, TYPE_CHECKING
+
+from BaseClasses import CollectionState
+from . import items, locations
+from .options import BossesAsChecks, VictoryCondition
+from worlds.generic import Rules as GenericRules
+
+if TYPE_CHECKING:
+ from . import NoitaWorld
+
+
+class EntranceLock(NamedTuple):
+ source: str
+ destination: str
+ event: str
+ items_needed: int
+
+
+entrance_locks: List[EntranceLock] = [
+ EntranceLock("Mines", "Coal Pits Holy Mountain", "Portal to Holy Mountain 1", 1),
+ EntranceLock("Coal Pits", "Snowy Depths Holy Mountain", "Portal to Holy Mountain 2", 2),
+ EntranceLock("Snowy Depths", "Hiisi Base Holy Mountain", "Portal to Holy Mountain 3", 3),
+ EntranceLock("Hiisi Base", "Underground Jungle Holy Mountain", "Portal to Holy Mountain 4", 4),
+ EntranceLock("Underground Jungle", "Vault Holy Mountain", "Portal to Holy Mountain 5", 5),
+ EntranceLock("The Vault", "Temple of the Art Holy Mountain", "Portal to Holy Mountain 6", 6),
+ EntranceLock("Temple of the Art", "Laboratory Holy Mountain", "Portal to Holy Mountain 7", 7),
+]
+
+
+holy_mountain_regions: List[str] = [
+ "Coal Pits Holy Mountain",
+ "Snowy Depths Holy Mountain",
+ "Hiisi Base Holy Mountain",
+ "Underground Jungle Holy Mountain",
+ "Vault Holy Mountain",
+ "Temple of the Art Holy Mountain",
+ "Laboratory Holy Mountain",
+]
+
+
+wand_tiers: List[str] = [
+ "Wand (Tier 1)", # Coal Pits
+ "Wand (Tier 2)", # Snowy Depths
+ "Wand (Tier 3)", # Hiisi Base
+ "Wand (Tier 4)", # Underground Jungle
+ "Wand (Tier 5)", # The Vault
+ "Wand (Tier 6)", # Temple of the Art
+]
+
+
+items_hidden_from_shops: Set[str] = {"Gold (200)", "Gold (1000)", "Potion", "Random Potion", "Secret Potion",
+ "Chaos Die", "Greed Die", "Kammi", "Refreshing Gourd", "Sädekivi", "Broken Wand",
+ "Powder Pouch"}
+
+perk_list: List[str] = list(filter(items.item_is_perk, items.item_table.keys()))
+
+
+# ----------------
+# Helper Functions
+# ----------------
+
+
+def has_perk_count(state: CollectionState, player: int, amount: int) -> bool:
+ return sum(state.count(perk, player) for perk in perk_list) >= amount
+
+
+def has_orb_count(state: CollectionState, player: int, amount: int) -> bool:
+ return state.count("Orb", player) >= amount
+
+
+def forbid_items_at_locations(world: "NoitaWorld", shop_locations: Set[str], forbidden_items: Set[str]):
+ for shop_location in shop_locations:
+ location = world.multiworld.get_location(shop_location, world.player)
+ GenericRules.forbid_items_for_player(location, forbidden_items, world.player)
+
+
+# ----------------
+# Rule Functions
+# ----------------
+
+
+# Prevent gold and potions from appearing as purchasable items in shops (because physics will destroy them)
+# def ban_items_from_shops(world: "NoitaWorld") -> None:
+# for location_name in Locations.location_name_to_id.keys():
+# if "Shop Item" in location_name:
+# forbid_items_at_location(world, location_name, items_hidden_from_shops)
+def ban_items_from_shops(world: "NoitaWorld") -> None:
+ forbid_items_at_locations(world, locations.shop_locations, items_hidden_from_shops)
+
+
+# Prevent high tier wands from appearing in early Holy Mountain shops
+def ban_early_high_tier_wands(world: "NoitaWorld") -> None:
+ for i, region_name in enumerate(holy_mountain_regions):
+ wands_to_forbid = set(wand_tiers[i+1:])
+
+ locations_in_region = set(locations.location_region_mapping[region_name].keys())
+ forbid_items_at_locations(world, locations_in_region, wands_to_forbid)
+
+ # Prevent high tier wands from appearing in the Secret shop
+ wands_to_forbid = set(wand_tiers[3:])
+ locations_in_region = set(locations.location_region_mapping["Secret Shop"].keys())
+ forbid_items_at_locations(world, locations_in_region, wands_to_forbid)
+
+
+def lock_holy_mountains_into_spheres(world: "NoitaWorld") -> None:
+ for lock in entrance_locks:
+ location = world.multiworld.get_entrance(f"From {lock.source} To {lock.destination}", world.player)
+ GenericRules.set_rule(location, lambda state, evt=lock.event: state.has(evt, world.player))
+
+
+def holy_mountain_unlock_conditions(world: "NoitaWorld") -> None:
+ victory_condition = world.options.victory_condition.value
+ for lock in entrance_locks:
+ location = world.multiworld.get_location(lock.event, world.player)
+
+ if victory_condition == VictoryCondition.option_greed_ending:
+ location.access_rule = lambda state, items_needed=lock.items_needed: (
+ has_perk_count(state, world.player, items_needed//2)
+ )
+ elif victory_condition == VictoryCondition.option_pure_ending:
+ location.access_rule = lambda state, items_needed=lock.items_needed: (
+ has_perk_count(state, world.player, items_needed//2) and
+ has_orb_count(state, world.player, items_needed)
+ )
+ elif victory_condition == VictoryCondition.option_peaceful_ending:
+ location.access_rule = lambda state, items_needed=lock.items_needed: (
+ has_perk_count(state, world.player, items_needed//2) and
+ has_orb_count(state, world.player, items_needed * 3)
+ )
+
+
+def biome_unlock_conditions(world: "NoitaWorld"):
+ lukki_entrances = world.multiworld.get_region("Lukki Lair", world.player).entrances
+ magical_entrances = world.multiworld.get_region("Magical Temple", world.player).entrances
+ wizard_entrances = world.multiworld.get_region("Wizards' Den", world.player).entrances
+ for entrance in lukki_entrances:
+ entrance.access_rule = lambda state: state.has("Melee Immunity Perk", world.player) and\
+ state.has("All-Seeing Eye Perk", world.player)
+ for entrance in magical_entrances:
+ entrance.access_rule = lambda state: state.has("All-Seeing Eye Perk", world.player)
+ for entrance in wizard_entrances:
+ entrance.access_rule = lambda state: state.has("All-Seeing Eye Perk", world.player)
+
+
+def victory_unlock_conditions(world: "NoitaWorld") -> None:
+ victory_condition = world.options.victory_condition.value
+ victory_location = world.multiworld.get_location("Victory", world.player)
+
+ if victory_condition == VictoryCondition.option_pure_ending:
+ victory_location.access_rule = lambda state: has_orb_count(state, world.player, 11)
+ elif victory_condition == VictoryCondition.option_peaceful_ending:
+ victory_location.access_rule = lambda state: has_orb_count(state, world.player, 33)
+
+
+# ----------------
+# Main Function
+# ----------------
+
+
+def create_all_rules(world: "NoitaWorld") -> None:
+ if world.multiworld.players > 1:
+ ban_items_from_shops(world)
+ ban_early_high_tier_wands(world)
+ lock_holy_mountains_into_spheres(world)
+ holy_mountain_unlock_conditions(world)
+ biome_unlock_conditions(world)
+ victory_unlock_conditions(world)
+
+ # Prevent the Map perk (used to find Toveri) from being on Toveri (boss)
+ if world.options.bosses_as_checks.value >= BossesAsChecks.option_all_bosses:
+ toveri = world.multiworld.get_location("Toveri", world.player)
+ GenericRules.forbid_items_for_player(toveri, {"Spatial Awareness Perk"}, world.player)
diff --git a/worlds/oot/Options.py b/worlds/oot/Options.py
index 120027e29dfa..2543cdc715c7 100644
--- a/worlds/oot/Options.py
+++ b/worlds/oot/Options.py
@@ -1271,7 +1271,7 @@ class LogicTricks(OptionList):
https://github.com/ArchipelagoMW/Archipelago/blob/main/worlds/oot/LogicTricks.py
"""
display_name = "Logic Tricks"
- valid_keys = frozenset(normalized_name_tricks)
+ valid_keys = tuple(normalized_name_tricks.keys())
valid_keys_casefold = True
diff --git a/worlds/oot/__init__.py b/worlds/oot/__init__.py
index eb9c41f0b032..2f06500e81b6 100644
--- a/worlds/oot/__init__.py
+++ b/worlds/oot/__init__.py
@@ -92,7 +92,7 @@ class RomStart(str):
class OOTWeb(WebWorld):
setup = Tutorial(
- "Multiworld Setup Tutorial",
+ "Multiworld Setup Guide",
"A guide to setting up the Archipelago Ocarina of Time software on your computer.",
"English",
"setup_en.md",
diff --git a/worlds/overcooked2/__init__.py b/worlds/overcooked2/__init__.py
index 0451f32bdd49..da0e1890894a 100644
--- a/worlds/overcooked2/__init__.py
+++ b/worlds/overcooked2/__init__.py
@@ -16,7 +16,7 @@ class Overcooked2Web(WebWorld):
bug_report_page = "https://github.com/toasterparty/oc2-modding/issues"
setup_en = Tutorial(
- "Multiworld Setup Tutorial",
+ "Multiworld Setup Guide",
"A guide to setting up the Overcooked! 2 randomizer on your computer.",
"English",
"setup_en.md",
@@ -90,13 +90,7 @@ def add_region(self, region_name: str):
def connect_regions(self, source: str, target: str, rule: Optional[Callable[[CollectionState], bool]] = None):
sourceRegion = self.multiworld.get_region(source, self.player)
targetRegion = self.multiworld.get_region(target, self.player)
-
- connection = Entrance(self.player, '', sourceRegion)
- if rule:
- connection.access_rule = rule
-
- sourceRegion.exits.append(connection)
- connection.connect(targetRegion)
+ sourceRegion.connect(targetRegion, rule=rule)
def add_level_location(
self,
diff --git a/worlds/pokemon_emerald/__init__.py b/worlds/pokemon_emerald/__init__.py
index 5d50e0db96dc..4d40dd196688 100644
--- a/worlds/pokemon_emerald/__init__.py
+++ b/worlds/pokemon_emerald/__init__.py
@@ -36,6 +36,7 @@ class PokemonEmeraldWebWorld(WebWorld):
Webhost info for Pokemon Emerald
"""
theme = "ocean"
+
setup_en = Tutorial(
"Multiworld Setup Guide",
"A guide to playing Pokémon Emerald with Archipelago.",
@@ -45,7 +46,16 @@ class PokemonEmeraldWebWorld(WebWorld):
["Zunawe"]
)
- tutorials = [setup_en]
+ setup_es = Tutorial(
+ "GuÃa de configuración para Multiworld",
+ "Una guÃa para jugar Pokémon Emerald en Archipelago",
+ "Español",
+ "setup_es.md",
+ "setup/es",
+ ["nachocua"]
+ )
+
+ tutorials = [setup_en, setup_es]
class PokemonEmeraldSettings(settings.Group):
@@ -279,6 +289,7 @@ def create_items(self) -> None:
def refresh_tm_choices() -> None:
fill_item_candidates_by_category["TM"] = all_tm_choices.copy()
self.random.shuffle(fill_item_candidates_by_category["TM"])
+ refresh_tm_choices()
# Create items
for item in default_itempool:
@@ -329,6 +340,7 @@ def convert_unrandomized_items_to_events(tag: str) -> None:
for location in locations:
if location.tags is not None and tag in location.tags:
location.place_locked_item(self.create_event(self.item_id_to_name[location.default_item_code]))
+ location.progress_type = LocationProgressType.DEFAULT
location.address = None
if self.options.badges == RandomizeBadges.option_vanilla:
@@ -365,6 +377,12 @@ def pre_fill(self) -> None:
}
badge_items.sort(key=lambda item: badge_priority.get(item.name, 0))
+ # Un-exclude badge locations, since we need to put progression items on them
+ for location in badge_locations:
+ location.progress_type = LocationProgressType.DEFAULT \
+ if location.progress_type == LocationProgressType.EXCLUDED \
+ else location.progress_type
+
collection_state = self.multiworld.get_all_state(False)
if self.hm_shuffle_info is not None:
for _, item in self.hm_shuffle_info:
@@ -409,6 +427,12 @@ def pre_fill(self) -> None:
}
hm_items.sort(key=lambda item: hm_priority.get(item.name, 0))
+ # Un-exclude HM locations, since we need to put progression items on them
+ for location in hm_locations:
+ location.progress_type = LocationProgressType.DEFAULT \
+ if location.progress_type == LocationProgressType.EXCLUDED \
+ else location.progress_type
+
collection_state = self.multiworld.get_all_state(False)
# In specific very constrained conditions, fill_restrictive may run
diff --git a/worlds/pokemon_emerald/docs/rom changes.md b/worlds/pokemon_emerald/docs/rom changes.md
new file mode 100644
index 000000000000..9b189d08e76a
--- /dev/null
+++ b/worlds/pokemon_emerald/docs/rom changes.md
@@ -0,0 +1,75 @@
+## QoL
+
+- The catch tutorial and cutscenes during your first visit to Petalburg are skipped
+- The match call tutorial after you leave Devon Corp is skipped
+- Cycling and running is allowed in every map (some exceptions like Fortree and Pacifidlog)
+- When you run out of Repel steps, you'll be prompted to use another one if you have more in your bag
+- Text is always rendered in its entirety on the first frame (instant text)
+- With an option set, text will advance if A is held
+- The message explaining that the trainer is about to send out a new pokemon is shortened to fit on two lines so that
+you can still read the species when deciding whether to change pokemon
+- The Pokemon Center Nurse dialogue is entirely removed except for the final text box
+- When receiving TMs and HMs, the move that it teaches is consistently displayed in the "received item" message (by
+default, certain ways of receiving items would only display the TM/HM number)
+- The Pokedex starts in national mode
+- The Oldale Pokemart sells Poke Balls at the start of the game
+- Pauses during battles (e.g. the ~1 second pause at the start of a turn before an opponent uses a potion) are shorter
+by 62.5%
+- The sliding animation for trainers and wild pokemon at the start of a battle runs at double speed.
+- Bag space was greatly expanded (there is room for one stack of every unique item in every pocket, plus a little bit
+extra for some pockets)
+ - Save data format was changed as a result of this. Shrank some unused space and removed some multiplayer phrases from
+ the save data.
+ - Pretty much any code that checks for bag space is ignored or bypassed (this sounds dangerous, but with expanded bag
+ space you should pretty much never have a full bag unless you're trying to fill it up, and skipping those checks
+ greatly simplifies detecting when items are picked up)
+- Pokemon are never disobedient
+- When moving in the overworld, set the input priority based on the most recently pressed direction rather than by some
+predetermined priority
+- Shoal cave changes state every time you reload the map and is no longer tied to the RTC
+- Increased safari zone steps from 500 to 50000
+- Trainers will not approach the player if the blind trainers option is set
+- Changed trade evolutions to be possible without trading:
+ - Politoed: Use King's Rock in bag menu
+ - Alakazam: Level 37
+ - Machamp: Level 37
+ - Golem: Level 37
+ - Slowking: Use King's Rock in bag menu
+ - Gengar: Level 37
+ - Steelix: Use Metal Coat in bag menu
+ - Kingdra: Use Dragon Scale in bag menu
+ - Scizor: Use Metal Coat in bag menu
+ - Porygon2: Use Up-Grade in bag menu
+ - Milotic: Level 30
+ - Huntail: Use Deep Sea Tooth in bag menu
+ - Gorebyss: Use Deep Sea Scale in bag menu
+
+## Game State Changes/Softlock Prevention
+
+- Mr. Briney never disappears or stops letting you use his ferry
+- Prevent the player from flying or surfing until they have received the Pokedex
+- The S.S. Tidal will be available at all times if you have the option enabled
+- Some NPCs or tiles are removed on the creation of a new save file based on player options
+- Ensured that every species has some damaging move by level 5
+- Route 115 may have strength boulders between the beach and cave entrance based on player options
+- The Petalburg Gym is set up based on your player options rather than after the first 4 gyms
+- The E4 guards will actually check all your badges (or gyms beaten based on your options) instead of just the Feather
+Badge
+- Steven cuts the conversation short in Granite Cave if you don't have the Letter
+- Dock checks that you have the Devon Goods before asking you to deliver them (and thus opening the museum)
+- Rydel gives you both bikes at the same time
+- The man in Pacifidlog who gives you Frustration and Return will give you both at the same time, does not check
+friendship first, and no longer has any behavior related to the RTC
+- The woman who gives you the Soothe Bell in Slateport does not check friendship
+- When trading the Scanner with Captain Stern, you will receive both the Deep Sea Tooth and Deep Sea Scale
+
+## Misc
+
+- You can no longer try to switch bikes in the bike shop
+- The Seashore House only rewards you with 1 Soda Pop instead of 6
+- Many small changes that make it possible to swap single battles to double battles
+ - Includes some safeguards against two trainers seeing you and initiating a battle while one or both of them are
+ "single trainer double battles"
+- Game now properly waits on vblank instead of spinning in a while loop
+- Misc small changes to text for consistency
+- Many bugfixes to the vanilla game code
diff --git a/worlds/pokemon_emerald/docs/setup_es.md b/worlds/pokemon_emerald/docs/setup_es.md
new file mode 100644
index 000000000000..65a74a9ddc70
--- /dev/null
+++ b/worlds/pokemon_emerald/docs/setup_es.md
@@ -0,0 +1,74 @@
+# GuÃa de Configuración para Pokémon Emerald
+
+## Software Requerido
+
+- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases)
+- Una ROM de Pokémon Emerald en Inglés. La comunidad de Archipelago no puede proveerla.
+- [BizHawk](https://tasvideos.org/BizHawk/ReleaseHistory) 2.7 o posterior
+
+### Configuración de BizHawk
+
+Una vez que hayas instalado BizHawk, abre `EmuHawk.exe` y cambia las siguientes configuraciones:
+
+- Si estás usando BizHawk 2.7 o 2.8, ve a `Config > Customize`. En la pestaña Advanced, cambia el Lua Core de
+`NLua+KopiLua` a `Lua+LuaInterface`, luego reinicia EmuHawk. (Si estás usando BizHawk 2.9, puedes saltar este paso.)
+- En `Config > Customize`, activa la opción "Run in background" para prevenir desconexiones del cliente mientras
+la aplicación activa no sea EmuHawk.
+- Abre el archivo `.gba` en EmuHawk y luego ve a `Config > Controllers…` para configurar los controles. Si no puedes
+hacer clic en `Controllers…`, debes abrir cualquier ROM `.gba` primeramente.
+- Considera limpiar tus macros y atajos en `Config > Hotkeys…` si no quieres usarlas de manera intencional. Para
+limpiarlas, selecciona el atajo y presiona la tecla Esc.
+
+## Software Opcional
+
+- [Pokémon Emerald AP Tracker](https://github.com/AliceMousie/emerald-ap-tracker/releases/latest), para usar con
+[PopTracker](https://github.com/black-sliver/PopTracker/releases)
+
+## Generando y Parcheando el Juego
+
+1. Crea tu archivo de configuración (YAML). Puedes hacerlo en
+[Página de Opciones de Pokémon Emerald](../../../games/Pokemon%20Emerald/player-options).
+2. Sigue las instrucciones generales de Archipelago para [Generar un juego]
+(../../Archipelago/setup/en#generating-a-game). Esto generará un archivo de salida (output file) para ti. Tu archivo
+de parche tendrá la extensión de archivo`.apemerald`.
+3. Abre `ArchipelagoLauncher.exe`
+4. Selecciona "Open Patch" en el lado derecho y elige tu archivo de parcheo.
+5. Si esta es la primera vez que vas a parchear, se te pedirá que selecciones la ROM sin parchear.
+6. Un archivo parcheado con extensión `.gba` será creado en el mismo lugar que el archivo de parcheo.
+7. La primera vez que abras un archivo parcheado con el BizHawk Client, se te preguntará donde está localizado
+`EmuHawk.exe` en tu instalación de BizHawk.
+
+Si estás jugando una seed Single-Player y no te interesa el auto-tracking o las pistas, puedes parar aquÃ, cierra el
+cliente, y carga la ROM ya parcheada en cualquier emulador. Pero para partidas multi-worlds y para otras
+implementaciones de Archipelago, continúa usando BizHawk como tu emulador
+
+## Conectando con el Servidor
+
+Por defecto, al abrir un archivo parcheado, se harán de manera automática 1-5 pasos. Aun asÃ, ten en cuenta lo
+siguiente en caso de que debas cerrar y volver a abrir la ventana en mitad de la partida por algún motivo.
+
+1. Pokémon Emerald usa el Archipelago BizHawk Client. Si el cliente no se encuentra abierto al abrir la rom
+parcheada, puedes volver a abrirlo desde el Archipelago Launcher.
+2. Asegúrate que EmuHawk está corriendo la ROM parcheada.
+3. En EmuHawk, ve a `Tools > Lua Console`. Debes tener esta ventana abierta mientras juegas.
+4. En la ventana de Lua Console, ve a `Script > Open Script…`.
+5. Ve a la carpeta donde está instalado Archipelago y abre `data/lua/connector_bizhawk_generic.lua`.
+6. El emulador y el cliente eventualmente se conectarán uno con el otro. La ventana de BizHawk Client indicará que te
+has conectado y reconocerá Pokémon Emerald.
+7. Para conectar el cliente con el servidor, ingresa la dirección y el puerto de la sala (ej. `archipelago.gg:38281`)
+en el campo de texto que se encuentra en la parte superior del cliente y haz click en Connect.
+
+Ahora deberÃas poder enviar y recibir Ãtems. Debes seguir estos pasos cada vez que quieras reconectarte. Es seguro
+jugar de manera offline; se sincronizará todo cuando te vuelvas a conectar.
+
+## Tracking Automático
+
+Pokémon Emerald tiene un Map Tracker completamente funcional que soporta auto-tracking.
+
+1. Descarga [Pokémon Emerald AP Tracker](https://github.com/AliceMousie/emerald-ap-tracker/releases/latest) y
+[PopTracker](https://github.com/black-sliver/PopTracker/releases).
+2. Coloca la carpeta del Tracker en la carpeta packs/ dentro de la carpeta de instalación del PopTracker.
+3. Abre PopTracker, y carga el Pack de Pokémon Emerald Map Tracker.
+4. Para utilizar el auto-tracking, haz click en el sÃmbolo "AP" que se encuentra en la parte superior.
+5. Entra la dirección del Servidor de Archipelago (la misma a la que te conectaste para jugar), nombre del jugador, y
+contraseña (deja vacÃo este campo en caso de no utilizar contraseña).
diff --git a/worlds/pokemon_emerald/locations.py b/worlds/pokemon_emerald/locations.py
index bfe5be754585..3d842ecbac98 100644
--- a/worlds/pokemon_emerald/locations.py
+++ b/worlds/pokemon_emerald/locations.py
@@ -114,8 +114,10 @@ def create_location_label_to_id_map() -> Dict[str, int]:
"Littleroot Town - S.S. Ticket from Norman",
"SS Tidal - Hidden Item in Lower Deck Trash Can",
"SS Tidal - TM49 from Thief",
+ "Safari Zone NE - Item on Ledge",
"Safari Zone NE - Hidden Item North",
"Safari Zone NE - Hidden Item East",
+ "Safari Zone SE - Item in Grass",
"Safari Zone SE - Hidden Item in South Grass 1",
"Safari Zone SE - Hidden Item in South Grass 2",
}
diff --git a/worlds/pokemon_rb/__init__.py b/worlds/pokemon_rb/__init__.py
index 169ff1d59f1e..56502f50299c 100644
--- a/worlds/pokemon_rb/__init__.py
+++ b/worlds/pokemon_rb/__init__.py
@@ -46,7 +46,7 @@ class BlueRomFile(settings.UserFilePath):
class PokemonWebWorld(WebWorld):
setup_en = Tutorial(
"Multiworld Setup Guide",
- "A guide to playing Pokemon Red and Blue with Archipelago.",
+ "A guide to playing Pokémon Red and Blue with Archipelago.",
"English",
"setup_en.md",
"setup/en",
diff --git a/worlds/pokemon_rb/client.py b/worlds/pokemon_rb/client.py
index 7424cc8ddff6..9e2689bccc37 100644
--- a/worlds/pokemon_rb/client.py
+++ b/worlds/pokemon_rb/client.py
@@ -10,7 +10,7 @@
logger = logging.getLogger("Client")
-BANK_EXCHANGE_RATE = 100000000
+BANK_EXCHANGE_RATE = 50000000
DATA_LOCATIONS = {
"ItemIndex": (0x1A6E, 0x02),
diff --git a/worlds/pokemon_rb/items.py b/worlds/pokemon_rb/items.py
index b584869f41b9..24cad13252b1 100644
--- a/worlds/pokemon_rb/items.py
+++ b/worlds/pokemon_rb/items.py
@@ -42,7 +42,7 @@ def __init__(self, item_id, classification, groups):
"Repel": ItemData(30, ItemClassification.filler, ["Consumables"]),
"Old Amber": ItemData(31, ItemClassification.progression_skip_balancing, ["Unique", "Fossils", "Key Items"]),
"Fire Stone": ItemData(32, ItemClassification.progression_skip_balancing, ["Unique", "Evolution Stones", "Key Items"]),
- "Thunder Stone": ItemData(33, ItemClassification.progression_skip_balancing, ["Unique", "Evolution Stones" "Key Items"]),
+ "Thunder Stone": ItemData(33, ItemClassification.progression_skip_balancing, ["Unique", "Evolution Stones", "Key Items"]),
"Water Stone": ItemData(34, ItemClassification.progression_skip_balancing, ["Unique", "Evolution Stones", "Key Items"]),
"HP Up": ItemData(35, ItemClassification.filler, ["Consumables", "Vitamins"]),
"Protein": ItemData(36, ItemClassification.filler, ["Consumables", "Vitamins"]),
diff --git a/worlds/ror2/regions.py b/worlds/ror2/regions.py
index 13b229da9249..199fdccf80e8 100644
--- a/worlds/ror2/regions.py
+++ b/worlds/ror2/regions.py
@@ -140,11 +140,7 @@ def create_explore_region(multiworld: MultiWorld, player: int, name: str, data:
def create_connections_in_regions(multiworld: MultiWorld, player: int, name: str, data: RoRRegionData) -> None:
region = multiworld.get_region(name, player)
if data.region_exits:
- for region_exit in data.region_exits:
- r_exit_stage = Entrance(player, region_exit, region)
- exit_region = multiworld.get_region(region_exit, player)
- r_exit_stage.connect(exit_region)
- region.exits.append(r_exit_stage)
+ region.add_exits(data.region_exits)
def create_classic_regions(ror2_world: "RiskOfRainWorld") -> None:
diff --git a/worlds/ror2/rules.py b/worlds/ror2/rules.py
index 442e6c0002aa..b4d5fe68b82e 100644
--- a/worlds/ror2/rules.py
+++ b/worlds/ror2/rules.py
@@ -9,14 +9,16 @@
# Rule to see if it has access to the previous stage
-def has_entrance_access_rule(multiworld: MultiWorld, stage: str, entrance: str, player: int) -> None:
- multiworld.get_entrance(entrance, player).access_rule = \
- lambda state: state.has(entrance, player) and state.has(stage, player)
+def has_entrance_access_rule(multiworld: MultiWorld, stage: str, region: str, player: int) -> None:
+ rule = lambda state: state.has(region, player) and state.has(stage, player)
+ for entrance in multiworld.get_region(region, player).entrances:
+ entrance.access_rule = rule
-def has_all_items(multiworld: MultiWorld, items: Set[str], entrance: str, player: int) -> None:
- multiworld.get_entrance(entrance, player).access_rule = \
- lambda state: state.has_all(items, player) and state.has(entrance, player)
+def has_all_items(multiworld: MultiWorld, items: Set[str], region: str, player: int) -> None:
+ rule = lambda state: state.has_all(items, player) and state.has(region, player)
+ for entrance in multiworld.get_region(region, player).entrances:
+ entrance.access_rule = rule
# Checks to see if chest/shrine are accessible
@@ -45,8 +47,9 @@ def check_location(state, environment: str, player: int, item_number: int, item_
def get_stage_event(multiworld: MultiWorld, player: int, stage_number: int) -> None:
if stage_number == 4:
return
- multiworld.get_entrance(f"OrderedStage_{stage_number + 1}", player).access_rule = \
- lambda state: state.has(f"Stage {stage_number + 1}", player)
+ rule = lambda state: state.has(f"Stage {stage_number + 1}", player)
+ for entrance in multiworld.get_region(f"OrderedStage_{stage_number + 1}", player).entrances:
+ entrance.access_rule = rule
def set_rules(ror2_world: "RiskOfRainWorld") -> None:
diff --git a/worlds/ror2/test/test_limbo_goal.py b/worlds/ror2/test/test_limbo_goal.py
index f8757a917641..9be9cca1206a 100644
--- a/worlds/ror2/test/test_limbo_goal.py
+++ b/worlds/ror2/test/test_limbo_goal.py
@@ -8,8 +8,8 @@ class LimboGoalTest(RoR2TestBase):
def test_limbo(self) -> None:
self.collect_all_but(["Hidden Realm: A Moment, Whole", "Victory"])
- self.assertFalse(self.can_reach_entrance("Hidden Realm: A Moment, Whole"))
+ self.assertFalse(self.can_reach_region("Hidden Realm: A Moment, Whole"))
self.assertBeatable(False)
self.collect_by_name("Hidden Realm: A Moment, Whole")
- self.assertTrue(self.can_reach_entrance("Hidden Realm: A Moment, Whole"))
+ self.assertTrue(self.can_reach_region("Hidden Realm: A Moment, Whole"))
self.assertBeatable(True)
diff --git a/worlds/ror2/test/test_mithrix_goal.py b/worlds/ror2/test/test_mithrix_goal.py
index 7ed9a2cd73a2..03b82311783c 100644
--- a/worlds/ror2/test/test_mithrix_goal.py
+++ b/worlds/ror2/test/test_mithrix_goal.py
@@ -8,18 +8,18 @@ class MithrixGoalTest(RoR2TestBase):
def test_mithrix(self) -> None:
self.collect_all_but(["Commencement", "Victory"])
- self.assertFalse(self.can_reach_entrance("Commencement"))
+ self.assertFalse(self.can_reach_region("Commencement"))
self.assertBeatable(False)
self.collect_by_name("Commencement")
- self.assertTrue(self.can_reach_entrance("Commencement"))
+ self.assertTrue(self.can_reach_region("Commencement"))
self.assertBeatable(True)
def test_stage5(self) -> None:
self.collect_all_but(["Stage 4", "Sky Meadow", "Victory"])
- self.assertFalse(self.can_reach_entrance("Sky Meadow"))
+ self.assertFalse(self.can_reach_region("Sky Meadow"))
self.assertBeatable(False)
self.collect_by_name("Sky Meadow")
- self.assertFalse(self.can_reach_entrance("Sky Meadow"))
+ self.assertFalse(self.can_reach_region("Sky Meadow"))
self.collect_by_name("Stage 4")
- self.assertTrue(self.can_reach_entrance("Sky Meadow"))
+ self.assertTrue(self.can_reach_region("Sky Meadow"))
self.assertBeatable(True)
diff --git a/worlds/ror2/test/test_voidling_goal.py b/worlds/ror2/test/test_voidling_goal.py
index a7520a5c5f95..77d1349f10eb 100644
--- a/worlds/ror2/test/test_voidling_goal.py
+++ b/worlds/ror2/test/test_voidling_goal.py
@@ -9,17 +9,17 @@ class VoidlingGoalTest(RoR2TestBase):
def test_planetarium(self) -> None:
self.collect_all_but(["The Planetarium", "Victory"])
- self.assertFalse(self.can_reach_entrance("The Planetarium"))
+ self.assertFalse(self.can_reach_region("The Planetarium"))
self.assertBeatable(False)
self.collect_by_name("The Planetarium")
- self.assertTrue(self.can_reach_entrance("The Planetarium"))
+ self.assertTrue(self.can_reach_region("The Planetarium"))
self.assertBeatable(True)
def test_void_locus_to_victory(self) -> None:
self.collect_all_but(["Void Locus", "Commencement"])
self.assertFalse(self.can_reach_location("Victory"))
self.collect_by_name("Void Locus")
- self.assertTrue(self.can_reach_entrance("Victory"))
+ self.assertTrue(self.can_reach_location("Victory"))
def test_commencement_to_victory(self) -> None:
self.collect_all_but(["Void Locus", "Commencement"])
diff --git a/worlds/sm64ex/Items.py b/worlds/sm64ex/Items.py
index 5b429a23cdc3..546f1abd316b 100644
--- a/worlds/sm64ex/Items.py
+++ b/worlds/sm64ex/Items.py
@@ -16,6 +16,21 @@ class SM64Item(Item):
"1Up Mushroom": 3626184
}
+action_item_table = {
+ "Double Jump": 3626185,
+ "Triple Jump": 3626186,
+ "Long Jump": 3626187,
+ "Backflip": 3626188,
+ "Side Flip": 3626189,
+ "Wall Kick": 3626190,
+ "Dive": 3626191,
+ "Ground Pound": 3626192,
+ "Kick": 3626193,
+ "Climb": 3626194,
+ "Ledge Grab": 3626195
+}
+
+
cannon_item_table = {
"Cannon Unlock BoB": 3626200,
"Cannon Unlock WF": 3626201,
@@ -29,4 +44,4 @@ class SM64Item(Item):
"Cannon Unlock RR": 3626214
}
-item_table = {**generic_item_table, **cannon_item_table}
\ No newline at end of file
+item_table = {**generic_item_table, **action_item_table, **cannon_item_table}
\ No newline at end of file
diff --git a/worlds/sm64ex/Options.py b/worlds/sm64ex/Options.py
index 8a10f3edea55..d9a877df2b37 100644
--- a/worlds/sm64ex/Options.py
+++ b/worlds/sm64ex/Options.py
@@ -1,9 +1,10 @@
import typing
from Options import Option, DefaultOnToggle, Range, Toggle, DeathLink, Choice
-
+from .Items import action_item_table
class EnableCoinStars(DefaultOnToggle):
- """Disable to Ignore 100 Coin Stars. You can still collect them, but they don't do anything"""
+ """Disable to Ignore 100 Coin Stars. You can still collect them, but they don't do anything.
+ Removes 15 locations from the pool."""
display_name = "Enable 100 Coin Stars"
@@ -13,56 +14,63 @@ class StrictCapRequirements(DefaultOnToggle):
class StrictCannonRequirements(DefaultOnToggle):
- """If disabled, Stars that expect cannons may have to be acquired without them. Only makes a difference if Buddy
- Checks are enabled"""
+ """If disabled, Stars that expect cannons may have to be acquired without them.
+ Has no effect if Buddy Checks and Move Randomizer are disabled"""
display_name = "Strict Cannon Requirements"
class FirstBowserStarDoorCost(Range):
- """How many stars are required at the Star Door to Bowser in the Dark World"""
+ """What percent of the total stars are required at the Star Door to Bowser in the Dark World"""
+ display_name = "First Star Door Cost %"
range_start = 0
- range_end = 50
- default = 8
+ range_end = 40
+ default = 7
class BasementStarDoorCost(Range):
- """How many stars are required at the Star Door in the Basement"""
+ """What percent of the total stars are required at the Star Door in the Basement"""
+ display_name = "Basement Star Door %"
range_start = 0
- range_end = 70
- default = 30
+ range_end = 50
+ default = 25
class SecondFloorStarDoorCost(Range):
- """How many stars are required to access the third floor"""
+ """What percent of the total stars are required to access the third floor"""
+ display_name = 'Second Floor Star Door %'
range_start = 0
- range_end = 90
- default = 50
+ range_end = 70
+ default = 42
class MIPS1Cost(Range):
- """How many stars are required to spawn MIPS the first time"""
+ """What percent of the total stars are required to spawn MIPS the first time"""
+ display_name = "MIPS 1 Star %"
range_start = 0
- range_end = 40
- default = 15
+ range_end = 35
+ default = 12
class MIPS2Cost(Range):
- """How many stars are required to spawn MIPS the second time."""
+ """What percent of the total stars are required to spawn MIPS the second time."""
+ display_name = "MIPS 2 Star %"
range_start = 0
- range_end = 80
- default = 50
+ range_end = 70
+ default = 42
class StarsToFinish(Range):
- """How many stars are required at the infinite stairs"""
- display_name = "Endless Stairs Stars"
+ """What percent of the total stars are required at the infinite stairs"""
+ display_name = "Endless Stairs Star %"
range_start = 0
- range_end = 100
- default = 70
+ range_end = 90
+ default = 58
class AmountOfStars(Range):
- """How many stars exist. Disabling 100 Coin Stars removes 15 from the Pool. At least max of any Cost set"""
+ """How many stars exist.
+ If there aren't enough locations to hold the given total, the total will be reduced."""
+ display_name = "Total Power Stars"
range_start = 35
range_end = 120
default = 120
@@ -83,11 +91,13 @@ class BuddyChecks(Toggle):
class ExclamationBoxes(Choice):
- """Include 1Up Exclamation Boxes during randomization"""
+ """Include 1Up Exclamation Boxes during randomization.
+ Adds 29 locations to the pool."""
display_name = "Randomize 1Up !-Blocks"
option_Off = 0
option_1Ups_Only = 1
+
class CompletionType(Choice):
"""Set goal for game completion"""
display_name = "Completion Goal"
@@ -96,17 +106,32 @@ class CompletionType(Choice):
class ProgressiveKeys(DefaultOnToggle):
- """Keys will first grant you access to the Basement, then to the Secound Floor"""
+ """Keys will first grant you access to the Basement, then to the Second Floor"""
display_name = "Progressive Keys"
+class StrictMoveRequirements(DefaultOnToggle):
+ """If disabled, Stars that expect certain moves may have to be acquired without them. Only makes a difference
+ if Move Randomization is enabled"""
+ display_name = "Strict Move Requirements"
+
+def getMoveRandomizerOption(action: str):
+ class MoveRandomizerOption(Toggle):
+ """Mario is unable to perform this action until a corresponding item is picked up.
+ This option is incompatible with builds using a 'nomoverando' branch."""
+ display_name = f"Randomize {action}"
+ return MoveRandomizerOption
+
sm64_options: typing.Dict[str, type(Option)] = {
"AreaRandomizer": AreaRandomizer,
+ "BuddyChecks": BuddyChecks,
+ "ExclamationBoxes": ExclamationBoxes,
"ProgressiveKeys": ProgressiveKeys,
"EnableCoinStars": EnableCoinStars,
- "AmountOfStars": AmountOfStars,
"StrictCapRequirements": StrictCapRequirements,
"StrictCannonRequirements": StrictCannonRequirements,
+ "StrictMoveRequirements": StrictMoveRequirements,
+ "AmountOfStars": AmountOfStars,
"FirstBowserStarDoorCost": FirstBowserStarDoorCost,
"BasementStarDoorCost": BasementStarDoorCost,
"SecondFloorStarDoorCost": SecondFloorStarDoorCost,
@@ -114,7 +139,10 @@ class ProgressiveKeys(DefaultOnToggle):
"MIPS2Cost": MIPS2Cost,
"StarsToFinish": StarsToFinish,
"death_link": DeathLink,
- "BuddyChecks": BuddyChecks,
- "ExclamationBoxes": ExclamationBoxes,
- "CompletionType" : CompletionType,
+ "CompletionType": CompletionType,
}
+
+for action in action_item_table:
+ # HACK: Disable randomization of double jump
+ if action == 'Double Jump': continue
+ sm64_options[f"MoveRandomizer{action.replace(' ','')}"] = getMoveRandomizerOption(action)
diff --git a/worlds/sm64ex/Regions.py b/worlds/sm64ex/Regions.py
index d426804c30f3..8c2d32e401bf 100644
--- a/worlds/sm64ex/Regions.py
+++ b/worlds/sm64ex/Regions.py
@@ -1,5 +1,4 @@
import typing
-
from enum import Enum
from BaseClasses import MultiWorld, Region, Entrance, Location
@@ -8,7 +7,8 @@
locHMC_table, locLLL_table, locSSL_table, locDDD_table, locSL_table, \
locWDW_table, locTTM_table, locTHI_table, locTTC_table, locRR_table, \
locPSS_table, locSA_table, locBitDW_table, locTotWC_table, locCotMC_table, \
- locVCutM_table, locBitFS_table, locWMotR_table, locBitS_table, locSS_table
+ locVCutM_table, locBitFS_table, locWMotR_table, locBitS_table, locSS_table
+
class SM64Levels(int, Enum):
BOB_OMB_BATTLEFIELD = 91
@@ -55,7 +55,7 @@ class SM64Levels(int, Enum):
SM64Levels.TICK_TOCK_CLOCK: "Tick Tock Clock",
SM64Levels.RAINBOW_RIDE: "Rainbow Ride"
}
-sm64_paintings_to_level = { painting: level for (level,painting) in sm64_level_to_paintings.items() }
+sm64_paintings_to_level = {painting: level for (level, painting) in sm64_level_to_paintings.items() }
# sm64secrets is a dict of secret areas, same format as sm64paintings
sm64_level_to_secrets: typing.Dict[SM64Levels, str] = {
@@ -68,168 +68,193 @@ class SM64Levels(int, Enum):
SM64Levels.BOWSER_IN_THE_FIRE_SEA: "Bowser in the Fire Sea",
SM64Levels.WING_MARIO_OVER_THE_RAINBOW: "Wing Mario over the Rainbow"
}
-sm64_secrets_to_level = { secret: level for (level,secret) in sm64_level_to_secrets.items() }
+sm64_secrets_to_level = {secret: level for (level,secret) in sm64_level_to_secrets.items() }
-sm64_entrances_to_level = { **sm64_paintings_to_level, **sm64_secrets_to_level }
-sm64_level_to_entrances = { **sm64_level_to_paintings, **sm64_level_to_secrets }
+sm64_entrances_to_level = {**sm64_paintings_to_level, **sm64_secrets_to_level }
+sm64_level_to_entrances = {**sm64_level_to_paintings, **sm64_level_to_secrets }
def create_regions(world: MultiWorld, player: int):
regSS = Region("Menu", player, world, "Castle Area")
- create_default_locs(regSS, locSS_table, player)
+ create_default_locs(regSS, locSS_table)
world.regions.append(regSS)
regBoB = create_region("Bob-omb Battlefield", player, world)
- create_default_locs(regBoB, locBoB_table, player)
+ create_locs(regBoB, "BoB: Big Bob-Omb on the Summit", "BoB: Footrace with Koopa The Quick",
+ "BoB: Mario Wings to the Sky", "BoB: Behind Chain Chomp's Gate", "BoB: Bob-omb Buddy")
+ create_subregion(regBoB, "BoB: Island", "BoB: Shoot to the Island in the Sky", "BoB: Find the 8 Red Coins")
if (world.EnableCoinStars[player].value):
- regBoB.locations.append(SM64Location(player, "BoB: 100 Coins", location_table["BoB: 100 Coins"], regBoB))
- world.regions.append(regBoB)
+ create_locs(regBoB, "BoB: 100 Coins")
regWhomp = create_region("Whomp's Fortress", player, world)
- create_default_locs(regWhomp, locWhomp_table, player)
+ create_locs(regWhomp, "WF: Chip Off Whomp's Block", "WF: Shoot into the Wild Blue", "WF: Red Coins on the Floating Isle",
+ "WF: Fall onto the Caged Island", "WF: Blast Away the Wall")
+ create_subregion(regWhomp, "WF: Tower", "WF: To the Top of the Fortress", "WF: Bob-omb Buddy")
if (world.EnableCoinStars[player].value):
- regWhomp.locations.append(SM64Location(player, "WF: 100 Coins", location_table["WF: 100 Coins"], regWhomp))
- world.regions.append(regWhomp)
+ create_locs(regWhomp, "WF: 100 Coins")
regJRB = create_region("Jolly Roger Bay", player, world)
- create_default_locs(regJRB, locJRB_table, player)
+ create_locs(regJRB, "JRB: Plunder in the Sunken Ship", "JRB: Can the Eel Come Out to Play?", "JRB: Treasure of the Ocean Cave",
+ "JRB: Blast to the Stone Pillar", "JRB: Through the Jet Stream", "JRB: Bob-omb Buddy")
+ jrb_upper = create_subregion(regJRB, 'JRB: Upper', "JRB: Red Coins on the Ship Afloat")
if (world.EnableCoinStars[player].value):
- regJRB.locations.append(SM64Location(player, "JRB: 100 Coins", location_table["JRB: 100 Coins"], regJRB))
- world.regions.append(regJRB)
+ create_locs(jrb_upper, "JRB: 100 Coins")
regCCM = create_region("Cool, Cool Mountain", player, world)
- create_default_locs(regCCM, locCCM_table, player)
+ create_default_locs(regCCM, locCCM_table)
if (world.EnableCoinStars[player].value):
- regCCM.locations.append(SM64Location(player, "CCM: 100 Coins", location_table["CCM: 100 Coins"], regCCM))
- world.regions.append(regCCM)
+ create_locs(regCCM, "CCM: 100 Coins")
regBBH = create_region("Big Boo's Haunt", player, world)
- create_default_locs(regBBH, locBBH_table, player)
+ create_locs(regBBH, "BBH: Go on a Ghost Hunt", "BBH: Ride Big Boo's Merry-Go-Round",
+ "BBH: Secret of the Haunted Books", "BBH: Seek the 8 Red Coins")
+ bbh_third_floor = create_subregion(regBBH, "BBH: Third Floor", "BBH: Eye to Eye in the Secret Room")
+ create_subregion(bbh_third_floor, "BBH: Roof", "BBH: Big Boo's Balcony", "BBH: 1Up Block Top of Mansion")
if (world.EnableCoinStars[player].value):
- regBBH.locations.append(SM64Location(player, "BBH: 100 Coins", location_table["BBH: 100 Coins"], regBBH))
- world.regions.append(regBBH)
+ create_locs(regBBH, "BBH: 100 Coins")
regPSS = create_region("The Princess's Secret Slide", player, world)
- create_default_locs(regPSS, locPSS_table, player)
- world.regions.append(regPSS)
+ create_default_locs(regPSS, locPSS_table)
regSA = create_region("The Secret Aquarium", player, world)
- create_default_locs(regSA, locSA_table, player)
- world.regions.append(regSA)
+ create_default_locs(regSA, locSA_table)
regTotWC = create_region("Tower of the Wing Cap", player, world)
- create_default_locs(regTotWC, locTotWC_table, player)
- world.regions.append(regTotWC)
+ create_default_locs(regTotWC, locTotWC_table)
regBitDW = create_region("Bowser in the Dark World", player, world)
- create_default_locs(regBitDW, locBitDW_table, player)
- world.regions.append(regBitDW)
+ create_default_locs(regBitDW, locBitDW_table)
- regBasement = create_region("Basement", player, world)
- world.regions.append(regBasement)
+ create_region("Basement", player, world)
regHMC = create_region("Hazy Maze Cave", player, world)
- create_default_locs(regHMC, locHMC_table, player)
+ create_locs(regHMC, "HMC: Swimming Beast in the Cavern", "HMC: Metal-Head Mario Can Move!",
+ "HMC: Watch for Rolling Rocks", "HMC: Navigating the Toxic Maze","HMC: 1Up Block Past Rolling Rocks")
+ hmc_red_coin_area = create_subregion(regHMC, "HMC: Red Coin Area", "HMC: Elevate for 8 Red Coins")
+ create_subregion(regHMC, "HMC: Pit Islands", "HMC: A-Maze-Ing Emergency Exit", "HMC: 1Up Block above Pit")
if (world.EnableCoinStars[player].value):
- regHMC.locations.append(SM64Location(player, "HMC: 100 Coins", location_table["HMC: 100 Coins"], regHMC))
- world.regions.append(regHMC)
+ create_locs(hmc_red_coin_area, "HMC: 100 Coins")
regLLL = create_region("Lethal Lava Land", player, world)
- create_default_locs(regLLL, locLLL_table, player)
+ create_locs(regLLL, "LLL: Boil the Big Bully", "LLL: Bully the Bullies",
+ "LLL: 8-Coin Puzzle with 15 Pieces", "LLL: Red-Hot Log Rolling")
+ create_subregion(regLLL, "LLL: Upper Volcano", "LLL: Hot-Foot-It into the Volcano", "LLL: Elevator Tour in the Volcano")
if (world.EnableCoinStars[player].value):
- regLLL.locations.append(SM64Location(player, "LLL: 100 Coins", location_table["LLL: 100 Coins"], regLLL))
- world.regions.append(regLLL)
+ create_locs(regLLL, "LLL: 100 Coins")
regSSL = create_region("Shifting Sand Land", player, world)
- create_default_locs(regSSL, locSSL_table, player)
+ create_locs(regSSL, "SSL: In the Talons of the Big Bird", "SSL: Shining Atop the Pyramid", "SSL: Inside the Ancient Pyramid",
+ "SSL: Free Flying for 8 Red Coins", "SSL: Bob-omb Buddy",
+ "SSL: 1Up Block Outside Pyramid", "SSL: 1Up Block Pyramid Left Path", "SSL: 1Up Block Pyramid Back")
+ create_subregion(regSSL, "SSL: Upper Pyramid", "SSL: Stand Tall on the Four Pillars", "SSL: Pyramid Puzzle")
if (world.EnableCoinStars[player].value):
- regSSL.locations.append(SM64Location(player, "SSL: 100 Coins", location_table["SSL: 100 Coins"], regSSL))
- world.regions.append(regSSL)
+ create_locs(regSSL, "SSL: 100 Coins")
regDDD = create_region("Dire, Dire Docks", player, world)
- create_default_locs(regDDD, locDDD_table, player)
+ create_locs(regDDD, "DDD: Board Bowser's Sub", "DDD: Chests in the Current", "DDD: Through the Jet Stream",
+ "DDD: The Manta Ray's Reward", "DDD: Collect the Caps...")
+ ddd_moving_poles = create_subregion(regDDD, "DDD: Moving Poles", "DDD: Pole-Jumping for Red Coins")
if (world.EnableCoinStars[player].value):
- regDDD.locations.append(SM64Location(player, "DDD: 100 Coins", location_table["DDD: 100 Coins"], regDDD))
- world.regions.append(regDDD)
+ create_locs(ddd_moving_poles, "DDD: 100 Coins")
regCotMC = create_region("Cavern of the Metal Cap", player, world)
- create_default_locs(regCotMC, locCotMC_table, player)
- world.regions.append(regCotMC)
+ create_default_locs(regCotMC, locCotMC_table)
regVCutM = create_region("Vanish Cap under the Moat", player, world)
- create_default_locs(regVCutM, locVCutM_table, player)
- world.regions.append(regVCutM)
+ create_default_locs(regVCutM, locVCutM_table)
regBitFS = create_region("Bowser in the Fire Sea", player, world)
- create_default_locs(regBitFS, locBitFS_table, player)
- world.regions.append(regBitFS)
+ create_subregion(regBitFS, "BitFS: Upper", *locBitFS_table.keys())
- regFloor2 = create_region("Second Floor", player, world)
- world.regions.append(regFloor2)
+ create_region("Second Floor", player, world)
regSL = create_region("Snowman's Land", player, world)
- create_default_locs(regSL, locSL_table, player)
+ create_default_locs(regSL, locSL_table)
if (world.EnableCoinStars[player].value):
- regSL.locations.append(SM64Location(player, "SL: 100 Coins", location_table["SL: 100 Coins"], regSL))
- world.regions.append(regSL)
+ create_locs(regSL, "SL: 100 Coins")
regWDW = create_region("Wet-Dry World", player, world)
- create_default_locs(regWDW, locWDW_table, player)
+ create_locs(regWDW, "WDW: Express Elevator--Hurry Up!")
+ wdw_top = create_subregion(regWDW, "WDW: Top", "WDW: Shocking Arrow Lifts!", "WDW: Top o' the Town",
+ "WDW: Secrets in the Shallows & Sky", "WDW: Bob-omb Buddy")
+ create_subregion(regWDW, "WDW: Downtown", "WDW: Go to Town for Red Coins", "WDW: Quick Race Through Downtown!", "WDW: 1Up Block in Downtown")
if (world.EnableCoinStars[player].value):
- regWDW.locations.append(SM64Location(player, "WDW: 100 Coins", location_table["WDW: 100 Coins"], regWDW))
- world.regions.append(regWDW)
+ create_locs(wdw_top, "WDW: 100 Coins")
regTTM = create_region("Tall, Tall Mountain", player, world)
- create_default_locs(regTTM, locTTM_table, player)
+ ttm_middle = create_subregion(regTTM, "TTM: Middle", "TTM: Scary 'Shrooms, Red Coins", "TTM: Blast to the Lonely Mushroom",
+ "TTM: Bob-omb Buddy", "TTM: 1Up Block on Red Mushroom")
+ ttm_top = create_subregion(ttm_middle, "TTM: Top", "TTM: Scale the Mountain", "TTM: Mystery of the Monkey Cage",
+ "TTM: Mysterious Mountainside", "TTM: Breathtaking View from Bridge")
if (world.EnableCoinStars[player].value):
- regTTM.locations.append(SM64Location(player, "TTM: 100 Coins", location_table["TTM: 100 Coins"], regTTM))
- world.regions.append(regTTM)
-
- regTHIT = create_region("Tiny-Huge Island (Tiny)", player, world)
- create_default_locs(regTHIT, locTHI_table, player)
+ create_locs(ttm_top, "TTM: 100 Coins")
+
+ create_region("Tiny-Huge Island (Huge)", player, world)
+ create_region("Tiny-Huge Island (Tiny)", player, world)
+ regTHI = create_region("Tiny-Huge Island", player, world)
+ create_locs(regTHI, "THI: The Tip Top of the Huge Island", "THI: 1Up Block THI Small near Start")
+ thi_pipes = create_subregion(regTHI, "THI: Pipes", "THI: Pluck the Piranha Flower", "THI: Rematch with Koopa the Quick",
+ "THI: Five Itty Bitty Secrets", "THI: Wiggler's Red Coins", "THI: Bob-omb Buddy",
+ "THI: 1Up Block THI Large near Start", "THI: 1Up Block Windy Area")
+ thi_large_top = create_subregion(thi_pipes, "THI: Large Top", "THI: Make Wiggler Squirm")
if (world.EnableCoinStars[player].value):
- regTHIT.locations.append(SM64Location(player, "THI: 100 Coins", location_table["THI: 100 Coins"], regTHIT))
- world.regions.append(regTHIT)
- regTHIH = create_region("Tiny-Huge Island (Huge)", player, world)
- world.regions.append(regTHIH)
+ create_locs(thi_large_top, "THI: 100 Coins")
regFloor3 = create_region("Third Floor", player, world)
- world.regions.append(regFloor3)
regTTC = create_region("Tick Tock Clock", player, world)
- create_default_locs(regTTC, locTTC_table, player)
+ create_locs(regTTC, "TTC: Stop Time for Red Coins")
+ ttc_lower = create_subregion(regTTC, "TTC: Lower", "TTC: Roll into the Cage", "TTC: Get a Hand", "TTC: 1Up Block Midway Up")
+ ttc_upper = create_subregion(ttc_lower, "TTC: Upper", "TTC: Timed Jumps on Moving Bars", "TTC: The Pit and the Pendulums")
+ ttc_top = create_subregion(ttc_upper, "TTC: Top", "TTC: Stomp on the Thwomp", "TTC: 1Up Block at the Top")
if (world.EnableCoinStars[player].value):
- regTTC.locations.append(SM64Location(player, "TTC: 100 Coins", location_table["TTC: 100 Coins"], regTTC))
- world.regions.append(regTTC)
+ create_locs(ttc_top, "TTC: 100 Coins")
regRR = create_region("Rainbow Ride", player, world)
- create_default_locs(regRR, locRR_table, player)
+ create_locs(regRR, "RR: Swingin' in the Breeze", "RR: Tricky Triangles!",
+ "RR: 1Up Block Top of Red Coin Maze", "RR: 1Up Block Under Fly Guy", "RR: Bob-omb Buddy")
+ rr_maze = create_subregion(regRR, "RR: Maze", "RR: Coins Amassed in a Maze")
+ create_subregion(regRR, "RR: Cruiser", "RR: Cruiser Crossing the Rainbow", "RR: Somewhere Over the Rainbow")
+ create_subregion(regRR, "RR: House", "RR: The Big House in the Sky", "RR: 1Up Block On House in the Sky")
if (world.EnableCoinStars[player].value):
- regRR.locations.append(SM64Location(player, "RR: 100 Coins", location_table["RR: 100 Coins"], regRR))
- world.regions.append(regRR)
+ create_locs(rr_maze, "RR: 100 Coins")
regWMotR = create_region("Wing Mario over the Rainbow", player, world)
- create_default_locs(regWMotR, locWMotR_table, player)
- world.regions.append(regWMotR)
+ create_default_locs(regWMotR, locWMotR_table)
regBitS = create_region("Bowser in the Sky", player, world)
- create_default_locs(regBitS, locBitS_table, player)
- world.regions.append(regBitS)
+ create_locs(regBitS, "Bowser in the Sky 1Up Block")
+ create_subregion(regBitS, "BitS: Top", "Bowser in the Sky Red Coins")
def connect_regions(world: MultiWorld, player: int, source: str, target: str, rule=None):
sourceRegion = world.get_region(source, player)
targetRegion = world.get_region(target, player)
+ sourceRegion.connect(targetRegion, rule=rule)
- connection = Entrance(player, '', sourceRegion)
- if rule:
- connection.access_rule = rule
-
- sourceRegion.exits.append(connection)
- connection.connect(targetRegion)
def create_region(name: str, player: int, world: MultiWorld) -> Region:
- return Region(name, player, world)
+ region = Region(name, player, world)
+ world.regions.append(region)
+ return region
+
+
+def create_subregion(source_region: Region, name: str, *locs: str) -> Region:
+ region = Region(name, source_region.player, source_region.multiworld)
+ connection = Entrance(source_region.player, name, source_region)
+ source_region.exits.append(connection)
+ connection.connect(region)
+ source_region.multiworld.regions.append(region)
+ create_locs(region, *locs)
+ return region
+
+
+def set_subregion_access_rule(world, player, region_name: str, rule):
+ world.get_entrance(world, player, region_name).access_rule = rule
+
+
+def create_default_locs(reg: Region, default_locs: dict):
+ create_locs(reg, *default_locs.keys())
+
-def create_default_locs(reg: Region, locs, player):
- reg_names = [name for name, id in locs.items()]
- reg.locations += [SM64Location(player, loc_name, location_table[loc_name], reg) for loc_name in locs]
+def create_locs(reg: Region, *locs: str):
+ reg.locations += [SM64Location(reg.player, loc_name, location_table[loc_name], reg) for loc_name in locs]
diff --git a/worlds/sm64ex/Rules.py b/worlds/sm64ex/Rules.py
index fedd5b7a6ebd..f2b8e0bcdf2d 100644
--- a/worlds/sm64ex/Rules.py
+++ b/worlds/sm64ex/Rules.py
@@ -1,36 +1,59 @@
-from ..generic.Rules import add_rule
-from .Regions import connect_regions, SM64Levels, sm64_level_to_paintings, sm64_paintings_to_level, sm64_level_to_secrets, sm64_secrets_to_level, sm64_entrances_to_level, sm64_level_to_entrances
+from typing import Callable, Union, Dict, Set
-def shuffle_dict_keys(world, obj: dict) -> dict:
- keys = list(obj.keys())
- values = list(obj.values())
+from BaseClasses import MultiWorld
+from ..generic.Rules import add_rule, set_rule
+from .Locations import location_table
+from .Regions import connect_regions, SM64Levels, sm64_level_to_paintings, sm64_paintings_to_level,\
+sm64_level_to_secrets, sm64_secrets_to_level, sm64_entrances_to_level, sm64_level_to_entrances
+from .Items import action_item_table
+
+def shuffle_dict_keys(world, dictionary: dict) -> dict:
+ keys = list(dictionary.keys())
+ values = list(dictionary.values())
world.random.shuffle(keys)
- return dict(zip(keys,values))
+ return dict(zip(keys, values))
-def fix_reg(entrance_map: dict, entrance: SM64Levels, invalid_regions: set,
- swapdict: dict, world):
+def fix_reg(entrance_map: Dict[SM64Levels, str], entrance: SM64Levels, invalid_regions: Set[str],
+ swapdict: Dict[SM64Levels, str], world):
if entrance_map[entrance] in invalid_regions: # Unlucky :C
- replacement_regions = [(rand_region, rand_entrance) for rand_region, rand_entrance in swapdict.items()
+ replacement_regions = [(rand_entrance, rand_region) for rand_entrance, rand_region in swapdict.items()
if rand_region not in invalid_regions]
- rand_region, rand_entrance = world.random.choice(replacement_regions)
+ rand_entrance, rand_region = world.random.choice(replacement_regions)
old_dest = entrance_map[entrance]
entrance_map[entrance], entrance_map[rand_entrance] = rand_region, old_dest
- swapdict[rand_region] = entrance
- swapdict.pop(entrance_map[entrance]) # Entrance now fixed to rand_region
+ swapdict[entrance], swapdict[rand_entrance] = rand_region, old_dest
+ swapdict.pop(entrance)
-def set_rules(world, player: int, area_connections: dict):
+def set_rules(world, player: int, area_connections: dict, star_costs: dict, move_rando_bitvec: int):
randomized_level_to_paintings = sm64_level_to_paintings.copy()
randomized_level_to_secrets = sm64_level_to_secrets.copy()
+ valid_move_randomizer_start_courses = [
+ "Bob-omb Battlefield", "Jolly Roger Bay", "Cool, Cool Mountain",
+ "Big Boo's Haunt", "Lethal Lava Land", "Shifting Sand Land",
+ "Dire, Dire Docks", "Snowman's Land"
+ ] # Excluding WF, HMC, WDW, TTM, THI, TTC, and RR
if world.AreaRandomizer[player].value >= 1: # Some randomization is happening, randomize Courses
randomized_level_to_paintings = shuffle_dict_keys(world,sm64_level_to_paintings)
+ # If not shuffling later, ensure a valid start course on move randomizer
+ if world.AreaRandomizer[player].value < 3 and move_rando_bitvec > 0:
+ swapdict = randomized_level_to_paintings.copy()
+ invalid_start_courses = {course for course in randomized_level_to_paintings.values() if course not in valid_move_randomizer_start_courses}
+ fix_reg(randomized_level_to_paintings, SM64Levels.BOB_OMB_BATTLEFIELD, invalid_start_courses, swapdict, world)
+ fix_reg(randomized_level_to_paintings, SM64Levels.WHOMPS_FORTRESS, invalid_start_courses, swapdict, world)
+
if world.AreaRandomizer[player].value == 2: # Randomize Secrets as well
randomized_level_to_secrets = shuffle_dict_keys(world,sm64_level_to_secrets)
- randomized_entrances = { **randomized_level_to_paintings, **randomized_level_to_secrets }
+ randomized_entrances = {**randomized_level_to_paintings, **randomized_level_to_secrets}
if world.AreaRandomizer[player].value == 3: # Randomize Courses and Secrets in one pool
- randomized_entrances = shuffle_dict_keys(world,randomized_entrances)
- swapdict = { entrance: level for (level,entrance) in randomized_entrances.items() }
+ randomized_entrances = shuffle_dict_keys(world, randomized_entrances)
# Guarantee first entrance is a course
- fix_reg(randomized_entrances, SM64Levels.BOB_OMB_BATTLEFIELD, sm64_secrets_to_level.keys(), swapdict, world)
+ swapdict = randomized_entrances.copy()
+ if move_rando_bitvec == 0:
+ fix_reg(randomized_entrances, SM64Levels.BOB_OMB_BATTLEFIELD, sm64_secrets_to_level.keys(), swapdict, world)
+ else:
+ invalid_start_courses = {course for course in randomized_entrances.values() if course not in valid_move_randomizer_start_courses}
+ fix_reg(randomized_entrances, SM64Levels.BOB_OMB_BATTLEFIELD, invalid_start_courses, swapdict, world)
+ fix_reg(randomized_entrances, SM64Levels.WHOMPS_FORTRESS, invalid_start_courses, swapdict, world)
# Guarantee BITFS is not mapped to DDD
fix_reg(randomized_entrances, SM64Levels.BOWSER_IN_THE_FIRE_SEA, {"Dire, Dire Docks"}, swapdict, world)
# Guarantee COTMC is not mapped to HMC, cuz thats impossible. If BitFS -> HMC, also no COTMC -> DDD.
@@ -43,27 +66,34 @@ def set_rules(world, player: int, area_connections: dict):
# Cast to int to not rely on availability of SM64Levels enum. Will cause crash in MultiServer otherwise
area_connections.update({int(entrance_lvl): int(sm64_entrances_to_level[destination]) for (entrance_lvl,destination) in randomized_entrances.items()})
randomized_entrances_s = {sm64_level_to_entrances[entrance_lvl]: destination for (entrance_lvl,destination) in randomized_entrances.items()}
-
+
+ rf = RuleFactory(world, player, move_rando_bitvec)
+
connect_regions(world, player, "Menu", randomized_entrances_s["Bob-omb Battlefield"])
connect_regions(world, player, "Menu", randomized_entrances_s["Whomp's Fortress"], lambda state: state.has("Power Star", player, 1))
connect_regions(world, player, "Menu", randomized_entrances_s["Jolly Roger Bay"], lambda state: state.has("Power Star", player, 3))
connect_regions(world, player, "Menu", randomized_entrances_s["Cool, Cool Mountain"], lambda state: state.has("Power Star", player, 3))
connect_regions(world, player, "Menu", randomized_entrances_s["Big Boo's Haunt"], lambda state: state.has("Power Star", player, 12))
connect_regions(world, player, "Menu", randomized_entrances_s["The Princess's Secret Slide"], lambda state: state.has("Power Star", player, 1))
- connect_regions(world, player, "Menu", randomized_entrances_s["The Secret Aquarium"], lambda state: state.has("Power Star", player, 3))
+ connect_regions(world, player, randomized_entrances_s["Jolly Roger Bay"], randomized_entrances_s["The Secret Aquarium"],
+ rf.build_rule("SF/BF | TJ & LG | MOVELESS & TJ"))
connect_regions(world, player, "Menu", randomized_entrances_s["Tower of the Wing Cap"], lambda state: state.has("Power Star", player, 10))
- connect_regions(world, player, "Menu", randomized_entrances_s["Bowser in the Dark World"], lambda state: state.has("Power Star", player, world.FirstBowserStarDoorCost[player].value))
+ connect_regions(world, player, "Menu", randomized_entrances_s["Bowser in the Dark World"],
+ lambda state: state.has("Power Star", player, star_costs["FirstBowserDoorCost"]))
connect_regions(world, player, "Menu", "Basement", lambda state: state.has("Basement Key", player) or state.has("Progressive Key", player, 1))
connect_regions(world, player, "Basement", randomized_entrances_s["Hazy Maze Cave"])
connect_regions(world, player, "Basement", randomized_entrances_s["Lethal Lava Land"])
connect_regions(world, player, "Basement", randomized_entrances_s["Shifting Sand Land"])
- connect_regions(world, player, "Basement", randomized_entrances_s["Dire, Dire Docks"], lambda state: state.has("Power Star", player, world.BasementStarDoorCost[player].value))
+ connect_regions(world, player, "Basement", randomized_entrances_s["Dire, Dire Docks"],
+ lambda state: state.has("Power Star", player, star_costs["BasementDoorCost"]))
connect_regions(world, player, "Hazy Maze Cave", randomized_entrances_s["Cavern of the Metal Cap"])
- connect_regions(world, player, "Basement", randomized_entrances_s["Vanish Cap under the Moat"])
- connect_regions(world, player, "Basement", randomized_entrances_s["Bowser in the Fire Sea"], lambda state: state.has("Power Star", player, world.BasementStarDoorCost[player].value) and
- state.can_reach("DDD: Board Bowser's Sub", 'Location', player))
+ connect_regions(world, player, "Basement", randomized_entrances_s["Vanish Cap under the Moat"],
+ rf.build_rule("GP"))
+ connect_regions(world, player, "Basement", randomized_entrances_s["Bowser in the Fire Sea"],
+ lambda state: state.has("Power Star", player, star_costs["BasementDoorCost"]) and
+ state.can_reach("DDD: Board Bowser's Sub", 'Location', player))
connect_regions(world, player, "Menu", "Second Floor", lambda state: state.has("Second Floor Key", player) or state.has("Progressive Key", player, 2))
@@ -72,66 +102,127 @@ def set_rules(world, player: int, area_connections: dict):
connect_regions(world, player, "Second Floor", randomized_entrances_s["Tall, Tall Mountain"])
connect_regions(world, player, "Second Floor", randomized_entrances_s["Tiny-Huge Island (Tiny)"])
connect_regions(world, player, "Second Floor", randomized_entrances_s["Tiny-Huge Island (Huge)"])
- connect_regions(world, player, "Tiny-Huge Island (Tiny)", "Tiny-Huge Island (Huge)")
- connect_regions(world, player, "Tiny-Huge Island (Huge)", "Tiny-Huge Island (Tiny)")
+ connect_regions(world, player, "Tiny-Huge Island (Tiny)", "Tiny-Huge Island")
+ connect_regions(world, player, "Tiny-Huge Island (Huge)", "Tiny-Huge Island")
- connect_regions(world, player, "Second Floor", "Third Floor", lambda state: state.has("Power Star", player, world.SecondFloorStarDoorCost[player].value))
+ connect_regions(world, player, "Second Floor", "Third Floor", lambda state: state.has("Power Star", player, star_costs["SecondFloorDoorCost"]))
connect_regions(world, player, "Third Floor", randomized_entrances_s["Tick Tock Clock"])
connect_regions(world, player, "Third Floor", randomized_entrances_s["Rainbow Ride"])
connect_regions(world, player, "Third Floor", randomized_entrances_s["Wing Mario over the Rainbow"])
- connect_regions(world, player, "Third Floor", "Bowser in the Sky", lambda state: state.has("Power Star", player, world.StarsToFinish[player].value))
+ connect_regions(world, player, "Third Floor", "Bowser in the Sky", lambda state: state.has("Power Star", player, star_costs["StarsToFinish"]))
- #Special Rules for some Locations
- add_rule(world.get_location("BoB: Mario Wings to the Sky", player), lambda state: state.has("Cannon Unlock BoB", player))
- add_rule(world.get_location("BBH: Eye to Eye in the Secret Room", player), lambda state: state.has("Vanish Cap", player))
- add_rule(world.get_location("DDD: Collect the Caps...", player), lambda state: state.has("Vanish Cap", player))
- add_rule(world.get_location("DDD: Pole-Jumping for Red Coins", player), lambda state: state.can_reach("Bowser in the Fire Sea", 'Region', player))
+ # Course Rules
+ # Bob-omb Battlefield
+ rf.assign_rule("BoB: Island", "CANN | CANNLESS & WC & TJ | CAPLESS & CANNLESS & LJ")
+ rf.assign_rule("BoB: Mario Wings to the Sky", "CANN & WC | CAPLESS & CANN")
+ rf.assign_rule("BoB: Behind Chain Chomp's Gate", "GP | MOVELESS")
+ # Whomp's Fortress
+ rf.assign_rule("WF: Tower", "{{WF: Chip Off Whomp's Block}}")
+ rf.assign_rule("WF: Chip Off Whomp's Block", "GP")
+ rf.assign_rule("WF: Shoot into the Wild Blue", "WK & TJ/SF | CANN")
+ rf.assign_rule("WF: Fall onto the Caged Island", "CL & {WF: Tower} | MOVELESS & TJ | MOVELESS & LJ | MOVELESS & CANN")
+ rf.assign_rule("WF: Blast Away the Wall", "CANN | CANNLESS & LG")
+ # Jolly Roger Bay
+ rf.assign_rule("JRB: Upper", "TJ/BF/SF/WK | MOVELESS & LG")
+ rf.assign_rule("JRB: Red Coins on the Ship Afloat", "CL/CANN/TJ/BF/WK")
+ rf.assign_rule("JRB: Blast to the Stone Pillar", "CANN+CL | CANNLESS & MOVELESS | CANN & MOVELESS")
+ rf.assign_rule("JRB: Through the Jet Stream", "MC | CAPLESS")
+ # Cool, Cool Mountain
+ rf.assign_rule("CCM: Wall Kicks Will Work", "TJ/WK & CANN | CANNLESS & TJ/WK | MOVELESS")
+ # Big Boo's Haunt
+ rf.assign_rule("BBH: Third Floor", "WK+LG | MOVELESS & WK")
+ rf.assign_rule("BBH: Roof", "LJ | MOVELESS")
+ rf.assign_rule("BBH: Secret of the Haunted Books", "KK | MOVELESS")
+ rf.assign_rule("BBH: Seek the 8 Red Coins", "BF/WK/TJ/SF")
+ rf.assign_rule("BBH: Eye to Eye in the Secret Room", "VC")
+ # Haze Maze Cave
+ rf.assign_rule("HMC: Red Coin Area", "CL & WK/LG/BF/SF/TJ | MOVELESS & WK")
+ rf.assign_rule("HMC: Pit Islands", "TJ+CL | MOVELESS & WK & TJ/LJ | MOVELESS & WK+SF+LG")
+ rf.assign_rule("HMC: Metal-Head Mario Can Move!", "LJ+MC | CAPLESS & LJ+TJ | CAPLESS & MOVELESS & LJ/TJ/WK")
+ rf.assign_rule("HMC: Navigating the Toxic Maze", "WK/SF/BF/TJ")
+ rf.assign_rule("HMC: Watch for Rolling Rocks", "WK")
+ # Lethal Lava Land
+ rf.assign_rule("LLL: Upper Volcano", "CL")
+ # Shifting Sand Land
+ rf.assign_rule("SSL: Upper Pyramid", "CL & TJ/BF/SF/LG | MOVELESS")
+ rf.assign_rule("SSL: Free Flying for 8 Red Coins", "TJ/SF/BF & TJ+WC | TJ/SF/BF & CAPLESS | MOVELESS")
+ # Dire, Dire Docks
+ rf.assign_rule("DDD: Moving Poles", "CL & {{Bowser in the Fire Sea Key}} | TJ+DV+LG+WK & MOVELESS")
+ rf.assign_rule("DDD: Through the Jet Stream", "MC | CAPLESS")
+ rf.assign_rule("DDD: Collect the Caps...", "VC+MC | CAPLESS & VC")
+ # Snowman's Land
+ rf.assign_rule("SL: Snowman's Big Head", "BF/SF/CANN/TJ")
+ rf.assign_rule("SL: In the Deep Freeze", "WK/SF/LG/BF/CANN/TJ")
+ rf.assign_rule("SL: Into the Igloo", "VC & TJ/SF/BF/WK/LG | MOVELESS & VC")
+ # Wet-Dry World
+ rf.assign_rule("WDW: Top", "WK/TJ/SF/BF | MOVELESS")
+ rf.assign_rule("WDW: Downtown", "NAR & LG & TJ/SF/BF | CANN | MOVELESS & TJ+DV")
+ rf.assign_rule("WDW: Go to Town for Red Coins", "WK | MOVELESS & TJ")
+ rf.assign_rule("WDW: Quick Race Through Downtown!", "VC & WK/BF | VC & TJ+LG | MOVELESS & VC & TJ")
+ rf.assign_rule("WDW: Bob-omb Buddy", "TJ | SF+LG | NAR & BF/SF")
+ # Tall, Tall Mountain
+ rf.assign_rule("TTM: Top", "MOVELESS & TJ | LJ/DV & LG/KK | MOVELESS & WK & SF/LG | MOVELESS & KK/DV")
+ rf.assign_rule("TTM: Blast to the Lonely Mushroom", "CANN | CANNLESS & LJ | MOVELESS & CANNLESS")
+ # Tiny-Huge Island
+ rf.assign_rule("THI: Pipes", "NAR | LJ/TJ/DV/LG | MOVELESS & BF/SF/KK")
+ rf.assign_rule("THI: Large Top", "NAR | LJ/TJ/DV | MOVELESS")
+ rf.assign_rule("THI: Wiggler's Red Coins", "WK")
+ rf.assign_rule("THI: Make Wiggler Squirm", "GP | MOVELESS & DV")
+ # Tick Tock Clock
+ rf.assign_rule("TTC: Lower", "LG/TJ/SF/BF/WK")
+ rf.assign_rule("TTC: Upper", "CL | SF+WK")
+ rf.assign_rule("TTC: Top", "CL | SF+WK")
+ rf.assign_rule("TTC: Stomp on the Thwomp", "LG & TJ/SF/BF")
+ rf.assign_rule("TTC: Stop Time for Red Coins", "NAR | {TTC: Lower}")
+ # Rainbow Ride
+ rf.assign_rule("RR: Maze", "WK | LJ & SF/BF/TJ | MOVELESS & LG/TJ")
+ rf.assign_rule("RR: Bob-omb Buddy", "WK | MOVELESS & LG")
+ rf.assign_rule("RR: Swingin' in the Breeze", "LG/TJ/BF/SF")
+ rf.assign_rule("RR: Tricky Triangles!", "LG/TJ/BF/SF")
+ rf.assign_rule("RR: Cruiser", "WK/SF/BF/LG/TJ")
+ rf.assign_rule("RR: House", "TJ/SF/BF/LG")
+ rf.assign_rule("RR: Somewhere Over the Rainbow", "CANN")
+ # Cavern of the Metal Cap
+ rf.assign_rule("Cavern of the Metal Cap Red Coins", "MC | CAPLESS")
+ # Vanish Cap Under the Moat
+ rf.assign_rule("Vanish Cap Under the Moat Switch", "WK/TJ/BF/SF/LG | MOVELESS")
+ rf.assign_rule("Vanish Cap Under the Moat Red Coins", "TJ/BF/SF/LG/WK & VC | CAPLESS & WK")
+ # Bowser in the Fire Sea
+ rf.assign_rule("BitFS: Upper", "CL")
+ rf.assign_rule("Bowser in the Fire Sea Red Coins", "LG/WK")
+ rf.assign_rule("Bowser in the Fire Sea 1Up Block Near Poles", "LG/WK")
+ # Wing Mario Over the Rainbow
+ rf.assign_rule("Wing Mario Over the Rainbow Red Coins", "TJ+WC")
+ rf.assign_rule("Wing Mario Over the Rainbow 1Up Block", "TJ+WC")
+ # Bowser in the Sky
+ rf.assign_rule("BitS: Top", "CL+TJ | CL+SF+LG | MOVELESS & TJ+WK+LG")
+ # 100 Coin Stars
if world.EnableCoinStars[player]:
- add_rule(world.get_location("DDD: 100 Coins", player), lambda state: state.can_reach("Bowser in the Fire Sea", 'Region', player))
- add_rule(world.get_location("SL: Into the Igloo", player), lambda state: state.has("Vanish Cap", player))
- add_rule(world.get_location("WDW: Quick Race Through Downtown!", player), lambda state: state.has("Vanish Cap", player))
- add_rule(world.get_location("RR: Somewhere Over the Rainbow", player), lambda state: state.has("Cannon Unlock RR", player))
-
- if world.AreaRandomizer[player] or world.StrictCannonRequirements[player]:
- # If area rando is on, it may not be possible to modify WDW's starting water level,
- # which would make it impossible to reach downtown area without the cannon.
- add_rule(world.get_location("WDW: Quick Race Through Downtown!", player), lambda state: state.has("Cannon Unlock WDW", player))
- add_rule(world.get_location("WDW: Go to Town for Red Coins", player), lambda state: state.has("Cannon Unlock WDW", player))
- add_rule(world.get_location("WDW: 1Up Block in Downtown", player), lambda state: state.has("Cannon Unlock WDW", player))
-
- if world.StrictCapRequirements[player]:
- add_rule(world.get_location("BoB: Mario Wings to the Sky", player), lambda state: state.has("Wing Cap", player))
- add_rule(world.get_location("HMC: Metal-Head Mario Can Move!", player), lambda state: state.has("Metal Cap", player))
- add_rule(world.get_location("JRB: Through the Jet Stream", player), lambda state: state.has("Metal Cap", player))
- add_rule(world.get_location("SSL: Free Flying for 8 Red Coins", player), lambda state: state.has("Wing Cap", player))
- add_rule(world.get_location("DDD: Through the Jet Stream", player), lambda state: state.has("Metal Cap", player))
- add_rule(world.get_location("DDD: Collect the Caps...", player), lambda state: state.has("Metal Cap", player))
- add_rule(world.get_location("Vanish Cap Under the Moat Red Coins", player), lambda state: state.has("Vanish Cap", player))
- add_rule(world.get_location("Cavern of the Metal Cap Red Coins", player), lambda state: state.has("Metal Cap", player))
- if world.StrictCannonRequirements[player]:
- add_rule(world.get_location("WF: Blast Away the Wall", player), lambda state: state.has("Cannon Unlock WF", player))
- add_rule(world.get_location("JRB: Blast to the Stone Pillar", player), lambda state: state.has("Cannon Unlock JRB", player))
- add_rule(world.get_location("CCM: Wall Kicks Will Work", player), lambda state: state.has("Cannon Unlock CCM", player))
- add_rule(world.get_location("TTM: Blast to the Lonely Mushroom", player), lambda state: state.has("Cannon Unlock TTM", player))
- if world.StrictCapRequirements[player] and world.StrictCannonRequirements[player]:
- # Ability to reach the floating island. Need some of those coins to get 100 coin star as well.
- add_rule(world.get_location("BoB: Find the 8 Red Coins", player), lambda state: state.has("Cannon Unlock BoB", player) or state.has("Wing Cap", player))
- add_rule(world.get_location("BoB: Shoot to the Island in the Sky", player), lambda state: state.has("Cannon Unlock BoB", player) or state.has("Wing Cap", player))
- if world.EnableCoinStars[player]:
- add_rule(world.get_location("BoB: 100 Coins", player), lambda state: state.has("Cannon Unlock BoB", player) or state.has("Wing Cap", player))
-
- #Rules for Secret Stars
- add_rule(world.get_location("Wing Mario Over the Rainbow Red Coins", player), lambda state: state.has("Wing Cap", player))
- add_rule(world.get_location("Wing Mario Over the Rainbow 1Up Block", player), lambda state: state.has("Wing Cap", player))
+ rf.assign_rule("BoB: 100 Coins", "CANN & WC | CANNLESS & WC & TJ")
+ rf.assign_rule("WF: 100 Coins", "GP | MOVELESS")
+ rf.assign_rule("JRB: 100 Coins", "GP & {JRB: Upper}")
+ rf.assign_rule("HMC: 100 Coins", "GP")
+ rf.assign_rule("SSL: 100 Coins", "{SSL: Upper Pyramid} | GP")
+ rf.assign_rule("DDD: 100 Coins", "GP")
+ rf.assign_rule("SL: 100 Coins", "VC | MOVELESS")
+ rf.assign_rule("WDW: 100 Coins", "GP | {WDW: Downtown}")
+ rf.assign_rule("TTC: 100 Coins", "GP")
+ rf.assign_rule("THI: 100 Coins", "GP")
+ rf.assign_rule("RR: 100 Coins", "GP & WK")
+ # Castle Stars
add_rule(world.get_location("Toad (Basement)", player), lambda state: state.can_reach("Basement", 'Region', player) and state.has("Power Star", player, 12))
add_rule(world.get_location("Toad (Second Floor)", player), lambda state: state.can_reach("Second Floor", 'Region', player) and state.has("Power Star", player, 25))
add_rule(world.get_location("Toad (Third Floor)", player), lambda state: state.can_reach("Third Floor", 'Region', player) and state.has("Power Star", player, 35))
- if world.MIPS1Cost[player].value > world.MIPS2Cost[player].value:
- (world.MIPS2Cost[player].value, world.MIPS1Cost[player].value) = (world.MIPS1Cost[player].value, world.MIPS2Cost[player].value)
- add_rule(world.get_location("MIPS 1", player), lambda state: state.can_reach("Basement", 'Region', player) and state.has("Power Star", player, world.MIPS1Cost[player].value))
- add_rule(world.get_location("MIPS 2", player), lambda state: state.can_reach("Basement", 'Region', player) and state.has("Power Star", player, world.MIPS2Cost[player].value))
+ if star_costs["MIPS1Cost"] > star_costs["MIPS2Cost"]:
+ (star_costs["MIPS2Cost"], star_costs["MIPS1Cost"]) = (star_costs["MIPS1Cost"], star_costs["MIPS2Cost"])
+ rf.assign_rule("MIPS 1", "DV | MOVELESS")
+ rf.assign_rule("MIPS 2", "DV | MOVELESS")
+ add_rule(world.get_location("MIPS 1", player), lambda state: state.can_reach("Basement", 'Region', player) and state.has("Power Star", player, star_costs["MIPS1Cost"]))
+ add_rule(world.get_location("MIPS 2", player), lambda state: state.can_reach("Basement", 'Region', player) and state.has("Power Star", player, star_costs["MIPS2Cost"]))
+
+ world.completion_condition[player] = lambda state: state.can_reach("BitS: Top", 'Region', player)
if world.CompletionType[player] == "last_bowser_stage":
world.completion_condition[player] = lambda state: state.can_reach("Bowser in the Sky", 'Region', player)
@@ -139,3 +230,145 @@ def set_rules(world, player: int, area_connections: dict):
world.completion_condition[player] = lambda state: state.can_reach("Bowser in the Dark World", 'Region', player) and \
state.can_reach("Bowser in the Fire Sea", 'Region', player) and \
state.can_reach("Bowser in the Sky", 'Region', player)
+
+
+class RuleFactory:
+
+ world: MultiWorld
+ player: int
+ move_rando_bitvec: bool
+ area_randomizer: bool
+ capless: bool
+ cannonless: bool
+ moveless: bool
+
+ token_table = {
+ "TJ": "Triple Jump",
+ "LJ": "Long Jump",
+ "BF": "Backflip",
+ "SF": "Side Flip",
+ "WK": "Wall Kick",
+ "DV": "Dive",
+ "GP": "Ground Pound",
+ "KK": "Kick",
+ "CL": "Climb",
+ "LG": "Ledge Grab",
+ "WC": "Wing Cap",
+ "MC": "Metal Cap",
+ "VC": "Vanish Cap"
+ }
+
+ class SM64LogicException(Exception):
+ pass
+
+ def __init__(self, world, player, move_rando_bitvec):
+ self.world = world
+ self.player = player
+ self.move_rando_bitvec = move_rando_bitvec
+ self.area_randomizer = world.AreaRandomizer[player].value > 0
+ self.capless = not world.StrictCapRequirements[player]
+ self.cannonless = not world.StrictCannonRequirements[player]
+ self.moveless = not world.StrictMoveRequirements[player] or not move_rando_bitvec > 0
+
+ def assign_rule(self, target_name: str, rule_expr: str):
+ target = self.world.get_location(target_name, self.player) if target_name in location_table else self.world.get_entrance(target_name, self.player)
+ cannon_name = "Cannon Unlock " + target_name.split(':')[0]
+ try:
+ rule = self.build_rule(rule_expr, cannon_name)
+ except RuleFactory.SM64LogicException as exception:
+ raise RuleFactory.SM64LogicException(
+ f"Error generating rule for {target_name} using rule expression {rule_expr}: {exception}")
+ if rule:
+ set_rule(target, rule)
+
+ def build_rule(self, rule_expr: str, cannon_name: str = '') -> Callable:
+ expressions = rule_expr.split(" | ")
+ rules = []
+ for expression in expressions:
+ or_clause = self.combine_and_clauses(expression, cannon_name)
+ if or_clause is True:
+ return None
+ if or_clause is not False:
+ rules.append(or_clause)
+ if rules:
+ if len(rules) == 1:
+ return rules[0]
+ else:
+ return lambda state: any(rule(state) for rule in rules)
+ else:
+ return None
+
+ def combine_and_clauses(self, rule_expr: str, cannon_name: str) -> Union[Callable, bool]:
+ expressions = rule_expr.split(" & ")
+ rules = []
+ for expression in expressions:
+ and_clause = self.make_lambda(expression, cannon_name)
+ if and_clause is False:
+ return False
+ if and_clause is not True:
+ rules.append(and_clause)
+ if rules:
+ if len(rules) == 1:
+ return rules[0]
+ return lambda state: all(rule(state) for rule in rules)
+ else:
+ return True
+
+ def make_lambda(self, expression: str, cannon_name: str) -> Union[Callable, bool]:
+ if '+' in expression:
+ tokens = expression.split('+')
+ items = set()
+ for token in tokens:
+ item = self.parse_token(token, cannon_name)
+ if item is True:
+ continue
+ if item is False:
+ return False
+ items.add(item)
+ if items:
+ return lambda state: state.has_all(items, self.player)
+ else:
+ return True
+ if '/' in expression:
+ tokens = expression.split('/')
+ items = set()
+ for token in tokens:
+ item = self.parse_token(token, cannon_name)
+ if item is True:
+ return True
+ if item is False:
+ continue
+ items.add(item)
+ if items:
+ return lambda state: state.has_any(items, self.player)
+ else:
+ return False
+ if '{{' in expression:
+ return lambda state: state.can_reach(expression[2:-2], "Location", self.player)
+ if '{' in expression:
+ return lambda state: state.can_reach(expression[1:-1], "Region", self.player)
+ item = self.parse_token(expression, cannon_name)
+ if item in (True, False):
+ return item
+ return lambda state: state.has(item, self.player)
+
+ def parse_token(self, token: str, cannon_name: str) -> Union[str, bool]:
+ if token == "CANN":
+ return cannon_name
+ if token == "CAPLESS":
+ return self.capless
+ if token == "CANNLESS":
+ return self.cannonless
+ if token == "MOVELESS":
+ return self.moveless
+ if token == "NAR":
+ return not self.area_randomizer
+ item = self.token_table.get(token, None)
+ if not item:
+ raise Exception(f"Invalid token: '{item}'")
+ if item in action_item_table:
+ if self.move_rando_bitvec & (1 << (action_item_table[item] - action_item_table['Double Jump'])) == 0:
+ # This action item is not randomized.
+ return True
+ return item
+
diff --git a/worlds/sm64ex/__init__.py b/worlds/sm64ex/__init__.py
index ab7409a324c3..e54a4b7a9103 100644
--- a/worlds/sm64ex/__init__.py
+++ b/worlds/sm64ex/__init__.py
@@ -1,7 +1,7 @@
import typing
import os
import json
-from .Items import item_table, cannon_item_table, SM64Item
+from .Items import item_table, action_item_table, cannon_item_table, SM64Item
from .Locations import location_table, SM64Location
from .Options import sm64_options
from .Rules import set_rules
@@ -35,14 +35,44 @@ class SM64World(World):
item_name_to_id = item_table
location_name_to_id = location_table
- data_version = 8
+ data_version = 9
required_client_version = (0, 3, 5)
area_connections: typing.Dict[int, int]
option_definitions = sm64_options
+ number_of_stars: int
+ move_rando_bitvec: int
+ filler_count: int
+ star_costs: typing.Dict[str, int]
+
def generate_early(self):
+ max_stars = 120
+ if (not self.multiworld.EnableCoinStars[self.player].value):
+ max_stars -= 15
+ self.move_rando_bitvec = 0
+ for action, itemid in action_item_table.items():
+ # HACK: Disable randomization of double jump
+ if action == 'Double Jump': continue
+ if getattr(self.multiworld, f"MoveRandomizer{action.replace(' ','')}")[self.player].value:
+ max_stars -= 1
+ self.move_rando_bitvec |= (1 << (itemid - action_item_table['Double Jump']))
+ if (self.multiworld.ExclamationBoxes[self.player].value > 0):
+ max_stars += 29
+ self.number_of_stars = min(self.multiworld.AmountOfStars[self.player].value, max_stars)
+ self.filler_count = max_stars - self.number_of_stars
+ self.star_costs = {
+ 'FirstBowserDoorCost': round(self.multiworld.FirstBowserStarDoorCost[self.player].value * self.number_of_stars / 100),
+ 'BasementDoorCost': round(self.multiworld.BasementStarDoorCost[self.player].value * self.number_of_stars / 100),
+ 'SecondFloorDoorCost': round(self.multiworld.SecondFloorStarDoorCost[self.player].value * self.number_of_stars / 100),
+ 'MIPS1Cost': round(self.multiworld.MIPS1Cost[self.player].value * self.number_of_stars / 100),
+ 'MIPS2Cost': round(self.multiworld.MIPS2Cost[self.player].value * self.number_of_stars / 100),
+ 'StarsToFinish': round(self.multiworld.StarsToFinish[self.player].value * self.number_of_stars / 100)
+ }
+ # Nudge MIPS 1 to match vanilla on default percentage
+ if self.number_of_stars == 120 and self.multiworld.MIPS1Cost[self.player].value == 12:
+ self.star_costs['MIPS1Cost'] = 15
self.topology_present = self.multiworld.AreaRandomizer[self.player].value
def create_regions(self):
@@ -50,7 +80,7 @@ def create_regions(self):
def set_rules(self):
self.area_connections = {}
- set_rules(self.multiworld, self.player, self.area_connections)
+ set_rules(self.multiworld, self.player, self.area_connections, self.star_costs, self.move_rando_bitvec)
if self.topology_present:
# Write area_connections to spoiler log
for entrance, destination in self.area_connections.items():
@@ -72,31 +102,29 @@ def create_item(self, name: str) -> Item:
return item
def create_items(self):
- starcount = self.multiworld.AmountOfStars[self.player].value
- if (not self.multiworld.EnableCoinStars[self.player].value):
- starcount = max(35,self.multiworld.AmountOfStars[self.player].value-15)
- starcount = max(starcount, self.multiworld.FirstBowserStarDoorCost[self.player].value,
- self.multiworld.BasementStarDoorCost[self.player].value, self.multiworld.SecondFloorStarDoorCost[self.player].value,
- self.multiworld.MIPS1Cost[self.player].value, self.multiworld.MIPS2Cost[self.player].value,
- self.multiworld.StarsToFinish[self.player].value)
- self.multiworld.itempool += [self.create_item("Power Star") for i in range(0,starcount)]
- self.multiworld.itempool += [self.create_item("1Up Mushroom") for i in range(starcount,120 - (15 if not self.multiworld.EnableCoinStars[self.player].value else 0))]
-
+ # 1Up Mushrooms
+ self.multiworld.itempool += [self.create_item("1Up Mushroom") for i in range(0,self.filler_count)]
+ # Power Stars
+ self.multiworld.itempool += [self.create_item("Power Star") for i in range(0,self.number_of_stars)]
+ # Keys
if (not self.multiworld.ProgressiveKeys[self.player].value):
key1 = self.create_item("Basement Key")
key2 = self.create_item("Second Floor Key")
self.multiworld.itempool += [key1, key2]
else:
self.multiworld.itempool += [self.create_item("Progressive Key") for i in range(0,2)]
-
- wingcap = self.create_item("Wing Cap")
- metalcap = self.create_item("Metal Cap")
- vanishcap = self.create_item("Vanish Cap")
- self.multiworld.itempool += [wingcap, metalcap, vanishcap]
-
+ # Caps
+ self.multiworld.itempool += [self.create_item(cap_name) for cap_name in ["Wing Cap", "Metal Cap", "Vanish Cap"]]
+ # Cannons
if (self.multiworld.BuddyChecks[self.player].value):
self.multiworld.itempool += [self.create_item(name) for name, id in cannon_item_table.items()]
- else:
+ # Moves
+ self.multiworld.itempool += [self.create_item(action)
+ for action, itemid in action_item_table.items()
+ if self.move_rando_bitvec & (1 << itemid - action_item_table['Double Jump'])]
+
+ def generate_basic(self):
+ if not (self.multiworld.BuddyChecks[self.player].value):
self.multiworld.get_location("BoB: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock BoB"))
self.multiworld.get_location("WF: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock WF"))
self.multiworld.get_location("JRB: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock JRB"))
@@ -108,9 +136,7 @@ def create_items(self):
self.multiworld.get_location("THI: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock THI"))
self.multiworld.get_location("RR: Bob-omb Buddy", self.player).place_locked_item(self.create_item("Cannon Unlock RR"))
- if (self.multiworld.ExclamationBoxes[self.player].value > 0):
- self.multiworld.itempool += [self.create_item("1Up Mushroom") for i in range(0,29)]
- else:
+ if (self.multiworld.ExclamationBoxes[self.player].value == 0):
self.multiworld.get_location("CCM: 1Up Block Near Snowman", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.multiworld.get_location("CCM: 1Up Block Ice Pillar", self.player).place_locked_item(self.create_item("1Up Mushroom"))
self.multiworld.get_location("CCM: 1Up Block Secret Slide", self.player).place_locked_item(self.create_item("1Up Mushroom"))
@@ -147,14 +173,10 @@ def get_filler_item_name(self) -> str:
def fill_slot_data(self):
return {
"AreaRando": self.area_connections,
- "FirstBowserDoorCost": self.multiworld.FirstBowserStarDoorCost[self.player].value,
- "BasementDoorCost": self.multiworld.BasementStarDoorCost[self.player].value,
- "SecondFloorDoorCost": self.multiworld.SecondFloorStarDoorCost[self.player].value,
- "MIPS1Cost": self.multiworld.MIPS1Cost[self.player].value,
- "MIPS2Cost": self.multiworld.MIPS2Cost[self.player].value,
- "StarsToFinish": self.multiworld.StarsToFinish[self.player].value,
+ "MoveRandoVec": self.move_rando_bitvec,
"DeathLink": self.multiworld.death_link[self.player].value,
- "CompletionType" : self.multiworld.CompletionType[self.player].value,
+ "CompletionType": self.multiworld.CompletionType[self.player].value,
+ **self.star_costs
}
def generate_output(self, output_directory: str):
diff --git a/worlds/soe/__init__.py b/worlds/soe/__init__.py
index 74387fb1be80..bbe018da5329 100644
--- a/worlds/soe/__init__.py
+++ b/worlds/soe/__init__.py
@@ -81,7 +81,7 @@
# item helpers
_ingredients = (
'Wax', 'Water', 'Vinegar', 'Root', 'Oil', 'Mushroom', 'Mud Pepper', 'Meteorite', 'Limestone', 'Iron',
- 'Gunpowder', 'Grease', 'Feather', 'Ethanol', 'Dry Ice', 'Crystal', 'Clay', 'Brimstone', 'Bone', 'Atlas Amulet',
+ 'Gunpowder', 'Grease', 'Feather', 'Ethanol', 'Dry Ice', 'Crystal', 'Clay', 'Brimstone', 'Bone', 'Atlas Medallion',
'Ash', 'Acorn'
)
_other_items = (
diff --git a/worlds/soe/test/test_item_mapping.py b/worlds/soe/test/test_item_mapping.py
new file mode 100644
index 000000000000..7df05837c78b
--- /dev/null
+++ b/worlds/soe/test/test_item_mapping.py
@@ -0,0 +1,21 @@
+from unittest import TestCase
+from .. import SoEWorld
+
+
+class TestMapping(TestCase):
+ def test_atlas_medallion_name_group(self) -> None:
+ """
+ Test that we used the pyevermizer name for Atlas Medallion (not Amulet) in item groups.
+ """
+ self.assertIn("Any Atlas Medallion", SoEWorld.item_name_groups)
+
+ def test_atlas_medallion_name_items(self) -> None:
+ """
+ Test that we used the pyevermizer name for Atlas Medallion (not Amulet) in items.
+ """
+ found_medallion = False
+ for name in SoEWorld.item_name_to_id:
+ self.assertNotIn("Atlas Amulet", name, "Expected Atlas Medallion, not Amulet")
+ if "Atlas Medallion" in name:
+ found_medallion = True
+ self.assertTrue(found_medallion, "Did not find Atlas Medallion in items")
diff --git a/worlds/stardew_valley/docs/en_Stardew Valley.md b/worlds/stardew_valley/docs/en_Stardew Valley.md
index a880a40b971a..04ba9c15c3c1 100644
--- a/worlds/stardew_valley/docs/en_Stardew Valley.md
+++ b/worlds/stardew_valley/docs/en_Stardew Valley.md
@@ -124,6 +124,6 @@ List of supported mods:
## Multiplayer
-You cannot play an Archipelago Slot in multiplayer at the moment. There is no short-terms plans to support that feature.
+You cannot play an Archipelago Slot in multiplayer at the moment. There are no short-term plans to support that feature.
You can, however, send Stardew Valley objects as gifts from one Stardew Player to another Stardew player, using in-game Joja Prime delivery, for a fee. This exclusive feature can be turned off if you don't want to send and receive gifts.
diff --git a/worlds/stardew_valley/docs/setup_en.md b/worlds/stardew_valley/docs/setup_en.md
index 68c7fb9af6a0..d8f0e16b1017 100644
--- a/worlds/stardew_valley/docs/setup_en.md
+++ b/worlds/stardew_valley/docs/setup_en.md
@@ -84,4 +84,4 @@ See the [Supported mods documentation](https://github.com/agilbert1412/StardewAr
### Multiplayer
-You cannot play an Archipelago Slot in multiplayer at the moment. There are no short-terms plans to support that feature.
\ No newline at end of file
+You cannot play an Archipelago Slot in multiplayer at the moment. There are no short-term plans to support that feature.
diff --git a/worlds/subnautica/__init__.py b/worlds/subnautica/__init__.py
index de4f4e33dc87..e9341ec3b9de 100644
--- a/worlds/subnautica/__init__.py
+++ b/worlds/subnautica/__init__.py
@@ -115,7 +115,7 @@ def create_items(self):
for i in range(item.count):
subnautica_item = self.create_item(item.name)
if item.name == "Neptune Launch Platform":
- self.multiworld.get_location("Aurora - Captain Data Terminal", self.player).place_locked_item(
+ self.get_location("Aurora - Captain Data Terminal").place_locked_item(
subnautica_item)
else:
pool.append(subnautica_item)
@@ -128,7 +128,7 @@ def create_items(self):
pool.append(self.create_item(name))
extras -= group_amount
- for item_name in self.multiworld.random.sample(
+ for item_name in self.random.sample(
# list of high-count important fragments as priority filler
[
"Cyclops Engine Fragment",
@@ -175,18 +175,6 @@ def create_item(self, name: str) -> SubnauticaItem:
item_table[item_id].classification,
item_id, player=self.player)
- def create_region(self, name: str, region_locations=None, exits=None):
- ret = Region(name, self.player, self.multiworld)
- if region_locations:
- for location in region_locations:
- loc_id = self.location_name_to_id.get(location, None)
- location = SubnauticaLocation(self.player, location, loc_id, ret)
- ret.locations.append(location)
- if exits:
- for region_exit in exits:
- ret.exits.append(Entrance(self.player, region_exit, ret))
- return ret
-
def get_filler_item_name(self) -> str:
return item_table[self.multiworld.random.choice(items_by_type[ItemType.resource])].name
diff --git a/worlds/timespinner/Regions.py b/worlds/timespinner/Regions.py
index fc7535642949..f80babc0e6d4 100644
--- a/worlds/timespinner/Regions.py
+++ b/worlds/timespinner/Regions.py
@@ -247,13 +247,7 @@ def connect(world: MultiWorld, player: int, source: str, target: str,
sourceRegion = world.get_region(source, player)
targetRegion = world.get_region(target, player)
-
- connection = Entrance(player, "", sourceRegion)
-
- if rule:
- connection.access_rule = rule
- sourceRegion.exits.append(connection)
- connection.connect(targetRegion)
+ sourceRegion.connect(targetRegion, rule=rule)
def split_location_datas_per_region(locations: List[LocationData]) -> Dict[str, List[LocationData]]:
diff --git a/worlds/tloz/__init__.py b/worlds/tloz/__init__.py
index 259bfe204716..27230654b8ce 100644
--- a/worlds/tloz/__init__.py
+++ b/worlds/tloz/__init__.py
@@ -45,7 +45,7 @@ class DisplayMsgs(settings.Bool):
class TLoZWeb(WebWorld):
theme = "stone"
setup = Tutorial(
- "Multiworld Setup Tutorial",
+ "Multiworld Setup Guide",
"A guide to setting up The Legend of Zelda for Archipelago on your computer.",
"English",
"multiworld_en.md",
diff --git a/worlds/tunic/__init__.py b/worlds/tunic/__init__.py
index b946ea8e3039..b10ccd43af59 100644
--- a/worlds/tunic/__init__.py
+++ b/worlds/tunic/__init__.py
@@ -24,15 +24,15 @@ class TunicWeb(WebWorld):
)
]
theme = "grassFlowers"
- game = "Tunic"
+ game = "TUNIC"
class TunicItem(Item):
- game: str = "Tunic"
+ game: str = "TUNIC"
class TunicLocation(Location):
- game: str = "Tunic"
+ game: str = "TUNIC"
class TunicWorld(World):
@@ -41,7 +41,7 @@ class TunicWorld(World):
about a small fox on a big adventure. Stranded on a mysterious beach, armed with only your own curiosity, you will
confront colossal beasts, collect strange and powerful items, and unravel long-lost secrets. Be brave, tiny fox!
"""
- game = "Tunic"
+ game = "TUNIC"
web = TunicWeb()
data_version = 2
@@ -59,8 +59,19 @@ class TunicWorld(World):
er_portal_hints: Dict[int, str]
def generate_early(self) -> None:
- if self.options.start_with_sword and "Sword" not in self.options.start_inventory:
- self.options.start_inventory.value["Sword"] = 1
+ # Universal tracker stuff, shouldn't do anything in standard gen
+ if hasattr(self.multiworld, "re_gen_passthrough"):
+ if "TUNIC" in self.multiworld.re_gen_passthrough:
+ passthrough = self.multiworld.re_gen_passthrough["TUNIC"]
+ self.options.start_with_sword.value = passthrough["start_with_sword"]
+ self.options.keys_behind_bosses.value = passthrough["keys_behind_bosses"]
+ self.options.sword_progression.value = passthrough["sword_progression"]
+ self.options.ability_shuffling.value = passthrough["ability_shuffling"]
+ self.options.logic_rules.value = passthrough["logic_rules"]
+ self.options.lanternless.value = passthrough["lanternless"]
+ self.options.maskless.value = passthrough["maskless"]
+ self.options.hexagon_quest.value = passthrough["hexagon_quest"]
+ self.options.entrance_rando.value = passthrough["entrance_rando"]
def create_item(self, name: str) -> TunicItem:
item_data = item_table[name]
@@ -80,6 +91,9 @@ def create_items(self) -> None:
items_to_create["Fool Trap"] += items_to_create[money_fool]
items_to_create[money_fool] = 0
+ if self.options.start_with_sword:
+ self.multiworld.push_precollected(self.create_item("Sword"))
+
if sword_progression:
items_to_create["Stick"] = 0
items_to_create["Sword"] = 0
@@ -150,10 +164,20 @@ def create_regions(self) -> None:
self.tunic_portal_pairs = {}
self.er_portal_hints = {}
self.ability_unlocks = randomize_ability_unlocks(self.random, self.options)
+
+ # stuff for universal tracker support, can be ignored for standard gen
+ if hasattr(self.multiworld, "re_gen_passthrough"):
+ if "TUNIC" in self.multiworld.re_gen_passthrough:
+ passthrough = self.multiworld.re_gen_passthrough["TUNIC"]
+ self.ability_unlocks["Pages 24-25 (Prayer)"] = passthrough["Hexagon Quest Prayer"]
+ self.ability_unlocks["Pages 42-43 (Holy Cross)"] = passthrough["Hexagon Quest Holy Cross"]
+ self.ability_unlocks["Pages 52-53 (Icebolt)"] = passthrough["Hexagon Quest Icebolt"]
+
if self.options.entrance_rando:
portal_pairs, portal_hints = create_er_regions(self)
for portal1, portal2 in portal_pairs.items():
self.tunic_portal_pairs[portal1.scene_destination()] = portal2.scene_destination()
+
self.er_portal_hints = portal_hints
else:
@@ -199,10 +223,13 @@ def fill_slot_data(self) -> Dict[str, Any]:
"ability_shuffling": self.options.ability_shuffling.value,
"hexagon_quest": self.options.hexagon_quest.value,
"fool_traps": self.options.fool_traps.value,
+ "logic_rules": self.options.logic_rules.value,
+ "lanternless": self.options.lanternless.value,
+ "maskless": self.options.maskless.value,
"entrance_rando": self.options.entrance_rando.value,
"Hexagon Quest Prayer": self.ability_unlocks["Pages 24-25 (Prayer)"],
"Hexagon Quest Holy Cross": self.ability_unlocks["Pages 42-43 (Holy Cross)"],
- "Hexagon Quest Ice Rod": self.ability_unlocks["Pages 52-53 (Ice Rod)"],
+ "Hexagon Quest Icebolt": self.ability_unlocks["Pages 52-53 (Icebolt)"],
"Hexagon Quest Goal": self.options.hexagon_goal.value,
"Entrance Rando": self.tunic_portal_pairs
}
@@ -236,44 +263,7 @@ def fill_slot_data(self) -> Dict[str, Any]:
return slot_data
# for the universal tracker, doesn't get called in standard gen
- def interpret_slot_data(self, slot_data: Dict[str, Any]) -> None:
- # bypassing random yaml settings
- self.options.start_with_sword.value = slot_data["start_with_sword"]
- self.options.keys_behind_bosses.value = slot_data["keys_behind_bosses"]
- self.options.sword_progression.value = slot_data["sword_progression"]
- self.options.ability_shuffling.value = slot_data["ability_shuffling"]
- self.options.hexagon_quest.value = slot_data["hexagon_quest"]
- self.ability_unlocks["Pages 24-25 (Prayer)"] = slot_data["Hexagon Quest Prayer"]
- self.ability_unlocks["Pages 42-43 (Holy Cross)"] = slot_data["Hexagon Quest Holy Cross"]
- self.ability_unlocks["Pages 52-53 (Ice Rod)"] = slot_data["Hexagon Quest Ice Rod"]
-
- # swapping entrances around so the mapping matches what was generated
- if slot_data["entrance_rando"]:
- from BaseClasses import Entrance
- from .er_data import portal_mapping
- entrance_dict: Dict[str, Entrance] = {entrance.name: entrance
- for region in self.multiworld.get_regions(self.player)
- for entrance in region.entrances}
- slot_portals: Dict[str, str] = slot_data["Entrance Rando"]
- for portal1, portal2 in slot_portals.items():
- portal_name1: str = ""
- portal_name2: str = ""
- entrance1 = None
- entrance2 = None
- for portal in portal_mapping:
- if portal.scene_destination() == portal1:
- portal_name1 = portal.name
- if portal.scene_destination() == portal2:
- portal_name2 = portal.name
-
- for entrance_name, entrance in entrance_dict.items():
- if entrance_name.startswith(portal_name1):
- entrance1 = entrance
- if entrance_name.startswith(portal_name2):
- entrance2 = entrance
- if entrance1 is None:
- raise Exception("entrance1 not found, portal1 is " + portal1)
- if entrance2 is None:
- raise Exception("entrance2 not found, portal2 is " + portal2)
- entrance1.connected_region = entrance2.parent_region
- entrance2.connected_region = entrance1.parent_region
+ @staticmethod
+ def interpret_slot_data(slot_data: Dict[str, Any]) -> Dict[str, Any]:
+ # returning slot_data so it regens, giving it back in multiworld.re_gen_passthrough
+ return slot_data
diff --git a/worlds/tunic/docs/en_Tunic.md b/worlds/tunic/docs/en_TUNIC.md
similarity index 100%
rename from worlds/tunic/docs/en_Tunic.md
rename to worlds/tunic/docs/en_TUNIC.md
diff --git a/worlds/tunic/er_data.py b/worlds/tunic/er_data.py
index 2d3bcc025f4b..7678d77fe034 100644
--- a/worlds/tunic/er_data.py
+++ b/worlds/tunic/er_data.py
@@ -37,7 +37,7 @@ def scene_destination(self) -> str: # full, nonchanging name to interpret by th
destination="Furnace_gyro_lower"),
Portal(name="Caustic Light Cave Entrance", region="Overworld",
destination="Overworld Cave_"),
- Portal(name="Swamp Upper Entrance", region="Overworld Laurels",
+ Portal(name="Swamp Upper Entrance", region="Overworld Swamp Upper Entry",
destination="Swamp Redux 2_wall"),
Portal(name="Swamp Lower Entrance", region="Overworld",
destination="Swamp Redux 2_conduit"),
@@ -49,7 +49,7 @@ def scene_destination(self) -> str: # full, nonchanging name to interpret by th
destination="Atoll Redux_upper"),
Portal(name="Atoll Lower Entrance", region="Overworld",
destination="Atoll Redux_lower"),
- Portal(name="Special Shop Entrance", region="Overworld Laurels",
+ Portal(name="Special Shop Entrance", region="Overworld Special Shop Entry",
destination="ShopSpecial_"),
Portal(name="Maze Cave Entrance", region="Overworld",
destination="Maze Room_"),
@@ -57,7 +57,7 @@ def scene_destination(self) -> str: # full, nonchanging name to interpret by th
destination="Archipelagos Redux_upper"),
Portal(name="West Garden Entrance from Furnace", region="Overworld to West Garden from Furnace",
destination="Archipelagos Redux_lower"),
- Portal(name="West Garden Laurels Entrance", region="Overworld Laurels",
+ Portal(name="West Garden Laurels Entrance", region="Overworld West Garden Laurels Entry",
destination="Archipelagos Redux_lowest"),
Portal(name="Temple Door Entrance", region="Overworld Temple Door",
destination="Temple_main"),
@@ -211,7 +211,7 @@ def scene_destination(self) -> str: # full, nonchanging name to interpret by th
destination="Shop_"),
Portal(name="Atoll to Far Shore", region="Ruined Atoll Portal",
destination="Transit_teleporter_atoll"),
- Portal(name="Atoll Statue Teleporter", region="Ruined Atoll Portal",
+ Portal(name="Atoll Statue Teleporter", region="Ruined Atoll Statue",
destination="Library Exterior_"),
Portal(name="Frog Stairs Eye Entrance", region="Ruined Atoll",
destination="Frog Stairs_eye"),
@@ -533,7 +533,9 @@ class Hint(IntEnum):
"Overworld": RegionInfo("Overworld Redux"),
"Overworld Holy Cross": RegionInfo("Fake", dead_end=DeadEnd.all_cats),
"Overworld Belltower": RegionInfo("Overworld Redux"), # the area with the belltower and chest
- "Overworld Laurels": RegionInfo("Overworld Redux"), # all spots in Overworld that you need laurels to reach
+ "Overworld Swamp Upper Entry": RegionInfo("Overworld Redux"), # upper swamp entry spot
+ "Overworld Special Shop Entry": RegionInfo("Overworld Redux"), # special shop entry spot
+ "Overworld West Garden Laurels Entry": RegionInfo("Overworld Redux"), # west garden laurels entry
"Overworld to West Garden from Furnace": RegionInfo("Overworld Redux", hint=Hint.region),
"Overworld Well to Furnace Rail": RegionInfo("Overworld Redux"), # the tiny rail passageway
"Overworld Ruined Passage Door": RegionInfo("Overworld Redux"), # the small space betweeen the door and the portal
@@ -598,6 +600,7 @@ class Hint(IntEnum):
"Ruined Atoll Lower Entry Area": RegionInfo("Atoll Redux"),
"Ruined Atoll Frog Mouth": RegionInfo("Atoll Redux"),
"Ruined Atoll Portal": RegionInfo("Atoll Redux"),
+ "Ruined Atoll Statue": RegionInfo("Atoll Redux"),
"Frog's Domain Entry": RegionInfo("Frog Stairs"),
"Frog's Domain": RegionInfo("frog cave main", hint=Hint.region),
"Frog's Domain Back": RegionInfo("frog cave main", hint=Hint.scene),
@@ -710,7 +713,7 @@ class Hint(IntEnum):
hallway_helper[p2] = p1
# so we can just loop over this instead of doing some complicated thing to deal with hallways in the hints
-hallways_nmg: Dict[str, str] = {
+hallways_ur: Dict[str, str] = {
"Ruins Passage, Overworld Redux_east": "Ruins Passage, Overworld Redux_west",
"East Forest Redux Interior, East Forest Redux_upper": "East Forest Redux Interior, East Forest Redux_lower",
"Forest Boss Room, East Forest Redux Laddercave_": "Forest Boss Room, Forest Belltower_",
@@ -720,20 +723,22 @@ class Hint(IntEnum):
"ziggurat2020_0, Quarry Redux_": "ziggurat2020_0, ziggurat2020_1_",
"Purgatory, Purgatory_bottom": "Purgatory, Purgatory_top",
}
-hallway_helper_nmg: Dict[str, str] = {}
-for p1, p2 in hallways.items():
- hallway_helper[p1] = p2
- hallway_helper[p2] = p1
+hallway_helper_ur: Dict[str, str] = {}
+for p1, p2 in hallways_ur.items():
+ hallway_helper_ur[p1] = p2
+ hallway_helper_ur[p2] = p1
# the key is the region you have, the value is the regions you get for having that region
# this is mostly so we don't have to do something overly complex to get this information
-dependent_regions: Dict[Tuple[str, ...], List[str]] = {
- ("Overworld", "Overworld Belltower", "Overworld Laurels", "Overworld Southeast Cross Door", "Overworld Temple Door",
+dependent_regions_restricted: Dict[Tuple[str, ...], List[str]] = {
+ ("Overworld", "Overworld Belltower", "Overworld Swamp Upper Entry", "Overworld Special Shop Entry",
+ "Overworld West Garden Laurels Entry", "Overworld Southeast Cross Door", "Overworld Temple Door",
"Overworld Fountain Cross Door", "Overworld Town Portal", "Overworld Spawn Portal"):
- ["Overworld", "Overworld Belltower", "Overworld Laurels", "Overworld Ruined Passage Door",
- "Overworld Southeast Cross Door", "Overworld Old House Door", "Overworld Temple Door",
- "Overworld Fountain Cross Door", "Overworld Town Portal", "Overworld Spawn Portal"],
+ ["Overworld", "Overworld Belltower", "Overworld Swamp Upper Entry", "Overworld Special Shop Entry",
+ "Overworld West Garden Laurels Entry", "Overworld Ruined Passage Door", "Overworld Southeast Cross Door",
+ "Overworld Old House Door", "Overworld Temple Door", "Overworld Fountain Cross Door", "Overworld Town Portal",
+ "Overworld Spawn Portal"],
("Old House Front",):
["Old House Front", "Old House Back"],
("Furnace Fuse", "Furnace Ladder Area", "Furnace Walking Path"):
@@ -745,6 +750,8 @@ class Hint(IntEnum):
["Forest Belltower Main", "Forest Belltower Lower"],
("East Forest", "East Forest Dance Fox Spot", "East Forest Portal"):
["East Forest", "East Forest Dance Fox Spot", "East Forest Portal"],
+ ("Guard House 1 East", "Guard House 1 West"):
+ ["Guard House 1 East", "Guard House 1 West"],
("Forest Grave Path Main", "Forest Grave Path Upper"):
["Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"],
("Forest Grave Path by Grave", "Forest Hero's Grave"):
@@ -758,8 +765,10 @@ class Hint(IntEnum):
("West Garden", "West Garden Laurels Exit", "West Garden after Boss", "West Garden Hero's Grave"):
["West Garden", "West Garden Laurels Exit", "West Garden after Boss", "West Garden Hero's Grave"],
("West Garden Portal", "West Garden Portal Item"): ["West Garden Portal", "West Garden Portal Item"],
- ("Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal"):
- ["Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal"],
+ ("Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal",
+ "Ruined Atoll Statue"):
+ ["Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal",
+ "Ruined Atoll Statue"],
("Frog's Domain",):
["Frog's Domain", "Frog's Domain Back"],
("Library Exterior Ladder", "Library Exterior Tree"):
@@ -818,12 +827,14 @@ class Hint(IntEnum):
dependent_regions_nmg: Dict[Tuple[str, ...], List[str]] = {
- ("Overworld", "Overworld Belltower", "Overworld Laurels", "Overworld Southeast Cross Door", "Overworld Temple Door",
+ ("Overworld", "Overworld Belltower", "Overworld Swamp Upper Entry", "Overworld Special Shop Entry",
+ "Overworld West Garden Laurels Entry", "Overworld Southeast Cross Door", "Overworld Temple Door",
"Overworld Fountain Cross Door", "Overworld Town Portal", "Overworld Spawn Portal",
"Overworld Ruined Passage Door"):
- ["Overworld", "Overworld Belltower", "Overworld Laurels", "Overworld Ruined Passage Door",
- "Overworld Southeast Cross Door", "Overworld Old House Door", "Overworld Temple Door",
- "Overworld Fountain Cross Door", "Overworld Town Portal", "Overworld Spawn Portal"],
+ ["Overworld", "Overworld Belltower", "Overworld Swamp Upper Entry", "Overworld Special Shop Entry",
+ "Overworld West Garden Laurels Entry", "Overworld Ruined Passage Door", "Overworld Southeast Cross Door",
+ "Overworld Old House Door", "Overworld Temple Door", "Overworld Fountain Cross Door", "Overworld Town Portal",
+ "Overworld Spawn Portal"],
# can laurels through the gate
("Old House Front", "Old House Back"):
["Old House Front", "Old House Back"],
@@ -836,6 +847,8 @@ class Hint(IntEnum):
["Forest Belltower Main", "Forest Belltower Lower"],
("East Forest", "East Forest Dance Fox Spot", "East Forest Portal"):
["East Forest", "East Forest Dance Fox Spot", "East Forest Portal"],
+ ("Guard House 1 East", "Guard House 1 West"):
+ ["Guard House 1 East", "Guard House 1 West"],
("Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"):
["Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"],
("Beneath the Well Front", "Beneath the Well Main", "Beneath the Well Back"):
@@ -848,8 +861,10 @@ class Hint(IntEnum):
"West Garden Portal", "West Garden Portal Item"):
["West Garden", "West Garden Laurels Exit", "West Garden after Boss", "West Garden Hero's Grave",
"West Garden Portal", "West Garden Portal Item"],
- ("Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal"):
- ["Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal"],
+ ("Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal",
+ "Ruined Atoll Statue"):
+ ["Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal",
+ "Ruined Atoll Statue"],
("Frog's Domain",):
["Frog's Domain", "Frog's Domain Back"],
("Library Exterior Ladder", "Library Exterior Tree"):
@@ -908,13 +923,14 @@ class Hint(IntEnum):
dependent_regions_ur: Dict[Tuple[str, ...], List[str]] = {
# can use ladder storage to get to the well rail
- ("Overworld", "Overworld Belltower", "Overworld Laurels", "Overworld Southeast Cross Door", "Overworld Temple Door",
+ ("Overworld", "Overworld Belltower", "Overworld Swamp Upper Entry", "Overworld Special Shop Entry",
+ "Overworld West Garden Laurels Entry", "Overworld Southeast Cross Door", "Overworld Temple Door",
"Overworld Fountain Cross Door", "Overworld Town Portal", "Overworld Spawn Portal",
"Overworld Ruined Passage Door"):
- ["Overworld", "Overworld Belltower", "Overworld Laurels", "Overworld Ruined Passage Door",
- "Overworld Southeast Cross Door", "Overworld Old House Door", "Overworld Temple Door",
- "Overworld Fountain Cross Door", "Overworld Town Portal", "Overworld Spawn Portal",
- "Overworld Well to Furnace Rail"],
+ ["Overworld", "Overworld Belltower", "Overworld Swamp Upper Entry", "Overworld Special Shop Entry",
+ "Overworld West Garden Laurels Entry", "Overworld Ruined Passage Door", "Overworld Southeast Cross Door",
+ "Overworld Old House Door", "Overworld Temple Door", "Overworld Fountain Cross Door", "Overworld Town Portal",
+ "Overworld Spawn Portal", "Overworld Well to Furnace Rail"],
# can laurels through the gate
("Old House Front", "Old House Back"):
["Old House Front", "Old House Back"],
@@ -927,6 +943,8 @@ class Hint(IntEnum):
["Forest Belltower Main", "Forest Belltower Lower"],
("East Forest", "East Forest Dance Fox Spot", "East Forest Portal"):
["East Forest", "East Forest Dance Fox Spot", "East Forest Portal"],
+ ("Guard House 1 East", "Guard House 1 West"):
+ ["Guard House 1 East", "Guard House 1 West"],
# can use laurels, ice grapple, or ladder storage to traverse
("Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"):
["Forest Grave Path Main", "Forest Grave Path Upper", "Forest Grave Path by Grave", "Forest Hero's Grave"],
@@ -941,8 +959,10 @@ class Hint(IntEnum):
"West Garden Portal", "West Garden Portal Item"):
["West Garden", "West Garden Laurels Exit", "West Garden after Boss", "West Garden Hero's Grave",
"West Garden Portal", "West Garden Portal Item"],
- ("Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal"):
- ["Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal"],
+ ("Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal",
+ "Ruined Atoll Statue"):
+ ["Ruined Atoll", "Ruined Atoll Lower Entry Area", "Ruined Atoll Frog Mouth", "Ruined Atoll Portal",
+ "Ruined Atoll Statue"],
("Frog's Domain",):
["Frog's Domain", "Frog's Domain Back"],
("Library Exterior Ladder", "Library Exterior Tree"):
diff --git a/worlds/tunic/er_rules.py b/worlds/tunic/er_rules.py
index 5d88022dc159..a7d0543c3f17 100644
--- a/worlds/tunic/er_rules.py
+++ b/worlds/tunic/er_rules.py
@@ -16,7 +16,7 @@
coins = "Golden Coin"
prayer = "Pages 24-25 (Prayer)"
holy_cross = "Pages 42-43 (Holy Cross)"
-ice_rod = "Pages 52-53 (Ice Rod)"
+icebolt = "Pages 52-53 (Icebolt)"
key = "Key"
house_key = "Old House Key"
vault_key = "Fortress Vault Key"
@@ -53,9 +53,23 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re
or (state.has(laurels, player) and options.logic_rules))
regions["Overworld"].connect(
- connecting_region=regions["Overworld Laurels"],
+ connecting_region=regions["Overworld Swamp Upper Entry"],
rule=lambda state: state.has(laurels, player))
- regions["Overworld Laurels"].connect(
+ regions["Overworld Swamp Upper Entry"].connect(
+ connecting_region=regions["Overworld"],
+ rule=lambda state: state.has(laurels, player))
+
+ regions["Overworld"].connect(
+ connecting_region=regions["Overworld Special Shop Entry"],
+ rule=lambda state: state.has(laurels, player))
+ regions["Overworld Special Shop Entry"].connect(
+ connecting_region=regions["Overworld"],
+ rule=lambda state: state.has(laurels, player))
+
+ regions["Overworld"].connect(
+ connecting_region=regions["Overworld West Garden Laurels Entry"],
+ rule=lambda state: state.has(laurels, player))
+ regions["Overworld West Garden Laurels Entry"].connect(
connecting_region=regions["Overworld"],
rule=lambda state: state.has(laurels, player))
@@ -230,7 +244,6 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re
connecting_region=regions["West Garden Laurels Exit"],
rule=lambda state: state.has(laurels, player))
- # todo: can you wake the boss, then grapple to it, then kill it?
regions["West Garden after Boss"].connect(
connecting_region=regions["West Garden"],
rule=lambda state: state.has(laurels, player))
@@ -282,6 +295,12 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re
regions["Ruined Atoll Portal"].connect(
connecting_region=regions["Ruined Atoll"])
+ regions["Ruined Atoll"].connect(
+ connecting_region=regions["Ruined Atoll Statue"],
+ rule=lambda state: has_ability(state, player, prayer, options, ability_unlocks))
+ regions["Ruined Atoll Statue"].connect(
+ connecting_region=regions["Ruined Atoll"])
+
regions["Frog's Domain"].connect(
connecting_region=regions["Frog's Domain Back"],
rule=lambda state: state.has(grapple, player))
@@ -364,13 +383,13 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re
# nmg: ice grapple through the big gold door, can do it both ways
regions["Eastern Vault Fortress"].connect(
connecting_region=regions["Eastern Vault Fortress Gold Door"],
- name="Fortress Gold Door",
+ name="Fortress to Gold Door",
rule=lambda state: state.has_all({"Activate Eastern Vault West Fuses",
"Activate Eastern Vault East Fuse"}, player)
or has_ice_grapple_logic(False, state, player, options, ability_unlocks))
regions["Eastern Vault Fortress Gold Door"].connect(
connecting_region=regions["Eastern Vault Fortress"],
- name="Fortress Gold Door",
+ name="Gold Door to Fortress",
rule=lambda state: has_ice_grapple_logic(True, state, player, options, ability_unlocks))
regions["Fortress Grave Path"].connect(
@@ -431,6 +450,13 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re
regions["Quarry"].connect(
connecting_region=regions["Quarry Monastery Entry"])
+ regions["Quarry Monastery Entry"].connect(
+ connecting_region=regions["Quarry Back"],
+ rule=lambda state: state.has(laurels, player))
+ regions["Quarry Back"].connect(
+ connecting_region=regions["Quarry Monastery Entry"],
+ rule=lambda state: state.has(laurels, player))
+
regions["Monastery Rope"].connect(
connecting_region=regions["Quarry Back"])
@@ -482,9 +508,13 @@ def set_er_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int], re
rule=lambda state: state.has(laurels, player)
or (has_sword(state, player) and has_ability(state, player, prayer, options, ability_unlocks)))
# unrestricted: use ladder storage to get to the front, get hit by one of the many enemies
+ # nmg: can ice grapple on the voidlings to the double admin fight, still need to pray at the fuse
regions["Rooted Ziggurat Lower Back"].connect(
connecting_region=regions["Rooted Ziggurat Lower Front"],
- rule=lambda state: state.has(laurels, player) or can_ladder_storage(state, player, options))
+ rule=lambda state: ((state.has(laurels, player) or
+ has_ice_grapple_logic(True, state, player, options, ability_unlocks)) and
+ has_ability(state, player, prayer, options, ability_unlocks)
+ and has_sword(state, player)) or can_ladder_storage(state, player, options))
regions["Rooted Ziggurat Lower Back"].connect(
connecting_region=regions["Rooted Ziggurat Portal Room Entrance"],
@@ -864,7 +894,7 @@ def set_er_location_rules(world: "TunicWorld", ability_unlocks: Dict[str, int])
lambda state: state.has_all({grapple, laurels}, player))
set_rule(multiworld.get_location("East Forest - Ice Rod Grapple Chest", player), lambda state: (
state.has_all({grapple, ice_dagger, fire_wand}, player) and
- has_ability(state, player, ice_rod, options, ability_unlocks)))
+ has_ability(state, player, icebolt, options, ability_unlocks)))
# West Garden
set_rule(multiworld.get_location("West Garden - [North] Across From Page Pickup", player),
@@ -920,10 +950,12 @@ def set_er_location_rules(world: "TunicWorld", ability_unlocks: Dict[str, int])
# Bosses
set_rule(multiworld.get_location("Fortress Arena - Siege Engine/Vault Key Pickup", player),
lambda state: has_sword(state, player))
+ # nmg - kill Librarian with a lure, or gun I guess
set_rule(multiworld.get_location("Librarian - Hexagon Green", player),
- lambda state: has_sword(state, player))
+ lambda state: has_sword(state, player) or options.logic_rules)
+ # nmg - kill boss scav with orb + firecracker, or similar
set_rule(multiworld.get_location("Rooted Ziggurat Lower - Hexagon Blue", player),
- lambda state: has_sword(state, player))
+ lambda state: has_sword(state, player) or (state.has(grapple, player) and options.logic_rules))
# Swamp
set_rule(multiworld.get_location("Cathedral Gauntlet - Gauntlet Reward", player),
diff --git a/worlds/tunic/er_scripts.py b/worlds/tunic/er_scripts.py
index 84b97e13daad..d2b854f5df0e 100644
--- a/worlds/tunic/er_scripts.py
+++ b/worlds/tunic/er_scripts.py
@@ -1,20 +1,21 @@
from typing import Dict, List, Set, Tuple, TYPE_CHECKING
from BaseClasses import Region, ItemClassification, Item, Location
from .locations import location_table
-from .er_data import Portal, tunic_er_regions, portal_mapping, hallway_helper, hallway_helper_nmg, \
- dependent_regions, dependent_regions_nmg, dependent_regions_ur
+from .er_data import Portal, tunic_er_regions, portal_mapping, hallway_helper, hallway_helper_ur, \
+ dependent_regions_restricted, dependent_regions_nmg, dependent_regions_ur
from .er_rules import set_er_region_rules
+from worlds.generic import PlandoConnection
if TYPE_CHECKING:
from . import TunicWorld
class TunicERItem(Item):
- game: str = "Tunic"
+ game: str = "TUNIC"
class TunicERLocation(Location):
- game: str = "Tunic"
+ game: str = "TUNIC"
def create_er_regions(world: "TunicWorld") -> Tuple[Dict[Portal, Portal], Dict[int, str]]:
@@ -22,14 +23,19 @@ def create_er_regions(world: "TunicWorld") -> Tuple[Dict[Portal, Portal], Dict[i
portal_pairs: Dict[Portal, Portal] = pair_portals(world)
logic_rules = world.options.logic_rules
+ # output the entrances to the spoiler log here for convenience
+ for portal1, portal2 in portal_pairs.items():
+ world.multiworld.spoiler.set_entrance(portal1.name, portal2.name, "both", world.player)
+
# check if a portal leads to a hallway. if it does, update the hint text accordingly
def hint_helper(portal: Portal, hint_string: str = "") -> str:
# start by setting it as the name of the portal, for the case we're not using the hallway helper
if hint_string == "":
hint_string = portal.name
- if logic_rules:
- hallways = hallway_helper_nmg
+ # unrestricted has fewer hallways, like the well rail
+ if logic_rules == "unrestricted":
+ hallways = hallway_helper_ur
else:
hallways = hallway_helper
@@ -69,6 +75,7 @@ def hint_helper(portal: Portal, hint_string: str = "") -> str:
return hint_string
# create our regions, give them hint text if they're in a spot where it makes sense to
+ # we're limiting which ones get hints so that it still gets that ER feel with a little less BS
for region_name, region_data in tunic_er_regions.items():
hint_text = "error"
if region_data.hint == 1:
@@ -90,7 +97,7 @@ def hint_helper(portal: Portal, hint_string: str = "") -> str:
break
regions[region_name] = Region(region_name, world.player, world.multiworld, hint_text)
elif region_data.hint == 3:
- # only the west garden portal item for now
+ # west garden portal item is at a dead end in restricted, otherwise just in west garden
if region_name == "West Garden Portal Item":
if world.options.logic_rules:
for portal1, portal2 in portal_pairs.items():
@@ -178,9 +185,17 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
portal_pairs: Dict[Portal, Portal] = {}
dead_ends: List[Portal] = []
two_plus: List[Portal] = []
+ plando_connections: List[PlandoConnection] = []
fixed_shop = False
logic_rules = world.options.logic_rules.value
+ if not logic_rules:
+ dependent_regions = dependent_regions_restricted
+ elif logic_rules == 1:
+ dependent_regions = dependent_regions_nmg
+ else:
+ dependent_regions = dependent_regions_ur
+
# create separate lists for dead ends and non-dead ends
if logic_rules:
for portal in portal_mapping:
@@ -200,8 +215,46 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
start_region = "Overworld"
connected_regions.update(add_dependent_regions(start_region, logic_rules))
+ # universal tracker support stuff, don't need to care about region dependency
+ if hasattr(world.multiworld, "re_gen_passthrough"):
+ if "TUNIC" in world.multiworld.re_gen_passthrough:
+ # universal tracker stuff, won't do anything in normal gen
+ for portal1, portal2 in world.multiworld.re_gen_passthrough["TUNIC"]["Entrance Rando"].items():
+ portal_name1 = ""
+ portal_name2 = ""
+
+ # skip this if 10 fairies laurels location is on, it can be handled normally
+ if portal1 == "Overworld Redux, Waterfall_" and portal2 == "Waterfall, Overworld Redux_" \
+ and world.options.laurels_location == "10_fairies":
+ continue
+
+ for portal in portal_mapping:
+ if portal.scene_destination() == portal1:
+ portal_name1 = portal.name
+ # connected_regions.update(add_dependent_regions(portal.region, logic_rules))
+ if portal.scene_destination() == portal2:
+ portal_name2 = portal.name
+ # connected_regions.update(add_dependent_regions(portal.region, logic_rules))
+ # shops have special handling
+ if not portal_name2 and portal2 == "Shop, Previous Region_":
+ portal_name2 = "Shop Portal"
+ plando_connections.append(PlandoConnection(portal_name1, portal_name2, "both"))
+
+ if plando_connections:
+ portal_pairs, dependent_regions, dead_ends, two_plus = \
+ create_plando_connections(plando_connections, dependent_regions, dead_ends, two_plus)
+
+ # if we have plando connections, our connected regions may change somewhat
+ while True:
+ test1 = len(connected_regions)
+ for region in connected_regions.copy():
+ connected_regions.update(add_dependent_regions(region, logic_rules))
+ test2 = len(connected_regions)
+ if test1 == test2:
+ break
+
# need to plando fairy cave, or it could end up laurels locked
- # fix this later to be random? probably not?
+ # fix this later to be random after adding some item logic to dependent regions
if world.options.laurels_location == "10_fairies":
portal1 = None
portal2 = None
@@ -217,7 +270,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
two_plus.remove(portal1)
dead_ends.remove(portal2)
- if world.options.fixed_shop:
+ if world.options.fixed_shop and not hasattr(world.multiworld, "re_gen_passthrough"):
fixed_shop = True
portal1 = None
for portal in two_plus:
@@ -283,6 +336,11 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
shop_count = 1
shop_scenes.add("Overworld Redux")
+ # for universal tracker, we want to skip shop gen
+ if hasattr(world.multiworld, "re_gen_passthrough"):
+ if "TUNIC" in world.multiworld.re_gen_passthrough:
+ shop_count = 0
+
for i in range(shop_count):
portal1 = None
for portal in two_plus:
@@ -311,10 +369,7 @@ def pair_portals(world: "TunicWorld") -> Dict[Portal, Portal]:
portal_pairs[portal1] = portal2
if len(two_plus) == 1:
- raise Exception("two plus had an odd number of portals, investigate this")
-
- for portal1, portal2 in portal_pairs.items():
- world.multiworld.spoiler.set_entrance(portal1.name, portal2.name, "both", world.player)
+ raise Exception("two plus had an odd number of portals, investigate this. last portal is " + two_plus[0].name)
return portal_pairs
@@ -331,10 +386,11 @@ def create_randomized_entrances(portal_pairs: Dict[Portal, Portal], regions: Dic
# loop through the static connections, return regions you can reach from this region
+# todo: refactor to take region_name and dependent_regions
def add_dependent_regions(region_name: str, logic_rules: int) -> Set[str]:
region_set = set()
if not logic_rules:
- regions_to_add = dependent_regions
+ regions_to_add = dependent_regions_restricted
elif logic_rules == 1:
regions_to_add = dependent_regions_nmg
else:
@@ -451,3 +507,65 @@ def gate_before_switch(check_portal: Portal, two_plus: List[Portal]) -> bool:
# false means you're good to place the portal
return False
+
+
+# this is for making the connections themselves
+def create_plando_connections(plando_connections: List[PlandoConnection],
+ dependent_regions: Dict[Tuple[str, ...], List[str]], dead_ends: List[Portal],
+ two_plus: List[Portal]) \
+ -> Tuple[Dict[Portal, Portal], Dict[Tuple[str, ...], List[str]], List[Portal], List[Portal]]:
+
+ portal_pairs: Dict[Portal, Portal] = {}
+ shop_num = 1
+ for connection in plando_connections:
+ p_entrance = connection.entrance
+ p_exit = connection.exit
+
+ portal1 = None
+ portal2 = None
+
+ # search two_plus for both at once
+ for portal in two_plus:
+ if p_entrance == portal.name:
+ portal1 = portal
+ if p_exit == portal.name:
+ portal2 = portal
+
+ # search dead_ends individually since we can't really remove items from two_plus during the loop
+ if not portal1:
+ for portal in dead_ends:
+ if p_entrance == portal.name:
+ portal1 = portal
+ break
+ dead_ends.remove(portal1)
+ else:
+ two_plus.remove(portal1)
+
+ if not portal2:
+ for portal in dead_ends:
+ if p_exit == portal.name:
+ portal2 = portal
+ break
+ if p_exit == "Shop Portal":
+ portal2 = Portal(name="Shop Portal", region=f"Shop Entrance {shop_num}", destination="Previous Region_")
+ shop_num += 1
+ else:
+ dead_ends.remove(portal2)
+ else:
+ two_plus.remove(portal2)
+
+ if not portal1:
+ raise Exception("could not find entrance named " + p_entrance + " for Tunic player's plando")
+ if not portal2:
+ raise Exception("could not find entrance named " + p_exit + " for Tunic player's plando")
+
+ portal_pairs[portal1] = portal2
+
+ # update dependent regions based on the plando'd connections, to make sure the portals connect well, logically
+ for origins, destinations in dependent_regions.items():
+ if portal1.region in origins:
+ destinations.append(portal2.region)
+ if portal2.region in origins:
+ destinations.append(portal1.region)
+
+ return portal_pairs, dependent_regions, dead_ends, two_plus
diff --git a/worlds/tunic/items.py b/worlds/tunic/items.py
index 16608620c6e3..547a0ffb816f 100644
--- a/worlds/tunic/items.py
+++ b/worlds/tunic/items.py
@@ -141,7 +141,7 @@ class TunicItemData(NamedTuple):
"Pages 46-47": TunicItemData(ItemClassification.useful, 1, 125, "pages"),
"Pages 48-49": TunicItemData(ItemClassification.useful, 1, 126, "pages"),
"Pages 50-51": TunicItemData(ItemClassification.useful, 1, 127, "pages"),
- "Pages 52-53 (Ice Rod)": TunicItemData(ItemClassification.progression, 1, 128, "pages"),
+ "Pages 52-53 (Icebolt)": TunicItemData(ItemClassification.progression, 1, 128, "pages"),
"Pages 54-55": TunicItemData(ItemClassification.useful, 1, 129, "pages"),
}
@@ -176,7 +176,7 @@ class TunicItemData(NamedTuple):
"Hero Relic - MP",
"Pages 24-25 (Prayer)",
"Pages 42-43 (Holy Cross)",
- "Pages 52-53 (Ice Rod)",
+ "Pages 52-53 (Icebolt)",
"Red Questagon",
"Green Questagon",
"Blue Questagon",
@@ -204,10 +204,11 @@ def get_item_group(item_name: str) -> str:
"magic rod": {"Magic Wand"},
"holy cross": {"Pages 42-43 (Holy Cross)"},
"prayer": {"Pages 24-25 (Prayer)"},
- "ice rod": {"Pages 52-53 (Ice Rod)"},
+ "icebolt": {"Pages 52-53 (Icebolt)"},
+ "ice rod": {"Pages 52-53 (Icebolt)"},
"melee weapons": {"Stick", "Sword", "Sword Upgrade"},
"progressive sword": {"Sword Upgrade"},
- "abilities": {"Pages 24-25 (Prayer)", "Pages 42-43 (Holy Cross)", "Pages 52-53 (Ice Rod)"},
+ "abilities": {"Pages 24-25 (Prayer)", "Pages 42-43 (Holy Cross)", "Pages 52-53 (Icebolt)"},
"questagons": {"Red Questagon", "Green Questagon", "Blue Questagon", "Gold Questagon"}
}
diff --git a/worlds/tunic/options.py b/worlds/tunic/options.py
index 77fa2cdaf5bc..f4790da36729 100644
--- a/worlds/tunic/options.py
+++ b/worlds/tunic/options.py
@@ -23,7 +23,7 @@ class KeysBehindBosses(Toggle):
class AbilityShuffling(Toggle):
- """Locks the usage of Prayer, Holy Cross*, and Ice Rod until the relevant pages of the manual have been found.
+ """Locks the usage of Prayer, Holy Cross*, and the Icebolt combo until the relevant pages of the manual have been found.
If playing Hexagon Quest, abilities are instead randomly unlocked after obtaining 25%, 50%, and 75% of the required
Hexagon goal amount.
*Certain Holy Cross usages are still allowed, such as the free bomb codes, the seeking spell, and other
@@ -36,9 +36,10 @@ class AbilityShuffling(Toggle):
class LogicRules(Choice):
"""Set which logic rules to use for your world.
Restricted: Standard logic, no glitches.
- No Major Glitches: Ice grapples through doors, shooting the west bell, and boss quick kills are included in logic.
+ No Major Glitches: Sneaky Laurels zips, ice grapples through doors, shooting the west bell, and boss quick kills are included in logic.
+ * Ice grappling through the Ziggurat door is not in logic since you will get stuck in there without Prayer.
Unrestricted: Logic in No Major Glitches, as well as ladder storage to get to certain places early.
- *Special Shop is not in logic without the Hero's Laurels in Unrestricted due to soft lock potential.
+ *Special Shop is not in logic without the Hero's Laurels due to soft lock potential.
*Using Ladder Storage to get to individual chests is not in logic to avoid tedium.
*Getting knocked out of the air by enemies during Ladder Storage to reach places is not in logic, except for in
Rooted Ziggurat Lower. This is so you're not punished for playing with enemy rando on."""
@@ -46,7 +47,9 @@ class LogicRules(Choice):
display_name = "Logic Rules"
option_restricted = 0
option_no_major_glitches = 1
+ alias_nmg = 1
option_unrestricted = 2
+ alias_ur = 2
default = 0
diff --git a/worlds/tunic/regions.py b/worlds/tunic/regions.py
index 5d5248f210d6..70204c639733 100644
--- a/worlds/tunic/regions.py
+++ b/worlds/tunic/regions.py
@@ -16,7 +16,7 @@
"Eastern Vault Fortress": {"Beneath the Vault"},
"Beneath the Vault": {"Eastern Vault Fortress"},
"Quarry Back": {"Quarry"},
- "Quarry": {"Lower Quarry", "Rooted Ziggurat"},
+ "Quarry": {"Lower Quarry"},
"Lower Quarry": {"Rooted Ziggurat"},
"Rooted Ziggurat": set(),
"Swamp": {"Cathedral"},
diff --git a/worlds/tunic/rules.py b/worlds/tunic/rules.py
index 9906936a469f..b3dd0b683220 100644
--- a/worlds/tunic/rules.py
+++ b/worlds/tunic/rules.py
@@ -16,7 +16,7 @@
coins = "Golden Coin"
prayer = "Pages 24-25 (Prayer)"
holy_cross = "Pages 42-43 (Holy Cross)"
-ice_rod = "Pages 52-53 (Ice Rod)"
+icebolt = "Pages 52-53 (Icebolt)"
key = "Key"
house_key = "Old House Key"
vault_key = "Fortress Vault Key"
@@ -33,7 +33,7 @@ def randomize_ability_unlocks(random: Random, options: TunicOptions) -> Dict[str
hexagon_goal = options.hexagon_goal.value
# Set ability unlocks to 25, 50, and 75% of goal amount
ability_requirement = [hexagon_goal // 4, hexagon_goal // 2, hexagon_goal * 3 // 4]
- abilities = [prayer, holy_cross, ice_rod]
+ abilities = [prayer, holy_cross, icebolt]
random.shuffle(abilities)
return dict(zip(abilities, ability_requirement))
@@ -65,7 +65,7 @@ def has_ice_grapple_logic(long_range: bool, state: CollectionState, player: int,
return state.has_all({ice_dagger, grapple}, player)
else:
return state.has_all({ice_dagger, fire_wand, grapple}, player) and \
- has_ability(state, player, ice_rod, options, ability_unlocks)
+ has_ability(state, player, icebolt, options, ability_unlocks)
def can_ladder_storage(state: CollectionState, player: int, options: TunicOptions) -> bool:
@@ -130,10 +130,7 @@ def set_region_rules(world: "TunicWorld", ability_unlocks: Dict[str, int]) -> No
multiworld.get_entrance("Quarry -> Lower Quarry", player).access_rule = \
lambda state: has_mask(state, player, options)
multiworld.get_entrance("Lower Quarry -> Rooted Ziggurat", player).access_rule = \
- lambda state: (state.has(grapple, player) and has_ability(state, player, prayer, options, ability_unlocks)) \
- or has_ice_grapple_logic(False, state, player, options, ability_unlocks)
- multiworld.get_entrance("Quarry -> Rooted Ziggurat", player).access_rule = \
- lambda state: has_ice_grapple_logic(False, state, player, options, ability_unlocks)
+ lambda state: state.has(grapple, player) and has_ability(state, player, prayer, options, ability_unlocks)
multiworld.get_entrance("Swamp -> Cathedral", player).access_rule = \
lambda state: state.has(laurels, player) and has_ability(state, player, prayer, options, ability_unlocks) \
or has_ice_grapple_logic(False, state, player, options, ability_unlocks)
@@ -252,7 +249,7 @@ def set_location_rules(world: "TunicWorld", ability_unlocks: Dict[str, int]) ->
lambda state: state.has_all({grapple, laurels}, player))
set_rule(multiworld.get_location("East Forest - Ice Rod Grapple Chest", player),
lambda state: state.has_all({grapple, ice_dagger, fire_wand}, player)
- and has_ability(state, player, ice_rod, options, ability_unlocks))
+ and has_ability(state, player, icebolt, options, ability_unlocks))
# West Garden
set_rule(multiworld.get_location("West Garden - [North] Across From Page Pickup", player),
@@ -313,8 +310,9 @@ def set_location_rules(world: "TunicWorld", ability_unlocks: Dict[str, int]) ->
lambda state: state.has(laurels, player))
set_rule(multiworld.get_location("Quarry - [West] Upper Area Bombable Wall", player),
lambda state: has_mask(state, player, options))
+ # nmg - kill boss scav with orb + firecracker, or similar
set_rule(multiworld.get_location("Rooted Ziggurat Lower - Hexagon Blue", player),
- lambda state: has_sword(state, player))
+ lambda state: has_sword(state, player) or (state.has(grapple, player) and options.logic_rules))
# Swamp
set_rule(multiworld.get_location("Cathedral Gauntlet - Gauntlet Reward", player),
diff --git a/worlds/tunic/test/__init__.py b/worlds/tunic/test/__init__.py
index f8ab99d67d24..d7ae47f7d74c 100644
--- a/worlds/tunic/test/__init__.py
+++ b/worlds/tunic/test/__init__.py
@@ -2,5 +2,5 @@
class TunicTestBase(WorldTestBase):
- game = "Tunic"
+ game = "TUNIC"
player: int = 1
\ No newline at end of file
diff --git a/worlds/undertale/__init__.py b/worlds/undertale/__init__.py
index 9e784a4a59a0..0694456a6b12 100644
--- a/worlds/undertale/__init__.py
+++ b/worlds/undertale/__init__.py
@@ -29,7 +29,7 @@ def data_path(file_name: str):
class UndertaleWeb(WebWorld):
tutorials = [Tutorial(
- "Multiworld Setup Tutorial",
+ "Multiworld Setup Guide",
"A guide to setting up the Archipelago Undertale software on your computer. This guide covers "
"single-player, multiworld, and related software.",
"English",
diff --git a/worlds/witness/WitnessItems.txt b/worlds/witness/WitnessItems.txt
index e17464a0923a..6f63eccc9521 100644
--- a/worlds/witness/WitnessItems.txt
+++ b/worlds/witness/WitnessItems.txt
@@ -39,8 +39,8 @@ Jokes:
Doors:
1100 - Glass Factory Entry (Panel) - 0x01A54
-1101 - Tutorial Outpost Entry (Panel) - 0x0A171
-1102 - Tutorial Outpost Exit (Panel) - 0x04CA4
+1101 - Outside Tutorial Outpost Entry (Panel) - 0x0A171
+1102 - Outside Tutorial Outpost Exit (Panel) - 0x04CA4
1105 - Symmetry Island Lower (Panel) - 0x000B0
1107 - Symmetry Island Upper (Panel) - 0x1C349
1108 - Desert Surface 3 Control (Panel) - 0x09FA0
@@ -168,9 +168,9 @@ Doors:
1750 - Theater Entry (Door) - 0x17F88
1753 - Theater Exit Left (Door) - 0x0A16D
1756 - Theater Exit Right (Door) - 0x3CCDF
-1759 - Jungle Bamboo Laser Shortcut (Door) - 0x3873B
+1759 - Jungle Laser Shortcut (Door) - 0x3873B
1760 - Jungle Popup Wall (Door) - 0x1475B
-1762 - River Monastery Garden Shortcut (Door) - 0x0CF2A
+1762 - Jungle Monastery Garden Shortcut (Door) - 0x0CF2A
1765 - Bunker Entry (Door) - 0x0C2A4
1768 - Bunker Tinted Glass Door - 0x17C79
1771 - Bunker UV Room Entry (Door) - 0x0C2A3
@@ -195,7 +195,7 @@ Doors:
1828 - Mountain Floor 2 Exit (Door) - 0x09EDD
1831 - Mountain Floor 2 Staircase Far (Door) - 0x09E07
1834 - Mountain Bottom Floor Giant Puzzle Exit (Door) - 0x09F89
-1840 - Mountain Bottom Floor Final Room Entry (Door) - 0x0C141
+1840 - Mountain Bottom Floor Pillars Room Entry (Door) - 0x0C141
1843 - Mountain Bottom Floor Rock (Door) - 0x17F33
1846 - Caves Entry (Door) - 0x2D77D
1849 - Caves Pillar Door - 0x019A5
@@ -247,7 +247,7 @@ Doors:
2035 - Mountain & Caves Control Panels - 0x09ED8,0x09E86,0x09E39,0x09EEB,0x335AB,0x335AC,0x3369D
2100 - Symmetry Island Panels - 0x1C349,0x000B0
-2101 - Tutorial Outpost Panels - 0x0A171,0x04CA4
+2101 - Outside Tutorial Outpost Panels - 0x0A171,0x04CA4
2105 - Desert Panels - 0x09FAA,0x1C2DF,0x1831E,0x1C260,0x1831C,0x1C2F3,0x1831D,0x1C2B1,0x1831B,0x0C339,0x0A249,0x0A015,0x09FA0,0x09F86
2110 - Quarry Outside Panels - 0x17C09,0x09E57,0x17CC4
2115 - Quarry Stoneworks Panels - 0x01E5A,0x01E59,0x03678,0x03676,0x03679,0x03675
diff --git a/worlds/witness/WitnessLogic.txt b/worlds/witness/WitnessLogic.txt
index ec0922bec697..e3bacfb4b0e4 100644
--- a/worlds/witness/WitnessLogic.txt
+++ b/worlds/witness/WitnessLogic.txt
@@ -1,12 +1,14 @@
+==Tutorial (Inside)==
+
Menu (Menu) - Entry - True:
Entry (Entry):
-First Hallway (First Hallway) - Entry - True - First Hallway Room - 0x00064:
+Tutorial First Hallway (Tutorial First Hallway) - Entry - True - Tutorial First Hallway Room - 0x00064:
158000 - 0x00064 (Straight) - True - True
159510 - 0x01848 (EP) - 0x00064 - True
-First Hallway Room (First Hallway) - Tutorial - 0x00182:
+Tutorial First Hallway Room (Tutorial First Hallway) - Tutorial - 0x00182:
158001 - 0x00182 (Bend) - True - True
Tutorial (Tutorial) - Outside Tutorial - 0x03629:
@@ -23,6 +25,8 @@ Tutorial (Tutorial) - Outside Tutorial - 0x03629:
159513 - 0x33600 (Patio Flowers EP) - 0x0C373 - True
159517 - 0x3352F (Gate EP) - 0x03505 - True
+==Tutorial (Outside)==
+
Outside Tutorial (Outside Tutorial) - Outside Tutorial Path To Outpost - 0x03BA2 - Outside Tutorial Vault - 0x033D0:
158650 - 0x033D4 (Vault Panel) - True - Dots & Black/White Squares
Door - 0x033D0 (Vault Door) - 0x033D4
@@ -58,9 +62,23 @@ Outside Tutorial Outpost (Outside Tutorial) - Outside Tutorial - 0x04CA3:
Door - 0x04CA3 (Outpost Exit) - 0x04CA4
158600 - 0x17CFB (Discard) - True - Triangles
+Orchard (Orchard) - Main Island - True - Orchard Beyond First Gate - 0x03307:
+158071 - 0x00143 (Apple Tree 1) - True - True
+158072 - 0x0003B (Apple Tree 2) - 0x00143 - True
+158073 - 0x00055 (Apple Tree 3) - 0x0003B - True
+Door - 0x03307 (First Gate) - 0x00055
+
+Orchard Beyond First Gate (Orchard) - Orchard End - 0x03313:
+158074 - 0x032F7 (Apple Tree 4) - 0x00055 - True
+158075 - 0x032FF (Apple Tree 5) - 0x032F7 - True
+Door - 0x03313 (Second Gate) - 0x032FF
+
+Orchard End (Orchard):
+
Main Island (Main Island) - Outside Tutorial - True:
159801 - 0xFFD00 (Reached Independently) - True - True
-159550 - 0x28B91 (Thundercloud EP) - 0x09F98 & 0x012FB - True
+
+==Glass Factory==
Outside Glass Factory (Glass Factory) - Main Island - True - Inside Glass Factory - 0x01A29:
158027 - 0x01A54 (Entry Panel) - True - Symmetry
@@ -85,6 +103,8 @@ Door - 0x0D7ED (Back Wall) - 0x0005C
Inside Glass Factory Behind Back Wall (Glass Factory) - The Ocean - 0x17CC8:
158039 - 0x17CC8 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat
+==Symmetry Island==
+
Outside Symmetry Island (Symmetry Island) - Main Island - True - Symmetry Island Lower - 0x17F3E:
158040 - 0x000B0 (Lower Panel) - 0x0343A - Dots
Door - 0x17F3E (Lower) - 0x000B0
@@ -128,20 +148,17 @@ Symmetry Island Upper (Symmetry Island):
Laser - 0x00509 (Laser) - 0x0360D
159001 - 0x03367 (Glass Factory Black Line EP) - True - True
-Orchard (Orchard) - Main Island - True - Orchard Beyond First Gate - 0x03307:
-158071 - 0x00143 (Apple Tree 1) - True - True
-158072 - 0x0003B (Apple Tree 2) - 0x00143 - True
-158073 - 0x00055 (Apple Tree 3) - 0x0003B - True
-Door - 0x03307 (First Gate) - 0x00055
-
-Orchard Beyond First Gate (Orchard) - Orchard End - 0x03313:
-158074 - 0x032F7 (Apple Tree 4) - 0x00055 - True
-158075 - 0x032FF (Apple Tree 5) - 0x032F7 - True
-Door - 0x03313 (Second Gate) - 0x032FF
+==Desert==
-Orchard End (Orchard):
+Desert Obelisk (Desert) - Entry - True:
+159700 - 0xFFE00 (Obelisk Side 1) - 0x0332B & 0x03367 & 0x28B8A - True
+159701 - 0xFFE01 (Obelisk Side 2) - 0x037B6 & 0x037B2 & 0x000F7 - True
+159702 - 0xFFE02 (Obelisk Side 3) - 0x3351D - True
+159703 - 0xFFE03 (Obelisk Side 4) - 0x0053C & 0x00771 & 0x335C8 & 0x335C9 & 0x337F8 & 0x037BB & 0x220E4 & 0x220E5 - True
+159704 - 0xFFE04 (Obelisk Side 5) - 0x334B9 & 0x334BC & 0x22106 & 0x0A14C & 0x0A14D - True
+159709 - 0x00359 (Obelisk) - True - True
-Desert Outside (Desert) - Main Island - True - Desert Floodlight Room - 0x09FEE - Desert Vault - 0x03444:
+Desert Outside (Desert) - Main Island - True - Desert Light Room - 0x09FEE - Desert Vault - 0x03444:
158652 - 0x0CC7B (Vault Panel) - True - Dots & Shapers & Rotated Shapers & Negative Shapers & Full Dots
Door - 0x03444 (Vault Door) - 0x0CC7B
158602 - 0x17CE7 (Discard) - True - Triangles
@@ -172,14 +189,14 @@ Laser - 0x012FB (Laser) - 0x03608
Desert Vault (Desert):
158653 - 0x0339E (Vault Box) - True - True
-Desert Floodlight Room (Desert) - Desert Pond Room - 0x0C2C3:
+Desert Light Room (Desert) - Desert Pond Room - 0x0C2C3:
158087 - 0x09FAA (Light Control) - True - True
158088 - 0x00422 (Light Room 1) - 0x09FAA - True
158089 - 0x006E3 (Light Room 2) - 0x09FAA - True
158090 - 0x0A02D (Light Room 3) - 0x09FAA & 0x00422 & 0x006E3 - True
Door - 0x0C2C3 (Pond Room Entry) - 0x0A02D
-Desert Pond Room (Desert) - Desert Water Levels Room - 0x0A24B:
+Desert Pond Room (Desert) - Desert Flood Room - 0x0A24B:
158091 - 0x00C72 (Pond Room 1) - True - True
158092 - 0x0129D (Pond Room 2) - 0x00C72 - True
158093 - 0x008BB (Pond Room 3) - 0x0129D - True
@@ -190,7 +207,7 @@ Door - 0x0A24B (Flood Room Entry) - 0x0A249
159043 - 0x0A14C (Pond Room Near Reflection EP) - True - True
159044 - 0x0A14D (Pond Room Far Reflection EP) - True - True
-Desert Water Levels Room (Desert) - Desert Elevator Room - 0x0C316:
+Desert Flood Room (Desert) - Desert Elevator Room - 0x0C316:
158097 - 0x1C2DF (Reduce Water Level Far Left) - True - True
158098 - 0x1831E (Reduce Water Level Far Right) - True - True
158099 - 0x1C260 (Reduce Water Level Near Left) - True - True
@@ -208,7 +225,7 @@ Desert Water Levels Room (Desert) - Desert Elevator Room - 0x0C316:
Door - 0x0C316 (Elevator Room Entry) - 0x18076
159034 - 0x337F8 (Flood Room EP) - 0x1C2DF - True
-Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x01317:
+Desert Elevator Room (Desert) - Desert Behind Elevator - 0x01317:
158111 - 0x17C31 (Elevator Room Transparent) - True - True
158113 - 0x012D7 (Elevator Room Hexagonal) - 0x17C31 & 0x0A015 - True
158114 - 0x0A015 (Elevator Room Hexagonal Control) - 0x17C31 - True
@@ -218,9 +235,19 @@ Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x0131
159035 - 0x037BB (Elevator EP) - 0x01317 - True
Door - 0x01317 (Elevator) - 0x03608
-Desert Lowest Level Inbetween Shortcuts (Desert):
+Desert Behind Elevator (Desert):
-Outside Quarry (Quarry) - Main Island - True - Quarry Between Entrys - 0x09D6F - Quarry Elevator - 0xFFD00 & 0xFFD01:
+==Quarry==
+
+Quarry Obelisk (Quarry) - Entry - True:
+159740 - 0xFFE40 (Obelisk Side 1) - 0x28A7B & 0x005F6 & 0x00859 & 0x17CB9 & 0x28A4A - True
+159741 - 0xFFE41 (Obelisk Side 2) - 0x334B6 & 0x00614 & 0x0069D & 0x28A4C - True
+159742 - 0xFFE42 (Obelisk Side 3) - 0x289CF & 0x289D1 - True
+159743 - 0xFFE43 (Obelisk Side 4) - 0x33692 - True
+159744 - 0xFFE44 (Obelisk Side 5) - 0x03E77 & 0x03E7C - True
+159749 - 0x22073 (Obelisk) - True - True
+
+Outside Quarry (Quarry) - Main Island - True - Quarry Between Entry Doors - 0x09D6F - Quarry Elevator - 0xFFD00 & 0xFFD01:
158118 - 0x09E57 (Entry 1 Panel) - True - Black/White Squares
158603 - 0x17CF0 (Discard) - True - Triangles
158702 - 0x03612 (Laser Panel) - 0x0A3D0 & 0x0367C - Eraser & Shapers
@@ -236,7 +263,7 @@ Quarry Elevator (Quarry) - Outside Quarry - 0x17CC4 - Quarry - 0x17CC4:
158120 - 0x17CC4 (Elevator Control) - 0x0367C - Dots & Eraser
159403 - 0x17CB9 (Railroad EP) - 0x17CC4 - True
-Quarry Between Entrys (Quarry) - Quarry - 0x17C07:
+Quarry Between Entry Doors (Quarry) - Quarry - 0x17C07:
158119 - 0x17C09 (Entry 2 Panel) - True - Shapers
Door - 0x17C07 (Entry 2) - 0x17C09
@@ -322,6 +349,8 @@ Door - 0x3865F (Second Barrier) - 0x38663
158169 - 0x0A3D0 (Back Second Row 3) - 0x0A3CC - Stars & Eraser & Shapers
159401 - 0x005F6 (Hook EP) - 0x275FA & 0x03852 & 0x3865F - True
+==Shadows==
+
Shadows (Shadows) - Main Island - True - Shadows Ledge - 0x19B24 - Shadows Laser Room - 0x194B2 | 0x19665:
158170 - 0x334DB (Door Timer Outside) - True - True
Door - 0x19B24 (Timed Door) - 0x334DB | 0x334DC
@@ -361,19 +390,18 @@ Shadows Laser Room (Shadows):
158703 - 0x19650 (Laser Panel) - 0x194B2 & 0x19665 - True
Laser - 0x181B3 (Laser) - 0x19650
-Treehouse Beach (Treehouse Beach) - Main Island - True:
-159200 - 0x0053D (Rock Shadow EP) - True - True
-159201 - 0x0053E (Sand Shadow EP) - True - True
-159212 - 0x220BD (Both Orange Bridges EP) - 0x17DA2 & 0x17DDB - True
+==Keep==
-Keep (Keep) - Main Island - True - Keep 2nd Maze - 0x01954 - Keep 2nd Pressure Plate - 0x01BEC:
+Outside Keep (Keep) - Main Island - True:
+159430 - 0x03E77 (Red Flowers EP) - True - True
+159431 - 0x03E7C (Purple Flowers EP) - True - True
+
+Keep (Keep) - Outside Keep - True - Keep 2nd Maze - 0x01954 - Keep 2nd Pressure Plate - 0x01BEC:
158193 - 0x00139 (Hedge Maze 1) - True - True
158197 - 0x0A3A8 (Reset Pressure Plates 1) - True - True
158198 - 0x033EA (Pressure Plates 1) - 0x0A3A8 - Dots
Door - 0x01954 (Hedge Maze 1 Exit) - 0x00139
Door - 0x01BEC (Pressure Plates 1 Exit) - 0x033EA
-159430 - 0x03E77 (Red Flowers EP) - True - True
-159431 - 0x03E7C (Purple Flowers EP) - True - True
Keep 2nd Maze (Keep) - Keep - 0x018CE - Keep 3rd Maze - 0x019D8:
Door - 0x018CE (Hedge Maze 2 Shortcut) - 0x00139
@@ -408,6 +436,22 @@ Door - 0x01D40 (Pressure Plates 4 Exit) - 0x01D3F
158205 - 0x09E49 (Shadows Shortcut Panel) - True - True
Door - 0x09E3D (Shadows Shortcut) - 0x09E49
+Keep Tower (Keep) - Keep - 0x04F8F:
+158206 - 0x0361B (Tower Shortcut Panel) - True - True
+Door - 0x04F8F (Tower Shortcut) - 0x0361B
+158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True
+158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Shapers & Black/White Squares & Colored Squares & Stars & Stars + Same Colored Symbol & Dots
+Laser - 0x014BB (Laser) - 0x0360E | 0x03317
+159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True
+159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 - True
+159242 - 0x033DD (Pressure Plates 3 EP) - 0x01CD3 & 0x01CD5 - True
+159243 - 0x033E5 (Pressure Plates 4 Left Exit EP) - 0x01D3F - True
+159244 - 0x018B6 (Pressure Plates 4 Right Exit EP) - 0x01D3F - True
+159250 - 0x28AE9 (Path EP) - True - True
+159251 - 0x3348F (Hedges EP) - True - True
+
+==Shipwreck==
+
Shipwreck (Shipwreck) - Keep 3rd Pressure Plate - True - Shipwreck Vault - 0x17BB4:
158654 - 0x00AFB (Vault Panel) - True - Symmetry & Sound Dots & Colored Dots
Door - 0x17BB4 (Vault Door) - 0x00AFB
@@ -423,19 +467,16 @@ Door - 0x17BB4 (Vault Door) - 0x00AFB
Shipwreck Vault (Shipwreck):
158655 - 0x03535 (Vault Box) - True - True
-Keep Tower (Keep) - Keep - 0x04F8F:
-158206 - 0x0361B (Tower Shortcut Panel) - True - True
-Door - 0x04F8F (Tower Shortcut) - 0x0361B
-158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True
-158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Shapers & Black/White Squares & Colored Squares & Stars & Stars + Same Colored Symbol & Dots
-Laser - 0x014BB (Laser) - 0x0360E | 0x03317
-159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True
-159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 - True
-159242 - 0x033DD (Pressure Plates 3 EP) - 0x01CD3 & 0x01CD5 - True
-159243 - 0x033E5 (Pressure Plates 4 Left Exit EP) - 0x01D3F - True
-159244 - 0x018B6 (Pressure Plates 4 Right Exit EP) - 0x01D3F - True
-159250 - 0x28AE9 (Path EP) - True - True
-159251 - 0x3348F (Hedges EP) - True - True
+==Monastery==
+
+Monastery Obelisk (Monastery) - Entry - True:
+159710 - 0xFFE10 (Obelisk Side 1) - 0x03ABC & 0x03ABE & 0x03AC0 & 0x03AC4 - True
+159711 - 0xFFE11 (Obelisk Side 2) - 0x03AC5 - True
+159712 - 0xFFE12 (Obelisk Side 3) - 0x03BE2 & 0x03BE3 & 0x0A409 - True
+159713 - 0xFFE13 (Obelisk Side 4) - 0x006E5 & 0x006E6 & 0x006E7 & 0x034A7 & 0x034AD & 0x034AF & 0x03DAB & 0x03DAC & 0x03DAD - True
+159714 - 0xFFE14 (Obelisk Side 5) - 0x03E01 - True
+159715 - 0xFFE15 (Obelisk Side 6) - 0x289F4 & 0x289F5 - True
+159719 - 0x00263 (Obelisk) - True - True
Outside Monastery (Monastery) - Main Island - True - Inside Monastery - 0x0C128 & 0x0C153 - Monastery Garden - 0x03750:
158207 - 0x03713 (Laser Shortcut Panel) - True - True
@@ -457,6 +498,9 @@ Laser - 0x17C65 (Laser) - 0x17CA4
159137 - 0x03DAC (Facade Left Stairs EP) - True - True
159138 - 0x03DAD (Facade Right Stairs EP) - True - True
159140 - 0x03E01 (Grass Stairs EP) - True - True
+159120 - 0x03BE2 (Garden Left EP) - 0x03750 - True
+159121 - 0x03BE3 (Garden Right EP) - True - True
+159122 - 0x0A409 (Wall EP) - True - True
Inside Monastery (Monastery):
158213 - 0x09D9B (Shutters Control) - True - Dots
@@ -470,7 +514,18 @@ Inside Monastery (Monastery):
Monastery Garden (Monastery):
-Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x28AA2 - Town Church - True - Town Wooden Rooftop - 0x034F5 - RGB House - 0x28A61 - Windmill Interior - 0x1845B - Town Inside Cargo Box - 0x0A0C9:
+==Town==
+
+Town Obelisk (Town) - Entry - True:
+159750 - 0xFFE50 (Obelisk Side 1) - 0x035C7 - True
+159751 - 0xFFE51 (Obelisk Side 2) - 0x01848 & 0x03D06 & 0x33530 & 0x33600 & 0x28A2F & 0x28A37 & 0x334A3 & 0x3352F - True
+159752 - 0xFFE52 (Obelisk Side 3) - 0x33857 & 0x33879 & 0x03C19 - True
+159753 - 0xFFE53 (Obelisk Side 4) - 0x28B30 & 0x035C9 - True
+159754 - 0xFFE54 (Obelisk Side 5) - 0x03335 & 0x03412 & 0x038A6 & 0x038AA & 0x03E3F & 0x03E40 & 0x28B8E - True
+159755 - 0xFFE55 (Obelisk Side 6) - 0x28B91 & 0x03BCE & 0x03BCF & 0x03BD1 & 0x339B6 & 0x33A20 & 0x33A29 & 0x33A2A & 0x33B06 - True
+159759 - 0x0A16C (Obelisk) - True - True
+
+Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x28AA2 - Town Church - True - Town Wooden Rooftop - 0x034F5 - Town RGB House - 0x28A61 - Town Inside Cargo Box - 0x0A0C9 - Outside Windmill - True:
158218 - 0x0A054 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat
158219 - 0x0A0C8 (Cargo Box Entry Panel) - True - Black/White Squares & Shapers
Door - 0x0A0C9 (Cargo Box Entry) - 0x0A0C8
@@ -491,11 +546,6 @@ Door - 0x28A61 (RGB House Entry) - 0x28998
Door - 0x03BB0 (Church Entry) - 0x28A0D
158228 - 0x28A79 (Maze Panel) - True - True
Door - 0x28AA2 (Maze Stairs) - 0x28A79
-158241 - 0x17F5F (Windmill Entry Panel) - True - Dots
-Door - 0x1845B (Windmill Entry) - 0x17F5F
-159010 - 0x037B6 (Windmill First Blade EP) - 0x17D02 - True
-159011 - 0x037B2 (Windmill Second Blade EP) - 0x17D02 - True
-159012 - 0x000F7 (Windmill Third Blade EP) - 0x17D02 - True
159540 - 0x03335 (Tower Underside Third EP) - True - True
159541 - 0x03412 (Tower Underside Fourth EP) - True - True
159542 - 0x038A6 (Tower Underside First EP) - True - True
@@ -528,20 +578,26 @@ Town Church (Town):
158227 - 0x28A69 (Church Lattice) - 0x03BB0 - True
159553 - 0x03BD1 (Black Line Church EP) - True - True
-RGB House (Town) - RGB Room - 0x2897B:
+Town RGB House (Town RGB House) - Town RGB House Upstairs - 0x2897B:
158242 - 0x034E4 (Sound Room Left) - True - True
158243 - 0x034E3 (Sound Room Right) - True - Sound Dots
-Door - 0x2897B (RGB House Stairs) - 0x034E4 & 0x034E3
+Door - 0x2897B (Stairs) - 0x034E4 & 0x034E3
-RGB Room (Town):
+Town RGB House Upstairs (Town RGB House Upstairs):
158244 - 0x334D8 (RGB Control) - True - Rotated Shapers & Colored Squares
-158245 - 0x03C0C (RGB Room Left) - 0x334D8 - Colored Squares & Black/White Squares
-158246 - 0x03C08 (RGB Room Right) - 0x334D8 - Stars
+158245 - 0x03C0C (Left) - 0x334D8 - Colored Squares & Black/White Squares
+158246 - 0x03C08 (Right) - 0x334D8 - Stars
+
+Town Tower Bottom (Town Tower) - Town - True - Town Tower After First Door - 0x27799:
+Door - 0x27799 (First Door) - 0x28A69
-Town Tower (Town Tower) - Town - True - Town Tower Top - 0x27798 & 0x27799 & 0x2779A & 0x2779C:
+Town Tower After First Door (Town Tower) - Town Tower After Second Door - 0x27798:
Door - 0x27798 (Second Door) - 0x28ACC
+
+Town Tower After Second Door (Town Tower) - Town Tower After Third Door - 0x2779C:
Door - 0x2779C (Third Door) - 0x28AD9
-Door - 0x27799 (First Door) - 0x28A69
+
+Town Tower After Third Door (Town Tower) - Town Tower Top - 0x2779A:
Door - 0x2779A (Fourth Door) - 0x28B39
Town Tower Top (Town):
@@ -550,6 +606,15 @@ Laser - 0x032F9 (Laser) - 0x032F5
159422 - 0x33692 (Brown Bridge EP) - True - True
159551 - 0x03BCE (Black Line Tower EP) - True - True
+==Windmill & Theater==
+
+Outside Windmill (Windmill) - Windmill Interior - 0x1845B:
+159010 - 0x037B6 (First Blade EP) - 0x17D02 - True
+159011 - 0x037B2 (Second Blade EP) - 0x17D02 - True
+159012 - 0x000F7 (Third Blade EP) - 0x17D02 - True
+158241 - 0x17F5F (Entry Panel) - True - Dots
+Door - 0x1845B (Entry) - 0x17F5F
+
Windmill Interior (Windmill) - Theater - 0x17F88:
158247 - 0x17D02 (Turn Control) - True - Dots
158248 - 0x17F89 (Theater Entry Panel) - True - Black/White Squares
@@ -573,6 +638,8 @@ Door - 0x3CCDF (Exit Right) - 0x33AB2
159556 - 0x33A2A (Door EP) - 0x03553 - True
159558 - 0x33B06 (Church EP) - 0x0354E - True
+==Jungle==
+
Jungle (Jungle) - Main Island - True - The Ocean - 0x17CDF:
158251 - 0x17CDF (Shore Boat Spawn) - True - Boat
158609 - 0x17F9B (Discard) - True - Triangles
@@ -604,19 +671,18 @@ Door - 0x3873B (Laser Shortcut) - 0x337FA
159350 - 0x035CB (Bamboo CCW EP) - True - True
159351 - 0x035CF (Bamboo CW EP) - True - True
-Outside Jungle River (River) - Main Island - True - Monastery Garden - 0x0CF2A - River Vault - 0x15287:
+Outside Jungle River (Jungle) - Main Island - True - Monastery Garden - 0x0CF2A - Jungle Vault - 0x15287:
158267 - 0x17CAA (Monastery Garden Shortcut Panel) - True - True
Door - 0x0CF2A (Monastery Garden Shortcut) - 0x17CAA
158663 - 0x15ADD (Vault Panel) - True - Black/White Squares & Dots
Door - 0x15287 (Vault Door) - 0x15ADD
159110 - 0x03AC5 (Green Leaf Moss EP) - True - True
-159120 - 0x03BE2 (Monastery Garden Left EP) - 0x03750 - True
-159121 - 0x03BE3 (Monastery Garden Right EP) - True - True
-159122 - 0x0A409 (Monastery Wall EP) - True - True
-River Vault (River):
+Jungle Vault (Jungle):
158664 - 0x03702 (Vault Box) - True - True
+==Bunker==
+
Outside Bunker (Bunker) - Main Island - True - Bunker - 0x0C2A4:
158268 - 0x17C2E (Entry Panel) - True - Black/White Squares
Door - 0x0C2A4 (Entry) - 0x17C2E
@@ -650,9 +716,11 @@ Door - 0x0A08D (Elevator Room Entry) - 0x17E67
Bunker Elevator Section (Bunker) - Bunker Elevator - TrueOneWay:
159311 - 0x035F5 (Tinted Door EP) - 0x17C79 - True
-Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079:
+Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Cyan Room - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079:
158286 - 0x0A079 (Elevator Control) - True - Colored Squares & Black/White Squares
+Bunker Cyan Room (Bunker) - Bunker Elevator - TrueOneWay:
+
Bunker Green Room (Bunker) - Bunker Elevator - TrueOneWay:
159310 - 0x000D3 (Green Room Flowers EP) - True - True
@@ -660,6 +728,8 @@ Bunker Laser Platform (Bunker) - Bunker Elevator - TrueOneWay:
158710 - 0x09DE0 (Laser Panel) - True - True
Laser - 0x0C2B2 (Laser) - 0x09DE0
+==Swamp==
+
Outside Swamp (Swamp) - Swamp Entry Area - 0x00C1C - Main Island - True:
158287 - 0x0056E (Entry Panel) - True - Shapers
Door - 0x00C1C (Entry) - 0x0056E
@@ -774,13 +844,29 @@ Laser - 0x00BF6 (Laser) - 0x03615
158342 - 0x17C02 (Laser Shortcut Right Panel) - 0x17C05 - Shapers & Negative Shapers & Rotated Shapers
Door - 0x2D880 (Laser Shortcut) - 0x17C02
-Treehouse Entry Area (Treehouse) - Treehouse Between Doors - 0x0C309 - The Ocean - 0x17C95:
+==Treehouse==
+
+Treehouse Obelisk (Treehouse) - Entry - True:
+159720 - 0xFFE20 (Obelisk Side 1) - 0x0053D & 0x0053E & 0x00769 - True
+159721 - 0xFFE21 (Obelisk Side 2) - 0x33721 & 0x220A7 & 0x220BD - True
+159722 - 0xFFE22 (Obelisk Side 3) - 0x03B22 & 0x03B23 & 0x03B24 & 0x03B25 & 0x03A79 & 0x28ABD & 0x28ABE - True
+159723 - 0xFFE23 (Obelisk Side 4) - 0x3388F & 0x28B29 & 0x28B2A - True
+159724 - 0xFFE24 (Obelisk Side 5) - 0x018B6 & 0x033BE & 0x033BF & 0x033DD & 0x033E5 - True
+159725 - 0xFFE25 (Obelisk Side 6) - 0x28AE9 & 0x3348F - True
+159729 - 0x00097 (Obelisk) - True - True
+
+Treehouse Beach (Treehouse Beach) - Main Island - True:
+159200 - 0x0053D (Rock Shadow EP) - True - True
+159201 - 0x0053E (Sand Shadow EP) - True - True
+159212 - 0x220BD (Both Orange Bridges EP) - 0x17DA2 & 0x17DDB - True
+
+Treehouse Entry Area (Treehouse) - Treehouse Between Entry Doors - 0x0C309 - The Ocean - 0x17C95:
158343 - 0x17C95 (Boat Spawn) - True - Boat
158344 - 0x0288C (First Door Panel) - True - Stars
Door - 0x0C309 (First Door) - 0x0288C
159210 - 0x33721 (Buoy EP) - 0x17C95 - True
-Treehouse Between Doors (Treehouse) - Treehouse Yellow Bridge - 0x0C310:
+Treehouse Between Entry Doors (Treehouse) - Treehouse Yellow Bridge - 0x0C310:
158345 - 0x02886 (Second Door Panel) - True - Stars
Door - 0x0C310 (Second Door) - 0x02886
@@ -809,7 +895,7 @@ Treehouse First Purple Bridge (Treehouse) - Treehouse Second Purple Bridge - 0x1
158360 - 0x17D2D (First Purple Bridge 4) - 0x17CE4 - Stars & Dots
158361 - 0x17D6C (First Purple Bridge 5) - 0x17D2D - Stars & Dots
-Treehouse Right Orange Bridge (Treehouse) - Treehouse Bridge Platform - 0x17DA2:
+Treehouse Right Orange Bridge (Treehouse) - Treehouse Drawbridge Platform - 0x17DA2:
158391 - 0x17D88 (Right Orange Bridge 1) - True - Stars
158392 - 0x17DB4 (Right Orange Bridge 2) - 0x17D88 - Stars
158393 - 0x17D8C (Right Orange Bridge 3) - 0x17DB4 - Stars
@@ -823,7 +909,7 @@ Treehouse Right Orange Bridge (Treehouse) - Treehouse Bridge Platform - 0x17DA2:
158401 - 0x17DB1 (Right Orange Bridge 11) - 0x17DB7 - Stars
158402 - 0x17DA2 (Right Orange Bridge 12) - 0x17DB1 - Stars
-Treehouse Bridge Platform (Treehouse) - Main Island - 0x0C32D:
+Treehouse Drawbridge Platform (Treehouse) - Main Island - 0x0C32D:
158404 - 0x037FF (Drawbridge Panel) - True - Stars
Door - 0x0C32D (Drawbridge) - 0x037FF
@@ -882,7 +968,19 @@ Treehouse Laser Room (Treehouse):
158403 - 0x17CBC (Laser House Door Timer Inside) - True - True
Laser - 0x028A4 (Laser) - 0x03613
+==Mountain (Outside)==
+
+Mountainside Obelisk (Mountainside) - Entry - True:
+159730 - 0xFFE30 (Obelisk Side 1) - 0x001A3 & 0x335AE - True
+159731 - 0xFFE31 (Obelisk Side 2) - 0x000D3 & 0x035F5 & 0x09D5D & 0x09D5E & 0x09D63 - True
+159732 - 0xFFE32 (Obelisk Side 3) - 0x3370E & 0x035DE & 0x03601 & 0x03603 & 0x03D0D & 0x3369A & 0x336C8 & 0x33505 - True
+159733 - 0xFFE33 (Obelisk Side 4) - 0x03A9E & 0x016B2 & 0x3365F & 0x03731 & 0x036CE & 0x03C07 & 0x03A93 - True
+159734 - 0xFFE34 (Obelisk Side 5) - 0x03AA6 & 0x3397C & 0x0105D & 0x0A304 - True
+159735 - 0xFFE35 (Obelisk Side 6) - 0x035CB & 0x035CF - True
+159739 - 0x00367 (Obelisk) - True - True
+
Mountainside (Mountainside) - Main Island - True - Mountaintop - True - Mountainside Vault - 0x00085:
+159550 - 0x28B91 (Thundercloud EP) - 0x09F98 & 0x012FB - True
158612 - 0x17C42 (Discard) - True - Triangles
158665 - 0x002A6 (Vault Panel) - True - Symmetry & Colored Dots & Black/White Squares & Dots
Door - 0x00085 (Vault Door) - 0x002A6
@@ -893,7 +991,7 @@ Door - 0x00085 (Vault Door) - 0x002A6
Mountainside Vault (Mountainside):
158666 - 0x03542 (Vault Box) - True - True
-Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34:
+Mountaintop (Mountaintop) - Mountain Floor 1 - 0x17C34:
158405 - 0x0042D (River Shape) - True - True
158406 - 0x09F7F (Box Short) - 7 Lasers + Redirect - True
158407 - 0x17C34 (Mountain Entry Panel) - 0x09F7F - Stars & Black/White Squares & Stars + Same Colored Symbol
@@ -903,10 +1001,12 @@ Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34:
159324 - 0x336C8 (Arch White Right EP) - True - True
159326 - 0x3369A (Arch White Left EP) - True - True
-Mountain Top Layer (Mountain Floor 1) - Mountain Top Layer Bridge - 0x09E39:
+==Mountain (Inside)==
+
+Mountain Floor 1 (Mountain Floor 1) - Mountain Floor 1 Bridge - 0x09E39:
158408 - 0x09E39 (Light Bridge Controller) - True - Black/White Squares & Colored Squares & Eraser
-Mountain Top Layer Bridge (Mountain Floor 1) - Mountain Top Layer At Door - TrueOneWay:
+Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneWay:
158409 - 0x09E7A (Right Row 1) - True - Black/White Squares & Dots
158410 - 0x09E71 (Right Row 2) - 0x09E7A - Black/White Squares & Dots
158411 - 0x09E72 (Right Row 3) - 0x09E71 - Black/White Squares & Shapers & Dots
@@ -925,10 +1025,10 @@ Mountain Top Layer Bridge (Mountain Floor 1) - Mountain Top Layer At Door - True
158424 - 0x09EAD (Trash Pillar 1) - True - Black/White Squares & Shapers
158425 - 0x09EAF (Trash Pillar 2) - 0x09EAD - Black/White Squares & Shapers
-Mountain Top Layer At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54:
+Mountain Floor 1 At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54:
Door - 0x09E54 (Exit) - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B
-Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Blue Bridge - 0x09E86 - Mountain Pink Bridge EP - TrueOneWay:
+Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Beyond Bridge - 0x09E86 - Mountain Floor 2 At Door - 0x09ED8 & 0x09E86 - Mountain Pink Bridge EP - TrueOneWay:
158426 - 0x09FD3 (Near Row 1) - True - Stars & Colored Squares & Stars + Same Colored Symbol
158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Stars & Colored Squares & Stars + Same Colored Symbol
158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Stars & Colored Squares & Stars + Same Colored Symbol
@@ -936,8 +1036,6 @@ Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near -
158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Colored Squares & Symmetry & Colored Dots
Door - 0x09FFB (Staircase Near) - 0x09FD8
-Mountain Floor 2 Blue Bridge (Mountain Floor 2) - Mountain Floor 2 Beyond Bridge - TrueOneWay - Mountain Floor 2 At Door - 0x09ED8:
-
Mountain Floor 2 At Door (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EDD:
Door - 0x09EDD (Elevator Room Entry) - 0x09ED8 & 0x09E86
@@ -959,10 +1057,10 @@ Mountain Floor 2 Light Bridge Room Far (Mountain Floor 2):
Mountain Floor 2 Elevator Room (Mountain Floor 2) - Mountain Floor 2 Elevator - TrueOneWay:
158613 - 0x17F93 (Elevator Discard) - True - Triangles
-Mountain Floor 2 Elevator (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EEB - Mountain Third Layer - 0x09EEB:
+Mountain Floor 2 Elevator (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EEB - Mountain Floor 3 - 0x09EEB:
158439 - 0x09EEB (Elevator Control Panel) - True - Dots
-Mountain Third Layer (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueOneWay - Mountain Bottom Floor - 0x09F89 - Mountain Pink Bridge EP - TrueOneWay:
+Mountain Floor 3 (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueOneWay - Mountain Bottom Floor - 0x09F89 - Mountain Pink Bridge EP - TrueOneWay:
158440 - 0x09FC1 (Giant Puzzle Bottom Left) - True - Shapers & Eraser
158441 - 0x09F8E (Giant Puzzle Bottom Right) - True - Shapers & Eraser
158442 - 0x09F01 (Giant Puzzle Top Right) - True - Rotated Shapers
@@ -972,13 +1070,32 @@ Mountain Third Layer (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueO
159314 - 0x09D5E (Blue Bridge EP) - 0x09E86 & 0x09ED8 - True
Door - 0x09F89 (Exit) - 0x09FDA
-Mountain Bottom Floor (Mountain Bottom Floor) - Mountain Path to Caves - 0x17F33 - Final Room - 0x0C141:
+Mountain Bottom Floor (Mountain Bottom Floor) - Mountain Path to Caves - 0x17F33 - Mountain Bottom Floor Pillars Room - 0x0C141:
158614 - 0x17FA2 (Discard) - 0xFFF00 - Triangles
-158445 - 0x01983 (Final Room Entry Left) - True - Shapers & Stars
-158446 - 0x01987 (Final Room Entry Right) - True - Colored Squares & Dots
-Door - 0x0C141 (Final Room Entry) - 0x01983 & 0x01987
+158445 - 0x01983 (Pillars Room Entry Left) - True - Shapers & Stars
+158446 - 0x01987 (Pillars Room Entry Right) - True - Colored Squares & Dots
+Door - 0x0C141 (Pillars Room Entry) - 0x01983 & 0x01987
Door - 0x17F33 (Rock Open) - 0x17FA2 | 0x334E1
+Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB & 0x33961:
+158522 - 0x0383A (Right Pillar 1) - True - Stars
+158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Stars & Dots
+158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Full Dots
+158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Dots & Symmetry
+158526 - 0x0383D (Left Pillar 1) - True - Dots
+158527 - 0x0383F (Left Pillar 2) - 0x0383D - Black/White Squares
+158528 - 0x03859 (Left Pillar 3) - 0x0383F - Shapers
+158529 - 0x339BB (Left Pillar 4) - 0x03859 - Black/White Squares & Stars & Symmetry
+
+Elevator (Mountain Bottom Floor):
+158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True
+158531 - 0x3D9A7 (Elevator Door Close Right) - True - True
+158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True
+158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True
+158534 - 0x3D9AA (Back Wall Left) - 0x3D9A6 | 0x3D9A7 - True
+158535 - 0x3D9A8 (Back Wall Right) - 0x3D9A6 | 0x3D9A7 - True
+158536 - 0x3D9A9 (Elevator Start) - 0x3D9AA & 7 Lasers | 0x3D9A8 & 7 Lasers - True
+
Mountain Pink Bridge EP (Mountain Floor 2):
159312 - 0x09D63 (Pink Bridge EP) - 0x09E39 - True
@@ -987,7 +1104,9 @@ Mountain Path to Caves (Mountain Bottom Floor) - Caves - 0x2D77D:
Door - 0x2D77D (Caves Entry) - 0x00FF8
158448 - 0x334E1 (Rock Control) - True - True
-Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Path to Challenge - 0x019A5:
+==Caves==
+
+Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x019A5:
158451 - 0x335AB (Elevator Inside Control) - True - Dots & Black/White Squares
158452 - 0x335AC (Elevator Upper Outside Control) - 0x335AB - Black/White Squares
158453 - 0x3369D (Elevator Lower Outside Control) - 0x335AB - Black/White Squares & Dots
@@ -1042,10 +1161,12 @@ Door - 0x2D73F (Mountain Shortcut Door) - 0x021D7
Door - 0x2D859 (Swamp Shortcut Door) - 0x17CF2
159341 - 0x3397C (Skylight EP) - True - True
-Path to Challenge (Caves) - Challenge - 0x0A19A:
+Caves Path to Challenge (Caves) - Challenge - 0x0A19A:
158477 - 0x0A16E (Challenge Entry Panel) - True - Stars & Shapers & Stars + Same Colored Symbol
Door - 0x0A19A (Challenge Entry) - 0x0A16E
+==Challenge==
+
Challenge (Challenge) - Tunnels - 0x0348A - Challenge Vault - 0x04D75:
158499 - 0x0A332 (Start Timer) - 11 Lasers - True
158500 - 0x0088E (Small Basic) - 0x0A332 - True
@@ -1074,7 +1195,9 @@ Door - 0x0348A (Tunnels Entry) - 0x039B4
Challenge Vault (Challenge):
158667 - 0x0356B (Vault Box) - 0x1C31A & 0x1C319 - True
-Tunnels (Tunnels) - Windmill Interior - 0x27739 - Desert Lowest Level Inbetween Shortcuts - 0x27263 - Town - 0x09E87:
+==Tunnels==
+
+Tunnels (Tunnels) - Windmill Interior - 0x27739 - Desert Behind Elevator - 0x27263 - Town - 0x09E87:
158668 - 0x2FAF6 (Vault Box) - True - True
158519 - 0x27732 (Theater Shortcut Panel) - True - True
Door - 0x27739 (Theater Shortcut) - 0x27732
@@ -1084,24 +1207,7 @@ Door - 0x27263 (Desert Shortcut) - 0x2773D
Door - 0x09E87 (Town Shortcut) - 0x09E85
159557 - 0x33A20 (Theater Flowers EP) - 0x03553 & Theater to Tunnels - True
-Final Room (Mountain Final Room) - Elevator - 0x339BB & 0x33961:
-158522 - 0x0383A (Right Pillar 1) - True - Stars
-158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Stars & Dots
-158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Full Dots
-158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Dots & Symmetry
-158526 - 0x0383D (Left Pillar 1) - True - Dots
-158527 - 0x0383F (Left Pillar 2) - 0x0383D - Black/White Squares
-158528 - 0x03859 (Left Pillar 3) - 0x0383F - Shapers
-158529 - 0x339BB (Left Pillar 4) - 0x03859 - Black/White Squares & Stars & Symmetry
-
-Elevator (Mountain Final Room):
-158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True
-158531 - 0x3D9A7 (Elevator Door Close Right) - True - True
-158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True
-158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True
-158534 - 0x3D9AA (Back Wall Left) - 0x3D9A6 | 0x3D9A7 - True
-158535 - 0x3D9A8 (Back Wall Right) - 0x3D9A6 | 0x3D9A7 - True
-158536 - 0x3D9A9 (Elevator Start) - 0x3D9AA & 7 Lasers | 0x3D9A8 & 7 Lasers - True
+==Boat==
The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Treehouse Entry Area - TrueOneWay - Quarry Boathouse Behind Staircase - TrueOneWay - Inside Glass Factory Behind Back Wall - TrueOneWay:
159042 - 0x22106 (Desert EP) - True - True
@@ -1114,45 +1220,3 @@ The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Tre
159521 - 0x33879 (Tutorial Reflection EP) - True - True
159522 - 0x03C19 (Tutorial Moss EP) - True - True
159531 - 0x035C9 (Cargo Box EP) - 0x0A0C9 - True
-
-Obelisks (EPs) - Entry - True:
-159700 - 0xFFE00 (Desert Obelisk Side 1) - 0x0332B & 0x03367 & 0x28B8A - True
-159701 - 0xFFE01 (Desert Obelisk Side 2) - 0x037B6 & 0x037B2 & 0x000F7 - True
-159702 - 0xFFE02 (Desert Obelisk Side 3) - 0x3351D - True
-159703 - 0xFFE03 (Desert Obelisk Side 4) - 0x0053C & 0x00771 & 0x335C8 & 0x335C9 & 0x337F8 & 0x037BB & 0x220E4 & 0x220E5 - True
-159704 - 0xFFE04 (Desert Obelisk Side 5) - 0x334B9 & 0x334BC & 0x22106 & 0x0A14C & 0x0A14D - True
-159709 - 0x00359 (Desert Obelisk) - True - True
-159710 - 0xFFE10 (Monastery Obelisk Side 1) - 0x03ABC & 0x03ABE & 0x03AC0 & 0x03AC4 - True
-159711 - 0xFFE11 (Monastery Obelisk Side 2) - 0x03AC5 - True
-159712 - 0xFFE12 (Monastery Obelisk Side 3) - 0x03BE2 & 0x03BE3 & 0x0A409 - True
-159713 - 0xFFE13 (Monastery Obelisk Side 4) - 0x006E5 & 0x006E6 & 0x006E7 & 0x034A7 & 0x034AD & 0x034AF & 0x03DAB & 0x03DAC & 0x03DAD - True
-159714 - 0xFFE14 (Monastery Obelisk Side 5) - 0x03E01 - True
-159715 - 0xFFE15 (Monastery Obelisk Side 6) - 0x289F4 & 0x289F5 - True
-159719 - 0x00263 (Monastery Obelisk) - True - True
-159720 - 0xFFE20 (Treehouse Obelisk Side 1) - 0x0053D & 0x0053E & 0x00769 - True
-159721 - 0xFFE21 (Treehouse Obelisk Side 2) - 0x33721 & 0x220A7 & 0x220BD - True
-159722 - 0xFFE22 (Treehouse Obelisk Side 3) - 0x03B22 & 0x03B23 & 0x03B24 & 0x03B25 & 0x03A79 & 0x28ABD & 0x28ABE - True
-159723 - 0xFFE23 (Treehouse Obelisk Side 4) - 0x3388F & 0x28B29 & 0x28B2A - True
-159724 - 0xFFE24 (Treehouse Obelisk Side 5) - 0x018B6 & 0x033BE & 0x033BF & 0x033DD & 0x033E5 - True
-159725 - 0xFFE25 (Treehouse Obelisk Side 6) - 0x28AE9 & 0x3348F - True
-159729 - 0x00097 (Treehouse Obelisk) - True - True
-159730 - 0xFFE30 (River Obelisk Side 1) - 0x001A3 & 0x335AE - True
-159731 - 0xFFE31 (River Obelisk Side 2) - 0x000D3 & 0x035F5 & 0x09D5D & 0x09D5E & 0x09D63 - True
-159732 - 0xFFE32 (River Obelisk Side 3) - 0x3370E & 0x035DE & 0x03601 & 0x03603 & 0x03D0D & 0x3369A & 0x336C8 & 0x33505 - True
-159733 - 0xFFE33 (River Obelisk Side 4) - 0x03A9E & 0x016B2 & 0x3365F & 0x03731 & 0x036CE & 0x03C07 & 0x03A93 - True
-159734 - 0xFFE34 (River Obelisk Side 5) - 0x03AA6 & 0x3397C & 0x0105D & 0x0A304 - True
-159735 - 0xFFE35 (River Obelisk Side 6) - 0x035CB & 0x035CF - True
-159739 - 0x00367 (River Obelisk) - True - True
-159740 - 0xFFE40 (Quarry Obelisk Side 1) - 0x28A7B & 0x005F6 & 0x00859 & 0x17CB9 & 0x28A4A - True
-159741 - 0xFFE41 (Quarry Obelisk Side 2) - 0x334B6 & 0x00614 & 0x0069D & 0x28A4C - True
-159742 - 0xFFE42 (Quarry Obelisk Side 3) - 0x289CF & 0x289D1 - True
-159743 - 0xFFE43 (Quarry Obelisk Side 4) - 0x33692 - True
-159744 - 0xFFE44 (Quarry Obelisk Side 5) - 0x03E77 & 0x03E7C - True
-159749 - 0x22073 (Quarry Obelisk) - True - True
-159750 - 0xFFE50 (Town Obelisk Side 1) - 0x035C7 - True
-159751 - 0xFFE51 (Town Obelisk Side 2) - 0x01848 & 0x03D06 & 0x33530 & 0x33600 & 0x28A2F & 0x28A37 & 0x334A3 & 0x3352F - True
-159752 - 0xFFE52 (Town Obelisk Side 3) - 0x33857 & 0x33879 & 0x03C19 - True
-159753 - 0xFFE53 (Town Obelisk Side 4) - 0x28B30 & 0x035C9 - True
-159754 - 0xFFE54 (Town Obelisk Side 5) - 0x03335 & 0x03412 & 0x038A6 & 0x038AA & 0x03E3F & 0x03E40 & 0x28B8E - True
-159755 - 0xFFE55 (Town Obelisk Side 6) - 0x28B91 & 0x03BCE & 0x03BCF & 0x03BD1 & 0x339B6 & 0x33A20 & 0x33A29 & 0x33A2A & 0x33B06 - True
-159759 - 0x0A16C (Town Obelisk) - True - True
diff --git a/worlds/witness/WitnessLogicExpert.txt b/worlds/witness/WitnessLogicExpert.txt
index 056ae145c47e..b01d5551ec55 100644
--- a/worlds/witness/WitnessLogicExpert.txt
+++ b/worlds/witness/WitnessLogicExpert.txt
@@ -1,12 +1,14 @@
+==Tutorial (Inside)==
+
Menu (Menu) - Entry - True:
Entry (Entry):
-First Hallway (First Hallway) - Entry - True - First Hallway Room - 0x00064:
+Tutorial First Hallway (Tutorial First Hallway) - Entry - True - Tutorial First Hallway Room - 0x00064:
158000 - 0x00064 (Straight) - True - True
159510 - 0x01848 (EP) - 0x00064 - True
-First Hallway Room (First Hallway) - Tutorial - 0x00182:
+Tutorial First Hallway Room (Tutorial First Hallway) - Tutorial - 0x00182:
158001 - 0x00182 (Bend) - True - True
Tutorial (Tutorial) - Outside Tutorial - True:
@@ -23,6 +25,8 @@ Tutorial (Tutorial) - Outside Tutorial - True:
159513 - 0x33600 (Patio Flowers EP) - 0x0C373 - True
159517 - 0x3352F (Gate EP) - 0x03505 - True
+==Tutorial (Outside)==
+
Outside Tutorial (Outside Tutorial) - Outside Tutorial Path To Outpost - 0x03BA2 - Outside Tutorial Vault - 0x033D0:
158650 - 0x033D4 (Vault Panel) - True - Dots & Full Dots & Squares & Black/White Squares
Door - 0x033D0 (Vault Door) - 0x033D4
@@ -58,9 +62,23 @@ Outside Tutorial Outpost (Outside Tutorial) - Outside Tutorial - 0x04CA3:
Door - 0x04CA3 (Outpost Exit) - 0x04CA4
158600 - 0x17CFB (Discard) - True - Arrows
+Orchard (Orchard) - Main Island - True - Orchard Beyond First Gate - 0x03307:
+158071 - 0x00143 (Apple Tree 1) - True - True
+158072 - 0x0003B (Apple Tree 2) - 0x00143 - True
+158073 - 0x00055 (Apple Tree 3) - 0x0003B - True
+Door - 0x03307 (First Gate) - 0x00055
+
+Orchard Beyond First Gate (Orchard) - Orchard End - 0x03313:
+158074 - 0x032F7 (Apple Tree 4) - 0x00055 - True
+158075 - 0x032FF (Apple Tree 5) - 0x032F7 - True
+Door - 0x03313 (Second Gate) - 0x032FF
+
+Orchard End (Orchard):
+
Main Island (Main Island) - Outside Tutorial - True:
159801 - 0xFFD00 (Reached Independently) - True - True
-159550 - 0x28B91 (Thundercloud EP) - 0x09F98 & 0x012FB - True
+
+==Glass Factory==
Outside Glass Factory (Glass Factory) - Main Island - True - Inside Glass Factory - 0x01A29:
158027 - 0x01A54 (Entry Panel) - True - Symmetry
@@ -85,6 +103,8 @@ Door - 0x0D7ED (Back Wall) - 0x0005C
Inside Glass Factory Behind Back Wall (Glass Factory) - The Ocean - 0x17CC8:
158039 - 0x17CC8 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat
+==Symmetry Island==
+
Outside Symmetry Island (Symmetry Island) - Main Island - True - Symmetry Island Lower - 0x17F3E:
158040 - 0x000B0 (Lower Panel) - 0x0343A - Triangles
Door - 0x17F3E (Lower) - 0x000B0
@@ -128,20 +148,17 @@ Symmetry Island Upper (Symmetry Island):
Laser - 0x00509 (Laser) - 0x0360D
159001 - 0x03367 (Glass Factory Black Line EP) - True - True
-Orchard (Orchard) - Main Island - True - Orchard Beyond First Gate - 0x03307:
-158071 - 0x00143 (Apple Tree 1) - True - True
-158072 - 0x0003B (Apple Tree 2) - 0x00143 - True
-158073 - 0x00055 (Apple Tree 3) - 0x0003B - True
-Door - 0x03307 (First Gate) - 0x00055
-
-Orchard Beyond First Gate (Orchard) - Orchard End - 0x03313:
-158074 - 0x032F7 (Apple Tree 4) - 0x00055 - True
-158075 - 0x032FF (Apple Tree 5) - 0x032F7 - True
-Door - 0x03313 (Second Gate) - 0x032FF
+==Desert==
-Orchard End (Orchard):
+Desert Obelisk (Desert) - Entry - True:
+159700 - 0xFFE00 (Obelisk Side 1) - 0x0332B & 0x03367 & 0x28B8A - True
+159701 - 0xFFE01 (Obelisk Side 2) - 0x037B6 & 0x037B2 & 0x000F7 - True
+159702 - 0xFFE02 (Obelisk Side 3) - 0x3351D - True
+159703 - 0xFFE03 (Obelisk Side 4) - 0x0053C & 0x00771 & 0x335C8 & 0x335C9 & 0x337F8 & 0x037BB & 0x220E4 & 0x220E5 - True
+159704 - 0xFFE04 (Obelisk Side 5) - 0x334B9 & 0x334BC & 0x22106 & 0x0A14C & 0x0A14D - True
+159709 - 0x00359 (Obelisk) - True - True
-Desert Outside (Desert) - Main Island - True - Desert Floodlight Room - 0x09FEE - Desert Vault - 0x03444:
+Desert Outside (Desert) - Main Island - True - Desert Light Room - 0x09FEE - Desert Vault - 0x03444:
158652 - 0x0CC7B (Vault Panel) - True - Dots & Full Dots & Stars & Stars + Same Colored Symbol & Eraser & Triangles & Shapers & Negative Shapers & Colored Squares
Door - 0x03444 (Vault Door) - 0x0CC7B
158602 - 0x17CE7 (Discard) - True - Arrows
@@ -172,14 +189,14 @@ Laser - 0x012FB (Laser) - 0x03608
Desert Vault (Desert):
158653 - 0x0339E (Vault Box) - True - True
-Desert Floodlight Room (Desert) - Desert Pond Room - 0x0C2C3:
+Desert Light Room (Desert) - Desert Pond Room - 0x0C2C3:
158087 - 0x09FAA (Light Control) - True - True
158088 - 0x00422 (Light Room 1) - 0x09FAA - True
158089 - 0x006E3 (Light Room 2) - 0x09FAA - True
158090 - 0x0A02D (Light Room 3) - 0x09FAA & 0x00422 & 0x006E3 - True
Door - 0x0C2C3 (Pond Room Entry) - 0x0A02D
-Desert Pond Room (Desert) - Desert Water Levels Room - 0x0A24B:
+Desert Pond Room (Desert) - Desert Flood Room - 0x0A24B:
158091 - 0x00C72 (Pond Room 1) - True - True
158092 - 0x0129D (Pond Room 2) - 0x00C72 - True
158093 - 0x008BB (Pond Room 3) - 0x0129D - True
@@ -190,7 +207,7 @@ Door - 0x0A24B (Flood Room Entry) - 0x0A249
159043 - 0x0A14C (Pond Room Near Reflection EP) - True - True
159044 - 0x0A14D (Pond Room Far Reflection EP) - True - True
-Desert Water Levels Room (Desert) - Desert Elevator Room - 0x0C316:
+Desert Flood Room (Desert) - Desert Elevator Room - 0x0C316:
158097 - 0x1C2DF (Reduce Water Level Far Left) - True - True
158098 - 0x1831E (Reduce Water Level Far Right) - True - True
158099 - 0x1C260 (Reduce Water Level Near Left) - True - True
@@ -208,7 +225,7 @@ Desert Water Levels Room (Desert) - Desert Elevator Room - 0x0C316:
Door - 0x0C316 (Elevator Room Entry) - 0x18076
159034 - 0x337F8 (Flood Room EP) - 0x1C2DF - True
-Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x01317:
+Desert Elevator Room (Desert) - Desert Behind Elevator - 0x01317:
158111 - 0x17C31 (Elevator Room Transparent) - True - True
158113 - 0x012D7 (Elevator Room Hexagonal) - 0x17C31 & 0x0A015 - True
158114 - 0x0A015 (Elevator Room Hexagonal Control) - 0x17C31 - True
@@ -218,9 +235,19 @@ Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x0131
159035 - 0x037BB (Elevator EP) - 0x01317 - True
Door - 0x01317 (Elevator) - 0x03608
-Desert Lowest Level Inbetween Shortcuts (Desert):
+Desert Behind Elevator (Desert):
-Outside Quarry (Quarry) - Main Island - True - Quarry Between Entrys - 0x09D6F - Quarry Elevator - 0xFFD00 & 0xFFD01:
+==Quarry==
+
+Quarry Obelisk (Quarry) - Entry - True:
+159740 - 0xFFE40 (Obelisk Side 1) - 0x28A7B & 0x005F6 & 0x00859 & 0x17CB9 & 0x28A4A - True
+159741 - 0xFFE41 (Obelisk Side 2) - 0x334B6 & 0x00614 & 0x0069D & 0x28A4C - True
+159742 - 0xFFE42 (Obelisk Side 3) - 0x289CF & 0x289D1 - True
+159743 - 0xFFE43 (Obelisk Side 4) - 0x33692 - True
+159744 - 0xFFE44 (Obelisk Side 5) - 0x03E77 & 0x03E7C - True
+159749 - 0x22073 (Obelisk) - True - True
+
+Outside Quarry (Quarry) - Main Island - True - Quarry Between Entry Doors - 0x09D6F - Quarry Elevator - 0xFFD00 & 0xFFD01:
158118 - 0x09E57 (Entry 1 Panel) - True - Squares & Black/White Squares & Triangles
158603 - 0x17CF0 (Discard) - True - Arrows
158702 - 0x03612 (Laser Panel) - 0x0A3D0 & 0x0367C - Eraser & Triangles & Stars & Stars + Same Colored Symbol
@@ -236,7 +263,7 @@ Quarry Elevator (Quarry) - Outside Quarry - 0x17CC4 - Quarry - 0x17CC4:
158120 - 0x17CC4 (Elevator Control) - 0x0367C - Dots & Eraser
159403 - 0x17CB9 (Railroad EP) - 0x17CC4 - True
-Quarry Between Entrys (Quarry) - Quarry - 0x17C07:
+Quarry Between Entry Doors (Quarry) - Quarry - 0x17C07:
158119 - 0x17C09 (Entry 2 Panel) - True - Shapers & Triangles
Door - 0x17C07 (Entry 2) - 0x17C09
@@ -322,6 +349,8 @@ Door - 0x3865F (Second Barrier) - 0x38663
158169 - 0x0A3D0 (Back Second Row 3) - 0x0A3CC - Stars & Eraser & Shapers & Negative Shapers & Stars + Same Colored Symbol
159401 - 0x005F6 (Hook EP) - 0x275FA & 0x03852 & 0x3865F - True
+==Shadows==
+
Shadows (Shadows) - Main Island - True - Shadows Ledge - 0x19B24 - Shadows Laser Room - 0x194B2 | 0x19665:
158170 - 0x334DB (Door Timer Outside) - True - True
Door - 0x19B24 (Timed Door) - 0x334DB | 0x334DC
@@ -361,19 +390,18 @@ Shadows Laser Room (Shadows):
158703 - 0x19650 (Laser Panel) - 0x194B2 & 0x19665 - True
Laser - 0x181B3 (Laser) - 0x19650
-Treehouse Beach (Treehouse Beach) - Main Island - True:
-159200 - 0x0053D (Rock Shadow EP) - True - True
-159201 - 0x0053E (Sand Shadow EP) - True - True
-159212 - 0x220BD (Both Orange Bridges EP) - 0x17DA2 & 0x17DDB - True
+==Keep==
-Keep (Keep) - Main Island - True - Keep 2nd Maze - 0x01954 - Keep 2nd Pressure Plate - 0x01BEC:
+Outside Keep (Keep) - Main Island - True:
+159430 - 0x03E77 (Red Flowers EP) - True - True
+159431 - 0x03E7C (Purple Flowers EP) - True - True
+
+Keep (Keep) - Outside Keep - True - Keep 2nd Maze - 0x01954 - Keep 2nd Pressure Plate - 0x01BEC:
158193 - 0x00139 (Hedge Maze 1) - True - True
158197 - 0x0A3A8 (Reset Pressure Plates 1) - True - True
158198 - 0x033EA (Pressure Plates 1) - 0x0A3A8 - Colored Squares & Triangles & Stars & Stars + Same Colored Symbol
Door - 0x01954 (Hedge Maze 1 Exit) - 0x00139
Door - 0x01BEC (Pressure Plates 1 Exit) - 0x033EA
-159430 - 0x03E77 (Red Flowers EP) - True - True
-159431 - 0x03E7C (Purple Flowers EP) - True - True
Keep 2nd Maze (Keep) - Keep - 0x018CE - Keep 3rd Maze - 0x019D8:
Door - 0x018CE (Hedge Maze 2 Shortcut) - 0x00139
@@ -408,6 +436,22 @@ Door - 0x01D40 (Pressure Plates 4 Exit) - 0x01D3F
158205 - 0x09E49 (Shadows Shortcut Panel) - True - True
Door - 0x09E3D (Shadows Shortcut) - 0x09E49
+Keep Tower (Keep) - Keep - 0x04F8F:
+158206 - 0x0361B (Tower Shortcut Panel) - True - True
+Door - 0x04F8F (Tower Shortcut) - 0x0361B
+158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True
+158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Shapers & Rotated Shapers & Triangles & Stars & Stars + Same Colored Symbol & Colored Squares & Black/White Squares
+Laser - 0x014BB (Laser) - 0x0360E | 0x03317
+159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True
+159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 - True
+159242 - 0x033DD (Pressure Plates 3 EP) - 0x01CD3 & 0x01CD5 - True
+159243 - 0x033E5 (Pressure Plates 4 Left Exit EP) - 0x01D3F - True
+159244 - 0x018B6 (Pressure Plates 4 Right Exit EP) - 0x01D3F - True
+159250 - 0x28AE9 (Path EP) - True - True
+159251 - 0x3348F (Hedges EP) - True - True
+
+==Shipwreck==
+
Shipwreck (Shipwreck) - Keep 3rd Pressure Plate - True - Shipwreck Vault - 0x17BB4:
158654 - 0x00AFB (Vault Panel) - True - Symmetry & Sound Dots & Colored Dots
Door - 0x17BB4 (Vault Door) - 0x00AFB
@@ -423,19 +467,16 @@ Door - 0x17BB4 (Vault Door) - 0x00AFB
Shipwreck Vault (Shipwreck):
158655 - 0x03535 (Vault Box) - True - True
-Keep Tower (Keep) - Keep - 0x04F8F:
-158206 - 0x0361B (Tower Shortcut Panel) - True - True
-Door - 0x04F8F (Tower Shortcut) - 0x0361B
-158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True
-158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Shapers & Rotated Shapers & Triangles & Stars & Stars + Same Colored Symbol & Colored Squares & Black/White Squares
-Laser - 0x014BB (Laser) - 0x0360E | 0x03317
-159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True
-159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 - True
-159242 - 0x033DD (Pressure Plates 3 EP) - 0x01CD3 & 0x01CD5 - True
-159243 - 0x033E5 (Pressure Plates 4 Left Exit EP) - 0x01D3F - True
-159244 - 0x018B6 (Pressure Plates 4 Right Exit EP) - 0x01D3F - True
-159250 - 0x28AE9 (Path EP) - True - True
-159251 - 0x3348F (Hedges EP) - True - True
+==Monastery==
+
+Monastery Obelisk (Monastery) - Entry - True:
+159710 - 0xFFE10 (Obelisk Side 1) - 0x03ABC & 0x03ABE & 0x03AC0 & 0x03AC4 - True
+159711 - 0xFFE11 (Obelisk Side 2) - 0x03AC5 - True
+159712 - 0xFFE12 (Obelisk Side 3) - 0x03BE2 & 0x03BE3 & 0x0A409 - True
+159713 - 0xFFE13 (Obelisk Side 4) - 0x006E5 & 0x006E6 & 0x006E7 & 0x034A7 & 0x034AD & 0x034AF & 0x03DAB & 0x03DAC & 0x03DAD - True
+159714 - 0xFFE14 (Obelisk Side 5) - 0x03E01 - True
+159715 - 0xFFE15 (Obelisk Side 6) - 0x289F4 & 0x289F5 - True
+159719 - 0x00263 (Obelisk) - True - True
Outside Monastery (Monastery) - Main Island - True - Inside Monastery - 0x0C128 & 0x0C153 - Monastery Garden - 0x03750:
158207 - 0x03713 (Laser Shortcut Panel) - True - True
@@ -457,6 +498,9 @@ Laser - 0x17C65 (Laser) - 0x17CA4
159137 - 0x03DAC (Facade Left Stairs EP) - True - True
159138 - 0x03DAD (Facade Right Stairs EP) - True - True
159140 - 0x03E01 (Grass Stairs EP) - True - True
+159120 - 0x03BE2 (Garden Left EP) - 0x03750 - True
+159121 - 0x03BE3 (Garden Right EP) - True - True
+159122 - 0x0A409 (Wall EP) - True - True
Inside Monastery (Monastery):
158213 - 0x09D9B (Shutters Control) - True - Dots
@@ -470,7 +514,18 @@ Inside Monastery (Monastery):
Monastery Garden (Monastery):
-Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x28AA2 - Town Church - True - Town Wooden Rooftop - 0x034F5 - RGB House - 0x28A61 - Windmill Interior - 0x1845B - Town Inside Cargo Box - 0x0A0C9:
+==Town==
+
+Town Obelisk (Town) - Entry - True:
+159750 - 0xFFE50 (Obelisk Side 1) - 0x035C7 - True
+159751 - 0xFFE51 (Obelisk Side 2) - 0x01848 & 0x03D06 & 0x33530 & 0x33600 & 0x28A2F & 0x28A37 & 0x334A3 & 0x3352F - True
+159752 - 0xFFE52 (Obelisk Side 3) - 0x33857 & 0x33879 & 0x03C19 - True
+159753 - 0xFFE53 (Obelisk Side 4) - 0x28B30 & 0x035C9 - True
+159754 - 0xFFE54 (Obelisk Side 5) - 0x03335 & 0x03412 & 0x038A6 & 0x038AA & 0x03E3F & 0x03E40 & 0x28B8E - True
+159755 - 0xFFE55 (Obelisk Side 6) - 0x28B91 & 0x03BCE & 0x03BCF & 0x03BD1 & 0x339B6 & 0x33A20 & 0x33A29 & 0x33A2A & 0x33B06 - True
+159759 - 0x0A16C (Obelisk) - True - True
+
+Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x28AA2 - Town Church - True - Town Wooden Rooftop - 0x034F5 - Town RGB House - 0x28A61 - Town Inside Cargo Box - 0x0A0C9 - Outside Windmill - True:
158218 - 0x0A054 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat
158219 - 0x0A0C8 (Cargo Box Entry Panel) - True - Squares & Black/White Squares & Shapers & Triangles
Door - 0x0A0C9 (Cargo Box Entry) - 0x0A0C8
@@ -491,11 +546,6 @@ Door - 0x28A61 (RGB House Entry) - 0x28A0D
Door - 0x03BB0 (Church Entry) - 0x03C08
158228 - 0x28A79 (Maze Panel) - True - True
Door - 0x28AA2 (Maze Stairs) - 0x28A79
-158241 - 0x17F5F (Windmill Entry Panel) - True - Dots
-Door - 0x1845B (Windmill Entry) - 0x17F5F
-159010 - 0x037B6 (Windmill First Blade EP) - 0x17D02 - True
-159011 - 0x037B2 (Windmill Second Blade EP) - 0x17D02 - True
-159012 - 0x000F7 (Windmill Third Blade EP) - 0x17D02 - True
159540 - 0x03335 (Tower Underside Third EP) - True - True
159541 - 0x03412 (Tower Underside Fourth EP) - True - True
159542 - 0x038A6 (Tower Underside First EP) - True - True
@@ -528,20 +578,26 @@ Town Church (Town):
158227 - 0x28A69 (Church Lattice) - 0x03BB0 - True
159553 - 0x03BD1 (Black Line Church EP) - True - True
-RGB House (Town) - RGB Room - 0x2897B:
+Town RGB House (Town RGB House) - Town RGB House Upstairs - 0x2897B:
158242 - 0x034E4 (Sound Room Left) - True - True
158243 - 0x034E3 (Sound Room Right) - True - Sound Dots
-Door - 0x2897B (RGB House Stairs) - 0x034E4 & 0x034E3
+Door - 0x2897B (Stairs) - 0x034E4 & 0x034E3
-RGB Room (Town):
+Town RGB House Upstairs (Town RGB House Upstairs):
158244 - 0x334D8 (RGB Control) - True - Rotated Shapers & Squares & Colored Squares & Triangles
-158245 - 0x03C0C (RGB Room Left) - 0x334D8 - Squares & Colored Squares & Black/White Squares & Eraser
-158246 - 0x03C08 (RGB Room Right) - 0x334D8 & 0x03C0C - Symmetry & Dots & Colored Dots & Triangles
+158245 - 0x03C0C (Left) - 0x334D8 - Squares & Colored Squares & Black/White Squares & Eraser
+158246 - 0x03C08 (Right) - 0x334D8 & 0x03C0C - Symmetry & Dots & Colored Dots & Triangles
+
+Town Tower Bottom (Town Tower) - Town - True - Town Tower After First Door - 0x27799:
+Door - 0x27799 (First Door) - 0x28A69
-Town Tower (Town Tower) - Town - True - Town Tower Top - 0x27798 & 0x27799 & 0x2779A & 0x2779C:
+Town Tower After First Door (Town Tower) - Town Tower After Second Door - 0x27798:
Door - 0x27798 (Second Door) - 0x28ACC
+
+Town Tower After Second Door (Town Tower) - Town Tower After Third Door - 0x2779C:
Door - 0x2779C (Third Door) - 0x28AD9
-Door - 0x27799 (First Door) - 0x28A69
+
+Town Tower After Third Door (Town Tower) - Town Tower Top - 0x2779A:
Door - 0x2779A (Fourth Door) - 0x28B39
Town Tower Top (Town):
@@ -550,6 +606,15 @@ Laser - 0x032F9 (Laser) - 0x032F5
159422 - 0x33692 (Brown Bridge EP) - True - True
159551 - 0x03BCE (Black Line Tower EP) - True - True
+==Windmill & Theater==
+
+Outside Windmill (Windmill) - Windmill Interior - 0x1845B:
+159010 - 0x037B6 (First Blade EP) - 0x17D02 - True
+159011 - 0x037B2 (Second Blade EP) - 0x17D02 - True
+159012 - 0x000F7 (Third Blade EP) - 0x17D02 - True
+158241 - 0x17F5F (Entry Panel) - True - Dots
+Door - 0x1845B (Entry) - 0x17F5F
+
Windmill Interior (Windmill) - Theater - 0x17F88:
158247 - 0x17D02 (Turn Control) - True - Dots
158248 - 0x17F89 (Theater Entry Panel) - True - Squares & Black/White Squares & Eraser & Triangles
@@ -573,6 +638,8 @@ Door - 0x3CCDF (Exit Right) - 0x33AB2
159556 - 0x33A2A (Door EP) - 0x03553 - True
159558 - 0x33B06 (Church EP) - 0x0354E - True
+==Jungle==
+
Jungle (Jungle) - Main Island - True - The Ocean - 0x17CDF:
158251 - 0x17CDF (Shore Boat Spawn) - True - Boat
158609 - 0x17F9B (Discard) - True - Arrows
@@ -604,19 +671,18 @@ Door - 0x3873B (Laser Shortcut) - 0x337FA
159350 - 0x035CB (Bamboo CCW EP) - True - True
159351 - 0x035CF (Bamboo CW EP) - True - True
-Outside Jungle River (River) - Main Island - True - Monastery Garden - 0x0CF2A - River Vault - 0x15287:
+Outside Jungle River (Jungle) - Main Island - True - Monastery Garden - 0x0CF2A - Jungle Vault - 0x15287:
158267 - 0x17CAA (Monastery Garden Shortcut Panel) - True - True
Door - 0x0CF2A (Monastery Garden Shortcut) - 0x17CAA
158663 - 0x15ADD (Vault Panel) - True - Black/White Squares & Dots
Door - 0x15287 (Vault Door) - 0x15ADD
159110 - 0x03AC5 (Green Leaf Moss EP) - True - True
-159120 - 0x03BE2 (Monastery Garden Left EP) - 0x03750 - True
-159121 - 0x03BE3 (Monastery Garden Right EP) - True - True
-159122 - 0x0A409 (Monastery Wall EP) - True - True
-River Vault (River):
+Jungle Vault (Jungle):
158664 - 0x03702 (Vault Box) - True - True
+==Bunker==
+
Outside Bunker (Bunker) - Main Island - True - Bunker - 0x0C2A4:
158268 - 0x17C2E (Entry Panel) - True - Squares & Black/White Squares
Door - 0x0C2A4 (Entry) - 0x17C2E
@@ -650,9 +716,11 @@ Door - 0x0A08D (Elevator Room Entry) - 0x17E67
Bunker Elevator Section (Bunker) - Bunker Elevator - TrueOneWay:
159311 - 0x035F5 (Tinted Door EP) - 0x17C79 - True
-Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079:
+Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Cyan Room - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079:
158286 - 0x0A079 (Elevator Control) - True - Colored Squares & Black/White Squares
+Bunker Cyan Room (Bunker) - Bunker Elevator - TrueOneWay:
+
Bunker Green Room (Bunker) - Bunker Elevator - TrueOneWay:
159310 - 0x000D3 (Green Room Flowers EP) - True - True
@@ -660,6 +728,8 @@ Bunker Laser Platform (Bunker) - Bunker Elevator - TrueOneWay:
158710 - 0x09DE0 (Laser Panel) - True - True
Laser - 0x0C2B2 (Laser) - 0x09DE0
+==Swamp==
+
Outside Swamp (Swamp) - Swamp Entry Area - 0x00C1C - Main Island - True:
158287 - 0x0056E (Entry Panel) - True - Rotated Shapers & Black/White Squares & Triangles
Door - 0x00C1C (Entry) - 0x0056E
@@ -774,13 +844,29 @@ Laser - 0x00BF6 (Laser) - 0x03615
158342 - 0x17C02 (Laser Shortcut Right Panel) - 0x17C05 - Shapers & Negative Shapers & Stars & Stars + Same Colored Symbol
Door - 0x2D880 (Laser Shortcut) - 0x17C02
-Treehouse Entry Area (Treehouse) - Treehouse Between Doors - 0x0C309 - The Ocean - 0x17C95:
+==Treehouse==
+
+Treehouse Obelisk (Treehouse) - Entry - True:
+159720 - 0xFFE20 (Obelisk Side 1) - 0x0053D & 0x0053E & 0x00769 - True
+159721 - 0xFFE21 (Obelisk Side 2) - 0x33721 & 0x220A7 & 0x220BD - True
+159722 - 0xFFE22 (Obelisk Side 3) - 0x03B22 & 0x03B23 & 0x03B24 & 0x03B25 & 0x03A79 & 0x28ABD & 0x28ABE - True
+159723 - 0xFFE23 (Obelisk Side 4) - 0x3388F & 0x28B29 & 0x28B2A - True
+159724 - 0xFFE24 (Obelisk Side 5) - 0x018B6 & 0x033BE & 0x033BF & 0x033DD & 0x033E5 - True
+159725 - 0xFFE25 (Obelisk Side 6) - 0x28AE9 & 0x3348F - True
+159729 - 0x00097 (Obelisk) - True - True
+
+Treehouse Beach (Treehouse Beach) - Main Island - True:
+159200 - 0x0053D (Rock Shadow EP) - True - True
+159201 - 0x0053E (Sand Shadow EP) - True - True
+159212 - 0x220BD (Both Orange Bridges EP) - 0x17DA2 & 0x17DDB - True
+
+Treehouse Entry Area (Treehouse) - Treehouse Between Entry Doors - 0x0C309 - The Ocean - 0x17C95:
158343 - 0x17C95 (Boat Spawn) - True - Boat
158344 - 0x0288C (First Door Panel) - True - Stars & Stars + Same Colored Symbol & Triangles
Door - 0x0C309 (First Door) - 0x0288C
159210 - 0x33721 (Buoy EP) - 0x17C95 - True
-Treehouse Between Doors (Treehouse) - Treehouse Yellow Bridge - 0x0C310:
+Treehouse Between Entry Doors (Treehouse) - Treehouse Yellow Bridge - 0x0C310:
158345 - 0x02886 (Second Door Panel) - True - Stars & Stars + Same Colored Symbol & Triangles
Door - 0x0C310 (Second Door) - 0x02886
@@ -809,7 +895,7 @@ Treehouse First Purple Bridge (Treehouse) - Treehouse Second Purple Bridge - 0x1
158360 - 0x17D2D (First Purple Bridge 4) - 0x17CE4 - Stars & Dots & Full Dots
158361 - 0x17D6C (First Purple Bridge 5) - 0x17D2D - Stars & Dots & Full Dots
-Treehouse Right Orange Bridge (Treehouse) - Treehouse Bridge Platform - 0x17DA2:
+Treehouse Right Orange Bridge (Treehouse) - Treehouse Drawbridge Platform - 0x17DA2:
158391 - 0x17D88 (Right Orange Bridge 1) - True - Stars & Stars + Same Colored Symbol & Triangles
158392 - 0x17DB4 (Right Orange Bridge 2) - 0x17D88 - Stars & Stars + Same Colored Symbol & Triangles
158393 - 0x17D8C (Right Orange Bridge 3) - 0x17DB4 - Stars & Stars + Same Colored Symbol & Triangles
@@ -823,7 +909,7 @@ Treehouse Right Orange Bridge (Treehouse) - Treehouse Bridge Platform - 0x17DA2:
158401 - 0x17DB1 (Right Orange Bridge 11) - 0x17DB7 - Stars & Stars + Same Colored Symbol & Triangles
158402 - 0x17DA2 (Right Orange Bridge 12) - 0x17DB1 - Stars & Stars + Same Colored Symbol & Triangles
-Treehouse Bridge Platform (Treehouse) - Main Island - 0x0C32D:
+Treehouse Drawbridge Platform (Treehouse) - Main Island - 0x0C32D:
158404 - 0x037FF (Drawbridge Panel) - True - Stars
Door - 0x0C32D (Drawbridge) - 0x037FF
@@ -882,7 +968,19 @@ Treehouse Laser Room (Treehouse):
158403 - 0x17CBC (Laser House Door Timer Inside) - True - True
Laser - 0x028A4 (Laser) - 0x03613
+==Mountain (Outside)==
+
+Mountainside Obelisk (Mountainside) - Entry - True:
+159730 - 0xFFE30 (Obelisk Side 1) - 0x001A3 & 0x335AE - True
+159731 - 0xFFE31 (Obelisk Side 2) - 0x000D3 & 0x035F5 & 0x09D5D & 0x09D5E & 0x09D63 - True
+159732 - 0xFFE32 (Obelisk Side 3) - 0x3370E & 0x035DE & 0x03601 & 0x03603 & 0x03D0D & 0x3369A & 0x336C8 & 0x33505 - True
+159733 - 0xFFE33 (Obelisk Side 4) - 0x03A9E & 0x016B2 & 0x3365F & 0x03731 & 0x036CE & 0x03C07 & 0x03A93 - True
+159734 - 0xFFE34 (Obelisk Side 5) - 0x03AA6 & 0x3397C & 0x0105D & 0x0A304 - True
+159735 - 0xFFE35 (Obelisk Side 6) - 0x035CB & 0x035CF - True
+159739 - 0x00367 (Obelisk) - True - True
+
Mountainside (Mountainside) - Main Island - True - Mountaintop - True - Mountainside Vault - 0x00085:
+159550 - 0x28B91 (Thundercloud EP) - 0x09F98 & 0x012FB - True
158612 - 0x17C42 (Discard) - True - Arrows
158665 - 0x002A6 (Vault Panel) - True - Symmetry & Colored Squares & Triangles & Stars & Stars + Same Colored Symbol
Door - 0x00085 (Vault Door) - 0x002A6
@@ -893,7 +991,7 @@ Door - 0x00085 (Vault Door) - 0x002A6
Mountainside Vault (Mountainside):
158666 - 0x03542 (Vault Box) - True - True
-Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34:
+Mountaintop (Mountaintop) - Mountain Floor 1 - 0x17C34:
158405 - 0x0042D (River Shape) - True - True
158406 - 0x09F7F (Box Short) - 7 Lasers + Redirect - True
158407 - 0x17C34 (Mountain Entry Panel) - 0x09F7F - Stars & Black/White Squares & Stars + Same Colored Symbol & Triangles
@@ -903,10 +1001,12 @@ Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34:
159324 - 0x336C8 (Arch White Right EP) - True - True
159326 - 0x3369A (Arch White Left EP) - True - True
-Mountain Top Layer (Mountain Floor 1) - Mountain Top Layer Bridge - 0x09E39:
+==Mountain (Inside)==
+
+Mountain Floor 1 (Mountain Floor 1) - Mountain Floor 1 Bridge - 0x09E39:
158408 - 0x09E39 (Light Bridge Controller) - True - Eraser & Triangles
-Mountain Top Layer Bridge (Mountain Floor 1) - Mountain Top Layer At Door - TrueOneWay:
+Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneWay:
158409 - 0x09E7A (Right Row 1) - True - Black/White Squares & Dots & Stars & Stars + Same Colored Symbol
158410 - 0x09E71 (Right Row 2) - 0x09E7A - Black/White Squares & Triangles
158411 - 0x09E72 (Right Row 3) - 0x09E71 - Black/White Squares & Shapers & Stars & Stars + Same Colored Symbol
@@ -925,10 +1025,10 @@ Mountain Top Layer Bridge (Mountain Floor 1) - Mountain Top Layer At Door - True
158424 - 0x09EAD (Trash Pillar 1) - True - Rotated Shapers & Stars
158425 - 0x09EAF (Trash Pillar 2) - 0x09EAD - Rotated Shapers & Triangles
-Mountain Top Layer At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54:
+Mountain Floor 1 At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54:
Door - 0x09E54 (Exit) - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B
-Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Blue Bridge - 0x09E86 - Mountain Pink Bridge EP - TrueOneWay:
+Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Beyond Bridge - 0x09E86 - Mountain Floor 2 At Door - 0x09ED8 & 0x09E86 - Mountain Pink Bridge EP - TrueOneWay:
158426 - 0x09FD3 (Near Row 1) - True - Stars & Colored Squares & Stars + Same Colored Symbol
158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Stars & Triangles & Stars + Same Colored Symbol
158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Stars & Shapers & Negative Shapers & Stars + Same Colored Symbol
@@ -936,8 +1036,6 @@ Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near -
158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Stars & Stars + Same Colored Symbol & Rotated Shapers & Eraser
Door - 0x09FFB (Staircase Near) - 0x09FD8
-Mountain Floor 2 Blue Bridge (Mountain Floor 2) - Mountain Floor 2 Beyond Bridge - TrueOneWay - Mountain Floor 2 At Door - 0x09ED8:
-
Mountain Floor 2 At Door (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EDD:
Door - 0x09EDD (Elevator Room Entry) - 0x09ED8 & 0x09E86
@@ -959,10 +1057,10 @@ Mountain Floor 2 Light Bridge Room Far (Mountain Floor 2):
Mountain Floor 2 Elevator Room (Mountain Floor 2) - Mountain Floor 2 Elevator - TrueOneWay:
158613 - 0x17F93 (Elevator Discard) - True - Arrows
-Mountain Floor 2 Elevator (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EEB - Mountain Third Layer - 0x09EEB:
+Mountain Floor 2 Elevator (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EEB - Mountain Floor 3 - 0x09EEB:
158439 - 0x09EEB (Elevator Control Panel) - True - Dots
-Mountain Third Layer (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueOneWay - Mountain Bottom Floor - 0x09F89 - Mountain Pink Bridge EP - TrueOneWay:
+Mountain Floor 3 (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueOneWay - Mountain Bottom Floor - 0x09F89 - Mountain Pink Bridge EP - TrueOneWay:
158440 - 0x09FC1 (Giant Puzzle Bottom Left) - True - Shapers & Eraser & Negative Shapers
158441 - 0x09F8E (Giant Puzzle Bottom Right) - True - Shapers & Eraser & Negative Shapers
158442 - 0x09F01 (Giant Puzzle Top Right) - True - Shapers & Eraser & Negative Shapers
@@ -972,13 +1070,32 @@ Mountain Third Layer (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueO
159314 - 0x09D5E (Blue Bridge EP) - 0x09E86 & 0x09ED8 - True
Door - 0x09F89 (Exit) - 0x09FDA
-Mountain Bottom Floor (Mountain Bottom Floor) - Mountain Path to Caves - 0x17F33 - Final Room - 0x0C141:
+Mountain Bottom Floor (Mountain Bottom Floor) - Mountain Path to Caves - 0x17F33 - Mountain Bottom Floor Pillars Room - 0x0C141:
158614 - 0x17FA2 (Discard) - 0xFFF00 - Arrows
-158445 - 0x01983 (Final Room Entry Left) - True - Shapers & Stars
-158446 - 0x01987 (Final Room Entry Right) - True - Squares & Colored Squares & Dots
-Door - 0x0C141 (Final Room Entry) - 0x01983 & 0x01987
+158445 - 0x01983 (Pillars Room Entry Left) - True - Shapers & Stars
+158446 - 0x01987 (Pillars Room Entry Right) - True - Squares & Colored Squares & Dots
+Door - 0x0C141 (Pillars Room Entry) - 0x01983 & 0x01987
Door - 0x17F33 (Rock Open) - 0x17FA2 | 0x334E1
+Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB & 0x33961:
+158522 - 0x0383A (Right Pillar 1) - True - Stars & Eraser & Triangles & Stars + Same Colored Symbol
+158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Dots & Full Dots & Triangles & Symmetry
+158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Shapers & Stars & Negative Shapers & Stars + Same Colored Symbol & Symmetry
+158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Eraser & Symmetry & Stars & Stars + Same Colored Symbol & Negative Shapers & Shapers
+158526 - 0x0383D (Left Pillar 1) - True - Stars & Black/White Squares & Stars + Same Colored Symbol
+158527 - 0x0383F (Left Pillar 2) - 0x0383D - Triangles & Symmetry
+158528 - 0x03859 (Left Pillar 3) - 0x0383F - Symmetry & Shapers & Black/White Squares
+158529 - 0x339BB (Left Pillar 4) - 0x03859 - Symmetry & Black/White Squares & Stars & Stars + Same Colored Symbol & Triangles & Colored Dots
+
+Elevator (Mountain Bottom Floor):
+158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True
+158531 - 0x3D9A7 (Elevator Door Close Right) - True - True
+158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True
+158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True
+158534 - 0x3D9AA (Back Wall Left) - 0x3D9A6 | 0x3D9A7 - True
+158535 - 0x3D9A8 (Back Wall Right) - 0x3D9A6 | 0x3D9A7 - True
+158536 - 0x3D9A9 (Elevator Start) - 0x3D9AA & 7 Lasers | 0x3D9A8 & 7 Lasers - True
+
Mountain Pink Bridge EP (Mountain Floor 2):
159312 - 0x09D63 (Pink Bridge EP) - 0x09E39 - True
@@ -987,7 +1104,9 @@ Mountain Path to Caves (Mountain Bottom Floor) - Caves - 0x2D77D:
Door - 0x2D77D (Caves Entry) - 0x00FF8
158448 - 0x334E1 (Rock Control) - True - True
-Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Path to Challenge - 0x019A5:
+==Caves==
+
+Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x019A5:
158451 - 0x335AB (Elevator Inside Control) - True - Dots & Squares & Black/White Squares
158452 - 0x335AC (Elevator Upper Outside Control) - 0x335AB - Squares & Black/White Squares
158453 - 0x3369D (Elevator Lower Outside Control) - 0x335AB - Squares & Black/White Squares & Dots
@@ -1042,10 +1161,12 @@ Door - 0x2D73F (Mountain Shortcut Door) - 0x021D7
Door - 0x2D859 (Swamp Shortcut Door) - 0x17CF2
159341 - 0x3397C (Skylight EP) - True - True
-Path to Challenge (Caves) - Challenge - 0x0A19A:
+Caves Path to Challenge (Caves) - Challenge - 0x0A19A:
158477 - 0x0A16E (Challenge Entry Panel) - True - Stars & Arrows & Stars + Same Colored Symbol
Door - 0x0A19A (Challenge Entry) - 0x0A16E
+==Challenge==
+
Challenge (Challenge) - Tunnels - 0x0348A - Challenge Vault - 0x04D75:
158499 - 0x0A332 (Start Timer) - 11 Lasers - True
158500 - 0x0088E (Small Basic) - 0x0A332 - True
@@ -1074,7 +1195,9 @@ Door - 0x0348A (Tunnels Entry) - 0x039B4
Challenge Vault (Challenge):
158667 - 0x0356B (Vault Box) - 0x1C31A & 0x1C319 - True
-Tunnels (Tunnels) - Windmill Interior - 0x27739 - Desert Lowest Level Inbetween Shortcuts - 0x27263 - Town - 0x09E87:
+==Tunnels==
+
+Tunnels (Tunnels) - Windmill Interior - 0x27739 - Desert Behind Elevator - 0x27263 - Town - 0x09E87:
158668 - 0x2FAF6 (Vault Box) - True - True
158519 - 0x27732 (Theater Shortcut Panel) - True - True
Door - 0x27739 (Theater Shortcut) - 0x27732
@@ -1084,24 +1207,7 @@ Door - 0x27263 (Desert Shortcut) - 0x2773D
Door - 0x09E87 (Town Shortcut) - 0x09E85
159557 - 0x33A20 (Theater Flowers EP) - 0x03553 & Theater to Tunnels - True
-Final Room (Mountain Final Room) - Elevator - 0x339BB & 0x33961:
-158522 - 0x0383A (Right Pillar 1) - True - Stars & Eraser & Triangles & Stars + Same Colored Symbol
-158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Dots & Full Dots & Triangles & Symmetry
-158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Shapers & Stars & Negative Shapers & Stars + Same Colored Symbol & Symmetry
-158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Eraser & Symmetry & Stars & Stars + Same Colored Symbol & Negative Shapers & Shapers
-158526 - 0x0383D (Left Pillar 1) - True - Stars & Black/White Squares & Stars + Same Colored Symbol
-158527 - 0x0383F (Left Pillar 2) - 0x0383D - Triangles & Symmetry
-158528 - 0x03859 (Left Pillar 3) - 0x0383F - Symmetry & Shapers & Black/White Squares
-158529 - 0x339BB (Left Pillar 4) - 0x03859 - Symmetry & Black/White Squares & Stars & Stars + Same Colored Symbol & Triangles & Colored Dots
-
-Elevator (Mountain Final Room):
-158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True
-158531 - 0x3D9A7 (Elevator Door Close Right) - True - True
-158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True
-158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True
-158534 - 0x3D9AA (Back Wall Left) - 0x3D9A6 | 0x3D9A7 - True
-158535 - 0x3D9A8 (Back Wall Right) - 0x3D9A6 | 0x3D9A7 - True
-158536 - 0x3D9A9 (Elevator Start) - 0x3D9AA & 7 Lasers | 0x3D9A8 & 7 Lasers - True
+==Boat==
The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Treehouse Entry Area - TrueOneWay - Quarry Boathouse Behind Staircase - TrueOneWay - Inside Glass Factory Behind Back Wall - TrueOneWay:
159042 - 0x22106 (Desert EP) - True - True
@@ -1114,45 +1220,3 @@ The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Tre
159521 - 0x33879 (Tutorial Reflection EP) - True - True
159522 - 0x03C19 (Tutorial Moss EP) - True - True
159531 - 0x035C9 (Cargo Box EP) - 0x0A0C9 - True
-
-Obelisks (EPs) - Entry - True:
-159700 - 0xFFE00 (Desert Obelisk Side 1) - 0x0332B & 0x03367 & 0x28B8A - True
-159701 - 0xFFE01 (Desert Obelisk Side 2) - 0x037B6 & 0x037B2 & 0x000F7 - True
-159702 - 0xFFE02 (Desert Obelisk Side 3) - 0x3351D - True
-159703 - 0xFFE03 (Desert Obelisk Side 4) - 0x0053C & 0x00771 & 0x335C8 & 0x335C9 & 0x337F8 & 0x037BB & 0x220E4 & 0x220E5 - True
-159704 - 0xFFE04 (Desert Obelisk Side 5) - 0x334B9 & 0x334BC & 0x22106 & 0x0A14C & 0x0A14D - True
-159709 - 0x00359 (Desert Obelisk) - True - True
-159710 - 0xFFE10 (Monastery Obelisk Side 1) - 0x03ABC & 0x03ABE & 0x03AC0 & 0x03AC4 - True
-159711 - 0xFFE11 (Monastery Obelisk Side 2) - 0x03AC5 - True
-159712 - 0xFFE12 (Monastery Obelisk Side 3) - 0x03BE2 & 0x03BE3 & 0x0A409 - True
-159713 - 0xFFE13 (Monastery Obelisk Side 4) - 0x006E5 & 0x006E6 & 0x006E7 & 0x034A7 & 0x034AD & 0x034AF & 0x03DAB & 0x03DAC & 0x03DAD - True
-159714 - 0xFFE14 (Monastery Obelisk Side 5) - 0x03E01 - True
-159715 - 0xFFE15 (Monastery Obelisk Side 6) - 0x289F4 & 0x289F5 - True
-159719 - 0x00263 (Monastery Obelisk) - True - True
-159720 - 0xFFE20 (Treehouse Obelisk Side 1) - 0x0053D & 0x0053E & 0x00769 - True
-159721 - 0xFFE21 (Treehouse Obelisk Side 2) - 0x33721 & 0x220A7 & 0x220BD - True
-159722 - 0xFFE22 (Treehouse Obelisk Side 3) - 0x03B22 & 0x03B23 & 0x03B24 & 0x03B25 & 0x03A79 & 0x28ABD & 0x28ABE - True
-159723 - 0xFFE23 (Treehouse Obelisk Side 4) - 0x3388F & 0x28B29 & 0x28B2A - True
-159724 - 0xFFE24 (Treehouse Obelisk Side 5) - 0x018B6 & 0x033BE & 0x033BF & 0x033DD & 0x033E5 - True
-159725 - 0xFFE25 (Treehouse Obelisk Side 6) - 0x28AE9 & 0x3348F - True
-159729 - 0x00097 (Treehouse Obelisk) - True - True
-159730 - 0xFFE30 (River Obelisk Side 1) - 0x001A3 & 0x335AE - True
-159731 - 0xFFE31 (River Obelisk Side 2) - 0x000D3 & 0x035F5 & 0x09D5D & 0x09D5E & 0x09D63 - True
-159732 - 0xFFE32 (River Obelisk Side 3) - 0x3370E & 0x035DE & 0x03601 & 0x03603 & 0x03D0D & 0x3369A & 0x336C8 & 0x33505 - True
-159733 - 0xFFE33 (River Obelisk Side 4) - 0x03A9E & 0x016B2 & 0x3365F & 0x03731 & 0x036CE & 0x03C07 & 0x03A93 - True
-159734 - 0xFFE34 (River Obelisk Side 5) - 0x03AA6 & 0x3397C & 0x0105D & 0x0A304 - True
-159735 - 0xFFE35 (River Obelisk Side 6) - 0x035CB & 0x035CF - True
-159739 - 0x00367 (River Obelisk) - True - True
-159740 - 0xFFE40 (Quarry Obelisk Side 1) - 0x28A7B & 0x005F6 & 0x00859 & 0x17CB9 & 0x28A4A - True
-159741 - 0xFFE41 (Quarry Obelisk Side 2) - 0x334B6 & 0x00614 & 0x0069D & 0x28A4C - True
-159742 - 0xFFE42 (Quarry Obelisk Side 3) - 0x289CF & 0x289D1 - True
-159743 - 0xFFE43 (Quarry Obelisk Side 4) - 0x33692 - True
-159744 - 0xFFE44 (Quarry Obelisk Side 5) - 0x03E77 & 0x03E7C - True
-159749 - 0x22073 (Quarry Obelisk) - True - True
-159750 - 0xFFE50 (Town Obelisk Side 1) - 0x035C7 - True
-159751 - 0xFFE51 (Town Obelisk Side 2) - 0x01848 & 0x03D06 & 0x33530 & 0x33600 & 0x28A2F & 0x28A37 & 0x334A3 & 0x3352F - True
-159752 - 0xFFE52 (Town Obelisk Side 3) - 0x33857 & 0x33879 & 0x03C19 - True
-159753 - 0xFFE53 (Town Obelisk Side 4) - 0x28B30 & 0x035C9 - True
-159754 - 0xFFE54 (Town Obelisk Side 5) - 0x03335 & 0x03412 & 0x038A6 & 0x038AA & 0x03E3F & 0x03E40 & 0x28B8E - True
-159755 - 0xFFE55 (Town Obelisk Side 6) - 0x28B91 & 0x03BCE & 0x03BCF & 0x03BD1 & 0x339B6 & 0x33A20 & 0x33A29 & 0x33A2A & 0x33B06 - True
-159759 - 0x0A16C (Town Obelisk) - True - True
diff --git a/worlds/witness/WitnessLogicVanilla.txt b/worlds/witness/WitnessLogicVanilla.txt
index 71af12f76dbb..62c38d412427 100644
--- a/worlds/witness/WitnessLogicVanilla.txt
+++ b/worlds/witness/WitnessLogicVanilla.txt
@@ -1,12 +1,14 @@
+==Tutorial (Inside)==
+
Menu (Menu) - Entry - True:
Entry (Entry):
-First Hallway (First Hallway) - Entry - True - First Hallway Room - 0x00064:
+Tutorial First Hallway (Tutorial First Hallway) - Entry - True - Tutorial First Hallway Room - 0x00064:
158000 - 0x00064 (Straight) - True - True
159510 - 0x01848 (EP) - 0x00064 - True
-First Hallway Room (First Hallway) - Tutorial - 0x00182:
+Tutorial First Hallway Room (Tutorial First Hallway) - Tutorial - 0x00182:
158001 - 0x00182 (Bend) - True - True
Tutorial (Tutorial) - Outside Tutorial - 0x03629:
@@ -23,6 +25,8 @@ Tutorial (Tutorial) - Outside Tutorial - 0x03629:
159513 - 0x33600 (Patio Flowers EP) - 0x0C373 - True
159517 - 0x3352F (Gate EP) - 0x03505 - True
+==Tutorial (Outside)==
+
Outside Tutorial (Outside Tutorial) - Outside Tutorial Path To Outpost - 0x03BA2 - Outside Tutorial Vault - 0x033D0:
158650 - 0x033D4 (Vault Panel) - True - Dots & Black/White Squares
Door - 0x033D0 (Vault Door) - 0x033D4
@@ -58,9 +62,23 @@ Outside Tutorial Outpost (Outside Tutorial) - Outside Tutorial - 0x04CA3:
Door - 0x04CA3 (Outpost Exit) - 0x04CA4
158600 - 0x17CFB (Discard) - True - Triangles
+Orchard (Orchard) - Main Island - True - Orchard Beyond First Gate - 0x03307:
+158071 - 0x00143 (Apple Tree 1) - True - True
+158072 - 0x0003B (Apple Tree 2) - 0x00143 - True
+158073 - 0x00055 (Apple Tree 3) - 0x0003B - True
+Door - 0x03307 (First Gate) - 0x00055
+
+Orchard Beyond First Gate (Orchard) - Orchard End - 0x03313:
+158074 - 0x032F7 (Apple Tree 4) - 0x00055 - True
+158075 - 0x032FF (Apple Tree 5) - 0x032F7 - True
+Door - 0x03313 (Second Gate) - 0x032FF
+
+Orchard End (Orchard):
+
Main Island (Main Island) - Outside Tutorial - True:
159801 - 0xFFD00 (Reached Independently) - True - True
-159550 - 0x28B91 (Thundercloud EP) - 0x09F98 & 0x012FB - True
+
+==Glass Factory==
Outside Glass Factory (Glass Factory) - Main Island - True - Inside Glass Factory - 0x01A29:
158027 - 0x01A54 (Entry Panel) - True - Symmetry
@@ -85,6 +103,8 @@ Door - 0x0D7ED (Back Wall) - 0x0005C
Inside Glass Factory Behind Back Wall (Glass Factory) - The Ocean - 0x17CC8:
158039 - 0x17CC8 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat
+==Symmetry Island==
+
Outside Symmetry Island (Symmetry Island) - Main Island - True - Symmetry Island Lower - 0x17F3E:
158040 - 0x000B0 (Lower Panel) - 0x0343A - Dots
Door - 0x17F3E (Lower) - 0x000B0
@@ -128,20 +148,17 @@ Symmetry Island Upper (Symmetry Island):
Laser - 0x00509 (Laser) - 0x0360D
159001 - 0x03367 (Glass Factory Black Line EP) - True - True
-Orchard (Orchard) - Main Island - True - Orchard Beyond First Gate - 0x03307:
-158071 - 0x00143 (Apple Tree 1) - True - True
-158072 - 0x0003B (Apple Tree 2) - 0x00143 - True
-158073 - 0x00055 (Apple Tree 3) - 0x0003B - True
-Door - 0x03307 (First Gate) - 0x00055
-
-Orchard Beyond First Gate (Orchard) - Orchard End - 0x03313:
-158074 - 0x032F7 (Apple Tree 4) - 0x00055 - True
-158075 - 0x032FF (Apple Tree 5) - 0x032F7 - True
-Door - 0x03313 (Second Gate) - 0x032FF
+==Desert==
-Orchard End (Orchard):
+Desert Obelisk (Desert) - Entry - True:
+159700 - 0xFFE00 (Obelisk Side 1) - 0x0332B & 0x03367 & 0x28B8A - True
+159701 - 0xFFE01 (Obelisk Side 2) - 0x037B6 & 0x037B2 & 0x000F7 - True
+159702 - 0xFFE02 (Obelisk Side 3) - 0x3351D - True
+159703 - 0xFFE03 (Obelisk Side 4) - 0x0053C & 0x00771 & 0x335C8 & 0x335C9 & 0x337F8 & 0x037BB & 0x220E4 & 0x220E5 - True
+159704 - 0xFFE04 (Obelisk Side 5) - 0x334B9 & 0x334BC & 0x22106 & 0x0A14C & 0x0A14D - True
+159709 - 0x00359 (Obelisk) - True - True
-Desert Outside (Desert) - Main Island - True - Desert Floodlight Room - 0x09FEE - Desert Vault - 0x03444:
+Desert Outside (Desert) - Main Island - True - Desert Light Room - 0x09FEE - Desert Vault - 0x03444:
158652 - 0x0CC7B (Vault Panel) - True - Dots & Shapers & Rotated Shapers & Negative Shapers & Full Dots
Door - 0x03444 (Vault Door) - 0x0CC7B
158602 - 0x17CE7 (Discard) - True - Triangles
@@ -172,14 +189,14 @@ Laser - 0x012FB (Laser) - 0x03608
Desert Vault (Desert):
158653 - 0x0339E (Vault Box) - True - True
-Desert Floodlight Room (Desert) - Desert Pond Room - 0x0C2C3:
+Desert Light Room (Desert) - Desert Pond Room - 0x0C2C3:
158087 - 0x09FAA (Light Control) - True - True
158088 - 0x00422 (Light Room 1) - 0x09FAA - True
158089 - 0x006E3 (Light Room 2) - 0x09FAA - True
158090 - 0x0A02D (Light Room 3) - 0x09FAA & 0x00422 & 0x006E3 - True
Door - 0x0C2C3 (Pond Room Entry) - 0x0A02D
-Desert Pond Room (Desert) - Desert Water Levels Room - 0x0A24B:
+Desert Pond Room (Desert) - Desert Flood Room - 0x0A24B:
158091 - 0x00C72 (Pond Room 1) - True - True
158092 - 0x0129D (Pond Room 2) - 0x00C72 - True
158093 - 0x008BB (Pond Room 3) - 0x0129D - True
@@ -190,7 +207,7 @@ Door - 0x0A24B (Flood Room Entry) - 0x0A249
159043 - 0x0A14C (Pond Room Near Reflection EP) - True - True
159044 - 0x0A14D (Pond Room Far Reflection EP) - True - True
-Desert Water Levels Room (Desert) - Desert Elevator Room - 0x0C316:
+Desert Flood Room (Desert) - Desert Elevator Room - 0x0C316:
158097 - 0x1C2DF (Reduce Water Level Far Left) - True - True
158098 - 0x1831E (Reduce Water Level Far Right) - True - True
158099 - 0x1C260 (Reduce Water Level Near Left) - True - True
@@ -208,7 +225,7 @@ Desert Water Levels Room (Desert) - Desert Elevator Room - 0x0C316:
Door - 0x0C316 (Elevator Room Entry) - 0x18076
159034 - 0x337F8 (Flood Room EP) - 0x1C2DF - True
-Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x01317:
+Desert Elevator Room (Desert) - Desert Behind Elevator - 0x01317:
158111 - 0x17C31 (Elevator Room Transparent) - True - True
158113 - 0x012D7 (Elevator Room Hexagonal) - 0x17C31 & 0x0A015 - True
158114 - 0x0A015 (Elevator Room Hexagonal Control) - 0x17C31 - True
@@ -218,9 +235,19 @@ Desert Elevator Room (Desert) - Desert Lowest Level Inbetween Shortcuts - 0x0131
159035 - 0x037BB (Elevator EP) - 0x01317 - True
Door - 0x01317 (Elevator) - 0x03608
-Desert Lowest Level Inbetween Shortcuts (Desert):
+Desert Behind Elevator (Desert):
-Outside Quarry (Quarry) - Main Island - True - Quarry Between Entrys - 0x09D6F - Quarry Elevator - 0xFFD00 & 0xFFD01:
+==Quarry==
+
+Quarry Obelisk (Quarry) - Entry - True:
+159740 - 0xFFE40 (Obelisk Side 1) - 0x28A7B & 0x005F6 & 0x00859 & 0x17CB9 & 0x28A4A - True
+159741 - 0xFFE41 (Obelisk Side 2) - 0x334B6 & 0x00614 & 0x0069D & 0x28A4C - True
+159742 - 0xFFE42 (Obelisk Side 3) - 0x289CF & 0x289D1 - True
+159743 - 0xFFE43 (Obelisk Side 4) - 0x33692 - True
+159744 - 0xFFE44 (Obelisk Side 5) - 0x03E77 & 0x03E7C - True
+159749 - 0x22073 (Obelisk) - True - True
+
+Outside Quarry (Quarry) - Main Island - True - Quarry Between Entry Doors - 0x09D6F - Quarry Elevator - 0xFFD00 & 0xFFD01:
158118 - 0x09E57 (Entry 1 Panel) - True - Black/White Squares
158603 - 0x17CF0 (Discard) - True - Triangles
158702 - 0x03612 (Laser Panel) - 0x0A3D0 & 0x0367C - Eraser & Shapers
@@ -236,7 +263,7 @@ Quarry Elevator (Quarry) - Outside Quarry - 0x17CC4 - Quarry - 0x17CC4:
158120 - 0x17CC4 (Elevator Control) - 0x0367C - Dots & Eraser
159403 - 0x17CB9 (Railroad EP) - 0x17CC4 - True
-Quarry Between Entrys (Quarry) - Quarry - 0x17C07:
+Quarry Between Entry Doors (Quarry) - Quarry - 0x17C07:
158119 - 0x17C09 (Entry 2 Panel) - True - Shapers
Door - 0x17C07 (Entry 2) - 0x17C09
@@ -322,6 +349,8 @@ Door - 0x3865F (Second Barrier) - 0x38663
158169 - 0x0A3D0 (Back Second Row 3) - 0x0A3CC - Stars & Eraser & Shapers
159401 - 0x005F6 (Hook EP) - 0x275FA & 0x03852 & 0x3865F - True
+==Shadows==
+
Shadows (Shadows) - Main Island - True - Shadows Ledge - 0x19B24 - Shadows Laser Room - 0x194B2 | 0x19665:
158170 - 0x334DB (Door Timer Outside) - True - True
Door - 0x19B24 (Timed Door) - 0x334DB | 0x334DC
@@ -361,19 +390,18 @@ Shadows Laser Room (Shadows):
158703 - 0x19650 (Laser Panel) - 0x194B2 & 0x19665 - True
Laser - 0x181B3 (Laser) - 0x19650
-Treehouse Beach (Treehouse Beach) - Main Island - True:
-159200 - 0x0053D (Rock Shadow EP) - True - True
-159201 - 0x0053E (Sand Shadow EP) - True - True
-159212 - 0x220BD (Both Orange Bridges EP) - 0x17DA2 & 0x17DDB - True
+==Keep==
-Keep (Keep) - Main Island - True - Keep 2nd Maze - 0x01954 - Keep 2nd Pressure Plate - 0x01BEC:
+Outside Keep (Keep) - Main Island - True:
+159430 - 0x03E77 (Red Flowers EP) - True - True
+159431 - 0x03E7C (Purple Flowers EP) - True - True
+
+Keep (Keep) - Outside Keep - True - Keep 2nd Maze - 0x01954 - Keep 2nd Pressure Plate - 0x01BEC:
158193 - 0x00139 (Hedge Maze 1) - True - True
158197 - 0x0A3A8 (Reset Pressure Plates 1) - True - True
158198 - 0x033EA (Pressure Plates 1) - 0x0A3A8 - Dots
Door - 0x01954 (Hedge Maze 1 Exit) - 0x00139
Door - 0x01BEC (Pressure Plates 1 Exit) - 0x033EA
-159430 - 0x03E77 (Red Flowers EP) - True - True
-159431 - 0x03E7C (Purple Flowers EP) - True - True
Keep 2nd Maze (Keep) - Keep - 0x018CE - Keep 3rd Maze - 0x019D8:
Door - 0x018CE (Hedge Maze 2 Shortcut) - 0x00139
@@ -408,6 +436,22 @@ Door - 0x01D40 (Pressure Plates 4 Exit) - 0x01D3F
158205 - 0x09E49 (Shadows Shortcut Panel) - True - True
Door - 0x09E3D (Shadows Shortcut) - 0x09E49
+Keep Tower (Keep) - Keep - 0x04F8F:
+158206 - 0x0361B (Tower Shortcut Panel) - True - True
+Door - 0x04F8F (Tower Shortcut) - 0x0361B
+158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True
+158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Dots & Shapers & Black/White Squares & Rotated Shapers
+Laser - 0x014BB (Laser) - 0x0360E | 0x03317
+159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True
+159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 & 0x01BEA - True
+159242 - 0x033DD (Pressure Plates 3 EP) - 0x01CD3 & 0x01CD5 - True
+159243 - 0x033E5 (Pressure Plates 4 Left Exit EP) - 0x01D3F - True
+159244 - 0x018B6 (Pressure Plates 4 Right Exit EP) - 0x01D3F - True
+159250 - 0x28AE9 (Path EP) - True - True
+159251 - 0x3348F (Hedges EP) - True - True
+
+==Shipwreck==
+
Shipwreck (Shipwreck) - Keep 3rd Pressure Plate - True - Shipwreck Vault - 0x17BB4:
158654 - 0x00AFB (Vault Panel) - True - Symmetry & Sound Dots & Colored Dots
Door - 0x17BB4 (Vault Door) - 0x00AFB
@@ -423,19 +467,16 @@ Door - 0x17BB4 (Vault Door) - 0x00AFB
Shipwreck Vault (Shipwreck):
158655 - 0x03535 (Vault Box) - True - True
-Keep Tower (Keep) - Keep - 0x04F8F:
-158206 - 0x0361B (Tower Shortcut Panel) - True - True
-Door - 0x04F8F (Tower Shortcut) - 0x0361B
-158704 - 0x0360E (Laser Panel Hedges) - 0x01A0F & 0x019E7 & 0x019DC & 0x00139 - True
-158705 - 0x03317 (Laser Panel Pressure Plates) - 0x033EA & 0x01BE9 & 0x01CD3 & 0x01D3F - Dots & Shapers & Black/White Squares & Rotated Shapers
-Laser - 0x014BB (Laser) - 0x0360E | 0x03317
-159240 - 0x033BE (Pressure Plates 1 EP) - 0x033EA - True
-159241 - 0x033BF (Pressure Plates 2 EP) - 0x01BE9 & 0x01BEA - True
-159242 - 0x033DD (Pressure Plates 3 EP) - 0x01CD3 & 0x01CD5 - True
-159243 - 0x033E5 (Pressure Plates 4 Left Exit EP) - 0x01D3F - True
-159244 - 0x018B6 (Pressure Plates 4 Right Exit EP) - 0x01D3F - True
-159250 - 0x28AE9 (Path EP) - True - True
-159251 - 0x3348F (Hedges EP) - True - True
+==Monastery==
+
+Monastery Obelisk (Monastery) - Entry - True:
+159710 - 0xFFE10 (Obelisk Side 1) - 0x03ABC & 0x03ABE & 0x03AC0 & 0x03AC4 - True
+159711 - 0xFFE11 (Obelisk Side 2) - 0x03AC5 - True
+159712 - 0xFFE12 (Obelisk Side 3) - 0x03BE2 & 0x03BE3 & 0x0A409 - True
+159713 - 0xFFE13 (Obelisk Side 4) - 0x006E5 & 0x006E6 & 0x006E7 & 0x034A7 & 0x034AD & 0x034AF & 0x03DAB & 0x03DAC & 0x03DAD - True
+159714 - 0xFFE14 (Obelisk Side 5) - 0x03E01 - True
+159715 - 0xFFE15 (Obelisk Side 6) - 0x289F4 & 0x289F5 - True
+159719 - 0x00263 (Obelisk) - True - True
Outside Monastery (Monastery) - Main Island - True - Inside Monastery - 0x0C128 & 0x0C153 - Monastery Garden - 0x03750:
158207 - 0x03713 (Laser Shortcut Panel) - True - True
@@ -457,6 +498,9 @@ Laser - 0x17C65 (Laser) - 0x17CA4
159137 - 0x03DAC (Facade Left Stairs EP) - True - True
159138 - 0x03DAD (Facade Right Stairs EP) - True - True
159140 - 0x03E01 (Grass Stairs EP) - True - True
+159120 - 0x03BE2 (Garden Left EP) - 0x03750 - True
+159121 - 0x03BE3 (Garden Right EP) - True - True
+159122 - 0x0A409 (Wall EP) - True - True
Inside Monastery (Monastery):
158213 - 0x09D9B (Shutters Control) - True - Dots
@@ -470,7 +514,18 @@ Inside Monastery (Monastery):
Monastery Garden (Monastery):
-Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x28AA2 - Town Church - True - Town Wooden Rooftop - 0x034F5 - RGB House - 0x28A61 - Windmill Interior - 0x1845B - Town Inside Cargo Box - 0x0A0C9:
+==Town==
+
+Town Obelisk (Town) - Entry - True:
+159750 - 0xFFE50 (Obelisk Side 1) - 0x035C7 - True
+159751 - 0xFFE51 (Obelisk Side 2) - 0x01848 & 0x03D06 & 0x33530 & 0x33600 & 0x28A2F & 0x28A37 & 0x334A3 & 0x3352F - True
+159752 - 0xFFE52 (Obelisk Side 3) - 0x33857 & 0x33879 & 0x03C19 - True
+159753 - 0xFFE53 (Obelisk Side 4) - 0x28B30 & 0x035C9 - True
+159754 - 0xFFE54 (Obelisk Side 5) - 0x03335 & 0x03412 & 0x038A6 & 0x038AA & 0x03E3F & 0x03E40 & 0x28B8E - True
+159755 - 0xFFE55 (Obelisk Side 6) - 0x28B91 & 0x03BCE & 0x03BCF & 0x03BD1 & 0x339B6 & 0x33A20 & 0x33A29 & 0x33A2A & 0x33B06 - True
+159759 - 0x0A16C (Obelisk) - True - True
+
+Town (Town) - Main Island - True - The Ocean - 0x0A054 - Town Maze Rooftop - 0x28AA2 - Town Church - True - Town Wooden Rooftop - 0x034F5 - Town RGB House - 0x28A61 - Town Inside Cargo Box - 0x0A0C9 - Outside Windmill - True:
158218 - 0x0A054 (Boat Spawn) - 0x17CA6 | 0x17CDF | 0x09DB8 | 0x17C95 - Boat
158219 - 0x0A0C8 (Cargo Box Entry Panel) - True - Black/White Squares & Shapers
Door - 0x0A0C9 (Cargo Box Entry) - 0x0A0C8
@@ -491,11 +546,6 @@ Door - 0x28A61 (RGB House Entry) - 0x28998
Door - 0x03BB0 (Church Entry) - 0x28A0D
158228 - 0x28A79 (Maze Panel) - True - True
Door - 0x28AA2 (Maze Stairs) - 0x28A79
-158241 - 0x17F5F (Windmill Entry Panel) - True - Dots
-Door - 0x1845B (Windmill Entry) - 0x17F5F
-159010 - 0x037B6 (Windmill First Blade EP) - 0x17D02 - True
-159011 - 0x037B2 (Windmill Second Blade EP) - 0x17D02 - True
-159012 - 0x000F7 (Windmill Third Blade EP) - 0x17D02 - True
159540 - 0x03335 (Tower Underside Third EP) - True - True
159541 - 0x03412 (Tower Underside Fourth EP) - True - True
159542 - 0x038A6 (Tower Underside First EP) - True - True
@@ -528,20 +578,26 @@ Town Church (Town):
158227 - 0x28A69 (Church Lattice) - 0x03BB0 - True
159553 - 0x03BD1 (Black Line Church EP) - True - True
-RGB House (Town) - RGB Room - 0x2897B:
+Town RGB House (Town RGB House) - Town RGB House Upstairs - 0x2897B:
158242 - 0x034E4 (Sound Room Left) - True - True
158243 - 0x034E3 (Sound Room Right) - True - Sound Dots
-Door - 0x2897B (RGB House Stairs) - 0x034E4 & 0x034E3
+Door - 0x2897B (Stairs) - 0x034E4 & 0x034E3
-RGB Room (Town):
+Town RGB House Upstairs (Town RGB House Upstairs):
158244 - 0x334D8 (RGB Control) - True - Rotated Shapers & Colored Squares
-158245 - 0x03C0C (RGB Room Left) - 0x334D8 - Colored Squares & Black/White Squares
-158246 - 0x03C08 (RGB Room Right) - 0x334D8 - Stars
+158245 - 0x03C0C (Left) - 0x334D8 - Colored Squares & Black/White Squares
+158246 - 0x03C08 (Right) - 0x334D8 - Stars
+
+Town Tower Bottom (Town Tower) - Town - True - Town Tower After First Door - 0x27799:
+Door - 0x27799 (First Door) - 0x28A69
-Town Tower (Town Tower) - Town - True - Town Tower Top - 0x27798 & 0x27799 & 0x2779A & 0x2779C:
+Town Tower After First Door (Town Tower) - Town Tower After Second Door - 0x27798:
Door - 0x27798 (Second Door) - 0x28ACC
+
+Town Tower After Second Door (Town Tower) - Town Tower After Third Door - 0x2779C:
Door - 0x2779C (Third Door) - 0x28AD9
-Door - 0x27799 (First Door) - 0x28A69
+
+Town Tower After Third Door (Town Tower) - Town Tower Top - 0x2779A:
Door - 0x2779A (Fourth Door) - 0x28B39
Town Tower Top (Town):
@@ -550,6 +606,15 @@ Laser - 0x032F9 (Laser) - 0x032F5
159422 - 0x33692 (Brown Bridge EP) - True - True
159551 - 0x03BCE (Black Line Tower EP) - True - True
+==Windmill & Theater==
+
+Outside Windmill (Windmill) - Windmill Interior - 0x1845B:
+159010 - 0x037B6 (First Blade EP) - 0x17D02 - True
+159011 - 0x037B2 (Second Blade EP) - 0x17D02 - True
+159012 - 0x000F7 (Third Blade EP) - 0x17D02 - True
+158241 - 0x17F5F (Entry Panel) - True - Dots
+Door - 0x1845B (Entry) - 0x17F5F
+
Windmill Interior (Windmill) - Theater - 0x17F88:
158247 - 0x17D02 (Turn Control) - True - Dots
158248 - 0x17F89 (Theater Entry Panel) - True - Black/White Squares
@@ -573,6 +638,8 @@ Door - 0x3CCDF (Exit Right) - 0x33AB2
159556 - 0x33A2A (Door EP) - 0x03553 - True
159558 - 0x33B06 (Church EP) - 0x0354E - True
+==Jungle==
+
Jungle (Jungle) - Main Island - True - The Ocean - 0x17CDF:
158251 - 0x17CDF (Shore Boat Spawn) - True - Boat
158609 - 0x17F9B (Discard) - True - Triangles
@@ -604,19 +671,18 @@ Door - 0x3873B (Laser Shortcut) - 0x337FA
159350 - 0x035CB (Bamboo CCW EP) - True - True
159351 - 0x035CF (Bamboo CW EP) - True - True
-Outside Jungle River (River) - Main Island - True - Monastery Garden - 0x0CF2A - River Vault - 0x15287:
+Outside Jungle River (Jungle) - Main Island - True - Monastery Garden - 0x0CF2A - Jungle Vault - 0x15287:
158267 - 0x17CAA (Monastery Garden Shortcut Panel) - True - True
Door - 0x0CF2A (Monastery Garden Shortcut) - 0x17CAA
158663 - 0x15ADD (Vault Panel) - True - Black/White Squares & Dots
Door - 0x15287 (Vault Door) - 0x15ADD
159110 - 0x03AC5 (Green Leaf Moss EP) - True - True
-159120 - 0x03BE2 (Monastery Garden Left EP) - 0x03750 - True
-159121 - 0x03BE3 (Monastery Garden Right EP) - True - True
-159122 - 0x0A409 (Monastery Wall EP) - True - True
-River Vault (River):
+Jungle Vault (Jungle):
158664 - 0x03702 (Vault Box) - True - True
+==Bunker==
+
Outside Bunker (Bunker) - Main Island - True - Bunker - 0x0C2A4:
158268 - 0x17C2E (Entry Panel) - True - Black/White Squares
Door - 0x0C2A4 (Entry) - 0x17C2E
@@ -650,9 +716,11 @@ Door - 0x0A08D (Elevator Room Entry) - 0x17E67
Bunker Elevator Section (Bunker) - Bunker Elevator - TrueOneWay:
159311 - 0x035F5 (Tinted Door EP) - 0x17C79 - True
-Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079:
+Bunker Elevator (Bunker) - Bunker Elevator Section - 0x0A079 - Bunker Cyan Room - 0x0A079 - Bunker Green Room - 0x0A079 - Bunker Laser Platform - 0x0A079 - Outside Bunker - 0x0A079:
158286 - 0x0A079 (Elevator Control) - True - Colored Squares & Black/White Squares
+Bunker Cyan Room (Bunker) - Bunker Elevator - TrueOneWay:
+
Bunker Green Room (Bunker) - Bunker Elevator - TrueOneWay:
159310 - 0x000D3 (Green Room Flowers EP) - True - True
@@ -660,6 +728,8 @@ Bunker Laser Platform (Bunker) - Bunker Elevator - TrueOneWay:
158710 - 0x09DE0 (Laser Panel) - True - True
Laser - 0x0C2B2 (Laser) - 0x09DE0
+==Swamp==
+
Outside Swamp (Swamp) - Swamp Entry Area - 0x00C1C - Main Island - True:
158287 - 0x0056E (Entry Panel) - True - Shapers
Door - 0x00C1C (Entry) - 0x0056E
@@ -774,13 +844,29 @@ Laser - 0x00BF6 (Laser) - 0x03615
158342 - 0x17C02 (Laser Shortcut Right Panel) - 0x17C05 - Shapers & Negative Shapers & Rotated Shapers
Door - 0x2D880 (Laser Shortcut) - 0x17C02
-Treehouse Entry Area (Treehouse) - Treehouse Between Doors - 0x0C309 - The Ocean - 0x17C95:
+==Treehouse==
+
+Treehouse Obelisk (Treehouse) - Entry - True:
+159720 - 0xFFE20 (Obelisk Side 1) - 0x0053D & 0x0053E & 0x00769 - True
+159721 - 0xFFE21 (Obelisk Side 2) - 0x33721 & 0x220A7 & 0x220BD - True
+159722 - 0xFFE22 (Obelisk Side 3) - 0x03B22 & 0x03B23 & 0x03B24 & 0x03B25 & 0x03A79 & 0x28ABD & 0x28ABE - True
+159723 - 0xFFE23 (Obelisk Side 4) - 0x3388F & 0x28B29 & 0x28B2A - True
+159724 - 0xFFE24 (Obelisk Side 5) - 0x018B6 & 0x033BE & 0x033BF & 0x033DD & 0x033E5 - True
+159725 - 0xFFE25 (Obelisk Side 6) - 0x28AE9 & 0x3348F - True
+159729 - 0x00097 (Obelisk) - True - True
+
+Treehouse Beach (Treehouse Beach) - Main Island - True:
+159200 - 0x0053D (Rock Shadow EP) - True - True
+159201 - 0x0053E (Sand Shadow EP) - True - True
+159212 - 0x220BD (Both Orange Bridges EP) - 0x17DA2 & 0x17DDB - True
+
+Treehouse Entry Area (Treehouse) - Treehouse Between Entry Doors - 0x0C309 - The Ocean - 0x17C95:
158343 - 0x17C95 (Boat Spawn) - True - Boat
158344 - 0x0288C (First Door Panel) - True - Stars
Door - 0x0C309 (First Door) - 0x0288C
159210 - 0x33721 (Buoy EP) - 0x17C95 - True
-Treehouse Between Doors (Treehouse) - Treehouse Yellow Bridge - 0x0C310:
+Treehouse Between Entry Doors (Treehouse) - Treehouse Yellow Bridge - 0x0C310:
158345 - 0x02886 (Second Door Panel) - True - Stars
Door - 0x0C310 (Second Door) - 0x02886
@@ -809,7 +895,7 @@ Treehouse First Purple Bridge (Treehouse) - Treehouse Second Purple Bridge - 0x1
158360 - 0x17D2D (First Purple Bridge 4) - 0x17CE4 - Stars & Dots
158361 - 0x17D6C (First Purple Bridge 5) - 0x17D2D - Stars & Dots
-Treehouse Right Orange Bridge (Treehouse) - Treehouse Bridge Platform - 0x17DA2:
+Treehouse Right Orange Bridge (Treehouse) - Treehouse Drawbridge Platform - 0x17DA2:
158391 - 0x17D88 (Right Orange Bridge 1) - True - Stars
158392 - 0x17DB4 (Right Orange Bridge 2) - 0x17D88 - Stars
158393 - 0x17D8C (Right Orange Bridge 3) - 0x17DB4 - Stars
@@ -823,7 +909,7 @@ Treehouse Right Orange Bridge (Treehouse) - Treehouse Bridge Platform - 0x17DA2:
158401 - 0x17DB1 (Right Orange Bridge 11) - 0x17DB7 - Stars
158402 - 0x17DA2 (Right Orange Bridge 12) - 0x17DB1 - Stars
-Treehouse Bridge Platform (Treehouse) - Main Island - 0x0C32D:
+Treehouse Drawbridge Platform (Treehouse) - Main Island - 0x0C32D:
158404 - 0x037FF (Drawbridge Panel) - True - Stars
Door - 0x0C32D (Drawbridge) - 0x037FF
@@ -882,7 +968,19 @@ Treehouse Laser Room (Treehouse):
158403 - 0x17CBC (Laser House Door Timer Inside) - True - True
Laser - 0x028A4 (Laser) - 0x03613
+==Mountain (Outside)==
+
+Mountainside Obelisk (Mountainside) - Entry - True:
+159730 - 0xFFE30 (Obelisk Side 1) - 0x001A3 & 0x335AE - True
+159731 - 0xFFE31 (Obelisk Side 2) - 0x000D3 & 0x035F5 & 0x09D5D & 0x09D5E & 0x09D63 - True
+159732 - 0xFFE32 (Obelisk Side 3) - 0x3370E & 0x035DE & 0x03601 & 0x03603 & 0x03D0D & 0x3369A & 0x336C8 & 0x33505 - True
+159733 - 0xFFE33 (Obelisk Side 4) - 0x03A9E & 0x016B2 & 0x3365F & 0x03731 & 0x036CE & 0x03C07 & 0x03A93 - True
+159734 - 0xFFE34 (Obelisk Side 5) - 0x03AA6 & 0x3397C & 0x0105D & 0x0A304 - True
+159735 - 0xFFE35 (Obelisk Side 6) - 0x035CB & 0x035CF - True
+159739 - 0x00367 (Obelisk) - True - True
+
Mountainside (Mountainside) - Main Island - True - Mountaintop - True - Mountainside Vault - 0x00085:
+159550 - 0x28B91 (Thundercloud EP) - 0x09F98 & 0x012FB - True
158612 - 0x17C42 (Discard) - True - Triangles
158665 - 0x002A6 (Vault Panel) - True - Symmetry & Colored Dots & Black/White Squares
Door - 0x00085 (Vault Door) - 0x002A6
@@ -893,7 +991,7 @@ Door - 0x00085 (Vault Door) - 0x002A6
Mountainside Vault (Mountainside):
158666 - 0x03542 (Vault Box) - True - True
-Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34:
+Mountaintop (Mountaintop) - Mountain Floor 1 - 0x17C34:
158405 - 0x0042D (River Shape) - True - True
158406 - 0x09F7F (Box Short) - 7 Lasers + Redirect - True
158407 - 0x17C34 (Mountain Entry Panel) - 0x09F7F - Black/White Squares
@@ -903,10 +1001,12 @@ Mountaintop (Mountaintop) - Mountain Top Layer - 0x17C34:
159324 - 0x336C8 (Arch White Right EP) - True - True
159326 - 0x3369A (Arch White Left EP) - True - True
-Mountain Top Layer (Mountain Floor 1) - Mountain Top Layer Bridge - 0x09E39:
+==Mountain (Inside)==
+
+Mountain Floor 1 (Mountain Floor 1) - Mountain Floor 1 Bridge - 0x09E39:
158408 - 0x09E39 (Light Bridge Controller) - True - Black/White Squares & Rotated Shapers
-Mountain Top Layer Bridge (Mountain Floor 1) - Mountain Top Layer At Door - TrueOneWay:
+Mountain Floor 1 Bridge (Mountain Floor 1) - Mountain Floor 1 At Door - TrueOneWay:
158409 - 0x09E7A (Right Row 1) - True - Black/White Squares & Dots
158410 - 0x09E71 (Right Row 2) - 0x09E7A - Black/White Squares & Dots
158411 - 0x09E72 (Right Row 3) - 0x09E71 - Black/White Squares & Shapers
@@ -925,10 +1025,10 @@ Mountain Top Layer Bridge (Mountain Floor 1) - Mountain Top Layer At Door - True
158424 - 0x09EAD (Trash Pillar 1) - True - Black/White Squares & Shapers
158425 - 0x09EAF (Trash Pillar 2) - 0x09EAD - Black/White Squares & Shapers
-Mountain Top Layer At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54:
+Mountain Floor 1 At Door (Mountain Floor 1) - Mountain Floor 2 - 0x09E54:
Door - 0x09E54 (Exit) - 0x09EAF & 0x09F6E & 0x09E6B & 0x09E7B
-Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Blue Bridge - 0x09E86 - Mountain Pink Bridge EP - TrueOneWay:
+Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near - 0x09FFB - Mountain Floor 2 Beyond Bridge - 0x09E86 - Mountain Floor 2 At Door - 0x09ED8 & 0x09E86 - Mountain Pink Bridge EP - TrueOneWay:
158426 - 0x09FD3 (Near Row 1) - True - Colored Squares
158427 - 0x09FD4 (Near Row 2) - 0x09FD3 - Colored Squares & Dots
158428 - 0x09FD6 (Near Row 3) - 0x09FD4 - Stars & Colored Squares & Stars + Same Colored Symbol
@@ -936,8 +1036,6 @@ Mountain Floor 2 (Mountain Floor 2) - Mountain Floor 2 Light Bridge Room Near -
158430 - 0x09FD8 (Near Row 5) - 0x09FD7 - Colored Squares
Door - 0x09FFB (Staircase Near) - 0x09FD8
-Mountain Floor 2 Blue Bridge (Mountain Floor 2) - Mountain Floor 2 Beyond Bridge - TrueOneWay - Mountain Floor 2 At Door - 0x09ED8:
-
Mountain Floor 2 At Door (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EDD:
Door - 0x09EDD (Elevator Room Entry) - 0x09ED8 & 0x09E86
@@ -959,10 +1057,10 @@ Mountain Floor 2 Light Bridge Room Far (Mountain Floor 2):
Mountain Floor 2 Elevator Room (Mountain Floor 2) - Mountain Floor 2 Elevator - TrueOneWay:
158613 - 0x17F93 (Elevator Discard) - True - Triangles
-Mountain Floor 2 Elevator (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EEB - Mountain Third Layer - 0x09EEB:
+Mountain Floor 2 Elevator (Mountain Floor 2) - Mountain Floor 2 Elevator Room - 0x09EEB - Mountain Floor 3 - 0x09EEB:
158439 - 0x09EEB (Elevator Control Panel) - True - Dots
-Mountain Third Layer (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueOneWay - Mountain Bottom Floor - 0x09F89 - Mountain Pink Bridge EP - TrueOneWay:
+Mountain Floor 3 (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueOneWay - Mountain Bottom Floor - 0x09F89 - Mountain Pink Bridge EP - TrueOneWay:
158440 - 0x09FC1 (Giant Puzzle Bottom Left) - True - Shapers & Eraser
158441 - 0x09F8E (Giant Puzzle Bottom Right) - True - Rotated Shapers & Eraser
158442 - 0x09F01 (Giant Puzzle Top Right) - True - Shapers & Eraser
@@ -972,13 +1070,32 @@ Mountain Third Layer (Mountain Bottom Floor) - Mountain Floor 2 Elevator - TrueO
159314 - 0x09D5E (Blue Bridge EP) - 0x09E86 & 0x09ED8 - True
Door - 0x09F89 (Exit) - 0x09FDA
-Mountain Bottom Floor (Mountain Bottom Floor) - Mountain Path to Caves - 0x17F33 - Final Room - 0x0C141:
+Mountain Bottom Floor (Mountain Bottom Floor) - Mountain Path to Caves - 0x17F33 - Mountain Bottom Floor Pillars Room - 0x0C141:
158614 - 0x17FA2 (Discard) - 0xFFF00 - Triangles
-158445 - 0x01983 (Final Room Entry Left) - True - Shapers & Stars
-158446 - 0x01987 (Final Room Entry Right) - True - Colored Squares & Dots
-Door - 0x0C141 (Final Room Entry) - 0x01983 & 0x01987
+158445 - 0x01983 (Pillars Room Entry Left) - True - Shapers & Stars
+158446 - 0x01987 (Pillars Room Entry Right) - True - Colored Squares & Dots
+Door - 0x0C141 (Pillars Room Entry) - 0x01983 & 0x01987
Door - 0x17F33 (Rock Open) - 0x17FA2 | 0x334E1
+Mountain Bottom Floor Pillars Room (Mountain Bottom Floor) - Elevator - 0x339BB & 0x33961:
+158522 - 0x0383A (Right Pillar 1) - True - Stars
+158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Stars & Dots
+158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Full Dots
+158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Dots & Symmetry
+158526 - 0x0383D (Left Pillar 1) - True - Dots & Full Dots
+158527 - 0x0383F (Left Pillar 2) - 0x0383D - Black/White Squares
+158528 - 0x03859 (Left Pillar 3) - 0x0383F - Shapers
+158529 - 0x339BB (Left Pillar 4) - 0x03859 - Black/White Squares & Stars & Symmetry
+
+Elevator (Mountain Bottom Floor):
+158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True
+158531 - 0x3D9A7 (Elevator Door Close Right) - True - True
+158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True
+158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True
+158534 - 0x3D9AA (Back Wall Left) - 0x3D9A6 | 0x3D9A7 - True
+158535 - 0x3D9A8 (Back Wall Right) - 0x3D9A6 | 0x3D9A7 - True
+158536 - 0x3D9A9 (Elevator Start) - 0x3D9AA & 7 Lasers | 0x3D9A8 & 7 Lasers - True
+
Mountain Pink Bridge EP (Mountain Floor 2):
159312 - 0x09D63 (Pink Bridge EP) - 0x09E39 - True
@@ -987,7 +1104,9 @@ Mountain Path to Caves (Mountain Bottom Floor) - Caves - 0x2D77D:
Door - 0x2D77D (Caves Entry) - 0x00FF8
158448 - 0x334E1 (Rock Control) - True - True
-Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Path to Challenge - 0x019A5:
+==Caves==
+
+Caves (Caves) - Main Island - 0x2D73F | 0x2D859 - Caves Path to Challenge - 0x019A5:
158451 - 0x335AB (Elevator Inside Control) - True - Dots & Black/White Squares
158452 - 0x335AC (Elevator Upper Outside Control) - 0x335AB - Black/White Squares
158453 - 0x3369D (Elevator Lower Outside Control) - 0x335AB - Black/White Squares & Dots
@@ -1042,10 +1161,12 @@ Door - 0x2D73F (Mountain Shortcut Door) - 0x021D7
Door - 0x2D859 (Swamp Shortcut Door) - 0x17CF2
159341 - 0x3397C (Skylight EP) - True - True
-Path to Challenge (Caves) - Challenge - 0x0A19A:
+Caves Path to Challenge (Caves) - Challenge - 0x0A19A:
158477 - 0x0A16E (Challenge Entry Panel) - True - Stars & Shapers & Stars + Same Colored Symbol
Door - 0x0A19A (Challenge Entry) - 0x0A16E
+==Challenge==
+
Challenge (Challenge) - Tunnels - 0x0348A - Challenge Vault - 0x04D75:
158499 - 0x0A332 (Start Timer) - 11 Lasers - True
158500 - 0x0088E (Small Basic) - 0x0A332 - True
@@ -1074,7 +1195,9 @@ Door - 0x0348A (Tunnels Entry) - 0x039B4
Challenge Vault (Challenge):
158667 - 0x0356B (Vault Box) - 0x1C31A & 0x1C319 - True
-Tunnels (Tunnels) - Windmill Interior - 0x27739 - Desert Lowest Level Inbetween Shortcuts - 0x27263 - Town - 0x09E87:
+==Tunnels==
+
+Tunnels (Tunnels) - Windmill Interior - 0x27739 - Desert Behind Elevator - 0x27263 - Town - 0x09E87:
158668 - 0x2FAF6 (Vault Box) - True - True
158519 - 0x27732 (Theater Shortcut Panel) - True - True
Door - 0x27739 (Theater Shortcut) - 0x27732
@@ -1084,24 +1207,7 @@ Door - 0x27263 (Desert Shortcut) - 0x2773D
Door - 0x09E87 (Town Shortcut) - 0x09E85
159557 - 0x33A20 (Theater Flowers EP) - 0x03553 & Theater to Tunnels - True
-Final Room (Mountain Final Room) - Elevator - 0x339BB & 0x33961:
-158522 - 0x0383A (Right Pillar 1) - True - Stars
-158523 - 0x09E56 (Right Pillar 2) - 0x0383A - Stars & Dots
-158524 - 0x09E5A (Right Pillar 3) - 0x09E56 - Dots & Full Dots
-158525 - 0x33961 (Right Pillar 4) - 0x09E5A - Dots & Symmetry
-158526 - 0x0383D (Left Pillar 1) - True - Dots & Full Dots
-158527 - 0x0383F (Left Pillar 2) - 0x0383D - Black/White Squares
-158528 - 0x03859 (Left Pillar 3) - 0x0383F - Shapers
-158529 - 0x339BB (Left Pillar 4) - 0x03859 - Black/White Squares & Stars & Symmetry
-
-Elevator (Mountain Final Room):
-158530 - 0x3D9A6 (Elevator Door Closer Left) - True - True
-158531 - 0x3D9A7 (Elevator Door Close Right) - True - True
-158532 - 0x3C113 (Elevator Entry Left) - 0x3D9A6 | 0x3D9A7 - True
-158533 - 0x3C114 (Elevator Entry Right) - 0x3D9A6 | 0x3D9A7 - True
-158534 - 0x3D9AA (Back Wall Left) - 0x3D9A6 | 0x3D9A7 - True
-158535 - 0x3D9A8 (Back Wall Right) - 0x3D9A6 | 0x3D9A7 - True
-158536 - 0x3D9A9 (Elevator Start) - 0x3D9AA & 7 Lasers | 0x3D9A8 & 7 Lasers - True
+==Boat==
The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Treehouse Entry Area - TrueOneWay - Quarry Boathouse Behind Staircase - TrueOneWay - Inside Glass Factory Behind Back Wall - TrueOneWay:
159042 - 0x22106 (Desert EP) - True - True
@@ -1114,45 +1220,3 @@ The Ocean (Boat) - Main Island - TrueOneWay - Swamp Near Boat - TrueOneWay - Tre
159521 - 0x33879 (Tutorial Reflection EP) - True - True
159522 - 0x03C19 (Tutorial Moss EP) - True - True
159531 - 0x035C9 (Cargo Box EP) - 0x0A0C9 - True
-
-Obelisks (EPs) - Entry - True:
-159700 - 0xFFE00 (Desert Obelisk Side 1) - 0x0332B & 0x03367 & 0x28B8A - True
-159701 - 0xFFE01 (Desert Obelisk Side 2) - 0x037B6 & 0x037B2 & 0x000F7 - True
-159702 - 0xFFE02 (Desert Obelisk Side 3) - 0x3351D - True
-159703 - 0xFFE03 (Desert Obelisk Side 4) - 0x0053C & 0x00771 & 0x335C8 & 0x335C9 & 0x337F8 & 0x037BB & 0x220E4 & 0x220E5 - True
-159704 - 0xFFE04 (Desert Obelisk Side 5) - 0x334B9 & 0x334BC & 0x22106 & 0x0A14C & 0x0A14D - True
-159709 - 0x00359 (Desert Obelisk) - True - True
-159710 - 0xFFE10 (Monastery Obelisk Side 1) - 0x03ABC & 0x03ABE & 0x03AC0 & 0x03AC4 - True
-159711 - 0xFFE11 (Monastery Obelisk Side 2) - 0x03AC5 - True
-159712 - 0xFFE12 (Monastery Obelisk Side 3) - 0x03BE2 & 0x03BE3 & 0x0A409 - True
-159713 - 0xFFE13 (Monastery Obelisk Side 4) - 0x006E5 & 0x006E6 & 0x006E7 & 0x034A7 & 0x034AD & 0x034AF & 0x03DAB & 0x03DAC & 0x03DAD - True
-159714 - 0xFFE14 (Monastery Obelisk Side 5) - 0x03E01 - True
-159715 - 0xFFE15 (Monastery Obelisk Side 6) - 0x289F4 & 0x289F5 - True
-159719 - 0x00263 (Monastery Obelisk) - True - True
-159720 - 0xFFE20 (Treehouse Obelisk Side 1) - 0x0053D & 0x0053E & 0x00769 - True
-159721 - 0xFFE21 (Treehouse Obelisk Side 2) - 0x33721 & 0x220A7 & 0x220BD - True
-159722 - 0xFFE22 (Treehouse Obelisk Side 3) - 0x03B22 & 0x03B23 & 0x03B24 & 0x03B25 & 0x03A79 & 0x28ABD & 0x28ABE - True
-159723 - 0xFFE23 (Treehouse Obelisk Side 4) - 0x3388F & 0x28B29 & 0x28B2A - True
-159724 - 0xFFE24 (Treehouse Obelisk Side 5) - 0x018B6 & 0x033BE & 0x033BF & 0x033DD & 0x033E5 - True
-159725 - 0xFFE25 (Treehouse Obelisk Side 6) - 0x28AE9 & 0x3348F - True
-159729 - 0x00097 (Treehouse Obelisk) - True - True
-159730 - 0xFFE30 (River Obelisk Side 1) - 0x001A3 & 0x335AE - True
-159731 - 0xFFE31 (River Obelisk Side 2) - 0x000D3 & 0x035F5 & 0x09D5D & 0x09D5E & 0x09D63 - True
-159732 - 0xFFE32 (River Obelisk Side 3) - 0x3370E & 0x035DE & 0x03601 & 0x03603 & 0x03D0D & 0x3369A & 0x336C8 & 0x33505 - True
-159733 - 0xFFE33 (River Obelisk Side 4) - 0x03A9E & 0x016B2 & 0x3365F & 0x03731 & 0x036CE & 0x03C07 & 0x03A93 - True
-159734 - 0xFFE34 (River Obelisk Side 5) - 0x03AA6 & 0x3397C & 0x0105D & 0x0A304 - True
-159735 - 0xFFE35 (River Obelisk Side 6) - 0x035CB & 0x035CF - True
-159739 - 0x00367 (River Obelisk) - True - True
-159740 - 0xFFE40 (Quarry Obelisk Side 1) - 0x28A7B & 0x005F6 & 0x00859 & 0x17CB9 & 0x28A4A - True
-159741 - 0xFFE41 (Quarry Obelisk Side 2) - 0x334B6 & 0x00614 & 0x0069D & 0x28A4C - True
-159742 - 0xFFE42 (Quarry Obelisk Side 3) - 0x289CF & 0x289D1 - True
-159743 - 0xFFE43 (Quarry Obelisk Side 4) - 0x33692 - True
-159744 - 0xFFE44 (Quarry Obelisk Side 5) - 0x03E77 & 0x03E7C - True
-159749 - 0x22073 (Quarry Obelisk) - True - True
-159750 - 0xFFE50 (Town Obelisk Side 1) - 0x035C7 - True
-159751 - 0xFFE51 (Town Obelisk Side 2) - 0x01848 & 0x03D06 & 0x33530 & 0x33600 & 0x28A2F & 0x28A37 & 0x334A3 & 0x3352F - True
-159752 - 0xFFE52 (Town Obelisk Side 3) - 0x33857 & 0x33879 & 0x03C19 - True
-159753 - 0xFFE53 (Town Obelisk Side 4) - 0x28B30 & 0x035C9 - True
-159754 - 0xFFE54 (Town Obelisk Side 5) - 0x03335 & 0x03412 & 0x038A6 & 0x038AA & 0x03E3F & 0x03E40 & 0x28B8E - True
-159755 - 0xFFE55 (Town Obelisk Side 6) - 0x28B91 & 0x03BCE & 0x03BCF & 0x03BD1 & 0x339B6 & 0x33A20 & 0x33A29 & 0x33A2A & 0x33B06 - True
-159759 - 0x0A16C (Town Obelisk) - True - True
diff --git a/worlds/witness/__init__.py b/worlds/witness/__init__.py
index a645abc08125..e985dde353aa 100644
--- a/worlds/witness/__init__.py
+++ b/worlds/witness/__init__.py
@@ -2,16 +2,17 @@
Archipelago init file for The Witness
"""
import dataclasses
-from typing import Dict, Optional
+from typing import Dict, Optional
from BaseClasses import Region, Location, MultiWorld, Item, Entrance, Tutorial, CollectionState
from Options import PerGameCommonOptions, Toggle
from .presets import witness_option_presets
-from .hints import get_always_hint_locations, get_always_hint_items, get_priority_hint_locations, \
- get_priority_hint_items, make_hints, generate_joke_hints
from worlds.AutoWorld import World, WebWorld
from .player_logic import WitnessPlayerLogic
from .static_logic import StaticWitnessLogic
+from .hints import get_always_hint_locations, get_always_hint_items, get_priority_hint_locations, \
+ get_priority_hint_items, make_always_and_priority_hints, generate_joke_hints, make_area_hints, get_hintable_areas, \
+ make_extra_location_hints, create_all_hints
from .locations import WitnessPlayerLocations, StaticWitnessLocations
from .items import WitnessItem, StaticWitnessItems, WitnessPlayerItems, ItemData
from .regions import WitnessRegions
@@ -43,10 +44,6 @@ class WitnessWorld(World):
"""
game = "The Witness"
topology_present = False
-
- StaticWitnessLogic()
- StaticWitnessLocations()
- StaticWitnessItems()
web = WitnessWebWorld()
options_dataclass = TheWitnessOptions
@@ -57,6 +54,7 @@ class WitnessWorld(World):
}
location_name_to_id = StaticWitnessLocations.ALL_LOCATIONS_TO_ID
item_name_groups = StaticWitnessItems.item_groups
+ location_name_groups = StaticWitnessLocations.AREA_LOCATION_GROUPS
required_client_version = (0, 4, 4)
@@ -161,7 +159,7 @@ def create_regions(self):
early_items = [item for item in self.items.get_early_items() if item in self.items.get_mandatory_items()]
if early_items:
random_early_item = self.random.choice(early_items)
- if self.options.puzzle_randomization == 1:
+ if self.options.puzzle_randomization == "sigma_expert":
# In Expert, only tag the item as early, rather than forcing it onto the gate.
self.multiworld.local_early_items[self.player][random_early_item] = 1
else:
@@ -184,15 +182,15 @@ def create_regions(self):
# Adjust the needed size for sphere 1 based on how restrictive the settings are in terms of items
needed_size = 3
- needed_size += self.options.puzzle_randomization == 1
+ needed_size += self.options.puzzle_randomization == "sigma_expert"
needed_size += self.options.shuffle_symbols
needed_size += self.options.shuffle_doors > 0
# Then, add checks in order until the required amount of sphere 1 checks is met.
extra_checks = [
- ("First Hallway Room", "First Hallway Bend"),
- ("First Hallway", "First Hallway Straight"),
+ ("Tutorial First Hallway Room", "Tutorial First Hallway Bend"),
+ ("Tutorial First Hallway", "Tutorial First Hallway Straight"),
("Desert Outside", "Desert Surface 1"),
("Desert Outside", "Desert Surface 2"),
]
@@ -277,26 +275,35 @@ def fill_slot_data(self) -> dict:
hint_amount = self.options.hint_amount.value
credits_hint = (
- "This Randomizer is brought to you by",
- "NewSoupVi, Jarno, blastron,",
- "jbzdarkid, sigma144, IHNN, oddGarrett, Exempt-Medic.", -1
+ "This Randomizer is brought to you by\n"
+ "NewSoupVi, Jarno, blastron,\n",
+ "jbzdarkid, sigma144, IHNN, oddGarrett, Exempt-Medic.", -1, -1
)
audio_logs = get_audio_logs().copy()
- if hint_amount != 0:
- generated_hints = make_hints(self, hint_amount, self.own_itempool)
+ if hint_amount:
+ area_hints = round(self.options.area_hint_percentage / 100 * hint_amount)
+
+ generated_hints = create_all_hints(self, hint_amount, area_hints)
self.random.shuffle(audio_logs)
duplicates = min(3, len(audio_logs) // hint_amount)
- for _ in range(0, hint_amount):
- hint = generated_hints.pop(0)
+ for hint in generated_hints:
+ location = hint.location
+ area_amount = hint.area_amount
+
+ # None if junk hint, address if location hint, area string if area hint
+ arg_1 = location.address if location else (hint.area if hint.area else None)
+
+ # self.player if junk hint, player if location hint, progression amount if area hint
+ arg_2 = area_amount if area_amount is not None else (location.player if location else self.player)
for _ in range(0, duplicates):
audio_log = audio_logs.pop()
- self.log_ids_to_hints[int(audio_log, 16)] = hint
+ self.log_ids_to_hints[int(audio_log, 16)] = (hint.wording, arg_1, arg_2)
if audio_logs:
audio_log = audio_logs.pop()
diff --git a/worlds/witness/hints.py b/worlds/witness/hints.py
index c00827feee20..545aef221677 100644
--- a/worlds/witness/hints.py
+++ b/worlds/witness/hints.py
@@ -1,6 +1,9 @@
-from typing import Tuple, List, TYPE_CHECKING
-
-from BaseClasses import Item
+import logging
+from dataclasses import dataclass
+from typing import Tuple, List, TYPE_CHECKING, Set, Dict, Optional
+from BaseClasses import Item, ItemClassification, Location, LocationProgressType, CollectionState
+from . import StaticWitnessLogic
+from .utils import weighted_sample
if TYPE_CHECKING:
from . import WitnessWorld
@@ -75,6 +78,9 @@
"Have you tried Bumper Stickers?\nMaybe after spending so much time on this island, you are longing for a simpler puzzle game.",
"Have you tried Pokemon Emerald?\nI'm going to say it: 10/10, just the right amount of water.",
"Have you tried Terraria?\nA prime example of a survival sandbox game that beats the \"Wide as an ocean, deep as a puddle\" allegations.",
+ "Have you tried Final Fantasy Mystic Quest?\nApparently, it was made in an attempt to simplify Final Fantasy for the western market.\nThey were right, I suck at RPGs.",
+ "Have you tried Shivers?\nWitness 2 should totally feature a haunted Museum.",
+ "Have you tried Heretic?\nWait, there is a Doom Engine game where you can look UP AND DOWN???",
"One day I was fascinated by the subject of generation of waves by wind.",
"I don't like sandwiches. Why would you think I like sandwiches? Have you ever seen me with a sandwich?",
@@ -148,7 +154,7 @@
"You don't have Boat? Invisible boat time!\nYou do have boat? Boat clipping time!",
"Cet indice est en français. Nous nous excusons de tout inconvénients engendrés par cela.",
"How many of you have personally witnessed a total solar eclipse?",
- "In the Treehouse area, you will find \n[Error: Data not found] progression items.",
+ "In the Treehouse area, you will find 69 progression items.\nNice.\n(Source: Just trust me)",
"Lingo\nLingoing\nLingone",
"The name of the captain was Albert Einstein.",
"Panel impossible Sigma plz fix",
@@ -161,6 +167,27 @@
]
+@dataclass
+class WitnessLocationHint:
+ location: Location
+ hint_came_from_location: bool
+
+ # If a hint gets added to a set twice, but once as an item hint and once as a location hint, those are the same
+ def __hash__(self):
+ return hash(self.location)
+
+ def __eq__(self, other):
+ return self.location == other.location
+
+
+@dataclass
+class WitnessWordedHint:
+ wording: str
+ location: Optional[Location] = None
+ area: Optional[str] = None
+ area_amount: Optional[int] = None
+
+
def get_always_hint_items(world: "WitnessWorld") -> List[str]:
always = [
"Boat",
@@ -173,15 +200,15 @@ def get_always_hint_items(world: "WitnessWorld") -> List[str]:
wincon = world.options.victory_condition
if discards:
- if difficulty == 1:
+ if difficulty == "sigma_expert":
always.append("Arrows")
else:
always.append("Triangles")
- if wincon == 0:
- always += ["Mountain Bottom Floor Final Room Entry (Door)", "Mountain Bottom Floor Doors"]
+ if wincon == "elevator":
+ always += ["Mountain Bottom Floor Pillars Room Entry (Door)", "Mountain Bottom Floor Doors"]
- if wincon == 1:
+ if wincon == "challenge":
always += ["Challenge Entry (Panel)", "Caves Panels"]
return always
@@ -197,12 +224,14 @@ def get_always_hint_locations(world: "WitnessWorld") -> List[str]:
]
# Add Obelisk Sides that contain EPs that are meant to be hinted, if they are necessary to complete the Obelisk Side
- if world.options.EP_difficulty == "eclipse":
+ if "0x339B6" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES:
always.append("Town Obelisk Side 6") # Eclipse EP
- if world.options.EP_difficulty != "normal":
+ if "0x3388F" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES:
always.append("Treehouse Obelisk Side 4") # Couch EP
- always.append("River Obelisk Side 1") # Cloud Cycle EP. Needs to be changed to "Mountainside Obelisk" soon
+
+ if "0x335AE" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES:
+ always.append("Mountainside Obelisk Side 1") # Cloud Cycle EP.
return always
@@ -260,10 +289,12 @@ def get_priority_hint_items(world: "WitnessWorld") -> List[str]:
def get_priority_hint_locations(world: "WitnessWorld") -> List[str]:
priority = [
+ "Tutorial Patio Floor",
+ "Tutorial Patio Flowers EP",
"Swamp Purple Underwater",
"Shipwreck Vault Box",
- "Town RGB Room Left",
- "Town RGB Room Right",
+ "Town RGB House Upstairs Left",
+ "Town RGB House Upstairs Right",
"Treehouse Green Bridge 7",
"Treehouse Green Bridge Discard",
"Shipwreck Discard",
@@ -276,14 +307,38 @@ def get_priority_hint_locations(world: "WitnessWorld") -> List[str]:
]
# Add Obelisk Sides that contain EPs that are meant to be hinted, if they are necessary to complete the Obelisk Side
- if world.options.EP_difficulty != "normal":
+ if "0x33A20" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES:
priority.append("Town Obelisk Side 6") # Theater Flowers EP
+
+ if "0x28B29" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES:
priority.append("Treehouse Obelisk Side 4") # Shipwreck Green EP
+ if "0x33600" not in world.player_logic.COMPLETELY_DISABLED_ENTITIES:
+ priority.append("Town Obelisk Side 2") # Tutorial Patio Flowers EP.
+
return priority
-def make_hint_from_item(world: "WitnessWorld", item_name: str, own_itempool: List[Item]):
+def word_direct_hint(world: "WitnessWorld", hint: WitnessLocationHint):
+ location_name = hint.location.name
+ if hint.location.player != world.player:
+ location_name += " (" + world.multiworld.get_player_name(hint.location.player) + ")"
+
+ item = hint.location.item
+ item_name = item.name
+ if item.player != world.player:
+ item_name += " (" + world.multiworld.get_player_name(item.player) + ")"
+
+ if hint.hint_came_from_location:
+ hint_text = f"{location_name} contains {item_name}."
+ else:
+ hint_text = f"{item_name} can be found at {location_name}."
+
+ return WitnessWordedHint(hint_text, hint.location)
+
+
+def hint_from_item(world: "WitnessWorld", item_name: str, own_itempool: List[Item]) -> Optional[WitnessLocationHint]:
+
locations = [item.location for item in own_itempool if item.name == item_name and item.location]
if not locations:
@@ -295,28 +350,39 @@ def make_hint_from_item(world: "WitnessWorld", item_name: str, own_itempool: Lis
if location_obj.player != world.player:
location_name += " (" + world.multiworld.get_player_name(location_obj.player) + ")"
- return location_name, item_name, location_obj.address if (location_obj.player == world.player) else -1
+ return WitnessLocationHint(location_obj, False)
-def make_hint_from_location(world: "WitnessWorld", location: str):
+def hint_from_location(world: "WitnessWorld", location: str) -> Optional[WitnessLocationHint]:
location_obj = world.multiworld.get_location(location, world.player)
item_obj = world.multiworld.get_location(location, world.player).item
item_name = item_obj.name
if item_obj.player != world.player:
item_name += " (" + world.multiworld.get_player_name(item_obj.player) + ")"
- return location, item_name, location_obj.address if (location_obj.player == world.player) else -1
+ return WitnessLocationHint(location_obj, True)
-def make_hints(world: "WitnessWorld", hint_amount: int, own_itempool: List[Item]):
- hints = list()
+def get_items_and_locations_in_random_order(world: "WitnessWorld", own_itempool: List[Item]):
+ prog_items_in_this_world = sorted(
+ item.name for item in own_itempool
+ if item.advancement and item.code and item.location
+ )
+ locations_in_this_world = sorted(
+ location.name for location in world.multiworld.get_locations(world.player)
+ if location.address and location.progress_type != LocationProgressType.EXCLUDED
+ )
- prog_items_in_this_world = {
- item.name for item in own_itempool if item.advancement and item.code and item.location
- }
- loc_in_this_world = {
- location.name for location in world.multiworld.get_locations(world.player) if location.address
- }
+ world.random.shuffle(prog_items_in_this_world)
+ world.random.shuffle(locations_in_this_world)
+
+ return prog_items_in_this_world, locations_in_this_world
+
+
+def make_always_and_priority_hints(world: "WitnessWorld", own_itempool: List[Item],
+ already_hinted_locations: Set[Location]
+ ) -> Tuple[List[WitnessLocationHint], List[WitnessLocationHint]]:
+ prog_items_in_this_world, loc_in_this_world = get_items_and_locations_in_random_order(world, own_itempool)
always_locations = [
location for location in get_always_hint_locations(world)
@@ -335,105 +401,323 @@ def make_hints(world: "WitnessWorld", hint_amount: int, own_itempool: List[Item]
if item in prog_items_in_this_world
]
- always_hint_pairs = dict()
+ # Get always and priority location/item hints
+ always_location_hints = {hint_from_location(world, location) for location in always_locations}
+ always_item_hints = {hint_from_item(world, item, own_itempool) for item in always_items}
+ priority_location_hints = {hint_from_location(world, location) for location in priority_locations}
+ priority_item_hints = {hint_from_item(world, item, own_itempool) for item in priority_items}
- for item in always_items:
- hint_pair = make_hint_from_item(world, item, own_itempool)
+ # Combine the sets. This will get rid of duplicates
+ always_hints_set = always_item_hints | always_location_hints
+ priority_hints_set = priority_item_hints | priority_location_hints
- if not hint_pair or hint_pair[2] == 158007: # Tutorial Gate Open
- continue
+ # Make sure priority hints doesn't contain any hints that are already always hints.
+ priority_hints_set -= always_hints_set
- always_hint_pairs[hint_pair[0]] = (hint_pair[1], True, hint_pair[2])
+ always_generator = [hint for hint in always_hints_set if hint and hint.location not in already_hinted_locations]
+ priority_generator = [hint for hint in priority_hints_set if hint and hint.location not in already_hinted_locations]
- for location in always_locations:
- hint_pair = make_hint_from_location(world, location)
- always_hint_pairs[hint_pair[0]] = (hint_pair[1], False, hint_pair[2])
+ # Convert both hint types to list and then shuffle. Also, get rid of None and Tutorial Gate Open.
+ always_hints = sorted(always_generator, key=lambda h: h.location)
+ priority_hints = sorted(priority_generator, key=lambda h: h.location)
+ world.random.shuffle(always_hints)
+ world.random.shuffle(priority_hints)
- priority_hint_pairs = dict()
+ return always_hints, priority_hints
- for item in priority_items:
- hint_pair = make_hint_from_item(world, item, own_itempool)
- if not hint_pair or hint_pair[2] == 158007: # Tutorial Gate Open
- continue
+def make_extra_location_hints(world: "WitnessWorld", hint_amount: int, own_itempool: List[Item],
+ already_hinted_locations: Set[Location], hints_to_use_first: List[WitnessLocationHint],
+ unhinted_locations_for_hinted_areas: Dict[str, Set[Location]]) -> List[WitnessWordedHint]:
+ prog_items_in_this_world, locations_in_this_world = get_items_and_locations_in_random_order(world, own_itempool)
- priority_hint_pairs[hint_pair[0]] = (hint_pair[1], True, hint_pair[2])
+ next_random_hint_is_location = world.random.randrange(0, 2)
- for location in priority_locations:
- hint_pair = make_hint_from_location(world, location)
- priority_hint_pairs[hint_pair[0]] = (hint_pair[1], False, hint_pair[2])
+ hints = []
- already_hinted_locations = set()
+ # This is a way to reverse a Dict[a,List[b]] to a Dict[b,a]
+ area_reverse_lookup = {v: k for k, l in unhinted_locations_for_hinted_areas.items() for v in l}
- for loc, item in always_hint_pairs.items():
- if loc in already_hinted_locations:
+ while len(hints) < hint_amount:
+ if not prog_items_in_this_world and not locations_in_this_world and not hints_to_use_first:
+ player_name = world.multiworld.get_player_name(world.player)
+ logging.warning(f"Ran out of items/locations to hint for player {player_name}.")
+ break
+
+ if hints_to_use_first:
+ location_hint = hints_to_use_first.pop()
+ elif next_random_hint_is_location and locations_in_this_world:
+ location_hint = hint_from_location(world, locations_in_this_world.pop())
+ elif not next_random_hint_is_location and prog_items_in_this_world:
+ location_hint = hint_from_item(world, prog_items_in_this_world.pop(), own_itempool)
+ # The list that the hint was supposed to be taken from was empty.
+ # Try the other list, which has to still have something, as otherwise, all lists would be empty,
+ # which would have triggered the guard condition above.
+ else:
+ next_random_hint_is_location = not next_random_hint_is_location
continue
- if item[1]:
- hints.append((f"{item[0]} can be found at {loc}.", item[2]))
- else:
- hints.append((f"{loc} contains {item[0]}.", item[2]))
+ if not location_hint or location_hint.location in already_hinted_locations:
+ continue
- already_hinted_locations.add(loc)
+ # Don't hint locations in areas that are almost fully hinted out already
+ if location_hint.location in area_reverse_lookup:
+ area = area_reverse_lookup[location_hint.location]
+ if len(unhinted_locations_for_hinted_areas[area]) == 1:
+ continue
+ del area_reverse_lookup[location_hint.location]
+ unhinted_locations_for_hinted_areas[area] -= {location_hint.location}
- world.random.shuffle(hints) # shuffle always hint order in case of low hint amount
+ hints.append(word_direct_hint(world, location_hint))
+ already_hinted_locations.add(location_hint.location)
- remaining_hints = hint_amount - len(hints)
- priority_hint_amount = int(max(0.0, min(len(priority_hint_pairs) / 2, remaining_hints / 2)))
+ next_random_hint_is_location = not next_random_hint_is_location
- prog_items_in_this_world = sorted(prog_items_in_this_world)
- locations_in_this_world = sorted(loc_in_this_world)
+ return hints
- world.random.shuffle(prog_items_in_this_world)
- world.random.shuffle(locations_in_this_world)
- priority_hint_list = list(priority_hint_pairs.items())
- world.random.shuffle(priority_hint_list)
- for _ in range(0, priority_hint_amount):
- next_priority_hint = priority_hint_list.pop()
- loc = next_priority_hint[0]
- item = next_priority_hint[1]
+def generate_joke_hints(world: "WitnessWorld", amount: int) -> List[Tuple[str, int, int]]:
+ return [(x, -1, -1) for x in world.random.sample(joke_hints, amount)]
- if loc in already_hinted_locations:
- continue
- if item[1]:
- hints.append((f"{item[0]} can be found at {loc}.", item[2]))
- else:
- hints.append((f"{loc} contains {item[0]}.", item[2]))
+def choose_areas(world: "WitnessWorld", amount: int, locations_per_area: Dict[str, List[Location]],
+ already_hinted_locations: Set[Location]) -> Tuple[List[str], Dict[str, Set[Location]]]:
+ """
+ Choose areas to hint.
+ This takes into account that some areas may already have had items hinted in them through location hints.
+ When this happens, they are made less likely to receive an area hint.
+ """
- already_hinted_locations.add(loc)
+ unhinted_locations_per_area = dict()
+ unhinted_location_percentage_per_area = dict()
- next_random_hint_is_item = world.random.randrange(0, 2)
+ for area_name, locations in locations_per_area.items():
+ not_yet_hinted_locations = sum(location not in already_hinted_locations for location in locations)
+ unhinted_locations_per_area[area_name] = {loc for loc in locations if loc not in already_hinted_locations}
+ unhinted_location_percentage_per_area[area_name] = not_yet_hinted_locations / len(locations)
- while len(hints) < hint_amount:
- if next_random_hint_is_item:
- if not prog_items_in_this_world:
- next_random_hint_is_item = not next_random_hint_is_item
- continue
+ items_per_area = {area_name: [location.item for location in locations]
+ for area_name, locations in locations_per_area.items()}
- hint = make_hint_from_item(world, prog_items_in_this_world.pop(), own_itempool)
+ areas = sorted(area for area in items_per_area if unhinted_location_percentage_per_area[area])
+ weights = [unhinted_location_percentage_per_area[area] for area in areas]
- if not hint or hint[0] in already_hinted_locations:
- continue
+ amount = min(amount, len(weights))
+
+ hinted_areas = weighted_sample(world.random, areas, weights, amount)
+
+ return hinted_areas, unhinted_locations_per_area
+
+
+def get_hintable_areas(world: "WitnessWorld") -> Tuple[Dict[str, List[Location]], Dict[str, List[Item]]]:
+ potential_areas = list(StaticWitnessLogic.ALL_AREAS_BY_NAME.keys())
+
+ locations_per_area = dict()
+ items_per_area = dict()
+
+ for area in potential_areas:
+ regions = [
+ world.regio.created_regions[region]
+ for region in StaticWitnessLogic.ALL_AREAS_BY_NAME[area]["regions"]
+ if region in world.regio.created_regions
+ ]
+ locations = [location for region in regions for location in region.get_locations() if location.address]
- hints.append((f"{hint[1]} can be found at {hint[0]}.", hint[2]))
+ if locations:
+ locations_per_area[area] = locations
+ items_per_area[area] = [location.item for location in locations]
- already_hinted_locations.add(hint[0])
+ return locations_per_area, items_per_area
+
+
+def word_area_hint(world: "WitnessWorld", hinted_area: str, corresponding_items: List[Item]) -> Tuple[str, int]:
+ """
+ Word the hint for an area using natural sounding language.
+ This takes into account how much progression there is, how much of it is local/non-local, and whether there are
+ any local lasers to be found in this area.
+ """
+
+ local_progression = sum(item.player == world.player and item.advancement for item in corresponding_items)
+ non_local_progression = sum(item.player != world.player and item.advancement for item in corresponding_items)
+
+ laser_names = {"Symmetry Laser", "Desert Laser", "Quarry Laser", "Shadows Laser", "Town Laser", "Monastery Laser",
+ "Jungle Laser", "Bunker Laser", "Swamp Laser", "Treehouse Laser", "Keep Laser", }
+
+ local_lasers = sum(
+ item.player == world.player and item.name in laser_names
+ for item in corresponding_items
+ )
+
+ total_progression = non_local_progression + local_progression
+
+ player_count = world.multiworld.players
+
+ area_progression_word = "Both" if total_progression == 2 else "All"
+
+ if not total_progression:
+ hint_string = f"In the {hinted_area} area, you will find no progression items."
+
+ elif total_progression == 1:
+ hint_string = f"In the {hinted_area} area, you will find 1 progression item."
+
+ if player_count > 1:
+ if local_lasers:
+ hint_string += "\nThis item is a laser for this world."
+ elif non_local_progression:
+ other_player_str = "the other player" if player_count == 2 else "another player"
+ hint_string += f"\nThis item is for {other_player_str}."
+ else:
+ hint_string += "\nThis item is for this world."
else:
- hint = make_hint_from_location(world, locations_in_this_world.pop())
+ if local_lasers:
+ hint_string += "\nThis item is a laser."
+
+ else:
+ hint_string = f"In the {hinted_area} area, you will find {total_progression} progression items."
+
+ if local_lasers == total_progression:
+ sentence_end = (" for this world." if player_count > 1 else ".")
+ hint_string += f"\nAll of them are lasers" + sentence_end
+
+ elif player_count > 1:
+ if local_progression and non_local_progression:
+ if non_local_progression == 1:
+ other_player_str = "the other player" if player_count == 2 else "another player"
+ hint_string += f"\nOne of them is for {other_player_str}."
+ else:
+ other_player_str = "the other player" if player_count == 2 else "other players"
+ hint_string += f"\n{non_local_progression} of them are for {other_player_str}."
+ elif non_local_progression:
+ other_players_str = "the other player" if player_count == 2 else "other players"
+ hint_string += f"\n{area_progression_word} of them are for {other_players_str}."
+ elif local_progression:
+ hint_string += f"\n{area_progression_word} of them are for this world."
+
+ if local_lasers == 1:
+ if not non_local_progression:
+ hint_string += "\nAlso, one of them is a laser."
+ else:
+ hint_string += "\nAlso, one of them is a laser for this world."
+ elif local_lasers:
+ if not non_local_progression:
+ hint_string += f"\nAlso, {local_lasers} of them are lasers."
+ else:
+ hint_string += f"\nAlso, {local_lasers} of them are lasers for this world."
- if hint[0] in already_hinted_locations:
- continue
+ else:
+ if local_lasers == 1:
+ hint_string += "\nOne of them is a laser."
+ elif local_lasers:
+ hint_string += f"\n{local_lasers} of them are lasers."
- hints.append((f"{hint[0]} contains {hint[1]}.", hint[2]))
+ return hint_string, total_progression
- already_hinted_locations.add(hint[0])
- next_random_hint_is_item = not next_random_hint_is_item
+def make_area_hints(world: "WitnessWorld", amount: int, already_hinted_locations: Set[Location]
+ ) -> Tuple[List[WitnessWordedHint], Dict[str, Set[Location]]]:
+ locs_per_area, items_per_area = get_hintable_areas(world)
- return hints
+ hinted_areas, unhinted_locations_per_area = choose_areas(world, amount, locs_per_area, already_hinted_locations)
+
+ hints = []
+
+ for hinted_area in hinted_areas:
+ hint_string, prog_amount = word_area_hint(world, hinted_area, items_per_area[hinted_area])
+
+ hints.append(WitnessWordedHint(hint_string, None, f"hinted_area:{hinted_area}", prog_amount))
+
+ if len(hinted_areas) < amount:
+ player_name = world.multiworld.get_player_name(world.player)
+ logging.warning(f"Was not able to make {amount} area hints for player {player_name}. "
+ f"Made {len(hinted_areas)} instead, and filled the rest with random location hints.")
+
+ return hints, unhinted_locations_per_area
+
+
+def create_all_hints(world: "WitnessWorld", hint_amount: int, area_hints: int) -> List[WitnessWordedHint]:
+ generated_hints: List[WitnessWordedHint] = []
+
+ state = CollectionState(world.multiworld)
+
+ # Keep track of already hinted locations. Consider early Tutorial as "already hinted"
+
+ already_hinted_locations = {
+ loc for loc in world.multiworld.get_reachable_locations(state, world.player)
+ if loc.address and StaticWitnessLogic.ENTITIES_BY_NAME[loc.name]["area"]["name"] == "Tutorial (Inside)"
+ }
+
+ intended_location_hints = hint_amount - area_hints
+
+ # First, make always and priority hints.
+
+ always_hints, priority_hints = make_always_and_priority_hints(
+ world, world.own_itempool, already_hinted_locations
+ )
+
+ generated_always_hints = len(always_hints)
+ possible_priority_hints = len(priority_hints)
+
+ # Make as many always hints as possible
+ always_hints_to_use = min(intended_location_hints, generated_always_hints)
+
+ # Make up to half of the rest of the location hints priority hints, using up to half of the possibly priority hints
+ remaining_location_hints = intended_location_hints - always_hints_to_use
+ priority_hints_to_use = int(max(0.0, min(possible_priority_hints / 2, remaining_location_hints / 2)))
+
+ for _ in range(always_hints_to_use):
+ location_hint = always_hints.pop()
+ generated_hints.append(word_direct_hint(world, location_hint))
+ already_hinted_locations.add(location_hint.location)
+
+ for _ in range(priority_hints_to_use):
+ location_hint = priority_hints.pop()
+ generated_hints.append(word_direct_hint(world, location_hint))
+ already_hinted_locations.add(location_hint.location)
+
+ location_hints_created_in_round_1 = len(generated_hints)
+
+ unhinted_locations_per_area: Dict[str, Set[Location]] = dict()
+
+ # Then, make area hints.
+ if area_hints:
+ generated_area_hints, unhinted_locations_per_area = make_area_hints(world, area_hints, already_hinted_locations)
+ generated_hints += generated_area_hints
+
+ # If we don't have enough hints yet, recalculate always and priority hints, then fill with random hints
+ if len(generated_hints) < hint_amount:
+ remaining_needed_location_hints = hint_amount - len(generated_hints)
+
+ # Save old values for used always and priority hints for later calculations
+ amt_of_used_always_hints = always_hints_to_use
+ amt_of_used_priority_hints = priority_hints_to_use
+
+ # Recalculate how many always hints and priority hints are supposed to be used
+ intended_location_hints = remaining_needed_location_hints + location_hints_created_in_round_1
+
+ always_hints_to_use = min(intended_location_hints, generated_always_hints)
+ priority_hints_to_use = int(max(0.0, min(possible_priority_hints / 2, remaining_location_hints / 2)))
+
+ # If we now need more always hints and priority hints than we thought previously, make some more.
+ more_always_hints = always_hints_to_use - amt_of_used_always_hints
+ more_priority_hints = priority_hints_to_use - amt_of_used_priority_hints
+
+ extra_always_and_priority_hints: List[WitnessLocationHint] = []
+
+ for _ in range(more_always_hints):
+ extra_always_and_priority_hints.append(always_hints.pop())
+
+ for _ in range(more_priority_hints):
+ extra_always_and_priority_hints.append(priority_hints.pop())
+
+ generated_hints += make_extra_location_hints(
+ world, hint_amount - len(generated_hints), world.own_itempool, already_hinted_locations,
+ extra_always_and_priority_hints, unhinted_locations_per_area
+ )
+ # If we still don't have enough for whatever reason, throw a warning, proceed with the lower amount
+ if len(generated_hints) != hint_amount:
+ player_name = world.multiworld.get_player_name(world.player)
+ logging.warning(f"Couldn't generate {hint_amount} hints for player {player_name}. "
+ f"Generated {len(generated_hints)} instead.")
-def generate_joke_hints(world: "WitnessWorld", amount: int) -> List[Tuple[str, int]]:
- return [(x, -1) for x in world.random.sample(joke_hints, amount)]
+ return generated_hints
diff --git a/worlds/witness/items.py b/worlds/witness/items.py
index a8c889de937a..6802fd2a21b5 100644
--- a/worlds/witness/items.py
+++ b/worlds/witness/items.py
@@ -112,30 +112,12 @@ def __init__(self, world: "WitnessWorld", logic: WitnessPlayerLogic, locat: Witn
or name in logic.PROG_ITEMS_ACTUALLY_IN_THE_GAME
}
- # Adjust item classifications based on game settings.
- eps_shuffled = self._world.options.shuffle_EPs
- come_to_you = self._world.options.elevators_come_to_you
- difficulty = self._world.options.puzzle_randomization
+ # Downgrade door items
for item_name, item_data in self.item_data.items():
- if not eps_shuffled and item_name in {"Monastery Garden Entry (Door)",
- "Monastery Shortcuts",
- "Quarry Boathouse Hook Control (Panel)",
- "Windmill Turn Control (Panel)"}:
- # Downgrade doors that only gate progress in EP shuffle.
- item_data.classification = ItemClassification.useful
- elif not come_to_you and not eps_shuffled and item_name in {"Quarry Elevator Control (Panel)",
- "Swamp Long Bridge (Panel)"}:
- # These Bridges/Elevators are not logical access because they may leave you stuck.
- item_data.classification = ItemClassification.useful
- elif item_name in {"River Monastery Garden Shortcut (Door)",
- "Monastery Laser Shortcut (Door)",
- "Orchard Second Gate (Door)",
- "Jungle Bamboo Laser Shortcut (Door)",
- "Caves Elevator Controls (Panel)"}:
- # Downgrade doors that don't gate progress.
- item_data.classification = ItemClassification.useful
- elif item_name == "Keep Pressure Plates 2 Exit (Door)" and not (difficulty == "none" and eps_shuffled):
- # PP2EP requires the door in vanilla puzzles, otherwise it's unnecessary
+ if not isinstance(item_data.definition, DoorItemDefinition):
+ continue
+
+ if all(not self._logic.solvability_guaranteed(e_hex) for e_hex in item_data.definition.panel_id_hexes):
item_data.classification = ItemClassification.useful
# Build the mandatory item list.
@@ -194,9 +176,14 @@ def get_filler_items(self, quantity: int) -> Dict[str, int]:
# Read trap configuration data.
trap_weight = self._world.options.trap_percentage / 100
- filler_weight = 1 - trap_weight
+ trap_items = self._world.options.trap_weights.value
+
+ if not sum(trap_items.values()):
+ trap_weight = 0
# Add filler items to the list.
+ filler_weight = 1 - trap_weight
+
filler_items: Dict[str, float]
filler_items = {name: data.definition.weight if isinstance(data.definition, WeightedItemDefinition) else 1
for (name, data) in self.item_data.items() if data.definition.category is ItemCategory.FILLER}
@@ -205,8 +192,6 @@ def get_filler_items(self, quantity: int) -> Dict[str, int]:
# Add trap items.
if trap_weight > 0:
- trap_items = {name: data.definition.weight if isinstance(data.definition, WeightedItemDefinition) else 1
- for (name, data) in self.item_data.items() if data.definition.category is ItemCategory.TRAP}
filler_items.update({name: base_weight * trap_weight / sum(trap_items.values())
for name, base_weight in trap_items.items() if base_weight > 0})
@@ -228,7 +213,7 @@ def get_early_items(self) -> List[str]:
output = {"Dots", "Black/White Squares", "Symmetry", "Shapers", "Stars"}
if self._world.options.shuffle_discarded_panels:
- if self._world.options.puzzle_randomization == 1:
+ if self._world.options.puzzle_randomization == "sigma_expert":
output.add("Arrows")
else:
output.add("Triangles")
@@ -285,3 +270,6 @@ def get_progressive_item_ids_in_pool(self) -> Dict[int, List[int]]:
output[item.ap_code] = [StaticWitnessItems.item_data[child_item].ap_code
for child_item in item.definition.child_item_names]
return output
+
+
+StaticWitnessItems()
diff --git a/worlds/witness/locations.py b/worlds/witness/locations.py
index 026977701a64..cd6d71f46911 100644
--- a/worlds/witness/locations.py
+++ b/worlds/witness/locations.py
@@ -110,13 +110,13 @@ class StaticWitnessLocations:
"Town Red Rooftop 5",
"Town Wooden Roof Lower Row 5",
"Town Wooden Rooftop",
- "Town Windmill Entry Panel",
+ "Windmill Entry Panel",
"Town RGB House Entry Panel",
"Town Laser Panel",
- "Town RGB Room Left",
- "Town RGB Room Right",
- "Town Sound Room Right",
+ "Town RGB House Upstairs Left",
+ "Town RGB House Upstairs Right",
+ "Town RGB House Sound Room Right",
"Windmill Theater Entry Panel",
"Theater Exit Left Panel",
@@ -134,8 +134,8 @@ class StaticWitnessLocations:
"Jungle Popup Wall 6",
"Jungle Laser Panel",
- "River Vault Box",
- "River Monastery Garden Shortcut Panel",
+ "Jungle Vault Box",
+ "Jungle Monastery Garden Shortcut Panel",
"Bunker Entry Panel",
"Bunker Intro Left 5",
@@ -177,7 +177,7 @@ class StaticWitnessLocations:
"Mountainside Vault Box",
"Mountaintop River Shape",
- "First Hallway EP",
+ "Tutorial First Hallway EP",
"Tutorial Cloud EP",
"Tutorial Patio Flowers EP",
"Tutorial Gate EP",
@@ -185,7 +185,7 @@ class StaticWitnessLocations:
"Outside Tutorial Town Sewer EP",
"Outside Tutorial Path EP",
"Outside Tutorial Tractor EP",
- "Main Island Thundercloud EP",
+ "Mountainside Thundercloud EP",
"Glass Factory Vase EP",
"Symmetry Island Glass Factory Black Line Reflection EP",
"Symmetry Island Glass Factory Black Line EP",
@@ -242,9 +242,9 @@ class StaticWitnessLocations:
"Monastery Left Shutter EP",
"Monastery Middle Shutter EP",
"Monastery Right Shutter EP",
- "Town Windmill First Blade EP",
- "Town Windmill Second Blade EP",
- "Town Windmill Third Blade EP",
+ "Windmill First Blade EP",
+ "Windmill Second Blade EP",
+ "Windmill Third Blade EP",
"Town Tower Underside Third EP",
"Town Tower Underside Fourth EP",
"Town Tower Underside First EP",
@@ -268,10 +268,10 @@ class StaticWitnessLocations:
"Jungle Tree Halo EP",
"Jungle Bamboo CCW EP",
"Jungle Bamboo CW EP",
- "River Green Leaf Moss EP",
- "River Monastery Garden Left EP",
- "River Monastery Garden Right EP",
- "River Monastery Wall EP",
+ "Jungle Green Leaf Moss EP",
+ "Monastery Garden Left EP",
+ "Monastery Garden Right EP",
+ "Monastery Wall EP",
"Bunker Tinted Door EP",
"Bunker Green Room Flowers EP",
"Swamp Purple Sand Middle EP",
@@ -330,12 +330,12 @@ class StaticWitnessLocations:
"Treehouse Obelisk Side 4",
"Treehouse Obelisk Side 5",
"Treehouse Obelisk Side 6",
- "River Obelisk Side 1",
- "River Obelisk Side 2",
- "River Obelisk Side 3",
- "River Obelisk Side 4",
- "River Obelisk Side 5",
- "River Obelisk Side 6",
+ "Mountainside Obelisk Side 1",
+ "Mountainside Obelisk Side 2",
+ "Mountainside Obelisk Side 3",
+ "Mountainside Obelisk Side 4",
+ "Mountainside Obelisk Side 5",
+ "Mountainside Obelisk Side 6",
"Quarry Obelisk Side 1",
"Quarry Obelisk Side 2",
"Quarry Obelisk Side 3",
@@ -407,13 +407,13 @@ class StaticWitnessLocations:
"Mountain Floor 2 Elevator Discard",
"Mountain Bottom Floor Giant Puzzle",
- "Mountain Bottom Floor Final Room Entry Left",
- "Mountain Bottom Floor Final Room Entry Right",
+ "Mountain Bottom Floor Pillars Room Entry Left",
+ "Mountain Bottom Floor Pillars Room Entry Right",
"Mountain Bottom Floor Caves Entry Panel",
- "Mountain Final Room Left Pillar 4",
- "Mountain Final Room Right Pillar 4",
+ "Mountain Bottom Floor Left Pillar 4",
+ "Mountain Bottom Floor Right Pillar 4",
"Challenge Vault Box",
"Theater Challenge Video",
@@ -438,12 +438,12 @@ class StaticWitnessLocations:
"Treehouse Obelisk Side 4",
"Treehouse Obelisk Side 5",
"Treehouse Obelisk Side 6",
- "River Obelisk Side 1",
- "River Obelisk Side 2",
- "River Obelisk Side 3",
- "River Obelisk Side 4",
- "River Obelisk Side 5",
- "River Obelisk Side 6",
+ "Mountainside Obelisk Side 1",
+ "Mountainside Obelisk Side 2",
+ "Mountainside Obelisk Side 3",
+ "Mountainside Obelisk Side 4",
+ "Mountainside Obelisk Side 5",
+ "Mountainside Obelisk Side 6",
"Quarry Obelisk Side 1",
"Quarry Obelisk Side 2",
"Quarry Obelisk Side 3",
@@ -459,6 +459,8 @@ class StaticWitnessLocations:
ALL_LOCATIONS_TO_ID = dict()
+ AREA_LOCATION_GROUPS = dict()
+
@staticmethod
def get_id(chex: str):
"""
@@ -491,6 +493,10 @@ def __init__(self):
for key, item in all_loc_to_id.items():
self.ALL_LOCATIONS_TO_ID[key] = item
+ for loc in all_loc_to_id:
+ area = StaticWitnessLogic.ENTITIES_BY_NAME[loc]["area"]["name"]
+ self.AREA_LOCATION_GROUPS.setdefault(area, []).append(loc)
+
class WitnessPlayerLocations:
"""
@@ -509,9 +515,9 @@ def __init__(self, world: "WitnessWorld", player_logic: WitnessPlayerLogic):
if world.options.shuffle_vault_boxes:
self.PANEL_TYPES_TO_SHUFFLE.add("Vault")
- if world.options.shuffle_EPs == 1:
+ if world.options.shuffle_EPs == "individual":
self.PANEL_TYPES_TO_SHUFFLE.add("EP")
- elif world.options.shuffle_EPs == 2:
+ elif world.options.shuffle_EPs == "obelisk_sides":
self.PANEL_TYPES_TO_SHUFFLE.add("Obelisk Side")
for obelisk_loc in StaticWitnessLocations.OBELISK_SIDES:
@@ -543,7 +549,7 @@ def __init__(self, world: "WitnessWorld", player_logic: WitnessPlayerLogic):
)
event_locations = {
- p for p in player_logic.EVENT_PANELS
+ p for p in player_logic.USED_EVENT_NAMES_BY_HEX
}
self.EVENT_LOCATION_TABLE = {
@@ -563,3 +569,6 @@ def add_location_late(self, entity_name: str):
entity_hex = StaticWitnessLogic.ENTITIES_BY_NAME[entity_name]["entity_hex"]
self.CHECK_LOCATION_TABLE[entity_hex] = entity_name
self.CHECK_PANELHEX_TO_ID[entity_hex] = StaticWitnessLocations.get_id(entity_hex)
+
+
+StaticWitnessLocations()
diff --git a/worlds/witness/options.py b/worlds/witness/options.py
index ac1f2bc82830..18aa76d95ae9 100644
--- a/worlds/witness/options.py
+++ b/worlds/witness/options.py
@@ -1,5 +1,10 @@
from dataclasses import dataclass
-from Options import Toggle, DefaultOnToggle, Range, Choice, PerGameCommonOptions
+
+from schema import Schema, And, Optional
+
+from Options import Toggle, DefaultOnToggle, Range, Choice, PerGameCommonOptions, OptionDict
+
+from worlds.witness.static_logic import WeightedItemDefinition, ItemCategory, StaticWitnessLogic
class DisableNonRandomizedPuzzles(Toggle):
@@ -172,6 +177,24 @@ class TrapPercentage(Range):
default = 20
+class TrapWeights(OptionDict):
+ """Specify the weights determining how many copies of each trap item will be in your itempool.
+ If you don't want a specific type of trap, you can set the weight for it to 0 (Do not delete the entry outright!).
+ If you set all trap weights to 0, you will get no traps, bypassing the "Trap Percentage" option."""
+
+ display_name = "Trap Weights"
+ schema = Schema({
+ trap_name: And(int, lambda n: n >= 0)
+ for trap_name, item_definition in StaticWitnessLogic.all_items.items()
+ if isinstance(item_definition, WeightedItemDefinition) and item_definition.category is ItemCategory.TRAP
+ })
+ default = {
+ trap_name: item_definition.weight
+ for trap_name, item_definition in StaticWitnessLogic.all_items.items()
+ if isinstance(item_definition, WeightedItemDefinition) and item_definition.category is ItemCategory.TRAP
+ }
+
+
class PuzzleSkipAmount(Range):
"""Adds this number of Puzzle Skips into the pool, if there is room. Puzzle Skips let you skip one panel.
Works on most panels in the game - The only big exception is The Challenge."""
@@ -187,7 +210,19 @@ class HintAmount(Range):
display_name = "Hints on Audio Logs"
range_start = 0
range_end = 49
- default = 10
+ default = 12
+
+
+class AreaHintPercentage(Range):
+ """There are two types of hints for The Witness.
+ "Location hints" hint one location in your world / containing an item for your world.
+ "Area hints" will tell you some general info about the items you can find in one of the
+ main geographic areas on the island.
+ Use this option to specify how many of your hints you want to be area hints. The rest will be location hints."""
+ display_name = "Area Hint Percentage"
+ range_start = 0
+ range_end = 100
+ default = 33
class DeathLink(Toggle):
@@ -225,7 +260,9 @@ class TheWitnessOptions(PerGameCommonOptions):
early_caves: EarlyCaves
elevators_come_to_you: ElevatorsComeToYou
trap_percentage: TrapPercentage
+ trap_weights: TrapWeights
puzzle_skip_amount: PuzzleSkipAmount
hint_amount: HintAmount
+ area_hint_percentage: AreaHintPercentage
death_link: DeathLink
death_link_amnesty: DeathLinkAmnesty
diff --git a/worlds/witness/player_logic.py b/worlds/witness/player_logic.py
index 5d538e62b748..229da0a2879a 100644
--- a/worlds/witness/player_logic.py
+++ b/worlds/witness/player_logic.py
@@ -101,8 +101,11 @@ def reduce_req_within_region(self, panel_hex: str) -> FrozenSet[FrozenSet[str]]:
for option_entity in option:
dep_obj = self.REFERENCE_LOGIC.ENTITIES_BY_HEX.get(option_entity)
- if option_entity in self.EVENT_NAMES_BY_HEX:
+ if option_entity in self.ALWAYS_EVENT_NAMES_BY_HEX:
new_items = frozenset({frozenset([option_entity])})
+ elif (panel_hex, option_entity) in self.CONDITIONAL_EVENTS:
+ new_items = frozenset({frozenset([option_entity])})
+ self.USED_EVENT_NAMES_BY_HEX[option_entity] = self.CONDITIONAL_EVENTS[(panel_hex, option_entity)]
elif option_entity in {"7 Lasers", "11 Lasers", "7 Lasers + Redirect", "11 Lasers + Redirect",
"PP2 Weirdness", "Theater to Tunnels"}:
new_items = frozenset({frozenset([option_entity])})
@@ -170,14 +173,11 @@ def make_single_adjustment(self, adj_type: str, line: str):
if adj_type == "Event Items":
line_split = line.split(" - ")
new_event_name = line_split[0]
- hex_set = line_split[1].split(",")
-
- for entity, event_name in self.EVENT_NAMES_BY_HEX.items():
- if event_name == new_event_name:
- self.DONT_MAKE_EVENTS.add(entity)
+ entity_hex = line_split[1]
+ dependent_hex_set = line_split[2].split(",")
- for hex_code in hex_set:
- self.EVENT_NAMES_BY_HEX[hex_code] = new_event_name
+ for dependent_hex in dependent_hex_set:
+ self.CONDITIONAL_EVENTS[(entity_hex, dependent_hex)] = new_event_name
return
@@ -253,51 +253,101 @@ def make_single_adjustment(self, adj_type: str, line: str):
line = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[line]["checkName"]
self.ADDED_CHECKS.add(line)
+ @staticmethod
+ def handle_postgame(world: "WitnessWorld"):
+ # In shuffle_postgame, panels that become accessible "after or at the same time as the goal" are disabled.
+ # This has a lot of complicated considerations, which I'll try my best to explain.
+ postgame_adjustments = []
+
+ # Make some quick references to some options
+ doors = world.options.shuffle_doors >= 2 # "Panels" mode has no overarching region accessibility implications.
+ early_caves = world.options.early_caves
+ victory = world.options.victory_condition
+ mnt_lasers = world.options.mountain_lasers
+ chal_lasers = world.options.challenge_lasers
+
+ # Goal is "short box" but short box requires more lasers than long box
+ reverse_shortbox_goal = victory == "mountain_box_short" and mnt_lasers > chal_lasers
+
+ # Goal is "short box", and long box requires at least as many lasers as short box (as god intended)
+ proper_shortbox_goal = victory == "mountain_box_short" and chal_lasers >= mnt_lasers
+
+ # Goal is "long box", but short box requires at least as many lasers than long box.
+ reverse_longbox_goal = victory == "mountain_box_long" and mnt_lasers >= chal_lasers
+
+ # If goal is shortbox or "reverse longbox", you will never enter the mountain from the top before winning.
+ mountain_enterable_from_top = not (victory == "mountain_box_short" or reverse_longbox_goal)
+
+ # Caves & Challenge should never have anything if doors are vanilla - definitionally "post-game"
+ # This is technically imprecise, but it matches player expectations better.
+ if not (early_caves or doors):
+ postgame_adjustments.append(get_caves_exclusion_list())
+ postgame_adjustments.append(get_beyond_challenge_exclusion_list())
+
+ # If Challenge is the goal, some panels on the way need to be left on, as well as Challenge Vault box itself
+ if not victory == "challenge":
+ postgame_adjustments.append(get_path_to_challenge_exclusion_list())
+ postgame_adjustments.append(get_challenge_vault_box_exclusion_list())
+
+ # Challenge can only have something if the goal is not challenge or longbox itself.
+ # In case of shortbox, it'd have to be a "reverse shortbox" situation where shortbox requires *more* lasers.
+ # In that case, it'd also have to be a doors mode, but that's already covered by the previous block.
+ if not (victory == "elevator" or reverse_shortbox_goal):
+ postgame_adjustments.append(get_beyond_challenge_exclusion_list())
+ if not victory == "challenge":
+ postgame_adjustments.append(get_challenge_vault_box_exclusion_list())
+
+ # Mountain can't be reached if the goal is shortbox (or "reverse long box")
+ if not mountain_enterable_from_top:
+ postgame_adjustments.append(get_mountain_upper_exclusion_list())
+
+ # Same goes for lower mountain, but that one *can* be reached in remote doors modes.
+ if not doors:
+ postgame_adjustments.append(get_mountain_lower_exclusion_list())
+
+ # The Mountain Bottom Floor Discard is a bit complicated, so we handle it separately. ("it" == the Discard)
+ # In Elevator Goal, it is definitionally in the post-game, unless remote doors is played.
+ # In Challenge Goal, it is before the Challenge, so it is not post-game.
+ # In Short Box Goal, you can win before turning it on, UNLESS Short Box requires MORE lasers than long box.
+ # In Long Box Goal, it is always in the post-game because solving long box is what turns it on.
+ if not ((victory == "elevator" and doors) or victory == "challenge" or (reverse_shortbox_goal and doors)):
+ # We now know Bottom Floor Discard is in the post-game.
+ # This has different consequences depending on whether remote doors is being played.
+ # If doors are vanilla, Bottom Floor Discard locks a door to an area, which has to be disabled as well.
+ if doors:
+ postgame_adjustments.append(get_bottom_floor_discard_exclusion_list())
+ else:
+ postgame_adjustments.append(get_bottom_floor_discard_nondoors_exclusion_list())
+
+ # In Challenge goal + early_caves + vanilla doors, you could find something important on Bottom Floor Discard,
+ # including the Caves Shortcuts themselves if playing "early_caves: start_inventory".
+ # This is another thing that was deemed "unfun" more than fitting the actual definition of post-game.
+ if victory == "challenge" and early_caves and not doors:
+ postgame_adjustments.append(get_bottom_floor_discard_nondoors_exclusion_list())
+
+ # If we have a proper short box goal, long box will never be activated first.
+ if proper_shortbox_goal:
+ postgame_adjustments.append(["Disabled Locations:", "0xFFF00 (Mountain Box Long)"])
+
+ return postgame_adjustments
+
def make_options_adjustments(self, world: "WitnessWorld"):
"""Makes logic adjustments based on options"""
adjustment_linesets_in_order = []
- # Postgame
+ # Make condensed references to some options
- doors = world.options.shuffle_doors >= 2
+ doors = world.options.shuffle_doors >= 2 # "Panels" mode has no overarching region accessibility implications.
lasers = world.options.shuffle_lasers
- early_caves = world.options.early_caves > 0
victory = world.options.victory_condition
mnt_lasers = world.options.mountain_lasers
chal_lasers = world.options.challenge_lasers
- mountain_enterable_from_top = victory == 0 or victory == 1 or (victory == 3 and chal_lasers > mnt_lasers)
-
+ # Exclude panels from the post-game if shuffle_postgame is false.
if not world.options.shuffle_postgame:
- if not (early_caves or doors):
- adjustment_linesets_in_order.append(get_caves_exclusion_list())
- if not victory == 1:
- adjustment_linesets_in_order.append(get_path_to_challenge_exclusion_list())
- adjustment_linesets_in_order.append(get_challenge_vault_box_exclusion_list())
- adjustment_linesets_in_order.append(get_beyond_challenge_exclusion_list())
-
- if not ((doors or early_caves) and (victory == 0 or (victory == 2 and mnt_lasers > chal_lasers))):
- adjustment_linesets_in_order.append(get_beyond_challenge_exclusion_list())
- if not victory == 1:
- adjustment_linesets_in_order.append(get_challenge_vault_box_exclusion_list())
-
- if not (doors or mountain_enterable_from_top):
- adjustment_linesets_in_order.append(get_mountain_lower_exclusion_list())
-
- if not mountain_enterable_from_top:
- adjustment_linesets_in_order.append(get_mountain_upper_exclusion_list())
-
- if not ((victory == 0 and doors) or victory == 1 or (victory == 2 and mnt_lasers > chal_lasers and doors)):
- if doors:
- adjustment_linesets_in_order.append(get_bottom_floor_discard_exclusion_list())
- else:
- adjustment_linesets_in_order.append(get_bottom_floor_discard_nondoors_exclusion_list())
-
- if victory == 2 and chal_lasers >= mnt_lasers:
- adjustment_linesets_in_order.append(["Disabled Locations:", "0xFFF00 (Mountain Box Long)"])
+ adjustment_linesets_in_order += self.handle_postgame(world)
# Exclude Discards / Vaults
-
if not world.options.shuffle_discarded_panels:
# In disable_non_randomized, the discards are needed for alternate activation triggers, UNLESS both
# (remote) doors and lasers are shuffled.
@@ -309,18 +359,18 @@ def make_options_adjustments(self, world: "WitnessWorld"):
if not world.options.shuffle_vault_boxes:
adjustment_linesets_in_order.append(get_vault_exclusion_list())
- if not victory == 1:
+ if not victory == "challenge":
adjustment_linesets_in_order.append(get_challenge_vault_box_exclusion_list())
# Victory Condition
- if victory == 0:
+ if victory == "elevator":
self.VICTORY_LOCATION = "0x3D9A9"
- elif victory == 1:
+ elif victory == "challenge":
self.VICTORY_LOCATION = "0x0356B"
- elif victory == 2:
+ elif victory == "mountain_box_short":
self.VICTORY_LOCATION = "0x09F7F"
- elif victory == 3:
+ elif victory == "mountain_box_long":
self.VICTORY_LOCATION = "0xFFF00"
# Long box can usually only be solved by opening Mountain Entry. However, if it requires 7 lasers or less
@@ -338,36 +388,36 @@ def make_options_adjustments(self, world: "WitnessWorld"):
if world.options.shuffle_symbols:
adjustment_linesets_in_order.append(get_symbol_shuffle_list())
- if world.options.EP_difficulty == 0:
+ if world.options.EP_difficulty == "normal":
adjustment_linesets_in_order.append(get_ep_easy())
- elif world.options.EP_difficulty == 1:
+ elif world.options.EP_difficulty == "tedious":
adjustment_linesets_in_order.append(get_ep_no_eclipse())
- if world.options.door_groupings == 1:
- if world.options.shuffle_doors == 1:
+ if world.options.door_groupings == "regional":
+ if world.options.shuffle_doors == "panels":
adjustment_linesets_in_order.append(get_simple_panels())
- elif world.options.shuffle_doors == 2:
+ elif world.options.shuffle_doors == "doors":
adjustment_linesets_in_order.append(get_simple_doors())
- elif world.options.shuffle_doors == 3:
+ elif world.options.shuffle_doors == "mixed":
adjustment_linesets_in_order.append(get_simple_doors())
adjustment_linesets_in_order.append(get_simple_additional_panels())
else:
- if world.options.shuffle_doors == 1:
+ if world.options.shuffle_doors == "panels":
adjustment_linesets_in_order.append(get_complex_door_panels())
adjustment_linesets_in_order.append(get_complex_additional_panels())
- elif world.options.shuffle_doors == 2:
+ elif world.options.shuffle_doors == "doors":
adjustment_linesets_in_order.append(get_complex_doors())
- elif world.options.shuffle_doors == 3:
+ elif world.options.shuffle_doors == "mixed":
adjustment_linesets_in_order.append(get_complex_doors())
adjustment_linesets_in_order.append(get_complex_additional_panels())
if world.options.shuffle_boat:
adjustment_linesets_in_order.append(get_boat())
- if world.options.early_caves == 2:
+ if world.options.early_caves == "starting_inventory":
adjustment_linesets_in_order.append(get_early_caves_start_list())
- if world.options.early_caves == 1 and not doors:
+ if world.options.early_caves == "add_to_pool" and not doors:
adjustment_linesets_in_order.append(get_early_caves_list())
if world.options.elevators_come_to_you:
@@ -387,29 +437,25 @@ def make_options_adjustments(self, world: "WitnessWorld"):
obelisk = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[self.REFERENCE_LOGIC.EP_TO_OBELISK_SIDE[ep_hex]]
obelisk_name = obelisk["checkName"]
ep_name = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[ep_hex]["checkName"]
- self.EVENT_NAMES_BY_HEX[ep_hex] = f"{obelisk_name} - {ep_name}"
+ self.ALWAYS_EVENT_NAMES_BY_HEX[ep_hex] = f"{obelisk_name} - {ep_name}"
else:
adjustment_linesets_in_order.append(["Disabled Locations:"] + get_ep_obelisks()[1:])
- if world.options.shuffle_EPs == 0:
+ if not world.options.shuffle_EPs:
adjustment_linesets_in_order.append(["Irrelevant Locations:"] + get_ep_all_individual()[1:])
- yaml_disabled_eps = []
-
for yaml_disabled_location in self.YAML_DISABLED_LOCATIONS:
if yaml_disabled_location not in self.REFERENCE_LOGIC.ENTITIES_BY_NAME:
continue
loc_obj = self.REFERENCE_LOGIC.ENTITIES_BY_NAME[yaml_disabled_location]
- if loc_obj["entityType"] == "EP" and world.options.shuffle_EPs != 0:
- yaml_disabled_eps.append(loc_obj["entity_hex"])
+ if loc_obj["entityType"] == "EP":
+ self.COMPLETELY_DISABLED_ENTITIES.add(loc_obj["entity_hex"])
- if loc_obj["entityType"] in {"EP", "General", "Vault", "Discard"}:
+ elif loc_obj["entityType"] in {"General", "Vault", "Discard"}:
self.EXCLUDED_LOCATIONS.add(loc_obj["entity_hex"])
- adjustment_linesets_in_order.append(["Disabled Locations:"] + yaml_disabled_eps)
-
for adjustment_lineset in adjustment_linesets_in_order:
current_adjustment_type = None
@@ -459,7 +505,8 @@ def make_dependency_reduced_checklist(self):
for option in connection[1]:
individual_entity_requirements = []
for entity in option:
- if entity in self.EVENT_NAMES_BY_HEX or entity not in self.REFERENCE_LOGIC.ENTITIES_BY_HEX:
+ if (entity in self.ALWAYS_EVENT_NAMES_BY_HEX
+ or entity not in self.REFERENCE_LOGIC.ENTITIES_BY_HEX):
individual_entity_requirements.append(frozenset({frozenset({entity})}))
else:
entity_req = self.reduce_req_within_region(entity)
@@ -476,6 +523,72 @@ def make_dependency_reduced_checklist(self):
self.CONNECTIONS_BY_REGION_NAME[region] = new_connections
+ def solvability_guaranteed(self, entity_hex: str):
+ return not (
+ entity_hex in self.ENTITIES_WITHOUT_ENSURED_SOLVABILITY
+ or entity_hex in self.COMPLETELY_DISABLED_ENTITIES
+ or entity_hex in self.IRRELEVANT_BUT_NOT_DISABLED_ENTITIES
+ )
+
+ def determine_unrequired_entities(self, world: "WitnessWorld"):
+ """Figure out which major items are actually useless in this world's settings"""
+
+ # Gather quick references to relevant options
+ eps_shuffled = world.options.shuffle_EPs
+ come_to_you = world.options.elevators_come_to_you
+ difficulty = world.options.puzzle_randomization
+ discards_shuffled = world.options.shuffle_discarded_panels
+ boat_shuffled = world.options.shuffle_boat
+ symbols_shuffled = world.options.shuffle_symbols
+ disable_non_randomized = world.options.disable_non_randomized_puzzles
+ postgame_included = world.options.shuffle_postgame
+ goal = world.options.victory_condition
+ doors = world.options.shuffle_doors
+ shortbox_req = world.options.mountain_lasers
+ longbox_req = world.options.challenge_lasers
+
+ # Make some helper booleans so it is easier to follow what's going on
+ mountain_upper_is_in_postgame = (
+ goal == "mountain_box_short"
+ or goal == "mountain_box_long" and longbox_req <= shortbox_req
+ )
+ mountain_upper_included = postgame_included or not mountain_upper_is_in_postgame
+ remote_doors = doors >= 2
+ door_panels = doors == "panels" or doors == "mixed"
+
+ # It is easier to think about when these items *are* required, so we make that dict first
+ # If the entity is disabled anyway, we don't need to consider that case
+ is_item_required_dict = {
+ "0x03750": eps_shuffled, # Monastery Garden Entry Door
+ "0x275FA": eps_shuffled, # Boathouse Hook Control
+ "0x17D02": eps_shuffled, # Windmill Turn Control
+ "0x0368A": symbols_shuffled or door_panels, # Quarry Stoneworks Stairs Door
+ "0x3865F": symbols_shuffled or door_panels or eps_shuffled, # Quarry Boathouse 2nd Barrier
+ "0x17CC4": come_to_you or eps_shuffled, # Quarry Elevator Panel
+ "0x17E2B": come_to_you and boat_shuffled or eps_shuffled, # Swamp Long Bridge
+ "0x0CF2A": False, # Jungle Monastery Garden Shortcut
+ "0x17CAA": remote_doors, # Jungle Monastery Garden Shortcut Panel
+ "0x0364E": False, # Monastery Laser Shortcut Door
+ "0x03713": remote_doors, # Monastery Laser Shortcut Panel
+ "0x03313": False, # Orchard Second Gate
+ "0x337FA": remote_doors, # Jungle Bamboo Laser Shortcut Panel
+ "0x3873B": False, # Jungle Bamboo Laser Shortcut Door
+ "0x335AB": False, # Caves Elevator Controls
+ "0x335AC": False, # Caves Elevator Controls
+ "0x3369D": False, # Caves Elevator Controls
+ "0x01BEA": difficulty == "none" and eps_shuffled, # Keep PP2
+ "0x0A0C9": eps_shuffled or discards_shuffled or disable_non_randomized, # Cargo Box Entry Door
+ "0x09EEB": discards_shuffled or mountain_upper_included, # Mountain Floor 2 Elevator Control Panel
+ "0x09EDD": mountain_upper_included, # Mountain Floor 2 Exit Door
+ "0x17CAB": symbols_shuffled or not disable_non_randomized or "0x17CAB" not in self.DOOR_ITEMS_BY_ID,
+ # Jungle Popup Wall Panel
+ }
+
+ # Now, return the keys of the dict entries where the result is False to get unrequired major items
+ self.ENTITIES_WITHOUT_ENSURED_SOLVABILITY |= {
+ item_name for item_name, is_required in is_item_required_dict.items() if not is_required
+ }
+
def make_event_item_pair(self, panel: str):
"""
Makes a pair of an event panel and its event item
@@ -483,21 +596,23 @@ def make_event_item_pair(self, panel: str):
action = " Opened" if self.REFERENCE_LOGIC.ENTITIES_BY_HEX[panel]["entityType"] == "Door" else " Solved"
name = self.REFERENCE_LOGIC.ENTITIES_BY_HEX[panel]["checkName"] + action
- if panel not in self.EVENT_NAMES_BY_HEX:
+ if panel not in self.USED_EVENT_NAMES_BY_HEX:
warning("Panel \"" + name + "\" does not have an associated event name.")
- self.EVENT_NAMES_BY_HEX[panel] = name + " Event"
- pair = (name, self.EVENT_NAMES_BY_HEX[panel])
+ self.USED_EVENT_NAMES_BY_HEX[panel] = name + " Event"
+ pair = (name, self.USED_EVENT_NAMES_BY_HEX[panel])
return pair
def make_event_panel_lists(self):
- self.EVENT_NAMES_BY_HEX[self.VICTORY_LOCATION] = "Victory"
+ self.ALWAYS_EVENT_NAMES_BY_HEX[self.VICTORY_LOCATION] = "Victory"
- for event_hex, event_name in self.EVENT_NAMES_BY_HEX.items():
- if event_hex in self.COMPLETELY_DISABLED_ENTITIES or event_hex in self.IRRELEVANT_BUT_NOT_DISABLED_ENTITIES:
- continue
- self.EVENT_PANELS.add(event_hex)
+ self.USED_EVENT_NAMES_BY_HEX.update(self.ALWAYS_EVENT_NAMES_BY_HEX)
- for panel in self.EVENT_PANELS:
+ self.USED_EVENT_NAMES_BY_HEX = {
+ event_hex: event_name for event_hex, event_name in self.USED_EVENT_NAMES_BY_HEX.items()
+ if self.solvability_guaranteed(event_hex)
+ }
+
+ for panel in self.USED_EVENT_NAMES_BY_HEX:
pair = self.make_event_item_pair(panel)
self.EVENT_ITEM_PAIRS[pair[0]] = pair[1]
@@ -510,6 +625,8 @@ def __init__(self, world: "WitnessWorld", disabled_locations: Set[str], start_in
self.IRRELEVANT_BUT_NOT_DISABLED_ENTITIES = set()
+ self.ENTITIES_WITHOUT_ENSURED_SOLVABILITY = set()
+
self.THEORETICAL_ITEMS = set()
self.THEORETICAL_ITEMS_NO_MULTI = set()
self.MULTI_AMOUNTS = defaultdict(lambda: 1)
@@ -519,13 +636,13 @@ def __init__(self, world: "WitnessWorld", disabled_locations: Set[str], start_in
self.DOOR_ITEMS_BY_ID: Dict[str, List[str]] = {}
self.STARTING_INVENTORY = set()
- self.DIFFICULTY = world.options.puzzle_randomization.value
+ self.DIFFICULTY = world.options.puzzle_randomization
- if self.DIFFICULTY == 0:
+ if self.DIFFICULTY == "sigma_normal":
self.REFERENCE_LOGIC = StaticWitnessLogic.sigma_normal
- elif self.DIFFICULTY == 1:
+ elif self.DIFFICULTY == "sigma_expert":
self.REFERENCE_LOGIC = StaticWitnessLogic.sigma_expert
- elif self.DIFFICULTY == 2:
+ elif self.DIFFICULTY == "none":
self.REFERENCE_LOGIC = StaticWitnessLogic.vanilla
self.CONNECTIONS_BY_REGION_NAME = copy.copy(self.REFERENCE_LOGIC.STATIC_CONNECTIONS_BY_REGION_NAME)
@@ -534,16 +651,14 @@ def __init__(self, world: "WitnessWorld", disabled_locations: Set[str], start_in
# Determining which panels need to be events is a difficult process.
# At the end, we will have EVENT_ITEM_PAIRS for all the necessary ones.
- self.EVENT_PANELS = set()
self.EVENT_ITEM_PAIRS = dict()
- self.DONT_MAKE_EVENTS = set()
self.COMPLETELY_DISABLED_ENTITIES = set()
self.PRECOMPLETED_LOCATIONS = set()
self.EXCLUDED_LOCATIONS = set()
self.ADDED_CHECKS = set()
self.VICTORY_LOCATION = "0x0356B"
- self.EVENT_NAMES_BY_HEX = {
+ self.ALWAYS_EVENT_NAMES_BY_HEX = {
"0x00509": "+1 Laser (Symmetry Laser)",
"0x012FB": "+1 Laser (Desert Laser)",
"0x09F98": "Desert Laser Redirection",
@@ -556,10 +671,14 @@ def __init__(self, world: "WitnessWorld", disabled_locations: Set[str], start_in
"0x0C2B2": "+1 Laser (Bunker Laser)",
"0x00BF6": "+1 Laser (Swamp Laser)",
"0x028A4": "+1 Laser (Treehouse Laser)",
- "0x09F7F": "Mountain Entry",
+ "0x17C34": "Mountain Entry",
"0xFFF00": "Bottom Floor Discard Turns On",
}
+ self.USED_EVENT_NAMES_BY_HEX = {}
+ self.CONDITIONAL_EVENTS = {}
+
self.make_options_adjustments(world)
+ self.determine_unrequired_entities(world)
self.make_dependency_reduced_checklist()
self.make_event_panel_lists()
diff --git a/worlds/witness/presets.py b/worlds/witness/presets.py
index 1fee1a7968b2..3f02de550b15 100644
--- a/worlds/witness/presets.py
+++ b/worlds/witness/presets.py
@@ -32,6 +32,7 @@
"trap_percentage": TrapPercentage.default,
"puzzle_skip_amount": PuzzleSkipAmount.default,
"hint_amount": HintAmount.default,
+ "area_hint_percentage": AreaHintPercentage.default,
"death_link": DeathLink.default,
},
@@ -64,6 +65,7 @@
"trap_percentage": TrapPercentage.default,
"puzzle_skip_amount": 15,
"hint_amount": HintAmount.default,
+ "area_hint_percentage": AreaHintPercentage.default,
"death_link": DeathLink.default,
},
@@ -96,6 +98,7 @@
"trap_percentage": TrapPercentage.default,
"puzzle_skip_amount": 15,
"hint_amount": HintAmount.default,
+ "area_hint_percentage": AreaHintPercentage.default,
"death_link": DeathLink.default,
},
}
diff --git a/worlds/witness/regions.py b/worlds/witness/regions.py
index e09702480515..350017c6943a 100644
--- a/worlds/witness/regions.py
+++ b/worlds/witness/regions.py
@@ -129,19 +129,20 @@ def create_regions(self, world: "WitnessWorld", player_logic: WitnessPlayerLogic
regions_to_check.add(target.name)
reachable_regions.add(target.name)
- final_regions_list = [v for k, v in regions_by_name.items() if k in reachable_regions]
+ self.created_regions = {k: v for k, v in regions_by_name.items() if k in reachable_regions}
- world.multiworld.regions += final_regions_list
+ world.multiworld.regions += self.created_regions.values()
def __init__(self, locat: WitnessPlayerLocations, world: "WitnessWorld"):
- difficulty = world.options.puzzle_randomization.value
+ difficulty = world.options.puzzle_randomization
- if difficulty == 0:
+ if difficulty == "sigma_normal":
self.reference_logic = StaticWitnessLogic.sigma_normal
- elif difficulty == 1:
+ elif difficulty == "sigma_expert":
self.reference_logic = StaticWitnessLogic.sigma_expert
- elif difficulty == 2:
+ elif difficulty == "none":
self.reference_logic = StaticWitnessLogic.vanilla
self.locat = locat
self.created_entrances: Dict[Tuple[str, str], List[Entrance]] = KeyedDefaultDict(lambda _: [])
+ self.created_regions: Dict[str, Region] = dict()
diff --git a/worlds/witness/rules.py b/worlds/witness/rules.py
index 5eded11ad412..8636829a4ef1 100644
--- a/worlds/witness/rules.py
+++ b/worlds/witness/rules.py
@@ -170,7 +170,7 @@ def _has_item(item: str, world: "WitnessWorld", player: int,
return lambda state: _can_do_expert_pp2(state, world)
elif item == "Theater to Tunnels":
return lambda state: _can_do_theater_to_tunnels(state, world)
- if item in player_logic.EVENT_PANELS:
+ if item in player_logic.USED_EVENT_NAMES_BY_HEX:
return _can_solve_panel(item, world, player, player_logic, locat)
prog_item = StaticWitnessLogic.get_parent_progressive_item(item)
diff --git a/worlds/witness/settings/Door_Shuffle/Complex_Door_Panels.txt b/worlds/witness/settings/Door_Shuffle/Complex_Door_Panels.txt
index 472403962065..70223bd74924 100644
--- a/worlds/witness/settings/Door_Shuffle/Complex_Door_Panels.txt
+++ b/worlds/witness/settings/Door_Shuffle/Complex_Door_Panels.txt
@@ -1,7 +1,7 @@
Items:
Glass Factory Entry (Panel)
-Tutorial Outpost Entry (Panel)
-Tutorial Outpost Exit (Panel)
+Outside Tutorial Outpost Entry (Panel)
+Outside Tutorial Outpost Exit (Panel)
Symmetry Island Lower (Panel)
Symmetry Island Upper (Panel)
Desert Light Room Entry (Panel)
diff --git a/worlds/witness/settings/Door_Shuffle/Complex_Doors.txt b/worlds/witness/settings/Door_Shuffle/Complex_Doors.txt
index 2f2b32171079..87ec69f59c81 100644
--- a/worlds/witness/settings/Door_Shuffle/Complex_Doors.txt
+++ b/worlds/witness/settings/Door_Shuffle/Complex_Doors.txt
@@ -58,9 +58,9 @@ Town Tower Third (Door)
Theater Entry (Door)
Theater Exit Left (Door)
Theater Exit Right (Door)
-Jungle Bamboo Laser Shortcut (Door)
+Jungle Laser Shortcut (Door)
Jungle Popup Wall (Door)
-River Monastery Garden Shortcut (Door)
+Jungle Monastery Garden Shortcut (Door)
Bunker Entry (Door)
Bunker Tinted Glass Door
Bunker UV Room Entry (Door)
@@ -85,7 +85,7 @@ Mountain Floor 2 Staircase Near (Door)
Mountain Floor 2 Exit (Door)
Mountain Floor 2 Staircase Far (Door)
Mountain Bottom Floor Giant Puzzle Exit (Door)
-Mountain Bottom Floor Final Room Entry (Door)
+Mountain Bottom Floor Pillars Room Entry (Door)
Mountain Bottom Floor Rock (Door)
Caves Entry (Door)
Caves Pillar Door
@@ -143,8 +143,8 @@ Town Wooden Roof Lower Row 5
Town RGB House Entry Panel
Town Church Entry Panel
Town Maze Panel
-Town Windmill Entry Panel
-Town Sound Room Right
+Windmill Entry Panel
+Town RGB House Sound Room Right
Town Red Rooftop 5
Town Church Lattice
Town Tall Hexagonal
@@ -154,7 +154,7 @@ Theater Exit Left Panel
Theater Exit Right Panel
Jungle Laser Shortcut Panel
Jungle Popup Wall Control
-River Monastery Garden Shortcut Panel
+Jungle Monastery Garden Shortcut Panel
Bunker Entry Panel
Bunker Tinted Glass Door Panel
Bunker Glass Room 3
@@ -186,8 +186,8 @@ Mountain Floor 2 Light Bridge Controller Near
Mountain Floor 2 Light Bridge Controller Far
Mountain Floor 2 Far Row 6
Mountain Bottom Floor Giant Puzzle
-Mountain Bottom Floor Final Room Entry Left
-Mountain Bottom Floor Final Room Entry Right
+Mountain Bottom Floor Pillars Room Entry Left
+Mountain Bottom Floor Pillars Room Entry Right
Mountain Bottom Floor Discard
Mountain Bottom Floor Rock Control
Mountain Bottom Floor Caves Entry Panel
diff --git a/worlds/witness/settings/Door_Shuffle/Simple_Doors.txt b/worlds/witness/settings/Door_Shuffle/Simple_Doors.txt
index 91a7132ec113..2059f43af62c 100644
--- a/worlds/witness/settings/Door_Shuffle/Simple_Doors.txt
+++ b/worlds/witness/settings/Door_Shuffle/Simple_Doors.txt
@@ -76,8 +76,8 @@ Town Wooden Roof Lower Row 5
Town RGB House Entry Panel
Town Church Entry Panel
Town Maze Panel
-Town Windmill Entry Panel
-Town Sound Room Right
+Windmill Entry Panel
+Town RGB House Sound Room Right
Town Red Rooftop 5
Town Church Lattice
Town Tall Hexagonal
@@ -87,7 +87,7 @@ Theater Exit Left Panel
Theater Exit Right Panel
Jungle Laser Shortcut Panel
Jungle Popup Wall Control
-River Monastery Garden Shortcut Panel
+Jungle Monastery Garden Shortcut Panel
Bunker Entry Panel
Bunker Tinted Glass Door Panel
Bunker Glass Room 3
@@ -119,8 +119,8 @@ Mountain Floor 2 Light Bridge Controller Near
Mountain Floor 2 Light Bridge Controller Far
Mountain Floor 2 Far Row 6
Mountain Bottom Floor Giant Puzzle
-Mountain Bottom Floor Final Room Entry Left
-Mountain Bottom Floor Final Room Entry Right
+Mountain Bottom Floor Pillars Room Entry Left
+Mountain Bottom Floor Pillars Room Entry Right
Mountain Bottom Floor Discard
Mountain Bottom Floor Rock Control
Mountain Bottom Floor Caves Entry Panel
diff --git a/worlds/witness/settings/Door_Shuffle/Simple_Panels.txt b/worlds/witness/settings/Door_Shuffle/Simple_Panels.txt
index 42258bca1a47..23501d20d3a7 100644
--- a/worlds/witness/settings/Door_Shuffle/Simple_Panels.txt
+++ b/worlds/witness/settings/Door_Shuffle/Simple_Panels.txt
@@ -1,6 +1,6 @@
Items:
Symmetry Island Panels
-Tutorial Outpost Panels
+Outside Tutorial Outpost Panels
Desert Panels
Quarry Outside Panels
Quarry Stoneworks Panels
diff --git a/worlds/witness/settings/EP_Shuffle/EP_Sides.txt b/worlds/witness/settings/EP_Shuffle/EP_Sides.txt
index 82ab63329500..d561ffdc183f 100644
--- a/worlds/witness/settings/EP_Shuffle/EP_Sides.txt
+++ b/worlds/witness/settings/EP_Shuffle/EP_Sides.txt
@@ -16,12 +16,12 @@ Added Locations:
0xFFE23 (Treehouse Obelisk Side 4)
0xFFE24 (Treehouse Obelisk Side 5)
0xFFE25 (Treehouse Obelisk Side 6)
-0xFFE30 (River Obelisk Side 1)
-0xFFE31 (River Obelisk Side 2)
-0xFFE32 (River Obelisk Side 3)
-0xFFE33 (River Obelisk Side 4)
-0xFFE34 (River Obelisk Side 5)
-0xFFE35 (River Obelisk Side 6)
+0xFFE30 (Mountainside Obelisk Side 1)
+0xFFE31 (Mountainside Obelisk Side 2)
+0xFFE32 (Mountainside Obelisk Side 3)
+0xFFE33 (Mountainside Obelisk Side 4)
+0xFFE34 (Mountainside Obelisk Side 5)
+0xFFE35 (Mountainside Obelisk Side 6)
0xFFE40 (Quarry Obelisk Side 1)
0xFFE41 (Quarry Obelisk Side 2)
0xFFE42 (Quarry Obelisk Side 3)
diff --git a/worlds/witness/settings/Exclusions/Disable_Unrandomized.txt b/worlds/witness/settings/Exclusions/Disable_Unrandomized.txt
index 2419bde06c14..09c366cfaabd 100644
--- a/worlds/witness/settings/Exclusions/Disable_Unrandomized.txt
+++ b/worlds/witness/settings/Exclusions/Disable_Unrandomized.txt
@@ -1,9 +1,9 @@
Event Items:
-Monastery Laser Activation - 0x00A5B,0x17CE7,0x17FA9
-Bunker Laser Activation - 0x00061,0x17D01,0x17C42
-Shadows Laser Activation - 0x00021,0x17D28,0x17C71
-Town Tower 4th Door Opens - 0x17CFB,0x3C12B,0x17CF7
-Jungle Popup Wall Lifts - 0x17FA0,0x17D27,0x17F9B,0x17CAB
+Monastery Laser Activation - 0x17C65 - 0x00A5B,0x17CE7,0x17FA9
+Bunker Laser Activation - 0x0C2B2 - 0x00061,0x17D01,0x17C42
+Shadows Laser Activation - 0x181B3 - 0x00021,0x17D28,0x17C71
+Town Tower 4th Door Opens - 0x2779A - 0x17CFB,0x3C12B,0x17CF7
+Jungle Popup Wall Lifts - 0x1475B - 0x17FA0,0x17D27,0x17F9B,0x17CAB
Requirement Changes:
0x17C65 - 0x00A5B | 0x17CE7 | 0x17FA9
diff --git a/worlds/witness/settings/Exclusions/Vaults.txt b/worlds/witness/settings/Exclusions/Vaults.txt
index f23a13183326..d9e5d28cd694 100644
--- a/worlds/witness/settings/Exclusions/Vaults.txt
+++ b/worlds/witness/settings/Exclusions/Vaults.txt
@@ -8,9 +8,9 @@ Disabled Locations:
0x00AFB (Shipwreck Vault)
0x03535 (Shipwreck Vault Box)
0x17BB4 (Shipwreck Vault Door)
-0x15ADD (River Vault)
-0x03702 (River Vault Box)
-0x15287 (River Vault Door)
+0x15ADD (Jungle Vault)
+0x03702 (Jungle Vault Box)
+0x15287 (Jungle Vault Door)
0x002A6 (Mountainside Vault)
0x03542 (Mountainside Vault Box)
0x00085 (Mountainside Vault Door)
diff --git a/worlds/witness/settings/Postgame/Mountain_Lower.txt b/worlds/witness/settings/Postgame/Mountain_Lower.txt
index 354e3feb82c3..aecddec5adde 100644
--- a/worlds/witness/settings/Postgame/Mountain_Lower.txt
+++ b/worlds/witness/settings/Postgame/Mountain_Lower.txt
@@ -7,9 +7,9 @@ Disabled Locations:
0x09EFF (Giant Puzzle Top Left)
0x09FDA (Giant Puzzle)
0x09F89 (Exit Door)
-0x01983 (Final Room Entry Left)
-0x01987 (Final Room Entry Right)
-0x0C141 (Final Room Entry Door)
+0x01983 (Pillars Room Entry Left)
+0x01987 (Pillars Room Entry Right)
+0x0C141 (Pillars Room Entry Door)
0x0383A (Right Pillar 1)
0x09E56 (Right Pillar 2)
0x09E5A (Right Pillar 3)
diff --git a/worlds/witness/static_logic.py b/worlds/witness/static_logic.py
index 0e8d649af6ff..3efab4915e69 100644
--- a/worlds/witness/static_logic.py
+++ b/worlds/witness/static_logic.py
@@ -56,6 +56,11 @@ def read_logic_file(self, lines):
"""
current_region = dict()
+ current_area = {
+ "name": "Misc",
+ "regions": [],
+ }
+ self.ALL_AREAS_BY_NAME["Misc"] = current_area
for line in lines:
if line == "" or line[0] == "#":
@@ -67,6 +72,16 @@ def read_logic_file(self, lines):
region_name = current_region["name"]
self.ALL_REGIONS_BY_NAME[region_name] = current_region
self.STATIC_CONNECTIONS_BY_REGION_NAME[region_name] = new_region_and_connections[1]
+ current_area["regions"].append(region_name)
+ continue
+
+ if line[0] == "=":
+ area_name = line[2:-2]
+ current_area = {
+ "name": area_name,
+ "regions": [],
+ }
+ self.ALL_AREAS_BY_NAME[area_name] = current_area
continue
line_split = line.split(" - ")
@@ -88,7 +103,8 @@ def read_logic_file(self, lines):
"entity_hex": entity_hex,
"region": None,
"id": None,
- "entityType": location_id
+ "entityType": location_id,
+ "area": current_area,
}
self.ENTITIES_BY_NAME[self.ENTITIES_BY_HEX[entity_hex]["checkName"]] = self.ENTITIES_BY_HEX[entity_hex]
@@ -120,7 +136,6 @@ def read_logic_file(self, lines):
location_type = "Laser"
elif "Obelisk Side" in entity_name:
location_type = "Obelisk Side"
- full_entity_name = entity_name
elif "EP" in entity_name:
location_type = "EP"
else:
@@ -151,7 +166,8 @@ def read_logic_file(self, lines):
"entity_hex": entity_hex,
"region": current_region,
"id": int(location_id),
- "entityType": location_type
+ "entityType": location_type,
+ "area": current_area,
}
self.ENTITY_ID_TO_NAME[entity_hex] = full_entity_name
@@ -167,6 +183,7 @@ def __init__(self, lines=None):
# All regions with a list of panels in them and the connections to other regions, before logic adjustments
self.ALL_REGIONS_BY_NAME = dict()
+ self.ALL_AREAS_BY_NAME = dict()
self.STATIC_CONNECTIONS_BY_REGION_NAME = dict()
self.ENTITIES_BY_HEX = dict()
@@ -188,6 +205,7 @@ class StaticWitnessLogic:
_progressive_lookup: Dict[str, str] = {}
ALL_REGIONS_BY_NAME = dict()
+ ALL_AREAS_BY_NAME = dict()
STATIC_CONNECTIONS_BY_REGION_NAME = dict()
OBELISK_SIDE_ID_TO_EP_HEXES = dict()
@@ -265,6 +283,7 @@ def __init__(self):
self.parse_items()
self.ALL_REGIONS_BY_NAME.update(self.sigma_normal.ALL_REGIONS_BY_NAME)
+ self.ALL_AREAS_BY_NAME.update(self.sigma_normal.ALL_AREAS_BY_NAME)
self.STATIC_CONNECTIONS_BY_REGION_NAME.update(self.sigma_normal.STATIC_CONNECTIONS_BY_REGION_NAME)
self.ENTITIES_BY_HEX.update(self.sigma_normal.ENTITIES_BY_HEX)
@@ -276,3 +295,6 @@ def __init__(self):
self.EP_TO_OBELISK_SIDE.update(self.sigma_normal.EP_TO_OBELISK_SIDE)
self.ENTITY_ID_TO_NAME.update(self.sigma_normal.ENTITY_ID_TO_NAME)
+
+
+StaticWitnessLogic()
diff --git a/worlds/witness/utils.py b/worlds/witness/utils.py
index fbb670fd0877..b1f1b6d83100 100644
--- a/worlds/witness/utils.py
+++ b/worlds/witness/utils.py
@@ -2,6 +2,21 @@
from math import floor
from typing import List, Collection, FrozenSet, Tuple, Dict, Any, Set
from pkgutil import get_data
+from random import random
+
+
+def weighted_sample(world_random: random, population: List, weights: List[float], k: int):
+ positions = range(len(population))
+ indices = []
+ while True:
+ needed = k - len(indices)
+ if not needed:
+ break
+ for i in world_random.choices(positions, weights, k=needed):
+ if weights[i]:
+ weights[i] = 0.0
+ indices.append(i)
+ return [population[i] for i in indices]
def build_weighted_int_list(inputs: Collection[float], total: int) -> List[int]:
diff --git a/worlds/zillion/__init__.py b/worlds/zillion/__init__.py
index d30bef144464..d7e653bb8017 100644
--- a/worlds/zillion/__init__.py
+++ b/worlds/zillion/__init__.py
@@ -25,7 +25,7 @@
from zilliandomizer.logic_components.locations import Location as ZzLocation, Req
from zilliandomizer.options import Chars
-from ..AutoWorld import World, WebWorld
+from worlds.AutoWorld import World, WebWorld
class ZillionSettings(settings.Group):
diff --git a/worlds/zillion/client.py b/worlds/zillion/client.py
index b10507aaf885..1a85b9df25f0 100644
--- a/worlds/zillion/client.py
+++ b/worlds/zillion/client.py
@@ -1,5 +1,7 @@
import asyncio
import base64
+import io
+import pkgutil
import platform
from typing import Any, ClassVar, Coroutine, Dict, List, Optional, Protocol, Tuple, cast
@@ -17,7 +19,7 @@
from zilliandomizer.patch import RescueInfo
from .id_maps import loc_name_to_id, make_id_to_others
-from .config import base_id, zillion_map
+from .config import base_id
class ZillionCommandProcessor(ClientCommandProcessor):
@@ -138,7 +140,9 @@ def run_gui(self) -> None:
from kvui import GameManager
from kivy.core.text import Label as CoreLabel
from kivy.graphics import Ellipse, Color, Rectangle
+ from kivy.graphics.texture import Texture
from kivy.uix.layout import Layout
+ from kivy.uix.image import CoreImage
from kivy.uix.widget import Widget
class ZillionManager(GameManager):
@@ -150,12 +154,21 @@ class ZillionManager(GameManager):
class MapPanel(Widget):
MAP_WIDTH: ClassVar[int] = 281
- _number_textures: List[Any] = []
+ map_background: CoreImage
+ _number_textures: List[Texture] = []
rooms: List[List[int]] = []
def __init__(self, **kwargs: Any) -> None:
super().__init__(**kwargs)
+ FILE_NAME = "empty-zillion-map-row-col-labels-281.png"
+ image_file_data = pkgutil.get_data(__name__, FILE_NAME)
+ if not image_file_data:
+ raise FileNotFoundError(f"{__name__=} {FILE_NAME=}")
+ data = io.BytesIO(image_file_data)
+ self.map_background = CoreImage(data, ext="png")
+ assert self.map_background.texture.size[0] == ZillionManager.MapPanel.MAP_WIDTH
+
self.rooms = [[0 for _ in range(8)] for _ in range(16)]
self._make_numbers()
@@ -176,10 +189,9 @@ def update_map(self, *args: Any) -> None:
with self.canvas:
Color(1, 1, 1, 1)
- Rectangle(source=zillion_map,
+ Rectangle(texture=self.map_background.texture,
pos=self.pos,
- size=(ZillionManager.MapPanel.MAP_WIDTH,
- int(ZillionManager.MapPanel.MAP_WIDTH * 1.456))) # aspect ratio of that image
+ size=self.map_background.texture.size)
for y in range(16):
for x in range(8):
num = self.rooms[15 - y][x]
@@ -194,7 +206,7 @@ def update_map(self, *args: Any) -> None:
def build(self) -> Layout:
container = super().build()
- self.map_widget = ZillionManager.MapPanel(size_hint_x=None, width=0)
+ self.map_widget = ZillionManager.MapPanel(size_hint_x=None, width=ZillionManager.MapPanel.MAP_WIDTH)
self.main_area_container.add_widget(self.map_widget)
return container
diff --git a/worlds/zillion/config.py b/worlds/zillion/config.py
index ca02f9a99f41..e08c4f4278ed 100644
--- a/worlds/zillion/config.py
+++ b/worlds/zillion/config.py
@@ -1,4 +1 @@
-import os
-
base_id = 8675309
-zillion_map = os.path.join(os.path.dirname(__file__), "empty-zillion-map-row-col-labels-281.png")
|